agentvibes 5.12.0-alpha.0 → 5.13.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.
Files changed (129) hide show
  1. package/.agentvibes/config.json +15 -1
  2. package/.agentvibes/install-manifest.json +342 -0
  3. package/.claude/activation-instructions +54 -54
  4. package/.claude/commands/agent-vibes/commands.json +0 -20
  5. package/.claude/commands/agent-vibes/unmute.md +6 -2
  6. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  7. package/.claude/commands/agent-vibes-rdp.md +24 -24
  8. package/.claude/config/audio-effects.cfg +6 -6
  9. package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
  10. package/.claude/config/background-music-enabled.txt +1 -1
  11. package/.claude/config/background-music-position.txt +27 -0
  12. package/.claude/config/background-music-volume.txt +1 -0
  13. package/.claude/config/background-music.cfg +1 -0
  14. package/.claude/config/background-music.txt +1 -0
  15. package/.claude/config/language.txt +1 -0
  16. package/.claude/config/personality.txt +1 -0
  17. package/.claude/config/tts-speech-rate.txt +1 -0
  18. package/.claude/config/tts-verbosity.txt +1 -0
  19. package/.claude/docs/TERMUX_SETUP.md +408 -408
  20. package/.claude/github-star-reminder.txt +1 -1
  21. package/.claude/hooks/agentvibes-session-id.sh +69 -0
  22. package/.claude/hooks/bmad-party-speak.sh +20 -4
  23. package/.claude/hooks/bmad-speak.sh +60 -2
  24. package/.claude/hooks/bmad-tts-injector.sh +20 -1
  25. package/.claude/hooks/bmad-voice-manager.sh +25 -3
  26. package/.claude/hooks/clawdbot-receiver-SECURE.sh +21 -2
  27. package/.claude/hooks/clawdbot-receiver.sh +19 -1
  28. package/.claude/hooks/elevenlabs-voices.sh +62 -0
  29. package/.claude/hooks/kokoro-installer.sh +20 -10
  30. package/.claude/hooks/language-manager.sh +10 -3
  31. package/.claude/hooks/party-set-room.sh +71 -0
  32. package/.claude/hooks/party-stage-roster.py +328 -0
  33. package/.claude/hooks/personality-manager.sh +19 -2
  34. package/.claude/hooks/piper-voice-manager.sh +3 -2
  35. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +24 -5
  36. package/.claude/hooks/play-tts-elevenlabs.sh +38 -118
  37. package/.claude/hooks/play-tts-kokoro.sh +33 -6
  38. package/.claude/hooks/play-tts-soprano.sh +3 -2
  39. package/.claude/hooks/play-tts-ssh-remote.sh +72 -27
  40. package/.claude/hooks/play-tts-termux-ssh.sh +5 -4
  41. package/.claude/hooks/play-tts.sh +86 -69
  42. package/.claude/hooks/provider-catalog.json +352 -0
  43. package/.claude/hooks/provider-catalog.sh +161 -0
  44. package/.claude/hooks/provider-commands.sh +2 -1
  45. package/.claude/hooks/provider-manager.sh +47 -9
  46. package/.claude/hooks/python-resolver.sh +117 -0
  47. package/.claude/hooks/session-id.sh +56 -0
  48. package/.claude/hooks/session-start-tts.sh +39 -0
  49. package/.claude/hooks/speed-manager.sh +1 -1
  50. package/.claude/hooks/translate-manager.sh +3 -2
  51. package/.claude/hooks/translator.py +1 -1
  52. package/.claude/hooks/voice-manager.sh +242 -10
  53. package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +119 -0
  54. package/.claude/hooks-windows/language-manager.ps1 +7 -1
  55. package/.claude/hooks-windows/personality-manager.ps1 +16 -1
  56. package/.claude/hooks-windows/play-tts-kokoro.ps1 +20 -4
  57. package/.claude/hooks-windows/play-tts.ps1 +32 -3
  58. package/.claude/hooks-windows/provider-catalog.ps1 +140 -0
  59. package/.claude/hooks-windows/provider-manager.ps1 +63 -8
  60. package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +153 -0
  61. package/.claude/hooks-windows/tts-watcher.ps1 +84 -8
  62. package/.claude/hooks-windows/voice-manager-windows.ps1 +49 -0
  63. package/.claude/piper-voices-dir.txt +1 -0
  64. package/.clawdbot/README.md +105 -105
  65. package/.mcp.json +15 -0
  66. package/LICENSE +190 -190
  67. package/README.md +20 -6
  68. package/RELEASE_NOTES.md +81 -0
  69. package/WINDOWS-SETUP.md +208 -208
  70. package/bin/agent-vibes +39 -39
  71. package/bin/mcp-server.js +121 -121
  72. package/bin/test-bmad-pr +78 -78
  73. package/mcp-server/QUICK_START.md +203 -203
  74. package/mcp-server/README.md +345 -345
  75. package/mcp-server/examples/claude_desktop_config.json +11 -11
  76. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  77. package/mcp-server/examples/custom_instructions.md +169 -169
  78. package/mcp-server/pyproject.toml +52 -52
  79. package/mcp-server/requirements.txt +2 -2
  80. package/mcp-server/server.py +146 -49
  81. package/mcp-server/test_mcp_correctness.py +20 -2
  82. package/mcp-server/test_server.py +395 -395
  83. package/mcp-server/test_windows_script_parity.py +0 -2
  84. package/package.json +1 -1
  85. package/src/cli/list-personalities.js +110 -110
  86. package/src/cli/list-voices.js +115 -11
  87. package/src/commands/bmad-voices.js +394 -394
  88. package/src/console/app.js +1 -9
  89. package/src/console/bling.js +71 -0
  90. package/src/console/brand-colors.js +13 -13
  91. package/src/console/constants/personalities.js +44 -44
  92. package/src/console/footer-config.js +0 -4
  93. package/src/console/music-preview.js +79 -0
  94. package/src/console/navigation.js +0 -1
  95. package/src/console/tabs/agents-tab.js +14 -2
  96. package/src/console/tabs/help-tab.js +314 -314
  97. package/src/console/tabs/music-tab.js +81 -5
  98. package/src/console/tabs/placeholder-tab.js +0 -2
  99. package/src/console/tabs/readme-tab.js +272 -272
  100. package/src/console/tabs/settings-tab.js +207 -14
  101. package/src/console/tabs/setup-tab.js +39 -36
  102. package/src/console/tabs/voices-tab.js +102 -23
  103. package/src/console/widgets/destroy-list.js +25 -25
  104. package/src/console/widgets/notice.js +55 -55
  105. package/src/console/widgets/track-picker.js +82 -0
  106. package/src/installer/language-screen.js +31 -31
  107. package/src/installer/music-file-input.js +304 -304
  108. package/src/installer.js +124 -10
  109. package/src/services/language-service.js +47 -47
  110. package/src/services/navigation-service.js +1 -1
  111. package/src/services/provider-catalog.js +412 -0
  112. package/src/services/provider-voice-catalog.js +52 -73
  113. package/src/services/tts-engine-service.js +29 -0
  114. package/src/utils/audio-format-validator.js +277 -277
  115. package/src/utils/dependency-checker.js +469 -469
  116. package/src/utils/file-ownership-verifier.js +358 -358
  117. package/src/utils/music-file-validator.js +285 -285
  118. package/src/utils/preview-list-prompt.js +144 -144
  119. package/src/utils/provider-validator.js +62 -12
  120. package/src/utils/secure-music-storage.js +412 -412
  121. package/voice-assignments.json +8244 -8244
  122. package/.claude/commands/agent-vibes/language.md +0 -23
  123. package/.claude/commands/agent-vibes/learn.md +0 -67
  124. package/.claude/commands/agent-vibes/replay-target.md +0 -14
  125. package/.claude/commands/agent-vibes/target-voice.md +0 -26
  126. package/.claude/commands/agent-vibes/target.md +0 -30
  127. package/.claude/hooks/learn-manager.sh +0 -492
  128. package/.claude/hooks/replay-target-audio.sh +0 -95
  129. package/.claude/hooks-windows/learn-manager.ps1 +0 -241
