deepctl-cmd-api 0.0.2__py3-none-any.whl

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,5 @@
1
+ """API command for deepctl."""
2
+
3
+ from .command import ApiCommand
4
+
5
+ __all__ = ["ApiCommand"]
@@ -0,0 +1,330 @@
1
+ """API command for deepctl."""
2
+
3
+ import json
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ from typing import Any
8
+
9
+ import httpx
10
+ from deepctl_core import AuthManager, BaseCommand, Config, DeepgramClient
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+
14
+ from .models import ApiResult
15
+
16
+ console = Console()
17
+
18
+ DEFAULT_BASE_URL = "https://api.deepgram.com"
19
+
20
+
21
+ class ApiCommand(BaseCommand):
22
+ """Make authenticated requests to Deepgram APIs."""
23
+
24
+ name = "api"
25
+ help = "Make authenticated requests to Deepgram APIs"
26
+ short_help = "Make API requests"
27
+
28
+ requires_auth = True
29
+ requires_project = False
30
+ ci_friendly = True
31
+
32
+ examples = [
33
+ "dg api /v1/projects",
34
+ "dg api -X POST /v1/projects -f name=MyProject",
35
+ "dg api /v1/projects --jq '.projects[0].name'",
36
+ ]
37
+ agent_help = (
38
+ "Make authenticated HTTP requests to any Deepgram REST API endpoint. "
39
+ "Works like curl with auto-injected credentials. Supports all HTTP methods, "
40
+ "body construction via -f key=value, and jq filtering of responses."
41
+ )
42
+
43
+ def get_arguments(self) -> list[dict[str, Any]]:
44
+ """Get command arguments and options."""
45
+ return [
46
+ {
47
+ "name": "endpoint",
48
+ "help": "API endpoint path (e.g. /v1/projects)",
49
+ "type": str,
50
+ "required": True,
51
+ "is_option": False,
52
+ },
53
+ {
54
+ "names": ["-X", "--method"],
55
+ "help": "HTTP method (default: GET)",
56
+ "type": str,
57
+ "default": "GET",
58
+ "is_option": True,
59
+ },
60
+ {
61
+ "names": ["-f", "--field"],
62
+ "help": (
63
+ "Request body field as key=value (string) or key:=value (raw JSON)"
64
+ ),
65
+ "type": str,
66
+ "multiple": True,
67
+ "is_option": True,
68
+ },
69
+ {
70
+ "names": ["--input"],
71
+ "help": "Request body from file path or - for stdin",
72
+ "type": str,
73
+ "is_option": True,
74
+ },
75
+ {
76
+ "names": ["-H", "--header"],
77
+ "help": "Custom header as 'Key: Value'",
78
+ "type": str,
79
+ "multiple": True,
80
+ "is_option": True,
81
+ },
82
+ {
83
+ "names": ["--jq"],
84
+ "help": "jq filter expression to apply to response",
85
+ "type": str,
86
+ "is_option": True,
87
+ },
88
+ {
89
+ "names": ["--raw"],
90
+ "help": "Output raw response without formatting",
91
+ "is_flag": True,
92
+ "is_option": True,
93
+ },
94
+ ]
95
+
96
+ def _resolve_url(self, endpoint: str) -> str:
97
+ """Resolve endpoint to full URL."""
98
+ if endpoint.startswith("http://") or endpoint.startswith("https://"):
99
+ return endpoint
100
+ base = DEFAULT_BASE_URL.rstrip("/")
101
+ path = endpoint if endpoint.startswith("/") else f"/{endpoint}"
102
+ return f"{base}{path}"
103
+
104
+ def _build_headers(
105
+ self,
106
+ api_key: str,
107
+ custom_headers: tuple[str, ...] | list[str] | None,
108
+ ) -> dict[str, str]:
109
+ """Build request headers with auth and custom headers."""
110
+ headers: dict[str, str] = {
111
+ "Authorization": f"Token {api_key}",
112
+ }
113
+ if custom_headers:
114
+ for header in custom_headers:
115
+ if ":" in header:
116
+ key, value = header.split(":", 1)
117
+ headers[key.strip()] = value.strip()
118
+ return headers
119
+
120
+ def _build_body(
121
+ self,
122
+ fields: tuple[str, ...] | list[str] | None,
123
+ input_source: str | None,
124
+ ) -> Any | None:
125
+ """Build request body from fields or input source."""
126
+ if fields and input_source:
127
+ raise ValueError("Cannot use both --field and --input. Choose one.")
128
+
129
+ if fields:
130
+ body: dict[str, Any] = {}
131
+ for field in fields:
132
+ if ":=" in field:
133
+ key, raw_value = field.split(":=", 1)
134
+ body[key] = json.loads(raw_value)
135
+ elif "=" in field:
136
+ key, value = field.split("=", 1)
137
+ body[key] = value
138
+ else:
139
+ raise ValueError(
140
+ f"Invalid field format: {field}. "
141
+ f"Use key=value or key:=json_value"
142
+ )
143
+ return body
144
+
145
+ if input_source:
146
+ if input_source == "-":
147
+ raw = sys.stdin.read()
148
+ else:
149
+ with open(input_source) as f:
150
+ raw = f.read()
151
+ try:
152
+ return json.loads(raw)
153
+ except json.JSONDecodeError:
154
+ return raw
155
+
156
+ return None
157
+
158
+ def _apply_jq_filter(self, response_text: str, jq_filter: str) -> str:
159
+ """Apply jq filter to response text."""
160
+ if not shutil.which("jq"):
161
+ console.print(
162
+ Panel(
163
+ "[red]jq is not installed[/red]\n\n"
164
+ "[bold]To install jq:[/bold]\n"
165
+ " macOS: [dim]brew install jq[/dim]\n"
166
+ " Ubuntu/Debian: [dim]sudo apt install jq[/dim]\n"
167
+ " Windows: [dim]choco install jq[/dim]\n"
168
+ " Or visit: [link]https://jqlang.github.io/jq/download/[/link]",
169
+ title="jq Required",
170
+ border_style="red",
171
+ )
172
+ )
173
+ raise RuntimeError("jq is not installed")
174
+
175
+ result = subprocess.run(
176
+ ["jq", jq_filter],
177
+ input=response_text,
178
+ capture_output=True,
179
+ text=True,
180
+ )
181
+ if result.returncode != 0:
182
+ raise RuntimeError(f"jq error: {result.stderr.strip()}")
183
+ return result.stdout.strip()
184
+
185
+ def handle(
186
+ self,
187
+ config: Config,
188
+ auth_manager: AuthManager,
189
+ client: DeepgramClient,
190
+ **kwargs: Any,
191
+ ) -> Any:
192
+ """Handle API command execution."""
193
+ endpoint = kwargs.get("endpoint", "")
194
+ method = (kwargs.get("method") or "GET").upper()
195
+ fields = kwargs.get("field") or ()
196
+ input_source = kwargs.get("input")
197
+ custom_headers = kwargs.get("header") or ()
198
+ jq_filter = kwargs.get("jq")
199
+ raw_output = kwargs.get("raw", False)
200
+
201
+ # Resolve URL
202
+ url = self._resolve_url(endpoint)
203
+
204
+ # Get API key
205
+ api_key = auth_manager.get_api_key()
206
+ if not api_key:
207
+ console.print("[red]No API key found. Run 'deepctl login' first.[/red]")
208
+ return ApiResult(
209
+ status="error",
210
+ message="No API key found",
211
+ method=method,
212
+ url=url,
213
+ )
214
+
215
+ # Build headers
216
+ headers = self._build_headers(api_key, custom_headers)
217
+
218
+ # Build body
219
+ try:
220
+ body = self._build_body(fields, input_source)
221
+ except (ValueError, json.JSONDecodeError) as e:
222
+ console.print(f"[red]Error building request body: {e}[/red]")
223
+ return ApiResult(
224
+ status="error",
225
+ message=str(e),
226
+ method=method,
227
+ url=url,
228
+ )
229
+
230
+ # Prepare request kwargs
231
+ request_kwargs: dict[str, Any] = {
232
+ "method": method,
233
+ "url": url,
234
+ "headers": headers,
235
+ }
236
+
237
+ if body is not None:
238
+ if isinstance(body, (dict, list)):
239
+ request_kwargs["json"] = body
240
+ else:
241
+ request_kwargs["content"] = body
242
+
243
+ # Make request
244
+ try:
245
+ with httpx.Client(timeout=30.0) as http_client:
246
+ response = http_client.request(**request_kwargs)
247
+ except httpx.RequestError as e:
248
+ console.print(f"[red]Request failed: {e}[/red]")
249
+ return ApiResult(
250
+ status="error",
251
+ message=f"Request failed: {e}",
252
+ method=method,
253
+ url=url,
254
+ )
255
+
256
+ elapsed_ms = response.elapsed.total_seconds() * 1000
257
+
258
+ # Handle response
259
+ response_text = response.text
260
+ response_body: Any = None
261
+
262
+ try:
263
+ response_body = response.json()
264
+ except (json.JSONDecodeError, ValueError):
265
+ response_body = response_text
266
+
267
+ # Check for errors
268
+ if response.status_code >= 400:
269
+ console.print(f"[red]{method} {url} → {response.status_code}[/red]")
270
+ if isinstance(response_body, dict):
271
+ console.print_json(data=response_body)
272
+ elif response_text:
273
+ console.print(f"[dim]{response_text[:1000]}[/dim]")
274
+
275
+ return ApiResult(
276
+ status="error",
277
+ message=f"HTTP {response.status_code}",
278
+ method=method,
279
+ url=url,
280
+ status_code=response.status_code,
281
+ response_body=response_body,
282
+ elapsed_ms=elapsed_ms,
283
+ )
284
+
285
+ # Apply jq filter if specified
286
+ if jq_filter:
287
+ try:
288
+ json_text = (
289
+ json.dumps(response_body)
290
+ if isinstance(response_body, (dict, list))
291
+ else response_text
292
+ )
293
+ filtered = self._apply_jq_filter(json_text, jq_filter)
294
+ console.print(filtered)
295
+ return ApiResult(
296
+ status="success",
297
+ method=method,
298
+ url=url,
299
+ status_code=response.status_code,
300
+ response_body=filtered,
301
+ elapsed_ms=elapsed_ms,
302
+ )
303
+ except RuntimeError as e:
304
+ console.print(f"[red]{e}[/red]")
305
+ return ApiResult(
306
+ status="error",
307
+ message=str(e),
308
+ method=method,
309
+ url=url,
310
+ status_code=response.status_code,
311
+ response_body=response_body,
312
+ elapsed_ms=elapsed_ms,
313
+ )
314
+
315
+ # Output response
316
+ if raw_output:
317
+ console.print(response_text)
318
+ elif isinstance(response_body, (dict, list)):
319
+ console.print_json(data=response_body)
320
+ else:
321
+ console.print(response_text)
322
+
323
+ return ApiResult(
324
+ status="success",
325
+ method=method,
326
+ url=url,
327
+ status_code=response.status_code,
328
+ response_body=response_body,
329
+ elapsed_ms=elapsed_ms,
330
+ )
@@ -0,0 +1,15 @@
1
+ """Data models for API command."""
2
+
3
+ from typing import Any
4
+
5
+ from deepctl_core import BaseResult
6
+
7
+
8
+ class ApiResult(BaseResult):
9
+ """Result from API command execution."""
10
+
11
+ method: str = ""
12
+ url: str = ""
13
+ status_code: int = 0
14
+ response_body: Any = None
15
+ elapsed_ms: float | None = None
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepctl-cmd-api
3
+ Version: 0.0.2
4
+ Summary: API command for deepctl
5
+ Author-email: Deepgram <devrel@deepgram.com>
6
+ Maintainer-email: Deepgram <devrel@deepgram.com>
7
+ License-Expression: MIT
8
+ Keywords: deepgram,cli,api,rest
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: deepctl-core>=0.1.10
18
+ Requires-Dist: click>=8.0.0
19
+ Requires-Dist: rich>=13.0.0
20
+ Requires-Dist: pydantic>=2.0.0
21
+ Requires-Dist: httpx>=0.24.0
22
+
23
+ # deepctl-cmd-api
24
+
25
+ > Part of [deepctl](https://github.com/deepgram/cli) — Official Deepgram CLI
26
+
27
+ API command for deepctl
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 api` | `deepctl_cmd_api.command:ApiCommand` |
55
+
56
+ ## Dependencies
57
+
58
+ - `click>=8.0.0`
59
+ - `rich>=13.0.0`
60
+ - `pydantic>=2.0.0`
61
+ - `httpx>=0.24.0`
62
+
63
+ ## License
64
+
65
+ MIT — see [LICENSE](../../LICENSE)
@@ -0,0 +1,8 @@
1
+ deepctl_cmd_api/__init__.py,sha256=-tM5NMfD0YOfZPWsJOeCV2-9RWfbiA-Rhqs9_354FRA,90
2
+ deepctl_cmd_api/command.py,sha256=a6ZBdsShkj0H-P2qobEnKw9yXqfTY-T-W005z27rwYY,10812
3
+ deepctl_cmd_api/models.py,sha256=jZJX4K1UFsdjyJaQXHJphUhEb1zDU3VMIAQpNmsBLqQ,303
4
+ deepctl_cmd_api-0.0.2.dist-info/METADATA,sha256=3GiwMpzxjtjP1_8BO5BDbJGdDEv4WLFjtGE3Qwu4_wA,1472
5
+ deepctl_cmd_api-0.0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ deepctl_cmd_api-0.0.2.dist-info/entry_points.txt,sha256=STXtVqiEWvWCfWYrBfIgnGsdvNH09knfiXVB4Azh91U,60
7
+ deepctl_cmd_api-0.0.2.dist-info/top_level.txt,sha256=eR7egK4KtMzd_nWbn-3f6GH_aIAyGYTVdlg1HIlln4c,16
8
+ deepctl_cmd_api-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [deepctl.commands]
2
+ api = deepctl_cmd_api.command:ApiCommand
@@ -0,0 +1 @@
1
+ deepctl_cmd_api