turnbell 1.0.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.
- package/LICENSE +21 -0
- package/README.md +301 -0
- package/dist/index.js +4673 -0
- package/package.json +67 -0
- package/scripts/helper/play-then-say.mjs +83 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Paurush Rai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# turnbell
|
|
2
|
+
|
|
3
|
+
**turnbell calls out the moment your CLI coding agent finishes or needs you.**
|
|
4
|
+
|
|
5
|
+
> **turnbell was previously named hollr.** Same tool, new name. If you installed
|
|
6
|
+
> `hollr-cli`, see [Migrating from hollr](#migrating-from-hollr) — your existing
|
|
7
|
+
> hooks keep working through a compatibility alias while you switch.
|
|
8
|
+
|
|
9
|
+
Kick off a long agent run, switch to something else, and stop babysitting the
|
|
10
|
+
terminal. The second your agent finishes a turn — or stalls waiting on your
|
|
11
|
+
input — turnbell speaks up: it reads the response aloud, fires a desktop
|
|
12
|
+
notification, plays a sound, and/or pings your phone. You get back to it exactly
|
|
13
|
+
when it needs you — not a minute sooner, not ten minutes later.
|
|
14
|
+
|
|
15
|
+
- **Local-first, zero telemetry.** Voice, notifications, and sound never leave
|
|
16
|
+
your machine. The only thing that ever goes off-device is a webhook you set up
|
|
17
|
+
yourself — and that payload is metadata only (see [Privacy](#privacy)).
|
|
18
|
+
- **Works with every agent.** 8 first-class integrations — Claude Code, Codex,
|
|
19
|
+
Gemini CLI, Copilot CLI, Cursor, opencode, Antigravity, Amp — plus a universal
|
|
20
|
+
wrapper (`turnbell run`) that adds done/error alerts to *any* command.
|
|
21
|
+
- **Nothing invasive in your setup.** turnbell wires each agent's own hook config
|
|
22
|
+
to call `turnbell emit` — no SDK, no background daemon. Every change is previewed
|
|
23
|
+
before it's written, and `turnbell uninstall` surgically removes only what turnbell
|
|
24
|
+
added — edits you make afterward are preserved (see
|
|
25
|
+
[Limitations & caveats](#limitations--caveats)).
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
Requires **Node ≥ 20**.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm i -g turnbell
|
|
33
|
+
turnbell init # interactive setup wizard: pick agents, sounds, webhooks
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or run it without a global install:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx turnbell init
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`turnbell init` detects which agents you have, shows you the exact config diff
|
|
43
|
+
before writing anything, and lets you choose what you hear and when.
|
|
44
|
+
|
|
45
|
+
## Migrating from hollr
|
|
46
|
+
|
|
47
|
+
turnbell is the same tool as **hollr** (npm: `hollr-cli`), renamed. Upgrade:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm rm -g hollr-cli
|
|
51
|
+
npm i -g turnbell
|
|
52
|
+
turnbell init # re-wires hooks to the new name, cleans old hollr entries
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Everything is handled for you:
|
|
56
|
+
|
|
57
|
+
- **Old hooks keep working.** turnbell ships a `hollr` bin alias, so hooks
|
|
58
|
+
already wired into your agents keep firing (with a one-line rename notice on
|
|
59
|
+
stderr) until you re-run `turnbell init`.
|
|
60
|
+
- **Config migrates automatically.** `~/.config/hollr` is moved to
|
|
61
|
+
`~/.config/turnbell` on first run; a `$HOLLR_HOME` override is still honored.
|
|
62
|
+
- **Re-wiring replaces, never duplicates.** `turnbell init` removes old
|
|
63
|
+
`hollr emit …` hook entries, `hollr.*` command/plugin files, and the old
|
|
64
|
+
read-aloud memory block as it writes the new ones — no double notifications.
|
|
65
|
+
- **`turnbell uninstall` also cleans hollr-era wiring**, so a full removal works
|
|
66
|
+
even if you never re-ran init.
|
|
67
|
+
|
|
68
|
+
## Turning it on and off
|
|
69
|
+
|
|
70
|
+
At setup, turnbell asks whether it should notify you **in every project** or
|
|
71
|
+
**only in projects you turn on**. You can change your mind per project or pause
|
|
72
|
+
everything for a while:
|
|
73
|
+
|
|
74
|
+
| Want | Command |
|
|
75
|
+
|------|---------|
|
|
76
|
+
| Turn turnbell on for this project | `turnbell on` |
|
|
77
|
+
| Turn it off for this project | `turnbell off` |
|
|
78
|
+
| Quiet everywhere for a bit | `turnbell quiet` · `turnbell quiet 30m` |
|
|
79
|
+
| Turn quiet back off | `turnbell quiet off` |
|
|
80
|
+
| See what turnbell is doing and why | `turnbell status` |
|
|
81
|
+
|
|
82
|
+
`turnbell quiet 30m` comes back on by itself after 30 minutes; a bare `turnbell quiet`
|
|
83
|
+
stays quiet until you run `turnbell quiet off`.
|
|
84
|
+
|
|
85
|
+
## Agent support
|
|
86
|
+
|
|
87
|
+
Each integration is defined by what the agent's own hooks can actually deliver —
|
|
88
|
+
this table reflects the shipped adapter capabilities, not aspirations.
|
|
89
|
+
|
|
90
|
+
| Agent | done | blocked | read-aloud | slash cmd | Notes |
|
|
91
|
+
|---|:---:|:---:|:---:|:---:|---|
|
|
92
|
+
| **Claude Code** | ✅ | ✅ | ✅ | `/turnbell` | Reference integration; read-aloud from the JSONL transcript. |
|
|
93
|
+
| **Codex** (OpenAI) | ✅ | ✅ | ✅ | — | `notify` for done + read-aloud; `hooks.json` PermissionRequest for blocked (needs one-time trust in Codex). |
|
|
94
|
+
| **Copilot CLI** (GitHub) | ✅ | ✅ | ✅ | — | `agentStop` + notification hooks. |
|
|
95
|
+
| **Gemini CLI** (Google) | ✅ | ✅ | ✅ | `/turnbell` | `AfterAgent` done, `Notification` blocked. |
|
|
96
|
+
| **Antigravity** (agy) | ✅ | — | — | — | Announce-only: no needs-input event, opaque transcript. |
|
|
97
|
+
| **Cursor** (cursor-agent) | ✅ | — | — | — | Native stop-hook announce; blocked + read-aloud come via `turnbell run`. |
|
|
98
|
+
| **opencode** (sst) | ✅ | ✅ | — | — | Plugin bridges `session.idle` (done) + `permission.asked` (blocked); read-aloud off (opaque storage). |
|
|
99
|
+
| **Amp** (Sourcegraph) | ✅ | — | — | — | Announce-only via Amp's built-in notifications + `turnbell run`; not auto-wired. |
|
|
100
|
+
| **`turnbell run`** (wrapper) | ✅ | — | ✅ | — | Universal fallback for *any* command: done/error on exit. Cursor stream read-aloud via `--announce-stream cursor`. |
|
|
101
|
+
|
|
102
|
+
Read-aloud speaks the agent's last response. "blocked" fires when the agent is
|
|
103
|
+
waiting on your input. A dash means the agent's surface doesn't expose that
|
|
104
|
+
signal — not that turnbell chose to omit it.
|
|
105
|
+
|
|
106
|
+
**Platform status:** macOS is stable. **Linux and Windows are beta** — voice,
|
|
107
|
+
desktop notifications, and sound are implemented but not yet
|
|
108
|
+
hardware-end-to-end verified.
|
|
109
|
+
|
|
110
|
+
> Locally verified against a real install: Claude Code. The Codex, Copilot,
|
|
111
|
+
> Gemini, opencode, Amp, and Cursor integrations were built against their
|
|
112
|
+
> published hook/config docs.
|
|
113
|
+
|
|
114
|
+
### Read-aloud "speakable mode"
|
|
115
|
+
|
|
116
|
+
When you pick **read-aloud** for the `done` event during `turnbell init`, turnbell can
|
|
117
|
+
add a small, clearly-marked instruction to each supported agent's global memory
|
|
118
|
+
file (Claude Code `~/.claude/CLAUDE.md`, Codex `~/.codex/AGENTS.md`, Gemini
|
|
119
|
+
`~/.gemini/GEMINI.md`). It asks the model to keep its final message speakable and
|
|
120
|
+
to write code or dense detail to a temp `.md` file it opens with your chosen
|
|
121
|
+
markdown command instead of speaking it.
|
|
122
|
+
|
|
123
|
+
- **Only three agents** support this today — **Claude Code, Codex, Gemini** (the
|
|
124
|
+
ones with a global standing-instructions file). Other agents don't offer it.
|
|
125
|
+
- **Opt-in** — offered only when read-aloud is your `done` mode, only for agents
|
|
126
|
+
you wire.
|
|
127
|
+
- **Reversible** — it's a marked block; re-running `turnbell init` with read-aloud
|
|
128
|
+
off removes just that block and leaves the rest of your file untouched.
|
|
129
|
+
`turnbell uninstall` reverses everything turnbell wired.
|
|
130
|
+
- **Best-effort, not a guarantee** — it's a prompt *nudge*. The model decides
|
|
131
|
+
whether to comply; it may still speak something technical, or over-use files.
|
|
132
|
+
- **turnbell opens the file, your editor renders it** — the model runs your
|
|
133
|
+
configured open command; whether that shows a *rendered* preview or raw source
|
|
134
|
+
is up to that app (VS Code needs a preview command, etc.). turnbell can't force a
|
|
135
|
+
rendered view.
|
|
136
|
+
- **Tidy, within its own directory** — temp files under
|
|
137
|
+
`~/.config/turnbell/readaloud/` are auto-removed after 24h. If the model ignores
|
|
138
|
+
that location and writes elsewhere, turnbell can't clean those up.
|
|
139
|
+
|
|
140
|
+
## Webhooks
|
|
141
|
+
|
|
142
|
+
Get pinged on your phone or a chat channel when an agent finishes. turnbell
|
|
143
|
+
supports four providers:
|
|
144
|
+
|
|
145
|
+
- **ntfy** — push to the ntfy app/self-hosted server.
|
|
146
|
+
- **pushover** — Pushover push notifications.
|
|
147
|
+
- **slack** — Slack incoming webhook.
|
|
148
|
+
- **generic** — raw JSON POST to any endpoint you control.
|
|
149
|
+
|
|
150
|
+
Configure targets in `turnbell init`. Each target has a `name`, `provider`, `url`,
|
|
151
|
+
the `events` it fires on (`done` / `blocked`), and optional auth `headers`.
|
|
152
|
+
`https://` is required; plain `http://` is rejected unless you opt in with
|
|
153
|
+
`allowHttp`. Targets with auth headers cause the global config to be chmod'd to
|
|
154
|
+
`0600`.
|
|
155
|
+
|
|
156
|
+
### The payload is metadata only
|
|
157
|
+
|
|
158
|
+
The webhook sink is the *only* part of turnbell that touches the network, and it
|
|
159
|
+
has a single serializer that emits exactly six fields — and nothing else:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"v": 1,
|
|
164
|
+
"ts": "2026-07-11T18:32:04.512Z",
|
|
165
|
+
"agent": "claude-code",
|
|
166
|
+
"event": "done",
|
|
167
|
+
"project": "my project",
|
|
168
|
+
"summary": "turnbell test"
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
It **never** sends your working directory, your code, or the agent's response.
|
|
173
|
+
`project` is only the basename of the folder (dashes/underscores turned into
|
|
174
|
+
spaces for speaking). `summary` is the short status line the agent supplied.
|
|
175
|
+
Provider bodies (ntfy title, Slack text, etc.) are derived from these same
|
|
176
|
+
fields — there is no other path off the machine.
|
|
177
|
+
|
|
178
|
+
Preview and test before you trust it:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
turnbell test --show-payload # print the exact off-machine payload, send nothing
|
|
182
|
+
turnbell test --webhook # fire your configured webhook targets for real
|
|
183
|
+
turnbell test # drive the local sinks (voice + desktop notify)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Hotkeys — control read-aloud
|
|
187
|
+
|
|
188
|
+
Read-aloud can run long. turnbell exposes `pause` / `resume` / `stop` so you can
|
|
189
|
+
bind them to system hotkeys:
|
|
190
|
+
|
|
191
|
+
- **macOS** — create Shortcuts in Shortcuts.app that run `turnbell pause`,
|
|
192
|
+
`turnbell resume`, and `turnbell stop`, then assign each a keyboard shortcut.
|
|
193
|
+
- **Linux** — bind `turnbell pause` / `turnbell resume` / `turnbell stop` to keys in your
|
|
194
|
+
desktop environment's keyboard settings (GNOME/KDE custom shortcuts).
|
|
195
|
+
- **Windows** — use AutoHotkey to map keys to the commands. **Windows is
|
|
196
|
+
stop-only** — `turnbell stop` works, but there is no pause/resume (no `SIGSTOP`
|
|
197
|
+
equivalent for the speech process).
|
|
198
|
+
|
|
199
|
+
## Command reference
|
|
200
|
+
|
|
201
|
+
| Command | What it does |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `turnbell init` | Interactive setup wizard: detect agents, wire hooks, configure sounds/webhooks. |
|
|
204
|
+
| `turnbell uninstall` | Reverse every wiring turnbell made — surgically removes only turnbell's own entries from shared files (your later edits survive) and deletes files turnbell created. |
|
|
205
|
+
| `turnbell emit` | Internal: agents' hooks call this to report an event (`--payload-stdin` / `--payload-argv`). Never breaks a turn. |
|
|
206
|
+
| `turnbell run -- <cmd>` | Universal wrapper: run any command and announce done/error on exit. `--announce-stream cursor` for Cursor read-aloud. |
|
|
207
|
+
| `turnbell test` | Fire a synthetic event to verify your setup. `--show-payload` / `--webhook`. |
|
|
208
|
+
| `turnbell status` | Explain, in plain words, whether turnbell is speaking here and why — scope, this project's state, and any active quiet. |
|
|
209
|
+
| `turnbell pause` | Pause the current read-aloud. |
|
|
210
|
+
| `turnbell resume` | Resume a paused read-aloud. |
|
|
211
|
+
| `turnbell stop` | Stop the current read-aloud. |
|
|
212
|
+
| `turnbell mute [on\|off]` | Mute/unmute all turnbell output for the current project (toggles if no arg). |
|
|
213
|
+
| `turnbell on` / `turnbell off` | Turn turnbell on or off for the current project (aliases: `unmute` / `mute`). |
|
|
214
|
+
| `turnbell quiet [duration\|off]` | Quiet all projects for a while (`turnbell quiet 30m`) or until `turnbell quiet off`. |
|
|
215
|
+
| `turnbell doctor` | Check prerequisites (voice/notify/sound tools, detected agents) with exact fix commands. |
|
|
216
|
+
| `turnbell --version` | Print the version (`-v`). |
|
|
217
|
+
|
|
218
|
+
turnbell also honors **quiet hours** (voice suppressed on a schedule; webhooks and
|
|
219
|
+
notifications configurable independently).
|
|
220
|
+
|
|
221
|
+
## Limitations & caveats
|
|
222
|
+
|
|
223
|
+
Known boundaries, so nothing surprises you:
|
|
224
|
+
|
|
225
|
+
- **Read-aloud "speakable mode" is a nudge, not a contract.** It works by adding
|
|
226
|
+
an instruction to the agent's memory file (Claude Code / Codex / Gemini only).
|
|
227
|
+
The *model* chooses whether to keep responses speakable and move code to a
|
|
228
|
+
file — turnbell can't enforce it. It opens that file with your command, but
|
|
229
|
+
rendering is your editor's job, and turnbell only auto-cleans temp files kept
|
|
230
|
+
under `~/.config/turnbell/readaloud/` (see [above](#read-aloud-speakable-mode)).
|
|
231
|
+
|
|
232
|
+
- **`turnbell uninstall` is surgical, not a time machine.** It removes turnbell's *own*
|
|
233
|
+
additions from each shared config file using the file's current contents, so
|
|
234
|
+
edits you made after setup are preserved. Consequences:
|
|
235
|
+
- **Codex `notify` is preserved.** Codex allows a single top-level `notify`
|
|
236
|
+
command, so turnbell's setup temporarily *replaces* any `notify` you already
|
|
237
|
+
had — but it archives your original first and restores it on
|
|
238
|
+
`turnbell uninstall`. (If `config.toml` is deleted between setup and uninstall,
|
|
239
|
+
there's nothing to restore into.)
|
|
240
|
+
- **Config files turnbell *created* are left empty, not deleted.** If turnbell had to
|
|
241
|
+
create a shared file (e.g. an agent's `settings.json` that didn't exist),
|
|
242
|
+
uninstall strips turnbell's entries and leaves an empty `{}` rather than
|
|
243
|
+
guessing the file is safe to remove. Files turnbell owns outright (its slash
|
|
244
|
+
command, the opencode plugin) are deleted.
|
|
245
|
+
- **Retired legacy (v0.1.x) hooks stay retired.** Setup permanently migrates
|
|
246
|
+
away from the old Python integration; uninstall does not resurrect it.
|
|
247
|
+
- **A rare write failure is isolated.** If one adapter's file can't be written
|
|
248
|
+
during uninstall (e.g. permissions), turnbell skips it, reports it, and
|
|
249
|
+
continues; re-run `turnbell uninstall` after fixing the cause.
|
|
250
|
+
|
|
251
|
+
- **Claude Code done-alerts wait for background sub-agents.** turnbell holds the
|
|
252
|
+
`done` announcement while Claude Code reports in-flight delegated work
|
|
253
|
+
(sub-agents, workflows, teammates) so you're not pinged mid-run — you're
|
|
254
|
+
alerted once, at the real end. Long-lived `shell`/watcher and `monitor` tasks
|
|
255
|
+
are ignored (they'd otherwise silence every alert). Requires **Claude Code ≥
|
|
256
|
+
2.1.145**; on older versions the alert fires as before.
|
|
257
|
+
|
|
258
|
+
- **Legacy `allowHttp` configs.** http opt-in is now per webhook target. A config
|
|
259
|
+
written before this change keeps its old global behavior until you re-run
|
|
260
|
+
`turnbell init`, which migrates it to per-target flags.
|
|
261
|
+
|
|
262
|
+
- **Codex "blocked" needs one-time trust.** Codex requires you to review and
|
|
263
|
+
trust its command hook once (run `codex` and approve it) before the blocked
|
|
264
|
+
alert fires.
|
|
265
|
+
|
|
266
|
+
- **Linux & Windows are beta**, and **Windows is stop-only** for read-aloud (no
|
|
267
|
+
pause/resume) — see [Agent support](#agent-support) and
|
|
268
|
+
[Hotkeys](#hotkeys--control-read-aloud).
|
|
269
|
+
|
|
270
|
+
## Privacy
|
|
271
|
+
|
|
272
|
+
turnbell is local-first. Voice (`say`/equivalent), desktop notifications, and
|
|
273
|
+
sound all run on your machine with no telemetry and no network. The *only*
|
|
274
|
+
outbound traffic is webhooks you explicitly configure, and those carry the
|
|
275
|
+
six-field metadata payload above — never your cwd, your code, or the agent's
|
|
276
|
+
response. Read-aloud reads the local transcript and speaks it locally; the text
|
|
277
|
+
never leaves the machine.
|
|
278
|
+
|
|
279
|
+
## Development
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
npm test # vitest
|
|
283
|
+
npm run coverage # vitest + v8 coverage (gate: ≥ 80%)
|
|
284
|
+
npm run build # bundle to dist/index.js via tsup
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Feedback & contributing
|
|
288
|
+
|
|
289
|
+
Found a bug, hit a limitation, or have an idea to make turnbell better? Please
|
|
290
|
+
raise it — feedback and contributions are genuinely welcome:
|
|
291
|
+
|
|
292
|
+
- **GitHub Issues** — bug reports and feature requests:
|
|
293
|
+
[github.com/paurushrai/turnbell/issues](https://github.com/paurushrai/turnbell/issues)
|
|
294
|
+
- **Email** — [paurushrai96@gmail.com](mailto:paurushrai96@gmail.com)
|
|
295
|
+
|
|
296
|
+
Built by **Paurush Rai**. Portfolio and other work — and the fastest way to
|
|
297
|
+
reach me directly — at **[www.paurushrai.in](https://www.paurushrai.in)**.
|
|
298
|
+
|
|
299
|
+
## License
|
|
300
|
+
|
|
301
|
+
MIT — see [LICENSE](LICENSE).
|