deepctl-core 0.2.14__tar.gz → 0.2.16__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.14 → deepctl_core-0.2.16}/PKG-INFO +3 -3
  2. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/README.md +1 -1
  3. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/pyproject.toml +2 -2
  4. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/__init__.py +3 -0
  5. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/base_command.py +53 -5
  6. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/client.py +9 -0
  7. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/output.py +16 -0
  8. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/skill_generator.py +11 -2
  9. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core.egg-info/PKG-INFO +3 -3
  10. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core.egg-info/requires.txt +1 -1
  11. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/setup.cfg +0 -0
  12. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/auth.py +0 -0
  13. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/base_group_command.py +0 -0
  14. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/config.py +0 -0
  15. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/models.py +0 -0
  16. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/plugin_env.py +0 -0
  17. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/plugin_manager.py +0 -0
  18. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/py.typed +0 -0
  19. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core/timing.py +0 -0
  20. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core.egg-info/SOURCES.txt +0 -0
  21. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/src/deepctl_core.egg-info/dependency_links.txt +0 -0
  22. {deepctl_core-0.2.14 → deepctl_core-0.2.16}/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.14
3
+ Version: 0.2.16
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>=7.5.0
19
+ Requires-Dist: deepgram-sdk<8,>=7.7.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
@@ -63,7 +63,7 @@ pipx run deepctl --help
63
63
  ## Dependencies
64
64
 
65
65
  - `click>=8.0.0`
66
- - `deepgram-sdk>=6.0.0rc2`
66
+ - `deepgram-sdk>=7.7.0,<8`
67
67
  - `pydantic>=2.0.0`
68
68
  - `rich>=13.0.0`
69
69
  - `httpx>=0.24.0`
@@ -30,7 +30,7 @@ pipx run deepctl --help
30
30
  ## Dependencies
31
31
 
32
32
  - `click>=8.0.0`
33
- - `deepgram-sdk>=6.0.0rc2`
33
+ - `deepgram-sdk>=7.7.0,<8`
34
34
  - `pydantic>=2.0.0`
35
35
  - `rich>=13.0.0`
