agentvibes 4.2.0 → 4.4.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/bmad/bmad-voices.md +69 -69
- package/.agentvibes/config.json +12 -0
- package/.claude/activation-instructions +54 -54
- package/.claude/audio/tracks/README.md +52 -52
- package/.claude/commands/agent-vibes/add.md +21 -21
- package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
- package/.claude/commands/agent-vibes/agent.md +79 -79
- package/.claude/commands/agent-vibes/background-music.md +111 -111
- package/.claude/commands/agent-vibes/bmad.md +198 -198
- package/.claude/commands/agent-vibes/clean.md +18 -18
- package/.claude/commands/agent-vibes/cleanup.md +18 -18
- package/.claude/commands/agent-vibes/commands.json +145 -145
- package/.claude/commands/agent-vibes/effects.md +97 -97
- package/.claude/commands/agent-vibes/get.md +9 -9
- package/.claude/commands/agent-vibes/hide.md +91 -91
- package/.claude/commands/agent-vibes/language.md +23 -23
- package/.claude/commands/agent-vibes/learn.md +67 -67
- package/.claude/commands/agent-vibes/list.md +13 -13
- package/.claude/commands/agent-vibes/mute.md +37 -37
- package/.claude/commands/agent-vibes/preview.md +17 -17
- package/.claude/commands/agent-vibes/provider.md +68 -68
- package/.claude/commands/agent-vibes/replay-target.md +14 -14
- package/.claude/commands/agent-vibes/sample.md +12 -12
- package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
- package/.claude/commands/agent-vibes/set-pretext.md +65 -65
- package/.claude/commands/agent-vibes/set-speed.md +41 -41
- package/.claude/commands/agent-vibes/show.md +84 -84
- package/.claude/commands/agent-vibes/switch.md +87 -87
- package/.claude/commands/agent-vibes/target-voice.md +26 -26
- package/.claude/commands/agent-vibes/target.md +30 -30
- package/.claude/commands/agent-vibes/translate.md +68 -68
- package/.claude/commands/agent-vibes/unmute.md +45 -45
- package/.claude/commands/agent-vibes/verbosity.md +89 -89
- package/.claude/commands/agent-vibes/whoami.md +7 -7
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/agentvibes.json +1 -0
- package/.claude/config/audio-effects.cfg +2 -2
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-volume.txt +1 -0
- package/.claude/config/intro-text.txt +1 -0
- package/.claude/config/piper-speech-rate.txt +4 -0
- package/.claude/config/piper-target-speech-rate.txt +1 -0
- package/.claude/config/reverb-level.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +4 -0
- package/.claude/config/tts-target-speech-rate.txt +1 -0
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/README-TTS-QUEUE.md +135 -135
- package/.claude/hooks/audio-cache-utils.sh +246 -246
- package/.claude/hooks/audio-processor.sh +433 -433
- package/.claude/hooks/background-music-manager.sh +404 -404
- package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
- package/.claude/hooks/bmad-speak.sh +269 -269
- package/.claude/hooks/bmad-tts-injector.sh +568 -568
- package/.claude/hooks/bmad-voice-manager.sh +928 -928
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
- package/.claude/hooks/clawdbot-receiver.sh +107 -107
- package/.claude/hooks/clean-audio-cache.sh +22 -22
- package/.claude/hooks/cleanup-cache.sh +106 -106
- package/.claude/hooks/configure-rdp-mode.sh +137 -137
- package/.claude/hooks/download-extra-voices.sh +244 -244
- package/.claude/hooks/effects-manager.sh +268 -268
- package/.claude/hooks/github-star-reminder.sh +154 -154
- package/.claude/hooks/language-manager.sh +362 -362
- package/.claude/hooks/learn-manager.sh +492 -492
- package/.claude/hooks/macos-voice-manager.sh +205 -205
- package/.claude/hooks/migrate-background-music.sh +125 -125
- package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
- package/.claude/hooks/optimize-background-music.sh +87 -87
- package/.claude/hooks/path-resolver.sh +60 -60
- package/.claude/hooks/personality-manager.sh +448 -448
- package/.claude/hooks/piper-download-voices.sh +225 -225
- package/.claude/hooks/piper-installer.sh +292 -292
- package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
- package/.claude/hooks/piper-voice-manager.sh +24 -3
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -90
- package/.claude/hooks/play-tts-enhanced.sh +105 -105
- package/.claude/hooks/play-tts-macos.sh +368 -368
- package/.claude/hooks/play-tts-piper.sh +679 -679
- package/.claude/hooks/play-tts-soprano.sh +356 -356
- package/.claude/hooks/play-tts-ssh-remote.sh +167 -167
- package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
- package/.claude/hooks/play-tts.sh +301 -301
- package/.claude/hooks/prepare-release.sh +54 -54
- package/.claude/hooks/provider-commands.sh +617 -617
- package/.claude/hooks/provider-manager.sh +399 -399
- package/.claude/hooks/replay-target-audio.sh +95 -95
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +201 -201
- package/.claude/hooks/session-start-tts.sh +81 -81
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +291 -291
- package/.claude/hooks/stop-tts.sh +84 -84
- package/.claude/hooks/termux-installer.sh +261 -261
- package/.claude/hooks/translate-manager.sh +341 -341
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks/tts-queue-worker.sh +145 -145
- package/.claude/hooks/tts-queue.sh +165 -165
- package/.claude/hooks/verbosity-manager.sh +178 -178
- package/.claude/hooks/voice-manager.sh +548 -548
- package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
- package/.claude/hooks-windows/background-music-manager.ps1 +348 -0
- package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -0
- package/.claude/hooks-windows/download-extra-voices.ps1 +185 -0
- package/.claude/hooks-windows/effects-manager.ps1 +294 -0
- package/.claude/hooks-windows/language-manager.ps1 +193 -0
- package/.claude/hooks-windows/learn-manager.ps1 +241 -0
- package/.claude/hooks-windows/personality-manager.ps1 +266 -0
- package/.claude/hooks-windows/play-tts-piper.ps1 +209 -0
- package/.claude/hooks-windows/play-tts-sapi.ps1 +108 -0
- package/.claude/hooks-windows/play-tts-soprano.ps1 +159 -158
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +50 -5
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
- package/.claude/hooks-windows/play-tts.ps1 +344 -266
- package/.claude/hooks-windows/provider-manager.ps1 +29 -10
- package/.claude/hooks-windows/session-start-tts.ps1 +124 -124
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/hooks-windows/speed-manager.ps1 +166 -0
- package/.claude/hooks-windows/verbosity-manager.ps1 +119 -0
- package/.claude/hooks-windows/voice-manager-windows.ps1 +92 -8
- package/.claude/output-styles/agent-vibes.md +202 -202
- package/.claude/personalities/angry.md +14 -14
- package/.claude/personalities/annoying.md +14 -14
- package/.claude/personalities/crass.md +14 -14
- package/.claude/personalities/dramatic.md +14 -14
- package/.claude/personalities/dry-humor.md +50 -50
- package/.claude/personalities/flirty.md +20 -20
- package/.claude/personalities/funny.md +14 -14
- package/.claude/personalities/grandpa.md +32 -32
- package/.claude/personalities/millennial.md +14 -14
- package/.claude/personalities/moody.md +14 -14
- package/.claude/personalities/normal.md +16 -16
- package/.claude/personalities/pirate.md +14 -14
- package/.claude/personalities/poetic.md +14 -14
- package/.claude/personalities/professional.md +14 -14
- package/.claude/personalities/rapper.md +55 -55
- package/.claude/personalities/robot.md +14 -14
- package/.claude/personalities/sarcastic.md +38 -38
- package/.claude/personalities/sassy.md +14 -14
- package/.claude/personalities/surfer-dude.md +14 -14
- package/.claude/personalities/zen.md +14 -14
- package/.claude/settings.json +15 -15
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.clawdbot/skill/SKILL.md +241 -241
- package/.mcp.json +12 -0
- package/CLAUDE.md +170 -170
- package/README.md +2029 -2007
- package/RELEASE_NOTES.md +1310 -1203
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +1840 -1840
- package/bin/agentvibes.js +48 -2
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +206 -206
- 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/WINDOWS_SETUP.md +260 -260
- package/mcp-server/docs/troubleshooting-audio.md +313 -313
- 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/install-deps.js +130 -130
- package/mcp-server/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +1465 -1453
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +336 -0
- package/package.json +110 -110
- package/setup-windows.ps1 +815 -815
- package/src/bmad-detector.js +71 -71
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/commands/install-mcp.js +476 -476
- package/src/console/app.js +824 -824
- package/src/console/audio-env.js +20 -1
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/footer-config.js +50 -50
- package/src/console/modals/modal-overlay.js +247 -247
- package/src/console/navigation.js +62 -62
- package/src/console/tabs/agents-tab.js +1684 -1516
- package/src/console/tabs/help-tab.js +261 -261
- package/src/console/tabs/install-tab.js +1007 -991
- package/src/console/tabs/music-tab.js +22 -8
- package/src/console/tabs/placeholder-tab.js +53 -53
- package/src/console/tabs/readme-tab.js +267 -267
- package/src/console/tabs/receiver-tab.js +1472 -1212
- package/src/console/tabs/settings-tab.js +152 -79
- package/src/console/tabs/voices-tab.js +100 -21
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/format-utils.js +89 -89
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +185 -185
- package/src/console/widgets/reverb-picker.js +94 -94
- package/src/console/widgets/track-picker.js +285 -285
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +5882 -5829
- package/src/services/agent-voice-store.js +423 -423
- package/src/services/config-service.js +264 -264
- package/src/services/navigation-service.js +123 -123
- package/src/services/provider-service.js +132 -132
- package/src/services/verbosity-service.js +157 -157
- package/src/utils/audio-duration-validator.js +298 -298
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -466
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/list-formatter.js +194 -194
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/provider-validator.js +96 -12
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +482 -482
- package/templates/audio/welcome-music.mp3 +0 -0
- package/voice-assignments.json +8244 -8244
- package/.claude/config/background-music-position.txt +0 -1
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes TUI Console — Navigation Service
|
|
3
|
-
* Story 6.2: Tab Bar & Global Keyboard Navigation
|
|
4
|
-
*
|
|
5
|
-
* Manages tab state, cycling, modal overlay state, and focus stack.
|
|
6
|
-
* Used by navigation.js (key bindings) and app.js (wiring).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** Ordered list of all tab IDs — used for cycling and validation */
|
|
10
|
-
export const TAB_ORDER = ['install', 'settings', 'voices', 'music', 'agents', 'receiver', 'readme', 'help'];
|
|
11
|
-
|
|
12
|
-
export class NavigationService {
|
|
13
|
-
/**
|
|
14
|
-
* @param {string} [initialTab='settings'] - Tab to activate on launch
|
|
15
|
-
*/
|
|
16
|
-
constructor(initialTab = 'settings') {
|
|
17
|
-
this._activeTab = TAB_ORDER.includes(initialTab) ? initialTab : 'settings';
|
|
18
|
-
this._switchCallbacks = [];
|
|
19
|
-
this._focusStack = [];
|
|
20
|
-
this._modalOpen = false;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Tab navigation
|
|
25
|
-
|
|
26
|
-
/** Returns the currently active tab ID */
|
|
27
|
-
getActiveTab() {
|
|
28
|
-
return this._activeTab;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Switch to the given tab.
|
|
33
|
-
* Ignores invalid tab IDs. Fires all registered onSwitch callbacks.
|
|
34
|
-
* @param {string} tabId
|
|
35
|
-
*/
|
|
36
|
-
switchTab(tabId) {
|
|
37
|
-
if (!TAB_ORDER.includes(tabId)) return;
|
|
38
|
-
if (tabId === this._activeTab) return; // no-op: already on this tab
|
|
39
|
-
this._activeTab = tabId;
|
|
40
|
-
this._switchCallbacks.forEach(cb => cb(tabId));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Activate a tab unconditionally, bypassing the same-tab no-op guard.
|
|
45
|
-
* Used for initial UI setup: the constructor pre-sets _activeTab but
|
|
46
|
-
* onSwitch callbacks must still fire to render the initial state.
|
|
47
|
-
* @param {string} tabId
|
|
48
|
-
*/
|
|
49
|
-
forceActivate(tabId) {
|
|
50
|
-
if (!TAB_ORDER.includes(tabId)) return;
|
|
51
|
-
this._activeTab = tabId;
|
|
52
|
-
this._switchCallbacks.forEach(cb => cb(tabId));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Cycle to the next tab in TAB_ORDER, wrapping from last back to first.
|
|
57
|
-
*/
|
|
58
|
-
cycleTab() {
|
|
59
|
-
const idx = TAB_ORDER.indexOf(this._activeTab);
|
|
60
|
-
const nextIdx = (idx + 1) % TAB_ORDER.length;
|
|
61
|
-
this.switchTab(TAB_ORDER[nextIdx]);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Cycle to the previous tab in TAB_ORDER, wrapping from first back to last.
|
|
66
|
-
*/
|
|
67
|
-
cycleTabPrev() {
|
|
68
|
-
const idx = TAB_ORDER.indexOf(this._activeTab);
|
|
69
|
-
const prevIdx = (idx - 1 + TAB_ORDER.length) % TAB_ORDER.length;
|
|
70
|
-
this.switchTab(TAB_ORDER[prevIdx]);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Register a callback fired whenever the active tab changes.
|
|
75
|
-
* @param {(tabId: string) => void} callback
|
|
76
|
-
*/
|
|
77
|
-
onSwitch(callback) {
|
|
78
|
-
this._switchCallbacks.push(callback);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
// Modal state (story 6.4 will expand this)
|
|
83
|
-
|
|
84
|
-
/** Returns true if a modal is currently open */
|
|
85
|
-
isModalOpen() {
|
|
86
|
-
return this._modalOpen;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Open a modal. Sets modal-open state and calls the factory fn if provided.
|
|
91
|
-
* @param {Function|null} fn - Optional factory/callback invoked immediately
|
|
92
|
-
*/
|
|
93
|
-
openModal(fn) {
|
|
94
|
-
this._modalOpen = true;
|
|
95
|
-
fn?.();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Close the current modal, restoring modal-closed state */
|
|
99
|
-
closeModal() {
|
|
100
|
-
this._modalOpen = false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
// Focus stack (story 7.6 will use this for button-level focus)
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Push a Blessed element onto the focus stack
|
|
109
|
-
* @param {object} element - Blessed widget
|
|
110
|
-
*/
|
|
111
|
-
pushFocus(element) {
|
|
112
|
-
this._focusStack.push(element);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Pop the last element from the focus stack.
|
|
117
|
-
* Returns undefined if the stack is empty.
|
|
118
|
-
* @returns {object|undefined}
|
|
119
|
-
*/
|
|
120
|
-
popFocus() {
|
|
121
|
-
return this._focusStack.pop();
|
|
122
|
-
}
|
|
123
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes TUI Console — Navigation Service
|
|
3
|
+
* Story 6.2: Tab Bar & Global Keyboard Navigation
|
|
4
|
+
*
|
|
5
|
+
* Manages tab state, cycling, modal overlay state, and focus stack.
|
|
6
|
+
* Used by navigation.js (key bindings) and app.js (wiring).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Ordered list of all tab IDs — used for cycling and validation */
|
|
10
|
+
export const TAB_ORDER = ['install', 'settings', 'voices', 'music', 'agents', 'receiver', 'readme', 'help'];
|
|
11
|
+
|
|
12
|
+
export class NavigationService {
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} [initialTab='settings'] - Tab to activate on launch
|
|
15
|
+
*/
|
|
16
|
+
constructor(initialTab = 'settings') {
|
|
17
|
+
this._activeTab = TAB_ORDER.includes(initialTab) ? initialTab : 'settings';
|
|
18
|
+
this._switchCallbacks = [];
|
|
19
|
+
this._focusStack = [];
|
|
20
|
+
this._modalOpen = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Tab navigation
|
|
25
|
+
|
|
26
|
+
/** Returns the currently active tab ID */
|
|
27
|
+
getActiveTab() {
|
|
28
|
+
return this._activeTab;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Switch to the given tab.
|
|
33
|
+
* Ignores invalid tab IDs. Fires all registered onSwitch callbacks.
|
|
34
|
+
* @param {string} tabId
|
|
35
|
+
*/
|
|
36
|
+
switchTab(tabId) {
|
|
37
|
+
if (!TAB_ORDER.includes(tabId)) return;
|
|
38
|
+
if (tabId === this._activeTab) return; // no-op: already on this tab
|
|
39
|
+
this._activeTab = tabId;
|
|
40
|
+
this._switchCallbacks.forEach(cb => cb(tabId));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Activate a tab unconditionally, bypassing the same-tab no-op guard.
|
|
45
|
+
* Used for initial UI setup: the constructor pre-sets _activeTab but
|
|
46
|
+
* onSwitch callbacks must still fire to render the initial state.
|
|
47
|
+
* @param {string} tabId
|
|
48
|
+
*/
|
|
49
|
+
forceActivate(tabId) {
|
|
50
|
+
if (!TAB_ORDER.includes(tabId)) return;
|
|
51
|
+
this._activeTab = tabId;
|
|
52
|
+
this._switchCallbacks.forEach(cb => cb(tabId));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Cycle to the next tab in TAB_ORDER, wrapping from last back to first.
|
|
57
|
+
*/
|
|
58
|
+
cycleTab() {
|
|
59
|
+
const idx = TAB_ORDER.indexOf(this._activeTab);
|
|
60
|
+
const nextIdx = (idx + 1) % TAB_ORDER.length;
|
|
61
|
+
this.switchTab(TAB_ORDER[nextIdx]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Cycle to the previous tab in TAB_ORDER, wrapping from first back to last.
|
|
66
|
+
*/
|
|
67
|
+
cycleTabPrev() {
|
|
68
|
+
const idx = TAB_ORDER.indexOf(this._activeTab);
|
|
69
|
+
const prevIdx = (idx - 1 + TAB_ORDER.length) % TAB_ORDER.length;
|
|
70
|
+
this.switchTab(TAB_ORDER[prevIdx]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Register a callback fired whenever the active tab changes.
|
|
75
|
+
* @param {(tabId: string) => void} callback
|
|
76
|
+
*/
|
|
77
|
+
onSwitch(callback) {
|
|
78
|
+
this._switchCallbacks.push(callback);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Modal state (story 6.4 will expand this)
|
|
83
|
+
|
|
84
|
+
/** Returns true if a modal is currently open */
|
|
85
|
+
isModalOpen() {
|
|
86
|
+
return this._modalOpen;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Open a modal. Sets modal-open state and calls the factory fn if provided.
|
|
91
|
+
* @param {Function|null} fn - Optional factory/callback invoked immediately
|
|
92
|
+
*/
|
|
93
|
+
openModal(fn) {
|
|
94
|
+
this._modalOpen = true;
|
|
95
|
+
fn?.();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Close the current modal, restoring modal-closed state */
|
|
99
|
+
closeModal() {
|
|
100
|
+
this._modalOpen = false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Focus stack (story 7.6 will use this for button-level focus)
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Push a Blessed element onto the focus stack
|
|
109
|
+
* @param {object} element - Blessed widget
|
|
110
|
+
*/
|
|
111
|
+
pushFocus(element) {
|
|
112
|
+
this._focusStack.push(element);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Pop the last element from the focus stack.
|
|
117
|
+
* Returns undefined if the stack is empty.
|
|
118
|
+
* @returns {object|undefined}
|
|
119
|
+
*/
|
|
120
|
+
popFocus() {
|
|
121
|
+
return this._focusStack.pop();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes Provider Service
|
|
3
|
-
* Story 7.1: Provider & Voice Settings Group
|
|
4
|
-
*
|
|
5
|
-
* Detects installed TTS providers, reads/writes active provider and voice
|
|
6
|
-
* through ConfigService. Gracefully degrades when detection fails.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { execFileSync } from 'node:child_process';
|
|
10
|
-
import fs from 'node:fs';
|
|
11
|
-
import path from 'node:path';
|
|
12
|
-
import os from 'node:os';
|
|
13
|
-
|
|
14
|
-
export class ProviderService {
|
|
15
|
-
/**
|
|
16
|
-
* @param {import('./config-service.js').ConfigService} configService
|
|
17
|
-
*/
|
|
18
|
-
constructor(configService) {
|
|
19
|
-
this._config = configService;
|
|
20
|
-
this._installedProviders = null; // cached after first detection
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Provider
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Returns the currently active TTS provider from config.
|
|
28
|
-
* Defaults to 'piper' if not configured.
|
|
29
|
-
* @returns {string}
|
|
30
|
-
*/
|
|
31
|
-
getActiveProvider() {
|
|
32
|
-
return this._config.getConfig().provider ?? 'piper';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Sets the active TTS provider in config AND syncs to .claude/tts-provider.txt
|
|
37
|
-
* so the shell hooks (play-tts.sh → provider-manager.sh) pick up the change.
|
|
38
|
-
* @param {string} provider
|
|
39
|
-
*/
|
|
40
|
-
setActiveProvider(provider) {
|
|
41
|
-
this._config.set('provider', provider);
|
|
42
|
-
this._syncProviderFile(provider);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Write provider to .claude/tts-provider.txt so shell hooks stay in sync.
|
|
47
|
-
* Writes to projectRoot/.claude/tts-provider.txt if .claude/ exists there,
|
|
48
|
-
* otherwise falls back to ~/.claude/tts-provider.txt.
|
|
49
|
-
* @param {string} provider
|
|
50
|
-
*/
|
|
51
|
-
_syncProviderFile(provider) {
|
|
52
|
-
try {
|
|
53
|
-
const projectClaudeDir = path.resolve(this._config.getProjectRoot(), '.claude');
|
|
54
|
-
const targetDir = fs.existsSync(projectClaudeDir)
|
|
55
|
-
? projectClaudeDir
|
|
56
|
-
: path.resolve(os.homedir(), '.claude');
|
|
57
|
-
const targetFile = path.resolve(targetDir, 'tts-provider.txt');
|
|
58
|
-
// Verify resolved path stays within targetDir (path traversal guard)
|
|
59
|
-
if (!targetFile.startsWith(targetDir + path.sep) && targetFile !== targetDir) return;
|
|
60
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
61
|
-
fs.writeFileSync(targetFile, provider, 'utf8');
|
|
62
|
-
} catch {
|
|
63
|
-
// Non-fatal — config.json is the authoritative source
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Returns an array of installed/available TTS providers.
|
|
69
|
-
* Detection uses `which` binary check. Always returns at least ['piper']
|
|
70
|
-
* as graceful degradation (piper is the primary supported provider).
|
|
71
|
-
* @returns {string[]}
|
|
72
|
-
*/
|
|
73
|
-
getInstalledProviders() {
|
|
74
|
-
if (this._installedProviders) return this._installedProviders;
|
|
75
|
-
|
|
76
|
-
const providers = [];
|
|
77
|
-
|
|
78
|
-
if (this._isAvailable('piper')) providers.push('piper');
|
|
79
|
-
if (this._isAvailable('soprano')) providers.push('soprano');
|
|
80
|
-
|
|
81
|
-
// macOS Say (darwin only)
|
|
82
|
-
if (process.platform === 'darwin' && this._isAvailable('say')) {
|
|
83
|
-
providers.push('macos');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Graceful degradation: always return at least piper
|
|
87
|
-
if (providers.length === 0) providers.push('piper');
|
|
88
|
-
|
|
89
|
-
this._installedProviders = providers;
|
|
90
|
-
return providers;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
// Voice
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Returns the currently active voice ID from config.
|
|
98
|
-
* Defaults to 'en_US-amy-medium' if not configured.
|
|
99
|
-
* @returns {string}
|
|
100
|
-
*/
|
|
101
|
-
getActiveVoiceId() {
|
|
102
|
-
return this._config.getConfig().voice ?? 'en_US-amy-medium';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Sets the active voice ID in config.
|
|
107
|
-
* @param {string} voiceId
|
|
108
|
-
*/
|
|
109
|
-
setActiveVoice(voiceId) {
|
|
110
|
-
this._config.set('voice', voiceId);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ---------------------------------------------------------------------------
|
|
114
|
-
// Private
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Check if a binary is available in PATH using `which`.
|
|
118
|
-
* Binary names are all hardcoded (not user input) — safe from injection.
|
|
119
|
-
* @param {string} binary - hardcoded binary name ('piper', 'soprano', 'say')
|
|
120
|
-
* @returns {boolean}
|
|
121
|
-
*/
|
|
122
|
-
_isAvailable(binary) {
|
|
123
|
-
try {
|
|
124
|
-
execFileSync('which', [binary], { stdio: 'ignore', timeout: 2000 });
|
|
125
|
-
return true;
|
|
126
|
-
} catch {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export default ProviderService;
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes Provider Service
|
|
3
|
+
* Story 7.1: Provider & Voice Settings Group
|
|
4
|
+
*
|
|
5
|
+
* Detects installed TTS providers, reads/writes active provider and voice
|
|
6
|
+
* through ConfigService. Gracefully degrades when detection fails.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
|
|
14
|
+
export class ProviderService {
|
|
15
|
+
/**
|
|
16
|
+
* @param {import('./config-service.js').ConfigService} configService
|
|
17
|
+
*/
|
|
18
|
+
constructor(configService) {
|
|
19
|
+
this._config = configService;
|
|
20
|
+
this._installedProviders = null; // cached after first detection
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Provider
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns the currently active TTS provider from config.
|
|
28
|
+
* Defaults to 'piper' if not configured.
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
getActiveProvider() {
|
|
32
|
+
return this._config.getConfig().provider ?? 'piper';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Sets the active TTS provider in config AND syncs to .claude/tts-provider.txt
|
|
37
|
+
* so the shell hooks (play-tts.sh → provider-manager.sh) pick up the change.
|
|
38
|
+
* @param {string} provider
|
|
39
|
+
*/
|
|
40
|
+
setActiveProvider(provider) {
|
|
41
|
+
this._config.set('provider', provider);
|
|
42
|
+
this._syncProviderFile(provider);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Write provider to .claude/tts-provider.txt so shell hooks stay in sync.
|
|
47
|
+
* Writes to projectRoot/.claude/tts-provider.txt if .claude/ exists there,
|
|
48
|
+
* otherwise falls back to ~/.claude/tts-provider.txt.
|
|
49
|
+
* @param {string} provider
|
|
50
|
+
*/
|
|
51
|
+
_syncProviderFile(provider) {
|
|
52
|
+
try {
|
|
53
|
+
const projectClaudeDir = path.resolve(this._config.getProjectRoot(), '.claude');
|
|
54
|
+
const targetDir = fs.existsSync(projectClaudeDir)
|
|
55
|
+
? projectClaudeDir
|
|
56
|
+
: path.resolve(os.homedir(), '.claude');
|
|
57
|
+
const targetFile = path.resolve(targetDir, 'tts-provider.txt');
|
|
58
|
+
// Verify resolved path stays within targetDir (path traversal guard)
|
|
59
|
+
if (!targetFile.startsWith(targetDir + path.sep) && targetFile !== targetDir) return;
|
|
60
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
61
|
+
fs.writeFileSync(targetFile, provider, 'utf8');
|
|
62
|
+
} catch {
|
|
63
|
+
// Non-fatal — config.json is the authoritative source
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns an array of installed/available TTS providers.
|
|
69
|
+
* Detection uses `which` binary check. Always returns at least ['piper']
|
|
70
|
+
* as graceful degradation (piper is the primary supported provider).
|
|
71
|
+
* @returns {string[]}
|
|
72
|
+
*/
|
|
73
|
+
getInstalledProviders() {
|
|
74
|
+
if (this._installedProviders) return this._installedProviders;
|
|
75
|
+
|
|
76
|
+
const providers = [];
|
|
77
|
+
|
|
78
|
+
if (this._isAvailable('piper')) providers.push('piper');
|
|
79
|
+
if (this._isAvailable('soprano')) providers.push('soprano');
|
|
80
|
+
|
|
81
|
+
// macOS Say (darwin only)
|
|
82
|
+
if (process.platform === 'darwin' && this._isAvailable('say')) {
|
|
83
|
+
providers.push('macos');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Graceful degradation: always return at least piper
|
|
87
|
+
if (providers.length === 0) providers.push('piper');
|
|
88
|
+
|
|
89
|
+
this._installedProviders = providers;
|
|
90
|
+
return providers;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Voice
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Returns the currently active voice ID from config.
|
|
98
|
+
* Defaults to 'en_US-amy-medium' if not configured.
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
getActiveVoiceId() {
|
|
102
|
+
return this._config.getConfig().voice ?? 'en_US-amy-medium';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Sets the active voice ID in config.
|
|
107
|
+
* @param {string} voiceId
|
|
108
|
+
*/
|
|
109
|
+
setActiveVoice(voiceId) {
|
|
110
|
+
this._config.set('voice', voiceId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Private
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if a binary is available in PATH using `which`.
|
|
118
|
+
* Binary names are all hardcoded (not user input) — safe from injection.
|
|
119
|
+
* @param {string} binary - hardcoded binary name ('piper', 'soprano', 'say')
|
|
120
|
+
* @returns {boolean}
|
|
121
|
+
*/
|
|
122
|
+
_isAvailable(binary) {
|
|
123
|
+
try {
|
|
124
|
+
execFileSync('which', [binary], { stdio: 'ignore', timeout: 2000 });
|
|
125
|
+
return true;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export default ProviderService;
|