agentvibes 5.12.0-alpha.0 → 5.13.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.
Files changed (129) hide show
  1. package/.agentvibes/config.json +15 -1
  2. package/.agentvibes/install-manifest.json +342 -0
  3. package/.claude/activation-instructions +54 -54
  4. package/.claude/commands/agent-vibes/commands.json +0 -20
  5. package/.claude/commands/agent-vibes/unmute.md +6 -2
  6. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  7. package/.claude/commands/agent-vibes-rdp.md +24 -24
  8. package/.claude/config/audio-effects.cfg +6 -6
  9. package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
  10. package/.claude/config/background-music-enabled.txt +1 -1
  11. package/.claude/config/background-music-position.txt +27 -0
  12. package/.claude/config/background-music-volume.txt +1 -0
  13. package/.claude/config/background-music.cfg +1 -0
  14. package/.claude/config/background-music.txt +1 -0
  15. package/.claude/config/language.txt +1 -0
  16. package/.claude/config/personality.txt +1 -0
  17. package/.claude/config/tts-speech-rate.txt +1 -0
  18. package/.claude/config/tts-verbosity.txt +1 -0
  19. package/.claude/docs/TERMUX_SETUP.md +408 -408
  20. package/.claude/github-star-reminder.txt +1 -1
  21. package/.claude/hooks/agentvibes-session-id.sh +69 -0
  22. package/.claude/hooks/bmad-party-speak.sh +20 -4
  23. package/.claude/hooks/bmad-speak.sh +60 -2
  24. package/.claude/hooks/bmad-tts-injector.sh +20 -1
  25. package/.claude/hooks/bmad-voice-manager.sh +25 -3
  26. package/.claude/hooks/clawdbot-receiver-SECURE.sh +21 -2
  27. package/.claude/hooks/clawdbot-receiver.sh +19 -1
  28. package/.claude/hooks/elevenlabs-voices.sh +62 -0
  29. package/.claude/hooks/kokoro-installer.sh +20 -10
  30. package/.claude/hooks/language-manager.sh +10 -3
  31. package/.claude/hooks/party-set-room.sh +71 -0
  32. package/.claude/hooks/party-stage-roster.py +328 -0
  33. package/.claude/hooks/personality-manager.sh +19 -2
  34. package/.claude/hooks/piper-voice-manager.sh +3 -2
  35. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +24 -5
  36. package/.claude/hooks/play-tts-elevenlabs.sh +38 -118
  37. package/.claude/hooks/play-tts-kokoro.sh +33 -6
  38. package/.claude/hooks/play-tts-soprano.sh +3 -2
  39. package/.claude/hooks/play-tts-ssh-remote.sh +72 -27
  40. package/.claude/hooks/play-tts-termux-ssh.sh +5 -4
  41. package/.claude/hooks/play-tts.sh +86 -69
  42. package/.claude/hooks/provider-catalog.json +352 -0
  43. package/.claude/hooks/provider-catalog.sh +161 -0
  44. package/.claude/hooks/provider-commands.sh +2 -1
  45. package/.claude/hooks/provider-manager.sh +47 -9
  46. package/.claude/hooks/python-resolver.sh +117 -0
  47. package/.claude/hooks/session-id.sh +56 -0
  48. package/.claude/hooks/session-start-tts.sh +39 -0
  49. package/.claude/hooks/speed-manager.sh +1 -1
  50. package/.claude/hooks/translate-manager.sh +3 -2
  51. package/.claude/hooks/translator.py +1 -1
  52. package/.claude/hooks/voice-manager.sh +242 -10
  53. package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +119 -0
  54. package/.claude/hooks-windows/language-manager.ps1 +7 -1
  55. package/.claude/hooks-windows/personality-manager.ps1 +16 -1
  56. package/.claude/hooks-windows/play-tts-kokoro.ps1 +20 -4
  57. package/.claude/hooks-windows/play-tts.ps1 +32 -3
  58. package/.claude/hooks-windows/provider-catalog.ps1 +140 -0
  59. package/.claude/hooks-windows/provider-manager.ps1 +63 -8
  60. package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +153 -0
  61. package/.claude/hooks-windows/tts-watcher.ps1 +84 -8
  62. package/.claude/hooks-windows/voice-manager-windows.ps1 +49 -0
  63. package/.claude/piper-voices-dir.txt +1 -0
  64. package/.clawdbot/README.md +105 -105
  65. package/.mcp.json +15 -0
  66. package/LICENSE +190 -190
  67. package/README.md +20 -6
  68. package/RELEASE_NOTES.md +81 -0
  69. package/WINDOWS-SETUP.md +208 -208
  70. package/bin/agent-vibes +39 -39
  71. package/bin/mcp-server.js +121 -121
  72. package/bin/test-bmad-pr +78 -78
  73. package/mcp-server/QUICK_START.md +203 -203
  74. package/mcp-server/README.md +345 -345
  75. package/mcp-server/examples/claude_desktop_config.json +11 -11
  76. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  77. package/mcp-server/examples/custom_instructions.md +169 -169
  78. package/mcp-server/pyproject.toml +52 -52
  79. package/mcp-server/requirements.txt +2 -2
  80. package/mcp-server/server.py +146 -49
  81. package/mcp-server/test_mcp_correctness.py +20 -2
  82. package/mcp-server/test_server.py +395 -395
  83. package/mcp-server/test_windows_script_parity.py +0 -2
  84. package/package.json +1 -1
  85. package/src/cli/list-personalities.js +110 -110
  86. package/src/cli/list-voices.js +115 -11
  87. package/src/commands/bmad-voices.js +394 -394
  88. package/src/console/app.js +1 -9
  89. package/src/console/bling.js +71 -0
  90. package/src/console/brand-colors.js +13 -13
  91. package/src/console/constants/personalities.js +44 -44
  92. package/src/console/footer-config.js +0 -4
  93. package/src/console/music-preview.js +79 -0
  94. package/src/console/navigation.js +0 -1
  95. package/src/console/tabs/agents-tab.js +14 -2
  96. package/src/console/tabs/help-tab.js +314 -314
  97. package/src/console/tabs/music-tab.js +81 -5
  98. package/src/console/tabs/placeholder-tab.js +0 -2
  99. package/src/console/tabs/readme-tab.js +272 -272
  100. package/src/console/tabs/settings-tab.js +207 -14
  101. package/src/console/tabs/setup-tab.js +39 -36
  102. package/src/console/tabs/voices-tab.js +102 -23
  103. package/src/console/widgets/destroy-list.js +25 -25
  104. package/src/console/widgets/notice.js +55 -55
  105. package/src/console/widgets/track-picker.js +82 -0
  106. package/src/installer/language-screen.js +31 -31
  107. package/src/installer/music-file-input.js +304 -304
  108. package/src/installer.js +124 -10
  109. package/src/services/language-service.js +47 -47
  110. package/src/services/navigation-service.js +1 -1
  111. package/src/services/provider-catalog.js +412 -0
  112. package/src/services/provider-voice-catalog.js +52 -73
  113. package/src/services/tts-engine-service.js +29 -0
  114. package/src/utils/audio-format-validator.js +277 -277
  115. package/src/utils/dependency-checker.js +469 -469
  116. package/src/utils/file-ownership-verifier.js +358 -358
  117. package/src/utils/music-file-validator.js +285 -285
  118. package/src/utils/preview-list-prompt.js +144 -144
  119. package/src/utils/provider-validator.js +62 -12
  120. package/src/utils/secure-music-storage.js +412 -412
  121. package/voice-assignments.json +8244 -8244
  122. package/.claude/commands/agent-vibes/language.md +0 -23
  123. package/.claude/commands/agent-vibes/learn.md +0 -67
  124. package/.claude/commands/agent-vibes/replay-target.md +0 -14
  125. package/.claude/commands/agent-vibes/target-voice.md +0 -26
  126. package/.claude/commands/agent-vibes/target.md +0 -30
  127. package/.claude/hooks/learn-manager.sh +0 -492
  128. package/.claude/hooks/replay-target-audio.sh +0 -95
  129. package/.claude/hooks-windows/learn-manager.ps1 +0 -241
