agentvibes 4.6.3 → 4.6.6

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/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # AgentVibes Release Notes
2
2
 
3
+ ## 🧭 v4.6.6 — Natural TUI Navigation
4
+
5
+ **Release Date:** April 2026
6
+
7
+ ### Improvements
8
+
9
+ The Settings TUI now navigates the way you'd expect. Arrow keys flow naturally top-to-bottom through the interface — main header → sub-tab bar → content → footer — with no surprising jumps or dead ends.
10
+
11
+ - **Down from the main header** lands on the active sub-tab bar item, so you're always oriented before pressing further.
12
+ - **Down through content** moves row-by-row within the current tab, reaching Full Preview and the Save buttons at the bottom. It no longer auto-jumps to the next sub-tab.
13
+ - **Up from the first content row** returns you to the current sub-tab (not always Voice).
14
+ - **Left/Right in the sub-tab bar** is the natural way to switch between Voice, Effects, Personality, Output, and Language tabs.
15
+ - **Left/Right in the footer** moves between Full Preview, Save Globally, Save Locally, and Cancel Changes.
16
+ - **Language tab** now has a proper scrollable list — arrow down to enter it, Up/Down to select a language, Enter to apply, Down past the last item to reach the footer.
17
+ - **Readme tab** falls back to the AgentVibes package README when opened in a project folder that has no README of its own.
18
+ - **Escape from the installer** now navigates cleanly to Settings instead of getting stuck.
19
+
20
+ ---
21
+
22
+ ## 🔧 v4.6.5 — Patch Release
23
+
24
+ **Release Date:** April 2026
25
+
26
+ ### Improvements
27
+
28
+ - **TUI non-interactive hint** — Installer header now shows a two-tone hint on row 2: `Skip this TUI?` (white) + `npx agentvibes install --non-interactive` (yellow), with a sponsor link on the right. Useful for CI/CD pipelines and scripted installs.
29
+
30
+ - **`--non-interactive` routing fix** — `npx agentvibes install --non-interactive` (and `--yes`/`-y`) now correctly bypasses the TUI and routes directly to the CLI installer. Previously the TUI would still launch.
31
+
32
+ - **Sponsor link in TUI header** — Row 2 of the header now shows a `♡ Sponsor this Developer github.com/sponsors/paulpreibisch` link on the right side, alongside the non-interactive hint on the left.
33
+
34
+ - **Consistent line endings via `.gitattributes`** — Added `.gitattributes` to enforce `LF` for shell scripts, JS, JSON, and markdown, and `CRLF` for PowerShell scripts. Prevents spurious `bin/` file modifications showing as dirty on Windows due to CRLF noise.
35
+
36
+ - **`bin/` execute bits restored** — Re-indexed `bin/agent-vibes`, `bin/agentvibes-voice-browser.js`, `bin/mcp-server.js`, and `bin/test-bmad-pr` to ensure consistent line endings; verified all four retain `chmod +x` (`100755` mode).
37
+
38
+
39
+ ### User Impact
40
+
41
+ - No functional changes — infrastructure and tooling improvements only
42
+
43
+ ---
44
+
45
+ ## 🐛 v4.6.4 — Patch Release
46
+
47
+ **Release Date:** April 2026
48
+
49
+ ### Bug Fixes
50
+
51
+ - **CI green on macOS** — `mktemp` with a file extension suffix (e.g. `tts-XXXXXX.wav`) works on Linux (GNU mktemp) but silently fails on macOS (BSD mktemp), which requires `XXXXXX` to be at the very end of the template. Fixed all 12 occurrences across `play-tts-piper.sh`, `play-tts-soprano.sh`, `play-tts-macos.sh`, and `audio-processor.sh` by creating the temp file without extension then renaming to add `.wav`/`.aiff`.
52
+
53
+ - **macOS path symlink in test 213** — macOS transparently resolves `/var/folders/...` to `/private/var/folders/...` via a symlink. The voice-manager replay test was comparing against the unresolved path and failing. Fixed by using `cd && pwd` to get the real path before asserting.
54
+
55
+ - **`bmad-party-speak.sh` execute permission** — The bash hook lost its `+x` bit, causing `party mode scripts ship with the package` CI test to fail. Restored `100755` mode via `git update-index`.
56
+
57
+ ### User Impact
58
+
59
+ - No user-facing changes — CI infrastructure fixes only
60
+
61
+ ---
62
+
3
63
  ## 🐛 v4.6.3 — Patch Release
4
64
 
5
65
  **Release Date:** April 2026
