deepctl-cmd-init 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.
- deepctl_cmd_init/__init__.py +6 -0
- deepctl_cmd_init/command.py +321 -0
- deepctl_cmd_init/lifecycle.py +104 -0
- deepctl_cmd_init/models.py +79 -0
- deepctl_cmd_init/templates_api.py +80 -0
- deepctl_cmd_init-0.0.2.dist-info/METADATA +65 -0
- deepctl_cmd_init-0.0.2.dist-info/RECORD +10 -0
- deepctl_cmd_init-0.0.2.dist-info/WHEEL +5 -0
- deepctl_cmd_init-0.0.2.dist-info/entry_points.txt +2 -0
- deepctl_cmd_init-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""Init command for deepctl — scaffold Deepgram starter apps."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from deepctl_core import (
|
|
10
|
+
AuthManager,
|
|
11
|
+
BaseCommand,
|
|
12
|
+
Config,
|
|
13
|
+
DeepgramClient,
|
|
14
|
+
)
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
|
|
18
|
+
from . import lifecycle, templates_api
|
|
19
|
+
from .models import InitResult, TemplateDetail, TemplateInfo
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InitCommand(BaseCommand):
|
|
25
|
+
"""Scaffold a Deepgram starter app from the templates gallery."""
|
|
26
|
+
|
|
27
|
+
name = "init"
|
|
28
|
+
help = (
|
|
29
|
+
"Browse, clone, and set up Deepgram starter apps. "
|
|
30
|
+
"Fetches templates from the Deepgram templates gallery and "
|
|
31
|
+
"optionally runs install/start lifecycle steps."
|
|
32
|
+
)
|
|
33
|
+
short_help = "Scaffold a Deepgram starter app"
|
|
34
|
+
|
|
35
|
+
requires_auth = False
|
|
36
|
+
requires_project = False
|
|
37
|
+
ci_friendly = True
|
|
38
|
+
|
|
39
|
+
examples = [
|
|
40
|
+
"dg init",
|
|
41
|
+
"dg init --search python",
|
|
42
|
+
"dg init node-transcription",
|
|
43
|
+
"dg init node-transcription --dir ./my-app",
|
|
44
|
+
"dg init node-transcription --install --start",
|
|
45
|
+
"dg init --list",
|
|
46
|
+
"dg init --list --search flask",
|
|
47
|
+
]
|
|
48
|
+
agent_help = (
|
|
49
|
+
"Scaffold a Deepgram starter app from the templates gallery. "
|
|
50
|
+
"Can list available templates, search/filter them, or clone a "
|
|
51
|
+
"specific template by name. After cloning, optionally runs "
|
|
52
|
+
"install and start lifecycle steps defined in deepgram.toml. "
|
|
53
|
+
"If authenticated, injects DEEPGRAM_API_KEY into .env automatically."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def get_arguments(self) -> list[dict[str, Any]]:
|
|
57
|
+
"""Get command arguments and options."""
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
"name": "template",
|
|
61
|
+
"help": "Template name/slug to clone directly",
|
|
62
|
+
"type": str,
|
|
63
|
+
"required": False,
|
|
64
|
+
"default": None,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"names": ["--dir", "-d"],
|
|
68
|
+
"help": "Target directory (default: ./{template-name})",
|
|
69
|
+
"type": str,
|
|
70
|
+
"is_option": True,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"names": ["--search", "-s"],
|
|
74
|
+
"help": "Filter templates by search term",
|
|
75
|
+
"type": str,
|
|
76
|
+
"is_option": True,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"names": ["--list", "-l"],
|
|
80
|
+
"help": "List matching templates and exit",
|
|
81
|
+
"is_flag": True,
|
|
82
|
+
"is_option": True,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"names": ["--install"],
|
|
86
|
+
"help": "Run install step without prompting",
|
|
87
|
+
"is_flag": True,
|
|
88
|
+
"is_option": True,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"names": ["--start"],
|
|
92
|
+
"help": "Run start step without prompting",
|
|
93
|
+
"is_flag": True,
|
|
94
|
+
"is_option": True,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"names": ["--no-install"],
|
|
98
|
+
"help": "Skip install step without prompting",
|
|
99
|
+
"is_flag": True,
|
|
100
|
+
"is_option": True,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"names": ["--no-start"],
|
|
104
|
+
"help": "Skip start step without prompting",
|
|
105
|
+
"is_flag": True,
|
|
106
|
+
"is_option": True,
|
|
107
|
+
},
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
def handle(
|
|
111
|
+
self,
|
|
112
|
+
config: Config,
|
|
113
|
+
auth_manager: AuthManager,
|
|
114
|
+
client: DeepgramClient,
|
|
115
|
+
**kwargs: Any,
|
|
116
|
+
) -> Any:
|
|
117
|
+
"""Handle the init command."""
|
|
118
|
+
template_slug: str | None = kwargs.get("template")
|
|
119
|
+
target_dir: str | None = kwargs.get("dir")
|
|
120
|
+
search: str | None = kwargs.get("search")
|
|
121
|
+
list_mode: bool = kwargs.get("list", False)
|
|
122
|
+
do_install: bool = kwargs.get("install", False)
|
|
123
|
+
do_start: bool = kwargs.get("start", False)
|
|
124
|
+
no_install: bool = kwargs.get("no_install", False)
|
|
125
|
+
no_start: bool = kwargs.get("no_start", False)
|
|
126
|
+
|
|
127
|
+
# --- List mode ---
|
|
128
|
+
if list_mode:
|
|
129
|
+
return self._handle_list(search)
|
|
130
|
+
|
|
131
|
+
# --- Direct template selection ---
|
|
132
|
+
if template_slug:
|
|
133
|
+
detail = self._fetch_template(template_slug)
|
|
134
|
+
if not detail:
|
|
135
|
+
return InitResult(
|
|
136
|
+
status="error", message=f"Template not found: {template_slug}"
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
# --- Interactive picker ---
|
|
140
|
+
detail = self._interactive_pick(search)
|
|
141
|
+
if not detail:
|
|
142
|
+
return InitResult(status="cancelled", message="No template selected")
|
|
143
|
+
|
|
144
|
+
# Resolve target directory
|
|
145
|
+
dest = Path(target_dir) if target_dir else Path(f"./{detail.name}")
|
|
146
|
+
dest = dest.resolve()
|
|
147
|
+
|
|
148
|
+
# Confirm selection
|
|
149
|
+
self._print_template_summary(detail)
|
|
150
|
+
console.print(f"[dim]Directory:[/dim] {dest}")
|
|
151
|
+
console.print()
|
|
152
|
+
|
|
153
|
+
if not self.confirm("Clone this template?", default=True):
|
|
154
|
+
return InitResult(status="cancelled", message="Clone cancelled")
|
|
155
|
+
|
|
156
|
+
# Clone
|
|
157
|
+
github_url = detail.links.get("github")
|
|
158
|
+
if not github_url:
|
|
159
|
+
return InitResult(status="error", message="Template has no GitHub URL")
|
|
160
|
+
|
|
161
|
+
console.print()
|
|
162
|
+
with console.status("[blue]Cloning template...[/blue]"):
|
|
163
|
+
lifecycle.clone_template(github_url, dest)
|
|
164
|
+
console.print(f"[green]Cloned to {dest}[/green]")
|
|
165
|
+
|
|
166
|
+
# Inject API key if authenticated
|
|
167
|
+
try:
|
|
168
|
+
api_key = auth_manager.get_api_key()
|
|
169
|
+
if api_key:
|
|
170
|
+
lifecycle.inject_env(dest, api_key)
|
|
171
|
+
except Exception:
|
|
172
|
+
pass # Auth is optional
|
|
173
|
+
|
|
174
|
+
installed = False
|
|
175
|
+
started = False
|
|
176
|
+
|
|
177
|
+
# Run lifecycle steps if config exists
|
|
178
|
+
if detail.config:
|
|
179
|
+
# Install step
|
|
180
|
+
if detail.config.install:
|
|
181
|
+
should_install = do_install or (
|
|
182
|
+
not no_install and self.confirm("Run install step?", default=True)
|
|
183
|
+
)
|
|
184
|
+
if should_install:
|
|
185
|
+
console.print()
|
|
186
|
+
installed = lifecycle.run_lifecycle_step(
|
|
187
|
+
detail.config.install, dest
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Start step
|
|
191
|
+
if detail.config.start:
|
|
192
|
+
should_start = do_start or (
|
|
193
|
+
not no_start and self.confirm("Run start step?", default=True)
|
|
194
|
+
)
|
|
195
|
+
if should_start:
|
|
196
|
+
console.print()
|
|
197
|
+
started = lifecycle.run_lifecycle_step(detail.config.start, dest)
|
|
198
|
+
|
|
199
|
+
# Summary
|
|
200
|
+
console.print()
|
|
201
|
+
console.print(
|
|
202
|
+
f"[green bold]Done![/green bold] Your app is ready at [cyan]{dest}[/cyan]"
|
|
203
|
+
)
|
|
204
|
+
if not installed and detail.config and detail.config.install:
|
|
205
|
+
console.print(
|
|
206
|
+
f"[dim]To install: cd {dest.name} && run the install command[/dim]"
|
|
207
|
+
)
|
|
208
|
+
if not started and detail.config and detail.config.start:
|
|
209
|
+
console.print(
|
|
210
|
+
f"[dim]To start: cd {dest.name} && run the start command[/dim]"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return InitResult(
|
|
214
|
+
status="success",
|
|
215
|
+
message=f"Cloned {detail.name}",
|
|
216
|
+
template=detail.name,
|
|
217
|
+
directory=str(dest),
|
|
218
|
+
installed=installed,
|
|
219
|
+
started=started,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def output_result(self, result: Any, config: Config) -> None:
|
|
223
|
+
"""Only emit structured output when an explicit --output format is set."""
|
|
224
|
+
from deepctl_core.output import get_output_format
|
|
225
|
+
|
|
226
|
+
output_format = get_output_format()
|
|
227
|
+
|
|
228
|
+
# Default format: the Rich table / console messages are already printed,
|
|
229
|
+
# so skip the automatic JSON dump.
|
|
230
|
+
if output_format in ("default",):
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
# Explicit --output json/yaml/table/csv: delegate to base
|
|
234
|
+
super().output_result(result, config)
|
|
235
|
+
|
|
236
|
+
def _handle_list(self, search: str | None) -> list[dict[str, Any]]:
|
|
237
|
+
"""List templates and return structured data."""
|
|
238
|
+
with console.status("[blue]Fetching templates...[/blue]"):
|
|
239
|
+
response = templates_api.list_templates(search=search)
|
|
240
|
+
|
|
241
|
+
templates = response.items
|
|
242
|
+
if not templates:
|
|
243
|
+
console.print("[yellow]No templates found[/yellow]")
|
|
244
|
+
return []
|
|
245
|
+
|
|
246
|
+
self._print_templates_table(templates)
|
|
247
|
+
console.print(f"\n[dim]{len(templates)} template(s) found[/dim]")
|
|
248
|
+
|
|
249
|
+
return [t.model_dump() for t in templates]
|
|
250
|
+
|
|
251
|
+
def _interactive_pick(self, search: str | None) -> TemplateDetail | None:
|
|
252
|
+
"""Show interactive picker and return selected template detail."""
|
|
253
|
+
with console.status("[blue]Fetching templates...[/blue]"):
|
|
254
|
+
response = templates_api.list_templates(search=search)
|
|
255
|
+
|
|
256
|
+
templates = response.items
|
|
257
|
+
if not templates:
|
|
258
|
+
console.print("[yellow]No templates found[/yellow]")
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
self._print_templates_table(templates)
|
|
262
|
+
console.print()
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
choice = click.prompt(
|
|
266
|
+
"Select a template",
|
|
267
|
+
type=click.IntRange(1, len(templates)),
|
|
268
|
+
)
|
|
269
|
+
except (click.Abort, EOFError):
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
selected = templates[choice - 1]
|
|
273
|
+
console.print()
|
|
274
|
+
|
|
275
|
+
# Fetch full detail
|
|
276
|
+
with console.status(f"[blue]Fetching {selected.name}...[/blue]"):
|
|
277
|
+
return templates_api.get_template(selected.name)
|
|
278
|
+
|
|
279
|
+
def _fetch_template(self, slug: str) -> TemplateDetail | None:
|
|
280
|
+
"""Fetch template detail by slug, handling errors."""
|
|
281
|
+
try:
|
|
282
|
+
with console.status(f"[blue]Fetching {slug}...[/blue]"):
|
|
283
|
+
return templates_api.get_template(slug)
|
|
284
|
+
except Exception as e:
|
|
285
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
def _print_templates_table(self, templates: list[TemplateInfo]) -> None:
|
|
289
|
+
"""Print a numbered table of templates."""
|
|
290
|
+
table = Table(show_header=True, header_style="bold blue")
|
|
291
|
+
table.add_column("#", style="dim", width=4, justify="right")
|
|
292
|
+
table.add_column("Name")
|
|
293
|
+
table.add_column("Language")
|
|
294
|
+
table.add_column("Framework")
|
|
295
|
+
table.add_column("Category")
|
|
296
|
+
|
|
297
|
+
for i, t in enumerate(templates, 1):
|
|
298
|
+
table.add_row(
|
|
299
|
+
str(i),
|
|
300
|
+
t.name,
|
|
301
|
+
t.language,
|
|
302
|
+
t.framework or "—",
|
|
303
|
+
t.category or "—",
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
console.print(table)
|
|
307
|
+
|
|
308
|
+
def _print_template_summary(self, detail: TemplateDetail) -> None:
|
|
309
|
+
"""Print a summary of the selected template."""
|
|
310
|
+
console.print()
|
|
311
|
+
console.print(f"[bold]{detail.title}[/bold]")
|
|
312
|
+
console.print(f"[dim]{detail.description}[/dim]")
|
|
313
|
+
console.print(
|
|
314
|
+
f"Language: [cyan]{detail.language}[/cyan]"
|
|
315
|
+
+ (
|
|
316
|
+
f" Framework: [cyan]{detail.framework}[/cyan]"
|
|
317
|
+
if detail.framework
|
|
318
|
+
else ""
|
|
319
|
+
)
|
|
320
|
+
+ (f" Stars: {detail.stats.stars}" if detail.stats.stars else "")
|
|
321
|
+
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Clone template repos and run lifecycle steps from deepgram.toml."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from .models import LifecycleStep
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def clone_template(github_url: str, target_dir: Path) -> None:
|
|
20
|
+
"""Clone a template repo with depth 1, then remove .git.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
github_url: Full GitHub URL to clone.
|
|
24
|
+
target_dir: Local directory to clone into.
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
RuntimeError: If git clone fails.
|
|
28
|
+
"""
|
|
29
|
+
if target_dir.exists():
|
|
30
|
+
raise RuntimeError(f"Directory already exists: {target_dir}")
|
|
31
|
+
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
["git", "clone", "--depth", "1", github_url, str(target_dir)],
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
)
|
|
37
|
+
if result.returncode != 0:
|
|
38
|
+
raise RuntimeError(f"git clone failed: {result.stderr.strip()}")
|
|
39
|
+
|
|
40
|
+
# Remove .git so the user starts fresh
|
|
41
|
+
git_dir = target_dir / ".git"
|
|
42
|
+
if git_dir.exists():
|
|
43
|
+
shutil.rmtree(git_dir)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def inject_env(target_dir: Path, api_key: str) -> None:
|
|
47
|
+
"""Create or append DEEPGRAM_API_KEY to .env in target_dir.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
target_dir: Directory containing the cloned template.
|
|
51
|
+
api_key: Deepgram API key to inject.
|
|
52
|
+
"""
|
|
53
|
+
env_path = target_dir / ".env"
|
|
54
|
+
|
|
55
|
+
# Check if key already present
|
|
56
|
+
if env_path.exists():
|
|
57
|
+
content = env_path.read_text()
|
|
58
|
+
if "DEEPGRAM_API_KEY=" in content:
|
|
59
|
+
console.print("[dim]DEEPGRAM_API_KEY already set in .env, skipping[/dim]")
|
|
60
|
+
return
|
|
61
|
+
# Append with newline separator
|
|
62
|
+
with open(env_path, "a") as f:
|
|
63
|
+
if not content.endswith("\n"):
|
|
64
|
+
f.write("\n")
|
|
65
|
+
f.write(f"DEEPGRAM_API_KEY={api_key}\n")
|
|
66
|
+
else:
|
|
67
|
+
env_path.write_text(f"DEEPGRAM_API_KEY={api_key}\n")
|
|
68
|
+
|
|
69
|
+
console.print("[green]Injected DEEPGRAM_API_KEY into .env[/green]")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def run_lifecycle_step(step: LifecycleStep, cwd: Path) -> bool:
|
|
73
|
+
"""Execute a lifecycle step from deepgram.toml.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
step: The lifecycle step definition.
|
|
77
|
+
cwd: Working directory for command execution.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
True if the step succeeded, False otherwise.
|
|
81
|
+
"""
|
|
82
|
+
if step.command is None:
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
commands = step.command if isinstance(step.command, list) else [step.command]
|
|
86
|
+
|
|
87
|
+
for cmd in commands:
|
|
88
|
+
if step.message:
|
|
89
|
+
console.print(f"[blue]{step.message}[/blue]")
|
|
90
|
+
else:
|
|
91
|
+
console.print(f"[dim]Running: {cmd}[/dim]")
|
|
92
|
+
|
|
93
|
+
result = subprocess.run(
|
|
94
|
+
cmd,
|
|
95
|
+
cwd=str(cwd),
|
|
96
|
+
shell=True,
|
|
97
|
+
)
|
|
98
|
+
if result.returncode != 0:
|
|
99
|
+
console.print(
|
|
100
|
+
f"[red]Command failed (exit {result.returncode}):[/red] {cmd}"
|
|
101
|
+
)
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
return True
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Models for init command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from deepctl_core import BaseResult
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TemplateInfo(BaseModel):
|
|
12
|
+
"""Summary info for a template (from list endpoint)."""
|
|
13
|
+
|
|
14
|
+
name: str
|
|
15
|
+
title: str
|
|
16
|
+
description: str
|
|
17
|
+
language: str
|
|
18
|
+
framework: str | None = None
|
|
19
|
+
category: str | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TemplateStats(BaseModel):
|
|
23
|
+
"""Repository statistics for a template."""
|
|
24
|
+
|
|
25
|
+
stars: int = 0
|
|
26
|
+
forks: int = 0
|
|
27
|
+
last_updated: str | None = Field(default=None, alias="lastUpdated")
|
|
28
|
+
|
|
29
|
+
model_config = {"populate_by_name": True}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LifecycleStep(BaseModel):
|
|
33
|
+
"""A single lifecycle step from deepgram.toml."""
|
|
34
|
+
|
|
35
|
+
command: str | list[str] | None = None
|
|
36
|
+
message: str | None = None
|
|
37
|
+
parallel: bool | None = None
|
|
38
|
+
pre: dict[str, Any] | None = None
|
|
39
|
+
post: dict[str, Any] | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TomlConfig(BaseModel):
|
|
43
|
+
"""Parsed deepgram.toml configuration."""
|
|
44
|
+
|
|
45
|
+
check: LifecycleStep | None = None
|
|
46
|
+
install: LifecycleStep | None = None
|
|
47
|
+
start: LifecycleStep | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TemplateDetail(TemplateInfo):
|
|
51
|
+
"""Full template detail (from detail endpoint)."""
|
|
52
|
+
|
|
53
|
+
sdk: str | None = None
|
|
54
|
+
tags: list[str] = Field(default_factory=list)
|
|
55
|
+
links: dict[str, str | None] = Field(default_factory=dict)
|
|
56
|
+
stats: TemplateStats = Field(default_factory=TemplateStats)
|
|
57
|
+
readme: str | None = None
|
|
58
|
+
config: TomlConfig | None = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TemplateListResponse(BaseModel):
|
|
62
|
+
"""Response from the templates list API."""
|
|
63
|
+
|
|
64
|
+
total: int = 0
|
|
65
|
+
page: int = 1
|
|
66
|
+
limit: int = 100
|
|
67
|
+
total_pages: int = Field(default=1, alias="totalPages")
|
|
68
|
+
items: list[TemplateInfo] = Field(default_factory=list)
|
|
69
|
+
|
|
70
|
+
model_config = {"populate_by_name": True}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class InitResult(BaseResult):
|
|
74
|
+
"""Result from init command."""
|
|
75
|
+
|
|
76
|
+
template: str | None = None
|
|
77
|
+
directory: str | None = None
|
|
78
|
+
installed: bool = False
|
|
79
|
+
started: bool = False
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""HTTP client for the Deepgram templates API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from .models import TemplateDetail, TemplateInfo, TemplateListResponse
|
|
8
|
+
|
|
9
|
+
BASE_URL = "https://templates.dx.deepgram.com"
|
|
10
|
+
DEFAULT_TIMEOUT = 15.0
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def list_templates(
|
|
14
|
+
search: str | None = None,
|
|
15
|
+
page: int = 1,
|
|
16
|
+
limit: int = 100,
|
|
17
|
+
) -> TemplateListResponse:
|
|
18
|
+
"""Fetch templates from the API.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
search: Optional search/filter term.
|
|
22
|
+
page: Page number (1-based).
|
|
23
|
+
limit: Results per page.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Parsed template list response.
|
|
27
|
+
"""
|
|
28
|
+
params: dict[str, str | int] = {"page": page, "limit": limit}
|
|
29
|
+
if search:
|
|
30
|
+
params["search"] = search
|
|
31
|
+
|
|
32
|
+
resp = httpx.get(
|
|
33
|
+
f"{BASE_URL}/api/templates",
|
|
34
|
+
params=params,
|
|
35
|
+
timeout=DEFAULT_TIMEOUT,
|
|
36
|
+
)
|
|
37
|
+
resp.raise_for_status()
|
|
38
|
+
return TemplateListResponse.model_validate(resp.json())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_template(slug: str) -> TemplateDetail:
|
|
42
|
+
"""Fetch a single template by slug.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
slug: Template name/slug (e.g. "node-transcription").
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Parsed template detail.
|
|
49
|
+
"""
|
|
50
|
+
resp = httpx.get(
|
|
51
|
+
f"{BASE_URL}/api/templates/{slug}",
|
|
52
|
+
timeout=DEFAULT_TIMEOUT,
|
|
53
|
+
)
|
|
54
|
+
resp.raise_for_status()
|
|
55
|
+
return TemplateDetail.model_validate(resp.json())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def filter_templates(templates: list[TemplateInfo], search: str) -> list[TemplateInfo]:
|
|
59
|
+
"""Client-side filter of templates by search term.
|
|
60
|
+
|
|
61
|
+
Matches against name, title, description, language, framework, and category.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
templates: List of templates to filter.
|
|
65
|
+
search: Search term (case-insensitive).
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Filtered list.
|
|
69
|
+
"""
|
|
70
|
+
term = search.lower()
|
|
71
|
+
results: list[TemplateInfo] = []
|
|
72
|
+
for t in templates:
|
|
73
|
+
fields = [t.name, t.title, t.description, t.language]
|
|
74
|
+
if t.framework:
|
|
75
|
+
fields.append(t.framework)
|
|
76
|
+
if t.category:
|
|
77
|
+
fields.append(t.category)
|
|
78
|
+
if any(term in f.lower() for f in fields):
|
|
79
|
+
results.append(t)
|
|
80
|
+
return results
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-cmd-init
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Init command for deepctl — scaffold Deepgram starter apps
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: deepgram,cli,init,starter,templates
|
|
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: httpx>=0.24.0
|
|
21
|
+
Requires-Dist: pydantic>=2.0.0
|
|
22
|
+
|
|
23
|
+
# deepctl-cmd-init
|
|
24
|
+
|
|
25
|
+
> Part of [deepctl](https://github.com/deepgram/cli) — Official Deepgram CLI
|
|
26
|
+
|
|
27
|
+
Init command for deepctl — scaffold Deepgram starter apps
|
|
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 init` | `deepctl_cmd_init.command:InitCommand` |
|
|
55
|
+
|
|
56
|
+
## Dependencies
|
|
57
|
+
|
|
58
|
+
- `click>=8.0.0`
|
|
59
|
+
- `rich>=13.0.0`
|
|
60
|
+
- `httpx>=0.24.0`
|
|
61
|
+
- `pydantic>=2.0.0`
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT — see [LICENSE](../../LICENSE)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
deepctl_cmd_init/__init__.py,sha256=mJ3X78F-lDDsIxAdV8I8O_cG0w--U641JCzrLZ-pftA,146
|
|
2
|
+
deepctl_cmd_init/command.py,sha256=IP_QZJ0aEQFMhPA_ySDbCvbz08a-W2IxUC0wVJY1kbc,11079
|
|
3
|
+
deepctl_cmd_init/lifecycle.py,sha256=5-ICKF3RTI-erUoJmMBHKKfF-8onSXXsrsBZLdTpMV0,2907
|
|
4
|
+
deepctl_cmd_init/models.py,sha256=uhBYvCYrOSbZ-mdrEwbCorApB89RemIAUtvBbNlxO4A,1971
|
|
5
|
+
deepctl_cmd_init/templates_api.py,sha256=0BDO8lFlmAEcaro1rDf213T2qv01d22zaL6cy0KCdVg,2068
|
|
6
|
+
deepctl_cmd_init-0.0.2.dist-info/METADATA,sha256=EhFy6qYfHtXc6P9Hgaw0e5KXZPLI0Ap59EHoHDYofuA,1563
|
|
7
|
+
deepctl_cmd_init-0.0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
deepctl_cmd_init-0.0.2.dist-info/entry_points.txt,sha256=lAaUfHpN1Zj3TwaOqgFfCu-WcK9BJgDkt0jD9v1nCq0,63
|
|
9
|
+
deepctl_cmd_init-0.0.2.dist-info/top_level.txt,sha256=2eTFp6AsUibDM5tRczLTEtdiznBPlMg1I14QCR38qoM,17
|
|
10
|
+
deepctl_cmd_init-0.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
deepctl_cmd_init
|