deepctl-core 0.2.12__tar.gz → 0.2.14__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 (22) hide show
  1. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/PKG-INFO +2 -2
  2. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/pyproject.toml +2 -2
  3. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/auth.py +8 -1
  4. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/base_command.py +14 -2
  5. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/client.py +62 -3
  6. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/skill_generator.py +49 -20
  7. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core.egg-info/PKG-INFO +2 -2
  8. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core.egg-info/requires.txt +1 -1
  9. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/README.md +0 -0
  10. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/setup.cfg +0 -0
  11. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/__init__.py +0 -0
  12. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/base_group_command.py +0 -0
  13. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/config.py +0 -0
  14. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/models.py +0 -0
  15. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/output.py +0 -0
  16. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/plugin_env.py +0 -0
  17. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/plugin_manager.py +0 -0
  18. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/py.typed +0 -0
  19. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core/timing.py +0 -0
  20. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core.egg-info/SOURCES.txt +0 -0
  21. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core.egg-info/dependency_links.txt +0 -0
  22. {deepctl_core-0.2.12 → deepctl_core-0.2.14}/src/deepctl_core.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-core
3
- Version: 0.2.12
3
+ Version: 0.2.14
4
4
  Summary: Core components for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -16,7 +16,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
16
  Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  Requires-Dist: click>=8.0.0
19
- Requires-Dist: deepgram-sdk>=6.0.0rc2
19
+ Requires-Dist: deepgram-sdk>=7.5.0
20
20
  Requires-Dist: pydantic>=2.0.0
21
21
  Requires-Dist: rich>=13.0.0
22
22
  Requires-Dist: httpx>=0.24.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepctl-core"
7
- version = "0.2.12" # x-release-please-version
7
+ version = "0.2.14" # x-release-please-version
8
8
  description = "Core components for deepctl"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -23,7 +23,7 @@ keywords = ["deepgram", "core", "auth", "config", "client"]
23
23
  requires-python = ">=3.10"
24
24
  dependencies = [
25
25
  "click>=8.0.0",
26
- "deepgram-sdk>=6.0.0rc2",
26
+ "deepgram-sdk>=7.5.0",
27
27
  "pydantic>=2.0.0",
28
28
  "rich>=13.0.0",
29
29
  "httpx>=0.24.0",
@@ -12,6 +12,7 @@ from pydantic import BaseModel
12
12
  from rich.console import Console
13
13
  from rich.progress import Progress, SpinnerColumn, TextColumn
14
14
 
15
+ from .client import _split_base_url
15
16
  from .config import Config
16
17
  from .models import ProfileInfo, ProfilesResult
17
18
 
@@ -273,8 +274,14 @@ class AuthManager:
273
274
  "Content-Type": "application/json",
274
275
  }
275
276
 
277
+ # Verify against the configured base URL (not hardcoded prod), so a
278
+ # custom base (e.g. staging) validates its own keys correctly.
279
+ # Preserve the input scheme so a plaintext endpoint isn't forced
280
+ # onto TLS.
281
+ base = self.config.get_profile().base_url or "https://api.deepgram.com"
282
+ rest_scheme, _, host = _split_base_url(base)
276
283
  response = self.client.get(
277
- "https://api.deepgram.com/v1/projects",
284
+ f"{rest_scheme}://{host}/v1/projects",
278
285
  headers=headers,
279
286
  )
280
287
 
@@ -56,7 +56,10 @@ class BaseCommand(ABC):
56
56
  config = Config()
57
57
 
58
58
  # Extract explicit credentials from kwargs if provided
59
- explicit_api_key = kwargs.get("api_key")
59
+ # (or from the global --api-key option stored on ctx.obj)
60
+ explicit_api_key = kwargs.get("api_key") or (
61
+ ctx.obj.get("api_key") if ctx.obj else None
62
+ )
60
63
  explicit_project_id = kwargs.get("project_id")
61
64
 
62
65
  # Create auth manager with explicit credentials
@@ -121,7 +124,16 @@ class BaseCommand(ABC):
121
124
  # Handle command result
122
125
  if result is not None:
123
126
  with TimingContext("output_processing"):
124
- self.output_result(result, config)
127
+ try:
128
+ self.output_result(result, config)
129
+ except (BrokenPipeError, OSError):
130
+ # Downstream stream closed (e.g. an MCP host disconnected
131
+ # stdio after `dg mcp` finished). Nothing useful to log
132
+ # here because the logger writes to the same closed stream.
133
+ pass
134
+ except ValueError as exc:
135
+ if "closed file" not in str(exc):
136
+ raise
125
137
 
126
138
  except KeyboardInterrupt:
127
139
  self._tag_telemetry_status("cancelled")
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from pathlib import Path
6
7
  from typing import TYPE_CHECKING, Any, cast
7
8
 
@@ -20,6 +21,22 @@ if TYPE_CHECKING:
20
21
  console = Console()
21
22
 
22
23
 
