agentvibes 5.7.4 → 5.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/play-tts-ssh-remote.sh +14 -0
- package/.claude/hooks/play-tts.sh +2 -2
- package/README.md +14 -2
- package/RELEASE_NOTES.md +36 -0
- package/package.json +1 -1
- package/src/console/app.js +7 -0
- package/src/console/tabs/agents-tab.js +64 -10
- package/src/console/tabs/music-tab.js +4 -4
- package/src/console/tabs/settings-tab.js +1 -1
- package/src/console/tabs/setup-tab.js +94 -28
- package/src/console/tabs/voices-tab.js +5 -5
- package/src/installer.js +15 -1
- package/src/services/llm-provider-service.js +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20260513
|
|
@@ -17,6 +17,7 @@ set -euo pipefail
|
|
|
17
17
|
TEXT="${1:-}"
|
|
18
18
|
VOICE="${2:-en_US-lessac-medium}"
|
|
19
19
|
AGENT_NAME="${3:-default}"
|
|
20
|
+
AGENT_PROFILE_FILE="${4:-}"
|
|
20
21
|
|
|
21
22
|
# LLM identity — forwarded to the remote so it can look up its own
|
|
22
23
|
# audio-effects.cfg llm:<name> row for voice, reverb, music, pretext, engine.
|
|
@@ -210,6 +211,19 @@ done
|
|
|
210
211
|
[[ -n "$LLM_BG_FILE" ]] && BG_FILE="$LLM_BG_FILE"
|
|
211
212
|
[[ -n "$LLM_BG_VOLUME" ]] && BG_VOLUME="$LLM_BG_VOLUME"
|
|
212
213
|
|
|
214
|
+
# Per-agent profile (written by bmad-speak.sh) takes highest priority for music
|
|
215
|
+
if [[ -n "$AGENT_PROFILE_FILE" ]] && [[ -f "$AGENT_PROFILE_FILE" ]]; then
|
|
216
|
+
_prof_track=$(_AV_PROF="$AGENT_PROFILE_FILE" node -e "try{const p=JSON.parse(require('fs').readFileSync(process.env._AV_PROF,'utf8'));process.stdout.write(p.backgroundMusic?.track??'')}catch{process.stdout.write('')}" 2>/dev/null || true)
|
|
217
|
+
_prof_vol=$(_AV_PROF="$AGENT_PROFILE_FILE" node -e "try{const p=JSON.parse(require('fs').readFileSync(process.env._AV_PROF,'utf8'));process.stdout.write(String(p.backgroundMusic?.volume??''))}catch{process.stdout.write('')}" 2>/dev/null || true)
|
|
218
|
+
_prof_enabled=$(_AV_PROF="$AGENT_PROFILE_FILE" node -e "try{const p=JSON.parse(require('fs').readFileSync(process.env._AV_PROF,'utf8'));process.stdout.write(String(p.backgroundMusic?.enabled??''))}catch{process.stdout.write('')}" 2>/dev/null || true)
|
|
219
|
+
if [[ "$_prof_enabled" == "true" ]] && [[ -n "$_prof_track" ]]; then
|
|
220
|
+
BG_FILE="$_prof_track"
|
|
221
|
+
if [[ "$_prof_vol" =~ ^[0-9]+$ ]]; then
|
|
222
|
+
BG_VOLUME=$(awk "BEGIN{printf \"%.2f\", ${_prof_vol}/100}")
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
fi
|
|
226
|
+
|
|
213
227
|
# Read pretext if configured
|
|
214
228
|
PRETEXT=""
|
|
215
229
|
PRETEXT_FILE="$PROJECT_ROOT/.agentvibes/config/pretext.txt"
|
|
@@ -367,7 +367,7 @@ speak_text() {
|
|
|
367
367
|
bash "$SCRIPT_DIR/play-tts-termux-ssh.sh" "$text" "$voice"
|
|
368
368
|
;;
|
|
369
369
|
ssh-remote)
|
|
370
|
-
bash "$SCRIPT_DIR/play-tts-ssh-remote.sh" "$text" "$voice"
|
|
370
|
+
bash "$SCRIPT_DIR/play-tts-ssh-remote.sh" "$text" "$voice" "" "${profile_file:-}"
|
|
371
371
|
;;
|
|
372
372
|
agentvibes-receiver)
|
|
373
373
|
bash "$SCRIPT_DIR/play-tts-agentvibes-receiver-for-voiceless-connections.sh" "$text" "$voice"
|
|
@@ -497,7 +497,7 @@ case "$ACTIVE_PROVIDER" in
|
|
|
497
497
|
exec bash "$SCRIPT_DIR/play-tts-termux-ssh.sh" "$TEXT" "$VOICE_OVERRIDE"
|
|
498
498
|
;;
|
|
499
499
|
ssh-remote)
|
|
500
|
-
exec bash "$SCRIPT_DIR/play-tts-ssh-remote.sh" "$TEXT" "$VOICE_OVERRIDE"
|
|
500
|
+
exec bash "$SCRIPT_DIR/play-tts-ssh-remote.sh" "$TEXT" "$VOICE_OVERRIDE" "" "${AGENT_PROFILE_FILE:-}"
|
|
501
501
|
;;
|
|
502
502
|
agentvibes-receiver)
|
|
503
503
|
exec bash "$SCRIPT_DIR/play-tts-agentvibes-receiver-for-voiceless-connections.sh" "$TEXT" "$VOICE_OVERRIDE"
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
[](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml)
|
|
12
12
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
13
13
|
|
|
14
|
-
**Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v5.7.
|
|
14
|
+
**Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v5.7.5
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -40,7 +40,19 @@ Whether you're coding in Claude Code, chatting in Claude Desktop, using Warp Ter
|
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
## 🌟 NEW IN v5.7.
|
|
43
|
+
## 🌟 NEW IN v5.7.5 — TUI Button Contrast + BMAD Routing Fixes
|
|
44
|
+
|
|
45
|
+
**TUI buttons:** All focused/selected buttons now show white text on dark green — grey text on light-blue is gone across all terminals and all tabs.
|
|
46
|
+
|
|
47
|
+
**BMAD tab:** The ♪ voice preview indicator now appears correctly in the voice list, with a 2-second minimum display timer for SSH-remote fire-and-forget mode.
|
|
48
|
+
|
|
49
|
+
**Installer pretext:** Non-interactive installs now derive the pretext from the project folder name (e.g., `"MyProject here"`) instead of always defaulting to `"Claude Code here"`.
|
|
50
|
+
|
|
51
|
+
**BMAD music routing:** Per-agent background music and reverb overrides now correctly reach the SSH receiver.
|
|
52
|
+
|
|
53
|
+
**TERM fix:** The TUI no longer throws a `plab_norm` error when `TERM` is a `screen-*` or `tmux-*` variant.
|
|
54
|
+
|
|
55
|
+
## v5.7.0 — BMAD v6.6 Support + Windows Auto-Restart Watcher
|
|
44
56
|
|
|
45
57
|
**BMAD v6.6.0:** AgentVibes now detects the new `.claude/skills/*/agents/` agent structure, correctly handles globally-installed BMAD at `~/_bmad`, and gracefully skips v6.6+ plain-Markdown agents during TTS injection instead of erroring. The BMAD tab now shows detection correctly for global installs.
|
|
46
58
|
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# AgentVibes Release Notes
|
|
2
2
|
|
|
3
|
+
## 🖥️ v5.7.5 — TUI Button Contrast + BMAD Routing Fixes
|
|
4
|
+
|
|
5
|
+
**Released:** 2026-05-13
|
|
6
|
+
|
|
7
|
+
### 🐛 TUI Button Focus: Grey Text Eliminated Across All Terminals
|
|
8
|
+
|
|
9
|
+
Focused and selected buttons in the TUI (voices, music, settings, setup tabs) displayed light grey text on light-blue backgrounds in many terminals. Root cause: `bold: true` combined with a dark foreground triggers terminal "bright mode," rendering the color as grey regardless of shade.
|
|
10
|
+
|
|
11
|
+
**Fix:** All button focus states now use **white text on dark green (`#2e7d32`) background** — the same high-contrast pattern already used by the Agents tab. Explicit `focus`/`blur` handlers were added to setup-tab modal buttons to prevent `attachBtnBlink` from interfering with blessed's passive `style.focus` color application.
|
|
12
|
+
|
|
13
|
+
### 🐛 BMAD Tab Voice Picker ♪ Indicator Not Showing
|
|
14
|
+
|
|
15
|
+
The ♪ preview indicator in the BMAD tab voice list didn't appear during preview. The Agents tab was missing `_refreshVP()` calls that the Settings tab already had. A 2-second minimum display timer now keeps the indicator visible when SSH-remote exits immediately (fire-and-forget mode).
|
|
16
|
+
|
|
17
|
+
### 🐛 Non-Interactive Install: Generic Pretext Instead of Project Name
|
|
18
|
+
|
|
19
|
+
Running `agentvibes install` non-interactively always set the pretext to `"Claude Code here"` regardless of project. The installer now derives a project-aware pretext from `path.basename(process.cwd())` with capitalization (e.g., `"MyProject here"`), with a safe fallback for Docker root paths.
|
|
20
|
+
|
|
21
|
+
### 🐛 Global Pretext Overriding Per-Project Config
|
|
22
|
+
|
|
23
|
+
`seedAllLlmDefaultsSync` was seeding project-level LLM rows with the global pretext string, causing the global `"Claude Code here"` to override per-project `tts-pretext.txt` values. Project-level rows are now seeded with empty pretexts so the per-project file takes precedence.
|
|
24
|
+
|
|
25
|
+
### 🐛 `screen`/`tmux` TERM Variant Caused `plab_norm` Capability Error
|
|
26
|
+
|
|
27
|
+
When `TERM` was set to a `screen-*` or `tmux-*` variant, blessed threw a `plab_norm` terminal capability error on startup. The app now overrides `TERM` to `xterm-256color` before creating the blessed screen when such a variant is detected.
|
|
28
|
+
|
|
29
|
+
### 🐛 BMAD Per-Agent Music/Reverb Not Reaching SSH Receiver
|
|
30
|
+
|
|
31
|
+
`play-tts.sh` was not forwarding `AGENT_PROFILE_FILE` to the SSH remote transport, so per-agent background music and reverb overrides in the BMAD tab were silently ignored for remote audio. The profile file path is now passed as argument 4 to `play-tts-ssh-remote.sh`.
|
|
32
|
+
|
|
33
|
+
### 🐛 Node 18 Compatibility: `import.meta.dirname` Replaced
|
|
34
|
+
|
|
35
|
+
A test file used `import.meta.dirname`, available only in Node 21+. Replaced with the `fileURLToPath(import.meta.url)` pattern so tests run correctly on Node 18 and 20.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
3
39
|
## 🎭 v5.7.0 — BMAD v6.6 Support + Windows Auto-Restart Watcher
|
|
4
40
|
|
|
5
41
|
**Released:** 2026-05-11
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "agentvibes",
|
|
4
|
-
"version": "5.7.
|
|
4
|
+
"version": "5.7.5",
|
|
5
5
|
"description": "Now your AI Agents can finally talk back! Professional TTS voice for Claude Code, Claude Desktop (via MCP), and Clawdbot with multi-provider support.",
|
|
6
6
|
"homepage": "https://agentvibes.org",
|
|
7
7
|
"keywords": [
|
package/src/console/app.js
CHANGED
|
@@ -138,7 +138,14 @@ export class AgentVibesConsole {
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// blessed's terminfo parser chokes on screen-256color's plab_norm capability.
|
|
142
|
+
// xterm-256color is functionally identical for our TUI and parses cleanly.
|
|
143
|
+
const _origTerm = process.env.TERM;
|
|
144
|
+
if (/^(screen|tmux)(-256color)?$/.test(process.env.TERM || '')) {
|
|
145
|
+
process.env.TERM = 'xterm-256color';
|
|
146
|
+
}
|
|
141
147
|
this.screen = blessed.screen(this._screenOptions);
|
|
148
|
+
if (_origTerm !== undefined) process.env.TERM = _origTerm;
|
|
142
149
|
|
|
143
150
|
// Reflow on terminal resize
|
|
144
151
|
this.screen.on('resize', () => this.screen.render());
|
|
@@ -367,7 +367,7 @@ ${_tl('bmadDesc')}
|
|
|
367
367
|
padding: { left: 1, right: 1 },
|
|
368
368
|
style: {
|
|
369
369
|
bg: COLORS.btnDefault,
|
|
370
|
-
fg: 'white',
|
|
370
|
+
fg: 'bright-white',
|
|
371
371
|
focus: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
372
372
|
hover: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
373
373
|
},
|
|
@@ -711,7 +711,7 @@ ${_tl('bmadDesc')}
|
|
|
711
711
|
padding: { left: 1, right: 1 },
|
|
712
712
|
style: {
|
|
713
713
|
bg: COLORS.btnDefault,
|
|
714
|
-
fg: 'white',
|
|
714
|
+
fg: 'bright-white',
|
|
715
715
|
focus: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
716
716
|
hover: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
717
717
|
},
|
|
@@ -1139,8 +1139,6 @@ ${_tl('bmadDesc')}
|
|
|
1139
1139
|
const inputBox = blessed.textbox({
|
|
1140
1140
|
parent: editModal, top: 3, left: 2, right: 2, height: 3,
|
|
1141
1141
|
border: { type: 'line' },
|
|
1142
|
-
inputOnFocus: true,
|
|
1143
|
-
value: draft.pretext,
|
|
1144
1142
|
style: {
|
|
1145
1143
|
fg: COLORS.valueFg, bg: '#0d1b35',
|
|
1146
1144
|
border: { fg: COLORS.borderFg },
|
|
@@ -1149,22 +1147,78 @@ ${_tl('bmadDesc')}
|
|
|
1149
1147
|
});
|
|
1150
1148
|
|
|
1151
1149
|
let _editClosed = false;
|
|
1150
|
+
let _cursor = draft.pretext.length;
|
|
1151
|
+
inputBox.value = draft.pretext;
|
|
1152
|
+
|
|
1153
|
+
function _renderPretext() {
|
|
1154
|
+
const val = inputBox.value;
|
|
1155
|
+
const lpos = inputBox._getCoords();
|
|
1156
|
+
if (!lpos) { screen.render(); return; }
|
|
1157
|
+
const contentWidth = Math.max(1, (lpos.xl - lpos.xi) - inputBox.iwidth);
|
|
1158
|
+
const start = _cursor > contentWidth - 1 ? _cursor - contentWidth + 1 : 0;
|
|
1159
|
+
inputBox.setContent(val.slice(start));
|
|
1160
|
+
screen.render();
|
|
1161
|
+
screen.program.cup(lpos.yi + inputBox.itop, lpos.xi + inputBox.ileft + (_cursor - start));
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
const _prevGrabKeys = screen.grabKeys;
|
|
1152
1165
|
function _closeEdit(save) {
|
|
1153
1166
|
if (_editClosed) return;
|
|
1154
1167
|
_editClosed = true;
|
|
1168
|
+
inputBox.removeAllListeners('keypress');
|
|
1169
|
+
screen.grabKeys = _prevGrabKeys;
|
|
1170
|
+
screen.program.hideCursor();
|
|
1155
1171
|
if (save) {
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
draft.pretext = (raw || draft.pretext).slice(0, MAX_PRETEXT_LENGTH);
|
|
1172
|
+
// M7: enforce max pretext length; allow clearing to empty string
|
|
1173
|
+
draft.pretext = inputBox.value.trim().slice(0, MAX_PRETEXT_LENGTH);
|
|
1159
1174
|
}
|
|
1160
1175
|
destroyList(editModal, screen, onDone);
|
|
1161
1176
|
}
|
|
1162
1177
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1178
|
+
// Guard: if editModal is destroyed externally (parent closed, signal, etc.)
|
|
1179
|
+
// without _closeEdit being called, restore grab state so TUI stays responsive.
|
|
1180
|
+
editModal.once('destroy', () => {
|
|
1181
|
+
if (!_editClosed) {
|
|
1182
|
+
_editClosed = true;
|
|
1183
|
+
inputBox.removeAllListeners('keypress');
|
|
1184
|
+
screen.grabKeys = _prevGrabKeys;
|
|
1185
|
+
screen.program.hideCursor();
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1165
1188
|
|
|
1189
|
+
screen.grabKeys = true;
|
|
1166
1190
|
inputBox.focus();
|
|
1167
|
-
screen.render();
|
|
1191
|
+
screen.render(); // Layout pass so _getCoords() returns valid coords on first _renderPretext
|
|
1192
|
+
screen.program.showCursor();
|
|
1193
|
+
_renderPretext();
|
|
1194
|
+
|
|
1195
|
+
inputBox.on('keypress', function(ch, key) {
|
|
1196
|
+
if (_editClosed) return;
|
|
1197
|
+
const val = inputBox.value;
|
|
1198
|
+
if (key.name === 'enter') { _closeEdit(true); return; }
|
|
1199
|
+
if (key.name === 'escape') { _closeEdit(false); return; }
|
|
1200
|
+
if (key.name === 'home' || (key.ctrl && key.name === 'a')) {
|
|
1201
|
+
_cursor = 0;
|
|
1202
|
+
} else if (key.name === 'end' || (key.ctrl && key.name === 'e')) {
|
|
1203
|
+
_cursor = val.length;
|
|
1204
|
+
} else if (key.name === 'left') {
|
|
1205
|
+
if (_cursor > 0) _cursor--; else return;
|
|
1206
|
+
} else if (key.name === 'right') {
|
|
1207
|
+
if (_cursor < val.length) _cursor++; else return;
|
|
1208
|
+
} else if (key.name === 'backspace') {
|
|
1209
|
+
if (_cursor > 0) { inputBox.value = val.slice(0, _cursor - 1) + val.slice(_cursor); _cursor--; }
|
|
1210
|
+
else return;
|
|
1211
|
+
} else if (key.name === 'delete') {
|
|
1212
|
+
if (_cursor < val.length) { inputBox.value = val.slice(0, _cursor) + val.slice(_cursor + 1); }
|
|
1213
|
+
else return;
|
|
1214
|
+
} else if (ch && !key.ctrl && !key.meta && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(ch)) {
|
|
1215
|
+
inputBox.value = val.slice(0, _cursor) + ch + val.slice(_cursor);
|
|
1216
|
+
_cursor++;
|
|
1217
|
+
} else {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
_renderPretext();
|
|
1221
|
+
});
|
|
1168
1222
|
}
|
|
1169
1223
|
|
|
1170
1224
|
// -------------------------------------------------------------------------
|
|
@@ -409,7 +409,7 @@ export function createMusicTab(screen, services) {
|
|
|
409
409
|
padding: { left: 1, right: 1 },
|
|
410
410
|
style: {
|
|
411
411
|
bg: COLORS.btnDefault,
|
|
412
|
-
fg: 'white',
|
|
412
|
+
fg: 'bright-white',
|
|
413
413
|
focus: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
414
414
|
hover: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
415
415
|
},
|
|
@@ -423,7 +423,7 @@ export function createMusicTab(screen, services) {
|
|
|
423
423
|
});
|
|
424
424
|
btn.on('blur', () => {
|
|
425
425
|
btn.style.bg = COLORS.btnDefault;
|
|
426
|
-
btn.style.fg = 'white';
|
|
426
|
+
btn.style.fg = 'bright-white';
|
|
427
427
|
const raw = btn.content.replace(/[►◄]/g, '').trim();
|
|
428
428
|
btn.setContent(raw);
|
|
429
429
|
screen.render();
|
|
@@ -776,7 +776,7 @@ export function createMusicTab(screen, services) {
|
|
|
776
776
|
padding: { left: 1, right: 1 },
|
|
777
777
|
style: {
|
|
778
778
|
bg,
|
|
779
|
-
fg: 'white',
|
|
779
|
+
fg: 'bright-white',
|
|
780
780
|
focus: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
781
781
|
hover: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
782
782
|
},
|
|
@@ -819,7 +819,7 @@ export function createMusicTab(screen, services) {
|
|
|
819
819
|
padding: { left: 1, right: 1 },
|
|
820
820
|
style: {
|
|
821
821
|
bg: '#e65100',
|
|
822
|
-
fg: 'white',
|
|
822
|
+
fg: 'bright-white',
|
|
823
823
|
focus: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
824
824
|
hover: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
825
825
|
},
|
|
@@ -583,7 +583,7 @@ export function createSettingsTab(screen, services) {
|
|
|
583
583
|
style: {
|
|
584
584
|
fg: COLORS.labelFg, bg: COLORS.contentBg,
|
|
585
585
|
border: { fg: 'blue' },
|
|
586
|
-
selected: { bg: 'green', fg: '
|
|
586
|
+
selected: { bg: 'green', fg: 'black', bold: true },
|
|
587
587
|
item: { fg: COLORS.labelFg },
|
|
588
588
|
},
|
|
589
589
|
});
|
|
@@ -78,7 +78,7 @@ const COLORS = {
|
|
|
78
78
|
noticeFg: 'white',
|
|
79
79
|
btnBg: 'blue',
|
|
80
80
|
btnFg: 'white',
|
|
81
|
-
btnFocusBg: '
|
|
81
|
+
btnFocusBg: '#2e7d32',
|
|
82
82
|
removeBg: 'red',
|
|
83
83
|
removeFocusBg: 'magenta',
|
|
84
84
|
cfgBg: 'green',
|
|
@@ -354,7 +354,7 @@ export function createSetupTab(screen, services) {
|
|
|
354
354
|
content: ' Install ', tags: true, mouse: true, keys: true, hidden: true,
|
|
355
355
|
style: {
|
|
356
356
|
fg: COLORS.btnFg, bg: COLORS.btnBg,
|
|
357
|
-
focus: { fg: '
|
|
357
|
+
focus: { fg: 'white', bg: COLORS.btnFocusBg },
|
|
358
358
|
},
|
|
359
359
|
});
|
|
360
360
|
|
|
@@ -534,7 +534,7 @@ export function createSetupTab(screen, services) {
|
|
|
534
534
|
style: {
|
|
535
535
|
fg: COLORS.btnFg,
|
|
536
536
|
bg: COLORS.btnBg,
|
|
537
|
-
focus: { fg: '
|
|
537
|
+
focus: { fg: 'white', bg: COLORS.btnFocusBg },
|
|
538
538
|
},
|
|
539
539
|
});
|
|
540
540
|
|
|
@@ -870,10 +870,12 @@ export function createSetupTab(screen, services) {
|
|
|
870
870
|
padding: { left: 1, right: 1 },
|
|
871
871
|
style: {
|
|
872
872
|
bg: 'blue', fg: 'white',
|
|
873
|
-
focus: { bg: '
|
|
874
|
-
hover: { bg: '
|
|
873
|
+
focus: { bg: '#2e7d32', fg: 'white', bold: true },
|
|
874
|
+
hover: { bg: '#2e7d32', fg: 'white', bold: true },
|
|
875
875
|
},
|
|
876
876
|
});
|
|
877
|
+
btn.on('focus', () => { btn.style.bg = '#2e7d32'; btn.style.fg = 'white'; screen.render(); });
|
|
878
|
+
btn.on('blur', () => { btn.style.bg = 'blue'; btn.style.fg = 'white'; screen.render(); });
|
|
877
879
|
btn.key(['enter', 'space'], () => onClick());
|
|
878
880
|
btn.on('click', () => onClick());
|
|
879
881
|
return btn;
|
|
@@ -1115,7 +1117,7 @@ export function createSetupTab(screen, services) {
|
|
|
1115
1117
|
style: {
|
|
1116
1118
|
fg: COLORS.labelFg, bg: COLORS.contentBg,
|
|
1117
1119
|
border: { fg: 'cyan' },
|
|
1118
|
-
selected: { bg: 'blue', fg: '
|
|
1120
|
+
selected: { bg: 'blue', fg: 'black', bold: true },
|
|
1119
1121
|
},
|
|
1120
1122
|
});
|
|
1121
1123
|
picker.setFront();
|
|
@@ -1154,7 +1156,7 @@ export function createSetupTab(screen, services) {
|
|
|
1154
1156
|
style: {
|
|
1155
1157
|
fg: COLORS.labelFg, bg: COLORS.contentBg,
|
|
1156
1158
|
border: { fg: 'cyan' },
|
|
1157
|
-
selected: { bg: 'blue', fg: '
|
|
1159
|
+
selected: { bg: 'blue', fg: 'black', bold: true },
|
|
1158
1160
|
},
|
|
1159
1161
|
});
|
|
1160
1162
|
picker.setFront();
|
|
@@ -1268,7 +1270,7 @@ export function createSetupTab(screen, services) {
|
|
|
1268
1270
|
style: {
|
|
1269
1271
|
fg: COLORS.labelFg, bg: COLORS.contentBg,
|
|
1270
1272
|
border: { fg: 'cyan' },
|
|
1271
|
-
selected: { bg: 'blue', fg: '
|
|
1273
|
+
selected: { bg: 'blue', fg: 'black', bold: true },
|
|
1272
1274
|
},
|
|
1273
1275
|
});
|
|
1274
1276
|
picker.setFront();
|
|
@@ -1326,7 +1328,7 @@ export function createSetupTab(screen, services) {
|
|
|
1326
1328
|
style: {
|
|
1327
1329
|
fg: COLORS.labelFg, bg: COLORS.contentBg,
|
|
1328
1330
|
border: { fg: 'cyan' },
|
|
1329
|
-
selected: { bg: 'blue', fg: '
|
|
1331
|
+
selected: { bg: 'blue', fg: 'black', bold: true },
|
|
1330
1332
|
},
|
|
1331
1333
|
});
|
|
1332
1334
|
picker.setFront();
|
|
@@ -1364,7 +1366,7 @@ export function createSetupTab(screen, services) {
|
|
|
1364
1366
|
style: {
|
|
1365
1367
|
fg: COLORS.labelFg, bg: COLORS.contentBg,
|
|
1366
1368
|
border: { fg: 'cyan' },
|
|
1367
|
-
selected: { bg: 'blue', fg: '
|
|
1369
|
+
selected: { bg: 'blue', fg: 'black', bold: true },
|
|
1368
1370
|
},
|
|
1369
1371
|
});
|
|
1370
1372
|
picker.setFront();
|
|
@@ -1400,7 +1402,7 @@ export function createSetupTab(screen, services) {
|
|
|
1400
1402
|
style: {
|
|
1401
1403
|
fg: COLORS.labelFg, bg: COLORS.contentBg,
|
|
1402
1404
|
border: { fg: 'cyan' },
|
|
1403
|
-
selected: { bg: 'blue', fg: '
|
|
1405
|
+
selected: { bg: 'blue', fg: 'black', bold: true },
|
|
1404
1406
|
},
|
|
1405
1407
|
});
|
|
1406
1408
|
picker.setFront();
|
|
@@ -1501,10 +1503,12 @@ export function createSetupTab(screen, services) {
|
|
|
1501
1503
|
padding: { left: 1, right: 1 },
|
|
1502
1504
|
style: {
|
|
1503
1505
|
bg: 'blue', fg: 'white',
|
|
1504
|
-
focus: { bg: '
|
|
1505
|
-
hover: { bg: '
|
|
1506
|
+
focus: { bg: '#2e7d32', fg: 'white', bold: true },
|
|
1507
|
+
hover: { bg: '#2e7d32', fg: 'white', bold: true },
|
|
1506
1508
|
},
|
|
1507
1509
|
});
|
|
1510
|
+
btn.on('focus', () => { btn.style.bg = '#2e7d32'; btn.style.fg = 'white'; screen.render(); });
|
|
1511
|
+
btn.on('blur', () => { btn.style.bg = 'blue'; btn.style.fg = 'white'; screen.render(); });
|
|
1508
1512
|
btn.key(['enter', 'space'], () => onClick());
|
|
1509
1513
|
btn.on('click', () => onClick());
|
|
1510
1514
|
return btn;
|
|
@@ -1618,11 +1622,15 @@ export function createSetupTab(screen, services) {
|
|
|
1618
1622
|
let _closed = false;
|
|
1619
1623
|
navigationService?.openModal(null, _closeModal);
|
|
1620
1624
|
|
|
1625
|
+
const _folderName = path.basename(targetDir);
|
|
1626
|
+
const _folderPretext = _folderName
|
|
1627
|
+
? _folderName.charAt(0).toUpperCase() + _folderName.slice(1) + ' here'
|
|
1628
|
+
: '';
|
|
1621
1629
|
const defaultPretext = {
|
|
1622
|
-
'claude-code':
|
|
1623
|
-
'copilot':
|
|
1624
|
-
'codex':
|
|
1625
|
-
'default':
|
|
1630
|
+
'claude-code': _folderPretext,
|
|
1631
|
+
'copilot': _folderPretext,
|
|
1632
|
+
'codex': _folderPretext,
|
|
1633
|
+
'default': _folderPretext,
|
|
1626
1634
|
};
|
|
1627
1635
|
|
|
1628
1636
|
// Read global defaults for display
|
|
@@ -1747,10 +1755,12 @@ export function createSetupTab(screen, services) {
|
|
|
1747
1755
|
style: {
|
|
1748
1756
|
bg: 'blue',
|
|
1749
1757
|
fg: 'white',
|
|
1750
|
-
focus: { bg: '
|
|
1751
|
-
hover: { bg: '
|
|
1758
|
+
focus: { bg: '#2e7d32', fg: 'white', bold: true },
|
|
1759
|
+
hover: { bg: '#2e7d32', fg: 'white', bold: true },
|
|
1752
1760
|
},
|
|
1753
1761
|
});
|
|
1762
|
+
btn.on('focus', () => { btn.style.bg = '#2e7d32'; btn.style.fg = 'white'; screen.render(); });
|
|
1763
|
+
btn.on('blur', () => { btn.style.bg = 'blue'; btn.style.fg = 'white'; screen.render(); });
|
|
1754
1764
|
btn.key(['enter', 'space'], () => onClick());
|
|
1755
1765
|
btn.on('click', () => onClick());
|
|
1756
1766
|
return btn;
|
|
@@ -2172,7 +2182,7 @@ export function createSetupTab(screen, services) {
|
|
|
2172
2182
|
style: {
|
|
2173
2183
|
fg: COLORS.labelFg, bg: COLORS.contentBg,
|
|
2174
2184
|
border: { fg: 'blue' },
|
|
2175
|
-
selected: { bg: 'green', fg: '
|
|
2185
|
+
selected: { bg: 'green', fg: 'black', bold: true },
|
|
2176
2186
|
item: { fg: COLORS.labelFg },
|
|
2177
2187
|
},
|
|
2178
2188
|
});
|
|
@@ -2431,8 +2441,6 @@ export function createSetupTab(screen, services) {
|
|
|
2431
2441
|
const inputBox = blessed.textbox({
|
|
2432
2442
|
parent: editModal, top: 3, left: 2, right: 2, height: 3,
|
|
2433
2443
|
border: { type: 'line' },
|
|
2434
|
-
inputOnFocus: true,
|
|
2435
|
-
value: draft.pretext,
|
|
2436
2444
|
style: {
|
|
2437
2445
|
fg: 'white', bg: 'black',
|
|
2438
2446
|
border: { fg: 'blue' },
|
|
@@ -2440,21 +2448,79 @@ export function createSetupTab(screen, services) {
|
|
|
2440
2448
|
},
|
|
2441
2449
|
});
|
|
2442
2450
|
|
|
2451
|
+
let _editClosed = false;
|
|
2452
|
+
let _cursor = (draft.pretext || '').length;
|
|
2453
|
+
inputBox.value = draft.pretext || '';
|
|
2454
|
+
|
|
2455
|
+
function _renderPretext() {
|
|
2456
|
+
const val = inputBox.value;
|
|
2457
|
+
const lpos = inputBox._getCoords();
|
|
2458
|
+
if (!lpos) { screen.render(); return; }
|
|
2459
|
+
const contentWidth = Math.max(1, (lpos.xl - lpos.xi) - inputBox.iwidth);
|
|
2460
|
+
const start = _cursor > contentWidth - 1 ? _cursor - contentWidth + 1 : 0;
|
|
2461
|
+
inputBox.setContent(val.slice(start));
|
|
2462
|
+
screen.render();
|
|
2463
|
+
screen.program.cup(lpos.yi + inputBox.itop, lpos.xi + inputBox.ileft + (_cursor - start));
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
const _prevGrabKeys = screen.grabKeys;
|
|
2443
2467
|
function _closeEdit(save) {
|
|
2468
|
+
if (_editClosed) return;
|
|
2469
|
+
_editClosed = true;
|
|
2470
|
+
inputBox.removeAllListeners('keypress');
|
|
2471
|
+
screen.grabKeys = _prevGrabKeys;
|
|
2472
|
+
screen.program.hideCursor();
|
|
2444
2473
|
if (save) {
|
|
2445
|
-
|
|
2446
|
-
draft.pretext = val;
|
|
2474
|
+
draft.pretext = (inputBox.value || '').trim().slice(0, 200);
|
|
2447
2475
|
}
|
|
2448
2476
|
destroyList(editModal, screen);
|
|
2449
2477
|
onDone();
|
|
2450
2478
|
}
|
|
2451
2479
|
|
|
2452
|
-
|
|
2453
|
-
|
|
2480
|
+
// Guard: if editModal is destroyed externally without _closeEdit being called,
|
|
2481
|
+
// restore grab state so TUI stays responsive.
|
|
2482
|
+
editModal.once('destroy', () => {
|
|
2483
|
+
if (!_editClosed) {
|
|
2484
|
+
_editClosed = true;
|
|
2485
|
+
inputBox.removeAllListeners('keypress');
|
|
2486
|
+
screen.grabKeys = _prevGrabKeys;
|
|
2487
|
+
screen.program.hideCursor();
|
|
2488
|
+
}
|
|
2489
|
+
});
|
|
2454
2490
|
|
|
2491
|
+
screen.grabKeys = true;
|
|
2455
2492
|
inputBox.focus();
|
|
2456
|
-
|
|
2457
|
-
screen.
|
|
2493
|
+
screen.render(); // Layout pass so _getCoords() returns valid coords on first _renderPretext
|
|
2494
|
+
screen.program.showCursor();
|
|
2495
|
+
_renderPretext();
|
|
2496
|
+
|
|
2497
|
+
inputBox.on('keypress', function(ch, key) {
|
|
2498
|
+
if (_editClosed) return;
|
|
2499
|
+
const val = inputBox.value;
|
|
2500
|
+
if (key.name === 'enter') { _closeEdit(true); return; }
|
|
2501
|
+
if (key.name === 'escape') { _closeEdit(false); return; }
|
|
2502
|
+
if (key.name === 'home' || (key.ctrl && key.name === 'a')) {
|
|
2503
|
+
_cursor = 0;
|
|
2504
|
+
} else if (key.name === 'end' || (key.ctrl && key.name === 'e')) {
|
|
2505
|
+
_cursor = val.length;
|
|
2506
|
+
} else if (key.name === 'left') {
|
|
2507
|
+
if (_cursor > 0) _cursor--; else return;
|
|
2508
|
+
} else if (key.name === 'right') {
|
|
2509
|
+
if (_cursor < val.length) _cursor++; else return;
|
|
2510
|
+
} else if (key.name === 'backspace') {
|
|
2511
|
+
if (_cursor > 0) { inputBox.value = val.slice(0, _cursor - 1) + val.slice(_cursor); _cursor--; }
|
|
2512
|
+
else return;
|
|
2513
|
+
} else if (key.name === 'delete') {
|
|
2514
|
+
if (_cursor < val.length) { inputBox.value = val.slice(0, _cursor) + val.slice(_cursor + 1); }
|
|
2515
|
+
else return;
|
|
2516
|
+
} else if (ch && !key.ctrl && !key.meta && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(ch)) {
|
|
2517
|
+
inputBox.value = val.slice(0, _cursor) + ch + val.slice(_cursor);
|
|
2518
|
+
_cursor++;
|
|
2519
|
+
} else {
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
_renderPretext();
|
|
2523
|
+
});
|
|
2458
2524
|
}
|
|
2459
2525
|
|
|
2460
2526
|
// ── Saved toast ───────────────────────────────────────────────────────────
|
|
@@ -1122,7 +1122,7 @@ export function createVoicesTab(screen, services) {
|
|
|
1122
1122
|
padding: { left: 1, right: 1 },
|
|
1123
1123
|
style: {
|
|
1124
1124
|
bg: COLORS.btnDefault,
|
|
1125
|
-
fg: 'white',
|
|
1125
|
+
fg: 'bright-white',
|
|
1126
1126
|
focus: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
1127
1127
|
hover: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
1128
1128
|
},
|
|
@@ -1136,7 +1136,7 @@ export function createVoicesTab(screen, services) {
|
|
|
1136
1136
|
});
|
|
1137
1137
|
btn.on('blur', () => {
|
|
1138
1138
|
btn.style.bg = COLORS.btnDefault;
|
|
1139
|
-
btn.style.fg = 'white';
|
|
1139
|
+
btn.style.fg = 'bright-white';
|
|
1140
1140
|
const raw = btn.content.replace(/[►◄]/g, '').trim();
|
|
1141
1141
|
btn.setContent(raw);
|
|
1142
1142
|
screen.render();
|
|
@@ -1306,7 +1306,7 @@ export function createVoicesTab(screen, services) {
|
|
|
1306
1306
|
padding: { left: 1, right: 1 },
|
|
1307
1307
|
style: {
|
|
1308
1308
|
bg,
|
|
1309
|
-
fg: 'white',
|
|
1309
|
+
fg: 'bright-white',
|
|
1310
1310
|
focus: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
1311
1311
|
hover: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
1312
1312
|
},
|
|
@@ -1341,7 +1341,7 @@ export function createVoicesTab(screen, services) {
|
|
|
1341
1341
|
padding: { left: 1, right: 1 },
|
|
1342
1342
|
style: {
|
|
1343
1343
|
bg: '#e65100',
|
|
1344
|
-
fg: 'white',
|
|
1344
|
+
fg: 'bright-white',
|
|
1345
1345
|
focus: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
1346
1346
|
hover: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
1347
1347
|
},
|
|
@@ -1550,7 +1550,7 @@ export function createVoicesTab(screen, services) {
|
|
|
1550
1550
|
padding: { left: 1, right: 1 },
|
|
1551
1551
|
style: {
|
|
1552
1552
|
bg,
|
|
1553
|
-
fg: 'white',
|
|
1553
|
+
fg: 'bright-white',
|
|
1554
1554
|
focus: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
1555
1555
|
hover: { bg: COLORS.btnFocus, fg: COLORS.btnFocusFg, bold: true },
|
|
1556
1556
|
},
|
package/src/installer.js
CHANGED
|
@@ -74,6 +74,7 @@ import { promptForCustomMusic } from './installer/music-file-input.js';
|
|
|
74
74
|
import { createPreviewListPrompt } from './utils/preview-list-prompt.js';
|
|
75
75
|
import { selectLanguage } from './installer/language-screen.js';
|
|
76
76
|
import { t } from './i18n/strings.js';
|
|
77
|
+
import { seedAllLlmDefaultsSync } from './services/llm-provider-service.js';
|
|
77
78
|
|
|
78
79
|
const __filename = fileURLToPath(import.meta.url);
|
|
79
80
|
const __dirname = path.dirname(__filename);
|
|
@@ -1248,12 +1249,20 @@ async function collectConfiguration(options = {}) {
|
|
|
1248
1249
|
config.provider = process.platform === 'darwin' ? 'macos' : 'piper';
|
|
1249
1250
|
config.defaultVoice = process.platform === 'darwin' ? 'Samantha' : 'en_US-ryan-high';
|
|
1250
1251
|
}
|
|
1251
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
1252
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
1252
1253
|
config.piperPath = path.join(homeDir, '.claude', 'piper-voices');
|
|
1253
1254
|
// AI agent / non-interactive defaults: no reverb, no background music, no hermes
|
|
1254
1255
|
config.reverb = 'none';
|
|
1255
1256
|
config.backgroundMusic = { enabled: false, track: 'agentvibes_soft_flamenco_loop.mp3' };
|
|
1256
1257
|
config.hermes = { enabled: false };
|
|
1258
|
+
// Use folder name as project identity when no existing pretext is set
|
|
1259
|
+
if (!config.pretext) {
|
|
1260
|
+
const folderName = path.basename(process.cwd());
|
|
1261
|
+
// Guard empty basename (e.g. process.cwd() === '/' in some Docker containers)
|
|
1262
|
+
if (folderName) {
|
|
1263
|
+
config.pretext = folderName.charAt(0).toUpperCase() + folderName.slice(1) + ' here';
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1257
1266
|
return config;
|
|
1258
1267
|
}
|
|
1259
1268
|
|
|
@@ -5761,6 +5770,11 @@ Troubleshooting:
|
|
|
5761
5770
|
}
|
|
5762
5771
|
// Do NOT unlink tts-pretext.txt when blank — user may have set it via MCP or manually.
|
|
5763
5772
|
|
|
5773
|
+
// Seed project-level llm: rows with empty pretext so global ~/.claude/config/audio-effects.cfg
|
|
5774
|
+
// "Claude Code here" rows don't override tts-pretext.txt (play-tts.sh stops searching when it
|
|
5775
|
+
// finds a row with a voice, then falls through to tts-pretext.txt for the pretext).
|
|
5776
|
+
seedAllLlmDefaultsSync(targetDir);
|
|
5777
|
+
|
|
5764
5778
|
// Apply reverb setting
|
|
5765
5779
|
const selectedReverb = userConfig.reverb;
|
|
5766
5780
|
if (selectedReverb && selectedReverb !== 'off') {
|
|
@@ -59,7 +59,7 @@ const DEFAULT_LLM_CONFIGS = {
|
|
|
59
59
|
bgTrack: 'agent_vibes_chillwave_v2_loop.mp3',
|
|
60
60
|
bgVolume: '0.15',
|
|
61
61
|
voice: 'en_US-lessac-high',
|
|
62
|
-
pretext: '
|
|
62
|
+
pretext: '',
|
|
63
63
|
ttsEngine: 'piper',
|
|
64
64
|
},
|
|
65
65
|
copilot: {
|
|
@@ -67,7 +67,7 @@ const DEFAULT_LLM_CONFIGS = {
|
|
|
67
67
|
bgTrack: 'agent_vibes_bossa_nova_v2_loop.mp3',
|
|
68
68
|
bgVolume: '0.15',
|
|
69
69
|
voice: 'en_US-libritts-high::Anna-11',
|
|
70
|
-
pretext: '
|
|
70
|
+
pretext: '',
|
|
71
71
|
ttsEngine: 'piper',
|
|
72
72
|
},
|
|
73
73
|
codex: {
|
|
@@ -78,7 +78,7 @@ const DEFAULT_LLM_CONFIGS = {
|
|
|
78
78
|
// Windows Piper installs (loads the model, exits with no output).
|
|
79
79
|
// lessac-high works reliably, so use it as the default for codex.
|
|
80
80
|
voice: 'en_US-lessac-high',
|
|
81
|
-
pretext: '
|
|
81
|
+
pretext: '',
|
|
82
82
|
ttsEngine: 'piper',
|
|
83
83
|
},
|
|
84
84
|
hermes: {
|
|
@@ -86,7 +86,7 @@ const DEFAULT_LLM_CONFIGS = {
|
|
|
86
86
|
bgTrack: 'agent_vibes_bachata_v1_loop.mp3',
|
|
87
87
|
bgVolume: '0.15',
|
|
88
88
|
voice: 'en_US-libritts-high::Leo-8',
|
|
89
|
-
pretext: '
|
|
89
|
+
pretext: '',
|
|
90
90
|
ttsEngine: 'piper',
|
|
91
91
|
},
|
|
92
92
|
};
|