thoughtleaders-cli 0.6.51__tar.gz → 0.6.52__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.51 → thoughtleaders_cli-0.6.52}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/AGENTS.md +1 -1
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/PKG-INFO +1 -1
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/pyproject.toml +1 -1
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl/SKILL.md +95 -6
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/__init__.py +1 -1
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/bulk_import.py +5 -1
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/credits.py +12 -1
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/reports.py +13 -6
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/schema.py +11 -4
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/setup.py +27 -9
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/whoami.py +2 -2
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/output/formatter.py +3 -4
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/.gitignore +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/API.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/LICENSE +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/README.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/agents/tl-analyst.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/hooks/scripts/load-tl-skill.mjs +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl/references/business-glossary.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl/references/elasticsearch-schema.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl/references/firebolt-schema.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl/references/postgres-schema.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-import/SKILL.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-keyword-research/SKILL.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-keyword-research/scripts/probe.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/SKILL.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/examples/golden_queries.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/columns_brands.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/columns_channels.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/columns_content.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/report_glossary.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/sortable_columns.json +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/references/widgets.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/tools/column_builder.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/tools/database_query.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/tools/name_resolver.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/tools/sample_judge.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/tools/similar_channels.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-report-builder/tools/widget_builder.md +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/auth/finalize.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/_comments_common.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/brands.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/channels.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/db.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/describe.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/doctor.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/recommender.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/snapshots.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/commands/uploads.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/config.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/filters.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/main.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/src/tl_cli/self_update.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/tests/test_filters.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/tests/test_http_auth.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/tests/test_reports.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/tests/test_sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/uv.lock +0 -0
|
@@ -117,7 +117,7 @@ The version string is defined in three files and all three must be updated toget
|
|
|
117
117
|
|
|
118
118
|
* Do not reference internal architecture of the ThoughtLeaders app in comments or skills. Specifially: do not reference internal table names, field names, API endpoints, Python modules or functions (including the sanitizer).
|
|
119
119
|
* Do not let server implementation details into skill files (anything under `skills/`). Skills describe *what the CLI does* from the user's seat — observable command surface, inputs, outputs, examples. Do not say "the server enforces X", "the API validates Y on its side", "the backend rejects Z" — those are mechanism notes that drift the moment the server changes. State the user-visible behaviour ("unknown keys come back as 400") without naming where it's enforced.
|
|
120
|
-
*
|
|
120
|
+
* **All `import` and `from X import Y` statements live at the top of the Python module file** — after the module docstring, before any code. No inline imports inside function bodies, no lazy imports for "speed" or "optional dependency" reasons. `from __future__ import …` goes at the very top (Python requires that). The only legitimate inline-import exception is **platform-conditional imports** that cannot succeed on the other platform (e.g. `import msvcrt` on Linux, `import termios`/`tty` on Windows) — those stay inside their `if sys.platform == …:` guard. If a circular-import problem makes a top-level import impossible, fix the circular dependency rather than working around it with an inline import.
|
|
121
121
|
|
|
122
122
|
# Git commit rules
|
|
123
123
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtleaders-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.52
|
|
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
|
|
@@ -181,10 +181,6 @@ tl channels history <id-or-name> # Sponsorship history
|
|
|
181
181
|
tl channels similar <id-or-name> # Similarity recommender (Intelligence plan)
|
|
182
182
|
tl brands show <id-or-name> # Brand detail
|
|
183
183
|
tl brands find <query> # Resolve a string to {id, name}; matches name, slug, domain, or keyword
|
|
184
|
-
tl brands history <id-or-name> # Sponsorship history
|
|
185
|
-
tl brands history <query> --channel <id> # Brand mentions on specific channel
|
|
186
|
-
tl brands history-stats <id-or-name> # Aggregate roll-up: counts, total/avg/median views, first/last seen, by-year, top channels
|
|
187
|
-
tl brands history-stats <q> --channel <id> # Same roll-up, narrowed to one channel
|
|
188
184
|
tl brands similar <id-or-name> # Find similar brands via similarity search
|
|
189
185
|
tl recommender tags [query] # List similarity tag names — categories, demographics, formats
|
|
190
186
|
tl recommender top-channels "<tag>" # Top channels loaded on a similarity tag (Intelligence)
|
|
@@ -595,9 +591,102 @@ tl db pg "SELECT al.id, al.weighted_price, al.purchase_date, b.name AS brand
|
|
|
595
591
|
LIMIT 500 OFFSET 0" --json
|
|
596
592
|
```
|
|
597
593
|
|
|
598
|
-
###
|
|
594
|
+
### Brand sponsorship history — what channels does Nike sponsor?
|
|
595
|
+
|
|
596
|
+
Resolve the brand to an ID, then probe ES for articles where the brand appears in `sponsored_brand_mentions`. Channel names live in PG (the ES article doc only carries `channel.id`), so the third call joins them in.
|
|
597
|
+
|
|
598
|
+
```bash
|
|
599
|
+
# 1. Resolve "Nike" → brand ID
|
|
600
|
+
tl brands find Nike --json # → results[0].id, say 21416
|
|
601
|
+
|
|
602
|
+
# 2. Recent sponsored videos for that brand (sorted by publication_date desc)
|
|
603
|
+
tl db es '{
|
|
604
|
+
"size": 50,
|
|
605
|
+
"track_total_hits": true,
|
|
606
|
+
"query": {"bool": {"filter": [
|
|
607
|
+
{"term": {"doc_type": "article"}},
|
|
608
|
+
{"term": {"sponsored_brand_mentions": "21416"}}
|
|
609
|
+
]}},
|
|
610
|
+
"sort": [{"publication_date": "desc"}],
|
|
611
|
+
"_source": ["title", "channel.id", "publication_date", "views"]
|
|
612
|
+
}' --json > /tmp/nike_history.json
|
|
613
|
+
|
|
614
|
+
# 3. Resolve channel.id → channel_name (one PG round-trip for the whole page)
|
|
615
|
+
jq -r '[.results[].channel.id] | unique | map(tostring) | join(",")' /tmp/nike_history.json \
|
|
616
|
+
| xargs -I CH_IDS tl db pg "SELECT id, channel_name FROM thoughtleaders_channel WHERE id IN (CH_IDS)" --json
|
|
617
|
+
|
|
618
|
+
# Narrow to a single channel:
|
|
619
|
+
tl db es '{
|
|
620
|
+
"size": 50,
|
|
621
|
+
"track_total_hits": true,
|
|
622
|
+
"query": {"bool": {"filter": [
|
|
623
|
+
{"term": {"doc_type": "article"}},
|
|
624
|
+
{"term": {"sponsored_brand_mentions": "21416"}},
|
|
625
|
+
{"term": {"channel.id": 5607}}
|
|
626
|
+
]}},
|
|
627
|
+
"sort": [{"publication_date": "desc"}],
|
|
628
|
+
"_source": ["title", "publication_date", "views"]
|
|
629
|
+
}'
|
|
630
|
+
|
|
631
|
+
# Was the video a TL-brokered deal? Cross-check ES video_id against AdLink.article_id:
|
|
632
|
+
tl db pg "SELECT article_id FROM thoughtleaders_adlink
|
|
633
|
+
WHERE article_id IN ('1247603:8LskGvKUA9I', '1247603:abc123')"
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### Brand sponsorship roll-up — totals, first/last seen, top channels, by-year
|
|
637
|
+
|
|
638
|
+
The same ES filter (`doc_type=article` + `sponsored_brand_mentions=<id>`) with `size:0` + aggregations replaces a roll-up call. ES accepts **one aggregation total per request** (top-level + sub-aggs all count), so what would be a single server-side roll-up here splits into a few `tl db es` calls and one client-side join.
|
|
639
|
+
|
|
599
640
|
```bash
|
|
600
|
-
|
|
641
|
+
# Totals + time range (one aggregation total — the four metric aggs are siblings under aggs and bill as a single body)
|
|
642
|
+
tl db es '{
|
|
643
|
+
"size": 0,
|
|
644
|
+
"track_total_hits": true,
|
|
645
|
+
"query": {"bool": {"filter": [
|
|
646
|
+
{"term": {"doc_type": "article"}},
|
|
647
|
+
{"term": {"sponsored_brand_mentions": "21416"}}
|
|
648
|
+
]}},
|
|
649
|
+
"aggs": {
|
|
650
|
+
"views_sum": {"sum": {"field": "views"}},
|
|
651
|
+
"views_avg": {"avg": {"field": "views"}},
|
|
652
|
+
"first_seen": {"min": {"field": "publication_date"}},
|
|
653
|
+
"last_seen": {"max": {"field": "publication_date"}}
|
|
654
|
+
}
|
|
655
|
+
}'
|
|
656
|
+
|
|
657
|
+
# By-year breakdown (date_histogram only — no sub-agg, that would push over the one-agg cap)
|
|
658
|
+
tl db es '{
|
|
659
|
+
"size": 0,
|
|
660
|
+
"query": {"bool": {"filter": [
|
|
661
|
+
{"term": {"doc_type": "article"}},
|
|
662
|
+
{"term": {"sponsored_brand_mentions": "21416"}}
|
|
663
|
+
]}},
|
|
664
|
+
"aggs": {
|
|
665
|
+
"by_year": {"date_histogram": {
|
|
666
|
+
"field": "publication_date", "calendar_interval": "year",
|
|
667
|
+
"format": "yyyy", "min_doc_count": 1
|
|
668
|
+
}}
|
|
669
|
+
}
|
|
670
|
+
}'
|
|
671
|
+
|
|
672
|
+
# Top channels by sponsored-video count (terms agg only — for views per channel, run a second call per channel)
|
|
673
|
+
tl db es '{
|
|
674
|
+
"size": 0,
|
|
675
|
+
"query": {"bool": {"filter": [
|
|
676
|
+
{"term": {"doc_type": "article"}},
|
|
677
|
+
{"term": {"sponsored_brand_mentions": "21416"}}
|
|
678
|
+
]}},
|
|
679
|
+
"aggs": {
|
|
680
|
+
"by_channel": {"terms": {"field": "channel.id", "size": 10, "order": {"_count": "desc"}}}
|
|
681
|
+
}
|
|
682
|
+
}'
|
|
683
|
+
|
|
684
|
+
# TL-brokered deal count for the brand (PG, not ES — adlinks where the brand is on the creator profile)
|
|
685
|
+
tl db pg "SELECT COUNT(*) AS tl_brokered
|
|
686
|
+
FROM thoughtleaders_adlink al
|
|
687
|
+
JOIN thoughtleaders_profile p ON p.id = al.creator_profile_id
|
|
688
|
+
JOIN thoughtleaders_profile_brands pb ON pb.profile_id = p.id
|
|
689
|
+
WHERE pb.brand_id = 21416 AND al.article_id IS NOT NULL"
|
|
601
690
|
```
|
|
602
691
|
|
|
603
692
|
### "Compare view curves for two videos":
|
|
@@ -11,6 +11,7 @@ import time
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
13
|
import typer
|
|
14
|
+
from pytoon import encode as toon_encode
|
|
14
15
|
from rich.console import Console
|
|
15
16
|
|
|
16
17
|
from tl_cli.client.errors import ApiError, handle_api_error
|
|
@@ -59,6 +60,7 @@ def bulk_import_command(
|
|
|
59
60
|
ids_file: str | None = typer.Option(None, "--ids-file", "-f", help="Path to file with one identifier per line. Omit to read from stdin."),
|
|
60
61
|
exclude: bool = typer.Option(False, "--exclude", help="Mark these identifiers as excluded from the report instead of included"),
|
|
61
62
|
json_output: bool = typer.Option(False, "--json", help="JSON output (default)"),
|
|
63
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
62
64
|
) -> None:
|
|
63
65
|
"""Bulk-import entities into a report.
|
|
64
66
|
|
|
@@ -113,7 +115,9 @@ def bulk_import_command(
|
|
|
113
115
|
client.close()
|
|
114
116
|
|
|
115
117
|
output = {"task_id": task_id, **result}
|
|
116
|
-
if
|
|
118
|
+
if toon_output:
|
|
119
|
+
sys.stdout.write(toon_encode(output) + "\n")
|
|
120
|
+
elif json_output or not sys.stdout.isatty():
|
|
117
121
|
json.dump(output, sys.stdout, indent=2)
|
|
118
122
|
sys.stdout.write("\n")
|
|
119
123
|
else:
|
|
@@ -18,6 +18,7 @@ import webbrowser
|
|
|
18
18
|
from decimal import Decimal, InvalidOperation
|
|
19
19
|
|
|
20
20
|
import typer
|
|
21
|
+
from pytoon import encode as toon_encode
|
|
21
22
|
from rich.console import Console
|
|
22
23
|
from rich.prompt import Prompt
|
|
23
24
|
from rich.table import Table
|
|
@@ -34,6 +35,7 @@ err = Console(stderr=True)
|
|
|
34
35
|
@app.command("pricing")
|
|
35
36
|
def pricing_cmd(
|
|
36
37
|
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
38
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
37
39
|
) -> None:
|
|
38
40
|
"""Show the credit-to-USD rate, minimum purchase, and starter balance.
|
|
39
41
|
|
|
@@ -48,6 +50,10 @@ def pricing_cmd(
|
|
|
48
50
|
finally:
|
|
49
51
|
client.close()
|
|
50
52
|
|
|
53
|
+
if toon_output:
|
|
54
|
+
print(toon_encode(data))
|
|
55
|
+
return
|
|
56
|
+
|
|
51
57
|
if json_output:
|
|
52
58
|
print(json.dumps(data, indent=2, default=str))
|
|
53
59
|
return
|
|
@@ -150,9 +156,10 @@ def history_cmd(
|
|
|
150
156
|
limit: int = typer.Option(25, "--limit", help="Max rows to show"),
|
|
151
157
|
offset: int = typer.Option(0, "--offset", help="Offset for pagination"),
|
|
152
158
|
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
159
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
153
160
|
) -> None:
|
|
154
161
|
"""Show recent credit top-ups for your organization (free)."""
|
|
155
|
-
fmt = detect_format(json_output, False, False,
|
|
162
|
+
fmt = detect_format(json_output, False, False, toon_output)
|
|
156
163
|
client = get_client()
|
|
157
164
|
try:
|
|
158
165
|
data = client.get("/credit-purchases", params={"limit": limit, "offset": offset})
|
|
@@ -166,6 +173,10 @@ def history_cmd(
|
|
|
166
173
|
print(json.dumps(data, indent=2, default=str))
|
|
167
174
|
return
|
|
168
175
|
|
|
176
|
+
if fmt == "toon":
|
|
177
|
+
print(toon_encode(data.get("results", [])))
|
|
178
|
+
return
|
|
179
|
+
|
|
169
180
|
rows = data.get("results", [])
|
|
170
181
|
if not rows:
|
|
171
182
|
console.print("[dim]No credit purchases yet. Run `tl credits buy --amount-usd N`.[/dim]")
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import time
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
|
+
from pytoon import encode as toon_encode
|
|
7
8
|
from rich.console import Console
|
|
8
9
|
from rich.panel import Panel
|
|
9
10
|
from rich.text import Text
|
|
@@ -320,6 +321,7 @@ def create_report(
|
|
|
320
321
|
),
|
|
321
322
|
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
|
|
322
323
|
json_output: bool = typer.Option(False, "--json", help="Output raw JSON config"),
|
|
324
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
323
325
|
timeout: int = typer.Option(300, "--timeout", help="Max orchestration time in seconds"),
|
|
324
326
|
) -> None:
|
|
325
327
|
"""Create a report from a natural-language prompt or a pre-built config.
|
|
@@ -366,14 +368,17 @@ def create_report(
|
|
|
366
368
|
saved_prompts = [prompt]
|
|
367
369
|
|
|
368
370
|
# --- Show preview ---
|
|
369
|
-
if json_output:
|
|
370
|
-
# Preview-only mode (--json without --yes): print the config and exit.
|
|
371
|
+
if json_output or toon_output:
|
|
372
|
+
# Preview-only mode (--json/--toon without --yes): print the config and exit.
|
|
371
373
|
# With --yes, skip the preview emit entirely so the only thing on
|
|
372
|
-
# stdout is the single save-response
|
|
373
|
-
# consumers can parse stdout with one
|
|
374
|
+
# stdout is the single save-response payload below — programmatic
|
|
375
|
+
# consumers can parse stdout with one parser call instead of having
|
|
374
376
|
# to split two documents.
|
|
375
377
|
if not yes:
|
|
376
|
-
|
|
378
|
+
if toon_output:
|
|
379
|
+
print(toon_encode(config))
|
|
380
|
+
else:
|
|
381
|
+
print(json.dumps(config, indent=2, default=str))
|
|
377
382
|
raise typer.Exit(0)
|
|
378
383
|
else:
|
|
379
384
|
err.print()
|
|
@@ -398,7 +403,9 @@ def create_report(
|
|
|
398
403
|
report_url = result.get("report_url", "")
|
|
399
404
|
campaign_id = result.get("campaign_id", "")
|
|
400
405
|
|
|
401
|
-
if
|
|
406
|
+
if toon_output:
|
|
407
|
+
print(toon_encode(data))
|
|
408
|
+
elif json_output:
|
|
402
409
|
print(json.dumps(data, indent=2, default=str))
|
|
403
410
|
else:
|
|
404
411
|
err.print()
|
|
@@ -5,6 +5,7 @@ import re
|
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
7
|
import yaml
|
|
8
|
+
from pytoon import encode as toon_encode
|
|
8
9
|
from rich.console import Console
|
|
9
10
|
from rich.markdown import Markdown
|
|
10
11
|
from rich.text import Text
|
|
@@ -79,11 +80,14 @@ def _try_render_yaml_tree(content: str) -> bool:
|
|
|
79
80
|
return rendered
|
|
80
81
|
|
|
81
82
|
|
|
82
|
-
def _show(db: str, json_output: bool, table: str | None = None) -> None:
|
|
83
|
+
def _show(db: str, json_output: bool, table: str | None = None, toon_output: bool = False) -> None:
|
|
83
84
|
client = get_client()
|
|
84
85
|
try:
|
|
85
86
|
params = {"table": table} if table else {}
|
|
86
87
|
data = client.get(f"/raw/{db}/schema", params=params)
|
|
88
|
+
if toon_output:
|
|
89
|
+
print(toon_encode(data))
|
|
90
|
+
return
|
|
87
91
|
if json_output:
|
|
88
92
|
print(json.dumps(data, indent=2, default=str))
|
|
89
93
|
return
|
|
@@ -108,6 +112,7 @@ def _show(db: str, json_output: bool, table: str | None = None) -> None:
|
|
|
108
112
|
def pg_cmd(
|
|
109
113
|
table: str = typer.Argument(None, help="Optional table name. When given, prints only that table's section in the same markdown format."),
|
|
110
114
|
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
115
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
111
116
|
) -> None:
|
|
112
117
|
"""Show PostgreSQL schema reference (for `tl db pg`).
|
|
113
118
|
|
|
@@ -123,13 +128,14 @@ def pg_cmd(
|
|
|
123
128
|
tl schema pg thoughtleaders_channel
|
|
124
129
|
tl schema pg thoughtleaders_adlink --json
|
|
125
130
|
"""
|
|
126
|
-
_show("pg", json_output, table=table)
|
|
131
|
+
_show("pg", json_output, table=table, toon_output=toon_output)
|
|
127
132
|
|
|
128
133
|
|
|
129
134
|
@app.command("fb")
|
|
130
135
|
def fb_cmd(
|
|
131
136
|
table: str = typer.Argument(None, help="Optional table name (`article_metrics` or `channel_metrics`). When given, prints only that table's section."),
|
|
132
137
|
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
138
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
133
139
|
) -> None:
|
|
134
140
|
"""Show Firebolt schema (live: tables and column types) for `tl db fb`.
|
|
135
141
|
|
|
@@ -144,12 +150,13 @@ def fb_cmd(
|
|
|
144
150
|
tl schema fb article_metrics
|
|
145
151
|
tl schema fb channel_metrics --json
|
|
146
152
|
"""
|
|
147
|
-
_show("fb", json_output, table=table)
|
|
153
|
+
_show("fb", json_output, table=table, toon_output=toon_output)
|
|
148
154
|
|
|
149
155
|
|
|
150
156
|
@app.command("es")
|
|
151
157
|
def es_cmd(
|
|
152
158
|
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
159
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
153
160
|
) -> None:
|
|
154
161
|
"""Show Elasticsearch document shape for `tl db es`."""
|
|
155
|
-
_show("es", json_output)
|
|
162
|
+
_show("es", json_output, toon_output=toon_output)
|
|
@@ -13,6 +13,7 @@ import subprocess
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
15
|
import typer
|
|
16
|
+
from pytoon import encode as toon_encode
|
|
16
17
|
from rich.console import Console
|
|
17
18
|
|
|
18
19
|
from tl_cli import __version__
|
|
@@ -172,6 +173,7 @@ def _print_manual_instructions() -> None:
|
|
|
172
173
|
@app.command("claude")
|
|
173
174
|
def setup_claude(
|
|
174
175
|
json_output: bool = typer.Option(False, "--json", help="JSON output (non-interactive)"),
|
|
176
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs, non-interactive)"),
|
|
175
177
|
) -> None:
|
|
176
178
|
"""Install the TL CLI plugin for Claude Code.
|
|
177
179
|
|
|
@@ -183,8 +185,8 @@ def setup_claude(
|
|
|
183
185
|
tl setup claude
|
|
184
186
|
tl setup claude --json
|
|
185
187
|
"""
|
|
186
|
-
if json_output:
|
|
187
|
-
_setup_noninteractive()
|
|
188
|
+
if json_output or toon_output:
|
|
189
|
+
_setup_noninteractive(fmt="toon" if toon_output else "json")
|
|
188
190
|
return
|
|
189
191
|
|
|
190
192
|
console.print()
|
|
@@ -283,8 +285,16 @@ def _install_standalone_skills_step(plugin_root: Path) -> None:
|
|
|
283
285
|
console.print(" [yellow]![/yellow] No skills found to install")
|
|
284
286
|
|
|
285
287
|
|
|
286
|
-
def
|
|
287
|
-
"""
|
|
288
|
+
def _emit_setup_result(result: dict, fmt: str) -> None:
|
|
289
|
+
"""Emit a setup-status dict in JSON (default) or TOON."""
|
|
290
|
+
if fmt == "toon":
|
|
291
|
+
print(toon_encode(result))
|
|
292
|
+
else:
|
|
293
|
+
print(json.dumps(result, indent=2))
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _setup_noninteractive(fmt: str = "json") -> None:
|
|
297
|
+
"""Non-interactive setup for --json / --toon / agent usage."""
|
|
288
298
|
result = {
|
|
289
299
|
"cli_version": __version__,
|
|
290
300
|
"marketplace_source": MARKETPLACE_SOURCE,
|
|
@@ -296,7 +306,7 @@ def _setup_noninteractive() -> None:
|
|
|
296
306
|
if plugin_root is None:
|
|
297
307
|
result["status"] = "error"
|
|
298
308
|
result["error"] = "Plugin assets not found"
|
|
299
|
-
|
|
309
|
+
_emit_setup_result(result, fmt)
|
|
300
310
|
raise SystemExit(1)
|
|
301
311
|
|
|
302
312
|
claude_bin = _find_claude_binary()
|
|
@@ -326,7 +336,7 @@ def _setup_noninteractive() -> None:
|
|
|
326
336
|
(version_dir / ".version").write_text(__version__)
|
|
327
337
|
|
|
328
338
|
result["status"] = "ok"
|
|
329
|
-
|
|
339
|
+
_emit_setup_result(result, fmt)
|
|
330
340
|
|
|
331
341
|
|
|
332
342
|
# --- OpenCode setup ---
|
|
@@ -375,6 +385,7 @@ def _setup_external_agent(
|
|
|
375
385
|
target_dir: Path,
|
|
376
386
|
post_install_lines: list[str] | None,
|
|
377
387
|
json_output: bool,
|
|
388
|
+
toon_output: bool = False,
|
|
378
389
|
) -> None:
|
|
379
390
|
"""Shared body for the OpenCode / Gemini / Codex setup commands.
|
|
380
391
|
|
|
@@ -388,19 +399,20 @@ def _setup_external_agent(
|
|
|
388
399
|
"""
|
|
389
400
|
plugin_root = _find_plugin_root()
|
|
390
401
|
|
|
391
|
-
if json_output:
|
|
402
|
+
if json_output or toon_output:
|
|
403
|
+
fmt = "toon" if toon_output else "json"
|
|
392
404
|
result: dict = {"cli_version": __version__}
|
|
393
405
|
if plugin_root is None:
|
|
394
406
|
result["status"] = "error"
|
|
395
407
|
result["error"] = "Plugin assets not found"
|
|
396
|
-
|
|
408
|
+
_emit_setup_result(result, fmt)
|
|
397
409
|
raise SystemExit(1)
|
|
398
410
|
result[f"{agent_binary}_detected"] = shutil.which(agent_binary) is not None
|
|
399
411
|
count = _install_skill_trees(plugin_root, target_dir)
|
|
400
412
|
result["skills_installed"] = count
|
|
401
413
|
result["install_dir"] = str(target_dir)
|
|
402
414
|
result["status"] = "ok"
|
|
403
|
-
|
|
415
|
+
_emit_setup_result(result, fmt)
|
|
404
416
|
return
|
|
405
417
|
|
|
406
418
|
console.print()
|
|
@@ -449,6 +461,7 @@ def _setup_external_agent(
|
|
|
449
461
|
@app.command("opencode")
|
|
450
462
|
def setup_opencode(
|
|
451
463
|
json_output: bool = typer.Option(False, "--json", help="JSON output (non-interactive)"),
|
|
464
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs, non-interactive)"),
|
|
452
465
|
) -> None:
|
|
453
466
|
"""Install the TL CLI skills for OpenCode.
|
|
454
467
|
|
|
@@ -469,6 +482,7 @@ def setup_opencode(
|
|
|
469
482
|
"The agent can use it when you ask about sponsorships, deals, channels, or brands.",
|
|
470
483
|
],
|
|
471
484
|
json_output=json_output,
|
|
485
|
+
toon_output=toon_output,
|
|
472
486
|
)
|
|
473
487
|
|
|
474
488
|
|
|
@@ -478,6 +492,7 @@ def setup_opencode(
|
|
|
478
492
|
@app.command("gemini")
|
|
479
493
|
def setup_gemini(
|
|
480
494
|
json_output: bool = typer.Option(False, "--json", help="JSON output (non-interactive)"),
|
|
495
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs, non-interactive)"),
|
|
481
496
|
) -> None:
|
|
482
497
|
"""Install the TL CLI skills for the Gemini CLI.
|
|
483
498
|
|
|
@@ -496,12 +511,14 @@ def setup_gemini(
|
|
|
496
511
|
target_dir=AGENTS_SKILLS_DIR,
|
|
497
512
|
post_install_lines=None,
|
|
498
513
|
json_output=json_output,
|
|
514
|
+
toon_output=toon_output,
|
|
499
515
|
)
|
|
500
516
|
|
|
501
517
|
|
|
502
518
|
@app.command("codex")
|
|
503
519
|
def setup_codex(
|
|
504
520
|
json_output: bool = typer.Option(False, "--json", help="JSON output (non-interactive)"),
|
|
521
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs, non-interactive)"),
|
|
505
522
|
) -> None:
|
|
506
523
|
"""Install the TL CLI skills for the Codex CLI.
|
|
507
524
|
|
|
@@ -520,4 +537,5 @@ def setup_codex(
|
|
|
520
537
|
target_dir=AGENTS_SKILLS_DIR,
|
|
521
538
|
post_install_lines=None,
|
|
522
539
|
json_output=json_output,
|
|
540
|
+
toon_output=toon_output,
|
|
523
541
|
)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import json
|
|
4
4
|
|
|
5
5
|
import typer
|
|
6
|
+
from pytoon import encode as toon_encode
|
|
6
7
|
from rich.console import Console
|
|
7
8
|
from rich.panel import Panel
|
|
8
9
|
from rich.table import Table
|
|
@@ -206,8 +207,7 @@ def whoami(
|
|
|
206
207
|
if fmt == "json":
|
|
207
208
|
print(json.dumps(data, indent=2, default=str))
|
|
208
209
|
elif fmt == "toon":
|
|
209
|
-
|
|
210
|
-
print(encode(data))
|
|
210
|
+
print(toon_encode(data))
|
|
211
211
|
elif fmt == "md":
|
|
212
212
|
_render_whoami_md(data)
|
|
213
213
|
else:
|
|
@@ -11,6 +11,7 @@ import json
|
|
|
11
11
|
import math
|
|
12
12
|
import sys
|
|
13
13
|
|
|
14
|
+
from pytoon import encode as toon_encode
|
|
14
15
|
from rich.console import Console
|
|
15
16
|
from rich.table import Table
|
|
16
17
|
|
|
@@ -332,16 +333,14 @@ def _output_markdown(results: list[dict], columns: list[str], column_types: dict
|
|
|
332
333
|
|
|
333
334
|
def _output_toon(results: list[dict], columns: list[str]) -> None:
|
|
334
335
|
"""TOON (Token-Oriented Object Notation) output for LLM consumption."""
|
|
335
|
-
from pytoon import encode
|
|
336
336
|
# Build column-filtered rows for uniform tabular encoding
|
|
337
337
|
rows = [{col: row.get(col) for col in columns} for row in results]
|
|
338
|
-
print(
|
|
338
|
+
print(toon_encode(rows))
|
|
339
339
|
|
|
340
340
|
|
|
341
341
|
def _output_toon_single(record: dict) -> None:
|
|
342
342
|
"""TOON output for a single detail record."""
|
|
343
|
-
|
|
344
|
-
print(encode(record))
|
|
343
|
+
print(toon_encode(record))
|
|
345
344
|
|
|
346
345
|
|
|
347
346
|
_RIGHT_ALIGN_COLS = {"price", "cost", "cpm"}
|
|
File without changes
|
{thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/.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
|
{thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl/references/business-glossary.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl/references/elasticsearch-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl/references/firebolt-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl/references/postgres-schema.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/skills/tl-keyword-research/scripts/probe.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
|
|
File without changes
|
{thoughtleaders_cli-0.6.51 → thoughtleaders_cli-0.6.52}/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
|