thoughtleaders-cli 0.6.11__tar.gz → 0.6.13__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.13}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/PKG-INFO +1 -1
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/pyproject.toml +1 -1
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/SKILL.md +3 -3
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/__init__.py +1 -1
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/reports.py +134 -58
- thoughtleaders_cli-0.6.13/tests/test_reports.py +79 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/.gitignore +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/AGENTS.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/LICENSE +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/README.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/agents/tl-analyst.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/commands/tl-balance.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/commands/tl-reports.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/commands/tl-sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/commands/tl.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/docs/architecture.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl/SKILL.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl/references/business-glossary.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl/references/elasticsearch-schema.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl/references/firebolt-schema.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl/references/postgres-schema.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/examples/golden_queries.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/columns_brands.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/columns_channels.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/columns_content.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/report_glossary.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/sortable_columns.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/references/widgets.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/tools/column_builder.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/tools/database_query.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/tools/keyword_research.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/tools/name_resolver.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/tools/sample_judge.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/tools/similar_channels.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl-report-builder/tools/widget_builder.md +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/_comments_common.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/ask.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/brands.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/channels.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/db.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/describe.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/doctor.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/recommender.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/schema.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/setup.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/snapshots.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/uploads.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/commands/whoami.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/config.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/filters.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/main.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/output/formatter.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/src/tl_cli/self_update.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/tests/test_filters.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/tests/test_sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/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.13
|
|
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
|
|
@@ -226,9 +226,9 @@ USER_QUERY
|
|
|
226
226
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
-
There is no fifth phase. Phase 4's output IS the deliverable: a complete, validated campaign config + takeaways. The save step happens **outside the skill**,
|
|
229
|
+
There is no fifth phase. Phase 4's output IS the deliverable: a complete, validated campaign config + takeaways. The save step happens **outside the skill**, by handing the JSON to `tl reports create --config '<json>' --yes`. The skill itself never writes to the database directly — reads use raw `tl db es` (intelligence reports — types 1/2/3) or raw `tl db pg` (sponsorship reports — type 8); writes go through the CLI command, which posts to the report-creation API.
|
|
230
230
|
|
|
231
|
-
> **Save-mechanism policy**:
|
|
231
|
+
> **Save-mechanism policy**: After Phase 4 emits the config, the skill instructs the user to run `tl reports create --config '<json>' --yes` to persist it. Edits to a saved report use `tl reports update <id> '<json>'`. Both commands route to the report-creation API endpoint, which delegates to the canonical campaign-update path. **Reads via `tl db es` / `tl db pg` (engine routed by report type — see Step 2.V1), writes via the CLI** is the architectural split. Do NOT instruct the user to paste the JSON into the platform UI — that's an obsolete pre-v0.6.12 fallback.
|
|
232
232
|
|
|
233
233
|
## Phase 1 — Report Type Selection (detail)
|
|
234
234
|
|
|
@@ -1327,7 +1327,7 @@ USER: Build me a report of gaming channels with 100K+ subscribers in English
|
|
|
1327
1327
|
|
|
1328
1328
|
Claude follows this SKILL.md, executing each phase in order. No external command needed — the skill IS the orchestration; `tl db pg` is invoked from within Phase 2/3/4 as needed; tools fire conditionally per their criteria.
|
|
1329
1329
|
|
|
1330
|
-
> **
|
|
1330
|
+
> **Saving the config**: after Phase 4 prints the JSON, instruct the user to run `tl reports create --config '<paste the JSON>' --yes` (tl-cli ≥ v0.6.12). For edits to an existing saved report, use `tl reports update <report_id> '<json patch>'`. Do NOT tell users to paste into the platform UI — that's an obsolete fallback from before the CLI commands existed.
|
|
1331
1331
|
|
|
1332
1332
|
## Reference Files
|
|
1333
1333
|
|
|
@@ -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.13}/.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.13}/skills/tl/references/business-glossary.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl/references/elasticsearch-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/skills/tl/references/firebolt-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/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
|
{thoughtleaders_cli-0.6.11 → thoughtleaders_cli-0.6.13}/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
|