agentvibes 2.12.5 โ†’ 2.12.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.
@@ -59,8 +59,12 @@ ENABLED_FLAG="$CONFIG_DIR/bmad-voices-enabled.flag"
59
59
  # @calledby auto_enable_if_bmad_detected, get_bmad_config_path
60
60
  # @calls None
61
61
  detect_bmad_version() {
62
- if [[ -f "bmad/_cfg/manifest.yaml" ]]; then
63
- # v6 detected
62
+ if [[ -f ".bmad/_cfg/manifest.yaml" ]]; then
63
+ # v6 detected (standard path with dot prefix)
64
+ echo "6"
65
+ return 0
66
+ elif [[ -f "bmad/_cfg/manifest.yaml" ]]; then
67
+ # v6 detected (alternative path without dot prefix)
64
68
  echo "6"
65
69
  return 0
66
70
  elif [[ -f ".bmad-core/install-manifest.yaml" ]]; then
@@ -88,7 +92,12 @@ get_bmad_config_path() {
88
92
  local version=$(detect_bmad_version)
89
93
 
90
94
  if [[ "$version" == "6" ]]; then
91
- echo "bmad/core/config.yaml"
95
+ # Check both possible v6 paths
96
+ if [[ -f ".bmad/core/config.yaml" ]]; then
97
+ echo ".bmad/core/config.yaml"
98
+ else
99
+ echo "bmad/core/config.yaml"
100
+ fi
92
101
  return 0
93
102
  elif [[ "$version" == "4" ]]; then
94
103
  echo ".bmad-core/config.yaml"
@@ -138,8 +147,15 @@ get_agent_voice() {
138
147
 
139
148
  # Check for BMAD v6 CSV file first (preferred, loose coupling)
140
149
  # If this exists, use it directly without requiring plugin enable flag
141
- local bmad_voice_map=".bmad/_cfg/agent-voice-map.csv"
142
- if [[ -f "$bmad_voice_map" ]]; then
150
+ # Support both .bmad (standard) and bmad (alternative) paths
151
+ local bmad_voice_map=""
152
+ if [[ -f ".bmad/_cfg/agent-voice-map.csv" ]]; then
153
+ bmad_voice_map=".bmad/_cfg/agent-voice-map.csv"
154
+ elif [[ -f "bmad/_cfg/agent-voice-map.csv" ]]; then
155
+ bmad_voice_map="bmad/_cfg/agent-voice-map.csv"
156
+ fi
157
+
158
+ if [[ -n "$bmad_voice_map" ]]; then
143
159
  # Read from BMAD's standard _cfg directory
144
160
  # CSV format: agent_id,voice_name
145
161
  local voice=$(grep "^$agent_id," "$bmad_voice_map" | cut -d',' -f2)
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![Publish](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml/badge.svg)](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml)
12
12
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
13
13
 
14
- **Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v2.12.5
14
+ **Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v2.12.6
15
15
 
16
16
  ---
17
17
 
@@ -94,17 +94,17 @@ Whether you're coding in Claude Code, chatting in Claude Desktop, or using Warp
94
94
 
95
95
  ## ๐Ÿ“ฐ Latest Release
96
96
 
97
- **[v2.12.5 - Code Quality Improvements](https://github.com/paulpreibisch/AgentVibes/releases/tag/v2.12.5)** ๐ŸŽ‰
97
+ **[v2.12.6 - Security & Reliability Improvements](https://github.com/paulpreibisch/AgentVibes/releases/tag/v2.12.6)** ๐ŸŽ‰
98
98
 
99
- AgentVibes v2.12.5 improves code quality by upgrading Sonar quality gates and implementing best practices identified through static analysis. This release includes enhanced input validation, improved shell command handling, better file locking for atomic operations, and secure temporary directory management while maintaining 100% backward compatibility.
99
+ AgentVibes v2.12.6 brings quality improvements based on SonarCloud analysis and enhances BMAD party mode. This release improves API key privacy in terminal output, adds better cleanup for long-running sessions, includes more robust error handling, and ensures BMAD agents each get their unique voice. All improvements maintain 100% backward compatibility.
100
100
 
101
101
  **Key Highlights:**
102
- - โœ… **Sonar Quality Gates Upgraded** - Enhanced code quality standards across the codebase
103
- - ๐Ÿ”’ **18 Code Improvements** - Better input validation, command handling, and file operations
104
- - ๐Ÿงช **110/110 Tests Passing** - All functionality verified and working
105
- - ๐Ÿ”„ **Zero Breaking Changes** - Fully backward compatible with existing installations
106
- - ๐Ÿ“Š **162 Lines Enhanced** - Code quality improvements across 8 files
107
- - ๐Ÿ’ก **Best Practices** - Improved error handling and validation
102
+ - ๐Ÿ”’ **API Key Privacy** - Masked display prevents credential leaks in terminal history
103
+ - ๐Ÿ›ก๏ธ **Resource Cleanup** - Better subprocess management for long-running stability
104
+ - โšก **Error Handling** - Graceful degradation instead of crashes on file operations
105
+ - ๐ŸŽญ **BMAD Voice Detection** - Party mode supports multiple directory paths
106
+ - โœ… **110/110 Tests Passing** - All functionality verified and working
107
+ - ๐Ÿ”„ **Zero Breaking Changes** - Fully backward compatible with v2.12.5
108
108
 
109
109
  ๐Ÿ’ก **Tip:** If `npx agentvibes` shows an older version or missing commands, clear your npm cache: `npm cache clean --force && npx agentvibes@latest --help`
110
110
 
package/RELEASE_NOTES.md CHANGED
@@ -1,3 +1,225 @@
1
+ # Release v2.12.6 - Security & Reliability Improvements
2
+
3
+ **Release Date:** 2025-01-24
4
+ **Type:** Patch Release (Security & Reliability)
5
+
6
+ ## ๐Ÿ”’ AI Summary
7
+
8
+ AgentVibes v2.12.6 brings quality improvements based on SonarCloud analysis and enhances BMAD party mode. This release improves API key privacy in terminal output, adds better cleanup for long-running sessions, includes more robust error handling, and ensures BMAD agents each get their unique voice. All improvements maintain 100% backward compatibility with 110/110 tests passing.
9
+
10
+ **Key Highlights:**
11
+ - ๐Ÿ”’ **API Key Security** - Masked API key display prevents credential leaks in terminal history
12
+ - ๐Ÿ›ก๏ธ **Resource Leak Prevention** - Subprocess cleanup prevents "too many open files" errors
13
+ - โšก **Enhanced Error Handling** - Graceful degradation instead of crashes on file operations
14
+ - ๐ŸŽญ **BMAD Voice Detection** - Fixed party mode to support both .bmad and bmad directory paths
15
+ - โœ… **110/110 Tests Passing** - All functionality verified and working
16
+
17
+ ---
18
+
19
+ ## ๐Ÿ”ง Security Improvements
20
+
21
+ ### API Key Masking (Issue #45)
22
+ **Files:** `src/installer.js` (lines 384, 445, 464, 489)
23
+
24
+ **Changes:**
25
+ - Replaced partial API key display (`first10chars...`) with masked format (`***************...`)
26
+ - Removed full API key from error messages and manual setup instructions
27
+ - Changed all console outputs to use placeholder `<your-api-key>`
28
+
29
+ **Impact:**
30
+ - Prevents credential leaks in terminal history (`.bash_history`, `.zsh_history`)
31
+ - Safer during screen recordings and screenshots
32
+ - Reduces risk during pair programming sessions
33
+
34
+ ### Path Validation Enhancement (Issue #45)
35
+ **File:** `src/installer.js:214-219`
36
+
37
+ **Changes:**
38
+ - Added `path.resolve()` validation for script execution
39
+ - Ensures scripts are within allowed `.claude/hooks` directory
40
+ - Defense-in-depth security measure
41
+
42
+ **Impact:**
43
+ - Prevents path traversal attacks
44
+ - Better error messages for invalid paths
45
+
46
+ ---
47
+
48
+ ## ๐Ÿ›ก๏ธ Reliability Improvements
49
+
50
+ ### Resource Leak Prevention (Issue #45)
51
+ **Files:** `mcp-server/server.py:144-174, 510-537`
52
+
53
+ **Changes:**
54
+ - Added try-finally blocks around subprocess operations
55
+ - Ensures processes are properly killed if still running after errors
56
+ - Cleanup happens even on exceptions
57
+
58
+ **Impact:**
59
+ - Prevents "too many open files" errors in long-running MCP server sessions
60
+ - Better resource management for continuous operation
61
+ - More stable in production environments
62
+
63
+ ### Enhanced Error Handling - Python (Issue #45)
64
+ **Files:** `mcp-server/server.py:544-558, 565-584`
65
+
66
+ **Changes:**
67
+ - Added error handling to `_get_personality()` function
68
+ - Added error handling to `_get_provider()` function
69
+ - Catches `PermissionError`, `UnicodeDecodeError`, `OSError`
70
+ - Returns sensible defaults instead of crashing
71
+
72
+ **Impact:**
73
+ - Graceful degradation when config files are corrupted or inaccessible
74
+ - Continues operation with defaults rather than failing
75
+ - Better user experience in edge cases
76
+
77
+ ### Enhanced Error Handling - JavaScript (Issue #45)
78
+ **Files:** `src/installer.js:500-536, 544-604`
79
+
80
+ **Changes:**
81
+ - Added comprehensive error handling to `copyCommandFiles()`
82
+ - Added comprehensive error handling to `copyHookFiles()`
83
+ - Tracks success/failure counts per file
84
+ - Continues with remaining files on partial failures
85
+ - Shows clear feedback about what succeeded vs failed
86
+
87
+ **Impact:**
88
+ - Installation continues even if individual files fail to copy
89
+ - Users see exactly which operations succeeded
90
+ - Partial installations are more visible and recoverable
91
+
92
+ ### Shell Config Deduplication (Issue #45)
93
+ **File:** `src/installer.js:482-498`
94
+
95
+ **Changes:**
96
+ - Checks if `ELEVENLABS_API_KEY` already exists before appending
97
+ - Shows friendly message when key already present
98
+ - Prevents duplicate entries on repeated installations
99
+
100
+ **Impact:**
101
+ - Cleaner shell configuration files
102
+ - No accumulation of duplicate exports
103
+ - Better UX for repeated installs
104
+
105
+ ---
106
+
107
+ ## ๐ŸŽญ BMAD Integration Improvements
108
+
109
+ ### Multi-Path Voice Detection (Issue #46)
110
+ **File:** `.claude/hooks/bmad-voice-manager.sh`
111
+
112
+ **Changes:**
113
+ - Enhanced `detect_bmad_version()` to check both `.bmad/` and `bmad/` paths
114
+ - Updated `get_bmad_config_path()` to support both v6 directory variants
115
+ - Fixed `get_agent_voice()` to find `agent-voice-map.csv` in either location
116
+
117
+ **Impact:**
118
+ - BMAD party mode now works regardless of installation path
119
+ - Each agent speaks with their unique assigned voice
120
+ - Resolves all agents using default "lessac" voice
121
+
122
+ **Supported Paths:**
123
+ ```
124
+ โœ“ .bmad/_cfg/agent-voice-map.csv (standard)
125
+ โœ“ bmad/_cfg/agent-voice-map.csv (alternative)
126
+ โœ“ .bmad/core/config.yaml (standard)
127
+ โœ“ bmad/core/config.yaml (alternative)
128
+ ```
129
+
130
+ ---
131
+
132
+ ## ๐Ÿ“Š Changes Summary
133
+
134
+ **Issues Resolved:**
135
+ - #45 - SonarCloud Quality Gate: 17 Security Hotspots & C Reliability
136
+ - #46 - BMAD Plugin: Missing agent-voice-map.csv path detection
137
+
138
+ **Files Modified:** 3
139
+ - `src/installer.js` - API key masking, error handling, shell config deduplication, path validation
140
+ - `mcp-server/server.py` - Resource cleanup, error handling
141
+ - `.claude/hooks/bmad-voice-manager.sh` - Multi-path BMAD detection
142
+
143
+ **Lines Changed:** 180 additions, 85 deletions
144
+
145
+ **Test Results:** 110/110 passing โœ…
146
+
147
+ ---
148
+
149
+ ## ๐Ÿ’ก Technical Details
150
+
151
+ ### SonarCloud Quality Gate Fixes
152
+
153
+ **Must-Fix Items (High Priority):**
154
+ 1. โœ… API Key Logging - Prevents accidental credential exposure
155
+ 2. โœ… Resource Leaks - Prevents crashes in long-running sessions
156
+ 3. โœ… Missing Error Handling - Prevents crashes on file system errors
157
+
158
+ **Nice-to-Fix Items (Medium Priority):**
159
+ 4. โœ… Shell Config Deduplication - Better UX on repeated installations
160
+ 5. โœ… Path Validation Enhancement - Defense-in-depth security
161
+
162
+ ### Error Handling Strategy
163
+
164
+ **Python (`server.py`):**
165
+ ```python
166
+ try:
167
+ if personality_file.exists():
168
+ return personality_file.read_text().strip()
169
+ except (PermissionError, UnicodeDecodeError, OSError) as e:
170
+ print(f"Warning: Could not read file: {e}", file=sys.stderr)
171
+ return "normal" # Sensible default
172
+ ```
173
+
174
+ **JavaScript (`installer.js`):**
175
+ ```javascript
176
+ try {
177
+ await fs.copyFile(srcPath, destPath);
178
+ successCount++;
179
+ } catch (err) {
180
+ console.log(chalk.yellow(`โš  Failed to copy ${file}: ${err.message}`));
181
+ // Continue with other files
182
+ }
183
+ ```
184
+
185
+ ---
186
+
187
+ ## โœ… Testing
188
+
189
+ ### Test Suite Results
190
+ - **110 tests total**
191
+ - **110 passing** โœ…
192
+ - **0 failing**
193
+ - All functionality verified working
194
+
195
+ ### Syntax Validation
196
+ - โœ… Node.js syntax check passed
197
+ - โœ… Python syntax check passed
198
+ - โœ… No runtime errors detected
199
+
200
+ ---
201
+
202
+ ## ๐ŸŽฏ Migration Notes
203
+
204
+ **No migration required** - This is a patch release with security and reliability improvements.
205
+
206
+ **Compatibility:** 100% backward compatible with v2.12.5
207
+
208
+ **Recommended Action:** Update to get the latest security and stability improvements
209
+ ```bash
210
+ npx agentvibes@latest update
211
+ ```
212
+
213
+ ---
214
+
215
+ ## ๐Ÿ”— References
216
+
217
+ - GitHub Issue #45: https://github.com/paulpreibisch/AgentVibes/issues/45
218
+ - GitHub Issue #46: https://github.com/paulpreibisch/AgentVibes/issues/46
219
+ - SonarCloud Quality Gates: Code quality and security analysis
220
+
221
+ ---
222
+
1
223
  # Release v2.12.5 - Code Quality Improvements
2
224
 
3
225
  **Release Date:** 2025-01-23
@@ -147,25 +147,31 @@ class AgentVibesServer:
147
147
  stderr=asyncio.subprocess.PIPE,
148
148
  env=env,
149
149
  )
150
- stdout, stderr = await result.communicate()
151
-
152
- if result.returncode == 0:
153
- output = stdout.decode().strip()
154
- # Extract file path from output
155
- for line in output.split("\n"):
156
- if "Saved to:" in line:
157
- file_path = line.split("Saved to:")[1].strip()
158
- truncated = (
159
- f"{text[:50]}..." if len(text) > 50 else text
160
- )
161
- return f"โœ… Spoke: {truncated}\n๐Ÿ“ Audio saved: {file_path}"
162
-
163
- return f"โœ… Spoke: {text[:50]}..." if len(text) > 50 else f"โœ… Spoke: {text}"
164
- else:
165
- error = stderr.decode().strip()
166
- stdout_output = stdout.decode().strip()
167
- full_error = f"{error}\nStdout: {stdout_output}" if stdout_output else error
168
- return f"โŒ TTS failed: {full_error}"
150
+ try:
151
+ stdout, stderr = await result.communicate()
152
+
153
+ if result.returncode == 0:
154
+ output = stdout.decode().strip()
155
+ # Extract file path from output
156
+ for line in output.split("\n"):
157
+ if "Saved to:" in line:
158
+ file_path = line.split("Saved to:")[1].strip()
159
+ truncated = (
160
+ f"{text[:50]}..." if len(text) > 50 else text
161
+ )
162
+ return f"โœ… Spoke: {truncated}\n๐Ÿ“ Audio saved: {file_path}"
163
+
164
+ return f"โœ… Spoke: {text[:50]}..." if len(text) > 50 else f"โœ… Spoke: {text}"
165
+ else:
166
+ error = stderr.decode().strip()
167
+ stdout_output = stdout.decode().strip()
168
+ full_error = f"{error}\nStdout: {stdout_output}" if stdout_output else error
169
+ return f"โŒ TTS failed: {full_error}"
170
+ finally:
171
+ # Ensure process cleanup
172
+ if result.returncode is None:
173
+ result.kill()
174
+ await result.wait()
169
175
 
170
176
  finally:
171
177
  # Restore original settings
@@ -513,14 +519,20 @@ class AgentVibesServer:
513
519
  stderr=asyncio.subprocess.PIPE,
514
520
  env=env,
515
521
  )
516
- stdout, stderr = await result.communicate()
517
- if result.returncode == 0:
518
- return stdout.decode().strip()
519
- else:
520
- error_msg = stderr.decode().strip()
521
- if not error_msg: # If stderr is empty, include stdout for debugging
522
- error_msg = f"Return code {result.returncode}. Stdout: {stdout.decode().strip()}"
523
- return error_msg
522
+ try:
523
+ stdout, stderr = await result.communicate()
524
+ if result.returncode == 0:
525
+ return stdout.decode().strip()
526
+ else:
527
+ error_msg = stderr.decode().strip()
528
+ if not error_msg: # If stderr is empty, include stdout for debugging
529
+ error_msg = f"Return code {result.returncode}. Stdout: {stdout.decode().strip()}"
530
+ return error_msg
531
+ finally:
532
+ # Ensure process cleanup
533
+ if result.returncode is None:
534
+ result.kill()
535
+ await result.wait()
524
536
  except Exception as e:
525
537
  return f"Error running script: {e}"
526
538
 
@@ -536,8 +548,13 @@ class AgentVibesServer:
536
548
  # Try global
537
549
  personality_file = Path.home() / ".claude" / "tts-personality.txt"
538
550
 
539
- if personality_file.exists():
540
- return personality_file.read_text().strip()
551
+ try:
552
+ if personality_file.exists():
553
+ return personality_file.read_text().strip()
554
+ except (PermissionError, UnicodeDecodeError, OSError) as e:
555
+ # Log error but don't crash - return default
556
+ import sys
557
+ print(f"Warning: Could not read personality file: {e}", file=sys.stderr)
541
558
  return "normal"
542
559
 
543
560
  async def _get_language(self) -> str:
@@ -551,13 +568,18 @@ class AgentVibesServer:
551
568
  if not provider_file.exists():
552
569
  provider_file = Path.home() / ".claude" / "tts-provider.txt"
553
570
 
554
- if provider_file.exists():
555
- provider = provider_file.read_text().strip()
556
- if provider == "elevenlabs":
557
- return "ElevenLabs (Premium AI)"
558
- elif provider == "piper":
559
- return "Piper TTS (Free, Offline)"
560
- return provider
571
+ try:
572
+ if provider_file.exists():
573
+ provider = provider_file.read_text().strip()
574
+ if provider == "elevenlabs":
575
+ return "ElevenLabs (Premium AI)"
576
+ elif provider == "piper":
577
+ return "Piper TTS (Free, Offline)"
578
+ return provider
579
+ except (PermissionError, UnicodeDecodeError, OSError) as e:
580
+ # Log error but don't crash - return default
581
+ import sys
582
+ print(f"Warning: Could not read provider file: {e}", file=sys.stderr)
561
583
  # Default to Piper (free, offline) instead of ElevenLabs
562
584
  return "Piper TTS (Free, Offline)"
563
585
 
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": "2.12.5",
4
+ "version": "2.12.6",
5
5
  "description": "Now your AI Agents can finally talk back! Professional TTS voice for Claude Code and Claude Desktop (via MCP) with multi-provider support.",
6
6
  "homepage": "https://agentvibes.org",
7
7
  "keywords": [
package/src/installer.js CHANGED
@@ -128,27 +128,27 @@ function showReleaseInfo() {
128
128
  console.log(
129
129
  boxen(
130
130
  chalk.white.bold('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n') +
131
- chalk.cyan.bold(' ๐Ÿ“ฆ AgentVibes v2.12.5 - Code Quality Improvements\n') +
131
+ chalk.cyan.bold(' ๐Ÿ“ฆ AgentVibes v2.12.6 - Security & Reliability Improvements\n') +
132
132
  chalk.white.bold('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n\n') +
133
133
  chalk.green.bold('๐ŸŽ™๏ธ WHAT\'S NEW:\n\n') +
134
- chalk.cyan('AgentVibes v2.12.5 improves code quality by upgrading Sonar quality gates\n') +
135
- chalk.cyan('and implementing best practices identified through static analysis. This\n') +
136
- chalk.cyan('release includes enhanced input validation, improved shell command handling,\n') +
137
- chalk.cyan('better file locking for atomic operations, and secure temporary directory\n') +
138
- chalk.cyan('management while maintaining 100% backward compatibility.\n\n') +
134
+ chalk.cyan('AgentVibes v2.12.6 brings quality improvements based on SonarCloud analysis\n') +
135
+ chalk.cyan('and enhances BMAD party mode. This release improves API key privacy in\n') +
136
+ chalk.cyan('terminal output, adds better cleanup for long-running sessions, includes\n') +
137
+ chalk.cyan('more robust error handling, and ensures BMAD agents each get their unique\n') +
138
+ chalk.cyan('voice. All improvements maintain 100% backward compatibility.\n\n') +
139
139
  chalk.green.bold('โœจ KEY HIGHLIGHTS:\n\n') +
140
- chalk.gray(' โœ… Sonar Quality Gates Upgraded - Enhanced code quality standards\n') +
141
- chalk.gray(' ๐Ÿ”’ 18 Code Improvements - Better input validation and command handling\n') +
142
- chalk.gray(' ๐Ÿงช 110/110 Tests Passing - All functionality verified and working\n') +
143
- chalk.gray(' ๐Ÿ”„ Zero Breaking Changes - Fully backward compatible with v2.12.4\n') +
144
- chalk.gray(' ๐Ÿ“Š 162 Lines Enhanced - Code quality improvements across 8 files\n') +
145
- chalk.gray(' ๐Ÿ’ก Best Practices - Improved error handling and validation\n\n') +
140
+ chalk.gray(' ๐Ÿ”’ API Key Privacy - Masked display prevents credential leaks in logs\n') +
141
+ chalk.gray(' ๐Ÿ›ก๏ธ Resource Cleanup - Better subprocess management for stability\n') +
142
+ chalk.gray(' โšก Error Handling - Graceful degradation instead of crashes\n') +
143
+ chalk.gray(' ๐ŸŽญ BMAD Voice Detection - Party mode supports multiple directory paths\n') +
144
+ chalk.gray(' โœ… 110/110 Tests Passing - All functionality verified and working\n') +
145
+ chalk.gray(' ๐Ÿ”„ Zero Breaking Changes - Fully backward compatible with v2.12.5\n\n') +
146
146
  chalk.cyan('Technical Improvements:\n') +
147
- chalk.gray(' โ€ข Enhanced input validation across all interfaces\n') +
148
- chalk.gray(' โ€ข Improved shell command handling with proper escaping\n') +
149
- chalk.gray(' โ€ข Better file system operations with path validation\n') +
150
- chalk.gray(' โ€ข Atomic PID file operations with file locking\n') +
151
- chalk.gray(' โ€ข Cleaner code with debug output removed\n\n') +
147
+ chalk.gray(' โ€ข API keys now masked in terminal output and error messages\n') +
148
+ chalk.gray(' โ€ข Process cleanup prevents "too many open files" errors\n') +
149
+ chalk.gray(' โ€ข Enhanced error handling for file operations\n') +
150
+ chalk.gray(' โ€ข BMAD agents now use unique voices in party mode\n') +
151
+ chalk.gray(' โ€ข Shell config deduplication on repeated installs\n\n') +
152
152
  chalk.white.bold('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n\n') +
153
153
  chalk.gray('๐Ÿ“– Full Release Notes: RELEASE_NOTES.md\n') +
154
154
  chalk.gray('๐ŸŒ Website: https://agentvibes.org\n') +
@@ -211,6 +211,13 @@ function execScript(scriptPath, options = {}) {
211
211
  throw new Error('Invalid characters in script path');
212
212
  }
213
213
 
214
+ // Validate path is within expected directory (defense in depth)
215
+ const resolvedPath = path.resolve(scriptFile);
216
+ const allowedDir = path.resolve(__dirname, '..', '.claude', 'hooks');
217
+ if (!resolvedPath.startsWith(allowedDir)) {
218
+ throw new Error('Script path outside allowed directory');
219
+ }
220
+
214
221
  // Escape each argument properly
215
222
  const escapedArgs = args.map(arg => {
216
223
  // Replace single quotes with '\'' (end quote, escaped quote, start quote)
@@ -381,7 +388,7 @@ async function handleElevenLabsApiKey(options) {
381
388
 
382
389
  if (elevenLabsKey) {
383
390
  console.log(chalk.green(`\nโœ“ ElevenLabs API key detected from environment`));
384
- console.log(chalk.gray(` Key: ${elevenLabsKey.substring(0, 10)}...`));
391
+ console.log(chalk.gray(` Key: ***************...`));
385
392
 
386
393
  if (!options.yes) {
387
394
  const { useExisting } = await inquirer.prompt([
@@ -442,7 +449,7 @@ async function handleElevenLabsApiKey(options) {
442
449
 
443
450
  if (setupMethod === 'manual') {
444
451
  console.log(chalk.yellow('\nโš ๏ธ Remember to add this to your environment variables later:'));
445
- console.log(chalk.gray(` export ELEVENLABS_API_KEY="${apiKey}"\n`));
452
+ console.log(chalk.gray(` export ELEVENLABS_API_KEY="<your-api-key>"\n`));
446
453
  } else if (setupMethod === 'shell') {
447
454
  await addApiKeyToShellConfig(apiKey);
448
455
  }
@@ -461,7 +468,7 @@ async function addApiKeyToShellConfig(apiKey) {
461
468
  if (!shellName || !shellConfig) {
462
469
  console.log(chalk.yellow('\nโš ๏ธ Could not detect shell type'));
463
470
  console.log(chalk.gray(' Please add manually to your shell config:'));
464
- console.log(chalk.gray(` export ELEVENLABS_API_KEY="${apiKey}"\n`));
471
+ console.log(chalk.gray(` export ELEVENLABS_API_KEY="<your-api-key>"\n`));
465
472
  return;
466
473
  }
467
474
 
@@ -479,14 +486,27 @@ async function addApiKeyToShellConfig(apiKey) {
479
486
 
480
487
  if (confirmShell) {
481
488
  try {
482
- const configContent = `\n# ElevenLabs API Key for AgentVibes\nexport ELEVENLABS_API_KEY="${apiKey}"\n`;
483
- await fs.appendFile(shellConfig, configContent);
484
- console.log(chalk.green(`\nโœ“ API key added to ${shellConfig}`));
485
- console.log(chalk.yellow(' Run this to use immediately: ') + chalk.cyan(`source ${shellConfig}`));
489
+ // Check if already exists to avoid duplicates
490
+ let existingContent = '';
491
+ try {
492
+ existingContent = await fs.readFile(shellConfig, 'utf8');
493
+ } catch (err) {
494
+ // File might not exist yet, which is fine
495
+ }
496
+
497
+ if (existingContent.includes('ELEVENLABS_API_KEY')) {
498
+ console.log(chalk.cyan(`\nโ†’ API key already exists in ${shellConfig}`));
499
+ console.log(chalk.gray(' No changes needed'));
500
+ } else {
501
+ const configContent = `\n# ElevenLabs API Key for AgentVibes\nexport ELEVENLABS_API_KEY="${apiKey}"\n`;
502
+ await fs.appendFile(shellConfig, configContent);
503
+ console.log(chalk.green(`\nโœ“ API key added to ${shellConfig}`));
504
+ console.log(chalk.yellow(' Run this to use immediately: ') + chalk.cyan(`source ${shellConfig}`));
505
+ }
486
506
  } catch (error) {
487
507
  console.log(chalk.red(`\nโœ— Failed to write to ${shellConfig}`));
488
508
  console.log(chalk.gray(' Please add manually:'));
489
- console.log(chalk.gray(` export ELEVENLABS_API_KEY="${apiKey}"\n`));
509
+ console.log(chalk.gray(` export ELEVENLABS_API_KEY="<your-api-key>"\n`));
490
510
  }
491
511
  }
492
512
  }
@@ -503,20 +523,36 @@ async function copyCommandFiles(targetDir, spinner) {
503
523
  const commandsDir = path.join(targetDir, '.claude', 'commands');
504
524
  const agentVibesCommandsDir = path.join(commandsDir, 'agent-vibes');
505
525
 
506
- await fs.mkdir(agentVibesCommandsDir, { recursive: true });
526
+ try {
527
+ await fs.mkdir(agentVibesCommandsDir, { recursive: true });
507
528
 
508
- const commandFiles = await fs.readdir(srcCommandsDir);
509
- console.log(chalk.cyan(`\n๐Ÿ“‹ Installing ${commandFiles.length} command files:`));
529
+ const commandFiles = await fs.readdir(srcCommandsDir);
530
+ console.log(chalk.cyan(`\n๐Ÿ“‹ Installing ${commandFiles.length} command files:`));
510
531
 
511
- for (const file of commandFiles) {
512
- const srcPath = path.join(srcCommandsDir, file);
513
- const destPath = path.join(agentVibesCommandsDir, file);
514
- await fs.copyFile(srcPath, destPath);
515
- console.log(chalk.gray(` โœ“ agent-vibes/${file}`));
516
- }
532
+ let successCount = 0;
533
+ for (const file of commandFiles) {
534
+ const srcPath = path.join(srcCommandsDir, file);
535
+ const destPath = path.join(agentVibesCommandsDir, file);
536
+ try {
537
+ await fs.copyFile(srcPath, destPath);
538
+ console.log(chalk.gray(` โœ“ agent-vibes/${file}`));
539
+ successCount++;
540
+ } catch (err) {
541
+ console.log(chalk.yellow(` โš  Failed to copy ${file}: ${err.message}`));
542
+ // Continue with other files
543
+ }
544
+ }
517
545
 
518
- spinner.succeed(chalk.green('Installed /agent-vibes commands!\n'));
519
- return commandFiles.length;
546
+ if (successCount === commandFiles.length) {
547
+ spinner.succeed(chalk.green('Installed /agent-vibes commands!\n'));
548
+ } else {
549
+ spinner.warn(chalk.yellow(`Installed ${successCount}/${commandFiles.length} commands (some failed)\n`));
550
+ }
551
+ return successCount;
552
+ } catch (err) {
553
+ spinner.fail(chalk.red(`Failed to install commands: ${err.message}`));
554
+ throw err;
555
+ }
520
556
  }
521
557
 
522
558
  /**
@@ -530,40 +566,61 @@ async function copyHookFiles(targetDir, spinner) {
530
566
  const srcHooksDir = path.join(__dirname, '..', '.claude', 'hooks');
531
567
  const hooksDir = path.join(targetDir, '.claude', 'hooks');
532
568
 
533
- await fs.mkdir(hooksDir, { recursive: true });
569
+ try {
570
+ await fs.mkdir(hooksDir, { recursive: true });
534
571
 
535
- const allHookFiles = await fs.readdir(srcHooksDir);
536
- const hookFiles = [];
572
+ const allHookFiles = await fs.readdir(srcHooksDir);
573
+ const hookFiles = [];
537
574
 
538
- for (const file of allHookFiles) {
539
- const srcPath = path.join(srcHooksDir, file);
540
- const stat = await fs.stat(srcPath);
575
+ for (const file of allHookFiles) {
576
+ const srcPath = path.join(srcHooksDir, file);
577
+ try {
578
+ const stat = await fs.stat(srcPath);
541
579
 
542
- if (stat.isFile() &&
543
- (file.endsWith('.sh') || file === 'hooks.json') &&
544
- !file.includes('prepare-release') &&
545
- !file.startsWith('.')) {
546
- hookFiles.push(file);
580
+ if (stat.isFile() &&
581
+ (file.endsWith('.sh') || file === 'hooks.json') &&
582
+ !file.includes('prepare-release') &&
583
+ !file.startsWith('.')) {
584
+ hookFiles.push(file);
585
+ }
586
+ } catch (err) {
587
+ console.log(chalk.yellow(` โš  Could not check ${file}: ${err.message}`));
588
+ // Continue with other files
589
+ }
547
590
  }
548
- }
549
591
 
550
- console.log(chalk.cyan(`๐Ÿ”ง Installing ${hookFiles.length} TTS scripts:`));
551
- for (const file of hookFiles) {
552
- const srcPath = path.join(srcHooksDir, file);
553
- const destPath = path.join(hooksDir, file);
554
- await fs.copyFile(srcPath, destPath);
592
+ console.log(chalk.cyan(`๐Ÿ”ง Installing ${hookFiles.length} TTS scripts:`));
593
+ let successCount = 0;
594
+ for (const file of hookFiles) {
595
+ const srcPath = path.join(srcHooksDir, file);
596
+ const destPath = path.join(hooksDir, file);
597
+ try {
598
+ await fs.copyFile(srcPath, destPath);
555
599
 
556
- if (file.endsWith('.sh')) {
557
- // Security: Use more restrictive permissions (owner: rwx, group: r-x, others: ---)
558
- await fs.chmod(destPath, 0o750);
559
- console.log(chalk.gray(` โœ“ ${file} (executable)`));
600
+ if (file.endsWith('.sh')) {
601
+ // Security: Use more restrictive permissions (owner: rwx, group: r-x, others: ---)
602
+ await fs.chmod(destPath, 0o750);
603
+ console.log(chalk.gray(` โœ“ ${file} (executable)`));
604
+ } else {
605
+ console.log(chalk.gray(` โœ“ ${file}`));
606
+ }
607
+ successCount++;
608
+ } catch (err) {
609
+ console.log(chalk.yellow(` โš  Failed to copy ${file}: ${err.message}`));
610
+ // Continue with other files
611
+ }
612
+ }
613
+
614
+ if (successCount === hookFiles.length) {
615
+ spinner.succeed(chalk.green('Installed TTS scripts!\n'));
560
616
  } else {
561
- console.log(chalk.gray(` โœ“ ${file}`));
617
+ spinner.warn(chalk.yellow(`Installed ${successCount}/${hookFiles.length} scripts (some failed)\n`));
562
618
  }
619
+ return successCount;
620
+ } catch (err) {
621
+ spinner.fail(chalk.red(`Failed to install hook scripts: ${err.message}`));
622
+ throw err;
563
623
  }
564
-
565
- spinner.succeed(chalk.green('Installed TTS scripts!\n'));
566
- return hookFiles.length;
567
624
  }
568
625
 
569
626
  /**