agentvibes 5.14.0 → 5.15.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.
@@ -1 +1 @@
1
- 20260719
1
+ 20260720
@@ -0,0 +1,83 @@
1
+ #
2
+ # File: .claude/hooks-windows/agentvibes-session-id.ps1
3
+ #
4
+ # AgentVibes - Text-to-Speech WITH personality for AI Assistants
5
+ # Website: https://agentvibes.org
6
+ # Repository: https://github.com/paulpreibisch/AgentVibes
7
+ #
8
+ # Licensed under the Apache License, Version 2.0
9
+ #
10
+ # @fileoverview Print a short spoken self-identifier for THIS session, e.g.
11
+ # "Claude on AgentVibes in Windows Terminal"
12
+ # @why With several agent sessions open at once, every one saying the same thing
13
+ # makes it impossible to tell which tab just spoke. Prefixing each session's
14
+ # speech with who+where answers "which one is that?" (community request).
15
+ # @usage agentvibes-session-id.ps1 <llm-key> <project-dir>
16
+ # Also used via the {{session}} token in a per-LLM PRETEXT (see play-tts.ps1).
17
+ #
18
+ # PowerShell mirror of .claude/hooks/agentvibes-session-id.sh — the LLM name map,
19
+ # the project-name fallback, and the terminal-detection ORDER must stay identical
20
+ # so a given session announces itself the same way on either runtime.
21
+ #
22
+ param(
23
+ [string]$LlmKey = "claude-code",
24
+ [string]$ProjectDir = ""
25
+ )
26
+
27
+ $ErrorActionPreference = "SilentlyContinue"
28
+
29
+ if (-not $ProjectDir) { $ProjectDir = (Get-Location).Path }
30
+
31
+ # LLM display name (same mapping as the bash version)
32
+ $LlmName = switch -Regex ($LlmKey) {
33
+ '^claude' { "Claude"; break }
34
+ '^gemini' { "Gemini"; break }
35
+ '^(codex|openai|gpt)' { "Codex"; break }
36
+ '^cursor' { "Cursor"; break }
37
+ '^default$' { "Agent"; break }
38
+ default { $LlmKey }
39
+ }
40
+
41
+ # Project name = leaf of the project dir. Split-Path is used rather than
42
+ # Get-Item so a path that no longer exists still yields a usable name.
43
+ $ProjName = ""
44
+ try { $ProjName = Split-Path -Leaf ($ProjectDir.TrimEnd('\', '/')) } catch { }
45
+ if (-not $ProjName -or $ProjName -in @('/', '.', '\')) { $ProjName = "this project" }
46
+
47
+ # Terminal detection — prefer the most specific signal available.
48
+ # Order mirrors the bash version exactly.
49
+ $TermName = ""
50
+ if ($env:WT_SESSION) {
51
+ $TermName = "Windows Terminal"
52
+ } elseif ($env:TERM_PROGRAM) {
53
+ $TermName = switch ($env:TERM_PROGRAM) {
54
+ "vscode" { "VS Code" }
55
+ "iTerm.app" { "iTerm" }
56
+ "Apple_Terminal" { "Terminal" }
57
+ # One entry only: PowerShell's switch is case-insensitive by default, so
58
+ # separate "ghostty"/"Ghostty" arms BOTH match and both emit, yielding
59
+ # "Ghostty Ghostty". Bash needs the two arms; PowerShell must not have them.
60
+ "ghostty" { "Ghostty" }
61
+ "WezTerm" { "WezTerm" }
62
+ "Hyper" { "Hyper" }
63
+ "tmux" { "tmux" }
64
+ "Tabby" { "Tabby" }
65
+ default { $env:TERM_PROGRAM }
66
+ }
67
+ } elseif ($env:TERMINAL_EMULATOR) {
68
+ $TermName = $env:TERMINAL_EMULATOR
69
+ } elseif ($env:KITTY_WINDOW_ID) {
70
+ $TermName = "kitty"
71
+ } elseif ($env:ALACRITTY_WINDOW_ID) {
72
+ $TermName = "Alacritty"
73
+ } elseif ($env:TMUX) {
74
+ $TermName = "tmux"
75
+ }
76
+
77
+ # -NoNewline so the caller can embed the result directly in a pretext, matching
78
+ # the bash version's printf (which also emits no trailing newline).
79
+ if ($TermName) {
80
+ Write-Host -NoNewline "$LlmName on $ProjName in $TermName"
81
+ } else {
82
+ Write-Host -NoNewline "$LlmName on $ProjName"
83
+ }
@@ -73,7 +73,8 @@ $HooksDir = "$ClaudeDir\hooks-windows"
73
73
  $ProviderFile = "$ClaudeDir\tts-provider.txt"
74
74
  $MuteFile = "$ClaudeDir\tts-muted.txt"
75
75
 
76
- # Check if TTS is muted
76
+ # Check if TTS is muted (receiver master switch — /agent-vibes:receiver off).
77
+ # Kept as an absolute pre-check so existing behaviour does not regress.
77
78
  if (Test-Path $MuteFile) {
78
79
  $muteStatus = Get-Content $MuteFile -Raw
79
80
  if ($muteStatus.Trim() -eq "true") {
@@ -81,6 +82,35 @@ if (Test-Path $MuteFile) {
81
82
  }
82
83
  }
83
84
 
85
+ # ---------------------------------------------------------------------------
86
+ # MUTE PRECEDENCE (parity with .claude/hooks/play-tts.sh:89-114).
87
+ #
88
+ # Until now this player read ONLY tts-muted.txt above, which /agent-vibes:mute
89
+ # never writes — so `/agent-vibes:mute` did not silence Windows audio at all,
90
+ # and the global `~/.agentvibes-muted` kill-switch was ignored on Windows.
91
+ # Same three levels as bash, in the same order:
92
+ # 1. project agentvibes-unmuted -> speak (overrides a global mute)
93
+ # 2. project agentvibes-muted -> silent
94
+ # 3. global ~/.agentvibes-muted -> silent
95
+ #
96
+ # The project markers live under the REAL project root (CLAUDE_PROJECT_DIR when
97
+ # set), not $ClaudeDir, which falls back to the user profile for global installs.
98
+ # $HOME is preferred over $env:USERPROFILE so markers written by bash under
99
+ # git-bash resolve to the same file here.
100
+ $HomeDir = if ($env:HOME) { $env:HOME } else { $env:USERPROFILE }
101
+ $MuteScopeDir = if ($env:CLAUDE_PROJECT_DIR -and (Test-Path "$env:CLAUDE_PROJECT_DIR\.claude")) {
102
+ "$env:CLAUDE_PROJECT_DIR\.claude"
103
+ } else {
104
+ $ClaudeDir
105
+ }
106
+ if (Test-Path (Join-Path $MuteScopeDir "agentvibes-unmuted")) {
107
+ # explicit per-project enable — wins over the global kill-switch
108
+ } elseif (Test-Path (Join-Path $MuteScopeDir "agentvibes-muted")) {
109
+ exit 0
110
+ } elseif (Test-Path (Join-Path $HomeDir ".agentvibes-muted")) {
111
+ exit 0
112
+ }
113
+
84
114
  # Determine active provider
85
115
  $ActiveProvider = "windows-sapi"
86
116
  if (Test-Path $ProviderFile) {
@@ -453,6 +483,70 @@ elseif (-not $PlanOk -and $_LlmVoice -and -not $VoiceOverride) {
453
483
  $VoiceOverride = $_LlmVoice
454
484
  }
455
485
 
486
+ # --- Dynamic session self-ID ({{session}} token) ------------------------------
487
+ # Parity with play-tts.sh:250-296. A per-LLM PRETEXT containing {{session}}
488
+ # expands to "<LLM> on <Project> in <Terminal>" so multi-session users can tell
489
+ # which window just spoke. Announced ONCE per session; on later utterances the
490
+ # token is dropped rather than prefixing every line. Opt-in — this whole block
491
+ # is inert unless the configured pretext actually contains the token.
492
+ if ($_LlmPretext -like "*{{session}}*") {
493
+ # Session key. There is no true Claude-session id available to a hook, so
494
+ # this is best-effort: a terminal-session id when the emulator exports one
495
+ # (stable for that tab's life), else this process's parent PID. Worst case
496
+ # the self-ID repeats or is skipped — cosmetic, and only when opted in.
497
+ $_SidRaw = if ($env:WT_SESSION) { $env:WT_SESSION }
498
+ elseif ($env:TERM_SESSION_ID) { $env:TERM_SESSION_ID }
499
+ elseif ($env:TMUX) { $env:TMUX }
500
+ else {
501
+ try { (Get-CimInstance Win32_Process -Filter "ProcessId=$PID").ParentProcessId } catch { $PID }
502
+ }
503
+ $_SidProj = ""
504
+ $_SidProjDirRaw = if ($env:CLAUDE_PROJECT_DIR) { $env:CLAUDE_PROJECT_DIR } else { $ClaudeDir }
505
+ try { $_SidProj = Split-Path -Leaf ($_SidProjDirRaw.TrimEnd('\', '/')) } catch { }
506
+ $_SidKey = (("$llm|$_SidProj|$_SidRaw") -replace '[^A-Za-z0-9]', '_')
507
+
508
+ # Announce-marker dir under the per-user temp path (already user-scoped on
509
+ # Windows: %LOCALAPPDATA%\Temp), so no cross-user marker collisions.
510
+ $_AnnDir = Join-Path ([System.IO.Path]::GetTempPath()) "agentvibes-session"
511
+ $_AnnOk = $false
512
+ try { $null = New-Item -ItemType Directory -Path $_AnnDir -Force -ErrorAction Stop; $_AnnOk = $true } catch { }
513
+ $_AnnFile = Join-Path $_AnnDir "announced-$_SidKey"
514
+
515
+ # First utterance -> replacement is the self-ID; later utterances -> empty.
516
+ $_SessionId = ""
517
+ if ($_AnnOk -and (Test-Path $_AnnFile)) {
518
+ $_SessionId = ""
519
+ } else {
520
+ $_SidScript = Join-Path $HooksDir "agentvibes-session-id.ps1"
521
+ if (Test-Path $_SidScript) {
522
+ $_SidProjDir = if ($env:CLAUDE_PROJECT_DIR) { $env:CLAUDE_PROJECT_DIR } else { $ClaudeDir }
523
+ try { $_SessionId = & $_SidScript -LlmKey $llm -ProjectDir $_SidProjDir } catch { $_SessionId = "" }
524
+ }
525
+ if ($_AnnOk) {
526
+ try {
527
+ if (-not (Test-Path $_AnnFile)) { $null = New-Item -ItemType File -Path $_AnnFile -ErrorAction Stop }
528
+ # Bound growth: drop announce-markers older than a day.
529
+ Get-ChildItem -Path $_AnnDir -Filter 'announced-*' -ErrorAction SilentlyContinue |
530
+ Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-1) } |
531
+ Remove-Item -Force -ErrorAction SilentlyContinue
532
+ } catch { }
533
+ }
534
+ }
535
+
536
+ if ($_SessionId) {
537
+ $_LlmPretext = $_LlmPretext -replace [regex]::Escape('{{session}}'), $_SessionId
538
+ } else {
539
+ # Empty replacement: also swallow one adjacent ", " so "Hey {{session}}, ready"
540
+ # becomes "Hey ready", not "Hey , ready"; then drop any bare token.
541
+ $_LlmPretext = $_LlmPretext -replace [regex]::Escape('{{session}}, '), ''
542
+ $_LlmPretext = $_LlmPretext -replace [regex]::Escape(', {{session}}'), ''
543
+ $_LlmPretext = $_LlmPretext -replace [regex]::Escape('{{session}}'), ''
544
+ }
545
+ # Final tidy: collapse doubled/edge commas and runs of whitespace.
546
+ $_LlmPretext = $_LlmPretext -replace ',\s*,', ',' -replace '^\s*,\s*', '' -replace '\s*,\s*$', '' -replace '\s{2,}', ' '
547
+ $_LlmPretext = $_LlmPretext.Trim()
548
+ }
549
+
456
550
  # --- Apply LLM-specific pretext ----------------------------------------------
457
551
  # Prepend the configured pretext (e.g. "Agent Vibes Here") to the speech
458
552
  # text. Guard against double-prefixing on re-entrant or looped calls by
@@ -33,6 +33,42 @@ if ($env:CLAUDE_PROJECT_DIR -and (Test-Path "$env:CLAUDE_PROJECT_DIR\.claude"))
33
33
  $ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptDir)) ".claude"
34
34
  }
35
35
 
36
+ # ---------------------------------------------------------------------------
37
+ # OPT-IN INJECTION GATE (Issue: global-install cacophony).
38
+ #
39
+ # Mirror of the gate in .claude/hooks/session-start-tts.sh — see the long
40
+ # rationale there. Previously this hook injected the ~250-token TTS protocol
41
+ # into EVERY Windows session where AgentVibes was found on disk, so a global
42
+ # install made every open session start speaking at once, and idle sessions
43
+ # still paid the token cost. Injection is now OPT-IN on Windows too.
44
+ #
45
+ # Precedence (identical to bash, so injection and audio agree):
46
+ # 1. project agentvibes-unmuted -> inject (explicit per-project ON)
47
+ # 2. project agentvibes-muted -> silent
48
+ # 3. global ~/.agentvibes-muted -> silent (the global kill-switch)
49
+ # 4. project agentvibes-enabled, OR global opt-in
50
+ # (~/.claude/agentvibes-enabled or ~/.claude/agentvibes-unmuted) -> inject
51
+ # 5. otherwise -> silent (opt-in default; zero tokens)
52
+ #
53
+ # $HOME is honoured ahead of $env:USERPROFILE so the markers resolve the same
54
+ # way under git-bash (where bash writes them) as under native PowerShell.
55
+ $HomeDir = if ($env:HOME) { $env:HOME } else { $env:USERPROFILE }
56
+ if (Test-Path (Join-Path $ProjectClaudeDir "agentvibes-unmuted")) {
57
+ # explicit per-project enable — wins over a global mute, as in play-tts.sh
58
+ } elseif (Test-Path (Join-Path $ProjectClaudeDir "agentvibes-muted")) {
59
+ exit 0
60
+ } elseif (Test-Path (Join-Path $HomeDir ".agentvibes-muted")) {
61
+ exit 0 # global kill-switch, no per-project override
62
+ } elseif ((Test-Path (Join-Path $ProjectClaudeDir "agentvibes-enabled")) -or
63
+ (Test-Path (Join-Path $HomeDir ".claude\agentvibes-enabled")) -or
64
+ (Test-Path (Join-Path $HomeDir ".claude\agentvibes-unmuted"))) {
65
+ # opted in (project install marker, or a deliberate global opt-in)
66
+ } else {
67
+ # Not enabled for this project -> inject nothing (zero tokens).
68
+ # Enable for this project with: /agent-vibes:unmute
69
+ exit 0
70
+ }
71
+
36
72
  # Build the -ProjectDir flag to inject into TTS commands (empty string = omit flag).
37
73
  # Sanitize: strip any embedded quotes that would break PowerShell argument quoting.
38
74
  $ProjectDirFlag = ""
package/README.md CHANGED
@@ -58,7 +58,15 @@ New here? The [**Quick Start guide**](docs/quick-start.md) walks you through you
58
58
 
59
59
  ---
60
60
 
61
- ## 🆕 Reliable setup and complete audio previews (v5.14.0)
61
+ ## 🆕 Multi-session control on Windows (v5.15.0)
62
+
63
+ - **Sessions stay quiet unless you enable them** — a session speaks only in a project you've turned on; others add no instructions and no token cost.
64
+ - **`/agent-vibes:mute` now works on Windows** — it previously had no effect there. Both project and global mute are honoured on every platform.
65
+ - **Sessions can introduce themselves on Windows** — `{{session}}` announces "Claude on my-app in Windows Terminal", once per session.
66
+ - **Self-introductions now reach global installs** — the script behind them was never delivered by the updater on any platform.
67
+ - **Note for global Windows installs:** sessions are off by default after this update — enable with `/agent-vibes:unmute`.
68
+
69
+ ### v5.14.0 — Reliable setup and complete audio previews
62
70
 
63
71
  Includes all changes from 5.13.2, which was not published to npm.
64
72
 
@@ -258,7 +266,7 @@ anything (no new network calls, no behavior change) for every current install.
258
266
 
259
267
  ## About
260
268
 
261
- **AgentVibes** · v5.14.0 · Licensed under [Apache-2.0](LICENSE)
269
+ **AgentVibes** · v5.15.0 · Licensed under [Apache-2.0](LICENSE)
262
270
 
263
271
  Built by **Paul Preibisch** — [@997Fire on X](https://x.com/997Fire) · [agentvibes.org](https://agentvibes.org) · [github.com/paulpreibisch/AgentVibes](https://github.com/paulpreibisch/AgentVibes)
264
272
 
package/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # AgentVibes Release Notes
2
2
 
3
+ ## v5.15.0 — Multi-session control on Windows
4
+
5
+ **Released:** 2026-07-20 · `npm install agentvibes@latest`
6
+
7
+ If you run several agent sessions at once, this release gives Windows the same
8
+ control macOS and Linux gained in 5.13.0: sessions stay quiet unless you turn
9
+ them on, and each one can tell you which window is speaking.
10
+
11
+ ### Sessions no longer all speak at once
12
+
13
+ Running AgentVibes across several projects previously meant every open session
14
+ announced itself, with no straightforward way to keep just one talking. On
15
+ macOS and Linux this was addressed in 5.13.0. **On Windows it was not** — the
16
+ Windows session hook enabled speech in every session regardless of your
17
+ settings.
18
+
19
+ Windows now follows the same rule: a session speaks only in a project you have
20
+ explicitly enabled. Projects you have not enabled contribute **nothing at all**
21
+ — not silent audio, but no added instructions and no token cost.
22
+
23
+ ### Mute now works on Windows
24
+
25
+ `/agent-vibes:mute` had no effect on Windows audio. It silenced macOS and Linux
26
+ only, because the Windows player read a different setting than the one the
27
+ command wrote.
28
+
29
+ Both the project mute and the global switch are now honoured on Windows:
30
+
31
+ - `/agent-vibes:mute` — quiet this project
32
+ - `/agent-vibes:unmute` — turn this project back on, even while everything else
33
+ is globally muted
34
+
35
+ This makes "off everywhere, on in the one project I am working in" a single
36
+ pair of commands on every platform.
37
+
38
+ ### Sessions can identify themselves on Windows
39
+
40
+ Include `{{session}}` in your pretext and a session introduces itself once:
41
+
42
+ ```
43
+ Claude on my-app in Windows Terminal
44
+ ```
45
+
46
+ It names the project and detects the terminal — Windows Terminal, VS Code,
47
+ Ghostty, iTerm, Terminal, WezTerm, tmux and others. Announced once per session
48
+ rather than before every line. This previously worked only on macOS and Linux.
49
+
50
+ ### A related fix for every platform
51
+
52
+ The script that produces that introduction was missing from the update process,
53
+ so on a global install it was never delivered and the feature silently did
54
+ nothing. It is now included on macOS, Linux and Windows alike.
55
+
56
+ ### Upgrading
57
+
58
+ Your existing choices are preserved — if you have already muted, unmuted, or
59
+ enabled a project, updating leaves that setting alone.
60
+
61
+ **One change worth noting for Windows users on a global install:** because
62
+ sessions are now off unless enabled, a global Windows install will be quiet
63
+ after updating. Turn on the projects you want with `/agent-vibes:unmute`.
64
+ Project installs are enabled automatically and are unaffected.
65
+
66
+ ### Quality
67
+
68
+ New regression tests assert that the session gate, the mute rules, and the
69
+ self-identification behave identically on both runtimes, so the platforms
70
+ cannot drift apart again without the build failing.
71
+
72
+ ---
73
+
3
74
  ## v5.14.0 — Reliable setup and complete audio previews
4
75
 
5
76
  **Released:** 2026-07-19 · `npm install agentvibes@latest`
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.14.0",
4
+ "version": "5.15.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": [
package/src/installer.js CHANGED
@@ -5442,8 +5442,13 @@ async function updateCommandFiles(targetDir, spinner) {
5442
5442
  * These hooks contain bug fixes (e.g. markdown stripping) that must propagate
5443
5443
  * on every `npx agentvibes update` regardless of target directory.
5444
5444
  */
5445
- const CRITICAL_HOOKS = ['stop-tts.sh', 'stop.sh', 'play-tts.sh', 'play-tts-piper.sh', 'audio-processor.sh', 'session-start-tts.sh', 'bmad-party-speak.sh'];
5446
- const CRITICAL_HOOKS_WINDOWS = ['play-tts.ps1', 'play-tts-piper.ps1', 'audio-processor.ps1', 'session-start-tts.ps1', 'bmad-speak.ps1', 'bmad-party-speak.ps1', 'tts-watcher.ps1'];
5445
+ // agentvibes-session-id.{sh,ps1} are listed because play-tts.{sh,ps1} SHELL OUT to
5446
+ // them to expand a {{session}} pretext. They were absent here, so on a global
5447
+ // install the global-update path never shipped them and the self-ID silently
5448
+ // degraded to an empty string forever — the callers fail soft by design, so the
5449
+ // omission produced no error, just a feature that never worked.
5450
+ const CRITICAL_HOOKS = ['stop-tts.sh', 'stop.sh', 'play-tts.sh', 'play-tts-piper.sh', 'audio-processor.sh', 'session-start-tts.sh', 'bmad-party-speak.sh', 'agentvibes-session-id.sh'];
5451
+ const CRITICAL_HOOKS_WINDOWS = ['play-tts.ps1', 'play-tts-piper.ps1', 'audio-processor.ps1', 'session-start-tts.ps1', 'bmad-speak.ps1', 'bmad-party-speak.ps1', 'tts-watcher.ps1', 'agentvibes-session-id.ps1'];
5447
5452
 
5448
5453
  /**
5449
5454
  * Update critical hooks in the global ~/.claude/hooks/ directory if it exists.