24
+ def _split_base_url(base_url: str) -> tuple[str, str, str]:
25
+ """Split a base URL into (rest_scheme, ws_scheme, host).
26
+
27
+ Preserves the transport implied by the input scheme so a plaintext
28
+ endpoint is not forced onto TLS: an ``http://`` base maps to ``http`` for
29
+ REST and ``ws`` for WebSocket; anything else (including a bare host) maps
30
+ to ``https``/``wss``. The host is returned without scheme or trailing slash.
31
+ """
32
+ match = re.match(r"^([a-z]+)://", base_url)
33
+ scheme = match.group(1) if match else "https"
34
+ host = re.sub(r"^[a-z]+://", "", base_url).rstrip("/")
35
+ if scheme in ("http", "ws"):
36
+ return "http", "ws", host
37
+ return "https", "wss", host
38
+
39
+
23
40
  class DeepgramClient:
24
41
  """Wrapper around Deepgram SDK with authentication integration."""
25
42
 
@@ -64,10 +81,18 @@ class DeepgramClient:
64
81
  current_profile.base_url
65
82
  and current_profile.base_url != "https://api.deepgram.com"
66
83
  ):
84
+ # Derive both the REST and WebSocket endpoints from the
85
+ # configured base URL, so a custom base works for batch REST
86
+ # *and* streaming. Preserve the transport implied by the input
87
+ # scheme (http→ws, https→wss) so a plaintext local/staging
88
+ # endpoint isn't forced onto TLS. agent_rest is required by the
89
+ # SDK environment.
90
+ rest_scheme, ws_scheme, host = _split_base_url(current_profile.base_url)
67
91
  kwargs["environment"] = DeepgramClientEnvironment(
68
- base=current_profile.base_url,
69
- production=current_profile.base_url,
70
- agent=current_profile.base_url,
92
+ base=f"{rest_scheme}://{host}",
93
+ production=f"{ws_scheme}://{host}",
94
+ agent=f"{ws_scheme}://{host}",
95
+ agent_rest=f"{rest_scheme}://{host}",
71
96
  )
72
97
 
73
98
  client = DGClient(**kwargs)
@@ -158,6 +183,40 @@ class DeepgramClient:
158
183
  except Exception as e:
159
184
  raise ApiError(body=f"Text-to-speech failed: {e}")
160
185
 
186
+ def speak_text_stream(
187
+ self,
188
+ text: str,
189
+ model: str,
190
+ encoding: str | None = None,
191
+ sample_rate: float | None = None,
192
+ ) -> Iterator[bytes]:
193
+ """Stream TTS audio over the Flux v2 WebSocket (speak.v2.connect).
194
+
195
+ Yields raw audio chunks as they arrive. The streaming transport emits
196
+ raw (non-containerized) audio, so only linear16/mulaw/alaw encodings
197
+ apply and sample_rate is sent as the string the streaming API expects.
198
+ """
199
+ from deepgram.speak.v2.types.speak_v2speak import SpeakV2Speak
200
+
201
+ connect_kwargs: dict[str, Any] = {"model": model}
202
+ if encoding:
203
+ connect_kwargs["encoding"] = encoding
204
+ if sample_rate:
205
+ connect_kwargs["sample_rate"] = str(int(sample_rate))
206
+
207
+ try:
208
+ with self.client.speak.v2.connect(**connect_kwargs) as conn:
209
+ conn.send_speak(SpeakV2Speak(type="Speak", text=text))
210
+ conn.send_flush()
211
+ conn.send_close()
212
+ for message in conn:
213
+ # Audio arrives as raw bytes; control messages are parsed
214
+ # objects, which we skip here.
215
+ if isinstance(message, bytes):
216
+ yield message
217
+ except Exception as e:
218
+ raise ApiError(body=f"Text-to-speech streaming failed: {e}")
219
+
161
220
  # ── Text Intelligence (Read) ───────────────────────────────────
162
221
 
