voice-vibecoder 2.15.0__tar.gz → 2.16.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_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/CHANGELOG.md +3 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/PKG-INFO +1 -1
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/pyproject.toml +1 -1
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/__init__.py +1 -1
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/cli_serve.py +13 -5
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/config.py +20 -4
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/server.py +11 -2
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/.github/workflows/ci.yml +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/.github/workflows/release.yml +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/.gitignore +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/LICENSE +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/README.md +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/app.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/index.html +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/app_config.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/audio.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/code_providers/__init__.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/code_providers/claude.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/code_providers/cursor.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/code_providers/openclaw.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/code_providers/registry.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/instances.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/session.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/tools/__init__.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/tools/agent_task.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/tools/definitions.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/tools/dispatch.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/tools/file_tree.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/tools/handlers.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/__init__.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/callbacks.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/diff_view.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/help_modal.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/panels.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/screen.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/setup.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/state.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/styles.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/update_modal.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/ui/wizard.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/voice_providers/gemini.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/voice_providers/openai.py +0 -0
- {voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/worktrees.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voice-vibecoder
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.16.0
|
|
4
4
|
Summary: Olaf The Vibecoder — a voice-controlled coding assistant using OpenAI Realtime API + Claude Code
|
|
5
5
|
Project-URL: Homepage, https://github.com/snokam/voice-vibecoder
|
|
6
6
|
Project-URL: Repository, https://github.com/snokam/voice-vibecoder
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "voice-vibecoder"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.16.0"
|
|
8
8
|
description = "Olaf The Vibecoder — a voice-controlled coding assistant using OpenAI Realtime API + Claude Code"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -40,7 +40,6 @@ def _apply_env_overrides(settings: RealtimeSettings) -> RealtimeSettings:
|
|
|
40
40
|
# Top-level overrides
|
|
41
41
|
simple_map = {
|
|
42
42
|
"VOICE_PROVIDER": "provider",
|
|
43
|
-
"VOICE_API_KEY": "api_key",
|
|
44
43
|
"VOICE_LANGUAGE": "language",
|
|
45
44
|
"VOICE_PERMISSION_MODE": "permission_mode",
|
|
46
45
|
}
|
|
@@ -48,20 +47,29 @@ def _apply_env_overrides(settings: RealtimeSettings) -> RealtimeSettings:
|
|
|
48
47
|
value = os.environ.get(env_var)
|
|
49
48
|
if value:
|
|
50
49
|
setattr(settings, attr, value)
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
applied.append(env_var)
|
|
51
|
+
|
|
52
|
+
# Legacy fallback: VOICE_API_KEY sets the top-level key
|
|
53
|
+
legacy_key = os.environ.get("VOICE_API_KEY")
|
|
54
|
+
if legacy_key:
|
|
55
|
+
settings.api_key = legacy_key
|
|
56
|
+
applied.append("VOICE_API_KEY=***")
|
|
53
57
|
|
|
54
|
-
# Nested provider overrides
|
|
58
|
+
# Nested provider overrides (including per-provider API keys)
|
|
55
59
|
nested_map = {
|
|
60
|
+
"VOICE_OPENAI_API_KEY": (settings.openai, "api_key"),
|
|
61
|
+
"VOICE_AZURE_API_KEY": (settings.azure, "api_key"),
|
|
56
62
|
"VOICE_AZURE_ENDPOINT": (settings.azure, "endpoint"),
|
|
57
63
|
"VOICE_AZURE_DEPLOYMENT": (settings.azure, "deployment"),
|
|
64
|
+
"VOICE_GEMINI_API_KEY": (settings.gemini, "api_key"),
|
|
58
65
|
"VOICE_GEMINI_MODEL": (settings.gemini, "model"),
|
|
59
66
|
}
|
|
60
67
|
for env_var, (obj, attr) in nested_map.items():
|
|
61
68
|
value = os.environ.get(env_var)
|
|
62
69
|
if value:
|
|
63
70
|
setattr(obj, attr, value)
|
|
64
|
-
|
|
71
|
+
safe = env_var if "KEY" not in env_var else f"{env_var}=***"
|
|
72
|
+
applied.append(safe)
|
|
65
73
|
|
|
66
74
|
if applied:
|
|
67
75
|
logger.info("Env overrides applied: %s", ", ".join(applied))
|
|
@@ -267,6 +267,7 @@ def configure_paths(config_dir: Path) -> None:
|
|
|
267
267
|
@dataclass
|
|
268
268
|
class OpenAIConfig:
|
|
269
269
|
"""Per-provider settings for OpenAI."""
|
|
270
|
+
api_key: str = ""
|
|
270
271
|
model: str = OPENAI_DEFAULT_MODEL
|
|
271
272
|
voice: str = "ash"
|
|
272
273
|
vad_threshold: float = 0.8
|
|
@@ -277,6 +278,7 @@ class OpenAIConfig:
|
|
|
277
278
|
@dataclass
|
|
278
279
|
class AzureConfig:
|
|
279
280
|
"""Per-provider settings for Azure OpenAI."""
|
|
281
|
+
api_key: str = ""
|
|
280
282
|
endpoint: str = AZURE_DEFAULT_ENDPOINT
|
|
281
283
|
deployment: str = AZURE_DEFAULT_DEPLOYMENT
|
|
282
284
|
voice: str = "ash"
|
|
@@ -288,6 +290,7 @@ class AzureConfig:
|
|
|
288
290
|
@dataclass
|
|
289
291
|
class GeminiConfig:
|
|
290
292
|
"""Per-provider settings for Gemini."""
|
|
293
|
+
api_key: str = ""
|
|
291
294
|
model: str = GEMINI_DEFAULT_MODEL
|
|
292
295
|
voice: str = "Puck"
|
|
293
296
|
start_sensitivity: str = "HIGH" # "HIGH" or "LOW"
|
|
@@ -329,6 +332,16 @@ class RealtimeSettings:
|
|
|
329
332
|
self.input_mode = input_mode
|
|
330
333
|
self.enabled_agents: list[str] = enabled_agents or ["claude"]
|
|
331
334
|
|
|
335
|
+
@property
|
|
336
|
+
def active_api_key(self) -> str:
|
|
337
|
+
"""Return the API key for the active provider.
|
|
338
|
+
|
|
339
|
+
Checks the per-provider key first, falls back to the legacy
|
|
340
|
+
top-level ``api_key`` for backward compatibility.
|
|
341
|
+
"""
|
|
342
|
+
provider_key = self.active_provider_config.api_key
|
|
343
|
+
return provider_key or self.api_key
|
|
344
|
+
|
|
332
345
|
@property
|
|
333
346
|
def active_voice(self) -> str:
|
|
334
347
|
"""Return the voice setting for the active provider."""
|
|
@@ -356,23 +369,23 @@ class RealtimeSettings:
|
|
|
356
369
|
f"&deployment={self.azure.deployment}"
|
|
357
370
|
)
|
|
358
371
|
if self.provider == "gemini":
|
|
359
|
-
return f"{GEMINI_WS_URL}?key={self.
|
|
372
|
+
return f"{GEMINI_WS_URL}?key={self.active_api_key}"
|
|
360
373
|
return f"{OPENAI_WS_URL}?model={self.openai.model}"
|
|
361
374
|
|
|
362
375
|
@property
|
|
363
376
|
def ws_headers(self) -> dict[str, str]:
|
|
364
377
|
if self.provider == "azure":
|
|
365
|
-
return {"api-key": self.
|
|
378
|
+
return {"api-key": self.active_api_key}
|
|
366
379
|
if self.provider == "gemini":
|
|
367
380
|
return {}
|
|
368
381
|
return {
|
|
369
|
-
"Authorization": f"Bearer {self.
|
|
382
|
+
"Authorization": f"Bearer {self.active_api_key}",
|
|
370
383
|
"OpenAI-Beta": "realtime=v1",
|
|
371
384
|
}
|
|
372
385
|
|
|
373
386
|
@property
|
|
374
387
|
def is_configured(self) -> bool:
|
|
375
|
-
return bool(self.
|
|
388
|
+
return bool(self.active_api_key)
|
|
376
389
|
|
|
377
390
|
@property
|
|
378
391
|
def provider_label(self) -> str:
|
|
@@ -427,6 +440,7 @@ class RealtimeSettings:
|
|
|
427
440
|
@staticmethod
|
|
428
441
|
def _load_openai(d: dict) -> OpenAIConfig:
|
|
429
442
|
return OpenAIConfig(
|
|
443
|
+
api_key=d.get("api_key", ""),
|
|
430
444
|
model=d.get("model", OPENAI_DEFAULT_MODEL),
|
|
431
445
|
voice=d.get("voice", "ash"),
|
|
432
446
|
vad_threshold=d.get("vad_threshold", 0.8),
|
|
@@ -437,6 +451,7 @@ class RealtimeSettings:
|
|
|
437
451
|
@staticmethod
|
|
438
452
|
def _load_azure(d: dict) -> AzureConfig:
|
|
439
453
|
return AzureConfig(
|
|
454
|
+
api_key=d.get("api_key", ""),
|
|
440
455
|
endpoint=d.get("endpoint", AZURE_DEFAULT_ENDPOINT),
|
|
441
456
|
deployment=d.get("deployment", AZURE_DEFAULT_DEPLOYMENT),
|
|
442
457
|
voice=d.get("voice", "ash"),
|
|
@@ -448,6 +463,7 @@ class RealtimeSettings:
|
|
|
448
463
|
@staticmethod
|
|
449
464
|
def _load_gemini(d: dict) -> GeminiConfig:
|
|
450
465
|
return GeminiConfig(
|
|
466
|
+
api_key=d.get("api_key", ""),
|
|
451
467
|
model=d.get("model", GEMINI_DEFAULT_MODEL),
|
|
452
468
|
voice=d.get("voice", "Puck"),
|
|
453
469
|
start_sensitivity=d.get("start_sensitivity", "HIGH"),
|
|
@@ -52,6 +52,15 @@ logger = logging.getLogger(__name__)
|
|
|
52
52
|
AUTH_TIMEOUT_SECONDS = 10
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
def _sanitize_settings(d: dict[str, Any]) -> dict[str, Any]:
|
|
56
|
+
"""Strip API keys from a settings dict before sending to the frontend."""
|
|
57
|
+
d = {k: v for k, v in d.items() if k != "api_key"}
|
|
58
|
+
for provider in ("openai", "azure", "gemini"):
|
|
59
|
+
if provider in d and isinstance(d[provider], dict):
|
|
60
|
+
d[provider] = {k: v for k, v in d[provider].items() if k != "api_key"}
|
|
61
|
+
return d
|
|
62
|
+
|
|
63
|
+
|
|
55
64
|
def _merge_settings(settings: RealtimeSettings, incoming: dict[str, Any]) -> None:
|
|
56
65
|
"""Merge a partial settings dict into an existing RealtimeSettings.
|
|
57
66
|
|
|
@@ -213,7 +222,7 @@ class WebSocketBridge:
|
|
|
213
222
|
# Send current settings so the frontend can render controls
|
|
214
223
|
self._send({
|
|
215
224
|
"type": "settings_updated",
|
|
216
|
-
"settings": self._settings._to_dict(),
|
|
225
|
+
"settings": _sanitize_settings(self._settings._to_dict()),
|
|
217
226
|
})
|
|
218
227
|
|
|
219
228
|
async def _handle_settings_update(self, incoming: dict[str, Any]) -> None:
|
|
@@ -246,7 +255,7 @@ class WebSocketBridge:
|
|
|
246
255
|
self._send({"type": "status", "status": "ready"})
|
|
247
256
|
self._send({
|
|
248
257
|
"type": "settings_updated",
|
|
249
|
-
"settings": self._settings._to_dict(),
|
|
258
|
+
"settings": _sanitize_settings(self._settings._to_dict()),
|
|
250
259
|
})
|
|
251
260
|
except Exception as e:
|
|
252
261
|
logger.error("Failed to reconnect after settings update: %s", e, exc_info=True)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/code_providers/__init__.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/code_providers/claude.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/code_providers/cursor.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/code_providers/openclaw.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/code_providers/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/voice_providers/__init__.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/voice_providers/gemini.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.15.0 → voice_vibecoder-2.16.0}/src/voice_vibecoder/voice_providers/openai.py
RENAMED
|
File without changes
|
|
File without changes
|