vibeaudio 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -147,7 +147,7 @@ Want the music to follow what the agent is doing — calm while it reads, busies
147
147
  vibe --reactive --install-hooks
148
148
  ```
149
149
 
150
- Prefer a guided setup? Run `vibe` with nothing after it: a menu asks for your agent, genre and volume (press `p` to hear a genre first) and installs the hooks for you.
150
+ Prefer a guided setup? Run `vibe` with nothing after it: a menu asks for your agent, genre and volume (press `p` to hear a genre first) and installs the hooks for you. Installing hooks doesn't start the agent: open it whenever you like, and the music plays from your first prompt. ("Just launch it" and "This session only" do start it.)
151
151
 
152
152
  Now run your agent normally, with no prefix. Music starts when you submit a prompt and stops with a chime when the agent finishes. [Details below.](#-agent-hooks-no-wrapper-needed)
153
153
 
@@ -522,7 +522,7 @@ Each session is `working`, `stuck` (4 of its last 8 tool calls failed) or `waiti
522
522
  {"v":1,"at":1791026130000,"event":"waiting","session":"…","project":"/work/api","tool":"Bash","status":"waiting"}
523
523
  ```
524
524
 
525
- 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.
525
+ Events: `started`, `waiting`, `resumed`, `stuck`, `recovered`, `finished` (with `outcome`: `success` or `failure`), `interrupted`, `ended` (with `reason` when `vibe --stop` or `--uninstall-hooks` ended it) and `announced` (from [`vibe --announce`](#-beyond-agents-vibe---announce), with `text` and `outcome`, and no session). 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.
526
526
 
527
527
  #### 📟 Status Bar & Desktop Integrations
528
528
 
@@ -563,11 +563,18 @@ The parentheses matter: Starship hides a group whose variables are empty, so the
563
563
  vibe --events | jq --unbuffered -c 'select(.event=="waiting" or .event=="stuck") | {event, project}' \
564
564
  | while read -r line; do curl -s -X POST -H 'Content-Type: application/json' -d "$line" https://example.com/hook; done
565
565
  ```
566
- The `select` also drops the snapshot `--events` prints first (it has no `event` field), so only changes after you start it are sent: a session already waiting at that moment is not announced. For the current state, call `vibe --state` once.
566
+ The `select` also drops the snapshot `--events` prints first (its `event` is `state`), so only changes after you start it are sent: a session already waiting at that moment is not announced. For the current state, call `vibe --state` once.
567
+
568
+ **Home Assistant (or any smart light)** — send the machine's overall state on every change, and let the receiving side choose the colour. Home Assistant takes this as a [webhook trigger](https://www.home-assistant.io/docs/automation/trigger/#webhook-trigger); replace the URL and the id:
569
+ ```bash
570
+ vibe --events | jq --unbuffered -c 'select(.event != "state") | {status, event, project}' \
571
+ | while read -r line; do curl -s -X POST -H 'Content-Type: application/json' -d "$line" http://homeassistant.local:8123/api/webhook/vibeaudio; done
572
+ ```
573
+ `status` is `idle`, `working`, `stuck` or `waiting`, already the most urgent across all your sessions, so the automation is a plain switch on one field (for example amber for `waiting`, red for `stuck`, off for `idle`) and never has to count sessions itself. A web request goes out on every change, so point it at a local bridge rather than a metered service.
567
574
 
568
575
  *Not covered yet: a macOS menu bar plugin (SwiftBar/xbar) and Linux desktop alerts. Neither has been run on a real setup, so neither is documented. `--notify` already raises a banner when a turn finishes or needs you.*
569
576
 
570
- *Verified on macOS arm64 with Node 24, tmux 3.7 and Starship 1.26: the tmux and Starship segments showed `working`, `working ×2` and `waiting: web` and disappeared when idle; the `say` and webhook pipelines fired once per `waiting` event while the stream stayed open. Not run on Linux or Windows.*
577
+ *Verified on macOS arm64 with Node 24, tmux 3.7 and Starship 1.26 (the Home Assistant recipe: the request it sends, against a local stand-in server, was `{"status":"waiting","event":"waiting","project":"/work/api"}` on a permission dialog; not run against Home Assistant itself): the tmux and Starship segments showed `working`, `working ×2` and `waiting: web` and disappeared when idle; the `say` and webhook pipelines fired once per `waiting` event while the stream stayed open. Not run on Linux or Windows.*
571
578
 
572
579
  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.
573
580
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeaudio",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
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",
package/src/cli.js CHANGED
@@ -102,7 +102,7 @@ Procedural focus music while your AI coding tools think.
102
102
  --dry-run With --install-hooks: show what would change, write nothing