163
222
  def analyze_text(
@@ -426,47 +426,76 @@ def render_developer_guide(
426
426
  "- **Aura-2** — Latest generation. High quality, low latency, many voices."
427
427
  )
428
428
  lines.append("- **Aura** — Previous generation. Solid quality and performance.")
429
+ lines.append(
430
+ "- **Flux** — Conversational TTS on the Speak v2 WebSocket API "
431
+ "(streaming, turn-based). Voices like `flux-alexis-en`."
432
+ )
429
433
  lines.append("")
430
434
  lines.append("### Popular Voices")
431
435
  lines.append("")
432
- lines.append("Voices follow the naming pattern `aura-2-{name}-en`. Examples:")
436
+ lines.append("Aura voices follow `aura-2-{name}-en`. Examples:")
433
437
  lines.append("- `aura-2-andromeda-en`, `aura-2-arcas-en`, `aura-2-atlas-en`")
434
438
  lines.append("- `aura-2-luna-en`, `aura-2-stella-en`, `aura-2-helios-en`")
435
439
  lines.append("")
440
+ lines.append(
441
+ "Flux (Speak v2) voices follow `flux-{name}-en` (English at launch), "
442
+ "e.g. `flux-alexis-en`."
443
+ )
444
+ lines.append("")
436
445
  lines.append("Full voice list: <https://developers.deepgram.com/docs/tts-models>")
437
446
  lines.append("")
438
- lines.append("### TTS Example (Python)")
447
+ lines.append("### Aura TTS — Speak v1, batch REST (Python)")
439
448
  lines.append("")
440
449
  lines.append("```python")
441
- lines.append("from deepgram import DeepgramClient, SpeakOptions")
450
+ lines.append("from deepgram import DeepgramClient")
442
451
  lines.append("")
443
- lines.append('dg = DeepgramClient("DEEPGRAM_API_KEY")')
452
+ lines.append('client = DeepgramClient(api_key="DEEPGRAM_API_KEY")')
444
453
  lines.append("")
445
- lines.append("options = SpeakOptions(")
446
- lines.append(' model="aura-2-andromeda-en"')
454
+ lines.append("audio = client.speak.v1.audio.generate(")
455
+ lines.append(' text="Hello from Deepgram!",')
456
+ lines.append(' model="aura-2-andromeda-en",')
457
+ lines.append(' encoding="mp3",')
447
458
  lines.append(")")
459
+ lines.append('with open("output.mp3", "wb") as f:')
460
+ lines.append(" for chunk in audio:")
461
+ lines.append(" f.write(chunk)")
462
+ lines.append("```")
448
463
  lines.append("")
449
- lines.append('response = dg.speak.rest.v("1").save(')
450
- lines.append(' "output.mp3",')
451
- lines.append(' {"text": "Hello from Deepgram!"},')
452
- lines.append(" options,")
453
- lines.append(")")
464
+ lines.append("### Flux TTS — Speak v2, WebSocket streaming (Python)")
465
+ lines.append("")
466
+ lines.append("```python")
467
+ lines.append("from deepgram import DeepgramClient")
468
+ lines.append("from deepgram.speak.v2.types.speak_v2speak import SpeakV2Speak")
469
+ lines.append("")
470
+ lines.append('client = DeepgramClient(api_key="DEEPGRAM_API_KEY")')
471
+ lines.append("")
472
+ lines.append("with client.speak.v2.connect(")
473
+ lines.append(' model="flux-alexis-en", encoding="linear16", sample_rate="24000"')
474
+ lines.append(") as conn:")
475
+ lines.append(
476
+ ' conn.send_speak(SpeakV2Speak(type="Speak", text="Hello from Flux!"))'
477
+ )
478
+ lines.append(" conn.send_flush()")
479
+ lines.append(" conn.send_close()")
480
+ lines.append(' with open("output.raw", "wb") as f:')
481
+ lines.append(" for message in conn:")
482
+ lines.append(" if isinstance(message, bytes):")
483
+ lines.append(" f.write(message) # raw linear16 PCM, 24kHz mono")
454
484
  lines.append("```")
455
485
  lines.append("")
456
- lines.append("### TTS Example (JavaScript)")
486
+ lines.append("### Aura TTS — Speak v1 (JavaScript)")
457
487
  lines.append("")
458
488
  lines.append("```javascript")
459
489
  lines.append('import { createClient } from "@deepgram/sdk";')
460
490
  lines.append("")
461
- lines.append('const dg = createClient("DEEPGRAM_API_KEY");')
462
- lines.append("")
463
- lines.append("const response = await dg.speak.request(")
464
- lines.append(' { text: "Hello from Deepgram!" },')
465
- lines.append(' { model: "aura-2-andromeda-en" }')
466
- lines.append(");")
491
+ lines.append('const client = createClient("DEEPGRAM_API_KEY");')
467
492
  lines.append("")
468
- lines.append("const stream = await response.getStream();")
469
- lines.append("// Write stream to file or audio output")
493
+ lines.append("const response = await client.speak.v1.audio.generate({")
494
+ lines.append(' text: "Hello from Deepgram!",')
495
+ lines.append(' model: "aura-2-andromeda-en",')
496
+ lines.append("});")
497
+ lines.append("const buffer = await response.arrayBuffer();")
498
+ lines.append("// Write buffer to a file or audio output")
470
499
  lines.append("```")
471
500
  lines.append("")
472
501
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-core
3
- Version: 0.2.12
3
+ Version: 0.2.14
4
4
  Summary: Core components for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -16,7 +16,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
16
  Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  Requires-Dist: click>=8.0.0
19
- Requires-Dist: deepgram-sdk>=6.0.0rc2
19
+ Requires-Dist: deepgram-sdk>=7.5.0
20
20
  Requires-Dist: pydantic>=2.0.0
21
21
  Requires-Dist: rich>=13.0.0
22
22
  Requires-Dist: httpx>=0.24.0
@@ -1,5 +1,5 @@
1
1
  click>=8.0.0
2
- deepgram-sdk>=6.0.0rc2
2
+ deepgram-sdk>=7.5.0
3
3
  pydantic>=2.0.0
4
4
  rich>=13.0.0
5
5
  httpx>=0.24.0
File without changes
File without changes