deepctl-cmd-debug-toolkit 0.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.
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepctl-cmd-debug-toolkit
3
+ Version: 0.1.0
4
+ Summary: Toolkit subcommand for dg debug — runs field support scripts from deepgram/support-toolkit
5
+ Author-email: Deepgram <devrel@deepgram.com>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: deepctl-core>=0.1.10
10
+ Requires-Dist: httpx>=0.27.0
11
+ Requires-Dist: pydantic>=2.10.1
12
+ Requires-Dist: platformdirs>=4.0.0
13
+ Requires-Dist: rich>=13.9.4
14
+ Requires-Dist: click>=8.1.7
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=8.3.3; extra == "dev"
17
+ Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
18
+ Requires-Dist: mypy>=1.13.0; extra == "dev"
19
+ Requires-Dist: ruff>=0.8.1; extra == "dev"
20
+
21
+ # deepctl-cmd-debug-toolkit
22
+
23
+ > Part of [deepctl](https://github.com/deepgram/cli) — Official Deepgram CLI
24
+
25
+ Toolkit subcommand for dg debug — runs field support scripts from deepgram/support-toolkit
26
+
27
+ This is a subcommand of `deepctl debug`.
28
+
29
+ ## Installation
30
+
31
+ This package is included with deepctl and does not need to be installed separately.
32
+
33
+ ### Install deepctl
34
+
35
+ ```bash
36
+ # Install with pip
37
+ pip install deepctl
38
+
39
+ # Or install with uv
40
+ uv tool install deepctl
41
+
42
+ # Or install with pipx
43
+ pipx install deepctl
44
+
45
+ # Or run without installing
46
+ uvx deepctl --help
47
+ pipx run deepctl --help
48
+ ```
49
+
50
+ ## Commands
51
+
52
+ | Command | Entry Point |
53
+ |---------|-------------|
54
+ | `deepctl debug toolkit` | `deepctl_cmd_debug_toolkit.command:ToolkitCommand` |
55
+
56
+ ## Dependencies
57
+
58
+ - `httpx>=0.27.0`
59
+ - `pydantic>=2.10.1`
60
+ - `platformdirs>=4.0.0`
61
+ - `rich>=13.9.4`
62
+ - `click>=8.1.7`
63
+
64
+ ## License
65
+
66
+ MIT — see [LICENSE](../../LICENSE)
@@ -0,0 +1,46 @@
1
+ # deepctl-cmd-debug-toolkit
2
+
3
+ > Part of [deepctl](https://github.com/deepgram/cli) — Official Deepgram CLI
4
+
5
+ Toolkit subcommand for dg debug — runs field support scripts from deepgram/support-toolkit
6
+
7
+ This is a subcommand of `deepctl debug`.
8
+
9
+ ## Installation
10
+
11
+ This package is included with deepctl and does not need to be installed separately.
12
+
13
+ ### Install deepctl
14
+
15
+ ```bash
16
+ # Install with pip
17
+ pip install deepctl
18
+
19
+ # Or install with uv
20
+ uv tool install deepctl
21
+
22
+ # Or install with pipx
23
+ pipx install deepctl
24
+
25
+ # Or run without installing
26
+ uvx deepctl --help
27
+ pipx run deepctl --help
28
+ ```
29
+
30
+ ## Commands
31
+
32
+ | Command | Entry Point |
33
+ |---------|-------------|
34
+ | `deepctl debug toolkit` | `deepctl_cmd_debug_toolkit.command:ToolkitCommand` |
35
+
36
+ ## Dependencies
37
+
38
+ - `httpx>=0.27.0`
39
+ - `pydantic>=2.10.1`
40
+ - `platformdirs>=4.0.0`
41
+ - `rich>=13.9.4`
42
+ - `click>=8.1.7`
43
+
44
+ ## License
45
+
46
+ MIT — see [LICENSE](../../LICENSE)
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "deepctl-cmd-debug-toolkit"
7
+ version = "0.1.0" # x-release-please-version
8
+ description = "Toolkit subcommand for dg debug — runs field support scripts from deepgram/support-toolkit"
9
+ readme = "README.md"
10
+ authors = [{ name = "Deepgram", email = "devrel@deepgram.com" }]
11
+ requires-python = ">=3.10"
12
+ license = "MIT"
13
+ dependencies = [
14
+ "deepctl-core>=0.1.10",
15
+ "httpx>=0.27.0",
16
+ "pydantic>=2.10.1",
17
+ "platformdirs>=4.0.0",
18
+ "rich>=13.9.4",
19
+ "click>=8.1.7",
20
+ ]
21
+
22
+ [project.entry-points."deepctl.subcommands.debug"]
23
+ toolkit = "deepctl_cmd_debug_toolkit.command:ToolkitCommand"
24
+
25
+ [project.optional-dependencies]
26
+ dev = [
27
+ "pytest>=8.3.3",
28
+ "pytest-mock>=3.14.0",
29
+ "mypy>=1.13.0",
30
+ "ruff>=0.8.1",
31
+ ]
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+ include = ["deepctl_cmd_debug_toolkit*"]
36
+
37
+ [tool.ruff]
38
+ line-length = 88
39
+ target-version = "py310"
40
+
41
+ [tool.ruff.lint]
42
+ select = ["E", "F", "I", "N", "W", "B", "Q"]
43
+ ignore = ["E501"]
44
+
45
+ [tool.mypy]
46
+ python_version = "3.10"
47
+ warn_return_any = true
48
+ warn_unused_configs = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """Toolkit subcommand for dg debug — field support scripts from deepgram/support-toolkit."""
2
+
3
+ from .command import ToolkitCommand
4
+
5
+ __all__ = ["ToolkitCommand"]
@@ -0,0 +1,143 @@
1
+ """Toolkit subcommand group — runs field support scripts from deepgram/support-toolkit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ from typing import Any
9
+
10
+ import click
11
+ from deepctl_core import AuthManager, BaseGroupCommand, Config
12
+ from rich.console import Console
13
+
14
+ from .fetcher import get_cached_manifest, get_or_fetch_script, refresh_manifest
15
+ from .models import ToolkitScript
16
+
17
+ console = Console()
18
+
19
+
20
+ class ToolkitCommand(BaseGroupCommand):
21
+ """Runs field engineering scripts from deepgram/support-toolkit."""
22
+
23
+ name = "toolkit"
24
+ help = (
25
+ "Run field engineering diagnostic scripts fetched from "
26
+ "deepgram/support-toolkit. Scripts are downloaded on first use, "
27
+ "verified against their GitHub blob SHA, and cached locally."
28
+ )
29
+ short_help = "Field support scripts (deepgram/support-toolkit)"
30
+
31
+ requires_auth = False
32
+ requires_project = False
33
+ ci_friendly = True
34
+ invoke_without_command = False
35
+
36
+ examples = [
37
+ "dg debug toolkit refresh",
38
+ "dg debug toolkit latency wss://api.deepgram.com/v1/listen",
39
+ "dg debug toolkit latency wss://api.deepgram.com/v1/listen --duration=60",
40
+ ]
41
+
42
+ def setup_commands(self) -> list[click.Command]:
43
+ """Return commands derived from the cached manifest.
44
+
45
+ Intentionally never performs network I/O — reads disk cache only so
46
+ that every `dg` invocation stays fast. Users run `refresh` once to
47
+ populate the cache; individual script commands fetch lazily at invoke
48
+ time.
49
+ """
50
+ commands: list[click.Command] = [_make_refresh_command()]
51
+
52
+ manifest = get_cached_manifest()
53
+ if manifest:
54
+ for name, entry in manifest.commands.items():
55
+ commands.append(_make_script_command(name, entry))
56
+
57
+ return commands
58
+
59
+ def handle_group(
60
+ self,
61
+ config: Config,
62
+ auth_manager: AuthManager,
63
+ client: Any,
64
+ **kwargs: Any,
65
+ ) -> None:
66
+ # BaseGroupCommand.handle() calls handle_group() even when a subcommand
67
+ # is being invoked — guard here so we don't add noise to subcommand runs.
68
+ if click.get_current_context().invoked_subcommand is not None:
69
+ return
70
+
71
+ manifest = get_cached_manifest()
72
+ if not manifest:
73
+ console.print(
74
+ "[yellow]No toolkit scripts cached yet.[/yellow]\n"
75
+ "Run [bold]dg debug toolkit refresh[/bold] to fetch the "
76
+ "latest scripts from deepgram/support-toolkit."
77
+ )
78
+ return
79
+
80
+ console.print("[bold]Available toolkit scripts:[/bold]\n")
81
+ for name, entry in manifest.commands.items():
82
+ console.print(f" [bold]dg debug toolkit {name}[/bold]")
83
+ console.print(f" [dim]{entry.description}[/dim]\n")
84
+
85
+
86
+ def _make_refresh_command() -> click.Command:
87
+ """Static 'refresh' command — always available regardless of cache state."""
88
+
89
+ @click.command(
90
+ "refresh", help="Fetch the latest script list from deepgram/support-toolkit."
91
+ )
92
+ def _refresh() -> None:
93
+ try:
94
+ manifest = refresh_manifest(console)
95
+ except Exception as exc:
96
+ console.print(f"[red]Error fetching manifest:[/red] {exc}")
97
+ sys.exit(1)
98
+
99
+ console.print(
100
+ f"\n[green]✓[/green] Toolkit manifest updated "
101
+ f"({len(manifest.commands)} script(s) available):\n"
102
+ )
103
+ for name, entry in manifest.commands.items():
104
+ console.print(f" [bold]dg debug toolkit {name}[/bold]")
105
+ console.print(f" [dim]{entry.description}[/dim]\n")
106
+
107
+ return _refresh
108
+
109
+
110
+ def _make_script_command(name: str, entry: ToolkitScript) -> click.Command:
111
+ """Create a Click command that fetches, verifies, and runs a toolkit script."""
112
+
113
+ @click.command(
114
+ name,
115
+ help=entry.description,
116
+ # Forward all unrecognised args directly to the underlying script.
117
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
118
+ )
119
+ @click.argument("script_args", nargs=-1, type=click.UNPROCESSED)
120
+ def _run(script_args: tuple[str, ...]) -> None:
121
+ try:
122
+ script_path = get_or_fetch_script(entry, console)
123
+ except Exception as exc:
124
+ console.print(f"[red]Error fetching script:[/red] {exc}")
125
+ sys.exit(1)
126
+
127
+ env = os.environ.copy()
128
+
129
+ if entry.pass_api_key:
130
+ try:
131
+ api_key = AuthManager(Config()).get_api_key()
132
+ if api_key:
133
+ env["DEEPGRAM_API_KEY"] = api_key
134
+ except Exception:
135
+ pass # Script will surface its own missing-key error
136
+
137
+ result = subprocess.run(
138
+ [sys.executable, str(script_path)] + list(script_args),
139
+ env=env,
140
+ )
141
+ sys.exit(result.returncode)
142
+
143
+ return _run
@@ -0,0 +1,145 @@
1
+ """GitHub fetch, blob-SHA integrity verification, and local cache management."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import hashlib
7
+ import os
8
+ import time
9
+ from pathlib import Path
10
+ from typing import TYPE_CHECKING
11
+
12
+ import httpx
13
+ import platformdirs
14
+ from rich.console import Console
15
+
16
+ from .models import ToolkitManifest, ToolkitScript
17
+
18
+ if TYPE_CHECKING:
19
+ pass
20
+
21
+ GITHUB_REPO = "deepgram/support-toolkit"
22
+ GITHUB_API_BASE = f"https://api.github.com/repos/{GITHUB_REPO}/contents"
23
+
24
+ _CACHE_DIR = Path(platformdirs.user_cache_dir("deepctl")) / "toolkit"
25
+ _MANIFEST_CACHE = _CACHE_DIR / "manifest.json"
26
+ _MANIFEST_AGE = _CACHE_DIR / "manifest.age"
27
+ _SCRIPTS_CACHE = _CACHE_DIR / "scripts"
28
+ _MANIFEST_TTL = 86400 # 24 hours
29
+
30
+
31
+ def git_blob_sha(content: bytes) -> str:
32
+ """Compute the git blob SHA1 for integrity verification.
33
+
34
+ GitHub's Contents API returns this hash alongside file content.
35
+ Re-deriving it from the downloaded bytes lets us confirm we received
36
+ exactly what GitHub has, using their own object model as a trust anchor.
37
+ """
38
+ header = f"blob {len(content)}\0".encode()
39
+ return hashlib.sha1(header + content).hexdigest()
40
+
41
+
42
+ def local_toolkit_path() -> Path | None:
43
+ """Return a local override path if DEEPGRAM_TOOLKIT_PATH is set."""
44
+ p = os.getenv("DEEPGRAM_TOOLKIT_PATH")
45
+ return Path(p) if p else None
46
+
47
+
48
+ def _fetch_github_contents(path: str) -> tuple[str, bytes]:
49
+ """Fetch a file from the GitHub Contents API.
50
+
51
+ Returns (blob_sha, raw_bytes). Raises on any HTTP error or SHA mismatch.
52
+ """
53
+ url = f"{GITHUB_API_BASE}/{path}"
54
+ resp = httpx.get(
55
+ url,
56
+ headers={"Accept": "application/vnd.github+json"},
57
+ timeout=30.0,
58
+ follow_redirects=True,
59
+ )
60
+ resp.raise_for_status()
61
+ data = resp.json()
62
+ sha: str = data["sha"]
63
+ content = base64.b64decode(data["content"])
64
+
65
+ # Verify the download is exactly what GitHub reported.
66
+ if git_blob_sha(content) != sha:
67
+ raise RuntimeError(
68
+ f"Integrity check failed for {path}: "
69
+ f"downloaded content does not match GitHub blob SHA."
70
+ )
71
+ return sha, content
72
+
73
+
74
+ def get_cached_manifest() -> ToolkitManifest | None:
75
+ """Return the cached manifest if it exists and is fresh, else None."""
76
+ if not _MANIFEST_CACHE.exists():
77
+ return None
78
+ if _MANIFEST_AGE.exists():
79
+ try:
80
+ age = float(_MANIFEST_AGE.read_text().strip())
81
+ if time.time() - age > _MANIFEST_TTL:
82
+ return None # Stale — caller should refresh
83
+ except Exception:
84
+ return None
85
+ try:
86
+ return ToolkitManifest.model_validate_json(_MANIFEST_CACHE.read_text())
87
+ except Exception:
88
+ return None
89
+
90
+
91
+ def refresh_manifest(console: Console) -> ToolkitManifest:
92
+ """Fetch the toolkit manifest, verify it, cache it, and return it."""
93
+ local = local_toolkit_path()
94
+ if local:
95
+ manifest_file = local / "toolkit.json"
96
+ content = manifest_file.read_bytes()
97
+ console.print(f"[dim]Loading toolkit manifest from {local}[/dim]")
98
+ else:
99
+ console.print(
100
+ f"[dim]Fetching toolkit manifest from github.com/{GITHUB_REPO}...[/dim]"
101
+ )
102
+ _sha, content = _fetch_github_contents("toolkit.json")
103
+
104
+ manifest = ToolkitManifest.model_validate_json(content)
105
+
106
+ _CACHE_DIR.mkdir(parents=True, exist_ok=True)
107
+ _MANIFEST_CACHE.write_bytes(content)
108
+ _MANIFEST_AGE.write_text(str(time.time()))
109
+
110
+ return manifest
111
+
112
+
113
+ def get_or_fetch_script(entry: ToolkitScript, console: Console) -> Path:
114
+ """Return the path to a verified local copy of the script.
115
+
116
+ Uses a local override if DEEPGRAM_TOOLKIT_PATH is set, otherwise
117
+ fetches from GitHub and caches by blob SHA. Re-downloads only when the
118
+ cached SHA no longer matches the stored value.
119
+ """
120
+ local = local_toolkit_path()
121
+ if local:
122
+ return local / entry.script
123
+
124
+ # Stable cache filename derived from the script path.
125
+ safe_name = entry.script.replace("/", "__")
126
+ cache_file = _SCRIPTS_CACHE / safe_name
127
+ sha_file = _SCRIPTS_CACHE / f"{safe_name}.sha"
128
+
129
+ # Use cache if content still matches its stored SHA.
130
+ if cache_file.exists() and sha_file.exists():
131
+ stored_sha = sha_file.read_text().strip()
132
+ if git_blob_sha(cache_file.read_bytes()) == stored_sha:
133
+ return cache_file
134
+
135
+ # Download from GitHub with integrity verification.
136
+ script_name = Path(entry.script).name
137
+ console.print(f"[blue]Downloading[/blue] {script_name} from support-toolkit...")
138
+ sha, content = _fetch_github_contents(entry.script)
139
+
140
+ _SCRIPTS_CACHE.mkdir(parents=True, exist_ok=True)
141
+ cache_file.write_bytes(content)
142
+ sha_file.write_text(sha)
143
+
144
+ console.print(f"[green]✓[/green] {script_name} verified (sha: {sha[:12]}…)")
145
+ return cache_file
@@ -0,0 +1,20 @@
1
+ """Models for the toolkit manifest."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class ToolkitScript(BaseModel):
9
+ """A single script entry from toolkit.json."""
10
+
11
+ description: str
12
+ script: str # Relative path within the support-toolkit repo
13
+ pass_api_key: bool = False # Inject DEEPGRAM_API_KEY into subprocess env
14
+
15
+
16
+ class ToolkitManifest(BaseModel):
17
+ """Parsed representation of toolkit.json."""
18
+
19
+ version: str
20
+ commands: dict[str, ToolkitScript]
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepctl-cmd-debug-toolkit
3
+ Version: 0.1.0
4
+ Summary: Toolkit subcommand for dg debug — runs field support scripts from deepgram/support-toolkit
5
+ Author-email: Deepgram <devrel@deepgram.com>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: deepctl-core>=0.1.10
10
+ Requires-Dist: httpx>=0.27.0
11
+ Requires-Dist: pydantic>=2.10.1
12
+ Requires-Dist: platformdirs>=4.0.0
13
+ Requires-Dist: rich>=13.9.4
14
+ Requires-Dist: click>=8.1.7
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=8.3.3; extra == "dev"
17
+ Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
18
+ Requires-Dist: mypy>=1.13.0; extra == "dev"
19
+ Requires-Dist: ruff>=0.8.1; extra == "dev"
20
+
21
+ # deepctl-cmd-debug-toolkit
22
+
23
+ > Part of [deepctl](https://github.com/deepgram/cli) — Official Deepgram CLI
24
+
25
+ Toolkit subcommand for dg debug — runs field support scripts from deepgram/support-toolkit
26
+
27
+ This is a subcommand of `deepctl debug`.
28
+
29
+ ## Installation
30
+
31
+ This package is included with deepctl and does not need to be installed separately.
32
+
33
+ ### Install deepctl
34
+
35
+ ```bash
36
+ # Install with pip
37
+ pip install deepctl
38
+
39
+ # Or install with uv
40
+ uv tool install deepctl
41
+
42
+ # Or install with pipx
43
+ pipx install deepctl
44
+
45
+ # Or run without installing
46
+ uvx deepctl --help
47
+ pipx run deepctl --help
48
+ ```
49
+
50
+ ## Commands
51
+
52
+ | Command | Entry Point |
53
+ |---------|-------------|
54
+ | `deepctl debug toolkit` | `deepctl_cmd_debug_toolkit.command:ToolkitCommand` |
55
+
56
+ ## Dependencies
57
+
58
+ - `httpx>=0.27.0`
59
+ - `pydantic>=2.10.1`
60
+ - `platformdirs>=4.0.0`
61
+ - `rich>=13.9.4`
62
+ - `click>=8.1.7`
63
+
64
+ ## License
65
+
66
+ MIT — see [LICENSE](../../LICENSE)
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/deepctl_cmd_debug_toolkit/__init__.py
4
+ src/deepctl_cmd_debug_toolkit/command.py
5
+ src/deepctl_cmd_debug_toolkit/fetcher.py
6
+ src/deepctl_cmd_debug_toolkit/models.py
7
+ src/deepctl_cmd_debug_toolkit.egg-info/PKG-INFO
8
+ src/deepctl_cmd_debug_toolkit.egg-info/SOURCES.txt
9
+ src/deepctl_cmd_debug_toolkit.egg-info/dependency_links.txt
10
+ src/deepctl_cmd_debug_toolkit.egg-info/entry_points.txt
11
+ src/deepctl_cmd_debug_toolkit.egg-info/requires.txt
12
+ src/deepctl_cmd_debug_toolkit.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [deepctl.subcommands.debug]
2
+ toolkit = deepctl_cmd_debug_toolkit.command:ToolkitCommand
@@ -0,0 +1,12 @@
1
+ deepctl-core>=0.1.10
2
+ httpx>=0.27.0
3
+ pydantic>=2.10.1
4
+ platformdirs>=4.0.0
5
+ rich>=13.9.4
6
+ click>=8.1.7
7
+
8
+ [dev]
9
+ pytest>=8.3.3
10
+ pytest-mock>=3.14.0
11
+ mypy>=1.13.0
12
+ ruff>=0.8.1