package/bin/agentvibes.js CHANGED
@@ -67,6 +67,11 @@ export function resolveStartTab(args, configService) {
67
67
  const cmd = args[0];
68
68
 
69
69
  if (cmd === 'install') {
70
+ // Non-interactive flags → delegate to CLI installer (src/installer.js), not TUI
71
+ const isNonInteractive = args.includes('--non-interactive') || args.includes('--yes') || args.includes('-y');
72
+ if (isNonInteractive) {
73
+ return { cliInstall: true, args: args.slice(1) };
74
+ }
70
75
  return { startTab: 'install' };
71
76
  }
72
77
 
@@ -139,6 +144,16 @@ if (_argv1 === _thisFile) {
139
144
  process.exit(1);
140
145
  }
141
146
 
147
+ if (result.cliInstall) {
148
+ // Route to CLI installer for non-interactive installs
149
+ const installerPath = path.resolve(__dirname, '..', 'src', 'installer.js');
150
+ execFileSync(process.execPath, [installerPath, 'install', ...result.args], {
151
+ stdio: 'inherit',
152
+ shell: false,
153
+ });
154
+ process.exit(0);
155
+ }
156
+
142
157
  launchConsole({ startTab: result.startTab }).catch(err => {
143
158
  process.stderr.write(`Failed to launch AgentVibes console: ${err.message}\n`);
144
159
  process.exit(1);
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": "4.6.3",
4
+ "version": "4.6.6",
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": [
@@ -151,7 +151,7 @@ export class AgentVibesConsole {
151
151
  top: 0,
152
152
  left: 0,
153
153
  width: '100%',
154
- height: 3,
154
+ height: 4,
155
155
  tags: false,
156
156
  wrap: false,
157
157
  scrollable: false,
@@ -192,6 +192,28 @@ export class AgentVibesConsole {
192
192
  style: { bg: COLORS.headerBg },
193
193
  });
194
194
 
195
+ // Row 2: non-interactive mode hint — direct screen child (like tab items) so tags render correctly
196
+ blessed.text({
197
+ parent: this.screen,
198
+ top: 2,
199
+ left: 2,
200
+ shrink: true,
201
+ tags: true,
202
+ content: `{white-fg}Skip this TUI?{/white-fg} {yellow-fg}npx agentvibes install --non-interactive{/yellow-fg}`,
203
+ style: { bg: COLORS.headerBg },
204
+ });
205
+
206
+ // Row 2 (right): sponsor message
207
+ blessed.text({
208
+ parent: this.screen,
209
+ top: 2,
210
+ right: 2,
211
+ shrink: true,
212
+ tags: true,
213
+ content: `{magenta-fg}\u2661{/magenta-fg} {white-fg}Sponsor this Developer{/white-fg} {magenta-fg}github.com/sponsors/paulpreibisch{/magenta-fg}`,
214
+ style: { bg: COLORS.headerBg },
215
+ });
216
+
195
217
  // Row 1 (right): Active settings summary [provider][voice][effects][music]
196
218
  this._headerStatusText = blessed.text({
197
219
  parent: this.headerBox,
@@ -281,17 +303,17 @@ export class AgentVibesConsole {
281
303
  // Background strip — screen child so blessed uses absolute coordinates directly.
282
304
  // Tab items are ALSO screen children (not children of tabBarBox) to avoid the
283
305
  // WSL/Windows Terminal parent-relative positioning bug that renders them 1 row
284
- // too high (at row 2 instead of row 3), producing a ghost duplicate tab bar.
306
+ // too high (at row 3 instead of row 4), producing a ghost duplicate tab bar.
285
307
  this.tabBarBox = blessed.box({
286
308
  parent: this.screen,
287
- top: 3,
309
+ top: 4,
288
310
  left: 0,
289
311
  width: '100%',
290
312
  height: 1,
291
313
  style: { bg: COLORS.tabBarBg },
292
314
  });
293
315
 
294
- // One box per tab — direct screen children at absolute top:3. No tag parsing, no wrapping.
316
+ // One box per tab — direct screen children at absolute top:4. No tag parsing, no wrapping.
295
317
  this._tabItems = {};
296
318
  this._tabItemXOffsets = {}; // track x positions for label refresh
297
319
  let xOffset = 1;
@@ -302,7 +324,7 @@ export class AgentVibesConsole {
302
324
  const text = ` [${shortcutKey}] ${label} `;
303
325
  const el = blessed.box({
304
326
  parent: this.screen,
305
- top: 3,
327
+ top: 4,
306
328
  left: xOffset,
307
329
  width: text.length,
308
330
  height: 1,
@@ -318,14 +340,14 @@ export class AgentVibesConsole {
318
340
  xOffset += text.length + 1; // 1-space gap between tabs
319
341
  }
320
342
 
321
- // Right-aligned Quit item — direct screen child at absolute top:3
343
+ // Right-aligned Quit item — direct screen child at absolute top:4
322
344
  const _quitText = ' [Q] Quit ';
323
345
  const _quitBase = _quitText;
324
346
  const _quitBlock = _quitText.slice(0, -1) + '█';
325
347
  let _quitInterval = null;
326
348
  this._quitItem = blessed.box({
327
349
  parent: this.screen,
328
- top: 3,
350
+ top: 4,
329
351
  right: 1,
330
352
  width: _quitText.length,
331
353
  height: 1,
@@ -532,12 +554,12 @@ export class AgentVibesConsole {
532
554
  }
533
555
 
534
556
  // ---------------------------------------------------------------------------
535
- // Private: Content area (rows 4..N-1) — tab components mount here
557
+ // Private: Content area (rows 5..N-1) — tab components mount here
536
558
 
537
559
  _createContentArea() {
538
560
  // bottom: 2 reserves 2 rows at the bottom: context footer (story 6.3) + GitHub footer
539
561
  this.contentArea = blessed.box({
540
- top: 4,
562
+ top: 5,
541
563
  left: 0,
542
564
  width: '100%',
543
565
  bottom: 2,
@@ -747,18 +769,18 @@ export class AgentVibesConsole {
747
769
  this.screen.render = () => {};
748
770
 
749
771
  try {
750
- // Nuclear clear: wipe the content area (row 4+) to remove stale cell content
751
- // from the previous tab. Start at row 4 — header (0-2) and tab bar (3) are
772
+ // Nuclear clear: wipe the content area (row 5+) to remove stale cell content
773
+ // from the previous tab. Start at row 5 — header (0-3) and tab bar (4) are
752
774
  // static widgets that don't need clearing; wiping them causes the double
753
- // tab bar artifact (row 2 of header shows tab bar ghost from prior render).
775
+ // tab bar artifact (row 3 of header shows tab bar ghost from prior render).
754
776
  // blessed's render loop never resets the `lines` buffer before rendering
755
777
  // (see: blessed/lib/widgets/screen.js line 733, commented-out clear).
756
- this.screen.clearRegion(0, this.screen.cols, 4, this.screen.rows - 2);
778
+ this.screen.clearRegion(0, this.screen.cols, 5, this.screen.rows - 2);
757
779
 
758
780
  // Force-invalidate olines for the entire visible area (rows 0..rows-3).
759
- // Includes header rows 0-1 so the branded header is always redrawn on
781
+ // Includes header rows 0-3 so the branded header is always redrawn on
760
782
  // tab switches — prevents corruption from persisting across tabs.
761
- // Row 2 (header bottom), row 3 (tab bar) and content rows accumulate
783
+ // Row 3 (header bottom), row 4 (tab bar) and content rows accumulate
762
784
  // ghost rendering artifacts — draw() skips them when lines==olines even
763
785
  // though the terminal still shows stale chars from earlier renders.
764
786
  // Setting attr=-1 is impossible for any real cell, so draw() is forced
@@ -772,13 +794,13 @@ export class AgentVibesConsole {
772
794
  orow.dirty = true;
773
795
  }
774
796
 
775
- // Row 2 (header bottom) is never dirty after draw 1 — its content (headerBg+
797
+ // Row 3 (header bottom) is never dirty after draw 1 — its content (headerBg+
776
798
  // spaces) never changes so element.render() never marks it dirty. The olines
777
- // invalidation above sets olines[2][c][0]=-1, but draw() only compares cells
799
+ // invalidation above sets olines[3][c][0]=-1, but draw() only compares cells
778
800
  // when lines[r].dirty is true; a false dirty flag skips the entire row without
779
801
  // ever consulting olines. Force-mark it dirty so draw() emits the explicit
780
- // cup(3,1)+headerBg+spaces sequence and overwrites any ghost terminal content.
781
- if (this.screen.lines?.[2]) this.screen.lines[2].dirty = true;
802
+ // cup(4,1)+headerBg+spaces sequence and overwrites any ghost terminal content.
803
+ if (this.screen.lines?.[3]) this.screen.lines[3].dirty = true;
782
804
 
783
805
  // Update tab bar, footer, and header status inside suppression — no intermediate render.
784
806
  this._updateTabBar(tabId);
@@ -124,7 +124,7 @@ const COLORS = {
124
124
  sectionHdr: '#7b1fa2',
125
125
  labelFg: '#e3f2fd',
126
126
  valueFg: '#ffff00',
127
- activeFg: '#ce93d8',
127
+
128
128
  btnDefault: '#6a1b9a',
129
129
  btnFocus: '#2e7d32', // Green — focused/selected
130
130
  btnFocusFg: '#ffffff',
@@ -227,7 +227,7 @@ ${_tl('bmadDesc')}
227
227
 
228
228
  const box = blessed.box({
229
229
  parent: screen,
230
- top: 4,
230
+ top: 5,
231
231
  left: 0,
232
232
  width: '100%',
233
233
  bottom: 2,
@@ -304,7 +304,7 @@ ${_tl('bmadDesc')}
304
304
  fg: COLORS.labelFg,
305
305
  bg: COLORS.contentBg,
306
306
  border: { fg: COLORS.borderFg },
307
- selected: { bg: '#4a148c', fg: COLORS.activeFg, bold: true },
307
+ selected: { bg: 'blue', fg: 'yellow' },
308
308
  item: { fg: COLORS.labelFg },
309
309
  },
310
310
  });
@@ -681,7 +681,7 @@ ${_tl('bmadDesc')}
681
681
  fg: COLORS.labelFg,
682
682
  bg: COLORS.contentBg,
683
683
  border: { fg: '#4a148c' },
684
- selected: { bg: '#4a148c', fg: COLORS.activeFg, bold: true },
684
+ selected: { bg: 'blue', fg: 'yellow' },
685
685
  item: { fg: COLORS.labelFg },
686
686
  },
687
687
  });
@@ -1529,7 +1529,7 @@ ${_tl('bmadDesc')}
1529
1529
  fg: COLORS.labelFg,
1530
1530
  bg: COLORS.contentBg,
1531
1531
  border: { fg: COLORS.btnFocus },
1532
- selected: { bg: '#4a148c', fg: COLORS.activeFg, bold: true },
1532
+ selected: { bg: 'blue', fg: 'yellow' },
1533
1533
  item: { fg: COLORS.labelFg },
1534
1534
  },
1535
1535
  });
@@ -160,7 +160,7 @@ export function createHelpTab(screen, services) {
160
160
 
161
161
  const box = blessed.box({
162
162
  parent: screen,
163
- top: 4,
163
+ top: 5,
164
164
  left: 0,
165
165
  width: '100%',
166
166
  bottom: 2,
@@ -166,7 +166,7 @@ export function createInstallTab(screen, services) {
166
166
 
167
167
  const box = blessed.box({
168
168
  parent: screen,
169
- top: 4,
169
+ top: 5,
170
170
  left: 0,
171
171
  width: '100%',
172
172
  bottom: 2,
@@ -942,13 +942,10 @@ export function createInstallTab(screen, services) {
942
942
  _screen--;
943
943
  _showCurrentScreen();
944
944
  } else {
945
- box.hide();
946
- screen.render();
947
- // Defer so the escape keypress event finishes propagating before focus changes.
948
- // Calling focusMainTabBar() synchronously here would set focus to the tab bar
949
- // item mid-event, causing its own key(['escape']) handler to fire in the same
950
- // emission and call onFocus() → re-focus a button inside the now-hidden box.
951
- if (typeof focusMainTabBar === 'function') setTimeout(() => focusMainTabBar(), 0);
945
+ // User pressed Escape at the first screen — they want out of the installer.
946
+ // Switch to Settings so they can configure without being stuck on the install tab.
947
+ // (focusMainTabBar would re-focus the install tab item, which loops back here.)
948
+ setTimeout(() => navigationService?.switchTab('settings'), 0);
952
949
  }
953
950
  });
954
951
 
@@ -284,7 +284,7 @@ export function createMusicTab(screen, services) {
284
284
 
285
285
  const box = blessed.box({
286
286
  parent: screen,
287
- top: 4,
287
+ top: 5,
288
288
  left: 0,
289
289
  width: '100%',
290
290
  bottom: 2,
@@ -132,7 +132,7 @@ export function createReadmeTab(screen, services) {
132
132
 
133
133
  const box = blessed.box({
134
134
  parent: screen,
135
- top: 4,
135
+ top: 5,
136
136
  left: 0,
137
137
  width: '100%',
138
138
  bottom: 2,
@@ -146,9 +146,12 @@ export function createReadmeTab(screen, services) {
146
146
  // Load README.md
147
147
 
148
148
  function _loadReadme() {
149
+ // Package root — works whether installed globally (node_modules/.bin) or run from source
150
+ const pkgRoot = path.resolve(new URL(import.meta.url).pathname, '..', '..', '..', '..');
149
151
  const candidates = [
150
152
  path.resolve(process.cwd(), 'README.md'),
151
153
  path.resolve(process.cwd(), 'readme.md'),
154
+ path.resolve(pkgRoot, 'README.md'), // AgentVibes package README fallback
152
155
  ];
153
156
  for (const p of candidates) {
154
157
  if (fs.existsSync(p)) {
@@ -790,7 +790,7 @@ export function createReceiverTab(screen, services) {
790
790
 
791
791
  const box = blessed.box({
792
792
  parent: screen,
793
- top: 4,
793
+ top: 5,
794
794
  left: 0,
795
795
  width: '100%',
796
796
  bottom: 2,