voice-mode 4.0.1__tar.gz → 4.1.0__tar.gz
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-4.0.1 → voice_mode-4.1.0}/CHANGELOG.md +14 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/PKG-INFO +1 -1
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/__version__.py +1 -1
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/cli.py +3 -0
- voice_mode-4.1.0/voice_mode/cli_commands/pronounce_commands.py +223 -0
- voice_mode-4.1.0/voice_mode/data/default_pronunciation.yaml +268 -0
- voice_mode-4.1.0/voice_mode/pronounce.py +397 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/converse.py +11 -0
- voice_mode-4.1.0/voice_mode/tools/pronounce.py +245 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/.gitignore +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/README.md +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/build_hooks.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/pyproject.toml +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/__main__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/cli_commands/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/cli_commands/exchanges.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/cli_commands/transcribe.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/config.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/conversation_logger.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/core.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/data/versions.json +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/conversations.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/filters.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/formatters.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/models.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/reader.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/stats.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/README.md +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/app/api/connection-details/route.ts +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/app/favicon.ico +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/app/globals.css +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/app/layout.tsx +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/app/page.tsx +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/components/CloseIcon.tsx +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/components/NoAgentNotification.tsx +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/components/TranscriptionView.tsx +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/hooks/useCombinedTranscriptions.ts +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/hooks/useLocalMicTrack.ts +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/next-env.d.ts +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/next.config.mjs +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/package-lock.json +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/package.json +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/pnpm-lock.yaml +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/postcss.config.mjs +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/tailwind.config.ts +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/tsconfig.json +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/prompts/README.md +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/prompts/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/prompts/converse.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/prompts/release_notes.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/prompts/services.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/provider_discovery.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/providers.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/audio_files.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/changelog.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/configuration.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/statistics.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/version.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/whisper_models.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/server.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/shared.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/simple_failover.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/statistics.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/streaming.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/com.voicemode.frontend.plist +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/com.voicemode.kokoro.plist +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/com.voicemode.livekit.plist +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/com.voicemode.whisper.plist +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/start-kokoro-with-health-check.sh +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/start-whisper-with-health-check.sh +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/scripts/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/scripts/start-whisper-server.sh +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/systemd/voicemode-frontend.service +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/systemd/voicemode-kokoro.service +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/systemd/voicemode-livekit.service +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/systemd/voicemode-whisper.service +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/configuration_management.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/dependencies.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/devices.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/diagnostics.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/providers.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/service.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/kokoro/install.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/kokoro/uninstall.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/list_versions.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/livekit/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/livekit/frontend.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/livekit/install.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/livekit/production_server.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/livekit/uninstall.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/version_info.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/install.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/list_models.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/model_active.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/model_benchmark.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/model_install.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/model_remove.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/models.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/uninstall.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/statistics.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/transcription/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/transcription/backends.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/transcription/core.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/transcription/formats.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/transcription/types.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/voice_registry.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/__init__.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/audio_diagnostics.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/event_logger.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/ffmpeg_check.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/format_migration.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/gpu_detection.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/migration_helpers.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/common.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/coreml_setup.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/kokoro_helpers.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/livekit_helpers.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/whisper_helpers.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/whisper_version.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/version_helpers.py +0 -0
- {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/version.py +0 -0
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [4.1.0] - 2025-09-01
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- **Pronunciation middleware for TTS/STT text processing**
|
14
|
+
- Configurable pronunciation rules system that processes text before TTS and after STT
|
15
|
+
- Regex-based text substitution rules with YAML configuration
|
16
|
+
- Separate TTS and STT rule sets for bidirectional corrections
|
17
|
+
- Privacy support - rules can be marked private to hide from LLM tool listings
|
18
|
+
- Default rules for common patterns (3M, PoE, GbE, etc.)
|
19
|
+
- Full CLI interface for managing pronunciation rules
|
20
|
+
- MCP tool for LLM-based rule management with `pronounce` tool
|
21
|
+
- Integrated into converse tool for automatic text processing
|
22
|
+
- New configuration file: `voice_mode/data/default_pronunciation.yaml`
|
23
|
+
|
10
24
|
## [4.0.1] - 2025-09-01
|
11
25
|
|
12
26
|
### Removed
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: voice-mode
|
3
|
-
Version: 4.0
|
3
|
+
Version: 4.1.0
|
4
4
|
Summary: VoiceMode - Voice interaction capabilities for AI assistants (formerly voice-mcp)
|
5
5
|
Project-URL: Homepage, https://github.com/mbailey/voicemode
|
6
6
|
Project-URL: Repository, https://github.com/mbailey/voicemode
|
@@ -1360,13 +1360,16 @@ def cli():
|
|
1360
1360
|
# Import subcommand groups
|
1361
1361
|
from voice_mode.cli_commands import exchanges as exchanges_cmd
|
1362
1362
|
from voice_mode.cli_commands import transcribe as transcribe_cmd
|
1363
|
+
from voice_mode.cli_commands import pronounce_commands
|
1363
1364
|
|
1364
1365
|
# Add subcommands to legacy CLI
|
1365
1366
|
cli.add_command(exchanges_cmd.exchanges)
|
1366
1367
|
cli.add_command(transcribe_cmd.transcribe)
|
1368
|
+
cli.add_command(pronounce_commands.pronounce_group)
|
1367
1369
|
|
1368
1370
|
# Add exchanges to main CLI
|
1369
1371
|
voice_mode_main_cli.add_command(exchanges_cmd.exchanges)
|
1372
|
+
voice_mode_main_cli.add_command(pronounce_commands.pronounce_group)
|
1370
1373
|
|
1371
1374
|
# Add transcribe to main CLI
|
1372
1375
|
voice_mode_main_cli.add_command(transcribe_cmd.transcribe)
|
@@ -0,0 +1,223 @@
|
|
1
|
+
"""CLI commands for managing pronunciation rules."""
|
2
|
+
|
3
|
+
import click
|
4
|
+
import yaml
|
5
|
+
import json
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Optional
|
8
|
+
|
9
|
+
from voice_mode.pronounce import get_manager
|
10
|
+
|
11
|
+
|
12
|
+
@click.group(name='pronounce')
|
13
|
+
def pronounce_group():
|
14
|
+
"""Manage pronunciation rules for TTS and STT."""
|
15
|
+
pass
|
16
|
+
|
17
|
+
|
18
|
+
@pronounce_group.command(name='list')
|
19
|
+
@click.option('--direction', '-d', type=click.Choice(['tts', 'stt', 'all']), default='all',
|
20
|
+
help='Filter by direction (tts/stt/all)')
|
21
|
+
@click.option('--enabled-only', '-e', is_flag=True, help='Show only enabled rules')
|
22
|
+
@click.option('--show-private', '-p', is_flag=True, help='Include private rules')
|
23
|
+
@click.option('--format', '-f', type=click.Choice(['table', 'yaml', 'json']), default='table',
|
24
|
+
help='Output format')
|
25
|
+
def list_rules(direction: str, enabled_only: bool, show_private: bool, format: str):
|
26
|
+
"""List pronunciation rules."""
|
27
|
+
manager = get_manager()
|
28
|
+
|
29
|
+
# Get rules
|
30
|
+
if direction == 'all':
|
31
|
+
rules = manager.list_rules(include_private=show_private)
|
32
|
+
else:
|
33
|
+
rules = manager.list_rules(direction=direction, include_private=show_private)
|
34
|
+
|
35
|
+
# Filter if needed
|
36
|
+
if enabled_only:
|
37
|
+
rules = [r for r in rules if r['enabled']]
|
38
|
+
|
39
|
+
# Format output
|
40
|
+
if format == 'table':
|
41
|
+
if not rules:
|
42
|
+
click.echo("No rules found.")
|
43
|
+
return
|
44
|
+
|
45
|
+
# Count private rules that were hidden
|
46
|
+
all_rules = manager.list_rules(include_private=True)
|
47
|
+
private_count = len(all_rules) - len(rules)
|
48
|
+
|
49
|
+
# Simple table format without tabulate
|
50
|
+
click.echo("\nPronunciation Rules:")
|
51
|
+
click.echo("=" * 80)
|
52
|
+
|
53
|
+
for rule in rules:
|
54
|
+
status = '✓' if rule['enabled'] else '✗'
|
55
|
+
click.echo(f"\n{status} [{rule['direction'].upper()}] {rule['name']} (order: {rule['order']})")
|
56
|
+
click.echo(f" Pattern: {rule['pattern']}")
|
57
|
+
click.echo(f" Replace: {rule['replacement']}")
|
58
|
+
if rule['description']:
|
59
|
+
click.echo(f" Desc: {rule['description']}")
|
60
|
+
|
61
|
+
if private_count > 0 and not show_private:
|
62
|
+
click.echo(f"\n({private_count} private rules hidden. Use --show-private to display)")
|
63
|
+
|
64
|
+
elif format == 'yaml':
|
65
|
+
import yaml
|
66
|
+
click.echo(yaml.dump(rules, default_flow_style=False))
|
67
|
+
|
68
|
+
elif format == 'json':
|
69
|
+
import json
|
70
|
+
click.echo(json.dumps(rules, indent=2))
|
71
|
+
|
72
|
+
|
73
|
+
@pronounce_group.command(name='test')
|
74
|
+
@click.argument('text')
|
75
|
+
@click.option('--direction', '-d', type=click.Choice(['tts', 'stt']), default='tts',
|
76
|
+
help='Test direction (tts/stt)')
|
77
|
+
def test_rule(text: str, direction: str):
|
78
|
+
"""Test pronunciation rules on text."""
|
79
|
+
manager = get_manager()
|
80
|
+
result = manager.test_rule(text, direction)
|
81
|
+
|
82
|
+
if text != result:
|
83
|
+
click.echo(f"Original: {text}")
|
84
|
+
click.echo(f"Modified: {result}")
|
85
|
+
|
86
|
+
# Show which rules were applied if logging is enabled
|
87
|
+
import os
|
88
|
+
if os.environ.get('VOICEMODE_PRONUNCIATION_LOG_SUBSTITUTIONS', '').lower() == 'true':
|
89
|
+
click.echo("\n(Check logs for applied rules)")
|
90
|
+
else:
|
91
|
+
click.echo(f"No changes: {text}")
|
92
|
+
|
93
|
+
|
94
|
+
@pronounce_group.command(name='add')
|
95
|
+
@click.option('--direction', '-d', type=click.Choice(['tts', 'stt']), required=True,
|
96
|
+
help='Rule direction (tts/stt)')
|
97
|
+
@click.option('--pattern', '-p', required=True, help='Regex pattern to match')
|
98
|
+
@click.option('--replacement', '-r', required=True, help='Replacement text')
|
99
|
+
@click.option('--name', '-n', help='Rule name (auto-generated if not provided)')
|
100
|
+
@click.option('--description', help='Rule description')
|
101
|
+
@click.option('--order', type=int, default=100, help='Processing order (lower = earlier)')
|
102
|
+
@click.option('--disabled', is_flag=True, help='Create rule as disabled')
|
103
|
+
def add_rule(direction: str, pattern: str, replacement: str, name: Optional[str],
|
104
|
+
description: str, order: int, disabled: bool):
|
105
|
+
"""Add a new pronunciation rule."""
|
106
|
+
manager = get_manager()
|
107
|
+
|
108
|
+
success = manager.add_rule(
|
109
|
+
direction=direction,
|
110
|
+
pattern=pattern,
|
111
|
+
replacement=replacement,
|
112
|
+
name=name,
|
113
|
+
description=description or "",
|
114
|
+
enabled=not disabled,
|
115
|
+
order=order,
|
116
|
+
private=False # CLI-created rules are not private
|
117
|
+
)
|
118
|
+
|
119
|
+
if success:
|
120
|
+
click.echo(f"✓ Rule added successfully")
|
121
|
+
else:
|
122
|
+
click.echo("✗ Failed to add rule (check pattern validity)", err=True)
|
123
|
+
|
124
|
+
|
125
|
+
@pronounce_group.command(name='remove')
|
126
|
+
@click.option('--direction', '-d', type=click.Choice(['tts', 'stt']), required=True,
|
127
|
+
help='Rule direction (tts/stt)')
|
128
|
+
@click.argument('name')
|
129
|
+
def remove_rule(direction: str, name: str):
|
130
|
+
"""Remove a pronunciation rule by name."""
|
131
|
+
manager = get_manager()
|
132
|
+
|
133
|
+
success = manager.remove_rule(direction, name)
|
134
|
+
|
135
|
+
if success:
|
136
|
+
click.echo(f"✓ Rule '{name}' removed")
|
137
|
+
else:
|
138
|
+
click.echo(f"✗ Rule '{name}' not found", err=True)
|
139
|
+
|
140
|
+
|
141
|
+
@pronounce_group.command(name='enable')
|
142
|
+
@click.option('--direction', '-d', type=click.Choice(['tts', 'stt']), required=True,
|
143
|
+
help='Rule direction (tts/stt)')
|
144
|
+
@click.argument('name')
|
145
|
+
def enable_rule(direction: str, name: str):
|
146
|
+
"""Enable a pronunciation rule."""
|
147
|
+
manager = get_manager()
|
148
|
+
|
149
|
+
success = manager.enable_rule(direction, name)
|
150
|
+
|
151
|
+
if success:
|
152
|
+
click.echo(f"✓ Rule '{name}' enabled")
|
153
|
+
else:
|
154
|
+
click.echo(f"✗ Failed to enable rule '{name}' (not found or private)", err=True)
|
155
|
+
|
156
|
+
|
157
|
+
@pronounce_group.command(name='disable')
|
158
|
+
@click.option('--direction', '-d', type=click.Choice(['tts', 'stt']), required=True,
|
159
|
+
help='Rule direction (tts/stt)')
|
160
|
+
@click.argument('name')
|
161
|
+
def disable_rule(direction: str, name: str):
|
162
|
+
"""Disable a pronunciation rule."""
|
163
|
+
manager = get_manager()
|
164
|
+
|
165
|
+
success = manager.disable_rule(direction, name)
|
166
|
+
|
167
|
+
if success:
|
168
|
+
click.echo(f"✓ Rule '{name}' disabled")
|
169
|
+
else:
|
170
|
+
click.echo(f"✗ Failed to disable rule '{name}' (not found or private)", err=True)
|
171
|
+
|
172
|
+
|
173
|
+
@pronounce_group.command(name='reload')
|
174
|
+
def reload_rules():
|
175
|
+
"""Reload pronunciation rules from configuration files."""
|
176
|
+
manager = get_manager()
|
177
|
+
manager.reload_rules()
|
178
|
+
click.echo("✓ Pronunciation rules reloaded")
|
179
|
+
|
180
|
+
|
181
|
+
@pronounce_group.command(name='edit')
|
182
|
+
@click.option('--system', is_flag=True, help='Edit system default rules (requires sudo)')
|
183
|
+
def edit_config(system: bool):
|
184
|
+
"""Open pronunciation config in editor."""
|
185
|
+
import os
|
186
|
+
import subprocess
|
187
|
+
|
188
|
+
if system:
|
189
|
+
# Edit system defaults
|
190
|
+
config_path = Path(__file__).parent.parent / 'data' / 'default_pronunciation.yaml'
|
191
|
+
if not config_path.exists():
|
192
|
+
click.echo(f"System config not found: {config_path}", err=True)
|
193
|
+
return
|
194
|
+
# Might need sudo
|
195
|
+
editor = os.environ.get('EDITOR', 'nano')
|
196
|
+
subprocess.run(['sudo', editor, str(config_path)])
|
197
|
+
else:
|
198
|
+
# Edit user config
|
199
|
+
config_path = Path.home() / '.voicemode' / 'config' / 'pronunciation.yaml'
|
200
|
+
if not config_path.exists():
|
201
|
+
# Create default config
|
202
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
203
|
+
default_config = {
|
204
|
+
'version': 1,
|
205
|
+
'tts_rules': [],
|
206
|
+
'stt_rules': []
|
207
|
+
}
|
208
|
+
with open(config_path, 'w') as f:
|
209
|
+
yaml.dump(default_config, f, default_flow_style=False)
|
210
|
+
|
211
|
+
editor = os.environ.get('EDITOR', 'nano')
|
212
|
+
subprocess.run([editor, str(config_path)])
|
213
|
+
|
214
|
+
# Reload after editing
|
215
|
+
manager = get_manager()
|
216
|
+
manager.reload_rules()
|
217
|
+
click.echo("✓ Configuration edited and reloaded")
|
218
|
+
|
219
|
+
|
220
|
+
# Register the command group
|
221
|
+
def register_commands(cli):
|
222
|
+
"""Register pronunciation commands with the main CLI."""
|
223
|
+
cli.add_command(pronounce_group)
|
@@ -0,0 +1,268 @@
|
|
1
|
+
version: 1
|
2
|
+
tts_rules:
|
3
|
+
# Company names
|
4
|
+
- name: "3m_company"
|
5
|
+
order: 10
|
6
|
+
pattern: '\b3M\b'
|
7
|
+
replacement: 'three em'
|
8
|
+
enabled: true
|
9
|
+
description: "Pronounce 3M company name correctly"
|
10
|
+
private: false
|
11
|
+
|
12
|
+
# Networking terms
|
13
|
+
- name: "poe_acronym"
|
14
|
+
order: 20
|
15
|
+
pattern: '\bPoE\+?\b'
|
16
|
+
replacement: 'P O E'
|
17
|
+
enabled: true
|
18
|
+
description: "Power over Ethernet acronym"
|
19
|
+
private: false
|
20
|
+
|
21
|
+
- name: "gigabit_ethernet"
|
22
|
+
order: 30
|
23
|
+
pattern: '\b(\d+(?:\.\d+)?)\s*GbE\b'
|
24
|
+
replacement: '$1 gigabit ethernet'
|
25
|
+
enabled: true
|
26
|
+
description: "Expand GbE to gigabit ethernet with number"
|
27
|
+
private: false
|
28
|
+
|
29
|
+
- name: "tcp_ip"
|
30
|
+
order: 40
|
31
|
+
pattern: '\bTCP/IP\b'
|
32
|
+
replacement: 'T C P I P'
|
33
|
+
enabled: true
|
34
|
+
description: "Pronounce TCP/IP as individual letters"
|
35
|
+
private: false
|
36
|
+
|
37
|
+
- name: "ssh"
|
38
|
+
order: 50
|
39
|
+
pattern: '\bSSH\b'
|
40
|
+
replacement: 'S S H'
|
41
|
+
enabled: true
|
42
|
+
description: "Pronounce SSH as individual letters"
|
43
|
+
private: false
|
44
|
+
|
45
|
+
- name: "https"
|
46
|
+
order: 60
|
47
|
+
pattern: '\bHTTPS\b'
|
48
|
+
replacement: 'H T T P S'
|
49
|
+
enabled: true
|
50
|
+
description: "Pronounce HTTPS as individual letters"
|
51
|
+
private: false
|
52
|
+
|
53
|
+
# Model numbers and versions
|
54
|
+
- name: "unifi_u7"
|
55
|
+
order: 70
|
56
|
+
pattern: '\bU7\b'
|
57
|
+
replacement: 'U seven'
|
58
|
+
enabled: true
|
59
|
+
description: "UniFi U7 model number"
|
60
|
+
private: false
|
61
|
+
|
62
|
+
- name: "wifi_6e"
|
63
|
+
order: 80
|
64
|
+
pattern: '\bWiFi 6E\b'
|
65
|
+
replacement: 'wifi six E'
|
66
|
+
enabled: true
|
67
|
+
description: "WiFi 6E standard"
|
68
|
+
private: false
|
69
|
+
|
70
|
+
- name: "wifi_7"
|
71
|
+
order: 90
|
72
|
+
pattern: '\bWiFi 7\b'
|
73
|
+
replacement: 'wifi seven'
|
74
|
+
enabled: true
|
75
|
+
description: "WiFi 7 standard"
|
76
|
+
private: false
|
77
|
+
|
78
|
+
# Units and measurements
|
79
|
+
- name: "gigahertz"
|
80
|
+
order: 100
|
81
|
+
pattern: '\b(\d+(?:\.\d+)?)\s*GHz\b'
|
82
|
+
replacement: '$1 gigahertz'
|
83
|
+
enabled: true
|
84
|
+
description: "Expand GHz to gigahertz"
|
85
|
+
private: false
|
86
|
+
|
87
|
+
- name: "megahertz"
|
88
|
+
order: 110
|
89
|
+
pattern: '\b(\d+(?:\.\d+)?)\s*MHz\b'
|
90
|
+
replacement: '$1 megahertz'
|
91
|
+
enabled: true
|
92
|
+
description: "Expand MHz to megahertz"
|
93
|
+
private: false
|
94
|
+
|
95
|
+
- name: "gigabytes"
|
96
|
+
order: 120
|
97
|
+
pattern: '\b(\d+(?:\.\d+)?)\s*GB\b'
|
98
|
+
replacement: '$1 gigabytes'
|
99
|
+
enabled: true
|
100
|
+
description: "Expand GB to gigabytes"
|
101
|
+
private: false
|
102
|
+
|
103
|
+
- name: "terabytes"
|
104
|
+
order: 130
|
105
|
+
pattern: '\b(\d+(?:\.\d+)?)\s*TB\b'
|
106
|
+
replacement: '$1 terabytes'
|
107
|
+
enabled: true
|
108
|
+
description: "Expand TB to terabytes"
|
109
|
+
private: false
|
110
|
+
|
111
|
+
- name: "milliseconds"
|
112
|
+
order: 140
|
113
|
+
pattern: '\b(\d+(?:\.\d+)?)\s*ms\b'
|
114
|
+
replacement: '$1 milliseconds'
|
115
|
+
enabled: true
|
116
|
+
description: "Expand ms to milliseconds"
|
117
|
+
private: false
|
118
|
+
|
119
|
+
# Cloud providers
|
120
|
+
- name: "aws"
|
121
|
+
order: 150
|
122
|
+
pattern: '\bAWS\b'
|
123
|
+
replacement: 'A W S'
|
124
|
+
enabled: true
|
125
|
+
description: "Amazon Web Services"
|
126
|
+
private: false
|
127
|
+
|
128
|
+
- name: "gcp"
|
129
|
+
order: 160
|
130
|
+
pattern: '\bGCP\b'
|
131
|
+
replacement: 'G C P'
|
132
|
+
enabled: true
|
133
|
+
description: "Google Cloud Platform"
|
134
|
+
private: false
|
135
|
+
|
136
|
+
# Programming terms
|
137
|
+
- name: "json"
|
138
|
+
order: 170
|
139
|
+
pattern: '\bJSON\b'
|
140
|
+
replacement: 'jason'
|
141
|
+
enabled: true
|
142
|
+
description: "Pronounce JSON as Jason"
|
143
|
+
private: false
|
144
|
+
|
145
|
+
- name: "yaml"
|
146
|
+
order: 180
|
147
|
+
pattern: '\bYAML\b'
|
148
|
+
replacement: 'yammel'
|
149
|
+
enabled: true
|
150
|
+
description: "Pronounce YAML as yammel"
|
151
|
+
private: false
|
152
|
+
|
153
|
+
- name: "api"
|
154
|
+
order: 190
|
155
|
+
pattern: '\bAPI\b'
|
156
|
+
replacement: 'A P I'
|
157
|
+
enabled: true
|
158
|
+
description: "Pronounce API as individual letters"
|
159
|
+
private: false
|
160
|
+
|
161
|
+
- name: "cli"
|
162
|
+
order: 200
|
163
|
+
pattern: '\bCLI\b'
|
164
|
+
replacement: 'C L I'
|
165
|
+
enabled: true
|
166
|
+
description: "Command Line Interface"
|
167
|
+
private: false
|
168
|
+
|
169
|
+
# Project specific
|
170
|
+
- name: "mcp"
|
171
|
+
order: 210
|
172
|
+
pattern: '\bMCP\b'
|
173
|
+
replacement: 'M C P'
|
174
|
+
enabled: true
|
175
|
+
description: "Model Context Protocol"
|
176
|
+
private: false
|
177
|
+
|
178
|
+
- name: "tts"
|
179
|
+
order: 220
|
180
|
+
pattern: '\bTTS\b'
|
181
|
+
replacement: 'T T S'
|
182
|
+
enabled: true
|
183
|
+
description: "Text to Speech"
|
184
|
+
private: false
|
185
|
+
|
186
|
+
- name: "stt"
|
187
|
+
order: 230
|
188
|
+
pattern: '\bSTT\b'
|
189
|
+
replacement: 'S T T'
|
190
|
+
enabled: true
|
191
|
+
description: "Speech to Text"
|
192
|
+
private: false
|
193
|
+
|
194
|
+
stt_rules:
|
195
|
+
# Common Whisper mishearings
|
196
|
+
- name: "metool_correction"
|
197
|
+
order: 10
|
198
|
+
pattern: '\bme tool\b'
|
199
|
+
replacement: 'metool'
|
200
|
+
enabled: true
|
201
|
+
description: "Correct Whisper hearing 'me tool' as two words"
|
202
|
+
private: false
|
203
|
+
|
204
|
+
- name: "cora_seven"
|
205
|
+
order: 20
|
206
|
+
pattern: '\bcora 7\b'
|
207
|
+
replacement: 'Cora 7'
|
208
|
+
enabled: true
|
209
|
+
description: "Ensure proper capitalization of Cora 7"
|
210
|
+
private: false
|
211
|
+
|
212
|
+
- name: "ai_cora"
|
213
|
+
order: 30
|
214
|
+
pattern: '\bAI cora\b'
|
215
|
+
replacement: 'ai-cora'
|
216
|
+
enabled: true
|
217
|
+
description: "GitHub username format"
|
218
|
+
private: false
|
219
|
+
|
220
|
+
# Programming terms often misheard
|
221
|
+
- name: "python_capital"
|
222
|
+
order: 40
|
223
|
+
pattern: '\bpython\b'
|
224
|
+
replacement: 'Python'
|
225
|
+
enabled: true
|
226
|
+
description: "Capitalize Python language name"
|
227
|
+
private: false
|
228
|
+
|
229
|
+
- name: "github_capital"
|
230
|
+
order: 50
|
231
|
+
pattern: '\bgithub\b'
|
232
|
+
replacement: 'GitHub'
|
233
|
+
enabled: true
|
234
|
+
description: "Correct GitHub capitalization"
|
235
|
+
private: false
|
236
|
+
|
237
|
+
- name: "tmux_spelling"
|
238
|
+
order: 60
|
239
|
+
pattern: '\b(tee mux|t mux|teamux)\b'
|
240
|
+
replacement: 'tmux'
|
241
|
+
enabled: true
|
242
|
+
description: "Correct various tmux mishearings"
|
243
|
+
private: false
|
244
|
+
|
245
|
+
- name: "neovim_spelling"
|
246
|
+
order: 70
|
247
|
+
pattern: '\b(neo vim|neovem|neovam)\b'
|
248
|
+
replacement: 'Neovim'
|
249
|
+
enabled: true
|
250
|
+
description: "Correct Neovim mishearings"
|
251
|
+
private: false
|
252
|
+
|
253
|
+
# Common phrases
|
254
|
+
- name: "lets_contraction"
|
255
|
+
order: 80
|
256
|
+
pattern: '\blet us\b'
|
257
|
+
replacement: "let's"
|
258
|
+
enabled: true
|
259
|
+
description: "Contract 'let us' to let's"
|
260
|
+
private: false
|
261
|
+
|
262
|
+
- name: "its_contraction"
|
263
|
+
order: 90
|
264
|
+
pattern: '\bit is\b'
|
265
|
+
replacement: "it's"
|
266
|
+
enabled: false # Disabled by default as it can be wrong
|
267
|
+
description: "Contract 'it is' to it's (use carefully)"
|
268
|
+
private: false
|