thoughtleaders-cli 0.5.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.
- thoughtleaders_cli-0.5.0.dist-info/METADATA +215 -0
- thoughtleaders_cli-0.5.0.dist-info/RECORD +59 -0
- thoughtleaders_cli-0.5.0.dist-info/WHEEL +4 -0
- thoughtleaders_cli-0.5.0.dist-info/entry_points.txt +2 -0
- thoughtleaders_cli-0.5.0.dist-info/licenses/LICENSE +21 -0
- tl_cli/__init__.py +3 -0
- tl_cli/_completions.py +4 -0
- tl_cli/_plugin/.claude-plugin/marketplace.json +17 -0
- tl_cli/_plugin/.claude-plugin/plugin.json +12 -0
- tl_cli/_plugin/agents/tl-analyst.md +66 -0
- tl_cli/_plugin/commands/tl-balance.md +10 -0
- tl_cli/_plugin/commands/tl-brands.md +16 -0
- tl_cli/_plugin/commands/tl-channels.md +31 -0
- tl_cli/_plugin/commands/tl-reports.md +16 -0
- tl_cli/_plugin/commands/tl-sponsorships.md +23 -0
- tl_cli/_plugin/commands/tl.md +28 -0
- tl_cli/_plugin/hooks/hooks.json +26 -0
- tl_cli/_plugin/hooks/scripts/post-usage.sh +26 -0
- tl_cli/_plugin/hooks/scripts/pre-check.sh +30 -0
- tl_cli/_plugin/skills/tl/SKILL.md +413 -0
- tl_cli/_plugin/skills/tl/references/business-glossary.md +159 -0
- tl_cli/_plugin/skills/tl/references/elasticsearch-schema.md +259 -0
- tl_cli/_plugin/skills/tl/references/firebolt-schema.md +208 -0
- tl_cli/_plugin/skills/tl/references/postgres-schema.md +269 -0
- tl_cli/auth/__init__.py +0 -0
- tl_cli/auth/commands.py +49 -0
- tl_cli/auth/login.py +328 -0
- tl_cli/auth/pkce.py +21 -0
- tl_cli/auth/token_store.py +98 -0
- tl_cli/client/__init__.py +0 -0
- tl_cli/client/errors.py +72 -0
- tl_cli/client/http.py +109 -0
- tl_cli/commands/__init__.py +0 -0
- tl_cli/commands/ask.py +54 -0
- tl_cli/commands/balance.py +68 -0
- tl_cli/commands/brands.py +174 -0
- tl_cli/commands/changelog.py +119 -0
- tl_cli/commands/channels.py +291 -0
- tl_cli/commands/comments.py +63 -0
- tl_cli/commands/db.py +104 -0
- tl_cli/commands/deals.py +52 -0
- tl_cli/commands/describe.py +166 -0
- tl_cli/commands/doctor.py +70 -0
- tl_cli/commands/matches.py +69 -0
- tl_cli/commands/proposals.py +69 -0
- tl_cli/commands/reports.py +346 -0
- tl_cli/commands/schema.py +55 -0
- tl_cli/commands/setup.py +401 -0
- tl_cli/commands/snapshots.py +93 -0
- tl_cli/commands/sponsorships.py +193 -0
- tl_cli/commands/uploads.py +84 -0
- tl_cli/commands/whoami.py +206 -0
- tl_cli/config.py +55 -0
- tl_cli/filters.py +88 -0
- tl_cli/hints.py +53 -0
- tl_cli/main.py +209 -0
- tl_cli/output/__init__.py +0 -0
- tl_cli/output/formatter.py +436 -0
- tl_cli/self_update.py +173 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""tl reports — List, run, and create reports."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
from tl_cli.client.errors import ApiError, handle_api_error
|
|
12
|
+
from tl_cli.client.http import get_client
|
|
13
|
+
from tl_cli.output.formatter import detect_format, output
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(help="Saved reports (list, run, create)")
|
|
16
|
+
err = Console(stderr=True)
|
|
17
|
+
|
|
18
|
+
# Report type labels matching Django's ReportType enum
|
|
19
|
+
REPORT_TYPE_LABELS = {1: "Content", 2: "Brands", 3: "Channels", 8: "Sponsorships"}
|
|
20
|
+
|
|
21
|
+
POLL_INTERVAL = 2 # seconds between server polls
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.callback(invoke_without_command=True)
|
|
25
|
+
def reports(
|
|
26
|
+
ctx: typer.Context,
|
|
27
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
28
|
+
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
29
|
+
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
30
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
31
|
+
) -> None:
|
|
32
|
+
"""List your organization's saved reports (free, no credits).
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
tl reports
|
|
36
|
+
tl reports --json
|
|
37
|
+
"""
|
|
38
|
+
if ctx.invoked_subcommand is not None:
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
42
|
+
|
|
43
|
+
client = get_client()
|
|
44
|
+
try:
|
|
45
|
+
data = client.get("/reports")
|
|
46
|
+
for r in data.get("results", []):
|
|
47
|
+
r["report_id"] = r.pop("id", None)
|
|
48
|
+
output(
|
|
49
|
+
data,
|
|
50
|
+
fmt,
|
|
51
|
+
columns=["report_id", "title", "report_type", "created_by", "updated_at"],
|
|
52
|
+
title="Saved Reports",
|
|
53
|
+
)
|
|
54
|
+
except ApiError as e:
|
|
55
|
+
handle_api_error(e)
|
|
56
|
+
finally:
|
|
57
|
+
client.close()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command("run")
|
|
61
|
+
def run_report(
|
|
62
|
+
report_id: int = typer.Argument(..., help="Report ID to execute"),
|
|
63
|
+
since: str | None = typer.Option(None, "--since", help="Override start date"),
|
|
64
|
+
until: str | None = typer.Option(None, "--until", help="Override end date"),
|
|
65
|
+
columns: str | None = typer.Option(None, "--columns", help="Comma-separated list of columns to display (overrides defaults)"),
|
|
66
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
67
|
+
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
68
|
+
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
69
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
70
|
+
limit: int = typer.Option(100, "--limit", "-l", help="Max results"),
|
|
71
|
+
offset: int = typer.Option(0, "--offset", help="Pagination offset"),
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Run a saved report with its configured filters.
|
|
74
|
+
|
|
75
|
+
Credits are charged based on the results returned (varies by report type).
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
tl reports run 789
|
|
79
|
+
tl reports run 789 --since 2026-01-01 --json
|
|
80
|
+
tl reports run 789 --columns brand_id,name,views_sum,channel_count
|
|
81
|
+
"""
|
|
82
|
+
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
83
|
+
|
|
84
|
+
params: dict[str, str] = {"limit": str(limit), "offset": str(offset)}
|
|
85
|
+
if since:
|
|
86
|
+
params["since"] = since
|
|
87
|
+
if until:
|
|
88
|
+
params["until"] = until
|
|
89
|
+
|
|
90
|
+
client = get_client()
|
|
91
|
+
try:
|
|
92
|
+
data = client.get(f"/reports/{report_id}/run", params=params)
|
|
93
|
+
|
|
94
|
+
# Build title from report metadata
|
|
95
|
+
report_title = data.get("report_title", "")
|
|
96
|
+
report_type = data.get("report_type", "")
|
|
97
|
+
title = f"Report #{report_id}"
|
|
98
|
+
if report_title:
|
|
99
|
+
title = f"{report_title} (#{report_id})"
|
|
100
|
+
|
|
101
|
+
# Column selection priority: --columns flag > server display_columns > auto-detect
|
|
102
|
+
col_list = None
|
|
103
|
+
if columns:
|
|
104
|
+
col_list = [c.strip() for c in columns.split(",") if c.strip()]
|
|
105
|
+
elif data.get("display_columns"):
|
|
106
|
+
col_list = data["display_columns"]
|
|
107
|
+
|
|
108
|
+
output(data, fmt, columns=col_list, title=title)
|
|
109
|
+
except ApiError as e:
|
|
110
|
+
handle_api_error(e)
|
|
111
|
+
finally:
|
|
112
|
+
client.close()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# tl reports create — AI Report Builder (server-side)
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _format_preview(config: dict) -> Panel:
|
|
121
|
+
"""Format a report config as a Rich panel for terminal display."""
|
|
122
|
+
lines = Text()
|
|
123
|
+
|
|
124
|
+
report_type = config.get("report_type", 3)
|
|
125
|
+
lines.append("Report Type: ", style="bold")
|
|
126
|
+
lines.append(f"{REPORT_TYPE_LABELS.get(report_type, report_type)}\n")
|
|
127
|
+
|
|
128
|
+
title = config.get("report_title", "Untitled")
|
|
129
|
+
lines.append("Title: ", style="bold")
|
|
130
|
+
lines.append(f"{title}\n")
|
|
131
|
+
|
|
132
|
+
filterset = config.get("filterset", {})
|
|
133
|
+
keyword_groups = filterset.get("keyword_groups", [])
|
|
134
|
+
if keyword_groups:
|
|
135
|
+
lines.append("\nKeywords: ", style="bold")
|
|
136
|
+
kw_texts = []
|
|
137
|
+
for g in keyword_groups:
|
|
138
|
+
if isinstance(g, dict):
|
|
139
|
+
text = g.get("text", "")
|
|
140
|
+
if g.get("exclude"):
|
|
141
|
+
kw_texts.append(f"-{text}")
|
|
142
|
+
else:
|
|
143
|
+
kw_texts.append(text)
|
|
144
|
+
lines.append(", ".join(kw_texts[:20]))
|
|
145
|
+
if len(kw_texts) > 20:
|
|
146
|
+
lines.append(f" ... and {len(kw_texts) - 20} more")
|
|
147
|
+
lines.append("\n")
|
|
148
|
+
|
|
149
|
+
filters = []
|
|
150
|
+
if filterset.get("languages"):
|
|
151
|
+
filters.append(f"Languages: {', '.join(str(lang) for lang in filterset['languages'])}")
|
|
152
|
+
if filterset.get("content_categories"):
|
|
153
|
+
filters.append(f"Categories: {', '.join(str(c) for c in filterset['content_categories'])}")
|
|
154
|
+
if filterset.get("youtube_views_from") or filterset.get("youtube_views_to"):
|
|
155
|
+
vf = filterset.get("youtube_views_from", "")
|
|
156
|
+
vt = filterset.get("youtube_views_to", "")
|
|
157
|
+
filters.append(f"Views: {vf} - {vt}")
|
|
158
|
+
if filterset.get("days_ago"):
|
|
159
|
+
filters.append(f"Last {filterset['days_ago']} days")
|
|
160
|
+
|
|
161
|
+
if filters:
|
|
162
|
+
lines.append("\nFilters: ", style="bold")
|
|
163
|
+
lines.append("; ".join(filters))
|
|
164
|
+
lines.append("\n")
|
|
165
|
+
|
|
166
|
+
summary = config.get("summary", "")
|
|
167
|
+
if summary:
|
|
168
|
+
lines.append("\nSummary: ", style="bold dim")
|
|
169
|
+
lines.append(summary, style="dim")
|
|
170
|
+
lines.append("\n")
|
|
171
|
+
|
|
172
|
+
return Panel(lines, title="[bold]Report Preview[/bold]", border_style="blue")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _poll_for_result(client, task_id: str, timeout: int) -> dict:
|
|
176
|
+
"""Poll the server for the orchestration result."""
|
|
177
|
+
deadline = time.time() + timeout
|
|
178
|
+
last_message = ""
|
|
179
|
+
|
|
180
|
+
with err.status("[bold blue]Analyzing your request...[/bold blue]") as status:
|
|
181
|
+
while time.time() < deadline:
|
|
182
|
+
data = client.get(f"/reports/poll/{task_id}")
|
|
183
|
+
|
|
184
|
+
for entry in data.get("status_log", []):
|
|
185
|
+
if isinstance(entry, dict):
|
|
186
|
+
msg = entry.get("description", "") or entry.get("title", "")
|
|
187
|
+
if msg and msg != last_message:
|
|
188
|
+
status.update(f"[bold blue]{msg}[/bold blue]")
|
|
189
|
+
last_message = msg
|
|
190
|
+
|
|
191
|
+
if data.get("finished"):
|
|
192
|
+
result = data.get("end_result")
|
|
193
|
+
if data.get("error") or not result:
|
|
194
|
+
err.print("[red]Report generation failed on the server.[/red]")
|
|
195
|
+
raise typer.Exit(1)
|
|
196
|
+
return result
|
|
197
|
+
|
|
198
|
+
time.sleep(POLL_INTERVAL)
|
|
199
|
+
|
|
200
|
+
err.print(f"[red]Orchestration timed out after {timeout}s[/red]")
|
|
201
|
+
raise typer.Exit(1)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _handle_follow_up(result: dict) -> str:
|
|
205
|
+
"""Display follow-up question and get user's answer."""
|
|
206
|
+
question = result.get("question", "Could you provide more details?")
|
|
207
|
+
suggestions = result.get("suggestions", [])
|
|
208
|
+
err.print(f"\n[yellow]{question}[/yellow]")
|
|
209
|
+
if suggestions:
|
|
210
|
+
for i, s in enumerate(suggestions, 1):
|
|
211
|
+
title = s.get("title", s) if isinstance(s, dict) else s
|
|
212
|
+
err.print(f" [dim]{i}.[/dim] {title}")
|
|
213
|
+
err.print()
|
|
214
|
+
|
|
215
|
+
answer = typer.prompt("Your answer")
|
|
216
|
+
|
|
217
|
+
# Allow picking by number
|
|
218
|
+
try:
|
|
219
|
+
idx = int(answer.strip()) - 1
|
|
220
|
+
if 0 <= idx < len(suggestions):
|
|
221
|
+
s = suggestions[idx]
|
|
222
|
+
answer = s.get("title", s) if isinstance(s, dict) else s
|
|
223
|
+
except ValueError:
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
return answer
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@app.command("create")
|
|
230
|
+
def create_report(
|
|
231
|
+
prompt: str = typer.Argument(..., help="Natural language description of the report you want"),
|
|
232
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
|
|
233
|
+
json_output: bool = typer.Option(False, "--json", help="Output raw JSON config"),
|
|
234
|
+
timeout: int = typer.Option(300, "--timeout", help="Max orchestration time in seconds"),
|
|
235
|
+
) -> None:
|
|
236
|
+
"""Create a report from a natural language description.
|
|
237
|
+
|
|
238
|
+
Sends your prompt to the ThoughtLeaders server, which runs the AI Report
|
|
239
|
+
Builder pipeline (keyword research, config generation, review). Then
|
|
240
|
+
confirms with the server to create the campaign.
|
|
241
|
+
|
|
242
|
+
Examples:
|
|
243
|
+
tl reports create "gaming channels sponsoring energy drinks"
|
|
244
|
+
tl reports create "tech review channels with 100K+ subscribers" --yes
|
|
245
|
+
tl reports create "beauty brands on YouTube" --json
|
|
246
|
+
"""
|
|
247
|
+
client = get_client()
|
|
248
|
+
try:
|
|
249
|
+
conversation: list[dict[str, str]] = []
|
|
250
|
+
current_prompt = prompt
|
|
251
|
+
|
|
252
|
+
while True:
|
|
253
|
+
# Send prompt to server, poll for result
|
|
254
|
+
try:
|
|
255
|
+
create_data = client.post("/reports/create", json_body={
|
|
256
|
+
"prompt": current_prompt,
|
|
257
|
+
"conversation": conversation,
|
|
258
|
+
})
|
|
259
|
+
except ApiError as e:
|
|
260
|
+
if e.status_code == 503:
|
|
261
|
+
err.print("[red]AI Report Builder is temporarily unavailable. Please try again later.[/red]")
|
|
262
|
+
raise typer.Exit(1)
|
|
263
|
+
handle_api_error(e)
|
|
264
|
+
raise typer.Exit(1)
|
|
265
|
+
|
|
266
|
+
task_id = create_data.get("task_id")
|
|
267
|
+
if not task_id:
|
|
268
|
+
err.print("[red]Server did not return a task ID.[/red]")
|
|
269
|
+
raise typer.Exit(1)
|
|
270
|
+
|
|
271
|
+
result = _poll_for_result(client, task_id, timeout)
|
|
272
|
+
action = result.get("action", "")
|
|
273
|
+
|
|
274
|
+
# Server wraps response: "preview" → config in result["config"]
|
|
275
|
+
if action == "follow_up":
|
|
276
|
+
answer = _handle_follow_up(result)
|
|
277
|
+
conversation.append({"role": "user", "content": current_prompt})
|
|
278
|
+
conversation.append({"role": "assistant", "content": result.get("question", "")})
|
|
279
|
+
current_prompt = answer
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
if action in ("error", "unsupported"):
|
|
283
|
+
message = result.get("message", "Request could not be processed.")
|
|
284
|
+
err.print(f"\n[red]{message}[/red]")
|
|
285
|
+
raise typer.Exit(1)
|
|
286
|
+
|
|
287
|
+
if action == "preview":
|
|
288
|
+
config = result.get("config", {})
|
|
289
|
+
elif action == "create_report":
|
|
290
|
+
config = result
|
|
291
|
+
else:
|
|
292
|
+
err.print(f"[yellow]Unexpected action: {action}[/yellow]")
|
|
293
|
+
if json_output:
|
|
294
|
+
print(json.dumps(result, indent=2, default=str))
|
|
295
|
+
raise typer.Exit(1)
|
|
296
|
+
|
|
297
|
+
break
|
|
298
|
+
|
|
299
|
+
# --- Show preview ---
|
|
300
|
+
if json_output:
|
|
301
|
+
print(json.dumps(config, indent=2, default=str))
|
|
302
|
+
if not yes:
|
|
303
|
+
raise typer.Exit(0)
|
|
304
|
+
else:
|
|
305
|
+
err.print()
|
|
306
|
+
err.print(_format_preview(config))
|
|
307
|
+
|
|
308
|
+
# --- Confirm ---
|
|
309
|
+
if not yes:
|
|
310
|
+
confirmed = typer.confirm("Create this report?", default=True)
|
|
311
|
+
if not confirmed:
|
|
312
|
+
err.print("[dim]Cancelled.[/dim]")
|
|
313
|
+
raise typer.Exit(0)
|
|
314
|
+
|
|
315
|
+
# --- Save to server ---
|
|
316
|
+
data = client.post("/reports/confirm", json_body={
|
|
317
|
+
"config": config,
|
|
318
|
+
"prompts": [prompt],
|
|
319
|
+
"reasoning": "",
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
results = data.get("results", [{}])
|
|
323
|
+
result = results[0] if results else {}
|
|
324
|
+
report_url = result.get("report_url", "")
|
|
325
|
+
campaign_id = result.get("campaign_id", "")
|
|
326
|
+
|
|
327
|
+
if json_output:
|
|
328
|
+
print(json.dumps(data, indent=2, default=str))
|
|
329
|
+
else:
|
|
330
|
+
err.print()
|
|
331
|
+
err.print("[green bold]Report created![/green bold]")
|
|
332
|
+
err.print(f" Campaign ID: {campaign_id}")
|
|
333
|
+
err.print(f" URL: https://app.thoughtleaders.io{report_url}")
|
|
334
|
+
|
|
335
|
+
unresolved = result.get("unresolved_names", [])
|
|
336
|
+
if unresolved:
|
|
337
|
+
err.print(f"\n [yellow]Unresolved names:[/yellow] {', '.join(unresolved)}")
|
|
338
|
+
|
|
339
|
+
usage = data.get("usage")
|
|
340
|
+
if usage:
|
|
341
|
+
err.print(f"\n [dim]{usage.get('credits_charged', 0)} credits · {usage.get('balance_remaining', '?')} remaining[/dim]")
|
|
342
|
+
|
|
343
|
+
except ApiError as e:
|
|
344
|
+
handle_api_error(e)
|
|
345
|
+
finally:
|
|
346
|
+
client.close()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""tl schema — Show raw-db schema documentation for `tl db pg|fb|es`."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.markdown import Markdown
|
|
8
|
+
|
|
9
|
+
from tl_cli.client.errors import ApiError, handle_api_error
|
|
10
|
+
from tl_cli.client.http import get_client
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="Show schema documentation for raw db queries (`tl db pg|fb|es`)")
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _show(db: str, json_output: bool) -> None:
|
|
17
|
+
client = get_client()
|
|
18
|
+
try:
|
|
19
|
+
data = client.get(f"/raw/{db}/schema")
|
|
20
|
+
if json_output:
|
|
21
|
+
print(json.dumps(data, indent=2, default=str))
|
|
22
|
+
return
|
|
23
|
+
content = data.get("content", "")
|
|
24
|
+
if console.is_terminal:
|
|
25
|
+
console.print(Markdown(content))
|
|
26
|
+
else:
|
|
27
|
+
print(content)
|
|
28
|
+
except ApiError as e:
|
|
29
|
+
handle_api_error(e)
|
|
30
|
+
finally:
|
|
31
|
+
client.close()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command("pg")
|
|
35
|
+
def pg_cmd(
|
|
36
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Show PostgreSQL schema reference (for `tl db pg`)."""
|
|
39
|
+
_show("pg", json_output)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command("fb")
|
|
43
|
+
def fb_cmd(
|
|
44
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Show Firebolt schema (live: tables and column types) for `tl db fb`."""
|
|
47
|
+
_show("fb", json_output)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.command("es")
|
|
51
|
+
def es_cmd(
|
|
52
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Show Elasticsearch document shape for `tl db es`."""
|
|
55
|
+
_show("es", json_output)
|