vibeaudio 0.4.0 โ†’ 0.6.1

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/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  > **๐Ÿ”Š [Listen to every genre โ†’](https://kiril6.github.io/vibeaudio/)** โ€” hear all 8 genres, the tier escalation, and the three chimes, rendered from the real synth.
12
12
 
13
- > **In a hurry?** `npm i -g github:kiril6/vibeaudio`, then `vibe --install-hooks`. Your next prompt has music.
13
+ > **In a hurry?** `npm i -g vibeaudio`, then `vibe --install-hooks`. Your next prompt has music.
14
14
 
15
15
  ---
16
16
 
@@ -22,20 +22,25 @@ AI coding agents take 15โ€“45 seconds to reason, read files and write code. Star
22
22
 
23
23
  * ๐Ÿงฎ **Pure synthesis, zero MP3s.** Every note, chord and pad is generated in code โ€” no audio assets, no npm dependencies, no `node-gyp`.
24
24
  * ๐ŸŽผ **A different arrangement per project.** Your working directory seeds the progression, bass line and melody, so each repo has its own sound and keeps it.
25
- * ๐Ÿ›ก๏ธ **A grace window.** Fast commands stay 100% silent โ€” music starts only past 1.5s (`--grace`).
26
25
  * ๐Ÿ“ˆ **Escalating layers.** Tier 1 (0โ€“15s) gentle intro โ†’ Tier 2 (15โ€“45s) main groove โ†’ Tier 3 (45s+) deep focus. You can hear how deep into the task the agent is.
27
26
  * ๐Ÿ”” **Outcome-aware chimes.** Ascending on success, a soft descending minor chord on failure, and **silence on `Ctrl+C`** โ€” an abort is never reported as done.
28
27
  * โœ‹ **A "your turn" chime.** When Claude Code stops to ask permission (or an MCP server asks for input), the music pauses and a rising two-note chime asks for you; it picks back up once you've answered.
28
+ * ๐Ÿ”Œ **Universal drop-in.** Hooks for **Claude Code, Codex, Cursor, Grok, Gemini CLI, Copilot CLI and Qwen Code**; MCP for **Claude Desktop and Antigravity**; the wrapper (`vibe <command>`) for anything else.
29
+
30
+ <details>
31
+ <summary><b>And the quieter four</b> โ€” silence on fast commands, several sessions at once, exit codes, the HUD</summary>
32
+
33
+ * ๐Ÿ›ก๏ธ **A grace window.** Fast commands stay 100% silent โ€” music starts only past 1.5s (`--grace`).
34
+ * ๐ŸชŸ **Several sessions, one soundtrack.** Run as many terminals of the same agent as you like: the music plays while *any* of them is working, and each finishes with its own chime. One session ending, pausing for a permission dialog or being interrupted never cuts off another that's still going.
29
35
  * ๐Ÿงฎ **Honest exit codes.** Your command's status passes straight through (`130` on `Ctrl+C`), so `vibe claude && next-step` behaves exactly as it would without the wrapper.
30
36
  * ๐ŸŒŠ **Terminal title HUD.** A live ASCII wave and elapsed timer in the window title, where it can't corrupt a full-screen TUI.
31
- * ๐Ÿ”Œ **Universal drop-in.** Hooks for **Claude Code, Codex, Cursor, Grok, Gemini CLI, Copilot CLI and Qwen Code**; MCP for **Claude Desktop and Antigravity**; the wrapper (`vibe <command>`) for anything else.
37
+
38
+ </details>
32
39
 
33
40
  ### ๐ŸŽผ Every project gets its own arrangement
34
41
 
35
42
  VibeAudio seeds the composition from your **project directory**. The repo you're in picks the chord progression, the bass line, the melodic contour, and where the ornaments land โ€” so `~/work/api` and `~/side/game` genuinely sound different, while each one sounds the *same every time you come back to it*.
36
43
 
37
- That's a deliberate choice. Focus music has one job: **be ignorable.** Music that reinvents itself every run keeps pulling your ear back, which is the opposite of what you want while reading an agent's output. Familiar-per-project gives you variety across contexts and predictability within one โ€” by your third session in a repo, its loop has faded into the furniture.
38
-
39
44
  Pin or explore arrangements when you want to:
40
45
 
41
46
  ```bash
@@ -43,8 +48,15 @@ vibe --seed 42 --preview jazz # audition one specific arrangement
43
48
  export VIBE_SEED=7 # pin the same sound everywhere
44
49
  ```
45
50
 
51
+ <details>
52
+ <summary><b>Why the same sound every time, rather than something new โ€” and what "procedural" actually means</b></summary>
53
+
54
+ That's a deliberate choice. Focus music has one job: **be ignorable.** Music that reinvents itself every run keeps pulling your ear back, which is the opposite of what you want while reading an agent's output. Familiar-per-project gives you variety across contexts and predictability within one โ€” by your third session in a repo, its loop has faded into the furniture.
55
+
46
56
  **How "procedural" actually works** โ€” worth being precise, since it shapes what you'll hear. Each genre is **synthesized from code** (oscillators, chord tables, envelopes rendered to PCM), not shipped as audio files. The seed selects among *hand-written, human-checked* variants โ€” three progressions per genre, each diatonic to that genre's key, plus seeded ornament placement โ€” so it never invents harmony and can't wander out of key. The chosen arrangement is rendered once per project, cached under `~/.vibeaudio/cache/`, and looped. Within a session you're hearing a 6โ€“8 second bar repeat that gains layers as you cross the tier boundaries.
47
57
 
58
+ </details>
59
+
48
60
  ---
49
61
 
50
62
  ## ๐Ÿ“‹ Requirements
@@ -69,13 +81,11 @@ If no player is found, VibeAudio prints a one-line notice and runs your command
69
81
  One line. No clone, no build step, no dependencies to resolve:
70
82
 
71
83
  ```bash
72
- npm i -g github:kiril6/vibeaudio
84
+ npm i -g vibeaudio
73
85
  ```
74
86
 
75
87
  That puts `vibe` and `vibeaudio` on your `PATH`. Re-run the same command to update, or see [Uninstall](#-uninstall) to remove it cleanly.
76
88
 
77
- > **Note:** VibeAudio isn't on the npm registry yet, so plain `npx vibeaudio` won't resolve โ€” use the `github:` form above.
78
-
79
89
  ### Then pick how it runs
80
90
 
81
91
  **Using Claude Code, Codex, Cursor or Grok interactively?** Install the hooks โ€” this is the mode that actually tracks thinking:
@@ -100,8 +110,8 @@ vibe claude -p "explain this repo"
100
110
  ### Try it without installing
101
111
 
102
112
  ```bash
103
- npx github:kiril6/vibeaudio --preview jazz # hear a loop right now
104
- npx github:kiril6/vibeaudio sleep 8 # hear the wrapper: music, then the done chime
113
+ npx vibeaudio --preview jazz # hear a loop right now
114
+ npx vibeaudio sleep 8 # hear the wrapper: music, then the done chime
105
115
  ```
106
116
 
107
117
  > **Heard nothing?** Music only starts once the wrapped command has run longer than the 1.5s grace window โ€” that's [the point](#-why), so quick commands stay silent. `sleep 8` is the reliable demo; something like `npm test` is silent if the tests finish fast or the command errors out immediately.
@@ -148,6 +158,13 @@ Choose your volume level:
148
158
  4. ๐Ÿ“ข Loud (75%) โ€” Audible across the room
149
159
  ```
150
160
 
161
+ **Press `p` to hear the highlighted genre** โ€” auditioning is just a keypress, and moving on replaces it.
162
+
163
+ Installed tools sort to the top, and the list is a shortcut rather than a compatibility list: **`vibe` wraps any command at all**, and "Custom command..." takes one you type.
164
+
165
+ <details>
166
+ <summary><b>Picking a hook-capable agent asks two more questions</b></summary>
167
+
151
168
  Pick **Claude Code** or **Codex** and it asks how the music should run โ€” hooks or just this session โ€” and the hooks branch then offers [reactive mode](#reactive-mode-opt-in) too:
152
169
 
153
170
  ```
@@ -160,9 +177,11 @@ Should the music react to what the agent is doing?
160
177
  2. Reactive โ€” Intensity follows the tool in use - noticeable, by design
161
178
  ```
162
179
 
163
- Choosing hooks installs them for the tool you picked, then launches it โ€” the same thing `vibe --genre <g> --volume <n> --reactive --install-hooks` does, without memorising flags. Once they're installed the first question becomes **Just launch it** / **Reconfigure the hooks**, and launching skips the genre and volume prompts, since the hooks own those.
180
+ Choosing hooks installs them for the tool you picked, then launches it โ€” the same thing `vibe --genre <g> --volume <n> --reactive --install-hooks` does, without memorising flags. Once they're installed the first question becomes **Just launch it** / **Reconfigure the hooks**, and launching skips the genre and volume prompts, since your saved default already answers those (`vibe --genre zen` changes it any time).
164
181
 
165
- Installed tools sort to the top. The list is a shortcut, not a compatibility list โ€” **`vibe` wraps any command at all**, and "Custom command..." takes one you type (quoted arguments survive intact). Adding an entry is one line in [`src/interactive.js`](src/interactive.js).
182
+ Adding an entry to the launcher is one line in [`src/interactive.js`](src/interactive.js).
183
+
184
+ </details>
166
185
 
167
186
  ---
168
187
 
@@ -177,12 +196,17 @@ Once hooks are installed they take over: running `vibe claude` (or `vibe codex`)
177
196
  ```bash
178
197
  vibe --install-hooks # every agent found on this machine
179
198
  vibe --install-hooks --tools codex # or just one of them
180
- vibe --genre jazz --volume 25 --install-hooks # pin the genre and volume
199
+ vibe --genre jazz --volume 25 --install-hooks # install, and save these as your default
181
200
  vibe --install-hooks --dry-run # show what would change, write nothing
182
201
  ```
183
202
 
184
203
  **It auto-detects.** With no `--tools`, VibeAudio wires up each supported agent it finds on your machine โ€” one counts as present when its config directory exists or its CLI is on your `PATH`. `--tools claude,codex,cursor,grok,gemini,copilot,qwen` overrides that. Add `--dry-run` to see, per event, what would be added or changed in each file before anything is written.
185
204
 
205
+ That's it โ€” run your agent normally, with no `vibe` prefix.
206
+
207
+ <details>
208
+ <summary><b>Which file and which events, per agent</b> โ€” seven dialects, all written for you</summary>
209
+
186
210
  Each tool spells its events its own way, and VibeAudio writes whichever dialect the file expects:
187
211
 
188
212
  | | File | Music starts | Music stops + chime | Reactive (opt-in) |
@@ -206,9 +230,11 @@ Where an agent reports more than start and stop, VibeAudio listens for that too
206
230
  | **Qwen Code** | `PermissionRequest` | `PostToolUse`, `PostToolUseFailure` | `StopFailure` | `SessionEnd` |
207
231
  | **Cursor, Grok** | โ€” | โ€” | โ€” | โ€” |
208
232
 
209
- That's it โ€” run your agent normally, with no `vibe` prefix.
233
+ </details>
234
+
235
+ <details>
236
+ <summary><b>Six things that differ per agent</b> โ€” only the first one needs anything from you</summary>
210
237
 
211
- Six things differ per agent, and none of them need any action from you except the first:
212
238
 
213
239
  | | |
214
240
  | :--- | :--- |
@@ -218,12 +244,32 @@ Six things differ per agent, and none of them need any action from you except th
218
244
  | **Only Cursor can play the failure chime** | Its stop event reports whether the turn completed, aborted or errored. The others send no verdict, so a turn there always ends on the success chime โ€” VibeAudio won't invent a failure the agent never claimed. |
219
245
  | **Grok and Copilot CLI get a file of their own** | Each reads every `*.json` in its `hooks/` directory, so VibeAudio writes `vibeaudio.json` rather than merging into anyone else's โ€” which makes uninstalling it a delete, and leaves no backup file behind. Copilot's honours `COPILOT_HOME`. |
220
246
 
221
- > **No restart needed, even mid-session โ€” for Claude Code, Codex, Cursor and Grok.** Each re-reads its hook file every time a hook fires, so changes land on your **next prompt**. A daemon already playing keeps its old settings until that prompt replaces it โ€” at most the tail of one turn. Whether an open Gemini CLI, Copilot CLI or Qwen Code session does the same hasn't been checked, so start a new session there to be sure.
247
+ </details>
248
+
249
+ ### Several sessions at once
250
+
251
+ Every session of an agent is tracked separately, by the session id in its hook payload, so two terminals (or a terminal and the desktop app) share one soundtrack instead of fighting over it:
252
+
253
+ * **The music plays while any session is working.** A second prompt doesn't restart it, and it stops only when the last working session finishes.
254
+ * **Every session gets its own chime.** A quick question that finishes while another agent is still busy chimes "done" and the music carries on underneath.
255
+
256
+ <details>
257
+ <summary><b>The rest of how sessions share one stream</b></summary>
258
+
259
+ * **More sessions, more music.** Two sessions working at once plays at least tier 2 and three or more plays tier 3 โ€” the same piece with more layers, arriving at the next loop boundary and easing off as sessions finish. It only ever raises the tier, so it works alongside time escalation and [reactive mode](#reactive-mode-opt-in).
260
+ * **Dialogs and Esc are per session.** A permission dialog in one terminal plays the "your turn" chime but only pauses the music once *every* session is waiting; Esc ends just that session's turn.
261
+ * **Crashed agents can't hold it hostage.** A session that never sent `Stop` is dropped after 15 minutes, the same ceiling the background player stops at.
222
262
 
223
- > **One player is shared.** They all drive the same background player, so if you prompt two agents at once, the last prompt owns the music. One person, one set of speakers โ€” deliberate, not a limitation being worked around.
263
+ Sessions with no id in their payload share a single slot, so they behave as one. VibeAudio keeps one stream: per-session genres or several streams mixed together aren't supported.
264
+
265
+ </details>
266
+
267
+ > **No restart needed, even mid-session โ€” for Claude Code, Codex, Cursor and Grok.** Each re-reads its hook file every time a hook fires, so changes land on your **next prompt**. A daemon already playing keeps its old settings until that prompt replaces it โ€” at most the tail of one turn. Whether an open Gemini CLI, Copilot CLI or Qwen Code session does the same hasn't been checked, so start a new session there to be sure.
224
268
 
225
269
  > **The Claude Code desktop app is covered too**, not just the terminal โ€” both read the same `~/.claude/settings.json`. (The separate **Claude Desktop** chat app is a different product with no hooks; that one needs [MCP](#-everything-else-claude-desktop-antigravity-via-mcp).)
226
270
 
271
+ > **One player is shared.** Prompt two agents at once and the last prompt owns the music. One person, one set of speakers โ€” deliberate, not a limitation being worked around.
272
+
227
273
  ### `/vibe` inside Claude Code
228
274
 
229
275
  Installing the Claude Code hooks also adds a `/vibe` command (`~/.claude/commands/vibe.md`), so you can control the music without leaving the session:
@@ -240,23 +286,32 @@ It asks Claude to run the matching `vibe` command, so Claude Code will ask permi
240
286
 
241
287
  ### Changing the sound later
242
288
 
243
- Re-run the install with the settings you want. It replaces the existing entry rather than adding a second one:
289
+ One command, and it reaches everything:
244
290
 
245
291
  ```bash
246
- vibe --genre electronic --volume 25 --install-hooks
292
+ vibe --genre electronic --volume 25
247
293
  ```
248
294
 
249
- The change applies to your next prompt โ€” no restart. Audition first with `vibe --preview electronic`.
295
+ That saves your default to `~/.vibeaudio/config.json`. **Installed hooks read it on their next prompt** โ€” nothing to reinstall, nothing to restart. The same file is what the wrapper and the MCP server use, so there is one answer to "what genre am I on" rather than three. `vibe --status` shows it.
250
296
 
251
- **The flags compose** โ€” set everything you want in one command, including [reactive mode](#reactive-mode-opt-in):
297
+ Audition before you commit: `vibe --preview electronic`.
298
+
299
+ A flag still beats an environment variable, which still beats the saved file โ€” so a one-off stays a one-off:
252
300
 
253
301
  ```bash
254
- vibe --genre jazz --volume 25 --reactive --install-hooks
302
+ vibe --genre 8bit npm test # this run only, nothing saved
255
303
  ```
256
304
 
257
- Two things to know: the reinstall **replaces** the whole entry, so flags you don't repeat are dropped (leave off `--reactive` and reactive mode goes away). And music already playing keeps the old genre until the next prompt swaps the daemon โ€” `pkill -f "vibeaudio.js --daemon"` cuts it short.
305
+ **Coming from an older install?** Its genre and volume were frozen into the hook entries. The first `vibe --install-hooks` after upgrading carries them over into the config file, so nothing about your music changes โ€” unless you name a new genre or volume, or have already saved one.
306
+
307
+ [Reactive mode](#reactive-mode-opt-in) is the exception, because it isn't a setting the music reads โ€” it decides which hooks exist at all. Turning it on or off means an install:
308
+
309
+ ```bash
310
+ vibe --reactive --install-hooks # on
311
+ vibe --install-hooks # off again
312
+ ```
258
313
 
259
- > **`VIBE_GENRE` / `VIBE_VOLUME` won't change an installed hook.** They're read once, at install time, and written into the hook command โ€” so exporting a new value later does nothing until you reinstall. The same goes for `vibe --genre <name>` on its own: with no command after it that opens the launcher menu, which asks for a genre and uses its own answer. Changing hook music always means re-running `--install-hooks`.
314
+ Music already playing keeps the old genre until the next prompt swaps the daemon โ€” `vibe --stop` cuts it short.
260
315
 
261
316
  Removing them is one command:
262
317
 
@@ -282,9 +337,10 @@ Each agent names its tools differently, and all the vocabularies are mapped. **M
282
337
 
283
338
  Changes land at the next loop boundary, so it shifts musically rather than cutting mid-bar. Without a tool signal it falls back to time-based escalation.
284
339
 
285
- **This is off by default on purpose.** Music that moves every time the agent switches tools is music you *notice* โ€” which is the opposite of what focus audio is for. Try it, but if you catch yourself listening to it instead of reading, reinstall without `--reactive` (which removes the `PreToolUse` hook again).
340
+ **Off by default on purpose** โ€” music that moves every time the agent switches tools is music you *notice*, which is the opposite of what focus audio is for. Try it; if you catch yourself listening instead of reading, reinstall without `--reactive` and the `PreToolUse` hook goes away again.
286
341
 
287
- **What the installer will and won't do to your config:**
342
+ <details>
343
+ <summary><b>What the installer will and won't do to your config</b> โ€” it edits a file you own, so here is the whole of it</summary>
288
344
 
289
345
  * **Merges, never replaces.** Other tools' hooks are left untouched and keep their position in the file โ€” which is what Codex keys its trust records by.
290
346
  * **Backs up first, once.** The first install copies your file alongside as `*.vibeaudio.bak`. Later installs leave it alone โ€” re-copying would overwrite your real pre-VibeAudio config with a copy of VibeAudio's own last install.
@@ -292,13 +348,15 @@ Changes land at the next loop boundary, so it shifts musically rather than cutti
292
348
  * **Refuses rather than clobbers.** Malformed JSON aborts the write; a temporary `npx` checkout is rejected outright, since the hook records an absolute path that npm's cache eviction would later delete.
293
349
  * **Can't run away.** The background player is capped at 15 minutes, so a missed stop event can't leave music looping.
294
350
 
351
+ </details>
352
+
295
353
  ---
296
354
 
297
355
  ## ๐Ÿ–ฅ๏ธ Everything Else (Claude Desktop, Antigravityโ€ฆ via MCP)
298
356
 
299
357
  **Claude Code, [Codex](https://github.com/openai/codex), [Cursor](https://cursor.com/docs/hooks), [Grok](https://docs.x.ai/build/features/hooks), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Copilot CLI](https://docs.github.com/en/copilot/reference/hooks-configuration) and [Qwen Code](https://github.com/QwenLM/qwen-code) have hook systems, and `--install-hooks` writes to all seven** โ€” use [hooks](#-agent-hooks-no-wrapper-needed) there, they're strictly better. This section is for everything else. MCP is the way in: it's plain stdio JSON-RPC, so the setup is identical everywhere and only the config file differs.
300
358
 
301
- > **This is weaker than hooks, by nature.** Hooks fire on an event โ€” the music always starts when you submit a prompt. MCP tools are *model-invoked*: the assistant has to decide to call `vibe_play`, and to remember `vibe_stop` when it's done. The server tells it when to do that (via the MCP `instructions` field), but it's a suggestion, not a guarantee โ€” expect the occasional silent turn, and say "play some focus music while you work on this" if you want it reliably. **On any of the seven agents above, use [hooks](#-agent-hooks-no-wrapper-needed) instead.**
359
+ > **This is weaker than hooks, by nature.** Hooks fire on an event; MCP tools are *model-invoked*, so the assistant has to decide to call `vibe_play` and remember `vibe_stop`. Expect the occasional silent turn โ€” say "play some focus music while you work on this" if you want it reliably. **On any of the seven agents above, use [hooks](#-agent-hooks-no-wrapper-needed) instead.**
302
360
 
303
361
  Use an **absolute path**, not the bare `vibe` command: GUI apps launched from Finder don't inherit your shell's `PATH`, and version managers like `fnm` or `nvm` put `vibe` on a per-shell path that won't resolve. Print yours with:
304
362
 
@@ -326,6 +384,11 @@ Where the file lives:
326
384
  | **Claude Desktop** (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
327
385
  | **Antigravity, VS Code, Zed, โ€ฆ** | that app's own MCP settings โ€” same JSON shape |
328
386
 
387
+ Restart the app afterwards.
388
+
389
+ <details>
390
+ <summary><b>Codex's TOML, and older Gemini CLI releases</b></summary>
391
+
329
392
  Codex uses TOML rather than JSON, in `~/.codex/config.toml` โ€” but it has hooks, so [use those instead](#-agent-hooks-no-wrapper-needed) unless you specifically want model-invoked music:
330
393
 
331
394
  ```toml
@@ -334,15 +397,17 @@ command = "node"
334
397
  args = ["/absolute/path/from/the/command/above", "--mcp"]
335
398
  ```
336
399
 
337
- Restart the app afterwards. Older Gemini CLI releases predate its hook system (0.10.0 has none) โ€” if you're on one and can't upgrade, the same JSON goes in `~/.gemini/settings.json`.
400
+ Older Gemini CLI releases predate its hook system (0.10.0 has none) โ€” if you're on one and can't upgrade, the same JSON goes in `~/.gemini/settings.json`.
401
+
402
+ </details>
338
403
 
339
404
  ### Changing the genre here
340
405
 
341
- `vibe --genre` doesn't apply โ€” the app launches the server, not you. Two ways instead:
406
+ **Set it like anywhere else.** `vibe --genre jazz --volume 25` saves the default the server reads, and it reads it per request โ€” so the next `vibe_play` picks it up with no restart and no config edit, even though the app launched the server and you didn't.
342
407
 
343
- **Just ask.** `vibe_play` takes a genre, so "play some jazz while you work on this" is enough, no config edit and no restart.
408
+ **Or just ask.** `vibe_play` takes a genre, so "play some jazz while you work on this" is enough. A genre the assistant passes wins over the saved default.
344
409
 
345
- **Or set a default in the config's `env` block**, since a GUI app won't inherit `VIBE_GENRE` from your shell. Most clients support `env` alongside `command`/`args`:
410
+ **Or pin one in the config's `env` block**, if you want this client to differ from the rest of your machine โ€” a GUI app won't inherit `VIBE_GENRE` from your shell. Most clients support `env` alongside `command`/`args`:
346
411
 
347
412
  ```json
348
413
  "env": { "VIBE_GENRE": "jazz", "VIBE_VOLUME": "25" }
@@ -354,7 +419,7 @@ VIBE_GENRE = "jazz"
354
419
  VIBE_VOLUME = "25"
355
420
  ```
356
421
 
357
- Restart the app afterwards. A genre the assistant passes to `vibe_play` still wins over this default.
422
+ Restart the app afterwards โ€” this one is read from the environment the server was launched with, so unlike the saved default it can't change under a running client.
358
423
 
359
424
  #### Exposed MCP Tools:
360
425
  * `vibe_play`: Start procedural focus music (`genre`: `lofi`, `synthwave`, `8bit`, `electronic`, `jazz`, `zen`, `piano`, `drone`, `random`; `volume`: `5-100`).
@@ -399,28 +464,43 @@ vibe --genre random claude # Surprise vibe each run
399
464
 
400
465
  ### โš™๏ธ Set Your Favorite Genre as Default
401
466
 
402
- How you change it depends on how you run VibeAudio โ€” **a shell `export` only reaches the wrapper**:
467
+ ```bash
468
+ vibe --genre jazz --volume 25
469
+ ```
470
+
471
+ Saved to `~/.vibeaudio/config.json` and read by all three ways of running VibeAudio โ€” wrapper, agent hooks, MCP. It applies on your next prompt.
403
472
 
404
- | You run it via | Change the genre with |
405
- | :--- | :--- |
406
- | **Wrapper** (`vibe <command>`) | `export VIBE_GENRE=jazz` โ€” or `--genre` per run |
407
- | **Agent hooks** (Claude Code, Codex, Cursor, Grok) | re-run `vibe --genre jazz --install-hooks` โ€” [why](#changing-the-sound-later) |
408
- | **MCP** (Gemini CLI, Claude Desktop, โ€ฆ) | ask the assistant, or the config's `env` block โ€” [how](#changing-the-genre-here) |
473
+ ```bash
474
+ vibe --genre jazz # lofi, synthwave, 8bit, electronic, jazz, zen, piano, drone, random
475
+ vibe --volume 25 # or --whisper / --quiet / --loud
476
+ vibe --chime-volume 70
477
+ vibe --status # what's saved, and what's overriding it
478
+ ```
479
+
480
+ An `export VIBE_GENRE=jazz` still works and takes precedence in that shell โ€” useful for one terminal you want different, not needed for a default any more.
481
+
482
+ **One project that should sound different:**
409
483
 
410
484
  ```bash
411
- export VIBE_GENRE=jazz # or synthwave, 8bit, electronic, zen, piano, drone, random
485
+ cd ~/work/api
486
+ vibe --genre zen --here # this directory and everything under it
412
487
  ```
413
488
 
489
+ Your global default is untouched, and an agent launched from a subdirectory still gets it โ€” the lookup walks up, so `~/work/api/src` finds what you saved at `~/work/api`. `vibe --status` says which one answered (`saved for this project` / `saved` / `default`).
490
+
491
+ The full order, highest first: **a flag** โ†’ **an environment variable** โ†’ **`--here`** โ†’ **your global default** โ†’ `lofi` at 40%.
492
+
414
493
  ---
415
494
 
416
495
  ## โš™๏ธ Options & Flags
417
496
 
418
497
  | Flag | Description | Default |
419
498
  | :--- | :--- | :--- |
420
- | `-g, --genre <name>` | Music style: `lofi`, `synthwave`, `8bit`, `electronic`, `jazz`, `zen`, `piano`, `drone`, `random` | `lofi` |
421
- | `-v, --volume <5-100>` | Set playback volume | `40` |
499
+ | `-g, --genre <name>` | Music style: `lofi`, `synthwave`, `8bit`, `electronic`, `jazz`, `zen`, `piano`, `drone`, `random`. With no command after it, saves your default | `lofi` |
500
+ | `-v, --volume <5-100>` | Set playback volume. With no command after it, saves your default | `40` |
422
501
  | `-cv, --chime-volume <5-100>` | Set independent completion chime volume | `volume ร— 1.1`, kept within 35โ€“65 |
423
502
  | `--grace <ms>` | Silence window before music starts | `1500` |
503
+ | `--here` | With a saved setting: this directory tree only, not everywhere | off |
424
504
  | `--seed <n>` | Force a specific arrangement | derived from the project directory |
425
505
  | `--whisper` | Quick preset: 15% volume (headphones / late night) | โ€” |
426
506
  | `--quiet` | Quick preset: 25% volume (focus / open office) | โ€” |
@@ -443,7 +523,8 @@ export VIBE_GENRE=jazz # or synthwave, 8bit, electronic, zen, piano, drone,
443
523
  | `--version` | Show version | โ€” |
444
524
 
445
525
  ### โš™๏ธ Environment Variables
446
- Set persistent defaults in your `~/.zshrc` or `~/.bashrc`:
526
+
527
+ **Defaults live in `~/.vibeaudio/config.json` now** โ€” `vibe --genre jazz` is the short way to set one. These override it for a single shell, which is what you want for one terminal that should sound different, or for a machine you don't want writing config at all:
447
528
 
448
529
  ```bash
449
530
  export VIBE_GENRE=jazz # lofi, synthwave, 8bit, electronic, jazz, zen, piano, drone, random
@@ -456,7 +537,7 @@ export VIBE_DISABLE=1 # Mute, without uninstalling anything
456
537
 
457
538
  **`VIBE_DISABLE=1` is for a shell you always want quiet** โ€” a CI job, a shared machine, a terminal profile you keep silent. It's read at playback time and covers hooks, wrapper and MCP alike.
458
539
 
459
- **For a call that's ringing right now, use `vibe --mute` instead.** An environment variable can't help there: a hook runs as a child of your agent and inherits the environment the agent had *when it launched*, so exporting `VIBE_DISABLE` in another terminal reaches nothing already running โ€” and restarting your agent is exactly what you can't do mid-call.
540
+ **For a call that's ringing right now, use `vibe --mute` instead** โ€” an exported variable can't reach a hook that's already running.
460
541
 
461
542
  ```bash
462
543
  vibe --mute # silent now, music returns by itself in an hour
@@ -465,11 +546,16 @@ vibe --mute 0 # stay off until I say otherwise
465
546
  vibe --unmute # end it early
466
547
  ```
467
548
 
468
- `--mute` writes a flag file, which crosses process boundaries where a variable cannot, and stops whatever is playing on the spot. Your hooks and settings are untouched, so there's nothing to put back afterwards.
549
+ It stops whatever is playing on the spot, leaves your hooks and settings untouched, and **expires after an hour** so you can't forget it. `--preview` still plays either way โ€” that one is an explicit request to hear something.
550
+
551
+ <details>
552
+ <summary><b>Why a file, and why it expires</b></summary>
469
553
 
470
- **It expires after an hour by default, and that's deliberate.** A call is a bounded thing; a mute you forget about is worse than no mute, because the tool just stops working and nothing ever tells you why. Expiring means the worst case is "music came back sooner than I wanted" rather than a week of silence you never diagnose. `--mute 0` opts into indefinite explicitly, and `vibe --status` always shows how much of the window is left.
554
+ A hook runs as a child of your agent and inherits the environment the agent had *when it launched*, so exporting `VIBE_DISABLE` in another terminal reaches nothing already running โ€” and restarting your agent is exactly what you can't do mid-call. `--mute` writes a flag file, which crosses that boundary where a variable cannot.
471
555
 
472
- Either way, `--preview` still plays: that one is an explicit request to hear something.
556
+ The expiry is deliberate too. A call is a bounded thing; a mute you forget about is worse than no mute, because the tool just stops working and nothing ever tells you why. Expiring means the worst case is "music came back sooner than I wanted" rather than a week of silence you never diagnose. `--mute 0` opts into indefinite explicitly, and `vibe --status` always shows how much of the window is left.
557
+
558
+ </details>
473
559
 
474
560
  ---
475
561
 
@@ -511,7 +597,7 @@ This needs a PulseAudio-compatible sound server on **your local machine**: a Lin
511
597
  If the second connection fails with the socket "already in use", an earlier session left it behind: `rm /tmp/vibe-pulse.sock` on the server, or set `StreamLocalBindUnlink yes` in the server's `sshd_config`. If `paplay` says access denied, your local PulseAudio requires its cookie โ€” copy `~/.config/pulse/cookie` to the same path on the server.
512
598
 
513
599
  **Volume flag does nothing**
514
- Shouldn't happen any more โ€” where the player can't attenuate (`aplay`, PowerShell), the gain is baked into the audio instead. If you changed `--volume` and hear no difference, you're most likely on hooks, which ignore the wrapper's flags: re-run `vibe --volume 25 --install-hooks`.
600
+ Shouldn't happen any more โ€” where the player can't attenuate (`aplay`, PowerShell), the gain is baked into the audio instead. A `--volume` change lands at the next loop boundary, and on hooks at your next prompt; `vibe --status` shows the volume in effect and what set it.
515
601
 
516
602
  ---
517
603
 
@@ -562,14 +648,17 @@ The last block is detected from your own `PATH`, so it answers "will this work w
562
648
  ```bash
563
649
  vibe --uninstall-hooks # 1. unwire every agent, and stop any player still running
564
650
  npm rm -g vibeaudio # 2. remove the CLI
565
- rm -rf ~/.vibeaudio # 3. optional: cached audio + daemon state
651
+ rm -rf ~/.vibeaudio # 3. optional: cached audio, saved settings, daemon state
566
652
  ```
567
653
 
654
+ <details>
655
+ <summary><b>Why that order, and what stays behind</b></summary>
656
+
568
657
  Step 1 also **stops a background player that's still going**. That matters: once the hooks are gone nothing will ever send the `Stop` event, and after step 2 there's no `vibe` left to stop it with โ€” music would simply play on until its 15-minute cap. If you ever need to do it by hand: `pkill -f "vibeaudio.js --daemon"`.
569
658
 
570
659
  > **Order matters.** `npm rm -g` deletes the binary but not your hook config. Removing the package first strands hook entries that point at a path that no longer exists, and your agent will run a failing hook on every prompt. If you already did it in the wrong order, reinstall, run `vibe --uninstall-hooks`, then remove again โ€” or delete the `vibeaudio` entries from `~/.claude/settings.json`, `~/.codex/hooks.json`, `~/.cursor/hooks.json`, `~/.gemini/settings.json` and `~/.qwen/settings.json` by hand, and delete `~/.grok/hooks/vibeaudio.json`, `~/.copilot/hooks/vibeaudio.json` and `~/.claude/commands/vibe.md`.
571
660
 
572
- Step 3 only reclaims disk โ€” the audio cache, pruned to the 3 most recent projects โ€” and it's regenerated on next use, so skip it if you're reinstalling.
661
+ Step 3 reclaims disk โ€” the audio cache, pruned to the 3 most recent projects โ€” and drops your saved genre and volume. Both come back on their own, so skip it if you're reinstalling.
573
662
 
574
663
  **Two things are deliberately left behind:**
575
664
 
@@ -580,6 +669,8 @@ Step 3 only reclaims disk โ€” the audio cache, pruned to the 3 most recent proje
580
669
 
581
670
  Apart from those two, the three commands above remove everything VibeAudio writes.
582
671
 
672
+ </details>
673
+
583
674
  ---
584
675
 
585
676
  ## ๐Ÿค Contributing
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "vibeaudio",
3
- "version": "0.4.0",
4
- "description": "Procedural focus music while your AI coding tools (Claude Code, Codex, Cursor, Grok, Gemini, Copilot) think โ€” a different arrangement per project.",
3
+ "version": "0.6.1",
4
+ "description": "Procedural focus music while your AI coding tools (Claude Code, Codex, Cursor, Grok, Gemini, Copilot) think \u2014 a different arrangement per project.",
5
5
  "bin": {
6
- "vibeaudio": "./bin/vibeaudio.js",
7
- "vibe": "./bin/vibeaudio.js"
6
+ "vibeaudio": "bin/vibeaudio.js",
7
+ "vibe": "bin/vibeaudio.js"
8
8
  },
9
9
  "main": "src/cli.js",
10
10
  "scripts": {
11
11
  "test": "node test/test-synth.js",
12
- "start": "node bin/vibeaudio.js"
12
+ "start": "node bin/vibeaudio.js",
13
+ "postinstall": "node scripts/postinstall.js"
13
14
  },
14
15
  "publishConfig": {
15
16
  "registry": "https://registry.npmjs.org/",
@@ -21,6 +22,7 @@
21
22
  "files": [
22
23
  "bin/",
23
24
  "src/",
25
+ "scripts/postinstall.js",
24
26
  "README.md",
25
27
  "LICENSE"
26
28
  ],
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The one line an install owes the user.
3
+ *
4
+ * `npm i -g vibeaudio` used to end in silence: nothing plays until hooks are
5
+ * installed or a command is wrapped, so someone who installed it and then went
6
+ * back to work heard nothing and concluded it was broken. This says what to
7
+ * run next, once, at the only moment the user is definitely looking.
8
+ *
9
+ * It must never fail an install. Anything thrown here is npm's problem to
10
+ * report and the user's to wonder about, so everything is wrapped and the exit
11
+ * code is always 0 - a greeting is not worth a failed install.
12
+ */
13
+ try {
14
+ // Local installs are a dependency of something else; their user is not here
15
+ // and not the one who would run `vibe`. CI and Docker set this too.
16
+ // No isTTY check: npm pipes lifecycle output often enough that gating on it
17
+ // would mean the hint never lands for the people who need it. A global
18
+ // install is already the narrow case - a dependency install sets this false.
19
+ if (process.env.npm_config_global === "true") {
20
+ const c = (code, text) => `\x1b[${code}m${text}\x1b[0m`;
21
+ console.log(`
22
+ ${c("1;36", "๐ŸŽง VibeAudio installed.")} Two ways to start:
23
+
24
+ ${c("1", "vibe --install-hooks")} ${c("90", "music follows your agent's thinking โ€” best for Claude Code, Codex, Cursorโ€ฆ")}
25
+ ${c("1", "vibe npm test")} ${c("90", "wrap any command that exits when it's done")}
26
+
27
+ ${c("90", "vibe to pick a tool and a sound from a menu")}
28
+ ${c("90", "vibe --preview jazz to hear one first")}
29
+ `);
30
+ }
31
+ } catch (e) {
32
+ // Deliberately silent: see above.
33
+ }