36
36
  - `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.14" # x-release-please-version
7
+ version = "0.2.16" # 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>=7.5.0",
26
+ "deepgram-sdk>=7.7.0,<8",
27
27
  "pydantic>=2.0.0",
28
28
  "rich>=13.0.0",
29
29
  "httpx>=0.24.0",
@@ -16,6 +16,7 @@ from .output import (
16
16
  OutputFormatter,
17
17
  get_console,
18
18
  get_output_format,
19
+ get_status_console,
19
20
  is_agentic,
20
21
  print_error,
21
22
  print_info,
@@ -50,6 +51,8 @@ __all__ = [
50
51
  "TimingContext",
51
52
  "enable_timing",
52
53
  "get_console",
54
+ "get_output_format",
55
+ "get_status_console",
53
56
  "get_timing_summary",
54
57
  "is_agentic",
55
58
  "is_timing_enabled",
@@ -29,6 +29,19 @@ class BaseCommand(ABC):
29
29
  requires_project: bool = False
30
30
  ci_friendly: bool = True
31
31
 
32
+ # Result status -> process exit code, per the contract published in
33
+ # llms-full.txt: 0 = success, 1 = error, 2 = user interrupt. A command that
34
+ # reports failure must not exit 0 -- scripts and CI branch on the exit code,
35
+ # not on the "status" field inside the payload. "cancelled" is 2 rather than
36
+ # the shell's 130 to match that contract and main.py's own KeyboardInterrupt
37
+ # handler; `dg mcp` in particular returns cancelled straight out of its
38
+ # KeyboardInterrupt path. Any status not listed here exits 0
39
+ # (success, succeeded, info, dry_run, warning).
40
+ EXIT_CODES: ClassVar[dict[str, int]] = {
41
+ "error": 1,
42
+ "cancelled": 2,
43
+ }
44
+
32
45
  # Agent-oriented metadata
33
46
  examples: ClassVar[list[str]] = []
34
47
  agent_help: str = ""
@@ -147,6 +160,23 @@ class BaseCommand(ABC):
147
160
  stderr_console.print_exception()
148
161
  raise click.ClickException(str(e))
149
162
 
163
+ # Payload is already on stdout; now make the exit code agree with it.
164
+ exit_code = self.exit_code_for(result)
165
+ if exit_code:
166
+ raise SystemExit(exit_code)
167
+
168
+ def exit_code_for(self, result: Any) -> int:
169
+ """Map a command result to a process exit code.
170
+
171
+ Args:
172
+ result: Command result (may be None)
173
+
174
+ Returns:
175
+ Exit code for this result's status; 0 when it reported no failure
176
+ """
177
+ status = str(getattr(result, "status", "") or "")
178
+ return self.EXIT_CODES.get(status, 0)
179
+
150
180
  def is_guided(self, ctx: click.Context) -> bool:
151
181
  """True only when the user invoked this command with no input at all.
152
182
 
@@ -285,9 +315,25 @@ class BaseCommand(ABC):
285
315
  elif output_format == "csv":
286
316
  self._output_csv(result)
287
317
  else:
288
- console.print(f"[red]Unknown output format:[/red] {output_format}")
318
+ # Diagnostics belong on stderr even here, or the complaint about
319
+ # the format corrupts the payload we fall back to.
320
+ stderr_console.print(
321
+ f"[red]Unknown output format:[/red] {output_format}"
322
+ )
289
323
  self._output_json(result)
290
324
 
325
+ def _write_payload(self, text: str) -> None:
326
+ """Write already-serialised machine-readable text to stdout verbatim.
327
+
328
+ Rich's default print would treat square brackets as style markup and
329
+ silently delete them, so a key comment of "[ci] runner" would lose the
330
+ "[ci]". It would also hard-wrap at the console width, injecting newlines
331
+ into the middle of a value. Both corrupt the payload, so markup,
332
+ highlighting and wrapping are all off. Routed through `console` rather
333
+ than `sys.stdout` so `--quiet` still suppresses output.
334
+ """
335
+ console.print(text, markup=False, highlight=False, soft_wrap=True)
336
+
291
337
  def _output_json(self, result: Any) -> None:
292
338
  """Output result as JSON."""
293
339
  import json
@@ -310,9 +356,11 @@ class BaseCommand(ABC):
310
356
  import yaml
311
357
 
312
358
  if isinstance(result, dict | list):
313
- console.print(yaml.dump(result, default_flow_style=False))
359
+ self._write_payload(yaml.dump(result, default_flow_style=False))
314
360
  else:
315
- console.print(yaml.dump({"result": str(result)}, default_flow_style=False))
361
+ self._write_payload(
362
+ yaml.dump({"result": str(result)}, default_flow_style=False)
363
+ )
316
364
 
317
365
  def _output_table(self, result: Any) -> None:
318
366
  """Output result as table."""
@@ -359,7 +407,7 @@ class BaseCommand(ABC):
359
407
  dict_writer = csv.DictWriter(output, fieldnames=result[0].keys())
360
408
  dict_writer.writeheader()
361
409
  dict_writer.writerows(result)
362
- console.print(output.getvalue())
410
+ self._write_payload(output.getvalue())
363
411
 
364
412
  elif isinstance(result, dict):
365
413
  # Dictionary
@@ -368,7 +416,7 @@ class BaseCommand(ABC):
368
416
  writer.writerow(["Key", "Value"])
369
417
  for key, value in result.items():
370
418
  writer.writerow([key, value])
371
- console.print(output.getvalue())
419
+ self._write_payload(output.getvalue())
372
420
 
373
421
  else:
374
422
  # Fallback to JSON
@@ -189,12 +189,17 @@ class DeepgramClient:
189
189
  model: str,
190
190
  encoding: str | None = None,
191
191
  sample_rate: float | None = None,
192
+ speed: float | None = None,
193
+ expressivity: int | None = None,
192
194
  ) -> Iterator[bytes]:
193
195
  """Stream TTS audio over the Flux v2 WebSocket (speak.v2.connect).
194
196
 
195
197
  Yields raw audio chunks as they arrive. The streaming transport emits
196
198
  raw (non-containerized) audio, so only linear16/mulaw/alaw encodings
197
199
  apply and sample_rate is sent as the string the streaming API expects.
200
+
201
+ ``speed`` (0.85-1.15) and beta ``expressivity`` (-2..2, default 0) are
202
+ Flux connect query parameters; they are only sent when set.
198
203
  """
199
204
  from deepgram.speak.v2.types.speak_v2speak import SpeakV2Speak
200
205
 
@@ -203,6 +208,10 @@ class DeepgramClient:
203
208
  connect_kwargs["encoding"] = encoding
204
209
  if sample_rate:
205
210
  connect_kwargs["sample_rate"] = str(int(sample_rate))
211
+ if speed is not None:
212
+ connect_kwargs["speed"] = speed
213
+ if expressivity is not None:
214
+ connect_kwargs["expressivity"] = expressivity
206
215
 
207
216
  try:
208
217
  with self.client.speak.v2.connect(**connect_kwargs) as conn:
@@ -505,3 +505,19 @@ def get_console() -> Console:
505
505
  Console instance
506
506
  """
507
507
  return console
508
+
509
+
510
+ def get_status_console() -> Console:
511
+ """Get the console for status/progress chrome.
512
+
513
+ Always writes to stderr, so anything printed through it can never corrupt
514
+ the machine-readable payload a command writes to stdout. Commands should
515
+ use this rather than declaring their own ``Console(stderr=True)`` — a
516
+ per-command console silently misses the agentic no-color settings, and a
517
+ command that reaches for a bare ``Console()`` reintroduces the stdout
518
+ pollution this exists to prevent.
519
+
520
+ Returns:
521
+ Console instance writing to stderr
522
+ """
523
+ return stderr_console
@@ -417,7 +417,8 @@ def render_developer_guide(
417
417
  lines.append("## Text-to-Speech (TTS)")
418
418
  lines.append("")
419
419
  lines.append(
420
- "Generate natural-sounding speech from text using Deepgram's Aura voices."
420
+ "Generate natural-sounding speech from text using Deepgram's Aura and "
421
+ "Flux voices."
421
422
  )
422
423
  lines.append("")
423
424
  lines.append("### Models")
@@ -463,6 +464,10 @@ def render_developer_guide(
463
464
  lines.append("")
464
465
  lines.append("### Flux TTS — Speak v2, WebSocket streaming (Python)")
465
466
  lines.append("")
467
+ lines.append(
468
+ "`expressivity` is beta and defaults to `0` (nominal delivery) when omitted."
469
+ )
470
+ lines.append("")
466
471
  lines.append("```python")
