agentvibes 4.0.1 → 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 +3 -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 -389
- 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 -112
- 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 -70
- package/.claude/hooks/play-tts-macos.sh +368 -345
- package/.claude/hooks/play-tts-piper.sh +679 -578
- package/.claude/hooks/play-tts-soprano.sh +356 -320
- package/.claude/hooks/play-tts-ssh-remote.sh +167 -88
- package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
- package/.claude/hooks/play-tts.sh +301 -298
- 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 -71
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +291 -291
- package/.claude/hooks/stop-tts.sh +84 -0
- 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 -114
- package/.claude/hooks/tts-queue.sh +165 -136
- package/.claude/hooks/verbosity-manager.sh +178 -178
- package/.claude/hooks/voice-manager.sh +548 -544
- 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 -181
- package/README.md +2029 -1909
- package/RELEASE_NOTES.md +1310 -66
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/agentvibes-voice-browser.js +1840 -1826
- 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 -1417
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +336 -0
- package/package.json +110 -112
- 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 -806
- package/src/console/audio-env.js +20 -1
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -0
- package/src/console/footer-config.js +50 -46
- package/src/console/modals/modal-overlay.js +247 -247
- package/src/console/navigation.js +62 -61
- package/src/console/tabs/agents-tab.js +1684 -369
- 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 -46
- package/src/console/tabs/readme-tab.js +267 -267
- package/src/console/tabs/receiver-tab.js +1472 -0
- package/src/console/tabs/settings-tab.js +185 -402
- package/src/console/tabs/voices-tab.js +100 -21
- package/src/console/widgets/destroy-list.js +25 -0
- package/src/console/widgets/format-utils.js +89 -0
- package/src/console/widgets/notice.js +55 -0
- package/src/console/widgets/personality-picker.js +185 -0
- package/src/console/widgets/reverb-picker.js +94 -0
- package/src/console/widgets/track-picker.js +285 -0
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +5882 -5777
- package/src/services/agent-voice-store.js +423 -163
- 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 -275
- 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 -162
- package/templates/audio/welcome-music.mp3 +0 -0
- package/voice-assignments.json +8244 -8244
- package/.claude/config/background-music-position.txt +0 -1
|
@@ -1,163 +1,423 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes Agent Voice Store
|
|
3
|
-
* Epic 11: Stories 11.1, 11.3, 11.5
|
|
4
|
-
*
|
|
5
|
-
* Manages global BMAD agent voice assignments at ~/.agentvibes/bmad-voice-map.json.
|
|
6
|
-
* All path operations use path.resolve() to prevent traversal.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes Agent Voice Store
|
|
3
|
+
* Epic 11: Stories 11.1, 11.3, 11.5
|
|
4
|
+
*
|
|
5
|
+
* Manages global BMAD agent voice/audio profile assignments at ~/.agentvibes/bmad-voice-map.json.
|
|
6
|
+
* All path operations use path.resolve() to prevent traversal.
|
|
7
|
+
*
|
|
8
|
+
* Store format:
|
|
9
|
+
* {
|
|
10
|
+
* "partyMode": true,
|
|
11
|
+
* "voiceMap": { "architect": "en_GB-alan-medium" }, // legacy compat
|
|
12
|
+
* "agents": {
|
|
13
|
+
* "architect": {
|
|
14
|
+
* "voice": "en_GB-alan-medium",
|
|
15
|
+
* "pretext": "Winston, Architect here.",
|
|
16
|
+
* "reverbPreset": "cathedral",
|
|
17
|
+
* "personality": "normal",
|
|
18
|
+
* "backgroundMusic": { "track": "soft_piano.mp3", "volume": 30, "enabled": true }
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import os from 'node:os';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Agent ID validation — prevents prototype pollution via __proto__ / constructor keys
|
|
30
|
+
|
|
31
|
+
const VALID_AGENT_ID = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validate an agent ID is safe for use as an object property key.
|
|
35
|
+
* Rejects __proto__, constructor, toString, etc.
|
|
36
|
+
* @param {string} id
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
function _isValidAgentId(id) {
|
|
40
|
+
if (!id || typeof id !== 'string') return false;
|
|
41
|
+
if (!VALID_AGENT_ID.test(id)) return false;
|
|
42
|
+
// Explicit blocklist for prototype pollution vectors
|
|
43
|
+
const blocked = new Set(['__proto__', 'constructor', 'prototype', 'toString', 'valueOf', 'hasOwnProperty']);
|
|
44
|
+
return !blocked.has(id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Advisory file locking for atomic read-modify-write
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Acquire an advisory lock via exclusive file open.
|
|
52
|
+
* Retries briefly on EEXIST; returns fd on success, null on timeout.
|
|
53
|
+
*/
|
|
54
|
+
function _acquireLock(lockPath) {
|
|
55
|
+
const maxRetries = 20;
|
|
56
|
+
const retryMs = 50;
|
|
57
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
58
|
+
try {
|
|
59
|
+
const fd = fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, 0o600);
|
|
60
|
+
return fd;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (err.code !== 'EEXIST') return null;
|
|
63
|
+
// Stale lock detection: if lock file is older than 5 seconds, remove it
|
|
64
|
+
try {
|
|
65
|
+
const stat = fs.statSync(lockPath);
|
|
66
|
+
if (Date.now() - stat.mtimeMs > 5000) {
|
|
67
|
+
try { fs.unlinkSync(lockPath); } catch {}
|
|
68
|
+
}
|
|
69
|
+
} catch {}
|
|
70
|
+
// Brief sync delay (10ms) — acceptable for a single-threaded TUI lock retry
|
|
71
|
+
const buf = new SharedArrayBuffer(4);
|
|
72
|
+
Atomics.wait(new Int32Array(buf), 0, 0, retryMs);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null; // Proceed without lock rather than blocking forever
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Release advisory lock.
|
|
80
|
+
*/
|
|
81
|
+
function _releaseLock(fd, lockPath) {
|
|
82
|
+
try { if (fd != null) fs.closeSync(fd); } catch {}
|
|
83
|
+
try { fs.unlinkSync(lockPath); } catch {}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Single-voice provider detection (story 11.3)
|
|
88
|
+
|
|
89
|
+
const SINGLE_VOICE_PROVIDERS = Object.freeze(new Set(['soprano']));
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns true if the given provider only has one voice.
|
|
93
|
+
* @param {string} provider
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
export function isSingleVoiceProvider(provider) {
|
|
97
|
+
return SINGLE_VOICE_PROVIDERS.has(provider?.toLowerCase());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// BMAD agent manifest parser
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parse the BMAD agent-manifest.csv to get rich agent metadata.
|
|
105
|
+
* Returns agents filtered to core and bmm modules only.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} projectRoot
|
|
108
|
+
* @returns {{ id: string, displayName: string, title: string, icon: string, module: string }[]}
|
|
109
|
+
*/
|
|
110
|
+
export function parseBmadManifest(projectRoot) {
|
|
111
|
+
const safeRoot = path.resolve(projectRoot ?? process.cwd());
|
|
112
|
+
const manifestPath = path.resolve(safeRoot, '_bmad', '_config', 'agent-manifest.csv');
|
|
113
|
+
|
|
114
|
+
if (!fs.existsSync(manifestPath)) return [];
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const raw = fs.readFileSync(manifestPath, 'utf8');
|
|
118
|
+
const lines = raw.split('\n').filter(l => l.trim());
|
|
119
|
+
if (lines.length < 2) return [];
|
|
120
|
+
|
|
121
|
+
// Parse CSV header
|
|
122
|
+
const headers = _parseCSVLine(lines[0]);
|
|
123
|
+
const nameIdx = headers.indexOf('name');
|
|
124
|
+
const displayIdx = headers.indexOf('displayName');
|
|
125
|
+
const titleIdx = headers.indexOf('title');
|
|
126
|
+
const iconIdx = headers.indexOf('icon');
|
|
127
|
+
const moduleIdx = headers.indexOf('module');
|
|
128
|
+
|
|
129
|
+
if (nameIdx < 0) return [];
|
|
130
|
+
|
|
131
|
+
const agents = [];
|
|
132
|
+
for (let i = 1; i < lines.length; i++) {
|
|
133
|
+
const cols = _parseCSVLine(lines[i]);
|
|
134
|
+
const module = cols[moduleIdx] ?? '';
|
|
135
|
+
|
|
136
|
+
// Filter to core and bmm modules only
|
|
137
|
+
if (module !== 'core' && module !== 'bmm') continue;
|
|
138
|
+
|
|
139
|
+
const agentId = cols[nameIdx] ?? '';
|
|
140
|
+
if (!_isValidAgentId(agentId)) continue;
|
|
141
|
+
|
|
142
|
+
agents.push({
|
|
143
|
+
id: agentId,
|
|
144
|
+
displayName: cols[displayIdx] ?? cols[nameIdx] ?? '',
|
|
145
|
+
title: cols[titleIdx] ?? '',
|
|
146
|
+
icon: cols[iconIdx] ?? '',
|
|
147
|
+
module,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return agents.sort((a, b) => a.id.localeCompare(b.id));
|
|
152
|
+
} catch {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Simple CSV line parser that handles quoted fields.
|
|
159
|
+
* @param {string} line
|
|
160
|
+
* @returns {string[]}
|
|
161
|
+
*/
|
|
162
|
+
function _parseCSVLine(line) {
|
|
163
|
+
const result = [];
|
|
164
|
+
let current = '';
|
|
165
|
+
let inQuotes = false;
|
|
166
|
+
|
|
167
|
+
for (let i = 0; i < line.length; i++) {
|
|
168
|
+
const ch = line[i];
|
|
169
|
+
if (ch === '"') {
|
|
170
|
+
if (inQuotes && line[i + 1] === '"') {
|
|
171
|
+
current += '"';
|
|
172
|
+
i++;
|
|
173
|
+
} else {
|
|
174
|
+
inQuotes = !inQuotes;
|
|
175
|
+
}
|
|
176
|
+
} else if (ch === ',' && !inQuotes) {
|
|
177
|
+
result.push(current.trim());
|
|
178
|
+
current = '';
|
|
179
|
+
} else {
|
|
180
|
+
current += ch;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
result.push(current.trim());
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// BMAD agent scanner (story 11.5) — fallback when manifest is unavailable
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Scan for BMAD agents in the project root.
|
|
192
|
+
* Prefers manifest-based discovery; falls back to directory scan.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} projectRoot
|
|
195
|
+
* @returns {{ id: string, displayName: string, title: string, icon: string, module: string }[]}
|
|
196
|
+
*/
|
|
197
|
+
export function scanBmadAgents(projectRoot) {
|
|
198
|
+
// Try manifest first
|
|
199
|
+
const fromManifest = parseBmadManifest(projectRoot);
|
|
200
|
+
if (fromManifest.length > 0) return fromManifest;
|
|
201
|
+
|
|
202
|
+
// Fallback: directory scan
|
|
203
|
+
const safeRoot = path.resolve(projectRoot ?? process.cwd());
|
|
204
|
+
const candidateDirs = [
|
|
205
|
+
path.resolve(safeRoot, '_bmad', 'bmm', 'agents'),
|
|
206
|
+
path.resolve(safeRoot, '.bmad', 'agents'),
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
for (const dir of candidateDirs) {
|
|
210
|
+
if (!fs.existsSync(dir)) continue;
|
|
211
|
+
try {
|
|
212
|
+
const files = fs.readdirSync(dir);
|
|
213
|
+
return files
|
|
214
|
+
.filter(f => f.endsWith('.md') && !f.includes('.backup') && !f.includes('.bak'))
|
|
215
|
+
.map(f => {
|
|
216
|
+
const id = f.replace(/\.md$/, '');
|
|
217
|
+
const displayName = id
|
|
218
|
+
.split('-')
|
|
219
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
220
|
+
.join(' ');
|
|
221
|
+
return { id, displayName, title: '', icon: '', module: 'bmm' };
|
|
222
|
+
})
|
|
223
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
224
|
+
} catch {
|
|
225
|
+
// Directory not readable — skip
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Detect whether BMAD is installed in the project.
|
|
233
|
+
* @param {string} projectRoot
|
|
234
|
+
* @returns {boolean}
|
|
235
|
+
*/
|
|
236
|
+
export function isBmadDetected(projectRoot) {
|
|
237
|
+
const safeRoot = path.resolve(projectRoot ?? process.cwd());
|
|
238
|
+
const manifestPath = path.resolve(safeRoot, '_bmad', '_config', 'agent-manifest.csv');
|
|
239
|
+
if (fs.existsSync(manifestPath)) return true;
|
|
240
|
+
|
|
241
|
+
// Fallback checks
|
|
242
|
+
const dirs = [
|
|
243
|
+
path.resolve(safeRoot, '_bmad', 'bmm', 'agents'),
|
|
244
|
+
path.resolve(safeRoot, '.bmad', 'agents'),
|
|
245
|
+
];
|
|
246
|
+
return dirs.some(d => fs.existsSync(d));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// AgentVoiceStore class
|
|
251
|
+
|
|
252
|
+
export class AgentVoiceStore {
|
|
253
|
+
/**
|
|
254
|
+
* @param {object} [opts]
|
|
255
|
+
* @param {string} [opts.homeDir] - User home dir. Defaults to os.homedir().
|
|
256
|
+
*/
|
|
257
|
+
constructor(opts = {}) {
|
|
258
|
+
this._homeDir = path.resolve(opts.homeDir ?? os.homedir());
|
|
259
|
+
this._filePath = path.resolve(this._homeDir, '.agentvibes', 'bmad-voice-map.json');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Read the full store.
|
|
264
|
+
* @returns {{ voiceMap: object, partyMode: boolean, agents: object }}
|
|
265
|
+
*/
|
|
266
|
+
_readStore() {
|
|
267
|
+
if (!fs.existsSync(this._filePath)) {
|
|
268
|
+
return { voiceMap: {}, partyMode: false, agents: {} };
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const raw = fs.readFileSync(this._filePath, 'utf8');
|
|
272
|
+
const data = JSON.parse(raw);
|
|
273
|
+
return {
|
|
274
|
+
voiceMap: data.voiceMap ?? {},
|
|
275
|
+
partyMode: data.partyMode ?? false,
|
|
276
|
+
agents: data.agents ?? {},
|
|
277
|
+
};
|
|
278
|
+
} catch {
|
|
279
|
+
return { voiceMap: {}, partyMode: false, agents: {} };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Atomically write store data with advisory file locking.
|
|
285
|
+
* @param {{ voiceMap: object, partyMode: boolean, agents: object }} data
|
|
286
|
+
*/
|
|
287
|
+
_writeStore(data) {
|
|
288
|
+
const dir = path.dirname(this._filePath);
|
|
289
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
290
|
+
// Ensure directory is user-only even if it already existed
|
|
291
|
+
try { fs.chmodSync(dir, 0o700); } catch {}
|
|
292
|
+
|
|
293
|
+
// Advisory lock: prevent concurrent read-modify-write from clobbering data
|
|
294
|
+
const lockPath = `${this._filePath}.lock`;
|
|
295
|
+
const lockFd = _acquireLock(lockPath);
|
|
296
|
+
try {
|
|
297
|
+
const tmpPath = `${this._filePath}.tmp`;
|
|
298
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
299
|
+
fs.renameSync(tmpPath, this._filePath);
|
|
300
|
+
fs.chmodSync(this._filePath, 0o600);
|
|
301
|
+
} finally {
|
|
302
|
+
_releaseLock(lockFd, lockPath);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Get the agent → voice ID map (legacy compat).
|
|
308
|
+
* Merges voiceMap with agents[id].voice for backward compat.
|
|
309
|
+
* @returns {object}
|
|
310
|
+
*/
|
|
311
|
+
getVoiceMap() {
|
|
312
|
+
const store = this._readStore();
|
|
313
|
+
const merged = { ...store.voiceMap };
|
|
314
|
+
for (const [id, profile] of Object.entries(store.agents)) {
|
|
315
|
+
if (profile.voice && !merged[id]) merged[id] = profile.voice;
|
|
316
|
+
}
|
|
317
|
+
return merged;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Assign a voice to an agent (legacy compat — also updates agent profile).
|
|
322
|
+
* @param {string} agentId
|
|
323
|
+
* @param {string} voiceId
|
|
324
|
+
*/
|
|
325
|
+
setVoice(agentId, voiceId) {
|
|
326
|
+
if (!_isValidAgentId(agentId)) return;
|
|
327
|
+
const store = this._readStore();
|
|
328
|
+
store.voiceMap[agentId] = voiceId;
|
|
329
|
+
if (!store.agents[agentId]) store.agents[agentId] = {};
|
|
330
|
+
store.agents[agentId].voice = voiceId;
|
|
331
|
+
this._writeStore(store);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Remove an agent's voice assignment (reset to default).
|
|
336
|
+
* @param {string} agentId
|
|
337
|
+
*/
|
|
338
|
+
resetVoice(agentId) {
|
|
339
|
+
if (!_isValidAgentId(agentId)) return;
|
|
340
|
+
const store = this._readStore();
|
|
341
|
+
delete store.voiceMap[agentId];
|
|
342
|
+
if (store.agents[agentId]) delete store.agents[agentId].voice;
|
|
343
|
+
this._writeStore(store);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Get party mode state.
|
|
348
|
+
* @returns {boolean}
|
|
349
|
+
*/
|
|
350
|
+
getPartyMode() {
|
|
351
|
+
return this._readStore().partyMode;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Set party mode state.
|
|
356
|
+
* @param {boolean} enabled
|
|
357
|
+
*/
|
|
358
|
+
setPartyMode(enabled) {
|
|
359
|
+
const store = this._readStore();
|
|
360
|
+
store.partyMode = Boolean(enabled);
|
|
361
|
+
this._writeStore(store);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// -------------------------------------------------------------------------
|
|
365
|
+
// Per-agent profile API
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Get the full profile for an agent. Missing fields are undefined (caller merges with global).
|
|
369
|
+
* @param {string} agentId
|
|
370
|
+
* @returns {{ voice?: string, pretext?: string, reverbPreset?: string, personality?: string, backgroundMusic?: object }}
|
|
371
|
+
*/
|
|
372
|
+
getAgentProfile(agentId) {
|
|
373
|
+
if (!_isValidAgentId(agentId)) return {};
|
|
374
|
+
const store = this._readStore();
|
|
375
|
+
const profile = { ...(store.agents[agentId] ?? {}) };
|
|
376
|
+
// Compat: if voice is only in voiceMap, include it
|
|
377
|
+
if (!profile.voice && store.voiceMap[agentId]) {
|
|
378
|
+
profile.voice = store.voiceMap[agentId];
|
|
379
|
+
}
|
|
380
|
+
return profile;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Set (merge) profile fields for an agent. Only provided fields are updated.
|
|
385
|
+
* @param {string} agentId
|
|
386
|
+
* @param {{ voice?: string, pretext?: string, reverbPreset?: string, personality?: string, backgroundMusic?: object }} partial
|
|
387
|
+
*/
|
|
388
|
+
setAgentProfile(agentId, partial) {
|
|
389
|
+
if (!_isValidAgentId(agentId)) return;
|
|
390
|
+
const store = this._readStore();
|
|
391
|
+
if (!store.agents[agentId]) store.agents[agentId] = {};
|
|
392
|
+
Object.assign(store.agents[agentId], partial);
|
|
393
|
+
// Keep voiceMap in sync
|
|
394
|
+
if (partial.voice) store.voiceMap[agentId] = partial.voice;
|
|
395
|
+
this._writeStore(store);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Reset all profile settings for an agent.
|
|
400
|
+
* @param {string} agentId
|
|
401
|
+
*/
|
|
402
|
+
resetAgentProfile(agentId) {
|
|
403
|
+
if (!_isValidAgentId(agentId)) return;
|
|
404
|
+
const store = this._readStore();
|
|
405
|
+
delete store.agents[agentId];
|
|
406
|
+
delete store.voiceMap[agentId];
|
|
407
|
+
this._writeStore(store);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Generate a default pretext for an agent.
|
|
412
|
+
* @param {string} displayName - e.g. "Winston"
|
|
413
|
+
* @param {string} title - e.g. "Architect"
|
|
414
|
+
* @returns {string}
|
|
415
|
+
*/
|
|
416
|
+
static getDefaultPretext(displayName, title) {
|
|
417
|
+
if (!displayName) return '';
|
|
418
|
+
if (!title) return `${displayName} here.`;
|
|
419
|
+
return `${displayName}, ${title} here.`;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export default AgentVoiceStore;
|