thoughtleaders-cli 0.6.11__tar.gz → 0.6.12__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.
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/PKG-INFO +1 -1
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/pyproject.toml +1 -1
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/__init__.py +1 -1
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/reports.py +134 -58
- thoughtleaders_cli-0.6.12/tests/test_reports.py +79 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/.gitignore +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/AGENTS.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/LICENSE +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/README.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/agents/tl-analyst.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/commands/tl-balance.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/commands/tl-reports.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/commands/tl-sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/commands/tl.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/docs/architecture.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl/SKILL.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl/references/business-glossary.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl/references/elasticsearch-schema.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl/references/firebolt-schema.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl/references/postgres-schema.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/SKILL.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/examples/golden_queries.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/columns_brands.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/columns_channels.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/columns_content.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/report_glossary.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/sortable_columns.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/references/widgets.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/tools/column_builder.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/tools/database_query.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/tools/keyword_research.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/tools/name_resolver.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/tools/sample_judge.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/tools/similar_channels.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl-report-builder/tools/widget_builder.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/_comments_common.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/ask.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/brands.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/channels.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/db.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/describe.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/doctor.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/recommender.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/schema.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/setup.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/snapshots.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/uploads.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/whoami.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/config.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/filters.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/main.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/output/formatter.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/self_update.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/tests/test_filters.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/tests/test_sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtleaders-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.12
|
|
4
4
|
Summary: ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence
|
|
5
5
|
Project-URL: Homepage, https://thoughtleaders.io
|
|
6
6
|
Project-URL: Repository, https://github.com/ThoughtLeaders-io/thoughtleaders-cli
|
|
@@ -10,9 +10,9 @@ from rich.text import Text
|
|
|
10
10
|
|
|
11
11
|
from tl_cli.client.errors import ApiError, handle_api_error
|
|
12
12
|
from tl_cli.client.http import get_client
|
|
13
|
-
from tl_cli.output.formatter import detect_format, output
|
|
13
|
+
from tl_cli.output.formatter import detect_format, output, output_single
|
|
14
14
|
|
|
15
|
-
app = typer.Typer(help="Saved reports (list, run, create)")
|
|
15
|
+
app = typer.Typer(help="Saved reports (list, run, create, update)")
|
|
16
16
|
err = Console(stderr=True)
|
|
17
17
|
|
|
18
18
|
# Report type labels matching Django's ReportType enum
|
|
@@ -226,75 +226,120 @@ def _handle_follow_up(result: dict) -> str:
|
|
|
226
226
|
return answer
|
|
227
227
|
|
|
228
228
|
|
|
229
|
+
def _parse_config_arg(config_json: str) -> dict:
|
|
230
|
+
"""Parse the --config argument string into a dict, exiting cleanly on bad input."""
|
|
231
|
+
try:
|
|
232
|
+
config = json.loads(config_json)
|
|
233
|
+
except json.JSONDecodeError as exc:
|
|
234
|
+
err.print(f"[red]--config is not valid JSON: {exc}[/red]")
|
|
235
|
+
raise typer.Exit(1)
|
|
236
|
+
if not isinstance(config, dict):
|
|
237
|
+
err.print("[red]--config must be a JSON object.[/red]")
|
|
238
|
+
raise typer.Exit(1)
|
|
239
|
+
return config
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _orchestrate_via_server(
|
|
243
|
+
client,
|
|
244
|
+
prompt: str,
|
|
245
|
+
timeout: int,
|
|
246
|
+
) -> dict:
|
|
247
|
+
"""Run the server-side AI Report Builder loop and return the resulting config."""
|
|
248
|
+
conversation: list[dict[str, str]] = []
|
|
249
|
+
current_prompt = prompt
|
|
250
|
+
|
|
251
|
+
while True:
|
|
252
|
+
# Send prompt to server, poll for result
|
|
253
|
+
try:
|
|
254
|
+
create_data = client.post("/reports/create", json_body={
|
|
255
|
+
"prompt": current_prompt,
|
|
256
|
+
"conversation": conversation,
|
|
257
|
+
})
|
|
258
|
+
except ApiError as e:
|
|
259
|
+
if e.status_code == 503:
|
|
260
|
+
err.print("[red]AI Report Builder is temporarily unavailable. Please try again later.[/red]")
|
|
261
|
+
raise typer.Exit(1)
|
|
262
|
+
handle_api_error(e)
|
|
263
|
+
raise typer.Exit(1)
|
|
264
|
+
|
|
265
|
+
task_id = create_data.get("task_id")
|
|
266
|
+
if not task_id:
|
|
267
|
+
err.print("[red]Server did not return a task ID.[/red]")
|
|
268
|
+
raise typer.Exit(1)
|
|
269
|
+
|
|
270
|
+
result = _poll_for_result(client, task_id, timeout)
|
|
271
|
+
action = result.get("action", "")
|
|
272
|
+
|
|
273
|
+
if action == "follow_up":
|
|
274
|
+
answer = _handle_follow_up(result)
|
|
275
|
+
conversation.append({"role": "user", "content": current_prompt})
|
|
276
|
+
conversation.append({"role": "assistant", "content": result.get("question", "")})
|
|
277
|
+
current_prompt = answer
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
if action in ("error", "unsupported"):
|
|
281
|
+
message = result.get("message", "Request could not be processed.")
|
|
282
|
+
err.print(f"\n[red]{message}[/red]")
|
|
283
|
+
raise typer.Exit(1)
|
|
284
|
+
|
|
285
|
+
if action == "preview":
|
|
286
|
+
return result.get("config", {})
|
|
287
|
+
if action == "create_report":
|
|
288
|
+
return result
|
|
289
|
+
|
|
290
|
+
err.print(f"[yellow]Unexpected action: {action}[/yellow]")
|
|
291
|
+
err.print(json.dumps(result, indent=2, default=str))
|
|
292
|
+
raise typer.Exit(1)
|
|
293
|
+
|
|
294
|
+
|
|
229
295
|
@app.command("create")
|
|
230
296
|
def create_report(
|
|
231
|
-
prompt: str = typer.Argument(
|
|
297
|
+
prompt: str | None = typer.Argument(
|
|
298
|
+
None,
|
|
299
|
+
help="Natural language description of the report you want. Omit when using --config.",
|
|
300
|
+
),
|
|
301
|
+
config_json: str | None = typer.Option(
|
|
302
|
+
None,
|
|
303
|
+
"--config",
|
|
304
|
+
help=(
|
|
305
|
+
"Pre-built report config as JSON. Skips the AI Report Builder "
|
|
306
|
+
"pipeline and saves the config directly. Mutually exclusive with "
|
|
307
|
+
"the prompt argument."
|
|
308
|
+
),
|
|
309
|
+
),
|
|
232
310
|
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
|
|
233
311
|
json_output: bool = typer.Option(False, "--json", help="Output raw JSON config"),
|
|
234
312
|
timeout: int = typer.Option(300, "--timeout", help="Max orchestration time in seconds"),
|
|
235
313
|
) -> None:
|
|
236
|
-
"""Create a report from a natural
|
|
314
|
+
"""Create a report from a natural-language prompt or a pre-built config.
|
|
237
315
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
316
|
+
With a prompt, runs the AI Report Builder pipeline (keyword research, config
|
|
317
|
+
generation, review) and saves the resulting campaign.
|
|
318
|
+
|
|
319
|
+
With --config '<json>', skips the orchestration pipeline and saves the
|
|
320
|
+
provided config directly. Useful when an external agent (e.g. the
|
|
321
|
+
tl-report-builder Claude Code skill) has already produced a validated
|
|
322
|
+
config and you just want to persist it.
|
|
241
323
|
|
|
242
324
|
Examples:
|
|
243
325
|
tl reports create "gaming channels sponsoring energy drinks"
|
|
244
326
|
tl reports create "tech review channels with 100K+ subscribers" --yes
|
|
245
|
-
tl reports create "
|
|
327
|
+
tl reports create --config "$(cat config.json)" --yes
|
|
246
328
|
"""
|
|
329
|
+
if (prompt is None) == (config_json is None):
|
|
330
|
+
err.print(
|
|
331
|
+
"[red]Provide either a natural-language prompt OR --config '<json>', not both.[/red]"
|
|
332
|
+
)
|
|
333
|
+
raise typer.Exit(1)
|
|
334
|
+
|
|
247
335
|
client = get_client()
|
|
248
336
|
try:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
|
337
|
+
if config_json is not None:
|
|
338
|
+
config = _parse_config_arg(config_json)
|
|
339
|
+
saved_prompts: list[str] = []
|
|
340
|
+
else:
|
|
341
|
+
config = _orchestrate_via_server(client, prompt, timeout)
|
|
342
|
+
saved_prompts = [prompt]
|
|
298
343
|
|
|
299
344
|
# --- Show preview ---
|
|
300
345
|
if json_output:
|
|
@@ -315,7 +360,7 @@ def create_report(
|
|
|
315
360
|
# --- Save to server ---
|
|
316
361
|
data = client.post("/reports/confirm", json_body={
|
|
317
362
|
"config": config,
|
|
318
|
-
"prompts":
|
|
363
|
+
"prompts": saved_prompts,
|
|
319
364
|
"reasoning": "",
|
|
320
365
|
})
|
|
321
366
|
|
|
@@ -344,3 +389,34 @@ def create_report(
|
|
|
344
389
|
handle_api_error(e)
|
|
345
390
|
finally:
|
|
346
391
|
client.close()
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@app.command("update")
|
|
395
|
+
def update_report(
|
|
396
|
+
report_id: int = typer.Argument(..., help="Report ID"),
|
|
397
|
+
fields: str = typer.Argument(..., help='JSON object of fields to update'),
|
|
398
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
399
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
400
|
+
) -> None:
|
|
401
|
+
"""Update a saved report.
|
|
402
|
+
|
|
403
|
+
Unknown fields are rejected with a 400 listing the offending key.
|
|
404
|
+
"""
|
|
405
|
+
fmt = detect_format(json_output, False, False, toon_output)
|
|
406
|
+
try:
|
|
407
|
+
body = json.loads(fields)
|
|
408
|
+
except json.JSONDecodeError as exc:
|
|
409
|
+
err.print(f"[red]Error:[/red] fields argument must be a JSON object: {exc}")
|
|
410
|
+
raise typer.Exit(1)
|
|
411
|
+
if not isinstance(body, dict):
|
|
412
|
+
err.print("[red]Error:[/red] fields argument must be a JSON object.")
|
|
413
|
+
raise typer.Exit(1)
|
|
414
|
+
|
|
415
|
+
client = get_client()
|
|
416
|
+
try:
|
|
417
|
+
data = client.post(f"/reports/{report_id}/edit", json_body=body)
|
|
418
|
+
output_single(data, fmt)
|
|
419
|
+
except ApiError as e:
|
|
420
|
+
handle_api_error(e)
|
|
421
|
+
finally:
|
|
422
|
+
client.close()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Tests for `tl reports create --config` and `tl reports update`."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import typer
|
|
5
|
+
from typer.testing import CliRunner
|
|
6
|
+
|
|
7
|
+
from tl_cli.commands.reports import _parse_config_arg, app
|
|
8
|
+
|
|
9
|
+
runner = CliRunner()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# _parse_config_arg
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestParseConfigArg:
|
|
18
|
+
def test_valid_object_returns_dict(self) -> None:
|
|
19
|
+
result = _parse_config_arg('{"report_title": "Test", "report_type": 3}')
|
|
20
|
+
assert result == {"report_title": "Test", "report_type": 3}
|
|
21
|
+
|
|
22
|
+
def test_invalid_json_exits(self) -> None:
|
|
23
|
+
with pytest.raises(typer.Exit) as excinfo:
|
|
24
|
+
_parse_config_arg('{not json')
|
|
25
|
+
assert excinfo.value.exit_code == 1
|
|
26
|
+
|
|
27
|
+
def test_non_object_exits(self) -> None:
|
|
28
|
+
# JSON arrays / strings / numbers are valid JSON but not the object the
|
|
29
|
+
# endpoint accepts.
|
|
30
|
+
with pytest.raises(typer.Exit) as excinfo:
|
|
31
|
+
_parse_config_arg('[1, 2, 3]')
|
|
32
|
+
assert excinfo.value.exit_code == 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# tl reports create — argument validation
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestCreateArgValidation:
|
|
41
|
+
def test_no_prompt_and_no_config_rejected(self) -> None:
|
|
42
|
+
result = runner.invoke(app, ["create"])
|
|
43
|
+
assert result.exit_code == 1
|
|
44
|
+
assert "either" in (result.stderr or result.output).lower() and "config" in (result.stderr or result.output).lower()
|
|
45
|
+
|
|
46
|
+
def test_both_prompt_and_config_rejected(self) -> None:
|
|
47
|
+
result = runner.invoke(
|
|
48
|
+
app,
|
|
49
|
+
["create", "gaming channels", "--config", '{"report_title": "x", "report_type": 3}'],
|
|
50
|
+
)
|
|
51
|
+
assert result.exit_code == 1
|
|
52
|
+
assert "either" in (result.stderr or result.output).lower()
|
|
53
|
+
|
|
54
|
+
def test_config_invalid_json_rejected(self) -> None:
|
|
55
|
+
result = runner.invoke(app, ["create", "--config", "{not json", "--yes"])
|
|
56
|
+
assert result.exit_code == 1
|
|
57
|
+
assert "valid json" in (result.stderr or result.output).lower()
|
|
58
|
+
|
|
59
|
+
def test_config_non_object_rejected(self) -> None:
|
|
60
|
+
result = runner.invoke(app, ["create", "--config", "[1,2,3]", "--yes"])
|
|
61
|
+
assert result.exit_code == 1
|
|
62
|
+
assert "json object" in (result.stderr or result.output).lower()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# tl reports update — argument validation
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TestUpdateArgValidation:
|
|
71
|
+
def test_invalid_json_rejected(self) -> None:
|
|
72
|
+
result = runner.invoke(app, ["update", "12345", "{not json"])
|
|
73
|
+
assert result.exit_code == 1
|
|
74
|
+
assert "json object" in (result.stderr or result.output).lower()
|
|
75
|
+
|
|
76
|
+
def test_non_object_rejected(self) -> None:
|
|
77
|
+
result = runner.invoke(app, ["update", "12345", '"just a string"'])
|
|
78
|
+
assert result.exit_code == 1
|
|
79
|
+
assert "json object" in (result.stderr or result.output).lower()
|
|
File without changes
|
{thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/.github/workflows/python-publish.yml
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
{thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl/references/business-glossary.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl/references/elasticsearch-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl/references/firebolt-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/skills/tl/references/postgres-schema.md
RENAMED
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.12}/src/tl_cli/commands/_comments_common.py
RENAMED
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|