agentvibes 4.6.8 → 5.1.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/.agentvibes/bmad-voice-map.json +104 -0
- package/.agentvibes/config.json +13 -12
- package/.agentvibes/copilot-sessions.log +4 -0
- package/.claude/audio/tracks/Drifting Down the Hall.mp3 +0 -0
- package/.claude/audio/tracks/Late Night Hip Hop Groove.mp3 +0 -0
- package/.claude/audio/tracks/Midnight Charleston Stomp.mp3 +0 -0
- package/.claude/audio/tracks/README.md +51 -52
- package/.claude/config/audio-effects-bmad.cfg +50 -0
- package/.claude/config/audio-effects.cfg +4 -4
- package/.claude/config/background-music-enabled.txt +1 -0
- package/.claude/config/personality.txt +1 -0
- package/.claude/hooks/play-tts-piper.sh +3 -1
- package/.claude/hooks/play-tts.sh +380 -301
- package/.claude/hooks/session-start-tts.sh +81 -81
- package/.claude/hooks-windows/audio-processor.ps1 +181 -0
- package/.claude/hooks-windows/play-tts-piper.ps1 +259 -245
- package/.claude/hooks-windows/play-tts.ps1 +28 -6
- package/.claude/hooks-windows/session-start-tts.ps1 +114 -114
- package/README.md +112 -6
- package/RELEASE_NOTES.md +83 -0
- package/bin/bmad-speak.js +16 -8
- package/mcp-server/server.py +15 -8
- package/package.json +1 -1
- package/src/console/app.js +899 -897
- package/src/console/footer-config.js +50 -50
- package/src/console/navigation.js +65 -65
- package/src/console/tabs/agents-tab.js +1899 -1886
- package/src/console/tabs/music-tab.js +1076 -1039
- package/src/console/tabs/placeholder-tab.js +81 -80
- package/src/console/tabs/settings-tab.js +941 -3988
- package/src/console/tabs/setup-tab.js +2071 -0
- package/src/console/tabs/voices-tab.js +1843 -1714
- package/src/console/widgets/format-utils.js +92 -89
- package/src/console/widgets/track-picker.js +325 -322
- package/src/installer.js +6147 -6092
- package/src/services/llm-provider-service.js +486 -0
- package/src/services/navigation-service.js +123 -123
- package/src/services/tts-engine-service.js +69 -0
- package/.claude/audio/tracks/dreamy_house_loop.mp3 +0 -0
- package/src/console/tabs/install-tab.js +0 -1081
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes — LLM Provider Service
|
|
3
|
+
*
|
|
4
|
+
* Extracted from llm-providers-tab.js: all provider logic as a standalone service.
|
|
5
|
+
* Config format: llm:key|effects|bgTrack|bgVolume|voice|pretext|ttsEngine
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
import fsSync from 'node:fs';
|
|
11
|
+
|
|
12
|
+
// ── Provider definitions ────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export const PROVIDERS = [
|
|
15
|
+
{
|
|
16
|
+
id: 'claude-code',
|
|
17
|
+
name: 'Claude Code',
|
|
18
|
+
desc: 'Anthropic CLI agent — hooks + MCP server',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'github-copilot',
|
|
22
|
+
name: 'GitHub Copilot',
|
|
23
|
+
desc: 'VS Code Copilot Chat — .vscode/mcp.json + instructions',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'openai-codex',
|
|
27
|
+
name: 'OpenAI Codex',
|
|
28
|
+
desc: 'OpenAI CLI agent — .codex/config.toml + AGENTS.md',
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// ── Provider install-checks ─────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export async function checkClaudeInstalled(targetDir) {
|
|
35
|
+
try {
|
|
36
|
+
await fs.access(path.join(targetDir, '.claude', 'hooks'));
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
try {
|
|
40
|
+
await fs.access(path.join(targetDir, '.claude', 'hooks-windows'));
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function checkCopilotInstalled(targetDir) {
|
|
49
|
+
try {
|
|
50
|
+
const content = await fs.readFile(path.join(targetDir, '.vscode', 'mcp.json'), 'utf8');
|
|
51
|
+
const parsed = JSON.parse(content);
|
|
52
|
+
return !!(parsed?.servers?.agentvibes);
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function checkCodexInstalled(targetDir) {
|
|
59
|
+
try {
|
|
60
|
+
const content = await fs.readFile(path.join(targetDir, '.codex', 'config.toml'), 'utf8');
|
|
61
|
+
return content.includes('[mcp_servers.agentvibes]');
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Claude Code install ────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create .mcp.json in target directory if it doesn't exist.
|
|
71
|
+
* Also copies hooks, commands, config, personality, plugin, and bmad config files.
|
|
72
|
+
*/
|
|
73
|
+
export async function installClaudeMcp(targetDir) {
|
|
74
|
+
const mcpConfigPath = path.join(targetDir, '.mcp.json');
|
|
75
|
+
|
|
76
|
+
const mcpConfig = {
|
|
77
|
+
mcpServers: {
|
|
78
|
+
agentvibes: {
|
|
79
|
+
command: 'npx',
|
|
80
|
+
args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
let mcpCreated = false;
|
|
87
|
+
try {
|
|
88
|
+
await fs.access(mcpConfigPath);
|
|
89
|
+
// Already exists — merge agentvibes key if missing
|
|
90
|
+
try {
|
|
91
|
+
const existing = JSON.parse(await fs.readFile(mcpConfigPath, 'utf8'));
|
|
92
|
+
if (!existing.mcpServers?.agentvibes) {
|
|
93
|
+
existing.mcpServers = existing.mcpServers || {};
|
|
94
|
+
existing.mcpServers.agentvibes = mcpConfig.mcpServers.agentvibes;
|
|
95
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify(existing, null, 2) + '\n');
|
|
96
|
+
mcpCreated = true;
|
|
97
|
+
}
|
|
98
|
+
} catch { /* parse error — don't corrupt */ }
|
|
99
|
+
} catch {
|
|
100
|
+
// File doesn't exist — create it
|
|
101
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
102
|
+
mcpCreated = true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Copy hooks, commands, config, personality, plugin, bmad config files
|
|
106
|
+
const silentSpinner = { start: () => {}, succeed: () => {}, fail: () => {} };
|
|
107
|
+
const installer = await import('../installer.js');
|
|
108
|
+
await installer.copyHookFiles(targetDir, silentSpinner);
|
|
109
|
+
await installer.copyCommandFiles(targetDir, silentSpinner);
|
|
110
|
+
await installer.copyConfigFiles(targetDir, silentSpinner);
|
|
111
|
+
await installer.copyPersonalityFiles(targetDir, silentSpinner);
|
|
112
|
+
await installer.copyPluginFiles(targetDir, silentSpinner);
|
|
113
|
+
await installer.copyBmadConfigFiles(targetDir, silentSpinner);
|
|
114
|
+
await installer.copyBackgroundMusicFiles(targetDir, silentSpinner);
|
|
115
|
+
|
|
116
|
+
return { success: true, mcpCreated };
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return { success: false, error: err.message };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function removeClaudeMcp(targetDir) {
|
|
123
|
+
const mcpConfigPath = path.join(targetDir, '.mcp.json');
|
|
124
|
+
try {
|
|
125
|
+
const content = await fs.readFile(mcpConfigPath, 'utf8');
|
|
126
|
+
const parsed = JSON.parse(content);
|
|
127
|
+
if (parsed.mcpServers?.agentvibes) {
|
|
128
|
+
delete parsed.mcpServers.agentvibes;
|
|
129
|
+
// Only delete file if mcpServers is empty AND no other top-level keys
|
|
130
|
+
const noServers = Object.keys(parsed.mcpServers).length === 0;
|
|
131
|
+
const noOtherKeys = Object.keys(parsed).length === 1;
|
|
132
|
+
if (noServers && noOtherKeys) {
|
|
133
|
+
await fs.unlink(mcpConfigPath);
|
|
134
|
+
} else {
|
|
135
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify(parsed, null, 2) + '\n');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch { /* file doesn't exist or can't parse — nothing to remove */ }
|
|
139
|
+
return { success: true };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Full uninstall: remove MCP entry + all AgentVibes files from the project.
|
|
144
|
+
* Does NOT touch user's own .claude/ settings (settings.json, CLAUDE.md etc.).
|
|
145
|
+
*/
|
|
146
|
+
export async function uninstallClaude(targetDir) {
|
|
147
|
+
const removed = [];
|
|
148
|
+
|
|
149
|
+
// 1. Remove MCP entry
|
|
150
|
+
await removeClaudeMcp(targetDir);
|
|
151
|
+
removed.push('.mcp.json (agentvibes entry)');
|
|
152
|
+
|
|
153
|
+
// 2. Remove AgentVibes directories
|
|
154
|
+
const dirs = [
|
|
155
|
+
['.claude', 'commands', 'agent-vibes'],
|
|
156
|
+
['.claude', 'hooks'],
|
|
157
|
+
['.claude', 'hooks-windows'],
|
|
158
|
+
['.claude', 'personalities'],
|
|
159
|
+
['.claude', 'output-styles'],
|
|
160
|
+
['.claude', 'plugins'],
|
|
161
|
+
['.claude', 'audio'],
|
|
162
|
+
['.claude', 'config'],
|
|
163
|
+
['.agentvibes'],
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
for (const parts of dirs) {
|
|
167
|
+
const dirPath = path.join(targetDir, ...parts);
|
|
168
|
+
try {
|
|
169
|
+
await fs.rm(dirPath, { recursive: true, force: true });
|
|
170
|
+
removed.push(parts.join('/'));
|
|
171
|
+
} catch { /* doesn't exist */ }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 3. Remove AgentVibes config files from .claude/
|
|
175
|
+
const configFiles = [
|
|
176
|
+
'tts-voice.txt', 'tts-provider.txt', 'tts-personality.txt',
|
|
177
|
+
'tts-verbosity.txt', 'tts-translate.txt', 'tts-target-voice.txt',
|
|
178
|
+
'tts-target-language.txt', 'tts-language.txt', 'tts-speech-rate.txt',
|
|
179
|
+
'tts-target-speech-rate.txt', 'piper-speech-rate.txt',
|
|
180
|
+
'piper-target-speech-rate.txt', 'personalities.json',
|
|
181
|
+
'github-star-reminder.txt', 'piper-voices-dir.txt',
|
|
182
|
+
'verbosity.txt', 'personality.txt', 'intro-text.txt',
|
|
183
|
+
'reverb-level.txt', 'background-music-enabled.txt',
|
|
184
|
+
'background-music-volume.txt',
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
for (const file of configFiles) {
|
|
188
|
+
try {
|
|
189
|
+
await fs.unlink(path.join(targetDir, '.claude', file));
|
|
190
|
+
} catch { /* doesn't exist */ }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 4. Remove settings.json hook entries if present
|
|
194
|
+
const settingsPath = path.join(targetDir, '.claude', 'settings.json');
|
|
195
|
+
try {
|
|
196
|
+
const content = await fs.readFile(settingsPath, 'utf8');
|
|
197
|
+
const settings = JSON.parse(content);
|
|
198
|
+
let changed = false;
|
|
199
|
+
if (settings.hooks) {
|
|
200
|
+
for (const hookKey of Object.keys(settings.hooks)) {
|
|
201
|
+
const hooks = settings.hooks[hookKey];
|
|
202
|
+
if (Array.isArray(hooks)) {
|
|
203
|
+
const filtered = hooks.filter(h =>
|
|
204
|
+
!(h.command && (h.command.includes('agentvibes') || h.command.includes('play-tts') || h.command.includes('bmad-speak'))));
|
|
205
|
+
if (filtered.length !== hooks.length) {
|
|
206
|
+
settings.hooks[hookKey] = filtered;
|
|
207
|
+
changed = true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (changed) {
|
|
213
|
+
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
214
|
+
removed.push('.claude/settings.json (hooks cleaned)');
|
|
215
|
+
}
|
|
216
|
+
} catch { /* no settings or parse error */ }
|
|
217
|
+
|
|
218
|
+
return { success: true, removed };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── Copilot install/remove ──────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
export async function installCopilotMcp(targetDir) {
|
|
224
|
+
const vscodeDir = path.join(targetDir, '.vscode');
|
|
225
|
+
const mcpJsonPath = path.join(vscodeDir, 'mcp.json');
|
|
226
|
+
|
|
227
|
+
const agentvibesServer = {
|
|
228
|
+
type: 'stdio',
|
|
229
|
+
command: 'npx',
|
|
230
|
+
args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
await fs.mkdir(vscodeDir, { recursive: true });
|
|
235
|
+
let mcpConfig = { servers: {} };
|
|
236
|
+
try {
|
|
237
|
+
const existing = await fs.readFile(mcpJsonPath, 'utf8');
|
|
238
|
+
const parsed = JSON.parse(existing);
|
|
239
|
+
if (parsed && typeof parsed === 'object') {
|
|
240
|
+
mcpConfig = parsed;
|
|
241
|
+
if (!mcpConfig.servers) mcpConfig.servers = {};
|
|
242
|
+
}
|
|
243
|
+
} catch { /* new file */ }
|
|
244
|
+
|
|
245
|
+
mcpConfig.servers.agentvibes = agentvibesServer;
|
|
246
|
+
await fs.writeFile(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
247
|
+
return { success: true };
|
|
248
|
+
} catch (err) {
|
|
249
|
+
return { success: false, error: err.message };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function removeCopilotMcp(targetDir) {
|
|
254
|
+
const mcpJsonPath = path.join(targetDir, '.vscode', 'mcp.json');
|
|
255
|
+
try {
|
|
256
|
+
const content = await fs.readFile(mcpJsonPath, 'utf8');
|
|
257
|
+
const parsed = JSON.parse(content);
|
|
258
|
+
if (parsed?.servers?.agentvibes) {
|
|
259
|
+
delete parsed.servers.agentvibes;
|
|
260
|
+
if (Object.keys(parsed.servers).length === 0) {
|
|
261
|
+
await fs.unlink(mcpJsonPath);
|
|
262
|
+
} else {
|
|
263
|
+
await fs.writeFile(mcpJsonPath, JSON.stringify(parsed, null, 2) + '\n');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return { success: true };
|
|
267
|
+
} catch {
|
|
268
|
+
return { success: true }; // Already gone
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export async function installCopilotInstructions(targetDir, packageDir) {
|
|
273
|
+
const destPath = path.join(targetDir, '.github', 'copilot-instructions.md');
|
|
274
|
+
const srcPath = path.join(packageDir, '.github', 'copilot-instructions.md');
|
|
275
|
+
try {
|
|
276
|
+
await fs.mkdir(path.join(targetDir, '.github'), { recursive: true });
|
|
277
|
+
const content = await fs.readFile(srcPath, 'utf8');
|
|
278
|
+
await fs.writeFile(destPath, content);
|
|
279
|
+
} catch { /* best effort */ }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export async function removeCopilotInstructions(targetDir) {
|
|
283
|
+
try {
|
|
284
|
+
await fs.unlink(path.join(targetDir, '.github', 'copilot-instructions.md'));
|
|
285
|
+
} catch { /* already gone */ }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── Codex install/remove ────────────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
export async function installCodexMcp(targetDir) {
|
|
291
|
+
const codexDir = path.join(targetDir, '.codex');
|
|
292
|
+
const tomlPath = path.join(codexDir, 'config.toml');
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
await fs.mkdir(codexDir, { recursive: true });
|
|
296
|
+
let existing = '';
|
|
297
|
+
try { existing = await fs.readFile(tomlPath, 'utf8'); } catch { /* new file */ }
|
|
298
|
+
const content = buildCodexToml(existing);
|
|
299
|
+
await fs.writeFile(tomlPath, content);
|
|
300
|
+
return { success: true };
|
|
301
|
+
} catch (err) {
|
|
302
|
+
return { success: false, error: err.message };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function removeCodexMcp(targetDir) {
|
|
307
|
+
const tomlPath = path.join(targetDir, '.codex', 'config.toml');
|
|
308
|
+
try {
|
|
309
|
+
const content = await fs.readFile(tomlPath, 'utf8');
|
|
310
|
+
const lines = content.split('\n');
|
|
311
|
+
const filtered = [];
|
|
312
|
+
let skipping = false;
|
|
313
|
+
for (const line of lines) {
|
|
314
|
+
if (line.trim() === '[mcp_servers.agentvibes]') {
|
|
315
|
+
skipping = true;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (skipping && line.startsWith('[')) {
|
|
319
|
+
skipping = false;
|
|
320
|
+
}
|
|
321
|
+
if (!skipping) filtered.push(line);
|
|
322
|
+
}
|
|
323
|
+
const result = filtered.join('\n').trim();
|
|
324
|
+
if (!result) {
|
|
325
|
+
await fs.unlink(tomlPath);
|
|
326
|
+
} else {
|
|
327
|
+
await fs.writeFile(tomlPath, result + '\n');
|
|
328
|
+
}
|
|
329
|
+
return { success: true };
|
|
330
|
+
} catch {
|
|
331
|
+
return { success: true }; // Already gone
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function buildCodexToml(existingContent = '') {
|
|
336
|
+
const serverBlock = [
|
|
337
|
+
'[mcp_servers.agentvibes]',
|
|
338
|
+
'command = "npx"',
|
|
339
|
+
'args = ["-y", "--package=agentvibes", "agentvibes-mcp-server"]',
|
|
340
|
+
].join('\n');
|
|
341
|
+
|
|
342
|
+
if (!existingContent.trim()) return serverBlock + '\n';
|
|
343
|
+
|
|
344
|
+
// Remove existing agentvibes block if present, then append fresh
|
|
345
|
+
const lines = existingContent.split('\n');
|
|
346
|
+
const filtered = [];
|
|
347
|
+
let skipping = false;
|
|
348
|
+
for (const line of lines) {
|
|
349
|
+
if (line.trim() === '[mcp_servers.agentvibes]') {
|
|
350
|
+
skipping = true;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (skipping && line.startsWith('[')) {
|
|
354
|
+
skipping = false;
|
|
355
|
+
}
|
|
356
|
+
if (!skipping) filtered.push(line);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let result = filtered.join('\n').trimEnd();
|
|
360
|
+
if (result.length) result += '\n\n';
|
|
361
|
+
return result + serverBlock + '\n';
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function installCodexInstructions(targetDir, packageDir) {
|
|
365
|
+
const srcPath = path.join(packageDir, '.codex', 'AGENTS.md');
|
|
366
|
+
try {
|
|
367
|
+
const content = await fs.readFile(srcPath, 'utf8');
|
|
368
|
+
await fs.mkdir(path.join(targetDir, '.codex'), { recursive: true });
|
|
369
|
+
await fs.writeFile(path.join(targetDir, '.codex', 'AGENTS.md'), content);
|
|
370
|
+
await fs.writeFile(path.join(targetDir, 'AGENTS.md'), content);
|
|
371
|
+
} catch { /* best effort */ }
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export async function installCodexHooks(targetDir, packageDir) {
|
|
375
|
+
const destDir = path.join(targetDir, '.codex', 'hooks');
|
|
376
|
+
const srcDir = path.join(packageDir, '.codex', 'hooks');
|
|
377
|
+
try {
|
|
378
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
379
|
+
for (const file of ['init-agentvibes.sh', 'init-agentvibes.ps1']) {
|
|
380
|
+
try {
|
|
381
|
+
const content = await fs.readFile(path.join(srcDir, file), 'utf8');
|
|
382
|
+
await fs.writeFile(path.join(destDir, file), content);
|
|
383
|
+
} catch { /* best effort */ }
|
|
384
|
+
}
|
|
385
|
+
} catch { /* best effort */ }
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export async function removeCodexInstructions(targetDir) {
|
|
389
|
+
try {
|
|
390
|
+
await fs.unlink(path.join(targetDir, '.codex', 'AGENTS.md'));
|
|
391
|
+
} catch { /* already gone */ }
|
|
392
|
+
try {
|
|
393
|
+
await fs.unlink(path.join(targetDir, 'AGENTS.md'));
|
|
394
|
+
} catch { /* already gone */ }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export async function removeCodexHooks(targetDir) {
|
|
398
|
+
const hooksDir = path.join(targetDir, '.codex', 'hooks');
|
|
399
|
+
try {
|
|
400
|
+
await fs.unlink(path.join(hooksDir, 'init-agentvibes.sh'));
|
|
401
|
+
} catch { /* already gone */ }
|
|
402
|
+
try {
|
|
403
|
+
await fs.unlink(path.join(hooksDir, 'init-agentvibes.ps1'));
|
|
404
|
+
} catch { /* already gone */ }
|
|
405
|
+
try {
|
|
406
|
+
await fs.rmdir(hooksDir);
|
|
407
|
+
} catch { /* not empty or gone */ }
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ── Config path resolution ──────────────────────────────────────────────────
|
|
411
|
+
|
|
412
|
+
export function resolveCfgPath(targetDir) {
|
|
413
|
+
const localCfg = path.join(targetDir, '.claude', 'config', 'audio-effects.cfg');
|
|
414
|
+
const homeDir = process.env.USERPROFILE || process.env.HOME || '';
|
|
415
|
+
const globalCfg = path.join(homeDir, '.claude', 'config', 'audio-effects.cfg');
|
|
416
|
+
return fsSync.existsSync(localCfg) ? localCfg : globalCfg;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ── LLM config read/write ───────────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Read per-LLM audio config from audio-effects.cfg.
|
|
423
|
+
* Format: llm:key|effects|bgTrack|bgVolume|voice|pretext|ttsEngine
|
|
424
|
+
* Handles old 6-field format gracefully (ttsEngine defaults to '').
|
|
425
|
+
*/
|
|
426
|
+
export function loadLlmConfigSync(llmKey, targetDir) {
|
|
427
|
+
const cfgKey = `llm:${llmKey}`;
|
|
428
|
+
const resolvedTargetDir = targetDir || process.env.INIT_CWD || process.cwd();
|
|
429
|
+
const cfgPaths = [
|
|
430
|
+
path.join(resolvedTargetDir, '.claude', 'config', 'audio-effects.cfg'),
|
|
431
|
+
path.join(process.env.USERPROFILE || process.env.HOME || '', '.claude', 'config', 'audio-effects.cfg'),
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
for (const cfgPath of cfgPaths) {
|
|
435
|
+
try {
|
|
436
|
+
const content = fsSync.readFileSync(cfgPath, 'utf8');
|
|
437
|
+
for (const line of content.split('\n')) {
|
|
438
|
+
if (line.startsWith(cfgKey + '|')) {
|
|
439
|
+
const parts = line.split('|');
|
|
440
|
+
return {
|
|
441
|
+
effects: (parts[1] || '').trim(),
|
|
442
|
+
bgTrack: (parts[2] || '').trim(),
|
|
443
|
+
bgVolume: (parts[3] || '0.15').trim(),
|
|
444
|
+
voice: (parts[4] || '').trim(),
|
|
445
|
+
pretext: (parts[5] || '').trim(),
|
|
446
|
+
ttsEngine: (parts[6] || '').trim(), // new field — empty if old format
|
|
447
|
+
sourcePath: cfgPath,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
} catch { /* file not found */ }
|
|
452
|
+
}
|
|
453
|
+
return { effects: '', bgTrack: '', bgVolume: '0.15', voice: '', pretext: '', ttsEngine: '', sourcePath: '' };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Write per-LLM audio config to audio-effects.cfg.
|
|
458
|
+
* Format: llm:key|effects|bgTrack|bgVolume|voice|pretext|ttsEngine
|
|
459
|
+
*/
|
|
460
|
+
export function saveLlmConfigSync(llmKey, config, targetDir) {
|
|
461
|
+
const cfgKey = `llm:${llmKey}`;
|
|
462
|
+
// Sanitize pipe chars in user-editable fields to prevent config format corruption
|
|
463
|
+
const sanitize = (v) => (v || '').replace(/\|/g, '');
|
|
464
|
+
const cfgLine = `${cfgKey}|${sanitize(config.effects)}|${sanitize(config.bgTrack)}|${config.bgVolume}|${sanitize(config.voice)}|${sanitize(config.pretext)}|${sanitize(config.ttsEngine)}`;
|
|
465
|
+
const resolvedTargetDir = targetDir || process.env.INIT_CWD || process.cwd();
|
|
466
|
+
const cfgPath = config.sourcePath || resolveCfgPath(resolvedTargetDir);
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
let content = '';
|
|
470
|
+
try { content = fsSync.readFileSync(cfgPath, 'utf8'); } catch { /* new file */ }
|
|
471
|
+
|
|
472
|
+
const lines = content.split('\n');
|
|
473
|
+
let found = false;
|
|
474
|
+
for (let i = 0; i < lines.length; i++) {
|
|
475
|
+
if (lines[i].startsWith(cfgKey + '|')) {
|
|
476
|
+
lines[i] = cfgLine;
|
|
477
|
+
found = true;
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (!found) lines.push(cfgLine);
|
|
482
|
+
|
|
483
|
+
fsSync.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
484
|
+
fsSync.writeFileSync(cfgPath, lines.join('\n'));
|
|
485
|
+
} catch { /* best effort */ }
|
|
486
|
+
}
|