package/src/installer.js CHANGED
@@ -141,6 +141,33 @@ async function saveManifest(manifestPath, files) {
141
141
  // • dest hash == manifest hash → copy (action: 'updated')
142
142
  // • dest hash != manifest hash → skip (action: 'skipped', .user.bak saved)
143
143
  // • no manifest entry yet → copy (action: 'updated')
144
+ // Preserve destPath's CURRENT content in a sidecar backup before anything
145
+ // overwrites it. Never clobbers an existing .user.bak — that backup may hold the
146
+ // only surviving copy of an earlier customization; a second backup gets a
147
+ // content-stamped name instead. Returns true only when the current content is
148
+ // safely stored somewhere, so callers can refuse to overwrite when it isn't.
149
+ async function backupUserFile(destPath) {
150
+ const bak = `${destPath}.user.bak`;
151
+ try {
152
+ const currentHash = await computeFileHash(destPath);
153
+ if (!currentHash) return false;
154
+
155
+ const existingHash = await computeFileHash(bak);
156
+ if (existingHash === currentHash) return true; // already backed up
157
+
158
+ if (existingHash) {
159
+ // A different backup exists — keep it and stamp this one.
160
+ await fs.copyFile(destPath, `${destPath}.user.bak.${currentHash.slice(0, 8)}`);
161
+ return true;
162
+ }
163
+
164
+ await fs.copyFile(destPath, bak);
165
+ return true;
166
+ } catch {
167
+ return false;
168
+ }
169
+ }
170
+
144
171
  async function manifestSafeCopy(srcPath, destPath, manifest) {
145
172
  const srcHash = await computeFileHash(srcPath);
146
173
  if (!srcHash) return { action: 'skipped', hash: null }; // src missing
@@ -157,15 +184,29 @@ async function manifestSafeCopy(srcPath, destPath, manifest) {
157
184
  }
158
185
 
159
186
  const manifestHash = manifest[destPath]?.hash;
160
- if (manifestHash && destHash !== manifestHash) {
161
- // User modified the file since we last installed it — preserve it
162
- try { await fs.copyFile(destPath, destPath + '.user.bak'); } catch { /* best effort */ }
163
- return { action: 'skipped', hash: destHash };
187
+
188
+ if (manifestHash) {
189
+ if (destHash !== manifestHash) {
190
+ // User modified the file since we last installed it — preserve it
191
+ await backupUserFile(destPath);
192
+ return { action: 'skipped', hash: destHash };
193
+ }
194
+ // Byte-identical to what we last installed — untouched, ours to update
195
+ await fs.copyFile(srcPath, destPath);
196
+ return { action: 'updated', hash: srcHash };
164
197
  }
165
198
 
166
- // Stock file (or no manifest entry yet) safe to overwrite
199
+ // No manifest entry, and dest differs from what we ship. This is either a
200
+ // user's customized file or an older stock copy predating the manifest — the
201
+ // two are indistinguishable, so we must assume it could be a customization.
202
+ // Back it up before overwriting, and if the backup can't be secured, refuse
203
+ // to overwrite at all rather than destroy the only copy.
204
+ // (CLAUDE.md: never overwrite existing user .claude/ config.)
205
+ if (!(await backupUserFile(destPath))) {
206
+ return { action: 'skipped', hash: destHash };
207
+ }
167
208
  await fs.copyFile(srcPath, destPath);
168
- return { action: 'updated', hash: srcHash };
209
+ return { action: 'updated', hash: srcHash, backedUp: true };
169
210
  }
170
211
 
171
212
  // Delete only the files listed in manifest that reside under baseDir.
@@ -3593,14 +3634,17 @@ async function copyCommandFiles(targetDir, spinner) {
3593
3634
  */
3594
3635
  function shouldIncludeHookFile(file, stat) {
3595
3636
  if (isNativeWindows()) {
3596
- // Include .ps1 scripts, .py helpers, and hooks.json; exclude dotfiles and prepare-release
3637
+ // Include .ps1 scripts, .py helpers, hooks.json, and the generated
3638
+ // provider-catalog.json; exclude dotfiles and prepare-release
3597
3639
  return stat.isFile() &&
3598
- (file.endsWith('.ps1') || file.endsWith('.py') || file === 'hooks.json') &&
3640
+ (file.endsWith('.ps1') || file.endsWith('.py') || file === 'hooks.json' ||
3641
+ file === 'provider-catalog.json') &&
3599
3642
  !file.includes('prepare-release') &&
3600
3643
  !file.startsWith('.');
3601
3644
  }
3602
3645
  return stat.isFile() &&
3603
- (file.endsWith('.sh') || file.endsWith('.py') || file === 'hooks.json') &&
3646
+ (file.endsWith('.sh') || file.endsWith('.py') || file === 'hooks.json' ||
3647
+ file === 'provider-catalog.json') &&
3604
3648
  !file.includes('prepare-release') &&
3605
3649
  !file.startsWith('.');
3606
3650
  }
@@ -5668,6 +5712,16 @@ async function performUpdateOperations(targetDir, spinner) {
5668
5712
  spinner.text = 'Checking for old configuration...';
5669
5713
  await detectAndMigrateOldConfig(targetDir, spinner);
5670
5714
 
5715
+ // Upgrade safety for the new opt-in injection gate (session-start-tts.sh):
5716
+ // a project that was already installed and talking pre-gate has no marker, so
5717
+ // the gate would newly silence it. Backfill the enable marker for a PROJECT
5718
+ // update so existing users keep their audio. A GLOBAL (home-dir) update stays
5719
+ // opt-in — and we say so, otherwise the user's voice just stops with no clue.
5720
+ if ((await configureInjectionScope(targetDir)) === 'global') {
5721
+ console.log('');
5722
+ warnGlobalInjectionScope();
5723
+ }
5724
+
5671
5725
  return {
5672
5726
  commandCount,
5673
5727
  hookCount: hookResult.count,
@@ -5721,6 +5775,61 @@ async function updateAgentVibes(targetDir, options) {
5721
5775
  }
5722
5776
  }
5723
5777
 
5778
+ /**
5779
+ * Configure the session-start injection opt-in marker for this install/update.
5780
+ *
5781
+ * - PROJECT install → drop `.claude/agentvibes-enabled` so session-start-tts.sh
5782
+ * injects the TTS protocol for THIS project (and only this one), preserving the
5783
+ * "install in a project and it just works" experience.
5784
+ * - GLOBAL install (target === home dir) → write nothing; injecting into every
5785
+ * session is exactly the "cacophony of agents" we are preventing.
5786
+ *
5787
+ * Uses `agentvibes-enabled`, NOT `agentvibes-unmuted`, on purpose: the unmuted
5788
+ * marker OVERRIDES a global mute in play-tts.sh, so writing it here would let an
5789
+ * install/update silently defeat a user's `~/.agentvibes-muted` kill-switch.
5790
+ * Non-destructive: never touches an existing mute/unmute/enabled choice.
5791
+ *
5792
+ * @param {string} targetDir - install target
5793
+ * @returns {Promise<'global'|'enabled'|'kept'|'error'>} what happened (for messaging)
5794
+ */
5795
+ async function configureInjectionScope(targetDir) {
5796
+ try {
5797
+ // path.relative is case-insensitive on win32 and normalizes separators, so a
5798
+ // lowercase drive letter or trailing slash no longer misdetects a home
5799
+ // install; realpath (best-effort) also collapses a symlinked $HOME.
5800
+ let resolvedTarget = path.resolve(targetDir);
5801
+ let resolvedHome = path.resolve(os.homedir());
5802
+ try { resolvedTarget = fsSync.realpathSync.native(resolvedTarget); } catch { /* dir may not exist yet */ }
5803
+ try { resolvedHome = fsSync.realpathSync.native(resolvedHome); } catch { /* ignore */ }
5804
+ if (path.relative(resolvedHome, resolvedTarget) === '') return 'global';
5805
+
5806
+ const claudeDir = path.join(targetDir, '.claude');
5807
+ // Respect any prior explicit choice — enabled OR either mute/unmute marker.
5808
+ for (const m of ['agentvibes-enabled', 'agentvibes-unmuted', 'agentvibes-muted']) {
5809
+ if (await fs.access(path.join(claudeDir, m)).then(() => true).catch(() => false)) {
5810
+ return 'kept';
5811
+ }
5812
+ }
5813
+ await fs.mkdir(claudeDir, { recursive: true });
5814
+ await fs.writeFile(path.join(claudeDir, 'agentvibes-enabled'), '', 'utf8');
5815
+ return 'enabled';
5816
+ } catch (err) {
5817
+ // Never fail install/update over the marker, but don't swallow silently —
5818
+ // a read-only FS here would otherwise produce a mysterious "silent" project.
5819
+ console.log(chalk.gray(` (Could not set TTS injection marker: ${err.code || err.message}. Run /agent-vibes:unmute to enable.)`));
5820
+ return 'error';
5821
+ }
5822
+ }
5823
+
5824
+ // Print the "global install stays silent" guidance (shared by install + update).
5825
+ function warnGlobalInjectionScope() {
5826
+ console.log(chalk.yellow.bold(' ⚠ Global install detected (installed at your home directory)'));
5827
+ console.log(chalk.gray(' TTS stays OFF by default so it will NOT talk in every session at once.'));
5828
+ console.log(chalk.gray(' Enable it in a specific project with: ') + chalk.cyan('/agent-vibes:unmute'));
5829
+ console.log(chalk.gray(' (or re-run the installer inside a project folder, not your home dir).'));
5830
+ console.log('');
5831
+ }
5832
+
5724
5833
  // Installation function
5725
5834
  async function install(options = {}) {
5726
5835
  const currentDir = process.env.INIT_CWD || process.cwd();
@@ -6243,6 +6352,11 @@ def _strip_markdown(text: str) -> str:
6243
6352
  console.log(chalk.magenta(' \u2661 Sponsor this Developer github.com/sponsors/paulpreibisch'));
6244
6353
  console.log('');
6245
6354
 
6355
+ // Opt-in injection marker (prevents the global-install cacophony). See
6356
+ // configureInjectionScope: project installs enable THIS project; a home-dir
6357
+ // install stays silent. Uses agentvibes-enabled so it can't override a global mute.
6358
+ if ((await configureInjectionScope(targetDir)) === 'global') warnGlobalInjectionScope();
6359
+
6246
6360
  if (!(options.nonInteractive || process.env.AGENT_VIBES_NON_INTERACTIVE === '1')) {
6247
6361
  // Clean final summary
6248
6362
  console.log('');
@@ -6997,7 +7111,7 @@ export {
6997
7111
  CRITICAL_HOOKS, CRITICAL_HOOKS_WINDOWS,
6998
7112
  // Manifest utilities (used by tests and external tooling)
6999
7113
  getProjectManifestPath, getGlobalManifestPath,
7000
- loadManifest, saveManifest, computeFileHash, manifestSafeCopy, removeManifestFiles,
7114
+ loadManifest, saveManifest, computeFileHash, manifestSafeCopy, backupUserFile, removeManifestFiles,
7001
7115
  // Pure helper functions exported for testing
7002
7116
  readJsonConfigSafe, backupConfigFile,
7003
7117
  isPiperProvider, supportsEmoji, getPersonalityIcon,
@@ -1,47 +1,47 @@
1
- /**
2
- * LanguageService — single source of truth for the selected UI language.
3
- *
4
- * Persists the selection to ~/.claude/config/language.txt so it survives
5
- * process restarts. Notifies registered listeners on every change so the
6
- * TUI can re-render dynamic labels immediately.
7
- */
8
-
9
- import fs from 'node:fs';
10
- import path from 'node:path';
11
- import os from 'node:os';
12
- import { t, SUPPORTED_LANGUAGES } from '../i18n/strings.js';
13
-
14
- const LANG_FILE = path.join(os.homedir(), '.claude', 'config', 'language.txt');
15
- const VALID_LANGS = new Set(SUPPORTED_LANGUAGES.map(l => l.value));
16
-
17
- export class LanguageService {
18
- constructor() {
19
- this._lang = this._load();
20
- this._listeners = [];
21
- }
22
-
23
- _load() {
24
- try {
25
- const val = fs.readFileSync(LANG_FILE, 'utf8').trim();
26
- return VALID_LANGS.has(val) ? val : 'en';
27
- } catch {
28
- return 'en';
29
- }
30
- }
31
-
32
- getLang() { return this._lang; }
33
-
34
- setLang(lang) {
35
- if (!VALID_LANGS.has(lang)) return;
36
- this._lang = lang;
37
- try {
38
- fs.mkdirSync(path.dirname(LANG_FILE), { recursive: true });
39
- fs.writeFileSync(LANG_FILE, lang, { mode: 0o600 });
40
- } catch { /* non-fatal */ }
41
- this._listeners.forEach(fn => fn(lang));
42
- }
43
-
44
- onChange(fn) { this._listeners.push(fn); }
45
-
46
- t(key) { return t(this._lang, key); }
47
- }
1
+ /**
2
+ * LanguageService — single source of truth for the selected UI language.
3
+ *
4
+ * Persists the selection to ~/.claude/config/language.txt so it survives
5
+ * process restarts. Notifies registered listeners on every change so the
6
+ * TUI can re-render dynamic labels immediately.
7
+ */
8
+
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import os from 'node:os';
12
+ import { t, SUPPORTED_LANGUAGES } from '../i18n/strings.js';
13
+
14
+ const LANG_FILE = path.join(os.homedir(), '.claude', 'config', 'language.txt');
15
+ const VALID_LANGS = new Set(SUPPORTED_LANGUAGES.map(l => l.value));
16
+
17
+ export class LanguageService {
18
+ constructor() {
19
+ this._lang = this._load();
20
+ this._listeners = [];
21
+ }
22
+
23
+ _load() {
24
+ try {
25
+ const val = fs.readFileSync(LANG_FILE, 'utf8').trim();
26
+ return VALID_LANGS.has(val) ? val : 'en';
27
+ } catch {
28
+ return 'en';
29
+ }
30
+ }
31
+
32
+ getLang() { return this._lang; }
33
+
34
+ setLang(lang) {
35
+ if (!VALID_LANGS.has(lang)) return;
36
+ this._lang = lang;
37
+ try {
38
+ fs.mkdirSync(path.dirname(LANG_FILE), { recursive: true });
39
+ fs.writeFileSync(LANG_FILE, lang, { mode: 0o600 });
40
+ } catch { /* non-fatal */ }
41
+ this._listeners.forEach(fn => fn(lang));
42
+ }
43
+
44
+ onChange(fn) { this._listeners.push(fn); }
45
+
46
+ t(key) { return t(this._lang, key); }
47
+ }
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  /** Ordered list of all tab IDs — used for cycling and validation */
10
- export const TAB_ORDER = ['setup', 'settings', 'voices', 'music', 'agents', 'receiver', 'readme', 'help'];
10
+ export const TAB_ORDER = ['setup', 'settings', 'music', 'agents', 'receiver', 'readme', 'help'];
11
11
 
12
12
  export class NavigationService {
13
13
  /**