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.
Files changed (127) hide show
  1. {voice_mode-4.0.1 → voice_mode-4.1.0}/CHANGELOG.md +14 -0
  2. {voice_mode-4.0.1 → voice_mode-4.1.0}/PKG-INFO +1 -1
  3. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/__version__.py +1 -1
  4. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/cli.py +3 -0
  5. voice_mode-4.1.0/voice_mode/cli_commands/pronounce_commands.py +223 -0
  6. voice_mode-4.1.0/voice_mode/data/default_pronunciation.yaml +268 -0
  7. voice_mode-4.1.0/voice_mode/pronounce.py +397 -0
  8. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/converse.py +11 -0
  9. voice_mode-4.1.0/voice_mode/tools/pronounce.py +245 -0
  10. {voice_mode-4.0.1 → voice_mode-4.1.0}/.gitignore +0 -0
  11. {voice_mode-4.0.1 → voice_mode-4.1.0}/README.md +0 -0
  12. {voice_mode-4.0.1 → voice_mode-4.1.0}/build_hooks.py +0 -0
  13. {voice_mode-4.0.1 → voice_mode-4.1.0}/pyproject.toml +0 -0
  14. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/__init__.py +0 -0
  15. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/__main__.py +0 -0
  16. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/cli_commands/__init__.py +0 -0
  17. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/cli_commands/exchanges.py +0 -0
  18. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/cli_commands/transcribe.py +0 -0
  19. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/config.py +0 -0
  20. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/conversation_logger.py +0 -0
  21. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/core.py +0 -0
  22. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/data/versions.json +0 -0
  23. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/__init__.py +0 -0
  24. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/conversations.py +0 -0
  25. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/filters.py +0 -0
  26. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/formatters.py +0 -0
  27. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/models.py +0 -0
  28. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/reader.py +0 -0
  29. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/exchanges/stats.py +0 -0
  30. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/README.md +0 -0
  31. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/app/api/connection-details/route.ts +0 -0
  32. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/app/favicon.ico +0 -0
  33. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/app/globals.css +0 -0
  34. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/app/layout.tsx +0 -0
  35. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/app/page.tsx +0 -0
  36. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/components/CloseIcon.tsx +0 -0
  37. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/components/NoAgentNotification.tsx +0 -0
  38. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/components/TranscriptionView.tsx +0 -0
  39. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/hooks/useCombinedTranscriptions.ts +0 -0
  40. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/hooks/useLocalMicTrack.ts +0 -0
  41. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/next-env.d.ts +0 -0
  42. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/next.config.mjs +0 -0
  43. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/package-lock.json +0 -0
  44. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/package.json +0 -0
  45. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/pnpm-lock.yaml +0 -0
  46. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/postcss.config.mjs +0 -0
  47. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/tailwind.config.ts +0 -0
  48. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/frontend/tsconfig.json +0 -0
  49. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/prompts/README.md +0 -0
  50. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/prompts/__init__.py +0 -0
  51. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/prompts/converse.py +0 -0
  52. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/prompts/release_notes.py +0 -0
  53. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/prompts/services.py +0 -0
  54. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/provider_discovery.py +0 -0
  55. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/providers.py +0 -0
  56. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/__init__.py +0 -0
  57. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/audio_files.py +0 -0
  58. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/changelog.py +0 -0
  59. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/configuration.py +0 -0
  60. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/statistics.py +0 -0
  61. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/version.py +0 -0
  62. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/resources/whisper_models.py +0 -0
  63. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/server.py +0 -0
  64. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/shared.py +0 -0
  65. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/simple_failover.py +0 -0
  66. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/statistics.py +0 -0
  67. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/streaming.py +0 -0
  68. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/__init__.py +0 -0
  69. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/com.voicemode.frontend.plist +0 -0
  70. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/com.voicemode.kokoro.plist +0 -0
  71. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/com.voicemode.livekit.plist +0 -0
  72. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/com.voicemode.whisper.plist +0 -0
  73. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/start-kokoro-with-health-check.sh +0 -0
  74. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/launchd/start-whisper-with-health-check.sh +0 -0
  75. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/scripts/__init__.py +0 -0
  76. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/scripts/start-whisper-server.sh +0 -0
  77. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/systemd/voicemode-frontend.service +0 -0
  78. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/systemd/voicemode-kokoro.service +0 -0
  79. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/systemd/voicemode-livekit.service +0 -0
  80. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/templates/systemd/voicemode-whisper.service +0 -0
  81. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/__init__.py +0 -0
  82. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/configuration_management.py +0 -0
  83. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/dependencies.py +0 -0
  84. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/devices.py +0 -0
  85. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/diagnostics.py +0 -0
  86. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/providers.py +0 -0
  87. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/service.py +0 -0
  88. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/kokoro/install.py +0 -0
  89. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/kokoro/uninstall.py +0 -0
  90. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/list_versions.py +0 -0
  91. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/livekit/__init__.py +0 -0
  92. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/livekit/frontend.py +0 -0
  93. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/livekit/install.py +0 -0
  94. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/livekit/production_server.py +0 -0
  95. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/livekit/uninstall.py +0 -0
  96. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/version_info.py +0 -0
  97. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/__init__.py +0 -0
  98. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/install.py +0 -0
  99. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/list_models.py +0 -0
  100. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/model_active.py +0 -0
  101. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/model_benchmark.py +0 -0
  102. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/model_install.py +0 -0
  103. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/model_remove.py +0 -0
  104. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/models.py +0 -0
  105. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/services/whisper/uninstall.py +0 -0
  106. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/statistics.py +0 -0
  107. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/transcription/__init__.py +0 -0
  108. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/transcription/backends.py +0 -0
  109. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/transcription/core.py +0 -0
  110. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/transcription/formats.py +0 -0
  111. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/transcription/types.py +0 -0
  112. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/tools/voice_registry.py +0 -0
  113. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/__init__.py +0 -0
  114. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/audio_diagnostics.py +0 -0
  115. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/event_logger.py +0 -0
  116. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/ffmpeg_check.py +0 -0
  117. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/format_migration.py +0 -0
  118. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/gpu_detection.py +0 -0
  119. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/migration_helpers.py +0 -0
  120. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/common.py +0 -0
  121. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/coreml_setup.py +0 -0
  122. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/kokoro_helpers.py +0 -0
  123. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/livekit_helpers.py +0 -0
  124. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/whisper_helpers.py +0 -0
  125. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/services/whisper_version.py +0 -0
  126. {voice_mode-4.0.1 → voice_mode-4.1.0}/voice_mode/utils/version_helpers.py +0 -0
  127. {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.1
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
@@ -1,3 +1,3 @@
1
1
  # This file is automatically updated by 'make release'
2
2
  # Do not edit manually
3
- __version__ = "4.0.1"
3
+ __version__ = "4.1.0"
@@ -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