vibeaudio 0.5.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
@@ -22,21 +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`).
29
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.
30
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.
31
36
  * ๐ŸŒŠ **Terminal title HUD.** A live ASCII wave and elapsed timer in the window title, where it can't corrupt a full-screen TUI.
32
- * ๐Ÿ”Œ **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>
33
39
 
34
40
  ### ๐ŸŽผ Every project gets its own arrangement
35
41
 
36
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*.
37
43
 
38
- 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.
39
-
40
44
  Pin or explore arrangements when you want to:
41
45
 
42
46
  ```bash
@@ -44,8 +48,15 @@ vibe --seed 42 --preview jazz # audition one specific arrangement
44
48
  export VIBE_SEED=7 # pin the same sound everywhere
45
49
  ```
46
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
+
47
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.
48
57
 
58
+ </details>
59
+
49
60
  ---
50
61
 
51
62
  ## ๐Ÿ“‹ Requirements
@@ -147,6 +158,13 @@ Choose your volume level:
147
158
  4. ๐Ÿ“ข Loud (75%) โ€” Audible across the room
148
159
  ```
149
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
+
150
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:
151
169
 
152
170
  ```
@@ -159,9 +177,11 @@ Should the music react to what the agent is doing?
159
177
  2. Reactive โ€” Intensity follows the tool in use - noticeable, by design
160
178
  ```
161
179
 
162
- 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).
163
181
 
164
- 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>
165
185
 
166
186
  ---
167
187
 
@@ -176,12 +196,17 @@ Once hooks are installed they take over: running `vibe claude` (or `vibe codex`)
176
196
  ```bash
177
197
  vibe --install-hooks # every agent found on this machine
178
198
  vibe --install-hooks --tools codex # or just one of them
179
- 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
180
200
  vibe --install-hooks --dry-run # show what would change, write nothing
181
201
  ```
182
202
 
183
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.
184
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
+
185
210
  Each tool spells its events its own way, and VibeAudio writes whichever dialect the file expects:
186
211
 
187
212
  | | File | Music starts | Music stops + chime | Reactive (opt-in) |
@@ -205,9 +230,11 @@ Where an agent reports more than start and stop, VibeAudio listens for that too
205
230
  | **Qwen Code** | `PermissionRequest` | `PostToolUse`, `PostToolUseFailure` | `StopFailure` | `SessionEnd` |
206
231
  | **Cursor, Grok** | โ€” | โ€” | โ€” | โ€” |
207
232
 
208
- 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>
209
237
 
210
- Six things differ per agent, and none of them need any action from you except the first:
211
238
 
212
239
  | | |
213
240
  | :--- | :--- |
@@ -217,24 +244,32 @@ Six things differ per agent, and none of them need any action from you except th
217
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. |
218
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`. |
219
246
 
247
+ </details>
248
+
220
249
  ### Several sessions at once
221
250
 
222
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:
223
252
 
224
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.
225
- * **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).
226
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).
227
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.
228
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.
229
262
 
230
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.
231
264
 
232
- > **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.
265
+ </details>
233
266
 
234
- > **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.
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.
235
268
 
236
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).)
237
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
+
238
273
  ### `/vibe` inside Claude Code
239
274
 
240
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:
@@ -251,23 +286,32 @@ It asks Claude to run the matching `vibe` command, so Claude Code will ask permi
251
286
 
252
287
  ### Changing the sound later
253
288
 
254
- 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:
255
290
 
256
291
  ```bash
257
- vibe --genre electronic --volume 25 --install-hooks
292
+ vibe --genre electronic --volume 25
258
293
  ```
259
294
 
260
- 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.
296
+
297
+ Audition before you commit: `vibe --preview electronic`.
261
298
 
262
- **The flags compose** โ€” set everything you want in one command, including [reactive mode](#reactive-mode-opt-in):
299
+ A flag still beats an environment variable, which still beats the saved file โ€” so a one-off stays a one-off:
263
300
 
264
301
  ```bash
265
- vibe --genre jazz --volume 25 --reactive --install-hooks
302
+ vibe --genre 8bit npm test # this run only, nothing saved
266
303
  ```
267
304
 
268
- 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
+ ```
269
313
 
270
- > **`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.
271
315
 
272
316
  Removing them is one command:
273
317
 
@@ -293,9 +337,10 @@ Each agent names its tools differently, and all the vocabularies are mapped. **M
293
337
 
294
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.
295
339
 
296
- **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.
297
341
 
298
- **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>
299
344
 
300
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.
301
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.
@@ -303,13 +348,15 @@ Changes land at the next loop boundary, so it shifts musically rather than cutti
303
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.
304
349
  * **Can't run away.** The background player is capped at 15 minutes, so a missed stop event can't leave music looping.
305
350
 
351
+ </details>
352
+
306
353
  ---
307
354
 
308
355
  ## ๐Ÿ–ฅ๏ธ Everything Else (Claude Desktop, Antigravityโ€ฆ via MCP)
309
356
 
310
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.
311
358
 
312
- > **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.**
313
360
 
314
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:
315
362
 
@@ -337,6 +384,11 @@ Where the file lives:
337
384
  | **Claude Desktop** (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
338
385
  | **Antigravity, VS Code, Zed, โ€ฆ** | that app's own MCP settings โ€” same JSON shape |
339
386
 
387
+ Restart the app afterwards.
388
+
389
+ <details>
390
+ <summary><b>Codex's TOML, and older Gemini CLI releases</b></summary>
391
+
340
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:
341
393
 
342
394
  ```toml
@@ -345,15 +397,17 @@ command = "node"
345
397
  args = ["/absolute/path/from/the/command/above", "--mcp"]