@@ -22,7 +22,6 @@ import { FOOTER_CONFIG, DEFAULT_FOOTER_COLOR } from './footer-config.js';
22
22
  import { createModalOverlay } from './modals/modal-overlay.js';
23
23
  import { BRAND_PINK } from './brand-colors.js';
24
24
  import { createSettingsTab } from './tabs/settings-tab.js';
25
- import { createVoicesTab } from './tabs/voices-tab.js';
26
25
  import { createMusicTab } from './tabs/music-tab.js';
27
26
  import { createSetupTab } from './tabs/setup-tab.js';
28
27
  import { createHelpTab } from './tabs/help-tab.js';
@@ -728,13 +727,6 @@ export class AgentVibesConsole {
728
727
  };
729
728
  this.tabs['settings'] = createSettingsTab(this.screen, services);
730
729
 
731
- // Destroy voices placeholder and mount real voices tab
732
- const voicesPlaceholder = this.tabs['voices'];
733
- if (voicesPlaceholder && typeof voicesPlaceholder.destroy === 'function') {
734
- voicesPlaceholder.destroy();
735
- }
736
- this.tabs['voices'] = createVoicesTab(this.screen, services);
737
-
738
730
  // Destroy music placeholder and mount real music tab
739
731
  const musicPlaceholder = this.tabs['music'];
