voice-mode 4.3.2__py3-none-any.whl → 4.5.0__py3-none-any.whl
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.
- voice_mode/__version__.py +1 -1
- voice_mode/cli.py +82 -5
- voice_mode/cli_commands/claude.py +6 -199
- voice_mode/cli_commands/hook.py +13 -16
- voice_mode/cli_commands/transcribe.py +7 -6
- voice_mode/config.py +1 -1
- voice_mode/conversation_logger.py +6 -0
- voice_mode/core.py +9 -2
- voice_mode/frontend/.next/BUILD_ID +1 -1
- voice_mode/frontend/.next/app-build-manifest.json +5 -5
- voice_mode/frontend/.next/build-manifest.json +3 -3
- voice_mode/frontend/.next/next-minimal-server.js.nft.json +1 -1
- voice_mode/frontend/.next/next-server.js.nft.json +1 -1
- voice_mode/frontend/.next/prerender-manifest.json +1 -1
- voice_mode/frontend/.next/required-server-files.json +1 -1
- voice_mode/frontend/.next/server/app/_not-found/page.js +1 -1
- voice_mode/frontend/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- voice_mode/frontend/.next/server/app/_not-found.html +1 -1
- voice_mode/frontend/.next/server/app/_not-found.rsc +1 -1
- voice_mode/frontend/.next/server/app/api/connection-details/route.js +2 -2
- voice_mode/frontend/.next/server/app/favicon.ico/route.js +2 -2
- voice_mode/frontend/.next/server/app/index.html +1 -1
- voice_mode/frontend/.next/server/app/index.rsc +2 -2
- voice_mode/frontend/.next/server/app/page.js +2 -2
- voice_mode/frontend/.next/server/app/page_client-reference-manifest.js +1 -1
- voice_mode/frontend/.next/server/chunks/994.js +2 -2
- voice_mode/frontend/.next/server/middleware-build-manifest.js +1 -1
- voice_mode/frontend/.next/server/next-font-manifest.js +1 -1
- voice_mode/frontend/.next/server/next-font-manifest.json +1 -1
- voice_mode/frontend/.next/server/pages/404.html +1 -1
- voice_mode/frontend/.next/server/pages/500.html +1 -1
- voice_mode/frontend/.next/server/server-reference-manifest.json +1 -1
- voice_mode/frontend/.next/standalone/.next/BUILD_ID +1 -1
- voice_mode/frontend/.next/standalone/.next/app-build-manifest.json +5 -5
- voice_mode/frontend/.next/standalone/.next/build-manifest.json +3 -3
- voice_mode/frontend/.next/standalone/.next/prerender-manifest.json +1 -1
- voice_mode/frontend/.next/standalone/.next/required-server-files.json +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found.html +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/api/connection-details/route.js +2 -2
- voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico/route.js +2 -2
- voice_mode/frontend/.next/standalone/.next/server/app/index.html +1 -1
- voice_mode/frontend/.next/standalone/.next/server/app/index.rsc +2 -2
- voice_mode/frontend/.next/standalone/.next/server/app/page.js +2 -2
- voice_mode/frontend/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/chunks/994.js +2 -2
- voice_mode/frontend/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.js +1 -1
- voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.json +1 -1
- voice_mode/frontend/.next/standalone/.next/server/pages/404.html +1 -1
- voice_mode/frontend/.next/standalone/.next/server/pages/500.html +1 -1
- voice_mode/frontend/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- voice_mode/frontend/.next/standalone/server.js +1 -1
- voice_mode/frontend/.next/static/chunks/app/layout-d3ec7f6f14ea7396.js +1 -0
- voice_mode/frontend/.next/static/chunks/app/{page-728ce55710c9bd80.js → page-471796963fb1a4bd.js} +1 -1
- voice_mode/frontend/.next/static/chunks/{main-app-991c8c54319817e0.js → main-app-78da5e437b6a2a9f.js} +1 -1
- voice_mode/frontend/.next/trace +43 -43
- voice_mode/frontend/.next/types/app/api/connection-details/route.ts +1 -1
- voice_mode/frontend/.next/types/app/layout.ts +1 -1
- voice_mode/frontend/.next/types/app/page.ts +1 -1
- voice_mode/frontend/package-lock.json +26 -15
- voice_mode/provider_discovery.py +55 -79
- voice_mode/providers.py +61 -45
- voice_mode/simple_failover.py +41 -12
- voice_mode/statistics_tracking.py +42 -0
- voice_mode/tools/__init__.py +150 -16
- voice_mode/tools/converse.py +149 -338
- voice_mode/tools/diagnostics.py +2 -1
- voice_mode/tools/statistics.py +0 -33
- voice_mode/tools/voice_registry.py +24 -28
- {voice_mode-4.3.2.dist-info → voice_mode-4.5.0.dist-info}/METADATA +36 -41
- {voice_mode-4.3.2.dist-info → voice_mode-4.5.0.dist-info}/RECORD +78 -78
- voice_mode/frontend/.next/static/chunks/app/layout-d89915a94cb8f604.js +0 -1
- voice_mode/tools/claude_thinking.py +0 -285
- /voice_mode/frontend/.next/static/{-9MkXl-xGB2BLmGEAZVmg → Ni4GIqyDdn0QehvmlLBZg}/_buildManifest.js +0 -0
- /voice_mode/frontend/.next/static/{-9MkXl-xGB2BLmGEAZVmg → Ni4GIqyDdn0QehvmlLBZg}/_ssgManifest.js +0 -0
- {voice_mode-4.3.2.dist-info → voice_mode-4.5.0.dist-info}/WHEEL +0 -0
- {voice_mode-4.3.2.dist-info → voice_mode-4.5.0.dist-info}/entry_points.txt +0 -0
voice_mode/__version__.py
CHANGED
voice_mode/cli.py
CHANGED
@@ -5,6 +5,8 @@ import asyncio
|
|
5
5
|
import sys
|
6
6
|
import os
|
7
7
|
import warnings
|
8
|
+
import subprocess
|
9
|
+
import shutil
|
8
10
|
import click
|
9
11
|
|
10
12
|
|
@@ -30,10 +32,12 @@ if not os.environ.get('VOICEMODE_DEBUG', '').lower() in ('true', '1', 'yes'):
|
|
30
32
|
@click.version_option()
|
31
33
|
@click.help_option('-h', '--help', help='Show this message and exit')
|
32
34
|
@click.option('--debug', is_flag=True, help='Enable debug mode and show all warnings')
|
35
|
+
@click.option('--tools-enabled', help='Comma-separated list of tools to enable (whitelist)')
|
36
|
+
@click.option('--tools-disabled', help='Comma-separated list of tools to disable (blacklist)')
|
33
37
|
@click.pass_context
|
34
|
-
def voice_mode_main_cli(ctx, debug):
|
38
|
+
def voice_mode_main_cli(ctx, debug, tools_enabled, tools_disabled):
|
35
39
|
"""Voice Mode - MCP server and service management.
|
36
|
-
|
40
|
+
|
37
41
|
Without arguments, starts the MCP server.
|
38
42
|
With subcommands, executes service management operations.
|
39
43
|
"""
|
@@ -44,7 +48,13 @@ def voice_mode_main_cli(ctx, debug):
|
|
44
48
|
# Re-enable INFO logging
|
45
49
|
import logging
|
46
50
|
logging.getLogger("voice-mode").setLevel(logging.INFO)
|
47
|
-
|
51
|
+
|
52
|
+
# Set environment variables from CLI args
|
53
|
+
if tools_enabled:
|
54
|
+
os.environ['VOICEMODE_TOOLS_ENABLED'] = tools_enabled
|
55
|
+
if tools_disabled:
|
56
|
+
os.environ['VOICEMODE_TOOLS_DISABLED'] = tools_disabled
|
57
|
+
|
48
58
|
if ctx.invoked_subcommand is None:
|
49
59
|
# No subcommand - run MCP server
|
50
60
|
# Note: warnings are already suppressed at module level unless debug is enabled
|
@@ -1277,6 +1287,72 @@ def config_set(key, value):
|
|
1277
1287
|
click.echo(result)
|
1278
1288
|
|
1279
1289
|
|
1290
|
+
@config.command("edit")
|
1291
|
+
@click.help_option('-h', '--help')
|
1292
|
+
@click.option('--editor', help='Editor to use (overrides $EDITOR)')
|
1293
|
+
def config_edit(editor):
|
1294
|
+
"""Open the configuration file in your default editor.
|
1295
|
+
|
1296
|
+
Opens ~/.voicemode/voicemode.env in your configured editor.
|
1297
|
+
Uses $EDITOR environment variable by default, or you can specify with --editor.
|
1298
|
+
|
1299
|
+
Examples:
|
1300
|
+
voicemode config edit # Use $EDITOR
|
1301
|
+
voicemode config edit --editor vim
|
1302
|
+
voicemode config edit --editor "code --wait"
|
1303
|
+
"""
|
1304
|
+
from pathlib import Path
|
1305
|
+
|
1306
|
+
# Find the config file
|
1307
|
+
config_path = Path.home() / ".voicemode" / "voicemode.env"
|
1308
|
+
|
1309
|
+
# Create default config if it doesn't exist
|
1310
|
+
if not config_path.exists():
|
1311
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
1312
|
+
from voice_mode.config import load_voicemode_env
|
1313
|
+
# This will create the default config
|
1314
|
+
load_voicemode_env()
|
1315
|
+
|
1316
|
+
# Determine which editor to use
|
1317
|
+
if editor:
|
1318
|
+
editor_cmd = editor
|
1319
|
+
else:
|
1320
|
+
# Try environment variables in order of preference
|
1321
|
+
editor_cmd = (
|
1322
|
+
os.environ.get('EDITOR') or
|
1323
|
+
os.environ.get('VISUAL') or
|
1324
|
+
shutil.which('nano') or
|
1325
|
+
shutil.which('vim') or
|
1326
|
+
shutil.which('vi')
|
1327
|
+
)
|
1328
|
+
|
1329
|
+
if not editor_cmd:
|
1330
|
+
click.echo("❌ No editor found. Please set $EDITOR or use --editor")
|
1331
|
+
click.echo(" Example: export EDITOR=vim")
|
1332
|
+
click.echo(" Or use: voicemode config edit --editor vim")
|
1333
|
+
return
|
1334
|
+
|
1335
|
+
# Handle complex editor commands (e.g., "code --wait")
|
1336
|
+
if ' ' in editor_cmd:
|
1337
|
+
import shlex
|
1338
|
+
cmd_parts = shlex.split(editor_cmd)
|
1339
|
+
cmd = cmd_parts + [str(config_path)]
|
1340
|
+
else:
|
1341
|
+
cmd = [editor_cmd, str(config_path)]
|
1342
|
+
|
1343
|
+
# Open the editor
|
1344
|
+
try:
|
1345
|
+
click.echo(f"Opening {config_path} in {editor_cmd}...")
|
1346
|
+
subprocess.run(cmd, check=True)
|
1347
|
+
click.echo("✅ Configuration file edited successfully")
|
1348
|
+
click.echo("\nChanges will take effect when voicemode is restarted.")
|
1349
|
+
except subprocess.CalledProcessError:
|
1350
|
+
click.echo(f"❌ Editor exited with an error")
|
1351
|
+
except FileNotFoundError:
|
1352
|
+
click.echo(f"❌ Editor not found: {editor_cmd}")
|
1353
|
+
click.echo(" Please check that the editor is installed and in your PATH")
|
1354
|
+
|
1355
|
+
|
1280
1356
|
# Diagnostics group
|
1281
1357
|
@voice_mode_main_cli.group()
|
1282
1358
|
@click.help_option('-h', '--help', help='Show this message and exit')
|
@@ -1394,8 +1470,9 @@ transcribe_audio_cmd = transcribe_cmd.transcribe.commands['audio']
|
|
1394
1470
|
transcribe_audio_cmd.name = 'transcribe'
|
1395
1471
|
audio.add_command(transcribe_audio_cmd)
|
1396
1472
|
|
1397
|
-
# Add
|
1398
|
-
|
1473
|
+
# Add hooks command under claude group
|
1474
|
+
from voice_mode.cli_commands.hook import hooks
|
1475
|
+
claude.claude_group.add_command(hooks)
|
1399
1476
|
|
1400
1477
|
# Add pronounce under config group
|
1401
1478
|
config.add_command(pronounce_commands.pronounce_group)
|
@@ -1,208 +1,15 @@
|
|
1
|
-
"""CLI commands for Claude
|
1
|
+
"""CLI commands for Claude-related functionality."""
|
2
2
|
|
3
3
|
import click
|
4
|
-
import json
|
5
|
-
import os
|
6
|
-
import logging
|
7
|
-
from pathlib import Path
|
8
|
-
from typing import Optional, List
|
9
|
-
from datetime import datetime
|
10
|
-
|
11
|
-
from voice_mode.tools.claude_thinking import (
|
12
|
-
find_claude_log_file,
|
13
|
-
extract_messages_from_log,
|
14
|
-
extract_thinking_from_messages
|
15
|
-
)
|
16
|
-
|
17
|
-
logger = logging.getLogger("voice-mode")
|
18
4
|
|
19
5
|
|
20
6
|
@click.group(name='claude')
|
21
7
|
def claude_group():
|
22
|
-
"""
|
8
|
+
"""Claude-related commands."""
|
23
9
|
pass
|
24
10
|
|
25
11
|
|
26
|
-
@
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
type=click.Choice(['user', 'assistant']),
|
31
|
-
help='Filter by message type (can specify multiple)')
|
32
|
-
@click.option('--format', '-f',
|
33
|
-
type=click.Choice(['full', 'text', 'thinking']),
|
34
|
-
default='full',
|
35
|
-
help='Output format: full (complete), text (text only), thinking (thinking only)')
|
36
|
-
@click.option('--working-dir', '-d', type=click.Path(exists=True),
|
37
|
-
help='Working directory to find logs for (defaults to CWD)')
|
38
|
-
@click.option('--output', '-o', type=click.Path(),
|
39
|
-
help='Save output to file')
|
40
|
-
@click.option('--json', 'as_json', is_flag=True,
|
41
|
-
help='Output as JSON (overrides format)')
|
42
|
-
def messages_command(last_n: int, message_types: tuple, format: str,
|
43
|
-
working_dir: Optional[str], output: Optional[str],
|
44
|
-
as_json: bool):
|
45
|
-
"""
|
46
|
-
Extract recent messages from Claude Code logs.
|
47
|
-
|
48
|
-
Examples:
|
49
|
-
|
50
|
-
voicemode claude messages
|
51
|
-
|
52
|
-
voicemode claude messages -n 5 --format thinking
|
53
|
-
|
54
|
-
voicemode claude messages --type assistant --format text
|
55
|
-
|
56
|
-
voicemode claude messages --json -o messages.json
|
57
|
-
"""
|
58
|
-
# Find log file
|
59
|
-
log_file = find_claude_log_file(working_dir)
|
60
|
-
if not log_file:
|
61
|
-
click.echo(f"Error: Could not find Claude Code logs for directory: {working_dir or os.getcwd()}", err=True)
|
62
|
-
return
|
63
|
-
|
64
|
-
# Extract messages
|
65
|
-
message_type_list = list(message_types) if message_types else None
|
66
|
-
messages = extract_messages_from_log(log_file, last_n, message_type_list)
|
67
|
-
|
68
|
-
if not messages:
|
69
|
-
click.echo("No messages found in recent logs", err=True)
|
70
|
-
return
|
71
|
-
|
72
|
-
# Format output
|
73
|
-
if as_json:
|
74
|
-
content = json.dumps(messages, indent=2)
|
75
|
-
elif format == 'thinking':
|
76
|
-
thinking_texts = extract_thinking_from_messages(messages)
|
77
|
-
if not thinking_texts:
|
78
|
-
click.echo("No thinking content found", err=True)
|
79
|
-
return
|
80
|
-
content = "\n\n=== Next Thinking ===\n\n".join(thinking_texts)
|
81
|
-
elif format == 'text':
|
82
|
-
result = []
|
83
|
-
for msg in messages:
|
84
|
-
content_text = []
|
85
|
-
content_items = msg.get('content', [])
|
86
|
-
logger.debug(f"Message has {len(content_items)} content items")
|
87
|
-
for item in content_items:
|
88
|
-
if isinstance(item, dict):
|
89
|
-
item_type = item.get('type')
|
90
|
-
logger.debug(f"Content item type: {item_type}")
|
91
|
-
if item_type == 'text':
|
92
|
-
text = item.get('text', '')
|
93
|
-
if text:
|
94
|
-
content_text.append(text)
|
95
|
-
elif item_type == 'thinking':
|
96
|
-
text = item.get('text', '')
|
97
|
-
if text:
|
98
|
-
content_text.append(f"[Thinking: {text}]")
|
99
|
-
if content_text:
|
100
|
-
result.append(f"{msg['type'].title()}: {' '.join(content_text)}")
|
101
|
-
content = "\n\n".join(result)
|
102
|
-
else: # full format
|
103
|
-
# Format as human-readable
|
104
|
-
result = []
|
105
|
-
for i, msg in enumerate(messages, 1):
|
106
|
-
result.append(f"=== Message {i} ===")
|
107
|
-
result.append(f"Type: {msg['type']}")
|
108
|
-
result.append(f"Timestamp: {msg.get('timestamp', 'Unknown')}")
|
109
|
-
if msg.get('model'):
|
110
|
-
result.append(f"Model: {msg['model']}")
|
111
|
-
|
112
|
-
# Format content
|
113
|
-
content_items = msg.get('content', [])
|
114
|
-
if content_items:
|
115
|
-
result.append("Content:")
|
116
|
-
for item in content_items:
|
117
|
-
if isinstance(item, dict):
|
118
|
-
item_type = item.get('type', 'unknown')
|
119
|
-
if item_type == 'text':
|
120
|
-
result.append(f" [Text]: {item.get('text', '')}")
|
121
|
-
elif item_type == 'thinking':
|
122
|
-
result.append(f" [Thinking]: {item.get('text', '')}")
|
123
|
-
elif item_type == 'tool_use':
|
124
|
-
result.append(f" [Tool Use]: {item.get('name', '')}")
|
125
|
-
elif item_type == 'tool_result':
|
126
|
-
result.append(f" [Tool Result]: {item.get('content', '')[:100]}...")
|
127
|
-
result.append("")
|
128
|
-
content = "\n".join(result).strip()
|
129
|
-
|
130
|
-
# Output
|
131
|
-
if output:
|
132
|
-
Path(output).write_text(content)
|
133
|
-
click.echo(f"Output saved to {output}")
|
134
|
-
else:
|
135
|
-
# Debug: ensure content is printed
|
136
|
-
logger.debug(f"About to output {len(content)} characters")
|
137
|
-
click.echo(content)
|
138
|
-
logger.debug("Output complete")
|
139
|
-
|
140
|
-
|
141
|
-
@claude_group.command(name='thinking')
|
142
|
-
@click.option('--last-n', '-n', default=1, type=int,
|
143
|
-
help='Number of messages to search for thinking (default: 1)')
|
144
|
-
@click.option('--working-dir', '-d', type=click.Path(exists=True),
|
145
|
-
help='Working directory to find logs for (defaults to CWD)')
|
146
|
-
def thinking_command(last_n: int, working_dir: Optional[str]):
|
147
|
-
"""
|
148
|
-
Extract only thinking content from Claude Code logs.
|
149
|
-
|
150
|
-
Convenience command equivalent to:
|
151
|
-
voicemode claude messages --format thinking --type assistant
|
152
|
-
|
153
|
-
Examples:
|
154
|
-
|
155
|
-
voicemode claude thinking
|
156
|
-
|
157
|
-
voicemode claude thinking -n 3
|
158
|
-
"""
|
159
|
-
# Delegate to messages command with thinking format
|
160
|
-
ctx = click.get_current_context()
|
161
|
-
ctx.invoke(messages_command,
|
162
|
-
last_n=last_n,
|
163
|
-
message_types=('assistant',),
|
164
|
-
format='thinking',
|
165
|
-
working_dir=working_dir,
|
166
|
-
output=None,
|
167
|
-
as_json=False)
|
168
|
-
|
169
|
-
|
170
|
-
@claude_group.command(name='check')
|
171
|
-
def check_command():
|
172
|
-
"""
|
173
|
-
Check if Claude Code context is available.
|
174
|
-
|
175
|
-
Shows information about the Claude Code environment including:
|
176
|
-
- Whether Claude Code logs are accessible
|
177
|
-
- Current working directory
|
178
|
-
- Log file location if found
|
179
|
-
- Last update time
|
180
|
-
|
181
|
-
Example:
|
182
|
-
voicemode claude check
|
183
|
-
"""
|
184
|
-
working_dir = os.getcwd()
|
185
|
-
log_file = find_claude_log_file(working_dir)
|
186
|
-
|
187
|
-
click.echo(f"Working Directory: {working_dir}")
|
188
|
-
click.echo(f"Claude Logs Found: {'Yes' if log_file else 'No'}")
|
189
|
-
|
190
|
-
if log_file:
|
191
|
-
click.echo(f"Log File: {log_file}")
|
192
|
-
click.echo(f"Log Size: {log_file.stat().st_size:,} bytes")
|
193
|
-
|
194
|
-
mtime = datetime.fromtimestamp(log_file.stat().st_mtime)
|
195
|
-
now = datetime.now()
|
196
|
-
age = now - mtime
|
197
|
-
|
198
|
-
if age.total_seconds() < 60:
|
199
|
-
click.echo(f"Last Updated: {int(age.total_seconds())} seconds ago")
|
200
|
-
elif age.total_seconds() < 3600:
|
201
|
-
click.echo(f"Last Updated: {int(age.total_seconds() / 60)} minutes ago")
|
202
|
-
else:
|
203
|
-
click.echo(f"Last Updated: {int(age.total_seconds() / 3600)} hours ago")
|
204
|
-
else:
|
205
|
-
project_dir = working_dir.replace('/', '-')
|
206
|
-
expected_path = Path.home() / '.claude' / 'projects' / project_dir
|
207
|
-
click.echo(f"Expected Log Location: {expected_path}")
|
208
|
-
click.echo("Note: Logs are only created when using Claude Code (claude.ai/code)")
|
12
|
+
@click.group(name='hooks')
|
13
|
+
def hooks_group():
|
14
|
+
"""Manage Voice Mode hooks and event handlers for Claude Code."""
|
15
|
+
pass
|
voice_mode/cli_commands/hook.py
CHANGED
@@ -12,18 +12,18 @@ from typing import Optional
|
|
12
12
|
|
13
13
|
@click.group()
|
14
14
|
@click.help_option('-h', '--help', help='Show this message and exit')
|
15
|
-
def
|
15
|
+
def hooks():
|
16
16
|
"""Manage Voice Mode hooks and event handlers."""
|
17
17
|
pass
|
18
18
|
|
19
19
|
|
20
|
-
@
|
20
|
+
@hooks.command('receiver')
|
21
21
|
@click.option('--tool-name', help='Override tool name (for testing)')
|
22
22
|
@click.option('--action', type=click.Choice(['start', 'end']), help='Override action (for testing)')
|
23
23
|
@click.option('--subagent-type', help='Override subagent type (for testing)')
|
24
24
|
@click.option('--event', type=click.Choice(['PreToolUse', 'PostToolUse']), help='Override event (for testing)')
|
25
25
|
@click.option('--debug', is_flag=True, help='Enable debug output')
|
26
|
-
def
|
26
|
+
def receiver(tool_name, action, subagent_type, event, debug):
|
27
27
|
"""Receive and process hook events from Claude Code via stdin.
|
28
28
|
|
29
29
|
This command reads JSON from stdin when called by Claude Code hooks,
|
@@ -34,13 +34,13 @@ def stdin_receiver(tool_name, action, subagent_type, event, debug):
|
|
34
34
|
|
35
35
|
Examples:
|
36
36
|
# Called by Claude Code (reads JSON from stdin)
|
37
|
-
voicemode
|
37
|
+
voicemode claude hooks receiver
|
38
38
|
|
39
39
|
# Testing with defaults
|
40
|
-
voicemode
|
40
|
+
voicemode claude hooks receiver --debug
|
41
41
|
|
42
42
|
# Testing with specific values
|
43
|
-
voicemode
|
43
|
+
voicemode claude hooks receiver --tool-name Task --action start --subagent-type mama-bear
|
44
44
|
"""
|
45
45
|
from voice_mode.tools.sound_fonts.audio_player import Player
|
46
46
|
|
@@ -188,22 +188,19 @@ def find_sound_file(event: str, tool: str, subagent: Optional[str] = None) -> Op
|
|
188
188
|
return None
|
189
189
|
|
190
190
|
|
191
|
-
# Keep the old receiver command for backwards compatibility (deprecated)
|
192
|
-
@
|
191
|
+
# Keep the old stdin-receiver command for backwards compatibility (deprecated)
|
192
|
+
@hooks.command('stdin-receiver', hidden=True)
|
193
193
|
@click.argument('tool_name')
|
194
194
|
@click.argument('action', type=click.Choice(['start', 'end', 'complete']))
|
195
195
|
@click.argument('subagent_type', required=False)
|
196
196
|
@click.option('--debug', is_flag=True, help='Enable debug output')
|
197
|
-
def
|
198
|
-
"""[DEPRECATED] Use
|
199
|
-
#
|
200
|
-
event = 'PreToolUse' if action == 'start' else 'PostToolUse'
|
201
|
-
|
202
|
-
# Call the new command
|
197
|
+
def stdin_receiver_deprecated(tool_name, action, subagent_type, event, debug):
|
198
|
+
"""[DEPRECATED] Use receiver instead."""
|
199
|
+
# Call the new receiver command
|
203
200
|
ctx = click.get_current_context()
|
204
|
-
ctx.invoke(
|
201
|
+
ctx.invoke(receiver,
|
205
202
|
tool_name=tool_name,
|
206
|
-
action=action
|
203
|
+
action=action,
|
207
204
|
subagent_type=subagent_type,
|
208
205
|
event=event,
|
209
206
|
debug=debug)
|
@@ -6,12 +6,6 @@ import asyncio
|
|
6
6
|
from pathlib import Path
|
7
7
|
from typing import Optional
|
8
8
|
|
9
|
-
from voice_mode.tools.transcription import (
|
10
|
-
transcribe_audio,
|
11
|
-
TranscriptionBackend,
|
12
|
-
OutputFormat
|
13
|
-
)
|
14
|
-
|
15
9
|
|
16
10
|
@click.group()
|
17
11
|
def transcribe():
|
@@ -61,6 +55,13 @@ def audio_command(
|
|
61
55
|
voice-mode transcribe audio spanish.mp3 --language es --backend whisperx
|
62
56
|
"""
|
63
57
|
async def run():
|
58
|
+
# Import here to avoid loading tools at module level
|
59
|
+
from voice_mode.tools.transcription import (
|
60
|
+
transcribe_audio,
|
61
|
+
TranscriptionBackend,
|
62
|
+
OutputFormat
|
63
|
+
)
|
64
|
+
|
64
65
|
# Perform transcription
|
65
66
|
result = await transcribe_audio(
|
66
67
|
audio_file=audio_file,
|
voice_mode/config.py
CHANGED
@@ -253,7 +253,7 @@ PREFER_LOCAL = os.getenv("VOICEMODE_PREFER_LOCAL", "true").lower() in ("true", "
|
|
253
253
|
ALWAYS_TRY_LOCAL = os.getenv("VOICEMODE_ALWAYS_TRY_LOCAL", "true").lower() in ("true", "1", "yes", "on")
|
254
254
|
|
255
255
|
# Use simple failover without health checks
|
256
|
-
|
256
|
+
# Simple failover is now the only mode - configuration removed
|
257
257
|
|
258
258
|
# Auto-start configuration
|
259
259
|
AUTO_START_KOKORO = os.getenv("VOICEMODE_AUTO_START_KOKORO", "").lower() in ("true", "1", "yes", "on")
|
@@ -189,6 +189,9 @@ class ConversationLogger:
|
|
189
189
|
"timing": kwargs.get("timing"),
|
190
190
|
"silence_detection": kwargs.get("silence_detection"),
|
191
191
|
"error": kwargs.get("error"),
|
192
|
+
# Fallback information
|
193
|
+
"is_fallback": kwargs.get("is_fallback"),
|
194
|
+
"fallback_reason": kwargs.get("fallback_reason"),
|
192
195
|
# Timing metrics
|
193
196
|
"transcription_time": kwargs.get("transcription_time"),
|
194
197
|
"total_turnaround_time": kwargs.get("total_turnaround_time"),
|
@@ -205,6 +208,9 @@ class ConversationLogger:
|
|
205
208
|
"provider": kwargs.get("provider"),
|
206
209
|
"provider_url": kwargs.get("provider_url"),
|
207
210
|
"provider_type": kwargs.get("provider_type"),
|
211
|
+
# Fallback information
|
212
|
+
"is_fallback": kwargs.get("is_fallback"),
|
213
|
+
"fallback_reason": kwargs.get("fallback_reason"),
|
208
214
|
"audio_format": kwargs.get("audio_format"),
|
209
215
|
"timing": kwargs.get("timing"),
|
210
216
|
"transport": kwargs.get("transport"),
|
voice_mode/core.py
CHANGED
@@ -18,6 +18,7 @@ from typing import Optional
|
|
18
18
|
import numpy as np
|
19
19
|
from pydub import AudioSegment
|
20
20
|
from openai import AsyncOpenAI
|
21
|
+
from .provider_discovery import is_local_provider
|
21
22
|
import httpx
|
22
23
|
|
23
24
|
from .config import SAMPLE_RATE
|
@@ -135,16 +136,22 @@ def get_openai_clients(api_key: str, stt_base_url: Optional[str] = None, tts_bas
|
|
135
136
|
'limits': httpx.Limits(max_keepalive_connections=5, max_connections=10),
|
136
137
|
}
|
137
138
|
|
139
|
+
# Disable retries for local endpoints - they either work or don't
|
140
|
+
stt_max_retries = 0 if is_local_provider(stt_base_url) else 2
|
141
|
+
tts_max_retries = 0 if is_local_provider(tts_base_url) else 2
|
142
|
+
|
138
143
|
return {
|
139
144
|
'stt': AsyncOpenAI(
|
140
145
|
api_key=api_key,
|
141
146
|
base_url=stt_base_url,
|
142
|
-
http_client=httpx.AsyncClient(**http_client_config)
|
147
|
+
http_client=httpx.AsyncClient(**http_client_config),
|
148
|
+
max_retries=stt_max_retries
|
143
149
|
),
|
144
150
|
'tts': AsyncOpenAI(
|
145
151
|
api_key=api_key,
|
146
152
|
base_url=tts_base_url,
|
147
|
-
http_client=httpx.AsyncClient(**http_client_config)
|
153
|
+
http_client=httpx.AsyncClient(**http_client_config),
|
154
|
+
max_retries=tts_max_retries
|
148
155
|
)
|
149
156
|
}
|
150
157
|
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
Ni4GIqyDdn0QehvmlLBZg
|
@@ -4,25 +4,25 @@
|
|
4
4
|
"static/chunks/webpack-0ea9b80f19935b70.js",
|
5
5
|
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
6
6
|
"static/chunks/117-40bc79a2b97edb21.js",
|
7
|
-
"static/chunks/main-app-
|
7
|
+
"static/chunks/main-app-78da5e437b6a2a9f.js",
|
8
8
|
"static/chunks/app/_not-found/page-5011050e402ab9c8.js"
|
9
9
|
],
|
10
10
|
"/layout": [
|
11
11
|
"static/chunks/webpack-0ea9b80f19935b70.js",
|
12
12
|
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
13
13
|
"static/chunks/117-40bc79a2b97edb21.js",
|
14
|
-
"static/chunks/main-app-
|
14
|
+
"static/chunks/main-app-78da5e437b6a2a9f.js",
|
15
15
|
"static/css/a2f49a47752b5010.css",
|
16
|
-
"static/chunks/app/layout-
|
16
|
+
"static/chunks/app/layout-d3ec7f6f14ea7396.js"
|
17
17
|
],
|
18
18
|
"/page": [
|
19
19
|
"static/chunks/webpack-0ea9b80f19935b70.js",
|
20
20
|
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
21
21
|
"static/chunks/117-40bc79a2b97edb21.js",
|
22
|
-
"static/chunks/main-app-
|
22
|
+
"static/chunks/main-app-78da5e437b6a2a9f.js",
|
23
23
|
"static/chunks/144d3bae-2d5f122b82426d88.js",
|
24
24
|
"static/chunks/471-bd4b96a33883dfa2.js",
|
25
|
-
"static/chunks/app/page-
|
25
|
+
"static/chunks/app/page-471796963fb1a4bd.js"
|
26
26
|
]
|
27
27
|
}
|
28
28
|
}
|
@@ -5,14 +5,14 @@
|
|
5
5
|
"devFiles": [],
|
6
6
|
"ampDevFiles": [],
|
7
7
|
"lowPriorityFiles": [
|
8
|
-
"static
|
9
|
-
"static
|
8
|
+
"static/Ni4GIqyDdn0QehvmlLBZg/_buildManifest.js",
|
9
|
+
"static/Ni4GIqyDdn0QehvmlLBZg/_ssgManifest.js"
|
10
10
|
],
|
11
11
|
"rootMainFiles": [
|
12
12
|
"static/chunks/webpack-0ea9b80f19935b70.js",
|
13
13
|
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
14
14
|
"static/chunks/117-40bc79a2b97edb21.js",
|
15
|
-
"static/chunks/main-app-
|
15
|
+
"static/chunks/main-app-78da5e437b6a2a9f.js"
|
16
16
|
],
|
17
17
|
"pages": {
|
18
18
|
"/_app": [
|
@@ -1 +1 @@
|
|
1
|
-
{"version":1,"files":["../node_modules/styled-jsx/index.js","../node_modules/styled-jsx/package.json","../node_modules/styled-jsx/dist/index/index.js","../node_modules/react/package.json","../node_modules/react/index.js","../node_modules/client-only/package.json","../node_modules/react/cjs/react.production.min.js","../node_modules/client-only/index.js","../node_modules/styled-jsx/style.js","../node_modules/next/dist/compiled/next-server/server.runtime.prod.js","../node_modules/next/package.json","../node_modules/next/dist/lib/constants.js","../node_modules/next/dist/server/body-streams.js","../node_modules/next/dist/lib/picocolors.js","../node_modules/next/dist/shared/lib/constants.js","../node_modules/next/dist/server/web/utils.js","../node_modules/next/dist/client/components/app-router-headers.js","../node_modules/next/dist/server/lib/trace/tracer.js","../node_modules/next/dist/server/lib/trace/constants.js","../node_modules/next/dist/client/components/static-generation-async-storage.external.js","../node_modules/next/dist/shared/lib/error-source.js","../node_modules/next/dist/shared/lib/modern-browserslist-target.js","../node_modules/next/dist/compiled/debug/package.json","../node_modules/next/dist/client/components/static-generation-async-storage-instance.js","../node_modules/next/dist/shared/lib/runtime-config.external.js","../node_modules/next/dist/compiled/debug/index.js","../node_modules/next/dist/compiled/ws/package.json","../node_modules/next/dist/compiled/
|
1
|
+
{"version":1,"files":["../node_modules/styled-jsx/index.js","../node_modules/styled-jsx/package.json","../node_modules/styled-jsx/dist/index/index.js","../node_modules/react/package.json","../node_modules/react/index.js","../node_modules/client-only/package.json","../node_modules/react/cjs/react.production.min.js","../node_modules/client-only/index.js","../node_modules/styled-jsx/style.js","../node_modules/next/dist/compiled/next-server/server.runtime.prod.js","../node_modules/next/package.json","../node_modules/next/dist/lib/constants.js","../node_modules/next/dist/server/body-streams.js","../node_modules/next/dist/lib/picocolors.js","../node_modules/next/dist/shared/lib/constants.js","../node_modules/next/dist/server/web/utils.js","../node_modules/next/dist/client/components/app-router-headers.js","../node_modules/next/dist/server/lib/trace/tracer.js","../node_modules/next/dist/server/lib/trace/constants.js","../node_modules/next/dist/client/components/static-generation-async-storage.external.js","../node_modules/next/dist/shared/lib/error-source.js","../node_modules/next/dist/shared/lib/modern-browserslist-target.js","../node_modules/next/dist/compiled/debug/package.json","../node_modules/next/dist/client/components/static-generation-async-storage-instance.js","../node_modules/next/dist/shared/lib/runtime-config.external.js","../node_modules/next/dist/compiled/debug/index.js","../node_modules/next/dist/compiled/ws/package.json","../node_modules/next/dist/compiled/lru-cache/package.json","../node_modules/next/dist/compiled/node-html-parser/package.json","../node_modules/@swc/helpers/_/_interop_require_default/package.json","../node_modules/next/dist/compiled/ws/index.js","../node_modules/next/dist/compiled/lru-cache/index.js","../node_modules/next/dist/compiled/node-html-parser/index.js","../node_modules/next/dist/client/components/async-local-storage.js","../node_modules/next/dist/compiled/@opentelemetry/api/package.json","../node_modules/@swc/helpers/package.json","../node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/parseStack.js","../node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/nodeStackFrames.js","../node_modules/next/dist/compiled/jsonwebtoken/package.json","../node_modules/next/dist/client/components/react-dev-overlay/server/middleware.js","../node_modules/@swc/helpers/cjs/_interop_require_default.cjs","../node_modules/next/dist/compiled/@opentelemetry/api/index.js","../node_modules/next/dist/compiled/jsonwebtoken/index.js","../node_modules/next/dist/compiled/browserslist/package.json","../node_modules/next/dist/compiled/browserslist/index.js","../node_modules/next/dist/client/components/react-dev-overlay/server/shared.js","../node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/getRawSourceMap.js","../node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/launchEditor.js","../node_modules/next/dist/compiled/babel/code-frame.js","../node_modules/next/dist/compiled/json5/package.json","../node_modules/next/dist/compiled/semver/package.json","../node_modules/next/dist/compiled/babel/package.json","../node_modules/next/dist/lib/semver-noop.js","../node_modules/next/dist/compiled/json5/index.js","../node_modules/next/dist/compiled/semver/index.js","../node_modules/next/dist/compiled/stacktrace-parser/package.json","../node_modules/next/dist/compiled/source-map08/package.json","../node_modules/caniuse-lite/dist/unpacker/agents.js","../node_modules/caniuse-lite/dist/unpacker/feature.js","../node_modules/caniuse-lite/dist/unpacker/region.js","../node_modules/next/dist/compiled/babel/bundle.js","../node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/getSourceMapUrl.js","../node_modules/next/dist/compiled/stacktrace-parser/stack-trace-parser.cjs.js","../node_modules/next/dist/compiled/source-map08/source-map.js","../node_modules/caniuse-lite/package.json","../node_modules/next/dist/compiled/babel/core.js","../node_modules/caniuse-lite/data/agents.js","../node_modules/caniuse-lite/dist/lib/statuses.js","../node_modules/caniuse-lite/dist/lib/supported.js","../node_modules/caniuse-lite/dist/unpacker/browsers.js","../node_modules/caniuse-lite/dist/unpacker/browserVersions.js","../node_modules/next/dist/compiled/shell-quote/package.json","../node_modules/next/dist/compiled/data-uri-to-buffer/package.json","../node_modules/next/dist/compiled/shell-quote/index.js","../node_modules/next/dist/compiled/data-uri-to-buffer/index.js","../node_modules/caniuse-lite/data/browsers.js","../node_modules/caniuse-lite/data/browserVersions.js","../node_modules/next/dist/compiled/babel-packages/package.json","../node_modules/next/dist/compiled/babel-packages/packages-bundle.js","../node_modules/next/dist/compiled/babel/parser.js","../node_modules/next/dist/compiled/babel/types.js","../node_modules/next/dist/compiled/babel/traverse.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/amp-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/app-router-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/entrypoints.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/head-manager-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/hooks-client-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/html-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/image-config-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/loadable-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/loadable.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/router-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/server-inserted-html.js","../node_modules/next/dist/server/future/route-modules/app-page/module.compiled.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/amp-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/app-router-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/entrypoints.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/head-manager-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/hooks-client-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/html-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/image-config-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/loadable-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/loadable.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/router-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/server-inserted-html.js","../node_modules/next/dist/server/future/route-modules/pages/module.compiled.js"]}
|