467
472
  lines.append("from deepgram import DeepgramClient")
468
473
  lines.append("from deepgram.speak.v2.types.speak_v2speak import SpeakV2Speak")
@@ -470,7 +475,11 @@ def render_developer_guide(
470
475
  lines.append('client = DeepgramClient(api_key="DEEPGRAM_API_KEY")')
471
476
  lines.append("")
472
477
  lines.append("with client.speak.v2.connect(")
473
- lines.append(' model="flux-alexis-en", encoding="linear16", sample_rate="24000"')
478
+ lines.append(' model="flux-alexis-en",')
479
+ lines.append(' encoding="linear16",')
480
+ lines.append(' sample_rate="24000",')
481
+ lines.append(" speed=1.0, # 0.85–1.15 in 0.05 steps (optional)")
482
+ lines.append(" expressivity=0, # beta; -2..2, default 0 = nominal (optional)")
474
483
  lines.append(") as conn:")
475
484
  lines.append(
476
485
  ' conn.send_speak(SpeakV2Speak(type="Speak", text="Hello from Flux!"))'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-core
3
- Version: 0.2.14
3
+ Version: 0.2.16
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>=7.5.0
19
+ Requires-Dist: deepgram-sdk<8,>=7.7.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
@@ -63,7 +63,7 @@ pipx run deepctl --help
63
63
  ## Dependencies
64
64
 
65
65
  - `click>=8.0.0`
66
- - `deepgram-sdk>=6.0.0rc2`
66
+ - `deepgram-sdk>=7.7.0,<8`
67
67
  - `pydantic>=2.0.0`
68
68
  - `rich>=13.0.0`
69
69
  - `httpx>=0.24.0`
@@ -1,5 +1,5 @@
1
1
  click>=8.0.0
2
- deepgram-sdk>=7.5.0
2
+ deepgram-sdk<8,>=7.7.0
3
3
  pydantic>=2.0.0
4
4
  rich>=13.0.0
5
5
  httpx>=0.24.0
File without changes