740
732
  if (musicPlaceholder && typeof musicPlaceholder.destroy === 'function') {
@@ -923,7 +915,7 @@ export class AgentVibesConsole {
923
915
  *
924
916
  * @param {object} opts
925
917
  * @param {string} [opts.startTab='settings'] - Which tab to show on launch.
926
- * Used by story 6.5 (command routing). Values: 'settings' | 'install' | 'voices' | 'music'
918
+ * Used by story 6.5 (command routing). Values: 'settings' | 'setup' | 'music'
927
919
  * @param {boolean} [opts._testMode=false] - Internal: skip render in test environments.
928
920
  * @returns {Promise<AgentVibesConsole>}
929
921
  */
@@ -0,0 +1,71 @@
1
+ /**
2
+ * AgentVibes — Shared "bling" readiness cue.
3
+ *
4
+ * A short, fire-and-forget chime played the instant a preview is committed, so
5
+ * silence during synthesis / an SSH round-trip never reads as a hang. Used by
6
+ * BOTH the voice picker (setup-tab.js) and the music-preview surfaces
7
+ * (music-tab.js, track-picker.js) so they can't drift.
8
+ *
9
+ * `buildBlingCommand` was originally defined in setup-tab.js; it was moved here
10
+ * and is re-exported from setup-tab.js for backward compatibility (existing
11
+ * imports and tests keep working).
12
+ */
13
+
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
16
+ import { spawn } from 'node:child_process';
17
+
18
+ /**
19
+ * Build the fire-and-forget "preview ready" cue command for a platform. Pure
20
+ * (no spawning here, so it is unit-testable). Plays the bundled CC0 wav when
21
+ * present, else falls back to a system sound (Windows) / freedesktop cue or
22
+ * terminal bell (POSIX). stdio is ignored by the caller, so the POSIX bell is
23
+ * redirected to /dev/tty rather than the discarded stdout.
24
+ * @param {string} platform - process.platform value
25
+ * @param {string} wavPath - absolute path to the bling wav
26
+ * @param {boolean} haveWav - whether wavPath exists on disk
27
+ * @returns {{command: string, args: string[]}}
28
+ */
29
+ export function buildBlingCommand(platform, wavPath, haveWav) {
30
+ if (platform === 'win32') {
31
+ const ps = haveWav
32
+ ? `Add-Type -AssemblyName System.Windows.Forms; (New-Object System.Media.SoundPlayer('${wavPath.replace(/'/g, "''")}')).PlaySync()`
33
+ : '[System.Media.SystemSounds]::Asterisk.Play(); Start-Sleep -Milliseconds 700';
34
+ return { command: 'powershell', args: ['-NoProfile', '-Command', ps] };
35
+ }
36
+ if (haveWav) {
37
+ // Pass wavPath as a positional arg ($1) so the path is never interpolated
38
+ // into the shell string (prevents injection / breakage on special chars).
39
+ const sh = 'paplay "$1" 2>/dev/null || aplay -q "$1" 2>/dev/null || printf "\\a" > /dev/tty 2>/dev/null';
40
+ return { command: 'bash', args: ['-c', sh, '--', wavPath] };
41
+ }
42
+ const sh = 'paplay /usr/share/sounds/freedesktop/stereo/message.oga 2>/dev/null || printf "\\a" > /dev/tty 2>/dev/null';
43
+ return { command: 'bash', args: ['-c', sh] };
44
+ }
45
+
46
+ /**
47
+ * Resolve the bundled CC0 bling wav for a package root.
48
+ * Sound: "Ui sounds - Shimmering success" by Philip_Berger, CC0 (freesound
49
+ * #648212). See .claude/audio/ui/CREDITS.txt.
50
+ * @param {string} packageRoot - AgentVibes package/repo root
51
+ * @returns {string}
52
+ */
53
+ export function resolveBlingWav(packageRoot) {
54
+ return path.join(packageRoot, '.claude', 'audio', 'ui', 'bling-success.wav');
55
+ }
56
+
57
+ /**
58
+ * Play the readiness cue, fire-and-forget. Detached + unref'd + errors
59
+ * swallowed, so it never blocks the UI or fails a preview. A missing wav falls
60
+ * back to a platform system sound via buildBlingCommand.
61
+ * @param {string} packageRoot - AgentVibes package/repo root
62
+ */
63
+ export function playBlingCue(packageRoot) {
64
+ try {
65
+ const wav = resolveBlingWav(packageRoot);
66
+ const { command, args } = buildBlingCommand(process.platform, wav, fs.existsSync(wav));
67
+ const cue = spawn(command, args, { stdio: 'ignore', detached: true }); // NOSONAR
68
+ cue.on('error', () => { /* best-effort cue; a spawn failure must not surface */ });
69
+ cue.unref();
70
+ } catch { /* the readiness cue is purely cosmetic — never break the preview */ }
71
+ }
@@ -1,13 +1,13 @@
1
- /**
2
- * AgentVibes TUI — Brand Color Constants
3
- *
4
- * Single source of truth for the two primary brand colors.
5
- * Change BRAND_PINK or BRAND_BLUE here to update every modal title,
6
- * button, and the "Vibes" logotype across the entire TUI.
7
- */
8
-
9
- /** Magenta-pink used for modal titles and the "Vibes" logotype. */
10
- export const BRAND_PINK = '#f06292'; // Light magenta — Pink 300
11
-
12
- /** Indigo blue used for default button backgrounds and primary accents. */
13
- export const BRAND_BLUE = '#3949ab';
1
+ /**
2
+ * AgentVibes TUI — Brand Color Constants
3
+ *
4
+ * Single source of truth for the two primary brand colors.
5
+ * Change BRAND_PINK or BRAND_BLUE here to update every modal title,
6
+ * button, and the "Vibes" logotype across the entire TUI.
7
+ */
8
+
9
+ /** Magenta-pink used for modal titles and the "Vibes" logotype. */
10
+ export const BRAND_PINK = '#f06292'; // Light magenta — Pink 300
11
+
12
+ /** Indigo blue used for default button backgrounds and primary accents. */
13
+ export const BRAND_BLUE = '#3949ab';
@@ -1,44 +1,44 @@
1
- /**
2
- * AgentVibes — Canonical personality constants.
3
- *
4
- * Single source of truth for personality names and their associated emoji
5
- * glyphs. All TUI modules (settings-tab, agents-tab, personality-picker …)
6
- * import from here; src/installer.js maintains its own copy because it
7
- * predates the TUI and uses a different module-load path.
8
- *
9
- * Exported:
10
- * PERSONALITY_EMOJIS — Map of personality name → emoji string
11
- * PERSONALITIES — Ordered array of personality names (canonical order
12
- * used for picker lists)
13
- */
14
-
15
- export const PERSONALITY_EMOJIS = Object.freeze({
16
- angry: '😠',
17
- annoying: '😤',
18
- crass: '🤬',
19
- dramatic: '🎭',
20
- 'dry-humor': '😐',
21
- flirty: '😘',
22
- funny: '😂',
23
- grandpa: '👴',
24
- millennial: '🙄',
25
- moody: '😒',
26
- none: '😊',
27
- normal: '😊',
28
- pirate: '⚓',
29
- poetic: '📜',
30
- professional: '👔',
31
- rapper: '🎤',
32
- robot: '🤖',
33
- sarcastic: '😏',
34
- sassy: '💁',
35
- 'surfer-dude':'🏄',
36
- zen: '🧘',
37
- });
38
-
39
- export const PERSONALITIES = Object.freeze([
40
- 'none', 'angry', 'annoying', 'crass', 'dramatic', 'dry-humor',
41
- 'flirty', 'funny', 'grandpa', 'millennial', 'moody', 'normal',
42
- 'pirate', 'poetic', 'professional', 'rapper', 'robot', 'sarcastic',
43
- 'sassy', 'surfer-dude', 'zen',
44
- ]);
1
+ /**
2
+ * AgentVibes — Canonical personality constants.
3
+ *
4
+ * Single source of truth for personality names and their associated emoji
5
+ * glyphs. All TUI modules (settings-tab, agents-tab, personality-picker …)
6
+ * import from here; src/installer.js maintains its own copy because it
7
+ * predates the TUI and uses a different module-load path.
8
+ *
9
+ * Exported:
10
+ * PERSONALITY_EMOJIS — Map of personality name → emoji string
11
+ * PERSONALITIES — Ordered array of personality names (canonical order
12
+ * used for picker lists)
13
+ */
14
+
15
+ export const PERSONALITY_EMOJIS = Object.freeze({
16
+ angry: '😠',
17
+ annoying: '😤',
18
+ crass: '🤬',
19
+ dramatic: '🎭',
20
+ 'dry-humor': '😐',
21
+ flirty: '😘',
22
+ funny: '😂',
23
+ grandpa: '👴',
24
+ millennial: '🙄',
25
+ moody: '😒',
26
+ none: '😊',
27
+ normal: '😊',
28
+ pirate: '⚓',
29
+ poetic: '📜',
30
+ professional: '👔',
31
+ rapper: '🎤',
32
+ robot: '🤖',
33
+ sarcastic: '😏',
34
+ sassy: '💁',
35
+ 'surfer-dude':'🏄',
36
+ zen: '🧘',
37
+ });
38
+
39
+ export const PERSONALITIES = Object.freeze([
40
+ 'none', 'angry', 'annoying', 'crass', 'dramatic', 'dry-humor',
41
+ 'flirty', 'funny', 'grandpa', 'millennial', 'moody', 'normal',
42
+ 'pirate', 'poetic', 'professional', 'rapper', 'robot', 'sarcastic',
43
+ 'sassy', 'surfer-dude', 'zen',
44
+ ]);
@@ -19,10 +19,6 @@ export const FOOTER_CONFIG = {
19
19
  color: '#2196f3',
20
20
  text: ` ${key('↑↓')} Navigate ${key('←→')} Same Row ${key('Enter')} Activate ${key('Space')} Preview ${key('Esc')} Cancel`,
21
21
  },
22
- voices: {
23
- color: '#00bcd4',
24
- text: ` ${key('1-6')} Sort ${key('/')} Search ${key('P')} Provider ${key('F')} Favorites ${key('Space')} Preview ${key('*')} Fav ${key('I')} Install`,
25
- },
26
22
  music: {
27
23
  color: '#ff9800',
28
24
  text: ` ${key('Space')} Preview ${key('Enter')} Select ${key('M')} Toggle ${key('*')} Fav ${key('F')} Filter ${key('↑↓')} Navigate`,
@@ -0,0 +1,79 @@
1
+ /**
2
+ * AgentVibes — Shared music-preview transport helpers.
3
+ *
4
+ * Both music-preview surfaces (the Music tab and the track-picker widget) must
5
+ * make the same remote-vs-local decision: a transport provider routes audio to
6
+ * a remote receiver, so a local MP3 preview would be silent on a headless box
7
+ * and must be forwarded instead. This module holds the provider-resolve and the
8
+ * remote-forward spawn so the two surfaces can't drift.
9
+ *
10
+ * The caller owns UI feedback (status text, error messages) and attaches its own
11
+ * exit/error/stderr handlers to the returned child process.
12
+ */
13
+
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
16
+ import os from 'node:os';
17
+ import { spawn } from 'node:child_process';
18
+
19
+ // Transport providers route audio to a remote receiver; local MP3 playback is
20
+ // silent on a headless/remote box, so track previews must be forwarded instead.
21
+ export const REMOTE_PROVIDERS = ['ssh-remote', 'agentvibes-receiver'];
22
+
23
+ /**
24
+ * Resolve the active provider and the project dir it was read from, using the
25
+ * same search order as the voice pickers (CLAUDE_PROJECT_DIR → cwd → package →
26
+ * home). Returns { remote, projectDir } so a remote preview can forward the
27
+ * track to the receiver and tell the sender which .claude dir to resolve.
28
+ * @param {string} packageRoot - AgentVibes package/repo root (package fallback)
29
+ * @returns {{ remote: boolean, projectDir: string }}
30
+ */
31
+ export function resolveMusicProvider(packageRoot) {
32
+ const dirs = [process.env.CLAUDE_PROJECT_DIR, process.cwd(), packageRoot, os.homedir()].filter(Boolean);
33
+ for (const d of dirs) {
34
+ const p = path.join(d, '.claude', 'tts-provider.txt');
35
+ try {
36
+ if (fs.existsSync(p)) {
37
+ const provider = fs.readFileSync(p, 'utf8').trim();
38
+ return { remote: REMOTE_PROVIDERS.includes(provider), projectDir: d };
39
+ }
40
+ } catch { /* next */ }
41
+ }
42
+ return { remote: false, projectDir: '' };
43
+ }
44
+
45
+ /**
46
+ * Resolve the SSH sender script path, preferring the package-bundled copy and
47
+ * falling back to the project's .claude/hooks copy.
48
+ * @param {string} packageRoot
49
+ * @param {string} projectDir
50
+ * @returns {string}
51
+ */
52
+ export function resolveRemoteSender(packageRoot, projectDir) {
53
+ const pkgSender = path.resolve(packageRoot, '.claude', 'hooks', 'play-tts-ssh-remote.sh');
54
+ return fs.existsSync(pkgSender)
55
+ ? pkgSender
56
+ : path.join(projectDir, '.claude', 'hooks', 'play-tts-ssh-remote.sh');
57
+ }
58
+
59
+ /**
60
+ * Spawn the SSH sender to forward a music track to the receiver, or stop the
61
+ * current one. Fire-and-forget (the sender exits after handing the payload to
62
+ * SSH; the receiver plays/stops asynchronously). Returns the child process so
63
+ * the caller can wire its own exit/error/stderr handlers for UI feedback.
64
+ *
65
+ * @param {object} opts
66
+ * @param {string} opts.packageRoot
67
+ * @param {string} opts.projectDir
68
+ * @param {object} opts.env - base spawn env (e.g. buildAudioEnv())
69
+ * @param {string} [opts.track] - track filename to play (ignored when stop)
70
+ * @param {boolean} [opts.stop] - send an explicit music-stop signal instead
71
+ * @returns {import('node:child_process').ChildProcess}
72
+ */
73
+ export function spawnMusicRemote({ packageRoot, projectDir, env, track = null, stop = false }) {
74
+ const senderPath = resolveRemoteSender(packageRoot, projectDir);
75
+ const spawnEnv = { ...env, CLAUDE_PROJECT_DIR: projectDir };
76
+ if (stop) spawnEnv.AGENTVIBES_MUSIC_STOP = '1';
77
+ else spawnEnv.AGENTVIBES_MUSIC_ONLY = track;
78
+ return spawn('bash', [senderPath, '', ''], { stdio: ['ignore', 'ignore', 'pipe'], detached: true, env: spawnEnv }); // NOSONAR
79
+ }
@@ -9,7 +9,6 @@
9
9
  /** Map of key → tab ID for global tab shortcut keys */
10
10
  const KEY_TO_TAB = {
11
11
  's': 'settings', 'S': 'settings',
12
- 'v': 'voices', 'V': 'voices',
13
12
  'm': 'music', 'M': 'music',
14
13
  'b': 'agents', 'B': 'agents',
15
14
  'x': 'receiver', 'X': 'receiver',
@@ -28,6 +28,7 @@ import crypto from 'node:crypto';
28
28
  import fs from 'node:fs';
29
29
  import os from 'node:os';
30
30
  import path from 'node:path';
31
+ import { fileURLToPath } from 'node:url';
31
32
  import { spawn } from 'node:child_process';
32
33
 
33
34
  // Max pretext length to prevent excessively long TTS utterances
@@ -235,6 +236,17 @@ ${_tl('bmadDesc')}
235
236
  // Capture cwd once at construction (L1 fix)
236
237
  const _projectRoot = process.cwd();
237
238
 
239
+ // Preview hooks: prefer the CURRENT package copy over the project-local
240
+ // .claude/hooks, which a tarball reinstall does NOT refresh (npm updates
241
+ // node_modules only). A stale project hook routes previews through an outdated
242
+ // sender (e.g. the old agentvibes-receiver→legacy-sender path). CLAUDE_PROJECT_DIR
243
+ // still points at _projectRoot so the hook reads the project's config/provider.
244
+ const _pkgClaudeDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '.claude');
245
+ function _hookScript(subdir, name) {
246
+ const pkg = path.join(_pkgClaudeDir, subdir, name);
247
+ return fs.existsSync(pkg) ? pkg : path.join(_projectRoot, '.claude', subdir, name);
248
+ }
249
+
238
250
  // Story 8.3 fix: Windows agent-preview temp-patches config files (personality.txt,
239
251
  // reverb-level.txt) to apply per-agent settings during a preview. Track every
240
252
  // patched file + its pre-patch content here so ALL of them can be restored —
@@ -1116,7 +1128,7 @@ ${_tl('bmadDesc')}
1116
1128
 
1117
1129
  if (_isWin) {
1118
1130
  // Windows: route through play-tts.ps1 (same pattern as non-Windows bash route)
1119
- const playTtsScript = path.join(_projectRoot, '.claude', 'hooks-windows', 'play-tts.ps1');
1131
+ const playTtsScript = _hookScript('hooks-windows', 'play-tts.ps1');
1120
1132
  if (!fs.existsSync(playTtsScript)) return;
1121
1133
  _previewVoiceId = voiceId;
1122
1134
  if (!_vpClosed) { vpPreviewLine.setContent(`{bright-cyan-fg}♪ Playing: ${voiceId}...{/bright-cyan-fg}`); _refreshVP(); }
@@ -1131,7 +1143,7 @@ ${_tl('bmadDesc')}
1131
1143
  }
1132
1144
 
1133
1145
  // Non-Windows: use bash play-tts.sh
1134
- const playTtsScript = path.join(_projectRoot, '.claude', 'hooks', 'play-tts.sh');
1146
+ const playTtsScript = _hookScript('hooks', 'play-tts.sh');
1135
1147
  if (!fs.existsSync(playTtsScript)) return;
1136
1148
 
1137
1149
  const remoteLlm = detectRemoteLlm();