eightstatecli 0.4.0__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.
- eightstatecli-0.4.0.dist-info/METADATA +177 -0
- eightstatecli-0.4.0.dist-info/RECORD +18 -0
- eightstatecli-0.4.0.dist-info/WHEEL +4 -0
- eightstatecli-0.4.0.dist-info/entry_points.txt +2 -0
- eightstatecli-0.4.0.dist-info/licenses/LICENSE +21 -0
- escli/__init__.py +837 -0
- escli/__main__.py +5 -0
- escli/commands/__init__.py +0 -0
- escli/commands/audio.py +438 -0
- escli/commands/docs.py +354 -0
- escli/commands/research.py +597 -0
- escli/commands/search.py +286 -0
- escli/commands/social.py +243 -0
- escli/commands/usage.py +428 -0
- escli/services/__init__.py +0 -0
- escli/services/credentials.py +117 -0
- escli/services/describe.py +186 -0
- escli/services/output.py +168 -0
escli/__main__.py
ADDED
|
File without changes
|
escli/commands/audio.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""
|
|
2
|
+
escli audio — transcription via AssemblyAI.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
escli audio transcribe <file-or-url> Upload + transcribe + poll + return
|
|
6
|
+
escli audio status <id> Check transcript status
|
|
7
|
+
escli audio get <id> Fetch completed transcript
|
|
8
|
+
escli audio list List recent transcripts
|
|
9
|
+
|
|
10
|
+
Speaker diarization:
|
|
11
|
+
--speakers Enable speaker labels
|
|
12
|
+
--speakers-expected N Hint: expected number of speakers (1-20)
|
|
13
|
+
--speaker-names A,B,C Identify speakers by name
|
|
14
|
+
|
|
15
|
+
Audio intelligence:
|
|
16
|
+
--sentiment Enable sentiment analysis
|
|
17
|
+
--chapters Enable auto chapters
|
|
18
|
+
--entities Enable entity detection
|
|
19
|
+
--summarize Enable summarization
|
|
20
|
+
--highlights Enable auto highlights
|
|
21
|
+
--topics Enable topic detection (IAB)
|
|
22
|
+
--content-safety Enable content safety detection
|
|
23
|
+
|
|
24
|
+
Transcription options:
|
|
25
|
+
--language CODE Language code (default: auto-detect)
|
|
26
|
+
--dual-channel Enable dual channel transcription
|
|
27
|
+
--multichannel Enable multichannel transcription
|
|
28
|
+
--word-boost W1,W2 Boost accuracy for specific words
|
|
29
|
+
--disfluencies Include filler words (umm, uh)
|
|
30
|
+
--filter-profanity Filter profanity
|
|
31
|
+
--redact-pii Redact personally identifiable info
|
|
32
|
+
|
|
33
|
+
Output:
|
|
34
|
+
--format text|json|srt|vtt Output format (default: text)
|
|
35
|
+
-o, --output FILE Write output to file
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import argparse
|
|
39
|
+
import json
|
|
40
|
+
import os
|
|
41
|
+
import pathlib
|
|
42
|
+
import sys
|
|
43
|
+
import time
|
|
44
|
+
|
|
45
|
+
from ..services.credentials import get_key_for_service, report_key
|
|
46
|
+
|
|
47
|
+
AAI_BASE = "https://api.assemblyai.com"
|
|
48
|
+
POLL_INTERVAL = 3.0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _get_api_key() -> str:
|
|
52
|
+
key = get_key_for_service("assemblyai", "ASSEMBLYAI_API_KEY")
|
|
53
|
+
if not key:
|
|
54
|
+
print(" ✗ no AssemblyAI API key. Set ASSEMBLYAI_API_KEY or add one via the dashboard.", file=sys.stderr)
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
return key
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _headers(api_key: str) -> dict:
|
|
60
|
+
return {"Authorization": api_key, "Content-Type": "application/json"}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _request(method: str, path: str, api_key: str, **kwargs):
|
|
64
|
+
import httpx
|
|
65
|
+
url = f"{AAI_BASE}{path}"
|
|
66
|
+
headers = {"Authorization": api_key}
|
|
67
|
+
if "json_body" in kwargs:
|
|
68
|
+
headers["Content-Type"] = "application/json"
|
|
69
|
+
|
|
70
|
+
resp = httpx.request(
|
|
71
|
+
method, url, headers=headers,
|
|
72
|
+
json=kwargs.get("json_body"),
|
|
73
|
+
content=kwargs.get("content"),
|
|
74
|
+
timeout=kwargs.get("timeout", 30),
|
|
75
|
+
)
|
|
76
|
+
resp.raise_for_status()
|
|
77
|
+
return resp
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _upload_file(filepath: str, api_key: str, quiet: bool = False) -> str:
|
|
81
|
+
"""Upload a local file to AssemblyAI, return the upload_url."""
|
|
82
|
+
path = pathlib.Path(filepath)
|
|
83
|
+
if not path.exists():
|
|
84
|
+
print(f" ✗ file not found: {filepath}", file=sys.stderr)
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
|
|
87
|
+
size_mb = path.stat().st_size / (1024 * 1024)
|
|
88
|
+
if not quiet:
|
|
89
|
+
print(f" ▸ uploading {path.name} ({size_mb:.1f} MB)...", file=sys.stderr)
|
|
90
|
+
|
|
91
|
+
with open(path, "rb") as f:
|
|
92
|
+
resp = _request("POST", "/v2/upload", api_key, content=f, timeout=300)
|
|
93
|
+
|
|
94
|
+
return resp.json()["upload_url"]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _build_transcript_body(args, audio_url: str) -> dict:
|
|
98
|
+
"""Build the transcript request body from CLI args."""
|
|
99
|
+
body: dict = {
|
|
100
|
+
"audio_url": audio_url,
|
|
101
|
+
"speech_models": ["universal-3-pro", "universal-2"],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Language
|
|
105
|
+
lang = getattr(args, "language", None)
|
|
106
|
+
if lang:
|
|
107
|
+
body["language_code"] = lang
|
|
108
|
+
else:
|
|
109
|
+
body["language_detection"] = True
|
|
110
|
+
|
|
111
|
+
# Speaker diarization
|
|
112
|
+
if getattr(args, "speakers", False):
|
|
113
|
+
body["speaker_labels"] = True
|
|
114
|
+
if getattr(args, "speakers_expected", None):
|
|
115
|
+
body["speaker_labels"] = True
|
|
116
|
+
body["speakers_expected"] = args.speakers_expected
|
|
117
|
+
if getattr(args, "speaker_names", None):
|
|
118
|
+
body["speaker_labels"] = True
|
|
119
|
+
names = [n.strip() for n in args.speaker_names.split(",")]
|
|
120
|
+
body["speech_understanding"] = {
|
|
121
|
+
"request": {
|
|
122
|
+
"speaker_identification": {
|
|
123
|
+
"speaker_type": "name",
|
|
124
|
+
"known_values": names,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Audio intelligence
|
|
130
|
+
if getattr(args, "sentiment", False):
|
|
131
|
+
body["sentiment_analysis"] = True
|
|
132
|
+
if getattr(args, "chapters", False):
|
|
133
|
+
body["auto_chapters"] = True
|
|
134
|
+
if getattr(args, "entities", False):
|
|
135
|
+
body["entity_detection"] = True
|
|
136
|
+
if getattr(args, "summarize", False):
|
|
137
|
+
body["summarization"] = True
|
|
138
|
+
body["summary_model"] = "informative"
|
|
139
|
+
body["summary_type"] = "bullets"
|
|
140
|
+
if getattr(args, "highlights", False):
|
|
141
|
+
body["auto_highlights"] = True
|
|
142
|
+
if getattr(args, "topics", False):
|
|
143
|
+
body["iab_categories"] = True
|
|
144
|
+
if getattr(args, "content_safety", False):
|
|
145
|
+
body["content_safety"] = True
|
|
146
|
+
|
|
147
|
+
# Transcription options
|
|
148
|
+
if getattr(args, "dual_channel", False):
|
|
149
|
+
body["dual_channel"] = True
|
|
150
|
+
if getattr(args, "multichannel", False):
|
|
151
|
+
body["multichannel"] = True
|
|
152
|
+
if getattr(args, "word_boost", None):
|
|
153
|
+
body["word_boost"] = [w.strip() for w in args.word_boost.split(",")]
|
|
154
|
+
body["boost_param"] = "high"
|
|
155
|
+
if getattr(args, "disfluencies", False):
|
|
156
|
+
body["disfluencies"] = True
|
|
157
|
+
if getattr(args, "filter_profanity", False):
|
|
158
|
+
body["filter_profanity"] = True
|
|
159
|
+
if getattr(args, "redact_pii", False):
|
|
160
|
+
body["redact_pii"] = True
|
|
161
|
+
body["redact_pii_policies"] = [
|
|
162
|
+
"email_address", "phone_number", "person_name",
|
|
163
|
+
"location", "date_of_birth", "credit_card_number",
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
return body
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _poll(transcript_id: str, api_key: str, quiet: bool = False) -> dict:
|
|
170
|
+
"""Poll until transcript is completed or errored."""
|
|
171
|
+
if not quiet:
|
|
172
|
+
print(f" ░░░░░░░░░░░░░░░░░░░░ transcribing...", file=sys.stderr, end="", flush=True)
|
|
173
|
+
|
|
174
|
+
while True:
|
|
175
|
+
resp = _request("GET", f"/v2/transcript/{transcript_id}", api_key)
|
|
176
|
+
data = resp.json()
|
|
177
|
+
status = data.get("status")
|
|
178
|
+
|
|
179
|
+
if status == "completed":
|
|
180
|
+
if not quiet:
|
|
181
|
+
print(f"\r ████████████████████ done ", file=sys.stderr)
|
|
182
|
+
return data
|
|
183
|
+
elif status == "error":
|
|
184
|
+
if not quiet:
|
|
185
|
+
print(f"\r ✗ transcription failed: {data.get('error', 'unknown')}", file=sys.stderr)
|
|
186
|
+
return data
|
|
187
|
+
|
|
188
|
+
time.sleep(POLL_INTERVAL)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _format_output(data: dict, fmt: str, args) -> str:
|
|
192
|
+
"""Format transcript output."""
|
|
193
|
+
if fmt == "json":
|
|
194
|
+
return json.dumps(data, indent=2)
|
|
195
|
+
|
|
196
|
+
if fmt == "srt":
|
|
197
|
+
resp = _request("GET", f"/v2/transcript/{data['id']}/srt", _get_api_key())
|
|
198
|
+
return resp.text
|
|
199
|
+
|
|
200
|
+
if fmt == "vtt":
|
|
201
|
+
resp = _request("GET", f"/v2/transcript/{data['id']}/vtt", _get_api_key())
|
|
202
|
+
return resp.text
|
|
203
|
+
|
|
204
|
+
# text format
|
|
205
|
+
lines = []
|
|
206
|
+
|
|
207
|
+
# Speaker diarization output
|
|
208
|
+
if data.get("utterances"):
|
|
209
|
+
for u in data["utterances"]:
|
|
210
|
+
speaker = u.get("speaker", "?")
|
|
211
|
+
text = u.get("text", "")
|
|
212
|
+
lines.append(f"Speaker {speaker}: {text}")
|
|
213
|
+
elif data.get("text"):
|
|
214
|
+
lines.append(data["text"])
|
|
215
|
+
|
|
216
|
+
# Chapters
|
|
217
|
+
if data.get("chapters"):
|
|
218
|
+
lines.append("\n--- Chapters ---")
|
|
219
|
+
for ch in data["chapters"]:
|
|
220
|
+
lines.append(f"\n## {ch.get('headline', '')}")
|
|
221
|
+
lines.append(ch.get("summary", ""))
|
|
222
|
+
|
|
223
|
+
# Summary
|
|
224
|
+
if data.get("summary"):
|
|
225
|
+
lines.append(f"\n--- Summary ---\n{data['summary']}")
|
|
226
|
+
|
|
227
|
+
# Sentiment
|
|
228
|
+
if data.get("sentiment_analysis_results"):
|
|
229
|
+
lines.append("\n--- Sentiment ---")
|
|
230
|
+
for s in data["sentiment_analysis_results"][:20]:
|
|
231
|
+
lines.append(f" [{s.get('sentiment', '')}] {s.get('text', '')[:80]}")
|
|
232
|
+
|
|
233
|
+
# Entities
|
|
234
|
+
if data.get("entities"):
|
|
235
|
+
lines.append("\n--- Entities ---")
|
|
236
|
+
for e in data["entities"][:20]:
|
|
237
|
+
lines.append(f" {e.get('entity_type', '')}: {e.get('text', '')}")
|
|
238
|
+
|
|
239
|
+
return "\n".join(lines)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ── Commands ─────────────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
def cmd_transcribe(args):
|
|
245
|
+
"""Upload (if local file), create transcript, poll, return result."""
|
|
246
|
+
api_key = _get_api_key()
|
|
247
|
+
source = args.source
|
|
248
|
+
t0 = time.time()
|
|
249
|
+
|
|
250
|
+
# Determine audio URL
|
|
251
|
+
if source.startswith("http://") or source.startswith("https://"):
|
|
252
|
+
audio_url = source
|
|
253
|
+
else:
|
|
254
|
+
audio_url = _upload_file(source, api_key, args.quiet)
|
|
255
|
+
|
|
256
|
+
# Build and submit
|
|
257
|
+
body = _build_transcript_body(args, audio_url)
|
|
258
|
+
if not args.quiet:
|
|
259
|
+
print(f" ▸ submitting transcription...", file=sys.stderr)
|
|
260
|
+
|
|
261
|
+
resp = _request("POST", "/v2/transcript", api_key, json_body=body)
|
|
262
|
+
data = resp.json()
|
|
263
|
+
transcript_id = data["id"]
|
|
264
|
+
|
|
265
|
+
if not args.quiet:
|
|
266
|
+
print(f" · id: {transcript_id}", file=sys.stderr)
|
|
267
|
+
|
|
268
|
+
# Poll
|
|
269
|
+
result = _poll(transcript_id, api_key, args.quiet)
|
|
270
|
+
elapsed = round(time.time() - t0, 1)
|
|
271
|
+
|
|
272
|
+
if result.get("status") == "error":
|
|
273
|
+
if args.json:
|
|
274
|
+
print(json.dumps({"success": False, "error": result.get("error"), "id": transcript_id}))
|
|
275
|
+
return 1
|
|
276
|
+
|
|
277
|
+
# Format and output
|
|
278
|
+
fmt = getattr(args, "format", "text") or "text"
|
|
279
|
+
output = _format_output(result, fmt, args)
|
|
280
|
+
|
|
281
|
+
if args.json and fmt != "json":
|
|
282
|
+
print(json.dumps({
|
|
283
|
+
"success": True,
|
|
284
|
+
"id": transcript_id,
|
|
285
|
+
"elapsed_seconds": elapsed,
|
|
286
|
+
"text": result.get("text", ""),
|
|
287
|
+
"speakers": len(set(u.get("speaker", "") for u in result.get("utterances", []))),
|
|
288
|
+
"words": result.get("words", []),
|
|
289
|
+
"utterances": result.get("utterances", []),
|
|
290
|
+
}))
|
|
291
|
+
elif getattr(args, "output", None):
|
|
292
|
+
outpath = pathlib.Path(args.output)
|
|
293
|
+
outpath.write_text(output)
|
|
294
|
+
if not args.quiet:
|
|
295
|
+
print(f" ✓ {outpath} ({elapsed}s)", file=sys.stderr)
|
|
296
|
+
if args.quiet:
|
|
297
|
+
print(str(outpath.resolve()))
|
|
298
|
+
else:
|
|
299
|
+
print(output)
|
|
300
|
+
if not args.quiet and fmt == "text":
|
|
301
|
+
print(f"\n ✓ {elapsed}s · {transcript_id}", file=sys.stderr)
|
|
302
|
+
|
|
303
|
+
return 0
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def cmd_status(args):
|
|
307
|
+
"""Check transcript status."""
|
|
308
|
+
api_key = _get_api_key()
|
|
309
|
+
resp = _request("GET", f"/v2/transcript/{args.transcript_id}", api_key)
|
|
310
|
+
data = resp.json()
|
|
311
|
+
|
|
312
|
+
if args.json:
|
|
313
|
+
print(json.dumps({"success": True, "id": args.transcript_id, "status": data.get("status"),
|
|
314
|
+
"error": data.get("error")}))
|
|
315
|
+
else:
|
|
316
|
+
status = data.get("status", "unknown")
|
|
317
|
+
print(f" {args.transcript_id}: {status}")
|
|
318
|
+
if data.get("error"):
|
|
319
|
+
print(f" error: {data['error']}")
|
|
320
|
+
return 0
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def cmd_get(args):
|
|
324
|
+
"""Fetch a completed transcript."""
|
|
325
|
+
api_key = _get_api_key()
|
|
326
|
+
resp = _request("GET", f"/v2/transcript/{args.transcript_id}", api_key)
|
|
327
|
+
data = resp.json()
|
|
328
|
+
|
|
329
|
+
if data.get("status") != "completed":
|
|
330
|
+
if args.json:
|
|
331
|
+
print(json.dumps({"success": False, "status": data.get("status"), "error": data.get("error")}))
|
|
332
|
+
else:
|
|
333
|
+
print(f" ✗ transcript not ready: {data.get('status')}", file=sys.stderr)
|
|
334
|
+
return 1
|
|
335
|
+
|
|
336
|
+
fmt = getattr(args, "format", "text") or "text"
|
|
337
|
+
output = _format_output(data, fmt, args)
|
|
338
|
+
|
|
339
|
+
if getattr(args, "output", None):
|
|
340
|
+
pathlib.Path(args.output).write_text(output)
|
|
341
|
+
print(f" ✓ {args.output}", file=sys.stderr)
|
|
342
|
+
else:
|
|
343
|
+
print(output)
|
|
344
|
+
return 0
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def cmd_list(args):
|
|
348
|
+
"""List recent transcripts."""
|
|
349
|
+
api_key = _get_api_key()
|
|
350
|
+
resp = _request("GET", "/v2/transcript?limit=20", api_key)
|
|
351
|
+
data = resp.json()
|
|
352
|
+
|
|
353
|
+
transcripts = data.get("transcripts", [])
|
|
354
|
+
if args.json:
|
|
355
|
+
print(json.dumps({"success": True, "transcripts": transcripts, "count": len(transcripts)}))
|
|
356
|
+
return 0
|
|
357
|
+
|
|
358
|
+
if not transcripts:
|
|
359
|
+
print(" No transcripts found.")
|
|
360
|
+
return 0
|
|
361
|
+
|
|
362
|
+
print(f"\n {'ID':<40} {'STATUS':<12} {'CREATED'}")
|
|
363
|
+
print(f" {'─' * 70}")
|
|
364
|
+
for t in transcripts:
|
|
365
|
+
print(f" {t.get('id', ''):<40} {t.get('status', ''):<12} {t.get('created', '')}")
|
|
366
|
+
print()
|
|
367
|
+
return 0
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# ── Parser ───────────────────────────────────────────────────────
|
|
371
|
+
|
|
372
|
+
def register(subparsers):
|
|
373
|
+
"""Register the audio subcommand group."""
|
|
374
|
+
F = argparse.RawDescriptionHelpFormatter
|
|
375
|
+
|
|
376
|
+
audio_p = subparsers.add_parser(
|
|
377
|
+
"audio", aliases=["au"], help="Audio transcription (AssemblyAI)",
|
|
378
|
+
formatter_class=F,
|
|
379
|
+
epilog="""subcommands:
|
|
380
|
+
transcribe <file-or-url> Transcribe audio with speaker diarization
|
|
381
|
+
status <id> Check transcript status
|
|
382
|
+
get <id> Fetch completed transcript
|
|
383
|
+
list List recent transcripts
|
|
384
|
+
|
|
385
|
+
examples:
|
|
386
|
+
escli audio transcribe meeting.mp3 --speakers
|
|
387
|
+
escli audio transcribe https://example.com/audio.mp3 --speakers-expected 3
|
|
388
|
+
escli audio transcribe call.wav --speakers --sentiment --summarize
|
|
389
|
+
escli audio transcribe interview.mp3 --speaker-names "Alice,Bob" -o transcript.txt
|
|
390
|
+
escli --json --quiet audio transcribe file.mp3 --speakers
|
|
391
|
+
""")
|
|
392
|
+
|
|
393
|
+
audio_subs = audio_p.add_subparsers(dest="audio_command", metavar="subcommand")
|
|
394
|
+
|
|
395
|
+
# transcribe
|
|
396
|
+
tr_p = audio_subs.add_parser("transcribe", aliases=["t"], help="Transcribe audio")
|
|
397
|
+
tr_p.add_argument("source", help="Audio file path or URL")
|
|
398
|
+
tr_p.add_argument("-o", "--output", default=None, help="Write output to file")
|
|
399
|
+
tr_p.add_argument("--format", choices=["text", "json", "srt", "vtt"], default="text", help="Output format")
|
|
400
|
+
# Speaker diarization
|
|
401
|
+
tr_p.add_argument("--speakers", action="store_true", help="Enable speaker labels")
|
|
402
|
+
tr_p.add_argument("--speakers-expected", type=int, default=None, metavar="N", help="Expected speaker count (1-20)")
|
|
403
|
+
tr_p.add_argument("--speaker-names", default=None, metavar="A,B,C", help="Identify speakers by name")
|
|
404
|
+
# Audio intelligence
|
|
405
|
+
tr_p.add_argument("--sentiment", action="store_true", help="Enable sentiment analysis")
|
|
406
|
+
tr_p.add_argument("--chapters", action="store_true", help="Enable auto chapters")
|
|
407
|
+
tr_p.add_argument("--entities", action="store_true", help="Enable entity detection")
|
|
408
|
+
tr_p.add_argument("--summarize", action="store_true", help="Enable summarization")
|
|
409
|
+
tr_p.add_argument("--highlights", action="store_true", help="Enable auto highlights")
|
|
410
|
+
tr_p.add_argument("--topics", action="store_true", help="Enable topic detection (IAB)")
|
|
411
|
+
tr_p.add_argument("--content-safety", action="store_true", help="Enable content safety detection")
|
|
412
|
+
# Transcription options
|
|
413
|
+
tr_p.add_argument("--language", default=None, metavar="CODE", help="Language code (default: auto-detect)")
|
|
414
|
+
tr_p.add_argument("--dual-channel", action="store_true", help="Dual channel transcription")
|
|
415
|
+
tr_p.add_argument("--multichannel", action="store_true", help="Multichannel transcription")
|
|
416
|
+
tr_p.add_argument("--word-boost", default=None, metavar="W1,W2", help="Boost accuracy for words")
|
|
417
|
+
tr_p.add_argument("--disfluencies", action="store_true", help="Include filler words")
|
|
418
|
+
tr_p.add_argument("--filter-profanity", action="store_true", help="Filter profanity")
|
|
419
|
+
tr_p.add_argument("--redact-pii", action="store_true", help="Redact PII")
|
|
420
|
+
tr_p.set_defaults(func=cmd_transcribe)
|
|
421
|
+
|
|
422
|
+
# status
|
|
423
|
+
st_p = audio_subs.add_parser("status", aliases=["s"], help="Check transcript status")
|
|
424
|
+
st_p.add_argument("transcript_id", help="Transcript ID")
|
|
425
|
+
st_p.set_defaults(func=cmd_status)
|
|
426
|
+
|
|
427
|
+
# get
|
|
428
|
+
get_p = audio_subs.add_parser("get", aliases=["g"], help="Fetch completed transcript")
|
|
429
|
+
get_p.add_argument("transcript_id", help="Transcript ID")
|
|
430
|
+
get_p.add_argument("--format", choices=["text", "json", "srt", "vtt"], default="text", help="Output format")
|
|
431
|
+
get_p.add_argument("-o", "--output", default=None, help="Write output to file")
|
|
432
|
+
get_p.set_defaults(func=cmd_get)
|
|
433
|
+
|
|
434
|
+
# list
|
|
435
|
+
list_p = audio_subs.add_parser("list", aliases=["ls"], help="List recent transcripts")
|
|
436
|
+
list_p.set_defaults(func=cmd_list)
|
|
437
|
+
|
|
438
|
+
return audio_p
|