deepctl-core 0.2.0__tar.gz → 0.2.2__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.
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/PKG-INFO +1 -1
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/pyproject.toml +1 -1
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/__init__.py +2 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/base_command.py +5 -1
- deepctl_core-0.2.2/src/deepctl_core/client.py +542 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/output.py +60 -33
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/skill_generator.py +53 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core.egg-info/PKG-INFO +1 -1
- deepctl_core-0.2.0/src/deepctl_core/client.py +0 -305
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/README.md +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/setup.cfg +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/auth.py +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/base_group_command.py +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/config.py +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/models.py +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/plugin_env.py +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/plugin_manager.py +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/py.typed +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core/timing.py +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core.egg-info/SOURCES.txt +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core.egg-info/dependency_links.txt +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core.egg-info/requires.txt +0 -0
- {deepctl_core-0.2.0 → deepctl_core-0.2.2}/src/deepctl_core.egg-info/top_level.txt +0 -0
|
@@ -16,6 +16,7 @@ from .output import (
|
|
|
16
16
|
OutputFormatter,
|
|
17
17
|
get_console,
|
|
18
18
|
get_output_format,
|
|
19
|
+
is_agentic,
|
|
19
20
|
print_error,
|
|
20
21
|
print_info,
|
|
21
22
|
print_output,
|
|
@@ -50,6 +51,7 @@ __all__ = [
|
|
|
50
51
|
"enable_timing",
|
|
51
52
|
"get_console",
|
|
52
53
|
"get_timing_summary",
|
|
54
|
+
"is_agentic",
|
|
53
55
|
"is_timing_enabled",
|
|
54
56
|
"print_error",
|
|
55
57
|
"print_info",
|
|
@@ -189,7 +189,11 @@ class BaseCommand(ABC):
|
|
|
189
189
|
result = [item.model_dump() for item in result]
|
|
190
190
|
|
|
191
191
|
with TimingContext(f"output_{output_format}"):
|
|
192
|
-
if output_format
|
|
192
|
+
if output_format == "default":
|
|
193
|
+
# Commands handle their own display in default mode;
|
|
194
|
+
# only emit structured output when explicitly requested.
|
|
195
|
+
return
|
|
196
|
+
elif output_format == "json":
|
|
193
197
|
self._output_json(result)
|
|
194
198
|
elif output_format == "yaml":
|
|
195
199
|
self._output_yaml(result)
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
"""Deepgram SDK wrapper for deepctl with authentication integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
7
|
+
|
|
8
|
+
from deepgram import DeepgramClient as DGClient
|
|
9
|
+
from deepgram import DeepgramClientEnvironment
|
|
10
|
+
from deepgram.core.api_error import ApiError
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Iterator
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
from .auth import AuthManager
|
|
18
|
+
from .config import Config
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DeepgramClient:
|
|
24
|
+
"""Wrapper around Deepgram SDK with authentication integration."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, config: Config, auth_manager: AuthManager):
|
|
27
|
+
self.config = config
|
|
28
|
+
self.auth_manager = auth_manager
|
|
29
|
+
self._client: DGClient | None = None
|
|
30
|
+
self._project_id: str | None = None
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def client(self) -> DGClient:
|
|
34
|
+
"""Get authenticated Deepgram client."""
|
|
35
|
+
if self._client is None:
|
|
36
|
+
self._client = self._create_client()
|
|
37
|
+
return self._client
|
|
38
|
+
|
|
39
|
+
def _resolve_project_id(self, project_id: str | None = None) -> str:
|
|
40
|
+
"""Resolve project ID from argument, config, or auth manager."""
|
|
41
|
+
if project_id:
|
|
42
|
+
return project_id
|
|
43
|
+
_ = self.client # ensure client is initialized
|
|
44
|
+
resolved = self._project_id or self.auth_manager.get_project_id()
|
|
45
|
+
if not resolved:
|
|
46
|
+
raise ApiError(body="No project ID available")
|
|
47
|
+
return resolved
|
|
48
|
+
|
|
49
|
+
def _create_client(self) -> DGClient:
|
|
50
|
+
"""Create authenticated Deepgram client."""
|
|
51
|
+
self.auth_manager.guard()
|
|
52
|
+
api_key = self.auth_manager.get_api_key()
|
|
53
|
+
project_id = self.auth_manager.get_project_id()
|
|
54
|
+
|
|
55
|
+
if not api_key:
|
|
56
|
+
raise ApiError(body="No API key available")
|
|
57
|
+
|
|
58
|
+
current_profile = self.config.get_profile()
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
kwargs: dict[str, Any] = {"api_key": api_key}
|
|
62
|
+
|
|
63
|
+
if (
|
|
64
|
+
current_profile.base_url
|
|
65
|
+
and current_profile.base_url != "https://api.deepgram.com"
|
|
66
|
+
):
|
|
67
|
+
kwargs["environment"] = DeepgramClientEnvironment(
|
|
68
|
+
base=current_profile.base_url,
|
|
69
|
+
production=current_profile.base_url,
|
|
70
|
+
agent=current_profile.base_url,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
client = DGClient(**kwargs)
|
|
74
|
+
self._project_id = project_id
|
|
75
|
+
return client
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
console.print(f"[red]Error creating Deepgram client:[/red] {e}")
|
|
79
|
+
raise ApiError(body=f"Failed to create client: {e}")
|
|
80
|
+
|
|
81
|
+
# ── Speech-to-Text (pre-recorded) ──────────────────────────────
|
|
82
|
+
|
|
83
|
+
def transcribe_file(
|
|
84
|
+
self,
|
|
85
|
+
file_path: str | Path,
|
|
86
|
+
options: dict[str, Any] | None = None,
|
|
87
|
+
) -> dict[str, Any]:
|
|
88
|
+
"""Transcribe an audio file."""
|
|
89
|
+
file_path = Path(file_path)
|
|
90
|
+
if not file_path.exists():
|
|
91
|
+
raise FileNotFoundError(f"Audio file not found: {file_path}")
|
|
92
|
+
|
|
93
|
+
default_options: dict[str, Any] = {
|
|
94
|
+
"model": "nova-3",
|
|
95
|
+
"smart_format": True,
|
|
96
|
+
"language": "en-US",
|
|
97
|
+
}
|
|
98
|
+
if options:
|
|
99
|
+
default_options.update(options)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
with open(file_path, "rb") as audio_file:
|
|
103
|
+
audio_data = audio_file.read()
|
|
104
|
+
|
|
105
|
+
response = self.client.listen.v1.media.transcribe_file(
|
|
106
|
+
request=audio_data, **default_options
|
|
107
|
+
)
|
|
108
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
raise ApiError(body=f"Transcription failed: {e}")
|
|
112
|
+
|
|
113
|
+
def transcribe_url(
|
|
114
|
+
self, url: str, options: dict[str, Any] | None = None
|
|
115
|
+
) -> dict[str, Any]:
|
|
116
|
+
"""Transcribe audio from URL."""
|
|
117
|
+
default_options: dict[str, Any] = {
|
|
118
|
+
"model": "nova-3",
|
|
119
|
+
"smart_format": True,
|
|
120
|
+
"language": "en-US",
|
|
121
|
+
}
|
|
122
|
+
if options:
|
|
123
|
+
default_options.update(options)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
response = self.client.listen.v1.media.transcribe_url(
|
|
127
|
+
url=url, **default_options
|
|
128
|
+
)
|
|
129
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
raise ApiError(body=f"Transcription failed: {e}")
|
|
133
|
+
|
|
134
|
+
# ── Text-to-Speech ─────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
def speak_text(
|
|
137
|
+
self,
|
|
138
|
+
text: str,
|
|
139
|
+
model: str = "aura-2-asteria-en",
|
|
140
|
+
encoding: str | None = None,
|
|
141
|
+
container: str | None = None,
|
|
142
|
+
sample_rate: float | None = None,
|
|
143
|
+
) -> Iterator[bytes]:
|
|
144
|
+
"""Generate speech audio from text. Returns an iterator of bytes."""
|
|
145
|
+
try:
|
|
146
|
+
kwargs: dict[str, Any] = {"text": text, "model": model}
|
|
147
|
+
if encoding:
|
|
148
|
+
kwargs["encoding"] = encoding
|
|
149
|
+
if container:
|
|
150
|
+
kwargs["container"] = container
|
|
151
|
+
if sample_rate:
|
|
152
|
+
kwargs["sample_rate"] = sample_rate
|
|
153
|
+
|
|
154
|
+
return cast(
|
|
155
|
+
"Iterator[bytes]", self.client.speak.v1.audio.generate(**kwargs)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
raise ApiError(body=f"Text-to-speech failed: {e}")
|
|
160
|
+
|
|
161
|
+
# ── Text Intelligence (Read) ───────────────────────────────────
|
|
162
|
+
|
|
163
|
+
def analyze_text(
|
|
164
|
+
self,
|
|
165
|
+
text: str,
|
|
166
|
+
sentiment: bool = False,
|
|
167
|
+
summarize: bool = False,
|
|
168
|
+
topics: bool = False,
|
|
169
|
+
intents: bool = False,
|
|
170
|
+
language: str | None = None,
|
|
171
|
+
) -> dict[str, Any]:
|
|
172
|
+
"""Analyze text for sentiment, topics, intents, summary."""
|
|
173
|
+
try:
|
|
174
|
+
from deepgram.requests.read_v1request_text import ReadV1RequestTextParams
|
|
175
|
+
|
|
176
|
+
kwargs: dict[str, Any] = {
|
|
177
|
+
"request": ReadV1RequestTextParams(text=text),
|
|
178
|
+
}
|
|
179
|
+
if sentiment:
|
|
180
|
+
kwargs["sentiment"] = True
|
|
181
|
+
if summarize:
|
|
182
|
+
kwargs["summarize"] = "v2"
|
|
183
|
+
if topics:
|
|
184
|
+
kwargs["topics"] = True
|
|
185
|
+
if intents:
|
|
186
|
+
kwargs["intents"] = True
|
|
187
|
+
kwargs["language"] = language or "en"
|
|
188
|
+
|
|
189
|
+
response = self.client.read.v1.text.analyze(**kwargs)
|
|
190
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
191
|
+
|
|
192
|
+
except Exception as e:
|
|
193
|
+
raise ApiError(body=f"Text analysis failed: {e}")
|
|
194
|
+
|
|
195
|
+
# ── Models ─────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
def list_models(self, include_outdated: bool = False) -> dict[str, Any]:
|
|
198
|
+
"""List all public models."""
|
|
199
|
+
try:
|
|
200
|
+
kwargs: dict[str, Any] = {}
|
|
201
|
+
if include_outdated:
|
|
202
|
+
kwargs["include_outdated"] = True
|
|
203
|
+
|
|
204
|
+
response = self.client.manage.v1.models.list(**kwargs)
|
|
205
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
raise ApiError(body=f"Failed to list models: {e}")
|
|
209
|
+
|
|
210
|
+
def get_model(self, model_id: str) -> dict[str, Any]:
|
|
211
|
+
"""Get a specific model by ID."""
|
|
212
|
+
try:
|
|
213
|
+
response = self.client.manage.v1.models.get(model_id)
|
|
214
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
raise ApiError(body=f"Failed to get model: {e}")
|
|
218
|
+
|
|
219
|
+
# ── Projects ───────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
def get_projects(self) -> dict[str, Any]:
|
|
222
|
+
"""Get user's projects."""
|
|
223
|
+
try:
|
|
224
|
+
response = self.client.manage.v1.projects.list()
|
|
225
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
raise ApiError(body=f"Failed to get projects: {e}")
|
|
229
|
+
|
|
230
|
+
def get_project(self, project_id: str | None = None) -> dict[str, Any]:
|
|
231
|
+
"""Get specific project."""
|
|
232
|
+
pid = self._resolve_project_id(project_id)
|
|
233
|
+
try:
|
|
234
|
+
response = self.client.manage.v1.projects.get(pid)
|
|
235
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
raise ApiError(body=f"Failed to get project: {e}")
|
|
239
|
+
|
|
240
|
+
def create_project(self, name: str, company: str | None = None) -> dict[str, Any]:
|
|
241
|
+
"""Create a new project."""
|
|
242
|
+
try:
|
|
243
|
+
project_data: dict[str, str] = {"name": name}
|
|
244
|
+
if company:
|
|
245
|
+
project_data["company"] = company
|
|
246
|
+
|
|
247
|
+
response = self.client.manage.v1.projects.update(request=project_data)
|
|
248
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
249
|
+
|
|
250
|
+
except Exception as e:
|
|
251
|
+
raise ApiError(body=f"Failed to create project: {e}")
|
|
252
|
+
|
|
253
|
+
# ── Usage ──────────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
def get_usage(
|
|
256
|
+
self,
|
|
257
|
+
project_id: str | None = None,
|
|
258
|
+
start_date: str | None = None,
|
|
259
|
+
end_date: str | None = None,
|
|
260
|
+
) -> dict[str, Any]:
|
|
261
|
+
"""Get usage statistics."""
|
|
262
|
+
pid = self._resolve_project_id(project_id)
|
|
263
|
+
try:
|
|
264
|
+
kwargs: dict[str, Any] = {}
|
|
265
|
+
if start_date:
|
|
266
|
+
kwargs["start"] = start_date
|
|
267
|
+
if end_date:
|
|
268
|
+
kwargs["end"] = end_date
|
|
269
|
+
|
|
270
|
+
response = self.client.manage.v1.projects.usage.get(pid, **kwargs)
|
|
271
|
+
result = cast("dict[str, Any]", response.model_dump())
|
|
272
|
+
result["project_id"] = pid
|
|
273
|
+
return result
|
|
274
|
+
|
|
275
|
+
except Exception as e:
|
|
276
|
+
raise ApiError(body=f"Failed to get usage: {e}")
|
|
277
|
+
|
|
278
|
+
# ── API Keys ───────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
def list_keys(
|
|
281
|
+
self,
|
|
282
|
+
project_id: str | None = None,
|
|
283
|
+
status: str | None = None,
|
|
284
|
+
) -> dict[str, Any]:
|
|
285
|
+
"""List API keys for a project."""
|
|
286
|
+
pid = self._resolve_project_id(project_id)
|
|
287
|
+
try:
|
|
288
|
+
kwargs: dict[str, Any] = {}
|
|
289
|
+
if status:
|
|
290
|
+
kwargs["status"] = status
|
|
291
|
+
|
|
292
|
+
response = self.client.manage.v1.projects.keys.list(pid, **kwargs)
|
|
293
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
raise ApiError(body=f"Failed to list keys: {e}")
|
|
297
|
+
|
|
298
|
+
def create_key(
|
|
299
|
+
self,
|
|
300
|
+
project_id: str | None = None,
|
|
301
|
+
comment: str = "",
|
|
302
|
+
scopes: list[str] | None = None,
|
|
303
|
+
expiration_date: str | None = None,
|
|
304
|
+
time_to_live: int | None = None,
|
|
305
|
+
tags: list[str] | None = None,
|
|
306
|
+
) -> dict[str, Any]:
|
|
307
|
+
"""Create a new API key."""
|
|
308
|
+
pid = self._resolve_project_id(project_id)
|
|
309
|
+
try:
|
|
310
|
+
request_data: dict[str, Any] = {
|
|
311
|
+
"comment": comment,
|
|
312
|
+
"scopes": scopes or ["member"],
|
|
313
|
+
}
|
|
314
|
+
if expiration_date:
|
|
315
|
+
request_data["expiration_date"] = expiration_date
|
|
316
|
+
if time_to_live:
|
|
317
|
+
request_data["time_to_live_in_seconds"] = time_to_live
|
|
318
|
+
if tags:
|
|
319
|
+
request_data["tags"] = tags
|
|
320
|
+
|
|
321
|
+
response = self.client.manage.v1.projects.keys.create(
|
|
322
|
+
pid, request=request_data
|
|
323
|
+
)
|
|
324
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
325
|
+
|
|
326
|
+
except Exception as e:
|
|
327
|
+
raise ApiError(body=f"Failed to create key: {e}")
|
|
328
|
+
|
|
329
|
+
def get_key(self, key_id: str, project_id: str | None = None) -> dict[str, Any]:
|
|
330
|
+
"""Get a specific API key."""
|
|
331
|
+
pid = self._resolve_project_id(project_id)
|
|
332
|
+
try:
|
|
333
|
+
response = self.client.manage.v1.projects.keys.get(pid, key_id)
|
|
334
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
335
|
+
|
|
336
|
+
except Exception as e:
|
|
337
|
+
raise ApiError(body=f"Failed to get key: {e}")
|
|
338
|
+
|
|
339
|
+
def delete_key(self, key_id: str, project_id: str | None = None) -> dict[str, Any]:
|
|
340
|
+
"""Delete an API key."""
|
|
341
|
+
pid = self._resolve_project_id(project_id)
|
|
342
|
+
try:
|
|
343
|
+
response = self.client.manage.v1.projects.keys.delete(pid, key_id)
|
|
344
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
345
|
+
|
|
346
|
+
except Exception as e:
|
|
347
|
+
raise ApiError(body=f"Failed to delete key: {e}")
|
|
348
|
+
|
|
349
|
+
# ── Requests ───────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
def list_requests(
|
|
352
|
+
self,
|
|
353
|
+
project_id: str | None = None,
|
|
354
|
+
start: datetime | None = None,
|
|
355
|
+
end: datetime | None = None,
|
|
356
|
+
limit: int | None = None,
|
|
357
|
+
page: int | None = None,
|
|
358
|
+
status: str | None = None,
|
|
359
|
+
endpoint: str | None = None,
|
|
360
|
+
method: str | None = None,
|
|
361
|
+
) -> dict[str, Any]:
|
|
362
|
+
"""List API requests for a project."""
|
|
363
|
+
pid = self._resolve_project_id(project_id)
|
|
364
|
+
try:
|
|
365
|
+
kwargs: dict[str, Any] = {}
|
|
366
|
+
if start:
|
|
367
|
+
kwargs["start"] = start
|
|
368
|
+
if end:
|
|
369
|
+
kwargs["end"] = end
|
|
370
|
+
if limit:
|
|
371
|
+
kwargs["limit"] = limit
|
|
372
|
+
if page:
|
|
373
|
+
kwargs["page"] = page
|
|
374
|
+
if status:
|
|
375
|
+
kwargs["status"] = status
|
|
376
|
+
if endpoint:
|
|
377
|
+
kwargs["endpoint"] = endpoint
|
|
378
|
+
if method:
|
|
379
|
+
kwargs["method"] = method
|
|
380
|
+
|
|
381
|
+
response = self.client.manage.v1.projects.requests.list(pid, **kwargs)
|
|
382
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
383
|
+
|
|
384
|
+
except Exception as e:
|
|
385
|
+
raise ApiError(body=f"Failed to list requests: {e}")
|
|
386
|
+
|
|
387
|
+
def get_request(
|
|
388
|
+
self, request_id: str, project_id: str | None = None
|
|
389
|
+
) -> dict[str, Any]:
|
|
390
|
+
"""Get a specific API request."""
|
|
391
|
+
pid = self._resolve_project_id(project_id)
|
|
392
|
+
try:
|
|
393
|
+
response = self.client.manage.v1.projects.requests.get(pid, request_id)
|
|
394
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
395
|
+
|
|
396
|
+
except Exception as e:
|
|
397
|
+
raise ApiError(body=f"Failed to get request: {e}")
|
|
398
|
+
|
|
399
|
+
# ── Billing ────────────────────────────────────────────────────
|
|
400
|
+
|
|
401
|
+
def get_billing_breakdown(
|
|
402
|
+
self,
|
|
403
|
+
project_id: str | None = None,
|
|
404
|
+
start: str | None = None,
|
|
405
|
+
end: str | None = None,
|
|
406
|
+
grouping: str | None = None,
|
|
407
|
+
) -> dict[str, Any]:
|
|
408
|
+
"""Get billing breakdown for a project."""
|
|
409
|
+
pid = self._resolve_project_id(project_id)
|
|
410
|
+
try:
|
|
411
|
+
kwargs: dict[str, Any] = {}
|
|
412
|
+
if start:
|
|
413
|
+
kwargs["start"] = start
|
|
414
|
+
if end:
|
|
415
|
+
kwargs["end"] = end
|
|
416
|
+
if grouping:
|
|
417
|
+
kwargs["grouping"] = grouping
|
|
418
|
+
|
|
419
|
+
response = self.client.manage.v1.projects.billing.breakdown.list(
|
|
420
|
+
pid, **kwargs
|
|
421
|
+
)
|
|
422
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
423
|
+
|
|
424
|
+
except Exception as e:
|
|
425
|
+
raise ApiError(body=f"Failed to get billing breakdown: {e}")
|
|
426
|
+
|
|
427
|
+
def get_balances(self, project_id: str | None = None) -> dict[str, Any]:
|
|
428
|
+
"""Get project balances."""
|
|
429
|
+
pid = self._resolve_project_id(project_id)
|
|
430
|
+
try:
|
|
431
|
+
response = self.client.manage.v1.projects.billing.balances.list(pid)
|
|
432
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
raise ApiError(body=f"Failed to get balances: {e}")
|
|
436
|
+
|
|
437
|
+
# ── Members ────────────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
def list_members(self, project_id: str | None = None) -> dict[str, Any]:
|
|
440
|
+
"""List project members."""
|
|
441
|
+
pid = self._resolve_project_id(project_id)
|
|
442
|
+
try:
|
|
443
|
+
response = self.client.manage.v1.projects.members.list(pid)
|
|
444
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
445
|
+
|
|
446
|
+
except Exception as e:
|
|
447
|
+
raise ApiError(body=f"Failed to list members: {e}")
|
|
448
|
+
|
|
449
|
+
def remove_member(
|
|
450
|
+
self, member_id: str, project_id: str | None = None
|
|
451
|
+
) -> dict[str, Any]:
|
|
452
|
+
"""Remove a member from the project."""
|
|
453
|
+
pid = self._resolve_project_id(project_id)
|
|
454
|
+
try:
|
|
455
|
+
response = self.client.manage.v1.projects.members.delete(pid, member_id)
|
|
456
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
457
|
+
|
|
458
|
+
except Exception as e:
|
|
459
|
+
raise ApiError(body=f"Failed to remove member: {e}")
|
|
460
|
+
|
|
461
|
+
def list_member_scopes(
|
|
462
|
+
self, member_id: str, project_id: str | None = None
|
|
463
|
+
) -> dict[str, Any]:
|
|
464
|
+
"""List scopes for a project member."""
|
|
465
|
+
pid = self._resolve_project_id(project_id)
|
|
466
|
+
try:
|
|
467
|
+
response = self.client.manage.v1.projects.members.scopes.list(
|
|
468
|
+
pid, member_id
|
|
469
|
+
)
|
|
470
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
471
|
+
|
|
472
|
+
except Exception as e:
|
|
473
|
+
raise ApiError(body=f"Failed to list member scopes: {e}")
|
|
474
|
+
|
|
475
|
+
def update_member_scopes(
|
|
476
|
+
self, member_id: str, scope: str, project_id: str | None = None
|
|
477
|
+
) -> dict[str, Any]:
|
|
478
|
+
"""Update scopes for a project member."""
|
|
479
|
+
pid = self._resolve_project_id(project_id)
|
|
480
|
+
try:
|
|
481
|
+
response = self.client.manage.v1.projects.members.scopes.update(
|
|
482
|
+
pid, member_id, scope=scope
|
|
483
|
+
)
|
|
484
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
485
|
+
|
|
486
|
+
except Exception as e:
|
|
487
|
+
raise ApiError(body=f"Failed to update member scopes: {e}")
|
|
488
|
+
|
|
489
|
+
# ── Invites ────────────────────────────────────────────────────
|
|
490
|
+
|
|
491
|
+
def list_invites(self, project_id: str | None = None) -> dict[str, Any]:
|
|
492
|
+
"""List project invites."""
|
|
493
|
+
pid = self._resolve_project_id(project_id)
|
|
494
|
+
try:
|
|
495
|
+
response = self.client.manage.v1.projects.members.invites.list(pid)
|
|
496
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
497
|
+
|
|
498
|
+
except Exception as e:
|
|
499
|
+
raise ApiError(body=f"Failed to list invites: {e}")
|
|
500
|
+
|
|
501
|
+
def create_invite(
|
|
502
|
+
self, email: str, scope: str, project_id: str | None = None
|
|
503
|
+
) -> dict[str, Any]:
|
|
504
|
+
"""Invite a member to the project."""
|
|
505
|
+
pid = self._resolve_project_id(project_id)
|
|
506
|
+
try:
|
|
507
|
+
response = self.client.manage.v1.projects.members.invites.create(
|
|
508
|
+
pid, email=email, scope=scope
|
|
509
|
+
)
|
|
510
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
511
|
+
|
|
512
|
+
except Exception as e:
|
|
513
|
+
raise ApiError(body=f"Failed to create invite: {e}")
|
|
514
|
+
|
|
515
|
+
def delete_invite(
|
|
516
|
+
self, email: str, project_id: str | None = None
|
|
517
|
+
) -> dict[str, Any]:
|
|
518
|
+
"""Delete a project invite."""
|
|
519
|
+
pid = self._resolve_project_id(project_id)
|
|
520
|
+
try:
|
|
521
|
+
response = self.client.manage.v1.projects.members.invites.delete(pid, email)
|
|
522
|
+
return cast("dict[str, Any]", response.model_dump())
|
|
523
|
+
|
|
524
|
+
except Exception as e:
|
|
525
|
+
raise ApiError(body=f"Failed to delete invite: {e}")
|
|
526
|
+
|
|
527
|
+
# ── Utilities ──────────────────────────────────────────────────
|
|
528
|
+
|
|
529
|
+
def validate_api_key(self, api_key: str | None = None) -> bool:
|
|
530
|
+
"""Validate API key by making a simple API call."""
|
|
531
|
+
project_id = self.auth_manager.get_project_id()
|
|
532
|
+
success, _, _ = self.auth_manager.verify_credentials(
|
|
533
|
+
api_key=api_key, project_id=project_id
|
|
534
|
+
)
|
|
535
|
+
return success
|
|
536
|
+
|
|
537
|
+
def test_connection(self) -> bool:
|
|
538
|
+
"""Test connection to Deepgram API."""
|
|
539
|
+
success, message, _ = self.auth_manager.verify_credentials()
|
|
540
|
+
if not success:
|
|
541
|
+
console.print(f"[red]Connection test failed:[/red] {message}")
|
|
542
|
+
return success
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Output formatting utilities for deepctl."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
4
6
|
from io import StringIO
|
|
5
7
|
from typing import Any
|
|
6
8
|
|
|
@@ -18,16 +20,42 @@ from rich.progress import (
|
|
|
18
20
|
from rich.syntax import Syntax
|
|
19
21
|
from rich.table import Table
|
|
20
22
|
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
# ── Agentic / non-TTY detection ──────────────────────────────────
|
|
24
|
+
|
|
25
|
+
_AGENTIC_ENV_VARS = (
|
|
26
|
+
"CLAUDE_CODE",
|
|
27
|
+
"CODEX_CLI",
|
|
28
|
+
"GEMINI_CLI",
|
|
29
|
+
"AIDER",
|
|
30
|
+
"CONTINUE_GLOBAL_DIR",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_agentic() -> bool:
|
|
35
|
+
"""Detect if running inside an AI coding tool or non-interactive pipe.
|
|
36
|
+
|
|
37
|
+
Returns True when stdout is not a terminal OR a known agentic
|
|
38
|
+
environment variable is set. This lets commands produce
|
|
39
|
+
machine-friendly output automatically.
|
|
40
|
+
"""
|
|
41
|
+
if not sys.stdout.isatty():
|
|
42
|
+
return True
|
|
43
|
+
return any(os.environ.get(v) for v in _AGENTIC_ENV_VARS)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_agentic = is_agentic()
|
|
47
|
+
|
|
48
|
+
# Global console instance — plain when agentic, rich when interactive
|
|
49
|
+
console = Console(no_color=_agentic, highlight=not _agentic)
|
|
50
|
+
stderr_console = Console(stderr=True, no_color=_agentic, highlight=not _agentic)
|
|
24
51
|
|
|
25
52
|
# Global output configuration
|
|
26
|
-
_output_config = {
|
|
53
|
+
_output_config: dict[str, Any] = {
|
|
27
54
|
"format": "default",
|
|
28
55
|
"quiet": False,
|
|
29
56
|
"verbose": False,
|
|
30
|
-
"color":
|
|
57
|
+
"color": not _agentic,
|
|
58
|
+
"agentic": _agentic,
|
|
31
59
|
}
|
|
32
60
|
|
|
33
61
|
|
|
@@ -36,11 +64,14 @@ def setup_output(
|
|
|
36
64
|
) -> None:
|
|
37
65
|
"""Setup global output configuration.
|
|
38
66
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
verbose: Enable verbose output
|
|
67
|
+
When running in an agentic/non-TTY context and no explicit format is
|
|
68
|
+
requested (format_type == "default"), auto-switch to JSON for
|
|
69
|
+
machine-friendly output.
|
|
43
70
|
"""
|
|
71
|
+
# Auto-default to JSON in agentic mode when user didn't pick a format
|
|
72
|
+
if format_type == "default" and _output_config["agentic"]:
|
|
73
|
+
format_type = "json"
|
|
74
|
+
|
|
44
75
|
_output_config.update({"format": format_type, "quiet": quiet, "verbose": verbose})
|
|
45
76
|
|
|
46
77
|
# Update console settings
|
|
@@ -273,42 +304,38 @@ def print_output(data: Any, format_type: str | None = None) -> None:
|
|
|
273
304
|
|
|
274
305
|
|
|
275
306
|
def print_success(message: str) -> None:
|
|
276
|
-
"""Print success message.
|
|
277
|
-
|
|
278
|
-
Args:
|
|
279
|
-
message: Success message
|
|
280
|
-
"""
|
|
307
|
+
"""Print success message."""
|
|
281
308
|
if not _output_config["quiet"]:
|
|
282
|
-
|
|
309
|
+
if _output_config["agentic"]:
|
|
310
|
+
stderr_console.print(f"OK: {message}")
|
|
311
|
+
else:
|
|
312
|
+
console.print(f"[green]✓[/green] {message}")
|
|
283
313
|
|
|
284
314
|
|
|
285
315
|
def print_error(message: str) -> None:
|
|
286
|
-
"""Print error message.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
stderr_console.print(f"[red]✗[/red] {message}")
|
|
316
|
+
"""Print error message."""
|
|
317
|
+
if _output_config["agentic"]:
|
|
318
|
+
stderr_console.print(f"ERROR: {message}")
|
|
319
|
+
else:
|
|
320
|
+
stderr_console.print(f"[red]✗[/red] {message}")
|
|
292
321
|
|
|
293
322
|
|
|
294
323
|
def print_warning(message: str) -> None:
|
|
295
|
-
"""Print warning message.
|
|
296
|
-
|
|
297
|
-
Args:
|
|
298
|
-
message: Warning message
|
|
299
|
-
"""
|
|
324
|
+
"""Print warning message."""
|
|
300
325
|
if not _output_config["quiet"]:
|
|
301
|
-
|
|
326
|
+
if _output_config["agentic"]:
|
|
327
|
+
stderr_console.print(f"WARN: {message}")
|
|
328
|
+
else:
|
|
329
|
+
console.print(f"[yellow]⚠[/yellow] {message}")
|
|
302
330
|
|
|
303
331
|
|
|
304
332
|
def print_info(message: str) -> None:
|
|
305
|
-
"""Print info message.
|
|
306
|
-
|
|
307
|
-
Args:
|
|
308
|
-
message: Info message
|
|
309
|
-
"""
|
|
333
|
+
"""Print info message."""
|
|
310
334
|
if not _output_config["quiet"]:
|
|
311
|
-
|
|
335
|
+
if _output_config["agentic"]:
|
|
336
|
+
stderr_console.print(f"INFO: {message}")
|
|
337
|
+
else:
|
|
338
|
+
console.print(f"[blue]ℹ[/blue] {message}")
|
|
312
339
|
|
|
313
340
|
|
|
314
341
|
def print_debug(message: str) -> None:
|
|
@@ -43,6 +43,9 @@ class CommandMetadata:
|
|
|
43
43
|
|
|
44
44
|
_SKILLS_DIR = Path.home() / ".deepctl" / "skills"
|
|
45
45
|
_STATE_FILE = _SKILLS_DIR / "skills.json"
|
|
46
|
+
_REPO_CACHE_DIR = _SKILLS_DIR / "repo_cache"
|
|
47
|
+
_SKILLS_REPO = "deepgram/skills"
|
|
48
|
+
_SKILLS_BRANCH = "main"
|
|
46
49
|
|
|
47
50
|
|
|
48
51
|
def get_skills_state() -> dict[str, Any]:
|
|
@@ -60,6 +63,56 @@ def save_skills_state(state: dict[str, Any]) -> None:
|
|
|
60
63
|
_STATE_FILE.write_text(json.dumps(state, indent=2))
|
|
61
64
|
|
|
62
65
|
|
|
66
|
+
def fetch_repo_skills(force: bool = False) -> dict[str, str]:
|
|
67
|
+
"""Download skill markdown files from the deepgram/skills GitHub repo.
|
|
68
|
+
|
|
69
|
+
Returns a mapping of skill name -> markdown content.
|
|
70
|
+
Caches locally to avoid repeated network requests.
|
|
71
|
+
"""
|
|
72
|
+
import urllib.request
|
|
73
|
+
|
|
74
|
+
cache_marker = _REPO_CACHE_DIR / ".fetched"
|
|
75
|
+
if not force and cache_marker.exists():
|
|
76
|
+
# Return cached content
|
|
77
|
+
return _read_cached_skills()
|
|
78
|
+
|
|
79
|
+
base = f"https://raw.githubusercontent.com/{_SKILLS_REPO}/{_SKILLS_BRANCH}"
|
|
80
|
+
skill_names = ["api", "docs", "starters", "mcp"]
|
|
81
|
+
skills: dict[str, str] = {}
|
|
82
|
+
|
|
83
|
+
_REPO_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
|
|
85
|
+
for name in skill_names:
|
|
86
|
+
url = f"{base}/skills/{name}/SKILL.md"
|
|
87
|
+
try:
|
|
88
|
+
with urllib.request.urlopen(url, timeout=10) as resp:
|
|
89
|
+
content = resp.read().decode("utf-8")
|
|
90
|
+
skills[name] = content
|
|
91
|
+
( # Cache to disk
|
|
92
|
+
_REPO_CACHE_DIR / f"{name}.md"
|
|
93
|
+
).write_text(content)
|
|
94
|
+
except Exception:
|
|
95
|
+
# Use cached version if available
|
|
96
|
+
cached = _REPO_CACHE_DIR / f"{name}.md"
|
|
97
|
+
if cached.exists():
|
|
98
|
+
skills[name] = cached.read_text()
|
|
99
|
+
|
|
100
|
+
if skills:
|
|
101
|
+
cache_marker.write_text("1")
|
|
102
|
+
|
|
103
|
+
return skills
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _read_cached_skills() -> dict[str, str]:
|
|
107
|
+
"""Read previously cached repo skills."""
|
|
108
|
+
skills: dict[str, str] = {}
|
|
109
|
+
if not _REPO_CACHE_DIR.exists():
|
|
110
|
+
return skills
|
|
111
|
+
for md_file in _REPO_CACHE_DIR.glob("*.md"):
|
|
112
|
+
skills[md_file.stem] = md_file.read_text()
|
|
113
|
+
return skills
|
|
114
|
+
|
|
115
|
+
|
|
63
116
|
def _commands_hash(commands: list[CommandMetadata]) -> str:
|
|
64
117
|
"""Compute a deterministic hash of the command set."""
|
|
65
118
|
blob = json.dumps(
|
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
"""Deepgram SDK wrapper for deepctl with authentication integration."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Any, cast
|
|
5
|
-
|
|
6
|
-
from deepgram import DeepgramClient as DGClient
|
|
7
|
-
from deepgram import DeepgramClientEnvironment
|
|
8
|
-
from deepgram.core.api_error import ApiError
|
|
9
|
-
from rich.console import Console
|
|
10
|
-
|
|
11
|
-
from .auth import AuthManager
|
|
12
|
-
from .config import Config
|
|
13
|
-
|
|
14
|
-
console = Console()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class DeepgramClient:
|
|
18
|
-
"""Wrapper around Deepgram SDK with authentication integration."""
|
|
19
|
-
|
|
20
|
-
def __init__(self, config: Config, auth_manager: AuthManager):
|
|
21
|
-
"""Initialize Deepgram client.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
config: Configuration manager
|
|
25
|
-
auth_manager: Authentication manager
|
|
26
|
-
"""
|
|
27
|
-
self.config = config
|
|
28
|
-
self.auth_manager = auth_manager
|
|
29
|
-
self._client: DGClient | None = None
|
|
30
|
-
self._project_id: str | None = None
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def client(self) -> DGClient:
|
|
34
|
-
"""Get authenticated Deepgram client."""
|
|
35
|
-
if self._client is None:
|
|
36
|
-
self._client = self._create_client()
|
|
37
|
-
return self._client
|
|
38
|
-
|
|
39
|
-
def _create_client(self) -> DGClient:
|
|
40
|
-
"""Create authenticated Deepgram client."""
|
|
41
|
-
# Ensure user is authenticated and credentials are valid
|
|
42
|
-
self.auth_manager.guard()
|
|
43
|
-
|
|
44
|
-
# Get API key and project ID
|
|
45
|
-
api_key = self.auth_manager.get_api_key()
|
|
46
|
-
project_id = self.auth_manager.get_project_id()
|
|
47
|
-
|
|
48
|
-
if not api_key:
|
|
49
|
-
raise ApiError(body="No API key available")
|
|
50
|
-
|
|
51
|
-
# Create client with configuration
|
|
52
|
-
current_profile = self.config.get_profile()
|
|
53
|
-
|
|
54
|
-
try:
|
|
55
|
-
kwargs: dict[str, Any] = {"api_key": api_key}
|
|
56
|
-
|
|
57
|
-
# Use a custom environment when a non-default base URL is
|
|
58
|
-
# configured.
|
|
59
|
-
if (
|
|
60
|
-
current_profile.base_url
|
|
61
|
-
and current_profile.base_url != "https://api.deepgram.com"
|
|
62
|
-
):
|
|
63
|
-
kwargs["environment"] = DeepgramClientEnvironment(
|
|
64
|
-
base=current_profile.base_url,
|
|
65
|
-
production=current_profile.base_url,
|
|
66
|
-
agent=current_profile.base_url,
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
client = DGClient(**kwargs)
|
|
70
|
-
|
|
71
|
-
# Store project ID for later use
|
|
72
|
-
self._project_id = project_id
|
|
73
|
-
|
|
74
|
-
return client
|
|
75
|
-
|
|
76
|
-
except Exception as e:
|
|
77
|
-
console.print(f"[red]Error creating Deepgram client:[/red] {e}")
|
|
78
|
-
raise ApiError(body=f"Failed to create client: {e}")
|
|
79
|
-
|
|
80
|
-
def transcribe_file(
|
|
81
|
-
self,
|
|
82
|
-
file_path: str | Path,
|
|
83
|
-
options: dict[str, Any] | None = None,
|
|
84
|
-
) -> dict[str, Any]:
|
|
85
|
-
"""Transcribe an audio file.
|
|
86
|
-
|
|
87
|
-
Args:
|
|
88
|
-
file_path: Path to audio file
|
|
89
|
-
options: Transcription options
|
|
90
|
-
|
|
91
|
-
Returns:
|
|
92
|
-
Transcription results
|
|
93
|
-
"""
|
|
94
|
-
file_path = Path(file_path)
|
|
95
|
-
|
|
96
|
-
if not file_path.exists():
|
|
97
|
-
raise FileNotFoundError(f"Audio file not found: {file_path}")
|
|
98
|
-
|
|
99
|
-
# Default options
|
|
100
|
-
default_options: dict[str, Any] = {
|
|
101
|
-
"model": "nova-3",
|
|
102
|
-
"smart_format": True,
|
|
103
|
-
"language": "en-US",
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if options:
|
|
107
|
-
default_options.update(options)
|
|
108
|
-
|
|
109
|
-
try:
|
|
110
|
-
with open(file_path, "rb") as audio_file:
|
|
111
|
-
audio_data = audio_file.read()
|
|
112
|
-
|
|
113
|
-
response = self.client.listen.v1.media.transcribe_file(
|
|
114
|
-
request=audio_data, **default_options
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
return cast("dict[str, Any]", response.model_dump())
|
|
118
|
-
|
|
119
|
-
except Exception as e:
|
|
120
|
-
console.print(f"[red]Error transcribing file:[/red] {e}")
|
|
121
|
-
raise ApiError(body=f"Transcription failed: {e}")
|
|
122
|
-
|
|
123
|
-
def transcribe_url(
|
|
124
|
-
self, url: str, options: dict[str, Any] | None = None
|
|
125
|
-
) -> dict[str, Any]:
|
|
126
|
-
"""Transcribe audio from URL.
|
|
127
|
-
|
|
128
|
-
Args:
|
|
129
|
-
url: URL to audio file
|
|
130
|
-
options: Transcription options
|
|
131
|
-
|
|
132
|
-
Returns:
|
|
133
|
-
Transcription results
|
|
134
|
-
"""
|
|
135
|
-
# Default options
|
|
136
|
-
default_options: dict[str, Any] = {
|
|
137
|
-
"model": "nova-3",
|
|
138
|
-
"smart_format": True,
|
|
139
|
-
"language": "en-US",
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if options:
|
|
143
|
-
default_options.update(options)
|
|
144
|
-
|
|
145
|
-
try:
|
|
146
|
-
response = self.client.listen.v1.media.transcribe_url(
|
|
147
|
-
url=url, **default_options
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
return cast("dict[str, Any]", response.model_dump())
|
|
151
|
-
|
|
152
|
-
except Exception as e:
|
|
153
|
-
console.print(f"[red]Error transcribing URL:[/red] {e}")
|
|
154
|
-
raise ApiError(body=f"Transcription failed: {e}")
|
|
155
|
-
|
|
156
|
-
def get_projects(self) -> dict[str, Any]:
|
|
157
|
-
"""Get user's projects.
|
|
158
|
-
|
|
159
|
-
Returns:
|
|
160
|
-
Projects data
|
|
161
|
-
"""
|
|
162
|
-
try:
|
|
163
|
-
response = self.client.manage.v("1").get_projects()
|
|
164
|
-
return cast("dict[str, Any]", response.model_dump())
|
|
165
|
-
|
|
166
|
-
except Exception as e:
|
|
167
|
-
console.print(f"[red]Error getting projects:[/red] {e}")
|
|
168
|
-
raise ApiError(body=f"Failed to get projects: {e}")
|
|
169
|
-
|
|
170
|
-
def get_project(self, project_id: str | None = None) -> dict[str, Any]:
|
|
171
|
-
"""Get specific project.
|
|
172
|
-
|
|
173
|
-
Args:
|
|
174
|
-
project_id: Project ID (uses configured project if not
|
|
175
|
-
provided)
|
|
176
|
-
|
|
177
|
-
Returns:
|
|
178
|
-
Project data
|
|
179
|
-
"""
|
|
180
|
-
if not project_id:
|
|
181
|
-
# Ensure client is initialized which sets _project_id
|
|
182
|
-
_ = self.client
|
|
183
|
-
project_id = self._project_id or self.auth_manager.get_project_id()
|
|
184
|
-
|
|
185
|
-
if not project_id:
|
|
186
|
-
raise ApiError(body="No project ID available")
|
|
187
|
-
|
|
188
|
-
try:
|
|
189
|
-
response = self.client.manage.v("1").get_project(project_id)
|
|
190
|
-
return cast("dict[str, Any]", response.model_dump())
|
|
191
|
-
|
|
192
|
-
except Exception as e:
|
|
193
|
-
console.print(f"[red]Error getting project:[/red] {e}")
|
|
194
|
-
raise ApiError(body=f"Failed to get project: {e}")
|
|
195
|
-
|
|
196
|
-
def create_project(self, name: str, company: str | None = None) -> dict[str, Any]:
|
|
197
|
-
"""Create a new project.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
name: Project name
|
|
201
|
-
company: Company name
|
|
202
|
-
|
|
203
|
-
Returns:
|
|
204
|
-
Created project data
|
|
205
|
-
"""
|
|
206
|
-
try:
|
|
207
|
-
project_data = {"name": name}
|
|
208
|
-
if company:
|
|
209
|
-
project_data["company"] = company
|
|
210
|
-
|
|
211
|
-
response = self.client.manage.v("1").create_project(project_data)
|
|
212
|
-
return cast("dict[str, Any]", response.model_dump())
|
|
213
|
-
|
|
214
|
-
except Exception as e:
|
|
215
|
-
console.print(f"[red]Error creating project:[/red] {e}")
|
|
216
|
-
raise ApiError(body=f"Failed to create project: {e}")
|
|
217
|
-
|
|
218
|
-
def get_usage(
|
|
219
|
-
self,
|
|
220
|
-
project_id: str | None = None,
|
|
221
|
-
start_date: str | None = None,
|
|
222
|
-
end_date: str | None = None,
|
|
223
|
-
) -> dict[str, Any]:
|
|
224
|
-
"""Get usage statistics.
|
|
225
|
-
|
|
226
|
-
Args:
|
|
227
|
-
project_id: Project ID (uses configured project if not
|
|
228
|
-
provided)
|
|
229
|
-
start_date: Start date (ISO format)
|
|
230
|
-
end_date: End date (ISO format)
|
|
231
|
-
|
|
232
|
-
Returns:
|
|
233
|
-
Usage data
|
|
234
|
-
"""
|
|
235
|
-
if not project_id:
|
|
236
|
-
_ = self.client
|
|
237
|
-
project_id = self._project_id or self.auth_manager.get_project_id()
|
|
238
|
-
|
|
239
|
-
if not project_id:
|
|
240
|
-
raise ApiError(body="No project ID available")
|
|
241
|
-
|
|
242
|
-
try:
|
|
243
|
-
params: dict[str, str] = {}
|
|
244
|
-
if start_date:
|
|
245
|
-
params["start"] = start_date
|
|
246
|
-
if end_date:
|
|
247
|
-
params["end"] = end_date
|
|
248
|
-
|
|
249
|
-
response = self.client.manage.v("1").get_usage_summary(project_id, params)
|
|
250
|
-
result = cast("dict[str, Any]", response.model_dump())
|
|
251
|
-
result["project_id"] = project_id
|
|
252
|
-
return result
|
|
253
|
-
|
|
254
|
-
except Exception as e:
|
|
255
|
-
console.print(f"[red]Error getting usage:[/red] {e}")
|
|
256
|
-
raise ApiError(body=f"Failed to get usage: {e}")
|
|
257
|
-
|
|
258
|
-
def get_models(self, project_id: str | None = None) -> dict[str, Any]:
|
|
259
|
-
"""Get available models.
|
|
260
|
-
|
|
261
|
-
Args:
|
|
262
|
-
project_id: Project ID (uses configured project if not
|
|
263
|
-
provided)
|
|
264
|
-
|
|
265
|
-
Returns:
|
|
266
|
-
Models data
|
|
267
|
-
"""
|
|
268
|
-
if not project_id:
|
|
269
|
-
_ = self.client
|
|
270
|
-
project_id = self._project_id or self.auth_manager.get_project_id()
|
|
271
|
-
|
|
272
|
-
try:
|
|
273
|
-
response = self.client.manage.v("1").get_project(project_id)
|
|
274
|
-
return cast("dict[str, Any]", response.model_dump())
|
|
275
|
-
|
|
276
|
-
except Exception as e:
|
|
277
|
-
console.print(f"[red]Error getting models:[/red] {e}")
|
|
278
|
-
raise ApiError(body=f"Failed to get models: {e}")
|
|
279
|
-
|
|
280
|
-
def validate_api_key(self, api_key: str | None = None) -> bool:
|
|
281
|
-
"""Validate API key by making a simple API call.
|
|
282
|
-
|
|
283
|
-
Args:
|
|
284
|
-
api_key: API key to validate (uses configured key if not
|
|
285
|
-
provided)
|
|
286
|
-
|
|
287
|
-
Returns:
|
|
288
|
-
True if valid, False otherwise
|
|
289
|
-
"""
|
|
290
|
-
project_id = self.auth_manager.get_project_id()
|
|
291
|
-
success, _, _ = self.auth_manager.verify_credentials(
|
|
292
|
-
api_key=api_key, project_id=project_id
|
|
293
|
-
)
|
|
294
|
-
return success
|
|
295
|
-
|
|
296
|
-
def test_connection(self) -> bool:
|
|
297
|
-
"""Test connection to Deepgram API.
|
|
298
|
-
|
|
299
|
-
Returns:
|
|
300
|
-
True if connection successful, False otherwise
|
|
301
|
-
"""
|
|
302
|
-
success, message, _ = self.auth_manager.verify_credentials()
|
|
303
|
-
if not success:
|
|
304
|
-
console.print(f"[red]Connection test failed:[/red] {message}")
|
|
305
|
-
return success
|
|
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
|