agentvibes 4.6.0 → 4.6.3
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/.claude/config/audio-effects.cfg +1 -1
- package/.claude/hooks/audio-processor.sh +1 -1
- package/.claude/hooks-windows/bmad-party-speak.ps1 +81 -0
- package/.claude/hooks-windows/bmad-speak.ps1 +32 -7
- package/.claude/hooks-windows/play-tts-piper.ps1 +43 -6
- package/.claude/hooks-windows/play-tts.ps1 +57 -30
- package/.mcp.json +7 -0
- package/README.md +64 -2
- package/RELEASE_NOTES.md +41 -0
- package/package.json +110 -110
- package/src/console/tabs/agents-tab.js +240 -34
- package/src/console/tabs/voices-tab.js +38 -5
- package/src/console/widgets/track-picker.js +50 -18
- package/templates/agentvibes-receiver.sh +1 -1
package/package.json
CHANGED
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"name": "agentvibes",
|
|
4
|
-
"version": "4.6.
|
|
5
|
-
"description": "Now your AI Agents can finally talk back! Professional TTS voice for Claude Code, Claude Desktop (via MCP), and Clawdbot with multi-provider support.",
|
|
6
|
-
"homepage": "https://agentvibes.org",
|
|
7
|
-
"keywords": [
|
|
8
|
-
"tts",
|
|
9
|
-
"text-to-speech",
|
|
10
|
-
"piper-tts",
|
|
11
|
-
"claude-code",
|
|
12
|
-
"claude-desktop",
|
|
13
|
-
"clawdbot",
|
|
14
|
-
"mcp",
|
|
15
|
-
"model-context-protocol",
|
|
16
|
-
"voice",
|
|
17
|
-
"ai",
|
|
18
|
-
"narration",
|
|
19
|
-
"agent-vibes"
|
|
20
|
-
],
|
|
21
|
-
"repository": {
|
|
22
|
-
"type": "git",
|
|
23
|
-
"url": "git+https://github.com/paulpreibisch/AgentVibes.git"
|
|
24
|
-
},
|
|
25
|
-
"license": "Apache-2.0",
|
|
26
|
-
"author": "Paul Preibisch <paul@paulpreibisch.com>",
|
|
27
|
-
"type": "module",
|
|
28
|
-
"main": "src/installer.js",
|
|
29
|
-
"bin": {
|
|
30
|
-
"agentvibes": "bin/agentvibes.js",
|
|
31
|
-
"agent-vibes": "bin/agent-vibes",
|
|
32
|
-
"agentvibes-mcp-server": "bin/mcp-server.js",
|
|
33
|
-
"agentvibes-voice-browser": "bin/agentvibes-voice-browser.js",
|
|
34
|
-
"test-bmad-pr": "bin/test-bmad-pr"
|
|
35
|
-
},
|
|
36
|
-
"files": [
|
|
37
|
-
"bin/",
|
|
38
|
-
"src/",
|
|
39
|
-
"templates/*.sh",
|
|
40
|
-
"templates/*.md",
|
|
41
|
-
"templates/audio/*.mp3",
|
|
42
|
-
"mcp-server/*.py",
|
|
43
|
-
"mcp-server/*.js",
|
|
44
|
-
"mcp-server/*.md",
|
|
45
|
-
"mcp-server/*.txt",
|
|
46
|
-
"mcp-server/*.toml",
|
|
47
|
-
"mcp-server/*.json",
|
|
48
|
-
"mcp-server/docs/",
|
|
49
|
-
"mcp-server/examples/",
|
|
50
|
-
".claude/commands/agent-vibes/",
|
|
51
|
-
".claude/commands/agent-vibes-bmad-voices.md",
|
|
52
|
-
".claude/commands/agent-vibes-rdp.md",
|
|
53
|
-
".claude/hooks/",
|
|
54
|
-
".claude/hooks-windows/",
|
|
55
|
-
".claude/personalities/",
|
|
56
|
-
".claude/output-styles/",
|
|
57
|
-
".claude/audio/tracks/",
|
|
58
|
-
".claude/config/",
|
|
59
|
-
".claude/docs/",
|
|
60
|
-
".claude/language-voices.yaml",
|
|
61
|
-
".claude/settings.json",
|
|
62
|
-
".claude/activation-instructions",
|
|
63
|
-
".claude/piper-voices-dir.txt",
|
|
64
|
-
".claude/verbosity.txt",
|
|
65
|
-
".claude/github-star-reminder.txt",
|
|
66
|
-
".claude/audio/voice-samples/",
|
|
67
|
-
"voice-assignments.json",
|
|
68
|
-
".agentvibes/",
|
|
69
|
-
".clawdbot/",
|
|
70
|
-
".mcp.json",
|
|
71
|
-
"setup-windows.ps1",
|
|
72
|
-
"WINDOWS-SETUP.md",
|
|
73
|
-
"README.md",
|
|
74
|
-
"RELEASE_NOTES.md",
|
|
75
|
-
"LICENSE",
|
|
76
|
-
"CLAUDE.md"
|
|
77
|
-
],
|
|
78
|
-
"scripts": {
|
|
79
|
-
"install-local": "node src/installer.js install",
|
|
80
|
-
"postinstall": "node mcp-server/install-deps.js",
|
|
81
|
-
"install-mcp-deps": "node mcp-server/install-deps.js",
|
|
82
|
-
"voice-browser": "node bin/agentvibes-voice-browser.js",
|
|
83
|
-
"test": "npm run test:syntax && AGENTVIBES_TEST_MODE=true bats test/unit/*.bats && npm run test:coverage",
|
|
84
|
-
"test:syntax": "node -c src/installer.js && node -c mcp-server/install-deps.js",
|
|
85
|
-
"test:bats": "AGENTVIBES_TEST_MODE=true bats test/unit/*.bats",
|
|
86
|
-
"test:node": "node --test test/unit/*.test.js",
|
|
87
|
-
"test:coverage": "c8 --reporter=lcov --reporter=text node --test test/unit/*.test.js",
|
|
88
|
-
"test:verbose": "AGENTVIBES_TEST_MODE=true bats -t test/unit/*.bats"
|
|
89
|
-
},
|
|
90
|
-
"dependencies": {
|
|
91
|
-
"@inquirer/search": "^3.1.3",
|
|
92
|
-
"blessed": "^0.1.81",
|
|
93
|
-
"boxen": "^7.0.0",
|
|
94
|
-
"chalk": "^5.0.0",
|
|
95
|
-
"commander": "^10.0.0",
|
|
96
|
-
"figlet": "^1.6.0",
|
|
97
|
-
"inquirer": "^12.0.0",
|
|
98
|
-
"js-yaml": "^4.1.1",
|
|
99
|
-
"ora": "^6.0.0"
|
|
100
|
-
},
|
|
101
|
-
"engines": {
|
|
102
|
-
"node": ">=16.0.0"
|
|
103
|
-
},
|
|
104
|
-
"publishConfig": {
|
|
105
|
-
"access": "public"
|
|
106
|
-
},
|
|
107
|
-
"devDependencies": {
|
|
108
|
-
"c8": "^10.1.3"
|
|
109
|
-
}
|
|
110
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"name": "agentvibes",
|
|
4
|
+
"version": "4.6.3",
|
|
5
|
+
"description": "Now your AI Agents can finally talk back! Professional TTS voice for Claude Code, Claude Desktop (via MCP), and Clawdbot with multi-provider support.",
|
|
6
|
+
"homepage": "https://agentvibes.org",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"tts",
|
|
9
|
+
"text-to-speech",
|
|
10
|
+
"piper-tts",
|
|
11
|
+
"claude-code",
|
|
12
|
+
"claude-desktop",
|
|
13
|
+
"clawdbot",
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"voice",
|
|
17
|
+
"ai",
|
|
18
|
+
"narration",
|
|
19
|
+
"agent-vibes"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/paulpreibisch/AgentVibes.git"
|
|
24
|
+
},
|
|
25
|
+
"license": "Apache-2.0",
|
|
26
|
+
"author": "Paul Preibisch <paul@paulpreibisch.com>",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "src/installer.js",
|
|
29
|
+
"bin": {
|
|
30
|
+
"agentvibes": "bin/agentvibes.js",
|
|
31
|
+
"agent-vibes": "bin/agent-vibes",
|
|
32
|
+
"agentvibes-mcp-server": "bin/mcp-server.js",
|
|
33
|
+
"agentvibes-voice-browser": "bin/agentvibes-voice-browser.js",
|
|
34
|
+
"test-bmad-pr": "bin/test-bmad-pr"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"bin/",
|
|
38
|
+
"src/",
|
|
39
|
+
"templates/*.sh",
|
|
40
|
+
"templates/*.md",
|
|
41
|
+
"templates/audio/*.mp3",
|
|
42
|
+
"mcp-server/*.py",
|
|
43
|
+
"mcp-server/*.js",
|
|
44
|
+
"mcp-server/*.md",
|
|
45
|
+
"mcp-server/*.txt",
|
|
46
|
+
"mcp-server/*.toml",
|
|
47
|
+
"mcp-server/*.json",
|
|
48
|
+
"mcp-server/docs/",
|
|
49
|
+
"mcp-server/examples/",
|
|
50
|
+
".claude/commands/agent-vibes/",
|
|
51
|
+
".claude/commands/agent-vibes-bmad-voices.md",
|
|
52
|
+
".claude/commands/agent-vibes-rdp.md",
|
|
53
|
+
".claude/hooks/",
|
|
54
|
+
".claude/hooks-windows/",
|
|
55
|
+
".claude/personalities/",
|
|
56
|
+
".claude/output-styles/",
|
|
57
|
+
".claude/audio/tracks/",
|
|
58
|
+
".claude/config/",
|
|
59
|
+
".claude/docs/",
|
|
60
|
+
".claude/language-voices.yaml",
|
|
61
|
+
".claude/settings.json",
|
|
62
|
+
".claude/activation-instructions",
|
|
63
|
+
".claude/piper-voices-dir.txt",
|
|
64
|
+
".claude/verbosity.txt",
|
|
65
|
+
".claude/github-star-reminder.txt",
|
|
66
|
+
".claude/audio/voice-samples/",
|
|
67
|
+
"voice-assignments.json",
|
|
68
|
+
".agentvibes/",
|
|
69
|
+
".clawdbot/",
|
|
70
|
+
".mcp.json",
|
|
71
|
+
"setup-windows.ps1",
|
|
72
|
+
"WINDOWS-SETUP.md",
|
|
73
|
+
"README.md",
|
|
74
|
+
"RELEASE_NOTES.md",
|
|
75
|
+
"LICENSE",
|
|
76
|
+
"CLAUDE.md"
|
|
77
|
+
],
|
|
78
|
+
"scripts": {
|
|
79
|
+
"install-local": "node src/installer.js install",
|
|
80
|
+
"postinstall": "node mcp-server/install-deps.js",
|
|
81
|
+
"install-mcp-deps": "node mcp-server/install-deps.js",
|
|
82
|
+
"voice-browser": "node bin/agentvibes-voice-browser.js",
|
|
83
|
+
"test": "npm run test:syntax && AGENTVIBES_TEST_MODE=true bats test/unit/*.bats && npm run test:coverage",
|
|
84
|
+
"test:syntax": "node -c src/installer.js && node -c mcp-server/install-deps.js",
|
|
85
|
+
"test:bats": "AGENTVIBES_TEST_MODE=true bats test/unit/*.bats",
|
|
86
|
+
"test:node": "node --test test/unit/*.test.js",
|
|
87
|
+
"test:coverage": "c8 --reporter=lcov --reporter=text node --test test/unit/*.test.js",
|
|
88
|
+
"test:verbose": "AGENTVIBES_TEST_MODE=true bats -t test/unit/*.bats"
|
|
89
|
+
},
|
|
90
|
+
"dependencies": {
|
|
91
|
+
"@inquirer/search": "^3.1.3",
|
|
92
|
+
"blessed": "^0.1.81",
|
|
93
|
+
"boxen": "^7.0.0",
|
|
94
|
+
"chalk": "^5.0.0",
|
|
95
|
+
"commander": "^10.0.0",
|
|
96
|
+
"figlet": "^1.6.0",
|
|
97
|
+
"inquirer": "^12.0.0",
|
|
98
|
+
"js-yaml": "^4.1.1",
|
|
99
|
+
"ora": "^6.0.0"
|
|
100
|
+
},
|
|
101
|
+
"engines": {
|
|
102
|
+
"node": ">=16.0.0"
|
|
103
|
+
},
|
|
104
|
+
"publishConfig": {
|
|
105
|
+
"access": "public"
|
|
106
|
+
},
|
|
107
|
+
"devDependencies": {
|
|
108
|
+
"c8": "^10.1.3"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -31,6 +31,84 @@ import { spawn } from 'node:child_process';
|
|
|
31
31
|
// Max pretext length to prevent excessively long TTS utterances
|
|
32
32
|
const MAX_PRETEXT_LENGTH = 200;
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Attach a blinking █ cursor to a set of blessed buttons.
|
|
36
|
+
* Works alongside existing focus/blur handlers (e.g. ►..◄ indicators).
|
|
37
|
+
* While a spinner is active on a button, blink is paused for that button.
|
|
38
|
+
* Returns { cleanup, startSpinner(btn, screen), stopSpinner(btn, screen) }.
|
|
39
|
+
*/
|
|
40
|
+
export function attachBtnBlink(btns, screen) {
|
|
41
|
+
let _interval = null;
|
|
42
|
+
let _on = true;
|
|
43
|
+
let _active = null;
|
|
44
|
+
let _spinning = null; // button currently showing a spinner
|
|
45
|
+
|
|
46
|
+
const _SPIN = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
47
|
+
let _spinIdx = 0;
|
|
48
|
+
let _spinInterval = null;
|
|
49
|
+
|
|
50
|
+
// Store original label on each button at attach time — never derive from current content
|
|
51
|
+
btns.forEach(btn => { btn._blinkBase = btn.content; });
|
|
52
|
+
|
|
53
|
+
// Focused with indicator ch right after ► e.g. ►█Preview◄ / ► Preview◄ (same width)
|
|
54
|
+
function _focused(base, ch) { return `►${ch}${base}◄`; }
|
|
55
|
+
|
|
56
|
+
function _tick() {
|
|
57
|
+
if (!_active || _active === _spinning) return;
|
|
58
|
+
_on = !_on;
|
|
59
|
+
_active.setContent(_focused(_active._blinkBase, _on ? '█' : ' '));
|
|
60
|
+
screen.render();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
btns.forEach(btn => {
|
|
64
|
+
btn.on('focus', () => {
|
|
65
|
+
_active = btn;
|
|
66
|
+
_on = true;
|
|
67
|
+
if (btn !== _spinning) {
|
|
68
|
+
btn.setContent(_focused(btn._blinkBase, '█'));
|
|
69
|
+
screen.render();
|
|
70
|
+
}
|
|
71
|
+
if (!_interval) _interval = setInterval(_tick, 500);
|
|
72
|
+
});
|
|
73
|
+
btn.on('blur', () => {
|
|
74
|
+
if (_active !== btn) return;
|
|
75
|
+
_active = null;
|
|
76
|
+
if (_interval) { clearInterval(_interval); _interval = null; _on = true; }
|
|
77
|
+
if (btn !== _spinning) {
|
|
78
|
+
btn.setContent(btn._blinkBase);
|
|
79
|
+
screen.render();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
function startSpinner(btn) {
|
|
85
|
+
_spinning = btn;
|
|
86
|
+
_spinIdx = 0;
|
|
87
|
+
if (_spinInterval) clearInterval(_spinInterval);
|
|
88
|
+
_spinInterval = setInterval(() => {
|
|
89
|
+
_spinIdx = (_spinIdx + 1) % _SPIN.length;
|
|
90
|
+
btn.setContent(_active === btn
|
|
91
|
+
? _focused(btn._blinkBase, _SPIN[_spinIdx])
|
|
92
|
+
: `${_SPIN[_spinIdx]}${btn._blinkBase}`);
|
|
93
|
+
screen.render();
|
|
94
|
+
}, 80);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function stopSpinner(btn) {
|
|
98
|
+
if (_spinInterval) { clearInterval(_spinInterval); _spinInterval = null; }
|
|
99
|
+
_spinning = null;
|
|
100
|
+
btn.setContent(_active === btn ? _focused(btn._blinkBase, '█') : btn._blinkBase);
|
|
101
|
+
screen.render();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function cleanup() {
|
|
105
|
+
if (_interval) { clearInterval(_interval); _interval = null; }
|
|
106
|
+
if (_spinInterval){ clearInterval(_spinInterval); _spinInterval = null; }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { cleanup, startSpinner, stopSpinner };
|
|
110
|
+
}
|
|
111
|
+
|
|
34
112
|
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
35
113
|
|
|
36
114
|
let blessed;
|
|
@@ -547,7 +625,7 @@ ${_tl('bmadDesc')}
|
|
|
547
625
|
top: 'center',
|
|
548
626
|
left: 'center',
|
|
549
627
|
width: 72,
|
|
550
|
-
height:
|
|
628
|
+
height: 19,
|
|
551
629
|
border: { type: 'line' },
|
|
552
630
|
tags: true,
|
|
553
631
|
label: _modalTitle(`${agent.icon || '🧙'} ${agent.displayName} (${agent.title || 'Agent'})`),
|
|
@@ -569,9 +647,13 @@ ${_tl('bmadDesc')}
|
|
|
569
647
|
const emoji = PERSONALITY_EMOJIS[p] || '';
|
|
570
648
|
return `${emoji} ${p === 'none' ? 'None' : p.charAt(0).toUpperCase() + p.slice(1)}`;
|
|
571
649
|
}},
|
|
572
|
-
{ key: '
|
|
650
|
+
{ key: 'musicTrack', label: 'Music Track', getValue: () => {
|
|
651
|
+
if (!draft.backgroundMusic.enabled) return '(disabled)';
|
|
652
|
+
return formatTrackName(draft.backgroundMusic.track) || '(none)';
|
|
653
|
+
}},
|
|
654
|
+
{ key: 'musicVol', label: 'Music Vol', getValue: () => {
|
|
573
655
|
if (!draft.backgroundMusic.enabled) return '(disabled)';
|
|
574
|
-
return `${
|
|
656
|
+
return `${draft.backgroundMusic.volume ?? 20}%`;
|
|
575
657
|
}},
|
|
576
658
|
];
|
|
577
659
|
|
|
@@ -612,7 +694,7 @@ ${_tl('bmadDesc')}
|
|
|
612
694
|
left: 2,
|
|
613
695
|
right: 2,
|
|
614
696
|
tags: true,
|
|
615
|
-
content: '{
|
|
697
|
+
content: '{white-fg}[↑↓] Navigate [Enter] Edit [Tab] → Preview/Save [Esc] Cancel{/white-fg}',
|
|
616
698
|
style: { bg: COLORS.contentBg },
|
|
617
699
|
});
|
|
618
700
|
|
|
@@ -634,22 +716,20 @@ ${_tl('bmadDesc')}
|
|
|
634
716
|
hover: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
635
717
|
},
|
|
636
718
|
});
|
|
637
|
-
|
|
638
|
-
const raw = btn.content.replace(/[►◄]/g, '').trim();
|
|
639
|
-
btn.setContent(`►${raw}◄`);
|
|
640
|
-
screen.render();
|
|
641
|
-
});
|
|
642
|
-
btn.on('blur', () => {
|
|
643
|
-
const raw = btn.content.replace(/[►◄]/g, '').trim();
|
|
644
|
-
btn.setContent(raw);
|
|
645
|
-
screen.render();
|
|
646
|
-
});
|
|
719
|
+
// Focus indicator handled by attachBtnBlink
|
|
647
720
|
btn.key(['enter', 'space'], () => onClick());
|
|
648
721
|
btn.on('click', () => onClick());
|
|
649
722
|
return btn;
|
|
650
723
|
}
|
|
651
724
|
|
|
652
|
-
const
|
|
725
|
+
const previewBtn = _modalBtn('Preview', 4, () => {
|
|
726
|
+
_sampleAgentWithDraft({ ...agent }, draft, () => {
|
|
727
|
+
btnBlink.stopSpinner(previewBtn);
|
|
728
|
+
});
|
|
729
|
+
btnBlink.startSpinner(previewBtn);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
const saveBtn = _modalBtn('Save', 18, () => {
|
|
653
733
|
// Only save fields that differ from global
|
|
654
734
|
const toSave = {};
|
|
655
735
|
if (draft.voice && draft.voice !== globalCfg.voice) toSave.voice = draft.voice;
|
|
@@ -668,20 +748,26 @@ ${_tl('bmadDesc')}
|
|
|
668
748
|
_showSavedToast(agent.displayName);
|
|
669
749
|
});
|
|
670
750
|
|
|
671
|
-
const resetAllBtn = _modalBtn('Reset to Defaults',
|
|
751
|
+
const resetAllBtn = _modalBtn('Reset to Defaults', 26, () => {
|
|
672
752
|
voiceStore.resetAgentProfile(agent.id);
|
|
673
753
|
_closeModal();
|
|
674
754
|
refreshDisplay();
|
|
675
755
|
});
|
|
676
756
|
|
|
677
|
-
const cancelBtn = _modalBtn('Cancel',
|
|
757
|
+
const cancelBtn = _modalBtn('Cancel', 50, _closeModal);
|
|
758
|
+
|
|
759
|
+
// Blinking █ cursor + preview spinner — reusable across all modal buttons
|
|
760
|
+
const btnBlink = attachBtnBlink([previewBtn, saveBtn, resetAllBtn, cancelBtn], screen);
|
|
678
761
|
|
|
679
762
|
function _closeModal() {
|
|
680
763
|
if (_closed) return;
|
|
681
764
|
_closed = true;
|
|
682
765
|
_killPreview();
|
|
766
|
+
btnBlink.cleanup();
|
|
683
767
|
navigationService?.closeModal();
|
|
684
768
|
destroyList(modal, screen);
|
|
769
|
+
agentList.focus();
|
|
770
|
+
screen.render();
|
|
685
771
|
}
|
|
686
772
|
|
|
687
773
|
// Field editing via Enter
|
|
@@ -735,10 +821,9 @@ ${_tl('bmadDesc')}
|
|
|
735
821
|
});
|
|
736
822
|
break;
|
|
737
823
|
|
|
738
|
-
case '
|
|
739
|
-
openTrackPicker(screen, draft.backgroundMusic.track, draft.backgroundMusic.volume, (track
|
|
824
|
+
case 'musicTrack':
|
|
825
|
+
openTrackPicker(screen, draft.backgroundMusic.track, draft.backgroundMusic.volume, (track) => {
|
|
740
826
|
draft.backgroundMusic.track = track;
|
|
741
|
-
draft.backgroundMusic.volume = volume;
|
|
742
827
|
draft.backgroundMusic.enabled = true;
|
|
743
828
|
fieldList.setItems(_fieldItems());
|
|
744
829
|
fieldList.select(idx);
|
|
@@ -747,6 +832,20 @@ ${_tl('bmadDesc')}
|
|
|
747
832
|
}, () => {
|
|
748
833
|
fieldList.focus();
|
|
749
834
|
screen.render();
|
|
835
|
+
}, { skipVolume: true });
|
|
836
|
+
break;
|
|
837
|
+
|
|
838
|
+
case 'musicVol':
|
|
839
|
+
openVolumeInput(screen, draft.backgroundMusic.volume, (volume) => {
|
|
840
|
+
draft.backgroundMusic.volume = volume;
|
|
841
|
+
if (draft.backgroundMusic.track) draft.backgroundMusic.enabled = true;
|
|
842
|
+
fieldList.setItems(_fieldItems());
|
|
843
|
+
fieldList.select(idx);
|
|
844
|
+
fieldList.focus();
|
|
845
|
+
screen.render();
|
|
846
|
+
}, () => {
|
|
847
|
+
fieldList.focus();
|
|
848
|
+
screen.render();
|
|
750
849
|
});
|
|
751
850
|
break;
|
|
752
851
|
}
|
|
@@ -761,15 +860,50 @@ ${_tl('bmadDesc')}
|
|
|
761
860
|
|
|
762
861
|
// Escape = close
|
|
763
862
|
fieldList.key(['escape', 'q'], _closeModal);
|
|
863
|
+
previewBtn.key(['escape'], _closeModal);
|
|
764
864
|
saveBtn.key(['escape'], _closeModal);
|
|
765
865
|
resetAllBtn.key(['escape'], _closeModal);
|
|
766
866
|
cancelBtn.key(['escape'], _closeModal);
|
|
767
867
|
|
|
768
|
-
// Tab navigation within modal
|
|
769
|
-
fieldList.key(['tab'], () => {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
868
|
+
// Tab + arrow navigation within modal
|
|
869
|
+
fieldList.key(['tab'], () => { previewBtn.focus(); screen.render(); });
|
|
870
|
+
|
|
871
|
+
// Wrap: down on last field → focus first button; up on first field → focus first button
|
|
872
|
+
// One extra arrow press at boundary moves to button row.
|
|
873
|
+
// Track previous selection so arriving at boundary doesn't immediately jump.
|
|
874
|
+
let _prevFieldSel = 0;
|
|
875
|
+
fieldList.key(['down'], () => {
|
|
876
|
+
const cur = fieldList.selected ?? 0;
|
|
877
|
+
if (cur === FIELDS.length - 1 && _prevFieldSel === FIELDS.length - 1) {
|
|
878
|
+
previewBtn.focus(); screen.render();
|
|
879
|
+
}
|
|
880
|
+
_prevFieldSel = cur;
|
|
881
|
+
});
|
|
882
|
+
fieldList.key(['up'], () => {
|
|
883
|
+
const cur = fieldList.selected ?? 0;
|
|
884
|
+
if (cur === 0 && _prevFieldSel === 0) {
|
|
885
|
+
previewBtn.focus(); screen.render();
|
|
886
|
+
}
|
|
887
|
+
_prevFieldSel = cur;
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
// Wrap: up on buttons → back to field list (last/first field respectively)
|
|
891
|
+
previewBtn.key(['up'], () => { fieldList.focus(); fieldList.select(FIELDS.length - 1); screen.render(); });
|
|
892
|
+
saveBtn.key(['up'], () => { fieldList.focus(); fieldList.select(FIELDS.length - 1); screen.render(); });
|
|
893
|
+
resetAllBtn.key(['up'], () => { fieldList.focus(); fieldList.select(FIELDS.length - 1); screen.render(); });
|
|
894
|
+
cancelBtn.key(['up'], () => { fieldList.focus(); fieldList.select(FIELDS.length - 1); screen.render(); });
|
|
895
|
+
|
|
896
|
+
previewBtn.key(['tab', 'right'], () => { saveBtn.focus(); screen.render(); });
|
|
897
|
+
previewBtn.key(['left'], () => { cancelBtn.focus(); screen.render(); });
|
|
898
|
+
|
|
899
|
+
saveBtn.key(['tab', 'right'], () => { resetAllBtn.focus(); screen.render(); });
|
|
900
|
+
saveBtn.key(['left'], () => { previewBtn.focus(); screen.render(); });
|
|
901
|
+
|
|
902
|
+
resetAllBtn.key(['tab', 'right'], () => { cancelBtn.focus(); screen.render(); });
|
|
903
|
+
resetAllBtn.key(['left'], () => { saveBtn.focus(); screen.render(); });
|
|
904
|
+
|
|
905
|
+
cancelBtn.key(['tab', 'right'], () => { fieldList.focus(); screen.render(); });
|
|
906
|
+
cancelBtn.key(['left'], () => { resetAllBtn.focus(); screen.render(); });
|
|
773
907
|
|
|
774
908
|
fieldList.focus();
|
|
775
909
|
screen.render();
|
|
@@ -1047,8 +1181,8 @@ ${_tl('bmadDesc')}
|
|
|
1047
1181
|
// -------------------------------------------------------------------------
|
|
1048
1182
|
// Sample agent with a draft profile (no save) — same full pipeline
|
|
1049
1183
|
|
|
1050
|
-
function _sampleAgentWithDraft(agent, draft) {
|
|
1051
|
-
_sampleWithFullProfile(agent, draft);
|
|
1184
|
+
function _sampleAgentWithDraft(agent, draft, onComplete) {
|
|
1185
|
+
_sampleWithFullProfile(agent, draft, onComplete);
|
|
1052
1186
|
}
|
|
1053
1187
|
|
|
1054
1188
|
// -------------------------------------------------------------------------
|
|
@@ -1056,7 +1190,7 @@ ${_tl('bmadDesc')}
|
|
|
1056
1190
|
// Writes a temp agent profile JSON, then calls the enhanced TTS pipeline
|
|
1057
1191
|
// which applies voice + reverb + background music.
|
|
1058
1192
|
|
|
1059
|
-
function _sampleWithFullProfile(agent, profile) {
|
|
1193
|
+
function _sampleWithFullProfile(agent, profile, onComplete) {
|
|
1060
1194
|
_killPreview();
|
|
1061
1195
|
const gen = ++_playGeneration;
|
|
1062
1196
|
|
|
@@ -1071,9 +1205,9 @@ ${_tl('bmadDesc')}
|
|
|
1071
1205
|
const isWindows = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
|
|
1072
1206
|
|
|
1073
1207
|
if (isWindows) {
|
|
1074
|
-
// On Windows,
|
|
1075
|
-
//
|
|
1076
|
-
|
|
1208
|
+
// On Windows, call play-tts.ps1 via PowerShell with draft settings patched
|
|
1209
|
+
// in so reverb, background music, and personality are applied.
|
|
1210
|
+
_sampleWithFullEffectsWindows(gen, agent, profile, phrase, onComplete);
|
|
1077
1211
|
} else {
|
|
1078
1212
|
// On Linux/macOS/WSL, use play-tts.sh
|
|
1079
1213
|
const _spawnEnv = buildAudioEnv();
|
|
@@ -1178,6 +1312,77 @@ ${_tl('bmadDesc')}
|
|
|
1178
1312
|
});
|
|
1179
1313
|
}
|
|
1180
1314
|
|
|
1315
|
+
/** Windows full-effects preview: temporarily patches config files then calls play-tts.ps1 */
|
|
1316
|
+
function _sampleWithFullEffectsWindows(gen, agent, profile, phrase, onComplete) {
|
|
1317
|
+
const _spawnEnv = buildAudioEnv();
|
|
1318
|
+
const homeDir = process.env.USERPROFILE || os.homedir();
|
|
1319
|
+
// Use project-local .claude if it exists, else global ~/.claude
|
|
1320
|
+
const claudeDir = fs.existsSync(path.join(_projectRoot, '.claude'))
|
|
1321
|
+
? path.join(_projectRoot, '.claude')
|
|
1322
|
+
: path.join(homeDir, '.claude');
|
|
1323
|
+
const configDir = path.join(claudeDir, 'config');
|
|
1324
|
+
const hooksDir = path.join(claudeDir, 'hooks-windows');
|
|
1325
|
+
const playTts = path.join(hooksDir, 'play-tts.ps1');
|
|
1326
|
+
if (!fs.existsSync(playTts)) { _sampleWithPiperDirect(gen, profile.voice || '', phrase); return; }
|
|
1327
|
+
|
|
1328
|
+
// Files to temporarily patch
|
|
1329
|
+
const personalityFile = path.join(configDir, 'personality.txt');
|
|
1330
|
+
const reverbFile = path.join(configDir, 'reverb-level.txt');
|
|
1331
|
+
const bgEnabledFile = path.join(configDir, 'background-music-enabled.txt');
|
|
1332
|
+
const audioEffectsCfg = path.join(configDir, 'audio-effects.cfg');
|
|
1333
|
+
|
|
1334
|
+
// Save originals
|
|
1335
|
+
const _read = f => { try { return fs.readFileSync(f, 'utf8'); } catch { return null; } };
|
|
1336
|
+
const origPersonality = _read(personalityFile);
|
|
1337
|
+
const origReverb = _read(reverbFile);
|
|
1338
|
+
const origBgEnabled = _read(bgEnabledFile);
|
|
1339
|
+
const origAudioEffects = _read(audioEffectsCfg);
|
|
1340
|
+
|
|
1341
|
+
const bgMusic = profile.backgroundMusic;
|
|
1342
|
+
let tempCfgLine = '';
|
|
1343
|
+
|
|
1344
|
+
try {
|
|
1345
|
+
if (profile.personality && profile.personality !== 'none')
|
|
1346
|
+
fs.writeFileSync(personalityFile, profile.personality);
|
|
1347
|
+
if (profile.reverbPreset)
|
|
1348
|
+
fs.writeFileSync(reverbFile, profile.reverbPreset);
|
|
1349
|
+
if (bgMusic?.enabled && bgMusic?.track) {
|
|
1350
|
+
fs.writeFileSync(bgEnabledFile, 'true');
|
|
1351
|
+
const vol = ((bgMusic.volume ?? 20) / 100).toFixed(2);
|
|
1352
|
+
tempCfgLine = `${agent.id}||${bgMusic.track}|${vol}`;
|
|
1353
|
+
fs.writeFileSync(audioEffectsCfg, `${tempCfgLine}\n${origAudioEffects || ''}`);
|
|
1354
|
+
}
|
|
1355
|
+
} catch { /* degrade gracefully */ }
|
|
1356
|
+
|
|
1357
|
+
const voiceId = profile.voice || '';
|
|
1358
|
+
const psArgs = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', playTts, phrase];
|
|
1359
|
+
if (voiceId) psArgs.push(voiceId);
|
|
1360
|
+
|
|
1361
|
+
const proc = spawn('powershell', psArgs, {
|
|
1362
|
+
stdio: 'ignore', detached: false, windowsHide: true,
|
|
1363
|
+
// CLAUDE_PROJECT_DIR tells play-tts.ps1 to use the project's .claude/config
|
|
1364
|
+
// rather than falling back to ~/.claude where our patches don't exist.
|
|
1365
|
+
env: { ..._spawnEnv, AGENTVIBES_AGENT_NAME: agent.id, CLAUDE_PROJECT_DIR: _projectRoot },
|
|
1366
|
+
});
|
|
1367
|
+
_playingProcess = proc;
|
|
1368
|
+
|
|
1369
|
+
function _restore() {
|
|
1370
|
+
try {
|
|
1371
|
+
if (origPersonality !== null) fs.writeFileSync(personalityFile, origPersonality);
|
|
1372
|
+
else try { fs.unlinkSync(personalityFile); } catch {}
|
|
1373
|
+
if (origReverb !== null) fs.writeFileSync(reverbFile, origReverb);
|
|
1374
|
+
if (bgMusic?.enabled && bgMusic?.track) {
|
|
1375
|
+
if (origBgEnabled !== null) fs.writeFileSync(bgEnabledFile, origBgEnabled);
|
|
1376
|
+
else try { fs.unlinkSync(bgEnabledFile); } catch {}
|
|
1377
|
+
if (origAudioEffects !== null) fs.writeFileSync(audioEffectsCfg, origAudioEffects);
|
|
1378
|
+
}
|
|
1379
|
+
} catch {}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
proc.on('exit', () => { if (gen === _playGeneration) { _playingProcess = null; _stopSpinner(); } _restore(); if (onComplete) onComplete(); });
|
|
1383
|
+
proc.on('error', () => { if (gen === _playGeneration) { _playingProcess = null; _stopSpinner(); } _restore(); if (onComplete) onComplete(); });
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1181
1386
|
// -------------------------------------------------------------------------
|
|
1182
1387
|
// Auto-assign helpers
|
|
1183
1388
|
|
|
@@ -1199,9 +1404,10 @@ ${_tl('bmadDesc')}
|
|
|
1199
1404
|
grace: 'Female', heather: 'Female', ivy: 'Female', jane: 'Female',
|
|
1200
1405
|
jenny: 'Female', julia: 'Female', kate: 'Female', laura: 'Female',
|
|
1201
1406
|
lily: 'Female', maria: 'Female', mary: 'Female', nina: 'Female',
|
|
1202
|
-
olivia: 'Female', paige: 'Female',
|
|
1203
|
-
|
|
1204
|
-
wendy: 'Female', zoe: 'Female',
|
|
1407
|
+
olivia: 'Female', paige: 'Female', quinn: 'Female', rachel: 'Female',
|
|
1408
|
+
sally: 'Female', sara: 'Female', sarah: 'Female', sophie: 'Female',
|
|
1409
|
+
tina: 'Female', wendy: 'Female', zoe: 'Female',
|
|
1410
|
+
freya: 'Female', saga: 'Female',
|
|
1205
1411
|
// Male
|
|
1206
1412
|
alan: 'Male', barry: 'Male', bob: 'Male', carl: 'Male',
|
|
1207
1413
|
charlie: 'Male', dan: 'Male', david: 'Male', eric: 'Male',
|