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,185 +1,185 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes TUI — Shared Widget: Personality Picker
|
|
3
|
-
*
|
|
4
|
-
* Inline modal list for selecting voice personalities with TTS preview.
|
|
5
|
-
* Extracted from settings-tab.js for reuse across tabs.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
import { spawn } from 'node:child_process';
|
|
10
|
-
import { destroyList } from './destroy-list.js';
|
|
11
|
-
import { buildAudioEnv } from '../audio-env.js';
|
|
12
|
-
import { BRAND_PINK } from '../brand-colors.js';
|
|
13
|
-
import { PERSONALITY_EMOJIS, PERSONALITIES } from '../constants/personalities.js';
|
|
14
|
-
|
|
15
|
-
export { PERSONALITY_EMOJIS, PERSONALITIES };
|
|
16
|
-
|
|
17
|
-
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
18
|
-
let blessed;
|
|
19
|
-
if (!IS_TEST) {
|
|
20
|
-
const { default: b } = await import('blessed');
|
|
21
|
-
blessed = b;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const _modalTitle = (text) => ` {${BRAND_PINK}-fg}${text}{/${BRAND_PINK}-fg} `;
|
|
25
|
-
|
|
26
|
-
const PERSONALITY_PREVIEW_PHRASES = Object.freeze({
|
|
27
|
-
angry: "UNACCEPTABLE! This build time is a DISASTER! Fix it NOW or so help me!",
|
|
28
|
-
annoying: "Oh oh oh! Can I tell you something? Can I? Can I? PLEASE? It is so important!",
|
|
29
|
-
crass: "Well damn, that code runs like my uncle's truck. Barely, and it smells funny.",
|
|
30
|
-
dramatic: "The tests... have failed. I don't know how much longer I can do this.",
|
|
31
|
-
'dry-humor': "Your code worked. I too am surprised.",
|
|
32
|
-
flirty: "Ooh, a clean merge? You know exactly how to make my heart race.",
|
|
33
|
-
funny: "Why do programmers hate nature? Too many bugs. I will show myself out.",
|
|
34
|
-
grandpa: "Back in my day, we compiled by hand. Uphill. In the snow. Both ways.",
|
|
35
|
-
millennial: "I literally cannot even with this error. I am so done. Like, actually deceased.",
|
|
36
|
-
moody: "...It works. Whatever. Do not get used to it.",
|
|
37
|
-
pirate: "Arrr! The build be sailin' smooth today, matey! No barnacles in sight!",
|
|
38
|
-
poetic: "Like rivers to the sea, your code flows toward eventual compilation.",
|
|
39
|
-
professional: "I have completed the requested task and am prepared to document outcomes.",
|
|
40
|
-
rapper: "Yo! Clean code flowin', tests are glowin', no bugs showin'!",
|
|
41
|
-
robot: "TASK COMPLETE. EFFICIENCY: OPTIMAL. PROBABILITY OF SUCCESS: 97.3 PERCENT. BEEP.",
|
|
42
|
-
sarcastic: "Oh wow, another bug. What a completely unexpected surprise. Truly shocking.",
|
|
43
|
-
sassy: "Honey, whoever told you that was good code was not your friend.",
|
|
44
|
-
'surfer-dude':"Duuude! That commit totally shredded! Gnarly clean code, bro!",
|
|
45
|
-
zen: "The bug is not the enemy. The bug is the teacher. Breathe. Commit.",
|
|
46
|
-
random: "Who will I be today? Even I do not know. Expect the unexpected.",
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Open the personality picker modal.
|
|
51
|
-
*
|
|
52
|
-
* @param {object} screen - blessed screen
|
|
53
|
-
* @param {string} currentPersonality - current personality value
|
|
54
|
-
* @param {Function} onSelect - called with selected personality
|
|
55
|
-
* @param {Function} [onClose] - called after modal closes
|
|
56
|
-
*/
|
|
57
|
-
export function openPersonalityPicker(screen, currentPersonality, onSelect, onClose) {
|
|
58
|
-
const current = currentPersonality ?? 'none';
|
|
59
|
-
const currentIdx = Math.max(0, PERSONALITIES.indexOf(current));
|
|
60
|
-
|
|
61
|
-
const COLORS = {
|
|
62
|
-
btnFocus: '#
|
|
63
|
-
btnFocusFg: '#
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const list = blessed.list({
|
|
67
|
-
parent: screen,
|
|
68
|
-
top: 'center',
|
|
69
|
-
left: 'center',
|
|
70
|
-
width: 44,
|
|
71
|
-
height: Math.min(PERSONALITIES.length + 4, 22),
|
|
72
|
-
border: { type: 'line' },
|
|
73
|
-
tags: true,
|
|
74
|
-
label: _modalTitle('Select Personality'),
|
|
75
|
-
items: PERSONALITIES.map((p, i) => {
|
|
76
|
-
const emoji = PERSONALITY_EMOJIS[p] ?? '✨';
|
|
77
|
-
const label = p === 'none' ? 'None' : p.charAt(0).toUpperCase() + p.slice(1);
|
|
78
|
-
const mark = i === currentIdx ? '✅' : ' ';
|
|
79
|
-
return `${mark} ${emoji} ${label}`;
|
|
80
|
-
}),
|
|
81
|
-
keys: true,
|
|
82
|
-
vi: true,
|
|
83
|
-
mouse: true,
|
|
84
|
-
style: {
|
|
85
|
-
border: { fg: COLORS.btnFocus },
|
|
86
|
-
selected: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
87
|
-
item: { fg: '#e3f2fd' },
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
list.select(currentIdx);
|
|
92
|
-
list.focus();
|
|
93
|
-
screen.render();
|
|
94
|
-
|
|
95
|
-
// TTS preview on hover
|
|
96
|
-
let _pickerTtsProc = null;
|
|
97
|
-
let _playingItemIdx = -1;
|
|
98
|
-
|
|
99
|
-
function _setItemPlaying(idx, playing) {
|
|
100
|
-
const item = list.items?.[idx];
|
|
101
|
-
if (!item) return;
|
|
102
|
-
const base = (item.content ?? '').replace(/ █$/, '').replace(/ \(playing\)$/, '');
|
|
103
|
-
item.setContent(playing ? `${base} (playing)` : base);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function _killPickerTts() {
|
|
107
|
-
if (_pickerTtsProc) {
|
|
108
|
-
try { process.kill(-_pickerTtsProc.pid, 'SIGTERM'); } catch {}
|
|
109
|
-
_pickerTtsProc = null;
|
|
110
|
-
}
|
|
111
|
-
if (_playingItemIdx >= 0) {
|
|
112
|
-
_setItemPlaying(_playingItemIdx, false);
|
|
113
|
-
_playingItemIdx = -1;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function _speakPreview(personality) {
|
|
118
|
-
_killPickerTts();
|
|
119
|
-
const phrase = PERSONALITY_PREVIEW_PHRASES[personality];
|
|
120
|
-
if (!phrase) return;
|
|
121
|
-
const ttsScript = path.join(process.cwd(), '.claude', 'hooks', 'play-tts.sh');
|
|
122
|
-
_pickerTtsProc = spawn('bash', [ttsScript, phrase], {
|
|
123
|
-
stdio: 'ignore',
|
|
124
|
-
detached: true,
|
|
125
|
-
env: buildAudioEnv(),
|
|
126
|
-
});
|
|
127
|
-
_playingItemIdx = list.selected;
|
|
128
|
-
_setItemPlaying(_playingItemIdx, true);
|
|
129
|
-
screen.render();
|
|
130
|
-
_pickerTtsProc.on('exit', () => {
|
|
131
|
-
if (_playingItemIdx >= 0) {
|
|
132
|
-
_setItemPlaying(_playingItemIdx, false);
|
|
133
|
-
_playingItemIdx = -1;
|
|
134
|
-
screen.render();
|
|
135
|
-
}
|
|
136
|
-
_pickerTtsProc = null;
|
|
137
|
-
});
|
|
138
|
-
_pickerTtsProc.unref();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
list.on('select item', () => {
|
|
142
|
-
_speakPreview(PERSONALITIES[list.selected]);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
list.key(['space'], () => {
|
|
146
|
-
if (_pickerTtsProc && _playingItemIdx === list.selected) {
|
|
147
|
-
_killPickerTts();
|
|
148
|
-
} else {
|
|
149
|
-
_speakPreview(PERSONALITIES[list.selected]);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// Type-to-jump
|
|
154
|
-
const _jumpBlocked = new Set(['j', 'k', 'g', 'h', 'l', 'd', 'u', 'q']);
|
|
155
|
-
list.on('keypress', (ch, key) => {
|
|
156
|
-
if (!ch || key.ctrl || key.meta) return;
|
|
157
|
-
const lower = ch.toLowerCase();
|
|
158
|
-
if (!/^[a-z]$/.test(lower)) return;
|
|
159
|
-
if (_jumpBlocked.has(lower)) return;
|
|
160
|
-
const count = PERSONALITIES.length;
|
|
161
|
-
const start = list.selected ?? 0;
|
|
162
|
-
for (let i = 1; i <= count; i++) {
|
|
163
|
-
const idx = (start + i) % count;
|
|
164
|
-
if (PERSONALITIES[idx].startsWith(lower)) {
|
|
165
|
-
list.select(idx);
|
|
166
|
-
screen.render();
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
list.key(['enter'], () => {
|
|
173
|
-
const selected = PERSONALITIES[list.selected];
|
|
174
|
-
if (!selected) return;
|
|
175
|
-
_killPickerTts();
|
|
176
|
-
// Call onSelect before destroying to avoid stale-state re-renders
|
|
177
|
-
onSelect(selected);
|
|
178
|
-
destroyList(list, screen, onClose);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
list.key(['escape', 'q'], () => {
|
|
182
|
-
_killPickerTts();
|
|
183
|
-
destroyList(list, screen, onClose);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes TUI — Shared Widget: Personality Picker
|
|
3
|
+
*
|
|
4
|
+
* Inline modal list for selecting voice personalities with TTS preview.
|
|
5
|
+
* Extracted from settings-tab.js for reuse across tabs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { destroyList } from './destroy-list.js';
|
|
11
|
+
import { buildAudioEnv } from '../audio-env.js';
|
|
12
|
+
import { BRAND_PINK } from '../brand-colors.js';
|
|
13
|
+
import { PERSONALITY_EMOJIS, PERSONALITIES } from '../constants/personalities.js';
|
|
14
|
+
|
|
15
|
+
export { PERSONALITY_EMOJIS, PERSONALITIES };
|
|
16
|
+
|
|
17
|
+
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
18
|
+
let blessed;
|
|
19
|
+
if (!IS_TEST) {
|
|
20
|
+
const { default: b } = await import('blessed');
|
|
21
|
+
blessed = b;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const _modalTitle = (text) => ` {${BRAND_PINK}-fg}${text}{/${BRAND_PINK}-fg} `;
|
|
25
|
+
|
|
26
|
+
const PERSONALITY_PREVIEW_PHRASES = Object.freeze({
|
|
27
|
+
angry: "UNACCEPTABLE! This build time is a DISASTER! Fix it NOW or so help me!",
|
|
28
|
+
annoying: "Oh oh oh! Can I tell you something? Can I? Can I? PLEASE? It is so important!",
|
|
29
|
+
crass: "Well damn, that code runs like my uncle's truck. Barely, and it smells funny.",
|
|
30
|
+
dramatic: "The tests... have failed. I don't know how much longer I can do this.",
|
|
31
|
+
'dry-humor': "Your code worked. I too am surprised.",
|
|
32
|
+
flirty: "Ooh, a clean merge? You know exactly how to make my heart race.",
|
|
33
|
+
funny: "Why do programmers hate nature? Too many bugs. I will show myself out.",
|
|
34
|
+
grandpa: "Back in my day, we compiled by hand. Uphill. In the snow. Both ways.",
|
|
35
|
+
millennial: "I literally cannot even with this error. I am so done. Like, actually deceased.",
|
|
36
|
+
moody: "...It works. Whatever. Do not get used to it.",
|
|
37
|
+
pirate: "Arrr! The build be sailin' smooth today, matey! No barnacles in sight!",
|
|
38
|
+
poetic: "Like rivers to the sea, your code flows toward eventual compilation.",
|
|
39
|
+
professional: "I have completed the requested task and am prepared to document outcomes.",
|
|
40
|
+
rapper: "Yo! Clean code flowin', tests are glowin', no bugs showin'!",
|
|
41
|
+
robot: "TASK COMPLETE. EFFICIENCY: OPTIMAL. PROBABILITY OF SUCCESS: 97.3 PERCENT. BEEP.",
|
|
42
|
+
sarcastic: "Oh wow, another bug. What a completely unexpected surprise. Truly shocking.",
|
|
43
|
+
sassy: "Honey, whoever told you that was good code was not your friend.",
|
|
44
|
+
'surfer-dude':"Duuude! That commit totally shredded! Gnarly clean code, bro!",
|
|
45
|
+
zen: "The bug is not the enemy. The bug is the teacher. Breathe. Commit.",
|
|
46
|
+
random: "Who will I be today? Even I do not know. Expect the unexpected.",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Open the personality picker modal.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} screen - blessed screen
|
|
53
|
+
* @param {string} currentPersonality - current personality value
|
|
54
|
+
* @param {Function} onSelect - called with selected personality
|
|
55
|
+
* @param {Function} [onClose] - called after modal closes
|
|
56
|
+
*/
|
|
57
|
+
export function openPersonalityPicker(screen, currentPersonality, onSelect, onClose) {
|
|
58
|
+
const current = currentPersonality ?? 'none';
|
|
59
|
+
const currentIdx = Math.max(0, PERSONALITIES.indexOf(current));
|
|
60
|
+
|
|
61
|
+
const COLORS = {
|
|
62
|
+
btnFocus: '#2e7d32',
|
|
63
|
+
btnFocusFg: '#ffffff',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const list = blessed.list({
|
|
67
|
+
parent: screen,
|
|
68
|
+
top: 'center',
|
|
69
|
+
left: 'center',
|
|
70
|
+
width: 44,
|
|
71
|
+
height: Math.min(PERSONALITIES.length + 4, 22),
|
|
72
|
+
border: { type: 'line' },
|
|
73
|
+
tags: true,
|
|
74
|
+
label: _modalTitle('Select Personality'),
|
|
75
|
+
items: PERSONALITIES.map((p, i) => {
|
|
76
|
+
const emoji = PERSONALITY_EMOJIS[p] ?? '✨';
|
|
77
|
+
const label = p === 'none' ? 'None' : p.charAt(0).toUpperCase() + p.slice(1);
|
|
78
|
+
const mark = i === currentIdx ? '✅' : ' ';
|
|
79
|
+
return `${mark} ${emoji} ${label}`;
|
|
80
|
+
}),
|
|
81
|
+
keys: true,
|
|
82
|
+
vi: true,
|
|
83
|
+
mouse: true,
|
|
84
|
+
style: {
|
|
85
|
+
border: { fg: COLORS.btnFocus },
|
|
86
|
+
selected: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
87
|
+
item: { fg: '#e3f2fd' },
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
list.select(currentIdx);
|
|
92
|
+
list.focus();
|
|
93
|
+
screen.render();
|
|
94
|
+
|
|
95
|
+
// TTS preview on hover
|
|
96
|
+
let _pickerTtsProc = null;
|
|
97
|
+
let _playingItemIdx = -1;
|
|
98
|
+
|
|
99
|
+
function _setItemPlaying(idx, playing) {
|
|
100
|
+
const item = list.items?.[idx];
|
|
101
|
+
if (!item) return;
|
|
102
|
+
const base = (item.content ?? '').replace(/ █$/, '').replace(/ \(playing\)$/, '');
|
|
103
|
+
item.setContent(playing ? `${base} (playing)` : base);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function _killPickerTts() {
|
|
107
|
+
if (_pickerTtsProc) {
|
|
108
|
+
try { process.kill(-_pickerTtsProc.pid, 'SIGTERM'); } catch {}
|
|
109
|
+
_pickerTtsProc = null;
|
|
110
|
+
}
|
|
111
|
+
if (_playingItemIdx >= 0) {
|
|
112
|
+
_setItemPlaying(_playingItemIdx, false);
|
|
113
|
+
_playingItemIdx = -1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function _speakPreview(personality) {
|
|
118
|
+
_killPickerTts();
|
|
119
|
+
const phrase = PERSONALITY_PREVIEW_PHRASES[personality];
|
|
120
|
+
if (!phrase) return;
|
|
121
|
+
const ttsScript = path.join(process.cwd(), '.claude', 'hooks', 'play-tts.sh');
|
|
122
|
+
_pickerTtsProc = spawn('bash', [ttsScript, phrase], {
|
|
123
|
+
stdio: 'ignore',
|
|
124
|
+
detached: true,
|
|
125
|
+
env: buildAudioEnv(),
|
|
126
|
+
});
|
|
127
|
+
_playingItemIdx = list.selected;
|
|
128
|
+
_setItemPlaying(_playingItemIdx, true);
|
|
129
|
+
screen.render();
|
|
130
|
+
_pickerTtsProc.on('exit', () => {
|
|
131
|
+
if (_playingItemIdx >= 0) {
|
|
132
|
+
_setItemPlaying(_playingItemIdx, false);
|
|
133
|
+
_playingItemIdx = -1;
|
|
134
|
+
screen.render();
|
|
135
|
+
}
|
|
136
|
+
_pickerTtsProc = null;
|
|
137
|
+
});
|
|
138
|
+
_pickerTtsProc.unref();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
list.on('select item', () => {
|
|
142
|
+
_speakPreview(PERSONALITIES[list.selected]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
list.key(['space'], () => {
|
|
146
|
+
if (_pickerTtsProc && _playingItemIdx === list.selected) {
|
|
147
|
+
_killPickerTts();
|
|
148
|
+
} else {
|
|
149
|
+
_speakPreview(PERSONALITIES[list.selected]);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Type-to-jump
|
|
154
|
+
const _jumpBlocked = new Set(['j', 'k', 'g', 'h', 'l', 'd', 'u', 'q']);
|
|
155
|
+
list.on('keypress', (ch, key) => {
|
|
156
|
+
if (!ch || key.ctrl || key.meta) return;
|
|
157
|
+
const lower = ch.toLowerCase();
|
|
158
|
+
if (!/^[a-z]$/.test(lower)) return;
|
|
159
|
+
if (_jumpBlocked.has(lower)) return;
|
|
160
|
+
const count = PERSONALITIES.length;
|
|
161
|
+
const start = list.selected ?? 0;
|
|
162
|
+
for (let i = 1; i <= count; i++) {
|
|
163
|
+
const idx = (start + i) % count;
|
|
164
|
+
if (PERSONALITIES[idx].startsWith(lower)) {
|
|
165
|
+
list.select(idx);
|
|
166
|
+
screen.render();
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
list.key(['enter'], () => {
|
|
173
|
+
const selected = PERSONALITIES[list.selected];
|
|
174
|
+
if (!selected) return;
|
|
175
|
+
_killPickerTts();
|
|
176
|
+
// Call onSelect before destroying to avoid stale-state re-renders
|
|
177
|
+
onSelect(selected);
|
|
178
|
+
destroyList(list, screen, onClose);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
list.key(['escape', 'q'], () => {
|
|
182
|
+
_killPickerTts();
|
|
183
|
+
destroyList(list, screen, onClose);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes TUI — Shared Widget: Reverb Preset Picker
|
|
3
|
-
*
|
|
4
|
-
* Inline modal list for selecting reverb presets.
|
|
5
|
-
* Extracted from settings-tab.js for reuse across tabs.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
import { spawnSync } from 'node:child_process';
|
|
10
|
-
import { destroyList } from './destroy-list.js';
|
|
11
|
-
import { BRAND_PINK } from '../brand-colors.js';
|
|
12
|
-
|
|
13
|
-
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
14
|
-
let blessed;
|
|
15
|
-
if (!IS_TEST) {
|
|
16
|
-
const { default: b } = await import('blessed');
|
|
17
|
-
blessed = b;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const _modalTitle = (text) => ` {${BRAND_PINK}-fg}${text}{/${BRAND_PINK}-fg} `;
|
|
21
|
-
|
|
22
|
-
export const REVERB_PRESETS = Object.freeze([
|
|
23
|
-
{ label: 'Off (Dry, no reverb)', value: 'off' },
|
|
24
|
-
{ label: 'Light (Small room)', value: 'light' },
|
|
25
|
-
{ label: 'Medium (Conference room)', value: 'medium' },
|
|
26
|
-
{ label: 'Heavy (Large hall)', value: 'heavy' },
|
|
27
|
-
{ label: 'Cathedral (Epic space)', value: 'cathedral' },
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Open the reverb preset picker modal.
|
|
32
|
-
*
|
|
33
|
-
* @param {object} screen - blessed screen
|
|
34
|
-
* @param {string} currentPreset - current reverb preset value
|
|
35
|
-
* @param {Function} onSelect - called with selected preset value
|
|
36
|
-
* @param {Function} [onClose] - called after modal closes
|
|
37
|
-
* @param {object} [opts] - options
|
|
38
|
-
* @param {boolean} [opts.applyToEffectsManager=true] - whether to apply via effects-manager.sh
|
|
39
|
-
*/
|
|
40
|
-
export function openReverbPicker(screen, currentPreset, onSelect, onClose, opts = {}) {
|
|
41
|
-
const applyToEffectsManager = opts.applyToEffectsManager !== false;
|
|
42
|
-
const currentIdx = Math.max(0, REVERB_PRESETS.findIndex(p => p.value === currentPreset));
|
|
43
|
-
|
|
44
|
-
const COLORS = {
|
|
45
|
-
btnFocus: '#
|
|
46
|
-
btnFocusFg: '#
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const list = blessed.list({
|
|
50
|
-
parent: screen,
|
|
51
|
-
top: 'center',
|
|
52
|
-
left: 'center',
|
|
53
|
-
width: 40,
|
|
54
|
-
height: REVERB_PRESETS.length + 4,
|
|
55
|
-
border: { type: 'line' },
|
|
56
|
-
tags: true,
|
|
57
|
-
label: _modalTitle('Select Reverb Preset'),
|
|
58
|
-
items: REVERB_PRESETS.map((p, i) => (i === currentIdx ? `● ${p.label}` : ` ${p.label}`)),
|
|
59
|
-
keys: true,
|
|
60
|
-
vi: false,
|
|
61
|
-
mouse: true,
|
|
62
|
-
style: {
|
|
63
|
-
border: { fg: COLORS.btnFocus },
|
|
64
|
-
selected: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
65
|
-
item: { fg: '#e3f2fd' },
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
list.select(currentIdx);
|
|
70
|
-
list.focus();
|
|
71
|
-
screen.render();
|
|
72
|
-
|
|
73
|
-
list.key(['enter', 'space'], () => {
|
|
74
|
-
const selected = REVERB_PRESETS[list.selected];
|
|
75
|
-
if (!selected) return;
|
|
76
|
-
|
|
77
|
-
if (applyToEffectsManager) {
|
|
78
|
-
const effectsScript = path.join(process.cwd(), '.claude', 'hooks', 'effects-manager.sh');
|
|
79
|
-
spawnSync('bash', [effectsScript, 'set-reverb', selected.value, 'default'], {
|
|
80
|
-
stdio: 'ignore',
|
|
81
|
-
timeout: 5000,
|
|
82
|
-
env: { ...process.env },
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Call onSelect before destroying to avoid stale-state re-renders
|
|
87
|
-
onSelect(selected.value);
|
|
88
|
-
destroyList(list, screen, onClose);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
list.key(['escape', 'q'], () => {
|
|
92
|
-
destroyList(list, screen, onClose);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes TUI — Shared Widget: Reverb Preset Picker
|
|
3
|
+
*
|
|
4
|
+
* Inline modal list for selecting reverb presets.
|
|
5
|
+
* Extracted from settings-tab.js for reuse across tabs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { destroyList } from './destroy-list.js';
|
|
11
|
+
import { BRAND_PINK } from '../brand-colors.js';
|
|
12
|
+
|
|
13
|
+
const IS_TEST = process.env.AGENTVIBES_TEST_MODE === 'true';
|
|
14
|
+
let blessed;
|
|
15
|
+
if (!IS_TEST) {
|
|
16
|
+
const { default: b } = await import('blessed');
|
|
17
|
+
blessed = b;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const _modalTitle = (text) => ` {${BRAND_PINK}-fg}${text}{/${BRAND_PINK}-fg} `;
|
|
21
|
+
|
|
22
|
+
export const REVERB_PRESETS = Object.freeze([
|
|
23
|
+
{ label: 'Off (Dry, no reverb)', value: 'off' },
|
|
24
|
+
{ label: 'Light (Small room)', value: 'light' },
|
|
25
|
+
{ label: 'Medium (Conference room)', value: 'medium' },
|
|
26
|
+
{ label: 'Heavy (Large hall)', value: 'heavy' },
|
|
27
|
+
{ label: 'Cathedral (Epic space)', value: 'cathedral' },
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Open the reverb preset picker modal.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} screen - blessed screen
|
|
34
|
+
* @param {string} currentPreset - current reverb preset value
|
|
35
|
+
* @param {Function} onSelect - called with selected preset value
|
|
36
|
+
* @param {Function} [onClose] - called after modal closes
|
|
37
|
+
* @param {object} [opts] - options
|
|
38
|
+
* @param {boolean} [opts.applyToEffectsManager=true] - whether to apply via effects-manager.sh
|
|
39
|
+
*/
|
|
40
|
+
export function openReverbPicker(screen, currentPreset, onSelect, onClose, opts = {}) {
|
|
41
|
+
const applyToEffectsManager = opts.applyToEffectsManager !== false;
|
|
42
|
+
const currentIdx = Math.max(0, REVERB_PRESETS.findIndex(p => p.value === currentPreset));
|
|
43
|
+
|
|
44
|
+
const COLORS = {
|
|
45
|
+
btnFocus: '#2e7d32',
|
|
46
|
+
btnFocusFg: '#ffffff',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const list = blessed.list({
|
|
50
|
+
parent: screen,
|
|
51
|
+
top: 'center',
|
|
52
|
+
left: 'center',
|
|
53
|
+
width: 40,
|
|
54
|
+
height: REVERB_PRESETS.length + 4,
|
|
55
|
+
border: { type: 'line' },
|
|
56
|
+
tags: true,
|
|
57
|
+
label: _modalTitle('Select Reverb Preset'),
|
|
58
|
+
items: REVERB_PRESETS.map((p, i) => (i === currentIdx ? `● ${p.label}` : ` ${p.label}`)),
|
|
59
|
+
keys: true,
|
|
60
|
+
vi: false,
|
|
61
|
+
mouse: true,
|
|
62
|
+
style: {
|
|
63
|
+
border: { fg: COLORS.btnFocus },
|
|
64
|
+
selected: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
65
|
+
item: { fg: '#e3f2fd' },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
list.select(currentIdx);
|
|
70
|
+
list.focus();
|
|
71
|
+
screen.render();
|
|
72
|
+
|
|
73
|
+
list.key(['enter', 'space'], () => {
|
|
74
|
+
const selected = REVERB_PRESETS[list.selected];
|
|
75
|
+
if (!selected) return;
|
|
76
|
+
|
|
77
|
+
if (applyToEffectsManager) {
|
|
78
|
+
const effectsScript = path.join(process.cwd(), '.claude', 'hooks', 'effects-manager.sh');
|
|
79
|
+
spawnSync('bash', [effectsScript, 'set-reverb', selected.value, 'default'], {
|
|
80
|
+
stdio: 'ignore',
|
|
81
|
+
timeout: 5000,
|
|
82
|
+
env: { ...process.env },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Call onSelect before destroying to avoid stale-state re-renders
|
|
87
|
+
onSelect(selected.value);
|
|
88
|
+
destroyList(list, screen, onClose);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
list.key(['escape', 'q'], () => {
|
|
92
|
+
destroyList(list, screen, onClose);
|
|
93
|
+
});
|
|
94
|
+
}
|