agentvibes 5.11.2 → 5.12.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.
- package/.claude/config/audio-effects.cfg +1 -1
- package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/audio-processor.sh +1 -1
- package/.claude/hooks/background-music-manager.sh +2 -1
- package/.claude/hooks/bmad-speak-enhanced.sh +37 -22
- package/.claude/hooks/bmad-speak.sh +26 -8
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +1 -1
- package/.claude/hooks/play-tts-kokoro.sh +9 -0
- package/.claude/hooks/play-tts-piper.sh +13 -1
- package/.claude/hooks/play-tts-ssh-remote.sh +117 -7
- package/.claude/hooks/play-tts-termux-ssh.sh +1 -1
- package/.claude/hooks/play-tts.sh +106 -13
- package/.claude/hooks-windows/background-music-manager.ps1 +2 -1
- package/.claude/hooks-windows/play-tts-kokoro.ps1 +14 -0
- package/.claude/hooks-windows/play-tts.ps1 +163 -30
- package/.claude/hooks-windows/tts-watcher.ps1 +55 -0
- package/.claude/hooks-windows/voice-manager-windows.ps1 +101 -1
- package/README.md +11 -6
- package/RELEASE_NOTES.md +67 -0
- package/bin/mcp-server.sh +6 -19
- package/bin/resolve-utterance.js +137 -0
- package/mcp-server/server.py +280 -99
- package/mcp-server/test_mcp_correctness.py +486 -0
- package/mcp-server/test_windows_script_parity.py +341 -336
- package/package.json +3 -2
- package/setup-windows.ps1 +867 -815
- package/src/console/app.js +14 -11
- package/src/console/footer-config.js +0 -4
- package/src/console/navigation.js +0 -1
- package/src/console/tabs/agents-tab.js +96 -11
- package/src/console/tabs/music-tab.js +108 -3
- package/src/console/tabs/placeholder-tab.js +0 -2
- package/src/console/tabs/settings-tab.js +41 -2
- package/src/console/tabs/setup-tab.js +34 -6
- package/src/console/tabs/voices-tab.js +19 -9
- package/src/console/widgets/track-picker.js +3 -3
- package/src/i18n/de.js +0 -1
- package/src/i18n/en.js +0 -1
- package/src/i18n/es.js +0 -1
- package/src/i18n/fr.js +0 -1
- package/src/i18n/hi.js +0 -1
- package/src/i18n/ja.js +0 -1
- package/src/i18n/ko.js +0 -1
- package/src/i18n/pt.js +0 -1
- package/src/i18n/zh-CN.js +0 -1
- package/src/installer.js +205 -15
- package/src/services/config-service.js +264 -264
- package/src/services/llm-provider-service.js +7 -7
- package/src/services/navigation-service.js +1 -1
- package/src/services/utterance-loader.js +280 -0
- package/src/services/utterance-resolver.js +526 -0
- package/templates/agentvibes-receiver.sh +74 -12
- package/voice-assignments.json +8245 -0
package/src/i18n/de.js
CHANGED
|
@@ -143,7 +143,6 @@ export default {
|
|
|
143
143
|
voicesSwitchBtn: "[Stimme wechseln]",
|
|
144
144
|
voicesFavoriteBtn: "[★ Favorit]",
|
|
145
145
|
voicesDownloadBtn: "[Stimme herunterladen]",
|
|
146
|
-
voicesInstallAllBtn: "[BMAD-Stimmen installieren]",
|
|
147
146
|
voicesRowHintInstalled: "[Space] Vorschau [Enter] Auswählen [*] Favorit",
|
|
148
147
|
voicesRowHintUninstalled: "[Enter] Herunterladen und installieren",
|
|
149
148
|
musicRowHint: "[Space] Abspielen [Enter] Auswählen [*] Favorit",
|
package/src/i18n/en.js
CHANGED
|
@@ -143,7 +143,6 @@ export default {
|
|
|
143
143
|
voicesSwitchBtn: "[Switch Voice]",
|
|
144
144
|
voicesFavoriteBtn: "[+ Thumbs Up]",
|
|
145
145
|
voicesDownloadBtn: "[Download Voice]",
|
|
146
|
-
voicesInstallAllBtn: "[Install BMAD Voices]",
|
|
147
146
|
voicesRowHintInstalled: "[Space] Preview [Enter] Select [+/*] Up [-] Down",
|
|
148
147
|
voicesRowHintUninstalled: "[Enter] Download & Install",
|
|
149
148
|
musicRowHint: "[Space] Play [Enter] Select [+/*] Favorite",
|
package/src/i18n/es.js
CHANGED
|
@@ -143,7 +143,6 @@ export default {
|
|
|
143
143
|
voicesSwitchBtn: "[Cambiar Voz]",
|
|
144
144
|
voicesFavoriteBtn: "[★ Favorito]",
|
|
145
145
|
voicesDownloadBtn: "[Descargar Voz]",
|
|
146
|
-
voicesInstallAllBtn: "[Instalar Voces BMAD]",
|
|
147
146
|
voicesRowHintInstalled: "[Space] Vista previa [Enter] Seleccionar [*] Favorito",
|
|
148
147
|
voicesRowHintUninstalled: "[Enter] Descargar e instalar",
|
|
149
148
|
musicRowHint: "[Space] Reproducir [Enter] Seleccionar [*] Favorito",
|
package/src/i18n/fr.js
CHANGED
|
@@ -143,7 +143,6 @@ export default {
|
|
|
143
143
|
voicesSwitchBtn: "[Changer la voix]",
|
|
144
144
|
voicesFavoriteBtn: "[★ Favori]",
|
|
145
145
|
voicesDownloadBtn: "[Télécharger la voix]",
|
|
146
|
-
voicesInstallAllBtn: "[Installer les voix BMAD]",
|
|
147
146
|
voicesRowHintInstalled: "[Space] Aperçu [Enter] Sélectionner [*] Favori",
|
|
148
147
|
voicesRowHintUninstalled: "[Enter] Télécharger et installer",
|
|
149
148
|
musicRowHint: "[Space] Lire [Enter] Sélectionner [*] Favori",
|
package/src/i18n/hi.js
CHANGED
|
@@ -143,7 +143,6 @@ export default {
|
|
|
143
143
|
voicesSwitchBtn: "[आवाज़ बदलें]",
|
|
144
144
|
voicesFavoriteBtn: "[★ पसंदीदा]",
|
|
145
145
|
voicesDownloadBtn: "[आवाज़ डाउनलोड]",
|
|
146
|
-
voicesInstallAllBtn: "[BMAD आवाज़ें इंस्टॉल करें]",
|
|
147
146
|
voicesRowHintInstalled: "[Space] पूर्वावलोकन [Enter] चुनें [*] पसंदीदा",
|
|
148
147
|
voicesRowHintUninstalled: "[Enter] डाउनलोड और इंस्टॉल करें",
|
|
149
148
|
musicRowHint: "[Space] चलाएं [Enter] चुनें [*] पसंदीदा",
|
package/src/i18n/ja.js
CHANGED
|
@@ -143,7 +143,6 @@ export default {
|
|
|
143
143
|
voicesSwitchBtn: "[音声切り替え]",
|
|
144
144
|
voicesFavoriteBtn: "[★ お気に入り]",
|
|
145
145
|
voicesDownloadBtn: "[音声ダウンロード]",
|
|
146
|
-
voicesInstallAllBtn: "[BMADボイスをインストール]",
|
|
147
146
|
voicesRowHintInstalled: "[Space] プレビュー [Enter] 選択 [*] お気に入り",
|
|
148
147
|
voicesRowHintUninstalled: "[Enter] ダウンロードしてインストール",
|
|
149
148
|
musicRowHint: "[Space] 再生 [Enter] 選択 [*] お気に入り",
|
package/src/i18n/ko.js
CHANGED
|
@@ -143,7 +143,6 @@ export default {
|
|
|
143
143
|
voicesSwitchBtn: "[음성 전환]",
|
|
144
144
|
voicesFavoriteBtn: "[★ 즐겨찾기]",
|
|
145
145
|
voicesDownloadBtn: "[음성 다운로드]",
|
|
146
|
-
voicesInstallAllBtn: "[BMAD 음성 설치]",
|
|
147
146
|
voicesRowHintInstalled: "[Space] 미리보기 [Enter] 선택 [*] 즐겨찾기",
|
|
148
147
|
voicesRowHintUninstalled: "[Enter] 다운로드 및 설치",
|
|
149
148
|
musicRowHint: "[Space] 재생 [Enter] 선택 [*] 즐겨찾기",
|
package/src/i18n/pt.js
CHANGED
|
@@ -143,7 +143,6 @@ export default {
|
|
|
143
143
|
voicesSwitchBtn: "[Trocar Voz]",
|
|
144
144
|
voicesFavoriteBtn: "[★ Favorito]",
|
|
145
145
|
voicesDownloadBtn: "[Baixar Voz]",
|
|
146
|
-
voicesInstallAllBtn: "[Instalar Vozes BMAD]",
|
|
147
146
|
voicesRowHintInstalled: "[Space] Visualizar [Enter] Selecionar [*] Favorito",
|
|
148
147
|
voicesRowHintUninstalled: "[Enter] Baixar e instalar",
|
|
149
148
|
musicRowHint: "[Space] Reproduzir [Enter] Selecionar [*] Favorito",
|
package/src/i18n/zh-CN.js
CHANGED
|
@@ -143,7 +143,6 @@ export default {
|
|
|
143
143
|
voicesSwitchBtn: "[切换语音]",
|
|
144
144
|
voicesFavoriteBtn: "[★ 收藏]",
|
|
145
145
|
voicesDownloadBtn: "[下载语音]",
|
|
146
|
-
voicesInstallAllBtn: "[安装BMAD语音]",
|
|
147
146
|
voicesRowHintInstalled: "[Space] 预览 [Enter] 选择 [*] 收藏",
|
|
148
147
|
voicesRowHintUninstalled: "[Enter] 下载并安装",
|
|
149
148
|
musicRowHint: "[Space] 播放 [Enter] 选择 [*] 收藏",
|
package/src/installer.js
CHANGED
|
@@ -4176,6 +4176,52 @@ async function copyCodexFiles(targetDir, spinner) {
|
|
|
4176
4176
|
* @param {string} targetDir - Target installation directory
|
|
4177
4177
|
* @param {Object} spinner - Ora spinner instance
|
|
4178
4178
|
*/
|
|
4179
|
+
/**
|
|
4180
|
+
* Safely read a JSON settings file that is about to be modified in place.
|
|
4181
|
+
*
|
|
4182
|
+
* Non-Destructive Configuration Rule: a corrupt settings.json (e.g. one stray
|
|
4183
|
+
* trailing comma) must NEVER be silently overwritten — doing so wipes all of the
|
|
4184
|
+
* user's real settings. This helper distinguishes three cases so callers can
|
|
4185
|
+
* skip-and-warn on corruption instead of clobbering:
|
|
4186
|
+
* - missing / empty -> { exists:false, corrupt:false } (safe to create fresh)
|
|
4187
|
+
* - present + valid -> { exists:true, corrupt:false, data }
|
|
4188
|
+
* - present + invalid -> { exists:true, corrupt:true } (caller must NOT write)
|
|
4189
|
+
* @param {string} filePath
|
|
4190
|
+
* @returns {Promise<{exists:boolean, corrupt:boolean, data:object}>}
|
|
4191
|
+
*/
|
|
4192
|
+
async function readJsonConfigSafe(filePath) {
|
|
4193
|
+
let raw;
|
|
4194
|
+
try {
|
|
4195
|
+
raw = await fs.readFile(filePath, 'utf8');
|
|
4196
|
+
} catch (err) {
|
|
4197
|
+
if (err && err.code === 'ENOENT') return { exists: false, corrupt: false, data: {} };
|
|
4198
|
+
// Unreadable for another reason (permissions, etc.) — treat as present+corrupt
|
|
4199
|
+
// so we never overwrite a file we could not fully read.
|
|
4200
|
+
return { exists: true, corrupt: true, data: {} };
|
|
4201
|
+
}
|
|
4202
|
+
if (raw.trim() === '') return { exists: false, corrupt: false, data: {} };
|
|
4203
|
+
try {
|
|
4204
|
+
const parsed = JSON.parse(raw);
|
|
4205
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
4206
|
+
return { exists: true, corrupt: true, data: {} };
|
|
4207
|
+
}
|
|
4208
|
+
return { exists: true, corrupt: false, data: parsed };
|
|
4209
|
+
} catch {
|
|
4210
|
+
return { exists: true, corrupt: true, data: {} };
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
/**
|
|
4215
|
+
* Best-effort backup of a config file before mutating it. Writes `<file>.bak`.
|
|
4216
|
+
* A backup failure is non-fatal (callers only mutate files that parsed cleanly).
|
|
4217
|
+
* @param {string} filePath
|
|
4218
|
+
*/
|
|
4219
|
+
async function backupConfigFile(filePath) {
|
|
4220
|
+
try {
|
|
4221
|
+
await fs.copyFile(filePath, filePath + '.bak');
|
|
4222
|
+
} catch { /* best-effort backup — never block the install on this */ }
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4179
4225
|
async function configureSessionStartHook(targetDir, spinner) {
|
|
4180
4226
|
spinner = createRobustSpinner(spinner);
|
|
4181
4227
|
spinner.start('Configuring AgentVibes hook for automatic TTS...');
|
|
@@ -4184,13 +4230,15 @@ async function configureSessionStartHook(targetDir, spinner) {
|
|
|
4184
4230
|
const templateSettingsPath = path.join(__dirname, '..', '.claude', 'settings.json');
|
|
4185
4231
|
|
|
4186
4232
|
try {
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4233
|
+
const existing = await readJsonConfigSafe(settingsPath);
|
|
4234
|
+
if (existing.corrupt) {
|
|
4235
|
+
spinner.warn(chalk.yellow(
|
|
4236
|
+
`settings.json at ${settingsPath} is not valid JSON — leaving it untouched to avoid data loss.\n` +
|
|
4237
|
+
` Fix the file manually and re-run to enable the AgentVibes hook.\n`
|
|
4238
|
+
));
|
|
4239
|
+
return;
|
|
4193
4240
|
}
|
|
4241
|
+
const existingSettings = existing.data;
|
|
4194
4242
|
|
|
4195
4243
|
const templateContent = await fs.readFile(templateSettingsPath, 'utf8');
|
|
4196
4244
|
const templateSettings = JSON.parse(templateContent);
|
|
@@ -4216,6 +4264,7 @@ async function configureSessionStartHook(targetDir, spinner) {
|
|
|
4216
4264
|
existingSettings.$schema = templateSettings.$schema;
|
|
4217
4265
|
}
|
|
4218
4266
|
|
|
4267
|
+
if (existing.exists) await backupConfigFile(settingsPath);
|
|
4219
4268
|
await fs.writeFile(settingsPath, JSON.stringify(existingSettings, null, 2));
|
|
4220
4269
|
spinner.succeed(chalk.green('SessionStart hook configured!\n'));
|
|
4221
4270
|
} else {
|
|
@@ -4260,14 +4309,17 @@ async function configurePartyModeHook(targetDir, spinner, homeDirOverride) {
|
|
|
4260
4309
|
? `powershell -NoProfile -ExecutionPolicy Bypass -File "$HOME\\.claude\\hooks-windows\\bmad-party-speak.ps1"`
|
|
4261
4310
|
: `bash "$HOME/.claude/hooks/bmad-party-speak.sh"`;
|
|
4262
4311
|
|
|
4263
|
-
// Read/create global settings.json
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4312
|
+
// Read/create global settings.json — never overwrite a corrupt file (would
|
|
4313
|
+
// wipe the user's real global settings).
|
|
4314
|
+
const existingGlobal = await readJsonConfigSafe(globalSettingsPath);
|
|
4315
|
+
if (existingGlobal.corrupt) {
|
|
4316
|
+
spinner.warn(chalk.yellow(
|
|
4317
|
+
`Global settings.json at ${globalSettingsPath} is not valid JSON — leaving it untouched.\n` +
|
|
4318
|
+
` Fix it manually and re-run to enable party mode TTS.\n`
|
|
4319
|
+
));
|
|
4320
|
+
return;
|
|
4270
4321
|
}
|
|
4322
|
+
let settings = existingGlobal.data;
|
|
4271
4323
|
|
|
4272
4324
|
if (!settings.hooks) settings.hooks = {};
|
|
4273
4325
|
|
|
@@ -4286,6 +4338,7 @@ async function configurePartyModeHook(targetDir, spinner, homeDirOverride) {
|
|
|
4286
4338
|
settings.hooks.PostToolUse.push({
|
|
4287
4339
|
hooks: [{ type: 'command', command: hookCommand }]
|
|
4288
4340
|
});
|
|
4341
|
+
if (existingGlobal.exists) await backupConfigFile(globalSettingsPath);
|
|
4289
4342
|
await fs.writeFile(globalSettingsPath, JSON.stringify(settings, null, 2));
|
|
4290
4343
|
spinner.succeed(chalk.green('BMAD party mode TTS hook configured!\n'));
|
|
4291
4344
|
} else {
|
|
@@ -4671,7 +4724,7 @@ async function checkAndInstallPiperWindows(targetDir, options) {
|
|
|
4671
4724
|
}
|
|
4672
4725
|
console.log(chalk.green('\n✅ LibriTTS voices ready! Browse with /agent-vibes:list\n'));
|
|
4673
4726
|
} else {
|
|
4674
|
-
console.log(chalk.gray(' Skipped. Download later via: npx agentvibes
|
|
4727
|
+
console.log(chalk.gray(' Skipped. Download later via: npx agentvibes voices download\n'));
|
|
4675
4728
|
}
|
|
4676
4729
|
}
|
|
4677
4730
|
}
|
|
@@ -5356,6 +5409,124 @@ async function updateGlobalHooks(srcHooksDir, homeDirOverride) {
|
|
|
5356
5409
|
return updated;
|
|
5357
5410
|
}
|
|
5358
5411
|
|
|
5412
|
+
/**
|
|
5413
|
+
* Story AVI-S8.5 (Stage 2 packaging) — ship the utterance resolver bundle
|
|
5414
|
+
* (bin/resolve-utterance.js + src/services/{utterance-resolver,utterance-loader}.js)
|
|
5415
|
+
* into a target `.claude` tree so the play-tts hooks can find it.
|
|
5416
|
+
*
|
|
5417
|
+
* Layout (F-5): a SELF-CONTAINED bundle dir `<claudeDir>/agentvibes-resolver/`:
|
|
5418
|
+
* agentvibes-resolver/
|
|
5419
|
+
* package.json -> {"type":"module"}
|
|
5420
|
+
* bin/resolve-utterance.js
|
|
5421
|
+
* src/services/utterance-resolver.js
|
|
5422
|
+
* src/services/utterance-loader.js
|
|
5423
|
+
* The bundle's OWN package.json declares ESM, so `node resolve-utterance.js`
|
|
5424
|
+
* works on EVERY Node version (the CLI uses `import`; without this, Node < 22.7
|
|
5425
|
+
* treats the copied .js as CommonJS and the import throws → players silently fall
|
|
5426
|
+
* back to legacy forever). It lives in a dedicated dir, NOT in `<claudeDir>/hooks/`,
|
|
5427
|
+
* so it can't change the module type of any user-owned `.js` hook. The players
|
|
5428
|
+
* probe `$SCRIPT_DIR/../agentvibes-resolver/bin/resolve-utterance.js`; its
|
|
5429
|
+
* `../src/services/...` imports resolve inside the bundle dir.
|
|
5430
|
+
*
|
|
5431
|
+
* This is AgentVibes-owned code — same class as the hook scripts themselves —
|
|
5432
|
+
* so copy/overwrite on update is correct. manifestSafeCopy still refuses to
|
|
5433
|
+
* clobber a user-modified copy (saves a .user.bak) and running this twice is
|
|
5434
|
+
* a no-op on the second pass (Non-Destructive Configuration Rule).
|
|
5435
|
+
*
|
|
5436
|
+
* @param {string} claudeDir - Absolute path to the target `.claude` directory
|
|
5437
|
+
* @param {Object} manifest - Loaded manifest (hash lookups for safe-copy decisions)
|
|
5438
|
+
* @param {Object} manifestUpdates - Mutated in place with new hash entries
|
|
5439
|
+
* @returns {Promise<number>} Count of files actually copied/updated this run
|
|
5440
|
+
*/
|
|
5441
|
+
async function copyResolverBundleTo(claudeDir, manifest, manifestUpdates) {
|
|
5442
|
+
const pkgRoot = path.join(__dirname, '..');
|
|
5443
|
+
const bundleDir = path.join(claudeDir, 'agentvibes-resolver');
|
|
5444
|
+
const srcBin = path.join(pkgRoot, 'bin', 'resolve-utterance.js');
|
|
5445
|
+
const srcResolver = path.join(pkgRoot, 'src', 'services', 'utterance-resolver.js');
|
|
5446
|
+
const srcLoader = path.join(pkgRoot, 'src', 'services', 'utterance-loader.js');
|
|
5447
|
+
|
|
5448
|
+
const targets = [
|
|
5449
|
+
{ src: srcBin, dest: path.join(bundleDir, 'bin', 'resolve-utterance.js') },
|
|
5450
|
+
{ src: srcResolver, dest: path.join(bundleDir, 'src', 'services', 'utterance-resolver.js') },
|
|
5451
|
+
{ src: srcLoader, dest: path.join(bundleDir, 'src', 'services', 'utterance-loader.js') },
|
|
5452
|
+
];
|
|
5453
|
+
|
|
5454
|
+
let count = 0;
|
|
5455
|
+
for (const { src, dest } of targets) {
|
|
5456
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
5457
|
+
try {
|
|
5458
|
+
const result = await manifestSafeCopy(src, dest, manifest);
|
|
5459
|
+
if (result.action !== 'skipped') {
|
|
5460
|
+
if (result.hash) manifestUpdates[dest] = { hash: result.hash, installedAt: new Date().toISOString() };
|
|
5461
|
+
if (result.action !== 'unchanged') count++;
|
|
5462
|
+
}
|
|
5463
|
+
} catch {
|
|
5464
|
+
// src missing (e.g. stripped package) — skip silently, players fall back to legacy
|
|
5465
|
+
}
|
|
5466
|
+
}
|
|
5467
|
+
|
|
5468
|
+
// The ESM marker — a fixed one-liner (not copied from a src file). Write it
|
|
5469
|
+
// only if absent or different, so a second run is a no-op.
|
|
5470
|
+
try {
|
|
5471
|
+
const pkgJsonPath = path.join(bundleDir, 'package.json');
|
|
5472
|
+
const desired = JSON.stringify({ type: 'module', private: true, name: 'agentvibes-resolver-bundle' }, null, 2) + '\n';
|
|
5473
|
+
let existing = null;
|
|
5474
|
+
try { existing = await fs.readFile(pkgJsonPath, 'utf8'); } catch { /* absent */ }
|
|
5475
|
+
if (existing !== desired) {
|
|
5476
|
+
await fs.mkdir(bundleDir, { recursive: true });
|
|
5477
|
+
await fs.writeFile(pkgJsonPath, desired);
|
|
5478
|
+
count++;
|
|
5479
|
+
}
|
|
5480
|
+
} catch {
|
|
5481
|
+
// best-effort — if this fails the players fall back to legacy, never break
|
|
5482
|
+
}
|
|
5483
|
+
return count;
|
|
5484
|
+
}
|
|
5485
|
+
|
|
5486
|
+
/**
|
|
5487
|
+
* Install/update the resolver bundle into a project-local (or explicitly
|
|
5488
|
+
* targeted) `.claude` directory. Mirrors copyHookFiles' manifest handling.
|
|
5489
|
+
* @param {string} targetDir - Target installation directory (parent of .claude/)
|
|
5490
|
+
* @param {Object} spinner - Ora spinner instance
|
|
5491
|
+
* @returns {Promise<{count: number}>} Count of files copied/updated
|
|
5492
|
+
*/
|
|
5493
|
+
async function copyResolverBundle(targetDir, spinner) {
|
|
5494
|
+
spinner = createRobustSpinner(spinner);
|
|
5495
|
+
spinner.start('Installing utterance resolver bundle...');
|
|
5496
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
5497
|
+
const manifestPath = getProjectManifestPath(targetDir);
|
|
5498
|
+
const manifest = await loadManifest(manifestPath);
|
|
5499
|
+
const manifestUpdates = {};
|
|
5500
|
+
|
|
5501
|
+
const count = await copyResolverBundleTo(claudeDir, manifest, manifestUpdates);
|
|
5502
|
+
|
|
5503
|
+
if (Object.keys(manifestUpdates).length > 0) {
|
|
5504
|
+
await saveManifest(manifestPath, { ...manifest, ...manifestUpdates }).catch(() => { /* best effort */ });
|
|
5505
|
+
}
|
|
5506
|
+
spinner.succeed(chalk.green('Installed utterance resolver bundle!\n'));
|
|
5507
|
+
return { count };
|
|
5508
|
+
}
|
|
5509
|
+
|
|
5510
|
+
/**
|
|
5511
|
+
* Keep the resolver bundle up to date in the global ~/.claude/ tree on every
|
|
5512
|
+
* `npx agentvibes update`, regardless of targetDir — mirrors updateGlobalHooks()
|
|
5513
|
+
* since most real installs point their hooks at $HOME, not a project dir.
|
|
5514
|
+
* @param {string} [homeDirOverride] - Override home dir (for testing only)
|
|
5515
|
+
* @returns {Promise<number>} Count of files copied/updated
|
|
5516
|
+
*/
|
|
5517
|
+
async function updateGlobalResolverBundle(homeDirOverride) {
|
|
5518
|
+
const homeDir = homeDirOverride || os.homedir();
|
|
5519
|
+
const claudeDir = path.join(homeDir, '.claude');
|
|
5520
|
+
const manifestPath = getGlobalManifestPath(homeDir);
|
|
5521
|
+
const manifest = await loadManifest(manifestPath);
|
|
5522
|
+
const manifestUpdates = { ...manifest };
|
|
5523
|
+
|
|
5524
|
+
const count = await copyResolverBundleTo(claudeDir, manifest, manifestUpdates);
|
|
5525
|
+
|
|
5526
|
+
await saveManifest(manifestPath, manifestUpdates).catch(() => { /* best effort */ });
|
|
5527
|
+
return count;
|
|
5528
|
+
}
|
|
5529
|
+
|
|
5359
5530
|
/**
|
|
5360
5531
|
* Restart the AgentVibes TTS queue watcher on Windows after an update.
|
|
5361
5532
|
* Only runs if the watcher is already installed (~/.agentvibes/tts-watcher.ps1 exists),
|
|
@@ -5378,7 +5549,9 @@ async function restartWatcherIfInstalled(homeDirOverride) {
|
|
|
5378
5549
|
return false;
|
|
5379
5550
|
}
|
|
5380
5551
|
|
|
5381
|
-
|
|
5552
|
+
// spawn/spawnSync come from the top-level ESM import (line 48). A require()
|
|
5553
|
+
// here throws "require is not defined" in this "type":"module" package and
|
|
5554
|
+
// aborted `agentvibes update` mid-run for every SSH-receiver user.
|
|
5382
5555
|
|
|
5383
5556
|
// Kill old watcher — use array args to avoid quoting issues
|
|
5384
5557
|
spawnSync('powershell.exe', [ // NOSONAR
|
|
@@ -5427,6 +5600,12 @@ async function performUpdateOperations(targetDir, spinner) {
|
|
|
5427
5600
|
const hookResult = await copyHookFiles(targetDir, silentSpinner);
|
|
5428
5601
|
console.log(chalk.green(`✓ Updated ${hookResult.count} TTS scripts`));
|
|
5429
5602
|
|
|
5603
|
+
// Update the utterance resolver bundle alongside the hooks it feeds (AVI-S8.5 Stage 2)
|
|
5604
|
+
const resolverResult = await copyResolverBundle(targetDir, silentSpinner);
|
|
5605
|
+
if (resolverResult.count > 0) {
|
|
5606
|
+
console.log(chalk.green(`✓ Updated ${resolverResult.count} resolver bundle files`));
|
|
5607
|
+
}
|
|
5608
|
+
|
|
5430
5609
|
// Also update critical hooks in global ~/.claude/hooks/ if present (fixes stale installs)
|
|
5431
5610
|
const hooksSubdir = isNativeWindows() ? 'hooks-windows' : 'hooks';
|
|
5432
5611
|
const srcHooksDir = path.join(__dirname, '..', '.claude', hooksSubdir);
|
|
@@ -5435,6 +5614,12 @@ async function performUpdateOperations(targetDir, spinner) {
|
|
|
5435
5614
|
console.log(chalk.green(`✓ Updated ${globalHooksUpdated} critical scripts in ~/.claude/hooks/`));
|
|
5436
5615
|
}
|
|
5437
5616
|
|
|
5617
|
+
// Keep the global ~/.claude/ resolver bundle current too (mirrors updateGlobalHooks)
|
|
5618
|
+
const globalResolverUpdated = await updateGlobalResolverBundle();
|
|
5619
|
+
if (globalResolverUpdated > 0) {
|
|
5620
|
+
console.log(chalk.green(`✓ Updated ${globalResolverUpdated} resolver bundle files in ~/.claude/`));
|
|
5621
|
+
}
|
|
5622
|
+
|
|
5438
5623
|
// On Windows: restart the TTS queue watcher if it was previously installed via
|
|
5439
5624
|
// setup-ssh-receiver.ps1. This propagates hook updates without requiring the
|
|
5440
5625
|
// user to manually run update-watcher.ps1 after every `npx agentvibes update`.
|
|
@@ -5648,6 +5833,7 @@ async function install(options = {}) {
|
|
|
5648
5833
|
// Copy all files silently
|
|
5649
5834
|
await copyCommandFiles(targetDir, silentSpinner);
|
|
5650
5835
|
await copyHookFiles(targetDir, silentSpinner);
|
|
5836
|
+
await copyResolverBundle(targetDir, silentSpinner);
|
|
5651
5837
|
await copyPersonalityFiles(targetDir, silentSpinner);
|
|
5652
5838
|
await copyPluginFiles(targetDir, silentSpinner);
|
|
5653
5839
|
await copyBmadConfigFiles(targetDir, silentSpinner);
|
|
@@ -5660,6 +5846,8 @@ async function install(options = {}) {
|
|
|
5660
5846
|
const hooksSubdirInstall = isNativeWindows() ? 'hooks-windows' : 'hooks';
|
|
5661
5847
|
const srcHooksDirInstall = path.join(__dirname, '..', '.claude', hooksSubdirInstall);
|
|
5662
5848
|
await updateGlobalHooks(srcHooksDirInstall);
|
|
5849
|
+
// Populate global ~/.claude/ resolver bundle too, same reasoning as above (AVI-S8.5 Stage 2)
|
|
5850
|
+
await updateGlobalResolverBundle();
|
|
5663
5851
|
|
|
5664
5852
|
await configureSessionStartHook(targetDir, silentSpinner);
|
|
5665
5853
|
await configurePartyModeHook(targetDir, silentSpinner);
|
|
@@ -6805,11 +6993,13 @@ export {
|
|
|
6805
6993
|
copyConfigFiles, copyCodexFiles, configureSessionStartHook, configurePartyModeHook, ensureGitRepo,
|
|
6806
6994
|
installPluginManifest, checkAndInstallPiper,
|
|
6807
6995
|
updateGlobalHooks, updateCommandFiles, updatePersonalityFiles,
|
|
6996
|
+
copyResolverBundle, updateGlobalResolverBundle,
|
|
6808
6997
|
CRITICAL_HOOKS, CRITICAL_HOOKS_WINDOWS,
|
|
6809
6998
|
// Manifest utilities (used by tests and external tooling)
|
|
6810
6999
|
getProjectManifestPath, getGlobalManifestPath,
|
|
6811
7000
|
loadManifest, saveManifest, computeFileHash, manifestSafeCopy, removeManifestFiles,
|
|
6812
7001
|
// Pure helper functions exported for testing
|
|
7002
|
+
readJsonConfigSafe, backupConfigFile,
|
|
6813
7003
|
isPiperProvider, supportsEmoji, getPersonalityIcon,
|
|
6814
7004
|
detectEnvironment, createPageHeaderFooter, buildNavigationChoices, handleNavigationAction, getPageTitle,
|
|
6815
7005
|
getUserShell, showWelcome, getReleaseInfoBoxen, generateActivationInstructions,
|