vibeaudio 0.14.0 โ 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -8
- package/package.json +2 -4
- package/src/hooks.js +6 -0
- package/scripts/postinstall.js +0 -50
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# ๐ง VibeAudio
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="docs/og.png?v=2" alt="VibeAudio โ Focus music while your AI codes" width="720">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
3
7
|
[](https://github.com/kiril6/vibeaudio/actions/workflows/test.yml) [](https://www.npmjs.com/package/vibeaudio) [](https://npm-stat.com/charts.html?package=vibeaudio) [](https://npm-stat.com/charts.html?package=vibeaudio)
|
|
4
8
|
|
|
5
9
|
> **Procedural focus music while your AI coding tools think.**
|
|
@@ -31,7 +35,8 @@ It is built as **calm technology**, in the sense of Mark Weiser and John Seely B
|
|
|
31
35
|
|
|
32
36
|
**How it behaves:**
|
|
33
37
|
|
|
34
|
-
* ๐งฎ **Pure synthesis, zero MP3s.** Every note, chord and pad is generated in code โ no audio assets, no npm dependencies, no `node-gyp`.
|
|
38
|
+
* ๐งฎ **Pure synthesis, zero MP3s.** Every note, chord and pad is generated in code โ no audio assets, no npm dependencies, no install scripts, no `node-gyp`.
|
|
39
|
+
* ๐ **Light on battery.** Each loop is rendered once and cached. While music plays, your OS's own audio player does the work. With hooks, nothing of VibeAudio's keeps running between turns.
|
|
35
40
|
* ๐ผ **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.
|
|
36
41
|
* ๐ **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.
|
|
37
42
|
* ๐ **A phrase, not a loop.** Each piece is three bars that rotate through your project's own progressions, so a long turn moves through a ~21-second phrase instead of replaying one 7-second bar for half an hour.
|
|
@@ -48,7 +53,7 @@ It is built as **calm technology**, in the sense of Mark Weiser and John Seely B
|
|
|
48
53
|
* ๐ก๏ธ **A grace window.** Fast commands stay 100% silent โ music starts only past 1.5s (`--grace`).
|
|
49
54
|
* ๐ช **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.
|
|
50
55
|
* ๐งฎ **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.
|
|
51
|
-
* ๐ **Terminal title HUD.** A live ASCII wave and elapsed timer in the window title, where it can't corrupt a full-screen TUI.
|
|
56
|
+
* ๐ **Terminal title HUD.** A live ASCII wave and elapsed timer in the window title (`[ โซ โโ
โโโโโโ lofi (0:24) ]`), where it can't corrupt a full-screen TUI.
|
|
52
57
|
|
|
53
58
|
</details>
|
|
54
59
|
|
|
@@ -351,11 +356,16 @@ Codex asks you to approve the plugin's hooks once, as it does for any hook. Upda
|
|
|
351
356
|
|
|
352
357
|
The plugin needs Node 18+ on your `PATH` (the hooks run `node`). In Claude Code it also adds `/vibeaudio:vibe`, the plugin's spelling of [`/vibe`](#vibe-inside-claude-code). Update with `claude plugin update vibeaudio@vibeaudio`; remove with `claude plugin uninstall vibeaudio@vibeaudio` (music already playing stops on its own within 15 minutes, or run `/vibeaudio:vibe stop` first).
|
|
353
358
|
|
|
359
|
+
<details>
|
|
360
|
+
<summary><b>Plugin details & coexistence with npm</b> โ settings file, coexistence, and limitations</summary>
|
|
361
|
+
|
|
354
362
|
- **Settings are the same file.** `vibe --genre jazz` and the rest work as always, but the `vibe` command comes from `npm i -g vibeaudio`; the plugin alone puts nothing on your `PATH`. Use `/vibeaudio:vibe genre jazz` instead.
|
|
355
363
|
- **Both at once is safe.** If `--install-hooks` has also been run, the plugin's hooks step aside and the installed ones play, so there's no double chime. Remove the installed hooks first if you want the plugin to own it.
|
|
356
364
|
- **Claude Code and Codex.** Cursor, Gemini and the others still use `vibe --install-hooks`. Copilot CLI and Qwen Code can load the same plugin and it carries their events, but neither has been run with it yet.
|
|
357
365
|
- **Not reactive.** `--reactive` is an `--install-hooks` option; the plugin doesn't carry it.
|
|
358
366
|
|
|
367
|
+
</details>
|
|
368
|
+
|
|
359
369
|
### `/vibe` inside Claude Code
|
|
360
370
|
|
|
361
371
|
Installing the Claude Code hooks also adds a `/vibe` command (`~/.claude/commands/vibe.md`, or under `$CLAUDE_CONFIG_DIR`), so you can control the music without leaving the session:
|
|
@@ -418,8 +428,13 @@ Hooks log each finished turn โ which project, how long, how it ended, and how
|
|
|
418
428
|
32s with a chime 1m 50s without (muted or --no-chime)
|
|
419
429
|
```
|
|
420
430
|
|
|
431
|
+
<details>
|
|
432
|
+
<summary><b>How the report metrics & return times are calculated</b></summary>
|
|
433
|
+
|
|
421
434
|
plus where the time went by project and, for windows up to two weeks, by day. "You waited" is the agent's own working time; the time it spent blocked on you is shown separately, because they are different problems. "Back to it" is the other direction: how long a finished turn sat before your next prompt in the same session โ gaps over 30 minutes count as breaks and are left out. Once both sides have five turns, it is split by whether a chime announced the turn, so you can see what the chime is worth to you. The log stays on your machine, never leaves it, is capped at about 1 MB, and `VIBE_NO_HISTORY=1` turns it off. Only hook-driven turns are logged โ a command wrapped as `vibe <command>` is not, since its lifetime isn't the same thing as an agent's working time.
|
|
422
435
|
|
|
436
|
+
</details>
|
|
437
|
+
|
|
423
438
|
### The chime is in the music's key
|
|
424
439
|
|
|
425
440
|
The success chime is the tonic chord of whatever key your genre sits in โ C major for lofi, 8bit, jazz and piano, D minor for synthwave and electronic, D major for zen, A minor for drone โ so it lands as the resolution of the piece rather than a bell over it. `rain`, `ocean` and `random` have no single key to match and keep the original C major chime.
|
|
@@ -428,10 +443,15 @@ The success chime is the tonic chord of whatever key your genre sits in โ C ma
|
|
|
428
443
|
|
|
429
444
|
The worst stretch of agent work is twenty minutes of the same thing failing while the music says all is well. So when **4 of a session's last 8 tool calls fail**, a soft heartbeat โ two low beats about once a second, on the tonic of your genre โ joins the music at the next loop boundary, and leaves once enough calls succeed. With `--notify` on, you also get one banner when the session crosses the line ("api: looks stuck"), not one per failure.
|
|
430
445
|
|
|
446
|
+
<details>
|
|
447
|
+
<summary><b>How the stuck threshold was calibrated (4,497 turns analyzed)</b></summary>
|
|
448
|
+
|
|
431
449
|
The threshold was picked against 4,497 real Claude Code turns (46,032 tool calls). It fires in about 1% of turns, at around minute 3, and those turns typically ran for another 3 minutes โ time you could have spent stepping in. "3 in a row" was rejected because the usual loop has a successful edit between every failing test run. Each new prompt starts with a clean slate, and an interrupt (Esc) never counts as a failure.
|
|
432
450
|
|
|
433
451
|
It needs the agent to report failed tool calls, which **Claude Code, Copilot CLI and Qwen Code** do (`PostToolUseFailure`). Codex, Cursor, Gemini, Grok and Windsurf don't, so for them the music never adds the heartbeat.
|
|
434
452
|
|
|
453
|
+
</details>
|
|
454
|
+
|
|
435
455
|
### Reactive mode (opt-in)
|
|
436
456
|
|
|
437
457
|
```bash
|
|
@@ -482,16 +502,27 @@ Each session is `working`, `stuck` (4 of its last 8 tool calls failed) or `waiti
|
|
|
482
502
|
{"v":1,"at":1791026130000,"event":"waiting","session":"โฆ","project":"/work/api","tool":"Bash","status":"waiting"}
|
|
483
503
|
```
|
|
484
504
|
|
|
485
|
-
Events: `started`, `waiting`, `resumed`, `stuck`, `recovered`, `finished` (with `outcome`: `success` or `failure`), `interrupted`, `ended` (with `reason` when `vibe --stop` or `--uninstall-hooks` ended it). Every line also carries `status`, the machine's state after the event, so a consumer that only cares about the overall state can read that one field.
|
|
505
|
+
Events: `started`, `waiting`, `resumed`, `stuck`, `recovered`, `finished` (with `outcome`: `success` or `failure`), `interrupted`, `ended` (with `reason` when `vibe --stop` or `--uninstall-hooks` ended it). Every line also carries `status`, the machine's state after the event, so a consumer that only cares about the overall state can read that one field.
|
|
486
506
|
|
|
507
|
+
#### ๐ Status Bar & Desktop Integrations
|
|
508
|
+
|
|
509
|
+
**tmux status bar** โ show overall agent state (`waiting`, `stuck`, `working`, or `idle`):
|
|
487
510
|
```bash
|
|
488
|
-
# tmux: show the state in the status bar
|
|
489
511
|
set -g status-right '#(vibe --state | jq -r .status)'
|
|
512
|
+
```
|
|
490
513
|
|
|
491
|
-
|
|
514
|
+
**macOS speech alert** โ announce out loud whenever an agent stops to ask for your input:
|
|
515
|
+
```bash
|
|
492
516
|
vibe --events | jq --unbuffered -r 'select(.event=="waiting") | .project' | while read p; do say "$(basename "$p") needs you"; done
|
|
493
517
|
```
|
|
494
518
|
|
|
519
|
+
**Starship prompt** โ custom indicator in `~/.config/starship.toml`:
|
|
520
|
+
```toml
|
|
521
|
+
[custom.vibe]
|
|
522
|
+
command = "vibe --state | jq -r 'if .status != \"idle\" then \"๐ง \" + .status else \"\" end'"
|
|
523
|
+
when = "command -v vibe >/dev/null"
|
|
524
|
+
```
|
|
525
|
+
|
|
495
526
|
The stream is a local file (`~/.vibeaudio/events.jsonl`, rotated at 256 KB), so it never leaves your machine. It keeps updating while you're muted, because a mute silences sound and a light isn't sound. The `v` field is the format version, and any breaking change will increment it.
|
|
496
527
|
|
|
497
528
|
## ๐ฅ๏ธ Everything Else (Claude Desktop, Antigravityโฆ via MCP)
|
|
@@ -574,7 +605,7 @@ Playback stops automatically if the desktop client disconnects, and caps out aft
|
|
|
574
605
|
|
|
575
606
|
## ๐จ Music Genres
|
|
576
607
|
|
|
577
|
-
VibeAudio includes **
|
|
608
|
+
VibeAudio includes **10 procedural sound styles** synthesized entirely in code:
|
|
578
609
|
|
|
579
610
|
| Genre | Style | Vibe |
|
|
580
611
|
| :--- | :--- | :--- |
|
|
@@ -590,7 +621,8 @@ VibeAudio includes **8 procedural music styles** synthesized entirely in code:
|
|
|
590
621
|
| `ocean` | ๐ **Ocean** | Low surf on a slow swell that rises and drains โ **no melody at all** |
|
|
591
622
|
| `random` | ๐ฒ **Shuffle Mode** | Picks a surprise genre for the run โ **never `drone`, `rain` or `ocean`** |
|
|
592
623
|
|
|
593
|
-
|
|
624
|
+
<details>
|
|
625
|
+
<summary><b>Genre aliases</b> โ you can ask for a genre the way you'd say it (<code>chill</code>, <code>retrowave</code>, <code>bossa</code>, <code>ambient</code>โฆ)</summary>
|
|
594
626
|
|
|
595
627
|
| Canonical | Also accepted |
|
|
596
628
|
| :--- | :--- |
|
|
@@ -607,6 +639,8 @@ VibeAudio includes **8 procedural music styles** synthesized entirely in code:
|
|
|
607
639
|
|
|
608
640
|
Matching is case-insensitive, so `BOSSA` works too.
|
|
609
641
|
|
|
642
|
+
</details>
|
|
643
|
+
|
|
610
644
|
> **If any melody distracts you, use `drone`.** Every other genre plays something โ notes, a progression, a bass line โ and some people can't read while that happens. `drone` holds one low tone under a slow-breathing noise bed and never moves: closer to a fan or rainfall than to music. Tiers add weight rather than movement. If you want the fan-and-rainfall idea literally, `rain` and `ocean` are synthesized the same way (seeded noise, no recorded files): rain adds more droplets as the turn runs longer, ocean adds a second swell and then foam.
|
|
611
645
|
>
|
|
612
646
|
> **`piano` is the gentler version of that idea.** It still plays notes โ two in eight seconds at tier 1 โ but they're single struck tones with silence between them and nothing running underneath. Higher tiers fill the gaps rather than adding a groove. Try it before `drone` if you want *something* there.
|
|
@@ -746,7 +780,9 @@ It shouldn't. The cache key includes a hash of the synth sources, so changing a
|
|
|
746
780
|
vibe --clear-cache
|
|
747
781
|
```
|
|
748
782
|
|
|
749
|
-
|
|
783
|
+
<details>
|
|
784
|
+
<summary><b>Silent when the agent runs on another machine over SSH (socket forwarding)</b></summary>
|
|
785
|
+
|
|
750
786
|
Sound plays on the machine where the agent runs, and a remote server usually has no sound card โ so VibeAudio finds no player and stays quiet. You can hear it locally by forwarding a PulseAudio socket through the SSH connection: the remote `paplay` sends the audio back over SSH to your own speakers.
|
|
751
787
|
|
|
752
788
|
This needs a PulseAudio-compatible sound server on **your local machine**: a Linux desktop (PipeWire and PulseAudio both provide one) or Windows with WSLg. macOS has none built in, so this route doesn't apply there.
|
|
@@ -768,6 +804,8 @@ This needs a PulseAudio-compatible sound server on **your local machine**: a Lin
|
|
|
768
804
|
|
|
769
805
|
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.
|
|
770
806
|
|
|
807
|
+
</details>
|
|
808
|
+
|
|
771
809
|
**Volume flag does nothing**
|
|
772
810
|
Shouldn't happen any more โ where the player can't attenuate (`aplay`, PowerShell), the gain is baked into the audio instead. A `--volume` change reaches music already playing within a second on macOS hooks (it eases there), at the next loop boundary elsewhere, and in the wrapper or MCP on their next run; `vibe --status` shows the volume in effect and what set it.
|
|
773
811
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeaudio",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
4
4
|
"description": "Procedural focus music while your AI coding tools (Claude Code, Codex, Cursor, Grok, Gemini, Copilot) think โ a different arrangement per project.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vibeaudio": "bin/vibeaudio.js",
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "node test/test-synth.js",
|
|
12
12
|
"start": "node bin/vibeaudio.js",
|
|
13
|
-
"version": "node -e \"const fs=require('fs'),v=require('./package.json').version;for(const f of ['.claude-plugin/plugin.json','.codex-plugin/plugin.json']){const j=JSON.parse(fs.readFileSync(f,'utf8'));j.version=v;fs.writeFileSync(f,JSON.stringify(j,null,2)+'\\n')}\" && git add .claude-plugin/plugin.json .codex-plugin/plugin.json"
|
|
14
|
-
"postinstall": "node scripts/postinstall.js"
|
|
13
|
+
"version": "node -e \"const fs=require('fs'),v=require('./package.json').version;for(const f of ['.claude-plugin/plugin.json','.codex-plugin/plugin.json']){const j=JSON.parse(fs.readFileSync(f,'utf8'));j.version=v;fs.writeFileSync(f,JSON.stringify(j,null,2)+'\\n')}\" && git add .claude-plugin/plugin.json .codex-plugin/plugin.json"
|
|
15
14
|
},
|
|
16
15
|
"publishConfig": {
|
|
17
16
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -23,7 +22,6 @@
|
|
|
23
22
|
"files": [
|
|
24
23
|
"bin/",
|
|
25
24
|
"src/",
|
|
26
|
-
"scripts/postinstall.js",
|
|
27
25
|
"README.md",
|
|
28
26
|
"LICENSE"
|
|
29
27
|
],
|
package/src/hooks.js
CHANGED
|
@@ -889,6 +889,12 @@ function runDaemon(genre, volume, { reactive = false, volumeSource = null } = {}
|
|
|
889
889
|
setTimeout(shutdown, MAX_DAEMON_MS);
|
|
890
890
|
|
|
891
891
|
setInterval(() => {
|
|
892
|
+
// The pid file names the one daemon that owns the speakers. Two prompts
|
|
893
|
+
// at once can each find none running and spawn one; the last to write the
|
|
894
|
+
// file wins, and the other must not play over it (#42).
|
|
895
|
+
// ponytail: up to one poll of doubled audio; claim the pid file with an
|
|
896
|
+
// exclusive create before spawning if that ever matters.
|
|
897
|
+
if (readPid() !== process.pid) return shutdown();
|
|
892
898
|
// A volume saved while this plays reaches it now, not at the next prompt.
|
|
893
899
|
if (volumeSource) player.setVolume(volumeSource());
|
|
894
900
|
if (sweep()) return;
|
package/scripts/postinstall.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
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
|
-
// npm 7+ hides lifecycle-script stdout unless the script fails - measured on
|
|
14
|
-
// npm 10, a global install printed only "added 1 package". The controlling
|
|
15
|
-
// terminal is outside npm's pipe, so write there; stdout remains for when
|
|
16
|
-
// there is no terminal (CI, Windows), where npm may or may not show it.
|
|
17
|
-
function say(text) {
|
|
18
|
-
const fs = require("fs");
|
|
19
|
-
try {
|
|
20
|
-
const fd = fs.openSync("/dev/tty", "w");
|
|
21
|
-
fs.writeSync(fd, text + "\n");
|
|
22
|
-
fs.closeSync(fd);
|
|
23
|
-
} catch (e) {
|
|
24
|
-
console.log(text);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
// Local installs are a dependency of something else; their user is not here
|
|
30
|
-
// and not the one who would run `vibe`. CI and Docker set this too.
|
|
31
|
-
// No isTTY check: npm pipes lifecycle output often enough that gating on it
|
|
32
|
-
// would mean the hint never lands for the people who need it. A global
|
|
33
|
-
// install is already the narrow case - a dependency install sets this false.
|
|
34
|
-
if (process.env.npm_config_global === "true") {
|
|
35
|
-
const c = (code, text) => `\x1b[${code}m${text}\x1b[0m`;
|
|
36
|
-
say(`
|
|
37
|
-
${c("1;36", "๐ง VibeAudio installed.")} Start here:
|
|
38
|
-
|
|
39
|
-
${c("1", "vibe")} ${c("90", "guided setup: pick your agent, genre and volume")}
|
|
40
|
-
|
|
41
|
-
Or skip the menu:
|
|
42
|
-
|
|
43
|
-
${c("1", "vibe --install-hooks")} ${c("90", "music follows your agent's thinking โ Claude Code, Codex, Cursorโฆ")}
|
|
44
|
-
${c("1", "vibe npm test")} ${c("90", "wrap any command that exits when it's done")}
|
|
45
|
-
${c("1", "vibe --preview jazz")} ${c("90", "hear a genre first")}
|
|
46
|
-
`);
|
|
47
|
-
}
|
|
48
|
-
} catch (e) {
|
|
49
|
-
// Deliberately silent: see above.
|
|
50
|
-
}
|