103
103
  --uninstall-hooks Remove the hooks again, from every agent
104
104
  -h, --help Show this help message
105
- --version Show version
105
+ --version Show version (update with: npm i -g vibeaudio)
106
106
 
107
107
  \x1b[1mSETTINGS:\x1b[0m
108
108
  A genre or volume with no command after it is saved as your default:
@@ -115,15 +115,43 @@ Procedural focus music while your AI coding tools think.
115
115
  \x1b[1m--here\x1b[0m setting beats the global one, so a one-off
116
116
  \x1b[1mvibe --genre 8bit npm test\x1b[0m stays a one-off.
117
117
 
118
+ \x1b[1mINTEGRATIONS:\x1b[0m
119
+ Every agent's state, as one stream you can build on (lights, banners, webhooks):
120
+
121
+ vibe --events \x1b[90m# the current state, then one JSON line per change\x1b[0m
122
+ vibe --state \x1b[90m# one snapshot: idle, working, stuck or waiting\x1b[0m
123
+ vibe --statusline \x1b[90m# one plain line for tmux or a prompt\x1b[0m
124
+
125
+ Events: started, waiting, resumed, stuck, recovered, finished, interrupted, ended, announced.
126
+ Recipes (tmux, Starship, voice, webhook, Home Assistant): https://kiril6.github.io/vibeaudio/
127
+
128
+ \x1b[1mUNINSTALL:\x1b[0m
129
+ In this order, so no agent is left running a hook that points at nothing:
130
+
131
+ vibe --uninstall-hooks \x1b[90m# 1. unwire every agent, and stop any player still running\x1b[0m
132
+ npm rm -g vibeaudio \x1b[90m# 2. remove the CLI\x1b[0m
133
+ rm -rf ~/.vibeaudio \x1b[90m# 3. optional: cached audio, saved settings, state\x1b[0m
134
+
118
135
  \x1b[1mENVIRONMENT:\x1b[0m
119
136
  VIBE_GENRE=<name> Override the saved genre for this shell
120
137
  VIBE_VOLUME=<5-100> Override the saved volume for this shell
121
138
  VIBE_CHIME_VOLUME=<5-100> Override the saved chime volume for this shell
122
139
  VIBE_GRACE_MS=<ms> Override the saved grace window, in ms
123
140
  VIBE_MUSIC=<on|off> Override the saved music setting for this shell
141
+ VIBE_NOTIFY=<on|off> Override the saved desktop-banner setting for this shell
124
142
  VIBE_SEED=<n> Pin the arrangement instead of deriving it from the directory
125
143
  VIBE_DISABLE=1 Mute automatic playback without uninstalling anything
126
144
  VIBE_NO_UPDATE_CHECK=1 Never check npm for a newer version
145
+
146
+ \x1b[1mNO SOUND?\x1b[0m
147
+ vibe --doctor \x1b[90m# checks the setup; each problem comes with its fix\x1b[0m
148
+ vibe --status \x1b[90m# shows whether you are muted, and what is installed\x1b[0m
149
+ vibe --unmute \x1b[90m# clears a mute\x1b[0m
150
+
151
+ \x1b[1mMORE:\x1b[0m
152
+ Settings and state: ~/.vibeaudio
153
+ Docs: https://kiril6.github.io/vibeaudio/
154
+ Problems and ideas: https://github.com/kiril6/vibeaudio/issues
127
155
  `);
128
156
  printUpdateNotice();
129
157
  }
@@ -717,6 +745,7 @@ function installHookTargets(ids, genre, volume, reactive, dryRun = false, typed
717
745
  const live = ids.filter((id) => hooks.TARGETS[id].liveReload !== false).map((id) => hooks.TARGETS[id].name);
718
746
  if (live.length) console.log(` ${live.join(", ")}: takes effect on your next prompt - no restart needed.`);
719
747
  console.log(` \x1b[90mChange the sound any time with: vibe --genre <name> --volume <n> — no reinstall.\x1b[0m`);
748
+ console.log(` \x1b[90mCheck it with vibe --status · quiet for a call: vibe --mute · everything else: vibe --help\x1b[0m`);
720
749
  if (ids.length > 1) {
721
750
  console.log(` \x1b[90mOne player is shared: whichever agent you prompt last owns the music.\x1b[0m`);
722
751
  }
@@ -1659,6 +1688,42 @@ function executeCommand(cmdArgs, genre, volume, chimeVolume, grace = DEFAULT_GRA
1659
1688
  process.on("SIGTERM", () => relaySignal("SIGTERM"));
1660
1689
  }
1661
1690
 
1691
+ /**
1692
+ * Acts on what the menu returned, and says whether to launch the tool.
1693
+ *
1694
+ * Choosing hooks is setup: it installs them and stops. Nothing is started, so
1695
+ * someone running several agents can set one up now and open it when they
1696
+ * like - the music plays from their first prompt either way. Only "just launch
1697
+ * it" and "this session only" go on to start the tool. A failed install is an
1698
+ * error with a non-zero exit, not a launch without the hooks that were asked for.
1699
+ */
1700
+ function applyMenuSelection(selection, volume) {
1701
+ if (!selection.installHooks) return true;
1702
+ const hooks = require("./hooks");
1703
+ try {
1704
+ installHookTargets([selection.hookTarget], selection.genre, volume, selection.reactive, false,
1705
+ { genre: selection.genre, volume });
1706
+ } catch (err) {
1707
+ console.error(`\x1b[31m[vibeaudio] ${err.message}\x1b[0m`);
1708
+ process.exit(1);
1709
+ }
1710
+ const saved = loadConfig();
1711
+ // Everything VibeAudio is hooked into on this machine, not just this run's
1712
+ // pick: with several agents, "which ones are set up?" is the question, and
1713
+ // which one to open first is the user's call.
1714
+ const hooked = Object.entries(hooks.TARGETS).filter(([id]) => hooks.userHooksInstalled(null, id));
1715
+ const names = hooked.map(([, t]) => t.name).join(", ");
1716
+ const cmds = hooked.map(([, t]) => t.cmd).join(", ");
1717
+ console.log(
1718
+ `\n\x1b[32m✔ VibeAudio v${pkg.version} is hooked into: ${names} (${saved.genre} @ ${saved.volume}%).\x1b[0m ` +
1719
+ `Nothing was started: open ${hooked.length > 1 ? "any of them" : "it"} whenever you like (\x1b[1m${cmds}\x1b[0m), and the music plays from your first prompt.\n` +
1720
+ ` \x1b[90mMore agents? Run vibe again for each, or vibe --install-hooks to set up every one it finds.\x1b[0m\n` +
1721
+ ` \x1b[90mStay current: run npm i -g ${pkg.name} now and then (hooks keep working); vibe --status tells you when a newer version is out.\x1b[0m\n`
1722
+ );
1723
+ printUpdateNotice();
1724
+ return false;
1725
+ }
1726
+
1662
1727
  async function run() {
1663
1728
  const {
1664
1729
  genre,
@@ -1875,18 +1940,7 @@ async function run() {
1875
1940
  }
1876
1941
 
1877
1942
  const chosenVol = selection.volume !== undefined ? selection.volume : volume;
1878
-
1879
- if (selection.installHooks) {
1880
- // The install can refuse (npx checkout) or abort (malformed settings).
1881
- // Either way, say so and still launch the tool the user asked for -
1882
- // they came here to start an agent, not to configure one.
1883
- try {
1884
- installHookTargets([selection.hookTarget], selection.genre, chosenVol, selection.reactive, false,
1885
- { genre: selection.genre, volume: chosenVol });
1886
- } catch (err) {
1887
- console.error(`\x1b[31m[vibeaudio] ${err.message}\x1b[0m`);
1888
- }
1889
- }
1943
+ if (!applyMenuSelection(selection, chosenVol)) return;
1890
1944
 
1891
1945
  return executeCommand(selection.cmd, selection.genre, chosenVol, chimeVolume, grace, noChime, noHud, music);
1892
1946
  }
@@ -1898,6 +1952,7 @@ module.exports = {
1898
1952
  run,
1899
1953
  parseArgs,
1900
1954
  hooksAlreadyCover,
1955
+ applyMenuSelection,
1901
1956
  previewAudioFile,
1902
1957
  windowsCommandNeedsShell
1903
1958
  };
@@ -263,7 +263,7 @@ const DELIVERY_FRESH = [
263
263
  {
264
264
  id: "hooks",
265
265
  name: "Agent hooks",
266
- desc: "Music follows the agent's thinking. Set once, no wrapper needed"
266
+ desc: "Music follows the agent's thinking. Installs, then returns to your shell: nothing starts"
267
267
  },
268
268
  {
269
269
  id: "wrapper",
@@ -285,7 +285,7 @@ const DELIVERY_INSTALLED = [
285
285
  {
286
286
  id: "hooks",
287
287
  name: "Reconfigure the hooks",
288
- desc: "Pick a new genre, volume or reactive setting"
288
+ desc: "Pick a new genre, volume or reactive setting. Nothing starts"
289
289
  }
290
290
  ];
291
291