agentvibes 5.1.3 → 5.2.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/config.json +23 -13
- package/.claude/commands/agent-vibes/verbosity.md +98 -89
- package/.claude/config/audio-effects.cfg +6 -1
- package/.claude/hooks/bmad-speak.sh +2 -2
- package/.claude/hooks/piper-download-voices.sh +233 -225
- package/.claude/hooks/piper-installer.sh +1 -1
- package/.claude/hooks/piper-voice-manager.sh +125 -0
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +97 -90
- package/.claude/hooks/play-tts-enhanced.sh +1 -1
- package/.claude/hooks/play-tts-piper.sh +16 -5
- package/.claude/hooks/play-tts-ssh-remote.sh +168 -167
- package/.claude/hooks/play-tts.sh +31 -9
- package/.claude/hooks/session-start-tts.sh +4 -1
- package/.claude/hooks/stop-tts.sh +1 -1
- package/.claude/hooks/verbosity-manager.sh +185 -178
- package/.claude/hooks-windows/download-extra-voices.ps1 +243 -185
- package/.claude/hooks-windows/play-tts-piper.ps1 +7 -2
- package/.claude/hooks-windows/play-tts.ps1 +219 -65
- package/.claude/hooks-windows/session-start-tts.ps1 +2 -1
- package/.claude/hooks-windows/verbosity-manager.ps1 +126 -119
- package/README.md +24 -1
- package/RELEASE_NOTES.md +113 -0
- package/bin/agentvibes-voice-browser.js +1939 -1840
- package/mcp-server/server.py +75 -25
- package/package.json +1 -1
- package/src/console/tabs/receiver-tab.js +1527 -1483
- package/src/console/tabs/settings-tab.js +2 -2
- package/src/console/tabs/setup-tab.js +122 -20
- package/src/console/tabs/voices-tab.js +130 -13
- package/src/i18n/en.js +202 -202
- package/src/installer.js +29 -25
- package/src/services/llm-provider-service.js +114 -11
- package/src/services/verbosity-service.js +159 -157
- package/templates/agentvibes-receiver.sh +3 -2
|
@@ -27,8 +27,66 @@ export const PROVIDERS = [
|
|
|
27
27
|
name: 'OpenAI Codex',
|
|
28
28
|
desc: 'OpenAI CLI agent — .codex/config.toml + AGENTS.md',
|
|
29
29
|
},
|
|
30
|
+
{
|
|
31
|
+
id: 'default',
|
|
32
|
+
name: 'Default (Fallback)',
|
|
33
|
+
desc: 'Used when any tool calls TTS without identifying its LLM',
|
|
34
|
+
// No install/uninstall — this is a config-only entry
|
|
35
|
+
isDefault: true,
|
|
36
|
+
},
|
|
30
37
|
];
|
|
31
38
|
|
|
39
|
+
const DEFAULT_LLM_CONFIGS = {
|
|
40
|
+
// Fallback used when play-tts is invoked with no -llm flag. Pretext is
|
|
41
|
+
// empty by default — users edit it via Setup → Default → Configure. When
|
|
42
|
+
// empty, no prefix is prepended at all.
|
|
43
|
+
default: {
|
|
44
|
+
effects: 'light',
|
|
45
|
+
bgTrack: '',
|
|
46
|
+
bgVolume: '0.15',
|
|
47
|
+
voice: 'en_US-lessac-high',
|
|
48
|
+
pretext: '',
|
|
49
|
+
ttsEngine: 'piper',
|
|
50
|
+
},
|
|
51
|
+
'claude-code': {
|
|
52
|
+
effects: 'light',
|
|
53
|
+
bgTrack: 'agent_vibes_chillwave_v2_loop.mp3',
|
|
54
|
+
bgVolume: '0.15',
|
|
55
|
+
voice: 'en_US-lessac-high',
|
|
56
|
+
pretext: 'Claude Code here',
|
|
57
|
+
ttsEngine: 'piper',
|
|
58
|
+
},
|
|
59
|
+
copilot: {
|
|
60
|
+
effects: 'light',
|
|
61
|
+
bgTrack: 'agent_vibes_bossa_nova_v2_loop.mp3',
|
|
62
|
+
bgVolume: '0.15',
|
|
63
|
+
voice: 'en_US-libritts-high::Anna-11',
|
|
64
|
+
pretext: 'Copilot here',
|
|
65
|
+
ttsEngine: 'piper',
|
|
66
|
+
},
|
|
67
|
+
codex: {
|
|
68
|
+
effects: 'light',
|
|
69
|
+
bgTrack: 'agent_vibes_chillwave_v2_loop.mp3',
|
|
70
|
+
bgVolume: '0.15',
|
|
71
|
+
// NOTE: lessac-medium appears to silently fail to synthesize on some
|
|
72
|
+
// Windows Piper installs (loads the model, exits with no output).
|
|
73
|
+
// lessac-high works reliably, so use it as the default for codex.
|
|
74
|
+
voice: 'en_US-lessac-high',
|
|
75
|
+
pretext: 'Codex here',
|
|
76
|
+
ttsEngine: 'piper',
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function ensureDefaultLlmConfigSync(llmKey, targetDir) {
|
|
81
|
+
const existing = loadLlmConfigSync(llmKey, targetDir);
|
|
82
|
+
if (existing.sourcePath) return;
|
|
83
|
+
|
|
84
|
+
const defaults = DEFAULT_LLM_CONFIGS[llmKey];
|
|
85
|
+
if (!defaults) return;
|
|
86
|
+
|
|
87
|
+
saveLlmConfigSync(llmKey, defaults, targetDir);
|
|
88
|
+
}
|
|
89
|
+
|
|
32
90
|
// ── Provider install-checks ─────────────────────────────────────────────────
|
|
33
91
|
|
|
34
92
|
export async function checkClaudeInstalled(targetDir) {
|
|
@@ -73,12 +131,24 @@ export async function checkCodexInstalled(targetDir) {
|
|
|
73
131
|
export async function installClaudeMcp(targetDir) {
|
|
74
132
|
const mcpConfigPath = path.join(targetDir, '.mcp.json');
|
|
75
133
|
|
|
134
|
+
// The agentvibes server entry for Claude Code's .mcp.json.
|
|
135
|
+
//
|
|
136
|
+
// IMPORTANT: no `env.AGENTVIBES_LLM` block here. GitHub Copilot CLI
|
|
137
|
+
// also reads project-level `.mcp.json` with precedence over its own
|
|
138
|
+
// `~/.copilot/mcp-config.json` — so if we set `AGENTVIBES_LLM=claude-code`
|
|
139
|
+
// in `.mcp.json`, Copilot CLI picks up that value too and mis-routes.
|
|
140
|
+
// Instead, the MCP server (mcp-server/server.py) auto-detects Claude
|
|
141
|
+
// Code via the `CLAUDECODE=1` env var that Claude Code sets on every
|
|
142
|
+
// subprocess it spawns. Copilot CLI does NOT set that var, so its
|
|
143
|
+
// spawned MCP server correctly falls back to its own config.
|
|
144
|
+
const agentvibesServer = {
|
|
145
|
+
command: 'npx',
|
|
146
|
+
args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
|
|
147
|
+
};
|
|
148
|
+
|
|
76
149
|
const mcpConfig = {
|
|
77
150
|
mcpServers: {
|
|
78
|
-
agentvibes:
|
|
79
|
-
command: 'npx',
|
|
80
|
-
args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
|
|
81
|
-
},
|
|
151
|
+
agentvibes: agentvibesServer,
|
|
82
152
|
},
|
|
83
153
|
};
|
|
84
154
|
|
|
@@ -86,15 +156,15 @@ export async function installClaudeMcp(targetDir) {
|
|
|
86
156
|
let mcpCreated = false;
|
|
87
157
|
try {
|
|
88
158
|
await fs.access(mcpConfigPath);
|
|
89
|
-
// Already exists — merge agentvibes
|
|
159
|
+
// Already exists — merge / upgrade the agentvibes entry. This also
|
|
160
|
+
// STRIPS any stale AGENTVIBES_LLM env block left over from v5.1.2..4
|
|
161
|
+
// so Copilot CLI stops mis-routing.
|
|
90
162
|
try {
|
|
91
163
|
const existing = JSON.parse(await fs.readFile(mcpConfigPath, 'utf8'));
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
mcpCreated = true;
|
|
97
|
-
}
|
|
164
|
+
existing.mcpServers = existing.mcpServers || {};
|
|
165
|
+
existing.mcpServers.agentvibes = { ...agentvibesServer };
|
|
166
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify(existing, null, 2) + '\n');
|
|
167
|
+
mcpCreated = true;
|
|
98
168
|
} catch { /* parse error — don't corrupt */ }
|
|
99
169
|
} catch {
|
|
100
170
|
// File doesn't exist — create it
|
|
@@ -112,6 +182,7 @@ export async function installClaudeMcp(targetDir) {
|
|
|
112
182
|
await installer.copyPluginFiles(targetDir, silentSpinner);
|
|
113
183
|
await installer.copyBmadConfigFiles(targetDir, silentSpinner);
|
|
114
184
|
await installer.copyBackgroundMusicFiles(targetDir, silentSpinner);
|
|
185
|
+
ensureDefaultLlmConfigSync('claude-code', targetDir);
|
|
115
186
|
|
|
116
187
|
return { success: true, mcpCreated };
|
|
117
188
|
} catch (err) {
|
|
@@ -247,6 +318,37 @@ export async function installCopilotMcp(targetDir) {
|
|
|
247
318
|
|
|
248
319
|
mcpConfig.servers.agentvibes = agentvibesServer;
|
|
249
320
|
await fs.writeFile(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
321
|
+
|
|
322
|
+
// Also write ~/.copilot/mcp-config.json so the GitHub Copilot CLI
|
|
323
|
+
// (different product from VS Code Copilot Chat!) can find the
|
|
324
|
+
// agentvibes MCP server. VS Code reads .vscode/mcp.json, but the
|
|
325
|
+
// CLI reads ONLY from ~/.copilot/mcp-config.json per docs:
|
|
326
|
+
// https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
|
|
327
|
+
try {
|
|
328
|
+
const copilotHome = process.env.COPILOT_HOME ||
|
|
329
|
+
path.join(process.env.USERPROFILE || process.env.HOME || '', '.copilot');
|
|
330
|
+
const copilotMcpPath = path.join(copilotHome, 'mcp-config.json');
|
|
331
|
+
await fs.mkdir(copilotHome, { recursive: true });
|
|
332
|
+
let cliConfig = { mcpServers: {} };
|
|
333
|
+
try {
|
|
334
|
+
const existingCli = await fs.readFile(copilotMcpPath, 'utf8');
|
|
335
|
+
const parsedCli = JSON.parse(existingCli);
|
|
336
|
+
if (parsedCli && typeof parsedCli === 'object') {
|
|
337
|
+
cliConfig = parsedCli;
|
|
338
|
+
if (!cliConfig.mcpServers) cliConfig.mcpServers = {};
|
|
339
|
+
}
|
|
340
|
+
} catch { /* new file */ }
|
|
341
|
+
cliConfig.mcpServers.agentvibes = {
|
|
342
|
+
type: 'local',
|
|
343
|
+
command: 'npx',
|
|
344
|
+
args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
|
|
345
|
+
env: { AGENTVIBES_LLM: 'copilot' },
|
|
346
|
+
tools: ['*'],
|
|
347
|
+
};
|
|
348
|
+
await fs.writeFile(copilotMcpPath, JSON.stringify(cliConfig, null, 2) + '\n');
|
|
349
|
+
} catch { /* best effort — CLI might not be installed */ }
|
|
350
|
+
|
|
351
|
+
ensureDefaultLlmConfigSync('copilot', targetDir);
|
|
250
352
|
return { success: true };
|
|
251
353
|
} catch (err) {
|
|
252
354
|
return { success: false, error: err.message };
|
|
@@ -300,6 +402,7 @@ export async function installCodexMcp(targetDir) {
|
|
|
300
402
|
try { existing = await fs.readFile(tomlPath, 'utf8'); } catch { /* new file */ }
|
|
301
403
|
const content = buildCodexToml(existing);
|
|
302
404
|
await fs.writeFile(tomlPath, content);
|
|
405
|
+
ensureDefaultLlmConfigSync('codex', targetDir);
|
|
303
406
|
return { success: true };
|
|
304
407
|
} catch (err) {
|
|
305
408
|
return { success: false, error: err.message };
|
|
@@ -1,157 +1,159 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentVibes VerbosityService
|
|
3
|
-
* Epic 10: Stories 10.1-10.4
|
|
4
|
-
*
|
|
5
|
-
* Centralises verbosity logic for 5 levels: minimal, low, medium, high, custom.
|
|
6
|
-
*
|
|
7
|
-
* Hook types:
|
|
8
|
-
* - 'prompt-submit' — fires when user submits a prompt
|
|
9
|
-
* - 'response-complete' — fires when Claude finishes responding
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Ordered verbosity levels from quietest to most verbose.
|
|
16
|
-
* @type {string[]}
|
|
17
|
-
*/
|
|
18
|
-
export const VERBOSITY_LEVELS = Object.freeze(['minimal', 'low', 'medium', 'high', 'custom']);
|
|
19
|
-
|
|
20
|
-
// Per-level shouldSpeak configuration (fixed levels only; custom reads from config)
|
|
21
|
-
const LEVEL_SPEAK = Object.freeze({
|
|
22
|
-
minimal: { 'prompt-submit': false, 'response-complete': true },
|
|
23
|
-
low: { 'prompt-submit': false, 'response-complete': true },
|
|
24
|
-
medium: { 'prompt-submit': true, 'response-complete': true },
|
|
25
|
-
high: { 'prompt-submit': true, 'response-complete': true },
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
'
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* @
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (level === '
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
1
|
+
/**
|
|
2
|
+
* AgentVibes VerbosityService
|
|
3
|
+
* Epic 10: Stories 10.1-10.4
|
|
4
|
+
*
|
|
5
|
+
* Centralises verbosity logic for 5 levels: minimal, low, medium, high, custom.
|
|
6
|
+
*
|
|
7
|
+
* Hook types:
|
|
8
|
+
* - 'prompt-submit' — fires when user submits a prompt
|
|
9
|
+
* - 'response-complete' — fires when Claude finishes responding
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ordered verbosity levels from quietest to most verbose.
|
|
16
|
+
* @type {string[]}
|
|
17
|
+
*/
|
|
18
|
+
export const VERBOSITY_LEVELS = Object.freeze(['minimal', 'low', 'medium', 'high', 'caveman', 'custom']);
|
|
19
|
+
|
|
20
|
+
// Per-level shouldSpeak configuration (fixed levels only; custom reads from config)
|
|
21
|
+
const LEVEL_SPEAK = Object.freeze({
|
|
22
|
+
minimal: { 'prompt-submit': false, 'response-complete': true },
|
|
23
|
+
low: { 'prompt-submit': false, 'response-complete': true },
|
|
24
|
+
medium: { 'prompt-submit': true, 'response-complete': true },
|
|
25
|
+
high: { 'prompt-submit': true, 'response-complete': true },
|
|
26
|
+
caveman: { 'prompt-submit': true, 'response-complete': true },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Per-level static messages (null = use hook default message)
|
|
30
|
+
const LEVEL_MESSAGES = Object.freeze({
|
|
31
|
+
minimal: {
|
|
32
|
+
'prompt-submit': null,
|
|
33
|
+
'response-complete': 'Claude is ready for your input',
|
|
34
|
+
},
|
|
35
|
+
low: {
|
|
36
|
+
'prompt-submit': null,
|
|
37
|
+
'response-complete': null, // built dynamically: "Done. <summary>"
|
|
38
|
+
},
|
|
39
|
+
medium: { 'prompt-submit': null, 'response-complete': null },
|
|
40
|
+
high: { 'prompt-submit': null, 'response-complete': null },
|
|
41
|
+
caveman: { 'prompt-submit': null, 'response-complete': null },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
export class VerbosityService {
|
|
47
|
+
/**
|
|
48
|
+
* @param {object} configService - AgentVibes ConfigService instance
|
|
49
|
+
*/
|
|
50
|
+
constructor(configService) {
|
|
51
|
+
this._config = configService;
|
|
52
|
+
this._migrated = false; // tracks whether migration ran in this session
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns the current verbosity level.
|
|
57
|
+
* Defaults to 'high' if not configured or unrecognised.
|
|
58
|
+
* @returns {'minimal'|'low'|'medium'|'high'|'custom'}
|
|
59
|
+
*/
|
|
60
|
+
getLevel() {
|
|
61
|
+
const cfg = this._config.getConfig();
|
|
62
|
+
const level = cfg.verbosity;
|
|
63
|
+
return VERBOSITY_LEVELS.includes(level) ? level : 'high';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Returns whether TTS should speak for the given hook type.
|
|
68
|
+
*
|
|
69
|
+
* @param {'prompt-submit'|'response-complete'} hookType
|
|
70
|
+
* @returns {boolean}
|
|
71
|
+
*/
|
|
72
|
+
shouldSpeak(hookType) {
|
|
73
|
+
const level = this.getLevel();
|
|
74
|
+
if (level === 'custom') {
|
|
75
|
+
return this._customShouldSpeak(hookType);
|
|
76
|
+
}
|
|
77
|
+
const table = LEVEL_SPEAK[level] ?? LEVEL_SPEAK.high;
|
|
78
|
+
return table[hookType] ?? true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns the message for the given hook type and context.
|
|
83
|
+
* Returns null to use the hook's own default message.
|
|
84
|
+
*
|
|
85
|
+
* @param {'prompt-submit'|'response-complete'} hookType
|
|
86
|
+
* @param {object} context - { summary?: string }
|
|
87
|
+
* @returns {string|null}
|
|
88
|
+
*/
|
|
89
|
+
getMessage(hookType, context) {
|
|
90
|
+
const level = this.getLevel();
|
|
91
|
+
if (level === 'high' || level === 'medium' || level === 'caveman') return null;
|
|
92
|
+
|
|
93
|
+
if (level === 'low' && hookType === 'response-complete') {
|
|
94
|
+
const summary = context?.summary ?? '';
|
|
95
|
+
const trimmed = summary.slice(0, 30);
|
|
96
|
+
return trimmed.length > 0 ? `Done. ${trimmed}` : 'Done';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (level === 'custom') return null; // custom uses hook defaults
|
|
100
|
+
|
|
101
|
+
return LEVEL_MESSAGES[level]?.[hookType] ?? null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Checks if migration is needed (old 'low' → 'medium').
|
|
106
|
+
* If needed and not already done, performs migration and returns true.
|
|
107
|
+
* @returns {boolean} true if migration was performed, false otherwise
|
|
108
|
+
*/
|
|
109
|
+
checkMigration() {
|
|
110
|
+
const cfg = this._config.getConfig();
|
|
111
|
+
if (cfg.verbosityMigrated) return false;
|
|
112
|
+
if (cfg.verbosity !== 'low') return false;
|
|
113
|
+
|
|
114
|
+
// Migrate: old 'low' users get 'medium' in new system
|
|
115
|
+
this._config.set('verbosity', 'medium');
|
|
116
|
+
this._config.set('verbosityMigrated', true);
|
|
117
|
+
this._migrated = true;
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Returns true if a migration notice should be shown to the user.
|
|
123
|
+
* @returns {boolean}
|
|
124
|
+
*/
|
|
125
|
+
needsMigrationNotice() {
|
|
126
|
+
if (!this._migrated) return false;
|
|
127
|
+
const cfg = this._config.getConfig();
|
|
128
|
+
return !cfg.migrationNoticeDismissed;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Marks the migration notice as dismissed.
|
|
133
|
+
*/
|
|
134
|
+
dismissMigrationNotice() {
|
|
135
|
+
this._config.set('migrationNoticeDismissed', true);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Private
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* shouldSpeak for CUSTOM level — reads per-hook toggles from config.customVerbosity.
|
|
143
|
+
* @param {string} hookType
|
|
144
|
+
* @returns {boolean}
|
|
145
|
+
*/
|
|
146
|
+
_customShouldSpeak(hookType) {
|
|
147
|
+
const cfg = this._config.getConfig();
|
|
148
|
+
const custom = cfg.customVerbosity ?? {};
|
|
149
|
+
const keyMap = {
|
|
150
|
+
'prompt-submit': 'promptSubmit',
|
|
151
|
+
'response-complete': 'responseComplete',
|
|
152
|
+
};
|
|
153
|
+
const key = keyMap[hookType];
|
|
154
|
+
if (key === undefined) return true;
|
|
155
|
+
return custom[key] !== false; // default true if not configured
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default VerbosityService;
|
|
@@ -201,8 +201,9 @@ if [[ -z "$TEXT" ]]; then
|
|
|
201
201
|
exit 1
|
|
202
202
|
fi
|
|
203
203
|
|
|
204
|
-
# SECURITY: Validate voice format (
|
|
205
|
-
|
|
204
|
+
# SECURITY: Validate voice format (allow :: for multi-speaker, . for locale, space for names)
|
|
205
|
+
_voice_re='^[a-zA-Z0-9_.: -]+$'
|
|
206
|
+
if [[ ! "$VOICE" =~ $_voice_re ]]; then
|
|
206
207
|
echo "Error: Invalid voice format" >&2
|
|
207
208
|
exit 1
|
|
208
209
|
fi
|