agentvibes 4.5.7 → 4.6.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.
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # File: ~/.claude/hooks/bmad-party-speak.sh
4
+ #
5
+ # AgentVibes PostToolUse Hook - BMAD Party Mode TTS (Linux / macOS / WSL)
6
+ #
7
+ # Fires after every Agent tool call. Detects BMAD party mode agents by
8
+ # fingerprinting the prompt, extracts the agent display name, maps it to
9
+ # the canonical agent ID via the manifest, then calls bmad-speak.sh.
10
+ # Uses flock for cross-process audio serialization (no overlapping speech).
11
+ #
12
+ # Installed globally so it works in any BMAD project.
13
+ # Uses CLAUDE_PROJECT_DIR env var to locate the project manifest at runtime.
14
+ #
15
+ # Input: JSON on stdin (Claude Code PostToolUse payload)
16
+ #
17
+
18
+ set -euo pipefail
19
+
20
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+ LOCK_FILE="/tmp/agentvibes-party-queue.lock"
22
+
23
+ # --- Read stdin ---
24
+ raw="$(cat)"
25
+ [[ -z "$raw" ]] && exit 0
26
+
27
+ # --- Parse all needed fields in one python3 call (fixes M5: 3x subprocess, echo safety) ---
28
+ # Outputs: TOOL_NAME|DISPLAY_NAME|RESPONSE_TEXT (newlines in response encoded as \n literals)
29
+ parsed="$(printf '%s' "$raw" | python3 - <<'PYEOF'
30
+ import sys, json, re
31
+
32
+ try:
33
+ d = json.load(sys.stdin)
34
+ except Exception:
35
+ print("|||")
36
+ sys.exit(0)
37
+
38
+ tool_name = d.get('tool_name', '')
39
+ prompt = d.get('tool_input', {}).get('prompt', '')
40
+
41
+ # Extract display name — safe alternative to grep -oP (fixes C2: macOS BSD grep no -P)
42
+ display_name = ''
43
+ m = re.search(r'You are ([A-Za-z]+)\s*\(', prompt)
44
+ if m:
45
+ display_name = m.group(1)
46
+
47
+ # Extract response text
48
+ response_text = ''
49
+ for item in d.get('tool_response', {}).get('content', []):
50
+ if item.get('type') == 'text':
51
+ response_text = item['text']
52
+ break
53
+
54
+ # Strip leading icon + bold name header (e.g. "📊 **Mary:** " or garbled prefix)
55
+ response_text = response_text.strip()
56
+ response_text = re.sub(r'^\S*\s*\*\*[^:]+:\*\*\s*', '', response_text).strip()
57
+
58
+ # Encode newlines so we can pass multi-line text through a single shell variable (fixes m3)
59
+ response_text = response_text.replace('\n', '\\n')
60
+
61
+ print(f"{tool_name}|{display_name}|{response_text}")
62
+ PYEOF
63
+ )" 2>/dev/null || true
64
+
65
+ [[ -z "$parsed" ]] && exit 0
66
+
67
+ tool_name="${parsed%%|*}"
68
+ rest="${parsed#*|}"
69
+ display_name="${rest%%|*}"
70
+ response_text="${rest#*|}"
71
+
72
+ # Decode \n back to newlines for TTS
73
+ response_text="${response_text//\\n/ }"
74
+
75
+ # --- Only handle Agent tool ---
76
+ [[ "$tool_name" != "Agent" ]] && exit 0
77
+
78
+ # --- Fingerprint: only fire for BMAD party mode agents (safe string match, no pipe) ---
79
+ [[ "$raw" == *"BMAD agent in a collaborative roundtable"* ]] || exit 0
80
+
81
+ [[ -z "$display_name" ]] && exit 0
82
+ [[ -z "$response_text" ]] && exit 0
83
+
84
+ # --- Resolve project root ---
85
+ project_root="${CLAUDE_PROJECT_DIR:-}"
86
+
87
+ # --- Find bmad-speak.sh (prefer project-local, fall back to global) ---
88
+ bmad_speak=""
89
+ if [[ -n "$project_root" && -f "$project_root/.claude/hooks/bmad-speak.sh" ]]; then
90
+ bmad_speak="$project_root/.claude/hooks/bmad-speak.sh"
91
+ elif [[ -f "$SCRIPT_DIR/bmad-speak.sh" ]]; then
92
+ bmad_speak="$SCRIPT_DIR/bmad-speak.sh"
93
+ fi
94
+ [[ -z "$bmad_speak" ]] && exit 0
95
+
96
+ # --- Look up canonical agent ID from project manifest via python3 (fixes M4: awk CSV comma) ---
97
+ agent_id="$display_name" # fallback
98
+ if [[ -n "$project_root" && -f "$project_root/_bmad/_config/agent-manifest.csv" ]]; then
99
+ manifest="$project_root/_bmad/_config/agent-manifest.csv"
100
+ matched="$(python3 - "$manifest" "$display_name" <<'PYEOF'
101
+ import sys, csv
102
+ manifest_path, target = sys.argv[1], sys.argv[2].lower()
103
+ try:
104
+ with open(manifest_path, newline='', encoding='utf-8') as f:
105
+ for row in csv.DictReader(f):
106
+ if row.get('displayName', '').lower() == target:
107
+ print(row.get('name', ''))
108
+ break
109
+ except Exception:
110
+ pass
111
+ PYEOF
112
+ )" 2>/dev/null || true
113
+ [[ -n "$matched" ]] && agent_id="$matched"
114
+ fi
115
+
116
+ # --- Apply verbosity truncation ---
117
+ verbosity="medium"
118
+ # Guard project_root empty to avoid /.claude/... path (fixes M1)
119
+ if [[ -n "$project_root" && -f "$project_root/.claude/tts-verbosity.txt" ]]; then
120
+ v="$(tr -d '[:space:]' < "$project_root/.claude/tts-verbosity.txt")"
121
+ [[ -n "$v" ]] && verbosity="$v"
122
+ elif [[ -f "$HOME/.claude/tts-verbosity.txt" ]]; then
123
+ v="$(tr -d '[:space:]' < "$HOME/.claude/tts-verbosity.txt")"
124
+ [[ -n "$v" ]] && verbosity="$v"
125
+ fi
126
+
127
+ case "$verbosity" in
128
+ low)
129
+ # First sentence — fall back to full text if no punctuation (fixes m1)
130
+ first="$(printf '%s' "$response_text" | python3 -c "
131
+ import sys, re
132
+ t = sys.stdin.read()
133
+ m = re.match(r'^.*?[.!?]', t)
134
+ print(m.group(0) if m else t)
135
+ " 2>/dev/null || printf '%s' "$response_text")"
136
+ [[ -n "$first" ]] && response_text="$first"
137
+ ;;
138
+ medium)
139
+ # First 2 sentences — fall back to full text if no punctuation (fixes m1)
140
+ two="$(printf '%s' "$response_text" | python3 -c "
141
+ import sys, re
142
+ t = sys.stdin.read()
143
+ parts = re.findall(r'.*?[.!?]', t)
144
+ print(' '.join(parts[:2]) if parts else t)
145
+ " 2>/dev/null || printf '%s' "$response_text")"
146
+ [[ -n "$two" ]] && response_text="$two"
147
+ ;;
148
+ # high = full text
149
+ esac
150
+
151
+ [[ -z "$response_text" ]] && exit 0
152
+
153
+ # --- Acquire queue lock (flock: cross-process, auto-releases on crash) ---
154
+ exec 9>"$LOCK_FILE"
155
+ if command -v flock &>/dev/null; then
156
+ flock -w 60 9
157
+ "$bmad_speak" "$agent_id" "$response_text" || true
158
+ flock -u 9
159
+ else
160
+ # macOS fallback: atomic mkdir polling lock
161
+ LOCK_DIR="/tmp/agentvibes-party-queue.lock.d"
162
+ # Register trap BEFORE acquiring lock so SIGTERM can't orphan it (fixes M3)
163
+ trap 'rmdir "$LOCK_DIR" 2>/dev/null || true' EXIT
164
+ WAITED=0
165
+ while ! mkdir "$LOCK_DIR" 2>/dev/null; do
166
+ sleep 0.5
167
+ WAITED=$((WAITED + 1))
168
+ if [[ $WAITED -ge 120 ]]; then
169
+ echo "[AgentVibes] Party mode TTS queue timeout for agent: $agent_id" >&2
170
+ exit 0
171
+ fi
172
+ done
173
+ "$bmad_speak" "$agent_id" "$response_text" || true
174
+ rmdir "$LOCK_DIR" 2>/dev/null || true
175
+ fi
@@ -0,0 +1,141 @@
1
+ #
2
+ # File: ~/.claude/hooks-windows/bmad-party-speak.ps1
3
+ #
4
+ # AgentVibes PostToolUse Hook - BMAD Party Mode TTS
5
+ #
6
+ # Fires after every Agent tool call. Detects BMAD party mode agents by
7
+ # fingerprinting the prompt, extracts the agent display name, maps it to
8
+ # the canonical agent ID via the manifest, then calls bmad-speak.ps1.
9
+ #
10
+ # Installed globally so it works in any BMAD project.
11
+ # Uses $env:CLAUDE_PROJECT_DIR to locate the project manifest at runtime.
12
+ #
13
+
14
+ try {
15
+ # --- Read stdin safely ---
16
+ $raw = [Console]::In.ReadToEnd()
17
+ if (-not $raw -or $raw.Trim() -eq "") { exit 0 }
18
+
19
+ $data = $raw | ConvertFrom-Json
20
+ if (-not $data) { exit 0 }
21
+
22
+ # --- Only handle Agent tool ---
23
+ if ($data.tool_name -ne "Agent") { exit 0 }
24
+
25
+
26
+ # --- Extract prompt ---
27
+ $prompt = $data.tool_input.prompt
28
+ if (-not $prompt) { exit 0 }
29
+
30
+ # --- Fingerprint: only fire for BMAD party mode agents ---
31
+ if ($prompt -notmatch "BMAD agent in a collaborative roundtable") { exit 0 }
32
+
33
+ # --- Extract display name from "You are {Name} (" ---
34
+ if ($prompt -notmatch "You are ([A-Za-z]+)\s*\(") { exit 0 }
35
+ $DisplayName = $Matches[1]
36
+
37
+ # --- Extract response text ---
38
+ $content = $data.tool_response.content
39
+ if (-not $content -or $content.Count -eq 0) { exit 0 }
40
+ $ResponseText = ($content | Where-Object { $_.type -eq "text" } | Select-Object -First 1).text
41
+ if (-not $ResponseText) { exit 0 }
42
+
43
+ # Strip leading icon + bold name header e.g. "📊 **Mary:**" or garbled "≡ƒôè **Mary:**"
44
+ # Trim first so leading newlines don't defeat the ^ anchor (fixes M7)
45
+ $ResponseText = $ResponseText.Trim()
46
+ $ResponseText = $ResponseText -replace '^\S*\s*\*\*[^:]+:\*\*\s*', ''
47
+ $ResponseText = $ResponseText.Trim()
48
+ if (-not $ResponseText) { exit 0 }
49
+
50
+ # --- Resolve paths ---
51
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
52
+ $ProjectRoot = $env:CLAUDE_PROJECT_DIR
53
+
54
+ # --- Find bmad-speak.ps1 (prefer project-local, fall back to global) ---
55
+ $BmadSpeak = $null
56
+ if ($ProjectRoot) {
57
+ $local = Join-Path $ProjectRoot ".claude\hooks-windows\bmad-speak.ps1"
58
+ if (Test-Path $local) { $BmadSpeak = $local }
59
+ }
60
+ if (-not $BmadSpeak) {
61
+ $global = Join-Path $ScriptDir "bmad-speak.ps1"
62
+ if (Test-Path $global) { $BmadSpeak = $global }
63
+ }
64
+ if (-not $BmadSpeak) { exit 0 }
65
+
66
+ # --- Look up canonical agent ID from project manifest (fixes m2: UTF8 encoding) ---
67
+ $AgentId = $DisplayName # fallback
68
+ if ($ProjectRoot) {
69
+ $ManifestFile = Join-Path $ProjectRoot "_bmad\_config\agent-manifest.csv"
70
+ if (Test-Path $ManifestFile) {
71
+ $rows = Import-Csv $ManifestFile -Encoding UTF8
72
+ foreach ($row in $rows) {
73
+ if ($row.displayName -ieq $DisplayName) {
74
+ $AgentId = $row.name
75
+ break
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ # --- Apply verbosity truncation ---
82
+ $Verbosity = "medium"
83
+ $verbosityPaths = @(
84
+ (Join-Path $env:USERPROFILE ".claude\tts-verbosity.txt")
85
+ )
86
+ if ($ProjectRoot) {
87
+ $verbosityPaths = @((Join-Path $ProjectRoot ".claude\tts-verbosity.txt")) + $verbosityPaths
88
+ }
89
+ foreach ($p in $verbosityPaths) {
90
+ if (Test-Path $p) {
91
+ $v = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
92
+ if ($v) { $Verbosity = $v; break }
93
+ }
94
+ }
95
+
96
+ switch ($Verbosity) {
97
+ "low" {
98
+ $sentences = [regex]::Split($ResponseText, '(?<=[.!?])\s+')
99
+ # Fall back to full text if no sentence-ending punctuation (fixes m1)
100
+ if ($sentences.Count -gt 0 -and $sentences[0]) { $ResponseText = $sentences[0] }
101
+ }
102
+ "medium" {
103
+ $sentences = [regex]::Split($ResponseText, '(?<=[.!?])\s+')
104
+ # Fall back to full text if no sentence-ending punctuation (fixes m1)
105
+ $truncated = ($sentences | Select-Object -First 2) -join " "
106
+ if ($truncated) { $ResponseText = $truncated }
107
+ }
108
+ # "high" = full text
109
+ }
110
+
111
+ # --- Speak with queue serialization (named mutex, cross-process) ---
112
+ $mutex = New-Object System.Threading.Mutex($false, "AgentVibesPartyModeTTSQueue")
113
+ try {
114
+ $acquired = $false
115
+ try {
116
+ # WaitOne throws AbandonedMutexException if prior process crashed while holding it.
117
+ # That exception means we DID acquire the mutex — treat it as success (fixes M2).
118
+ $acquired = $mutex.WaitOne(60000)
119
+ } catch [System.Threading.AbandonedMutexException] {
120
+ $acquired = $true # abandoned = we now own it
121
+ }
122
+
123
+ if ($acquired) {
124
+ try {
125
+ # Pass positional args directly after -File (spaces handled by quoting via array)
126
+ & powershell -NoProfile -ExecutionPolicy Bypass -File $BmadSpeak $AgentId $ResponseText
127
+ } finally {
128
+ $mutex.ReleaseMutex()
129
+ }
130
+ } else {
131
+ # Timed out — log to stderr so it's visible in hook error output (fixes M6)
132
+ [Console]::Error.WriteLine("[AgentVibes] Party mode TTS queue timeout for agent: $AgentId")
133
+ }
134
+ } finally {
135
+ $mutex.Close()
136
+ }
137
+
138
+ } catch {
139
+ # Silently exit — never block Claude
140
+ exit 0
141
+ }
package/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # AgentVibes Release Notes
2
2
 
3
+ ## ✨ v4.6.0 — Minor Release
4
+
5
+ **Release Date:** April 2026
6
+
7
+ ### New Features
8
+
9
+ - **BMAD party mode TTS auto-installs for all platforms** — The installer now automatically copies `bmad-party-speak.sh` (Linux/macOS/WSL) or `bmad-party-speak.ps1` (Windows) to `~/.claude/hooks/` and registers a `PostToolUse` hook in `~/.claude/settings.json`. Party mode agents now speak out of the box in any BMAD project without manual setup. Both scripts are included in critical hooks so `npx agentvibes update` keeps them fresh.
10
+
11
+ ### Bug Fixes
12
+
13
+ - **Background music volume default** — All volume defaults lowered from 70% to 20% across the UI (settings tab, agents tab, music tab, track picker) and scripts (`audio-processor.sh`, `bmad-speak.sh`, `bmad-speak.ps1`). New installs and newly configured agents default to a much more reasonable level.
14
+ - **bmad-speak volume inheritance** — `bmad-speak.sh` and `bmad-speak.ps1` now read the global `background-music-volume.txt` config file as the fallback volume instead of a hardcoded value.
15
+ - **Installer wizard left arrow** — Pressing ← on the completion screen (screen 5) to move from Done-Quit to Done-Customize More no longer jumps back to the installation step.
16
+
17
+ ### Tests
18
+
19
+ - 29 new tests: volume default regression guards across all affected files, `configurePartyModeHook` installer coverage (idempotency, settings.json registration, script copying, hook preservation), and a regression test for the screen 5 navigation fix.
20
+
21
+ ---
22
+
3
23
  ## 🐛 v4.5.7 — Patch Release
4
24
 
5
25
  **Release Date:** April 2026
package/bin/agent-vibes CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * AgentVibes - Beautiful ElevenLabs TTS voice commands for Claude Code
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * AgentVibes Voice Browser
package/bin/mcp-server.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * AgentVibes MCP Server Launcher (Cross-Platform)
package/bin/test-bmad-pr CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env bash
2
2
  #
3
3
  # AgentVibes BMAD PR Testing Command
4
4
  # Quick command to test BMAD PRs with AgentVibes integration
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.5.7",
4
+ "version": "4.6.0",
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": [
@@ -974,6 +974,7 @@ export function createInstallTab(screen, services) {
974
974
  screen.key(['left'], () => {
975
975
  if (box.hidden || _checking) return;
976
976
  if (_screen === 4) return;
977
+ if (_screen === 5) return; // Screen 5: ← handled by button nav
977
978
  if (_screen > 0) {
978
979
  _screen--;
979
980
  _showCurrentScreen();
package/src/installer.js CHANGED
@@ -3822,6 +3822,76 @@ async function configureSessionStartHook(targetDir, spinner) {
3822
3822
  }
3823
3823
  }
3824
3824
 
3825
+ /**
3826
+ * Configure BMAD party mode PostToolUse hook in the global ~/.claude/settings.json.
3827
+ * Copies bmad-party-speak script to ~/.claude/hooks/ (or hooks-windows/ on Windows)
3828
+ * and registers the PostToolUse hook so party mode TTS works in any BMAD project.
3829
+ * @param {string} targetDir - Target installation directory (used to locate source scripts)
3830
+ * @param {Object} spinner - Ora spinner instance
3831
+ */
3832
+ async function configurePartyModeHook(targetDir, spinner, homeDirOverride) {
3833
+ spinner.start('Configuring BMAD party mode TTS hook...');
3834
+ const homeDir = homeDirOverride || os.homedir();
3835
+ const globalClaudeDir = path.join(homeDir, '.claude');
3836
+ const globalSettingsPath = path.join(globalClaudeDir, 'settings.json');
3837
+
3838
+ try {
3839
+ // Determine platform-specific paths
3840
+ const hooksSubdir = isNativeWindows() ? 'hooks-windows' : 'hooks';
3841
+ const scriptName = isNativeWindows() ? 'bmad-party-speak.ps1' : 'bmad-party-speak.sh';
3842
+ const globalHooksDir = path.join(globalClaudeDir, hooksSubdir);
3843
+ const srcScript = path.join(__dirname, '..', '.claude', hooksSubdir, scriptName);
3844
+ const destScript = path.join(globalHooksDir, scriptName);
3845
+
3846
+ // Copy script to global hooks dir (create dir if needed)
3847
+ await fs.mkdir(globalHooksDir, { recursive: true });
3848
+ await fs.copyFile(srcScript, destScript);
3849
+ if (!isNativeWindows()) {
3850
+ await fs.chmod(destScript, 0o750);
3851
+ }
3852
+
3853
+ // Build the PostToolUse hook command
3854
+ const hookCommand = isNativeWindows()
3855
+ ? `powershell -NoProfile -ExecutionPolicy Bypass -File "$HOME\\.claude\\hooks-windows\\bmad-party-speak.ps1"`
3856
+ : `bash "$HOME/.claude/hooks/bmad-party-speak.sh"`;
3857
+
3858
+ // Read/create global settings.json
3859
+ let settings = {};
3860
+ try {
3861
+ const content = await fs.readFile(globalSettingsPath, 'utf8');
3862
+ settings = JSON.parse(content);
3863
+ } catch {
3864
+ // File missing or invalid — start fresh
3865
+ }
3866
+
3867
+ if (!settings.hooks) settings.hooks = {};
3868
+
3869
+ // Check if PostToolUse hook already registered
3870
+ const existing = settings.hooks.PostToolUse;
3871
+ const alreadyRegistered = Array.isArray(existing) &&
3872
+ existing.some(entry =>
3873
+ Array.isArray(entry.hooks) &&
3874
+ entry.hooks.some(h => h.command && h.command.includes('bmad-party-speak'))
3875
+ );
3876
+
3877
+ if (!alreadyRegistered) {
3878
+ if (!Array.isArray(settings.hooks.PostToolUse)) {
3879
+ settings.hooks.PostToolUse = [];
3880
+ }
3881
+ settings.hooks.PostToolUse.push({
3882
+ hooks: [{ type: 'command', command: hookCommand }]
3883
+ });
3884
+ await fs.writeFile(globalSettingsPath, JSON.stringify(settings, null, 2));
3885
+ spinner.succeed(chalk.green('BMAD party mode TTS hook configured!\n'));
3886
+ } else {
3887
+ // Script still updated above — just note settings unchanged
3888
+ spinner.succeed(chalk.green('BMAD party mode TTS hook up to date\n'));
3889
+ }
3890
+ } catch (error) {
3891
+ spinner.warn(chalk.yellow(`BMAD party mode hook setup skipped: ${error.message}\n`));
3892
+ }
3893
+ }
3894
+
3825
3895
  /**
3826
3896
  * Ensure target directory is a git repo (required for Claude Code hook context injection)
3827
3897
  * @param {string} targetDir - Target installation directory
@@ -4781,7 +4851,8 @@ async function updateCommandFiles(targetDir, spinner) {
4781
4851
  * These hooks contain bug fixes (e.g. markdown stripping) that must propagate
4782
4852
  * on every `npx agentvibes update` regardless of target directory.
4783
4853
  */
4784
- const CRITICAL_HOOKS = ['stop-tts.sh', 'stop.sh', 'play-tts.sh', 'session-start-tts.sh'];
4854
+ const CRITICAL_HOOKS = ['stop-tts.sh', 'stop.sh', 'play-tts.sh', 'session-start-tts.sh', 'bmad-party-speak.sh'];
4855
+ const CRITICAL_HOOKS_WINDOWS = ['play-tts.ps1', 'session-start-tts.ps1', 'bmad-speak.ps1', 'bmad-party-speak.ps1'];
4785
4856
 
4786
4857
  /**
4787
4858
  * Update critical hooks in the global ~/.claude/hooks/ directory if it exists.
@@ -4811,6 +4882,27 @@ async function updateGlobalHooks(srcHooksDir, homeDirOverride) {
4811
4882
  // file not in global dir or src missing — skip silently
4812
4883
  }
4813
4884
  }
4885
+
4886
+ // Also update Windows global hooks-windows dir if present
4887
+ const globalHooksWindowsDir = path.join(homeDirOverride || os.homedir(), '.claude', 'hooks-windows');
4888
+ const srcHooksWindowsDir = path.join(path.dirname(srcHooksDir), 'hooks-windows');
4889
+ try {
4890
+ await fs.access(globalHooksWindowsDir);
4891
+ for (const hook of CRITICAL_HOOKS_WINDOWS) {
4892
+ const destPath = path.join(globalHooksWindowsDir, hook);
4893
+ const srcPath = path.join(srcHooksWindowsDir, hook);
4894
+ try {
4895
+ await fs.access(destPath); // only update if already installed
4896
+ await fs.copyFile(srcPath, destPath);
4897
+ updated++;
4898
+ } catch {
4899
+ // file not in global dir or src missing — skip silently
4900
+ }
4901
+ }
4902
+ } catch {
4903
+ // hooks-windows dir not present — nothing to do
4904
+ }
4905
+
4814
4906
  return updated;
4815
4907
  }
4816
4908
 
@@ -4873,6 +4965,7 @@ async function performUpdateOperations(targetDir, spinner) {
4873
4965
  // Update settings.json
4874
4966
  spinner.text = 'Updating AgentVibes hook configuration...';
4875
4967
  await configureSessionStartHook(targetDir, silentSpinner);
4968
+ await configurePartyModeHook(targetDir, silentSpinner);
4876
4969
  await ensureGitRepo(targetDir, silentSpinner);
4877
4970
 
4878
4971
  // Detect and migrate old configuration
@@ -5050,6 +5143,7 @@ async function install(options = {}) {
5050
5143
  await copyBackgroundMusicFiles(targetDir, silentSpinner);
5051
5144
  await copyConfigFiles(targetDir, silentSpinner);
5052
5145
  await configureSessionStartHook(targetDir, silentSpinner);
5146
+ await configurePartyModeHook(targetDir, silentSpinner);
5053
5147
  await installPluginManifest(targetDir, silentSpinner);
5054
5148
  await ensureGitRepo(targetDir, silentSpinner);
5055
5149
 
@@ -5943,7 +6037,7 @@ export {
5943
6037
  isTermux, isNativeWindows, detectAndNotifyTermux,
5944
6038
  copyCommandFiles, copyHookFiles, copyPersonalityFiles,
5945
6039
  copyPluginFiles, copyBmadConfigFiles, copyBackgroundMusicFiles,
5946
- copyConfigFiles, configureSessionStartHook, ensureGitRepo,
6040
+ copyConfigFiles, configureSessionStartHook, configurePartyModeHook, ensureGitRepo,
5947
6041
  installPluginManifest, checkAndInstallPiper,
5948
- updateGlobalHooks, CRITICAL_HOOKS,
6042
+ updateGlobalHooks, CRITICAL_HOOKS, CRITICAL_HOOKS_WINDOWS,
5949
6043
  };