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.
- package/.agentvibes/config.json +15 -1
- package/.agentvibes/install-manifest.json +342 -0
- package/.claude/activation-instructions +54 -54
- package/.claude/commands/agent-vibes/commands.json +0 -20
- package/.claude/commands/agent-vibes/unmute.md +6 -2
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/audio-effects.cfg +6 -6
- package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
- package/.claude/config/background-music-enabled.txt +1 -1
- package/.claude/config/background-music-position.txt +27 -0
- package/.claude/config/background-music-volume.txt +1 -0
- package/.claude/config/background-music.cfg +1 -0
- package/.claude/config/background-music.txt +1 -0
- package/.claude/config/language.txt +1 -0
- package/.claude/config/personality.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +1 -0
- package/.claude/config/tts-verbosity.txt +1 -0
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/agentvibes-session-id.sh +69 -0
- package/.claude/hooks/bmad-party-speak.sh +20 -4
- package/.claude/hooks/bmad-speak.sh +60 -2
- package/.claude/hooks/bmad-tts-injector.sh +20 -1
- package/.claude/hooks/bmad-voice-manager.sh +25 -3
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +21 -2
- package/.claude/hooks/clawdbot-receiver.sh +19 -1
- package/.claude/hooks/elevenlabs-voices.sh +62 -0
- package/.claude/hooks/kokoro-installer.sh +20 -10
- package/.claude/hooks/language-manager.sh +10 -3
- package/.claude/hooks/party-set-room.sh +71 -0
- package/.claude/hooks/party-stage-roster.py +328 -0
- package/.claude/hooks/personality-manager.sh +19 -2
- package/.claude/hooks/piper-voice-manager.sh +3 -2
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +24 -5
- package/.claude/hooks/play-tts-elevenlabs.sh +38 -118
- package/.claude/hooks/play-tts-kokoro.sh +33 -6
- package/.claude/hooks/play-tts-soprano.sh +3 -2
- package/.claude/hooks/play-tts-ssh-remote.sh +72 -27
- package/.claude/hooks/play-tts-termux-ssh.sh +5 -4
- package/.claude/hooks/play-tts.sh +86 -69
- package/.claude/hooks/provider-catalog.json +352 -0
- package/.claude/hooks/provider-catalog.sh +161 -0
- package/.claude/hooks/provider-commands.sh +2 -1
- package/.claude/hooks/provider-manager.sh +47 -9
- package/.claude/hooks/python-resolver.sh +117 -0
- package/.claude/hooks/session-id.sh +56 -0
- package/.claude/hooks/session-start-tts.sh +39 -0
- package/.claude/hooks/speed-manager.sh +1 -1
- package/.claude/hooks/translate-manager.sh +3 -2
- package/.claude/hooks/translator.py +1 -1
- package/.claude/hooks/voice-manager.sh +242 -10
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +119 -0
- package/.claude/hooks-windows/language-manager.ps1 +7 -1
- package/.claude/hooks-windows/personality-manager.ps1 +16 -1
- package/.claude/hooks-windows/play-tts-kokoro.ps1 +20 -4
- package/.claude/hooks-windows/play-tts.ps1 +32 -3
- package/.claude/hooks-windows/provider-catalog.ps1 +140 -0
- package/.claude/hooks-windows/provider-manager.ps1 +63 -8
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +153 -0
- package/.claude/hooks-windows/tts-watcher.ps1 +84 -8
- package/.claude/hooks-windows/voice-manager-windows.ps1 +49 -0
- package/.claude/piper-voices-dir.txt +1 -0
- package/.clawdbot/README.md +105 -105
- package/.mcp.json +15 -0
- package/LICENSE +190 -190
- package/README.md +20 -6
- package/RELEASE_NOTES.md +81 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/mcp-server.js +121 -121
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +146 -49
- package/mcp-server/test_mcp_correctness.py +20 -2
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +0 -2
- package/package.json +1 -1
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +115 -11
- package/src/commands/bmad-voices.js +394 -394
- package/src/console/app.js +1 -9
- package/src/console/bling.js +71 -0
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/footer-config.js +0 -4
- package/src/console/music-preview.js +79 -0
- package/src/console/navigation.js +0 -1
- package/src/console/tabs/agents-tab.js +14 -2
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +81 -5
- package/src/console/tabs/placeholder-tab.js +0 -2
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/settings-tab.js +207 -14
- package/src/console/tabs/setup-tab.js +39 -36
- package/src/console/tabs/voices-tab.js +102 -23
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/track-picker.js +82 -0
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +124 -10
- package/src/services/language-service.js +47 -47
- package/src/services/navigation-service.js +1 -1
- package/src/services/provider-catalog.js +412 -0
- package/src/services/provider-voice-catalog.js +52 -73
- package/src/services/tts-engine-service.js +29 -0
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +144 -144
- package/src/utils/provider-validator.js +62 -12
- package/src/utils/secure-music-storage.js +412 -412
- package/voice-assignments.json +8244 -8244
- package/.claude/commands/agent-vibes/language.md +0 -23
- package/.claude/commands/agent-vibes/learn.md +0 -67
- package/.claude/commands/agent-vibes/replay-target.md +0 -14
- package/.claude/commands/agent-vibes/target-voice.md +0 -26
- package/.claude/commands/agent-vibes/target.md +0 -30
- package/.claude/hooks/learn-manager.sh +0 -492
- package/.claude/hooks/replay-target-audio.sh +0 -95
- package/.claude/hooks-windows/learn-manager.ps1 +0 -241
package/src/console/app.js
CHANGED
|
@@ -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' | '
|
|
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
|
+
}
|
|
@@ -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 =
|
|
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 =
|
|
1146
|
+
const playTtsScript = _hookScript('hooks', 'play-tts.sh');
|
|
1135
1147
|
if (!fs.existsSync(playTtsScript)) return;
|
|
1136
1148
|
|
|
1137
1149
|
const remoteLlm = detectRemoteLlm();
|