vibeaudio 0.6.2 โ 0.7.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 +20 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +23 -6
- package/src/cli.js +13 -0
- package/src/update.js +106 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ๐ง VibeAudio
|
|
2
2
|
|
|
3
|
-
[](https://github.com/kiril6/vibeaudio/actions/workflows/test.yml)
|
|
3
|
+
[](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
4
|
|
|
5
5
|
> **Procedural focus music while your AI coding tools think.**
|
|
6
6
|
> Every project gets its own arrangement. Zero dependencies, zero audio files.
|
|
@@ -23,6 +23,7 @@ AI coding agents take 15โ45 seconds to reason, read files and write code. Star
|
|
|
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
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.
|
|
26
|
+
* ๐๏ธ **Music that follows the work** ([reactive mode](#reactive-mode-opt-in), opt-in). Calm while the agent reads, fuller while it edits, busiest when it runs commands or hands work to sub-agents โ you can hear *what* it's doing, not just for how long.
|
|
26
27
|
* ๐ **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.
|
|
27
28
|
* โ **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
29
|
* ๐ **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.
|
|
@@ -86,6 +87,15 @@ npm i -g vibeaudio
|
|
|
86
87
|
|
|
87
88
|
That puts `vibe` and `vibeaudio` on your `PATH`. Re-run the same command to update, or see [Uninstall](#-uninstall) to remove it cleanly.
|
|
88
89
|
|
|
90
|
+
**Staying up to date.** When a newer version is out, `vibe --status`, `vibe --help` and the menu say so in one line. They're the only places it's shown โ never in your agent's hooks, and never over a command you wrapped. What changed is on the [releases page](https://github.com/kiril6/vibeaudio/releases).
|
|
91
|
+
|
|
92
|
+
<details>
|
|
93
|
+
<summary><b>What the update check sends, and how to turn it off</b></summary>
|
|
94
|
+
|
|
95
|
+
Once a day at most, a background process asks the public npm registry for the latest version number โ the same request `npm outdated` makes. It sends nothing about you: no ID, no settings, no usage. It's the only network request VibeAudio ever makes, and `vibe` never waits for it; the answer is shown the next time. It's skipped in CI, under `npx`, and whenever `VIBE_NO_UPDATE_CHECK=1` or `NO_UPDATE_NOTIFIER=1` is set.
|
|
96
|
+
|
|
97
|
+
</details>
|
|
98
|
+
|
|
89
99
|
### Then pick how it runs
|
|
90
100
|
|
|
91
101
|
**Using Claude Code, Codex, Cursor or Grok interactively?** Install the hooks โ this is the mode that actually tracks thinking:
|
|
@@ -94,6 +104,14 @@ That puts `vibe` and `vibeaudio` on your `PATH`. Re-run the same command to upda
|
|
|
94
104
|
vibe --install-hooks
|
|
95
105
|
```
|
|
96
106
|
|
|
107
|
+
Want the music to follow what the agent is doing โ calm while it reads, busiest while it runs commands? Add `--reactive`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
vibe --reactive --install-hooks
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
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.
|
|
114
|
+
|
|
97
115
|
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)
|
|
98
116
|
|
|
99
117
|
**Running one-shot commands?** Wrap them:
|
|
@@ -533,6 +551,7 @@ export VIBE_CHIME_VOLUME=70 # Crisp completion chime at 70%
|
|
|
533
551
|
export VIBE_GRACE_MS=3000 # Wait 3s of thinking before any music
|
|
534
552
|
export VIBE_SEED=7 # Same arrangement everywhere, ignoring the directory
|
|
535
553
|
export VIBE_DISABLE=1 # Mute, without uninstalling anything
|
|
554
|
+
export VIBE_NO_UPDATE_CHECK=1 # Never ask npm whether a newer version is out
|
|
536
555
|
```
|
|
537
556
|
|
|
538
557
|
**`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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeaudio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
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",
|
package/scripts/postinstall.js
CHANGED
|
@@ -10,6 +10,21 @@
|
|
|
10
10
|
* report and the user's to wonder about, so everything is wrapped and the exit
|
|
11
11
|
* code is always 0 - a greeting is not worth a failed install.
|
|
12
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
|
+
|
|
13
28
|
try {
|
|
14
29
|
// Local installs are a dependency of something else; their user is not here
|
|
15
30
|
// and not the one who would run `vibe`. CI and Docker set this too.
|
|
@@ -18,14 +33,16 @@ try {
|
|
|
18
33
|
// install is already the narrow case - a dependency install sets this false.
|
|
19
34
|
if (process.env.npm_config_global === "true") {
|
|
20
35
|
const c = (code, text) => `\x1b[${code}m${text}\x1b[0m`;
|
|
21
|
-
|
|
22
|
-
${c("1;36", "๐ง VibeAudio installed.")}
|
|
36
|
+
say(`
|
|
37
|
+
${c("1;36", "๐ง VibeAudio installed.")} Start here:
|
|
23
38
|
|
|
24
|
-
${c("1", "vibe
|
|
25
|
-
|
|
39
|
+
${c("1", "vibe")} ${c("90", "guided setup: pick your agent, genre and volume")}
|
|
40
|
+
|
|
41
|
+
Or skip the menu:
|
|
26
42
|
|
|
27
|
-
${c("
|
|
28
|
-
${c("
|
|
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")}
|
|
29
46
|
`);
|
|
30
47
|
}
|
|
31
48
|
} catch (e) {
|
package/src/cli.js
CHANGED
|
@@ -103,7 +103,17 @@ Procedural focus music while your AI coding tools think.
|
|
|
103
103
|
VIBE_GRACE_MS=<ms> Override the saved grace window, in ms
|
|
104
104
|
VIBE_SEED=<n> Pin the arrangement instead of deriving it from the directory
|
|
105
105
|
VIBE_DISABLE=1 Mute automatic playback without uninstalling anything
|
|
106
|
+
VIBE_NO_UPDATE_CHECK=1 Never check npm for a newer version
|
|
106
107
|
`);
|
|
108
|
+
printUpdateNotice();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Only where a person is reading: help, status and the menu. See update.js.
|
|
112
|
+
function printUpdateNotice() {
|
|
113
|
+
if (!process.stdout.isTTY) return;
|
|
114
|
+
const { ephemeralInstallReason } = require("./hooks");
|
|
115
|
+
const line = require("./update").updateNotice({ ephemeral: Boolean(ephemeralInstallReason()) });
|
|
116
|
+
if (line) console.log(`${line}\n`);
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
/**
|
|
@@ -709,6 +719,7 @@ function printStatus() {
|
|
|
709
719
|
}
|
|
710
720
|
}
|
|
711
721
|
console.log();
|
|
722
|
+
printUpdateNotice();
|
|
712
723
|
}
|
|
713
724
|
|
|
714
725
|
/**
|
|
@@ -1226,6 +1237,8 @@ async function run() {
|
|
|
1226
1237
|
// leave. Wrapping the launch too turned every real failure below into a
|
|
1227
1238
|
// silent exit 0, which is the worst possible thing for a wrapper to do:
|
|
1228
1239
|
// `vibe && deploy` would chain on a run that never happened.
|
|
1240
|
+
printUpdateNotice();
|
|
1241
|
+
|
|
1229
1242
|
let selection;
|
|
1230
1243
|
try {
|
|
1231
1244
|
selection = await promptInteractive({
|
package/src/update.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "A newer version is out", said where a person is looking.
|
|
3
|
+
*
|
|
4
|
+
* A global CLI has no other way to reach its users: nothing tells them to
|
|
5
|
+
* re-run `npm i -g`, so fixes never arrive. This asks the registry at most once
|
|
6
|
+
* a day, in a detached process, and shows the answer the *next* time - a run
|
|
7
|
+
* never waits on the network, and an offline machine is simply never told.
|
|
8
|
+
*
|
|
9
|
+
* The request is the one `npm outdated` makes, to the public registry, and
|
|
10
|
+
* carries nothing about the user: no id, no settings, no usage. It is the only
|
|
11
|
+
* network call VibeAudio makes, which is why it can be switched off.
|
|
12
|
+
*
|
|
13
|
+
* Shown only by `--status`, `--help` and the menu. Never by hooks - their
|
|
14
|
+
* output goes to the agent, not the person - and never by the wrapper, whose
|
|
15
|
+
* terminal belongs to the command it runs.
|
|
16
|
+
*/
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const os = require("os");
|
|
19
|
+
const path = require("path");
|
|
20
|
+
const { spawn } = require("child_process");
|
|
21
|
+
const pkg = require("../package.json");
|
|
22
|
+
|
|
23
|
+
const CHECK_FILE = path.join(os.homedir(), ".vibeaudio", "update.json");
|
|
24
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
25
|
+
const REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
26
|
+
|
|
27
|
+
// CI and the de-facto standard opt-out (update-notifier's) as well as our own.
|
|
28
|
+
function checkDisabled(env = process.env) {
|
|
29
|
+
return Boolean(env.VIBE_NO_UPDATE_CHECK || env.NO_UPDATE_NOTIFIER || env.CI);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Numeric x.y.z comparison; a prerelease or malformed version is never "newer". */
|
|
33
|
+
function isNewer(latest, current) {
|
|
34
|
+
const parse = (v) => (/^\d+\.\d+\.\d+$/.test(v || "") ? v.split(".").map(Number) : null);
|
|
35
|
+
const a = parse(latest);
|
|
36
|
+
const b = parse(current);
|
|
37
|
+
if (!a || !b) return false;
|
|
38
|
+
for (let i = 0; i < 3; i++) {
|
|
39
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readCheck(file) {
|
|
45
|
+
try {
|
|
46
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
47
|
+
return data && typeof data === "object" ? data : {};
|
|
48
|
+
} catch (e) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeCheck(file, data) {
|
|
54
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
55
|
+
// Per-process temp name: two runs refreshing at once must not share one.
|
|
56
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
57
|
+
fs.writeFileSync(tmp, `${JSON.stringify(data)}\n`);
|
|
58
|
+
fs.renameSync(tmp, file);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function refreshInBackground(file) {
|
|
62
|
+
try {
|
|
63
|
+
// Stamp the attempt first, so a burst of runs starts one check, not many.
|
|
64
|
+
writeCheck(file, { ...readCheck(file), checked: Date.now() });
|
|
65
|
+
const child = spawn(process.execPath, [__filename, file], { detached: true, stdio: "ignore" });
|
|
66
|
+
child.unref();
|
|
67
|
+
} catch (e) {
|
|
68
|
+
// An update notice is never worth an error.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The line to print, or null. Synchronous and offline: it reads what the last
|
|
74
|
+
* check found, and starts the next check if that one is a day old.
|
|
75
|
+
* `ephemeral`: running from an npx cache, where "npm i -g" is the wrong advice
|
|
76
|
+
* and npx fetches the latest on its own anyway.
|
|
77
|
+
*/
|
|
78
|
+
function updateNotice({ file = CHECK_FILE, env = process.env, now = Date.now(), current = pkg.version, ephemeral = false, refresh = refreshInBackground } = {}) {
|
|
79
|
+
if (checkDisabled(env) || ephemeral) return null;
|
|
80
|
+
|
|
81
|
+
const state = readCheck(file);
|
|
82
|
+
if (!(now - (state.checked || 0) < CHECK_INTERVAL_MS)) refresh(file);
|
|
83
|
+
|
|
84
|
+
if (!isNewer(state.latest, current)) return null;
|
|
85
|
+
return `\x1b[33mUpdate available ${current} โ ${state.latest}\x1b[0m \x1b[1mnpm i -g ${pkg.name}\x1b[0m` +
|
|
86
|
+
` \x1b[90m(what's new: https://github.com/kiril6/vibeaudio/releases)\x1b[0m`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// The detached child: one request, bounded, and silent whatever happens.
|
|
90
|
+
async function runCheck(file) {
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch(REGISTRY_URL, {
|
|
93
|
+
headers: { accept: "application/json" },
|
|
94
|
+
signal: AbortSignal.timeout(5000)
|
|
95
|
+
});
|
|
96
|
+
if (!res.ok) return;
|
|
97
|
+
const { version } = await res.json();
|
|
98
|
+
if (typeof version === "string") writeCheck(file, { ...readCheck(file), latest: version });
|
|
99
|
+
} catch (e) {
|
|
100
|
+
// Offline, blocked, or a registry hiccup: try again tomorrow.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (require.main === module) runCheck(process.argv[2] || CHECK_FILE);
|
|
105
|
+
|
|
106
|
+
module.exports = { updateNotice, isNewer, checkDisabled, CHECK_FILE };
|