346
398
  ```
347
399
 
348
- 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>
349
403
 
350
404
  ### Changing the genre here
351
405
 
352
- `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.
353
407
 
354
- **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.
355
409
 
356
- **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`:
357
411
 
358
412
  ```json
359
413
  "env": { "VIBE_GENRE": "jazz", "VIBE_VOLUME": "25" }
@@ -365,7 +419,7 @@ VIBE_GENRE = "jazz"
365
419
  VIBE_VOLUME = "25"
366
420
  ```
367
421
 
368
- 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.
369
423
 
370
424
  #### Exposed MCP Tools:
371
425
  * `vibe_play`: Start procedural focus music (`genre`: `lofi`, `synthwave`, `8bit`, `electronic`, `jazz`, `zen`, `piano`, `drone`, `random`; `volume`: `5-100`).
@@ -410,28 +464,43 @@ vibe --genre random claude # Surprise vibe each run
410
464
 
411
465
  ### โš™๏ธ Set Your Favorite Genre as Default
412
466
 
413
- 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
+ ```
414
470
 
415
- | You run it via | Change the genre with |
416
- | :--- | :--- |
417
- | **Wrapper** (`vibe <command>`) | `export VIBE_GENRE=jazz` โ€” or `--genre` per run |
418
- | **Agent hooks** (Claude Code, Codex, Cursor, Grok) | re-run `vibe --genre jazz --install-hooks` โ€” [why](#changing-the-sound-later) |
419
- | **MCP** (Gemini CLI, Claude Desktop, โ€ฆ) | ask the assistant, or the config's `env` block โ€” [how](#changing-the-genre-here) |
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.
420
472
 
421
473
  ```bash
422
- export VIBE_GENRE=jazz # or synthwave, 8bit, electronic, zen, piano, drone, random
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
423
478
  ```
424
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:**
483
+
484
+ ```bash
485
+ cd ~/work/api
486
+ vibe --genre zen --here # this directory and everything under it
487
+ ```
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
+
425
493
  ---
426
494
 
427
495
  ## โš™๏ธ Options & Flags
428
496
 
429
497
  | Flag | Description | Default |
430
498
  | :--- | :--- | :--- |
431
- | `-g, --genre <name>` | Music style: `lofi`, `synthwave`, `8bit`, `electronic`, `jazz`, `zen`, `piano`, `drone`, `random` | `lofi` |
432
- | `-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` |
433
501
  | `-cv, --chime-volume <5-100>` | Set independent completion chime volume | `volume ร— 1.1`, kept within 35โ€“65 |
434
502
  | `--grace <ms>` | Silence window before music starts | `1500` |
503
+ | `--here` | With a saved setting: this directory tree only, not everywhere | off |
435
504
  | `--seed <n>` | Force a specific arrangement | derived from the project directory |
436
505
  | `--whisper` | Quick preset: 15% volume (headphones / late night) | โ€” |
437
506
  | `--quiet` | Quick preset: 25% volume (focus / open office) | โ€” |
@@ -454,7 +523,8 @@ export VIBE_GENRE=jazz # or synthwave, 8bit, electronic, zen, piano, drone,
454
523
  | `--version` | Show version | โ€” |
455
524
 
456
525
  ### โš™๏ธ Environment Variables
457
- 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:
458
528
 
459
529
  ```bash
460
530
  export VIBE_GENRE=jazz # lofi, synthwave, 8bit, electronic, jazz, zen, piano, drone, random
@@ -467,7 +537,7 @@ export VIBE_DISABLE=1 # Mute, without uninstalling anything
467
537
 
468
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.
469
539
 
470
- **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.
471
541
 
472
542
  ```bash
473
543
  vibe --mute # silent now, music returns by itself in an hour
@@ -476,11 +546,16 @@ vibe --mute 0 # stay off until I say otherwise
476
546
  vibe --unmute # end it early
477
547
  ```
478
548
 
479
- `--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>
480
553
 
481
- **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.
482
555
 
483
- 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>
484
559
 
485
560
  ---
486
561
 
@@ -522,7 +597,7 @@ This needs a PulseAudio-compatible sound server on **your local machine**: a Lin
522
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.
523
598
 
524
599
  **Volume flag does nothing**
525
- 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.
526
601
 
527
602
  ---
528
603
 
@@ -573,14 +648,17 @@ The last block is detected from your own `PATH`, so it answers "will this work w
573
648
  ```bash
574
649
  vibe --uninstall-hooks # 1. unwire every agent, and stop any player still running
575
650
  npm rm -g vibeaudio # 2. remove the CLI
576
- rm -rf ~/.vibeaudio # 3. optional: cached audio + daemon state
651
+ rm -rf ~/.vibeaudio # 3. optional: cached audio, saved settings, daemon state
577
652
  ```
578
653
 
654
+ <details>
655
+ <summary><b>Why that order, and what stays behind</b></summary>
656
+
579
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"`.
580
658
 
581
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`.
582
660
 
583
- 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.
584
662
 
585
663
  **Two things are deliberately left behind:**
586
664
 
@@ -591,6 +669,8 @@ Step 3 only reclaims disk โ€” the audio cache, pruned to the 3 most recent proje
591
669
 
592
670
  Apart from those two, the three commands above remove everything VibeAudio writes.
593
671
 
672
+ </details>
673
+
594
674
  ---
595
675
 
596
676
  ## ๐Ÿค Contributing
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vibeaudio",
3
- "version": "0.5.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
6
  "vibeaudio": "bin/vibeaudio.js",
7
7
  "vibe": "bin/vibeaudio.js"
@@ -9,7 +9,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
+ }