agentvibes 4.6.8 → 5.1.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 (40) hide show
  1. package/.agentvibes/bmad-voice-map.json +104 -0
  2. package/.agentvibes/config.json +13 -12
  3. package/.agentvibes/copilot-sessions.log +4 -0
  4. package/.claude/audio/tracks/Drifting Down the Hall.mp3 +0 -0
  5. package/.claude/audio/tracks/Late Night Hip Hop Groove.mp3 +0 -0
  6. package/.claude/audio/tracks/Midnight Charleston Stomp.mp3 +0 -0
  7. package/.claude/audio/tracks/README.md +51 -52
  8. package/.claude/config/audio-effects-bmad.cfg +50 -0
  9. package/.claude/config/audio-effects.cfg +4 -4
  10. package/.claude/config/background-music-enabled.txt +1 -0
  11. package/.claude/config/personality.txt +1 -0
  12. package/.claude/hooks/play-tts-piper.sh +3 -1
  13. package/.claude/hooks/play-tts.sh +380 -301
  14. package/.claude/hooks/session-start-tts.sh +81 -81
  15. package/.claude/hooks-windows/audio-processor.ps1 +181 -0
  16. package/.claude/hooks-windows/play-tts-piper.ps1 +259 -245
  17. package/.claude/hooks-windows/play-tts.ps1 +28 -6
  18. package/.claude/hooks-windows/session-start-tts.ps1 +114 -114
  19. package/README.md +112 -6
  20. package/RELEASE_NOTES.md +83 -0
  21. package/bin/bmad-speak.js +16 -8
  22. package/mcp-server/server.py +15 -8
  23. package/package.json +1 -1
  24. package/src/console/app.js +899 -897
  25. package/src/console/footer-config.js +50 -50
  26. package/src/console/navigation.js +65 -65
  27. package/src/console/tabs/agents-tab.js +1899 -1886
  28. package/src/console/tabs/music-tab.js +1076 -1039
  29. package/src/console/tabs/placeholder-tab.js +81 -80
  30. package/src/console/tabs/settings-tab.js +941 -3988
  31. package/src/console/tabs/setup-tab.js +2071 -0
  32. package/src/console/tabs/voices-tab.js +1843 -1714
  33. package/src/console/widgets/format-utils.js +92 -89
  34. package/src/console/widgets/track-picker.js +325 -322
  35. package/src/installer.js +6147 -6092
  36. package/src/services/llm-provider-service.js +486 -0
  37. package/src/services/navigation-service.js +123 -123
  38. package/src/services/tts-engine-service.js +69 -0
  39. package/.claude/audio/tracks/dreamy_house_loop.mp3 +0 -0
  40. package/src/console/tabs/install-tab.js +0 -1081
@@ -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 = ['setup', '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
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * AgentVibes — TTS Engine Service
3
+ *
4
+ * OS-aware TTS engine detection: enumerates available engines,
5
+ * checks binary availability, and reports installation status.
6
+ */
7
+
8
+ import { execFileSync } from 'node:child_process';
9
+ import path from 'node:path';
10
+ import fs from 'node:fs';
11
+
12
+ const TTS_ENGINES = [
13
+ {
14
+ id: 'piper', name: 'Piper TTS', desc: 'Open-source, fast, many voices — recommended', native: false,
15
+ installCmd: process.platform === 'win32'
16
+ ? 'winget install --id Rhasspy.Piper --accept-package-agreements --accept-source-agreements'
17
+ : process.platform === 'darwin'
18
+ ? 'brew install piper'
19
+ : 'pip install piper-tts',
20
+ },
21
+ {
22
+ id: 'soprano', name: 'Soprano TTS', desc: 'Web-based TTS service with premium voices', native: false,
23
+ installCmd: 'pip install soprano-tts',
24
+ },
25
+ { id: 'sapi', name: 'Windows SAPI', desc: 'Built-in Windows speech — no install needed', native: true, platform: 'win32' },
26
+ { id: 'macos-say', name: 'macOS Say', desc: 'Built-in macOS speech synthesis — no install needed', native: true, platform: 'darwin' },
27
+ ];
28
+
29
+ export function getAvailableEngines() {
30
+ return TTS_ENGINES.filter(e => !e.platform || e.platform === process.platform);
31
+ }
32
+
33
+ export function checkEngineInstalled(engineId) {
34
+ const engine = TTS_ENGINES.find(e => e.id === engineId);
35
+ if (!engine) return false;
36
+ if (engine.native) return true; // Native engines are always available on their platform
37
+
38
+ // Check binary availability — soprano has two possible binaries
39
+ const binaryMap = { piper: ['piper'], soprano: ['soprano-tts', 'soprano-webui'] };
40
+ const binaries = binaryMap[engineId];
41
+ if (!binaries) return false;
42
+
43
+ for (const binary of binaries) {
44
+ try {
45
+ if (process.platform === 'win32') {
46
+ // Check Windows Piper location
47
+ if (engineId === 'piper') {
48
+ const localAppData = process.env.LOCALAPPDATA ||
49
+ (process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
50
+ if (localAppData && fs.existsSync(path.join(localAppData, 'Programs', 'Piper', 'piper.exe'))) return true;
51
+ }
52
+ execFileSync('where', [binary], { stdio: 'ignore', timeout: 2000 });
53
+ return true;
54
+ }
55
+ execFileSync('which', [binary], { stdio: 'ignore', timeout: 2000 });
56
+ return true;
57
+ } catch {
58
+ // try next binary
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+
64
+ export function getEngineStatuses() {
65
+ return getAvailableEngines().map(e => ({
66
+ ...e,
67
+ installed: checkEngineInstalled(e.id),
68
+ }));
69
+ }