thoughtleaders-cli 0.6.4__tar.gz → 0.6.6__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.4 → thoughtleaders_cli-0.6.6}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/AGENTS.md +2 -2
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/PKG-INFO +6 -6
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/README.md +5 -5
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/agents/tl-analyst.md +2 -2
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/docs/architecture.md +2 -2
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/pyproject.toml +1 -1
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/skills/tl/SKILL.md +38 -21
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/skills/tl/references/elasticsearch-schema.md +2 -4
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/skills/tl/references/firebolt-schema.md +1 -1
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/__init__.py +1 -1
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/brands.py +2 -2
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/channels.py +4 -4
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/describe.py +11 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/recommender.py +77 -28
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/main.py +0 -60
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/uv.lock +1 -1
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/.gitignore +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/LICENSE +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/commands/tl-balance.md +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/commands/tl-reports.md +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/commands/tl-sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/commands/tl.md +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/skills/tl/references/business-glossary.md +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/skills/tl/references/postgres-schema.md +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/ask.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/comments.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/db.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/doctor.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/reports.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/schema.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/setup.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/snapshots.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/uploads.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/whoami.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/config.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/filters.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/output/formatter.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/src/tl_cli/self_update.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/tests/test_filters.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/tests/test_sponsorships.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Project Overview
|
|
2
2
|
|
|
3
|
-
**tl-cli** is a Python CLI for querying ThoughtLeaders sponsorship data (sponsorships, channels, brands, uploads, snapshots, reports,
|
|
3
|
+
**tl-cli** is a Python CLI for querying ThoughtLeaders sponsorship data (sponsorships, channels, brands, uploads, snapshots, reports, recommender). Built with Typer + Rich + httpx. Designed as an "agent-first tool" — the CLI handles structured commands and output, while the user's AI agent (Claude) provides intelligence.
|
|
4
4
|
|
|
5
5
|
# Architecture
|
|
6
6
|
|
|
@@ -20,7 +20,7 @@ When adding a new data command, follow this pattern. See `sponsorships.py` for t
|
|
|
20
20
|
|
|
21
21
|
`deals`, `matches`, and `proposals` are shortcut commands that delegate to sponsorships' `do_list`/`do_show`/`do_create` with a pre-set status filter. They reject explicit `status:` filters — users should use `tl sponsorships list` for finer-grained status filtering.
|
|
22
22
|
|
|
23
|
-
`recommender` (`commands/recommender.py`) wraps the
|
|
23
|
+
`recommender` (`commands/recommender.py`) wraps the recommender API at `/api/cli/v1/recommender/*` — `tags` (free), `top-channels` / `top-profiles` / `top-brands`, `inspect-channel`, `inspect-brand`, `similar-to-profile` (all 25 credits flat, Intelligence-gated). The three `top-*` URLs share one server resolver; `top-brands` dedupes the underlying profile rows by brand. Channel→channel and brand→brand similarity stay on `tl channels similar` / `tl brands similar`. When updating the SKILL or examples, prefer steering category/topic discovery (e.g. "Cooking channels") to `tl recommender top-channels "<tag>"` rather than `WHERE content_category = <code>` SQL — the recommender is ranked, not equality-based. The underlying recommender code uses "element"/"field_name" terminology; the CLI/API layer renames these to "tag" at the boundary.
|
|
24
24
|
|
|
25
25
|
## Filter Parsing (`filters.py`)
|
|
26
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtleaders-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.6
|
|
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
|
|
@@ -83,7 +83,7 @@ tl uploads show 1174310:0BehkmVa7ak
|
|
|
83
83
|
|
|
84
84
|
# Search channels via raw SQL — `tl db pg` against thoughtleaders_channel
|
|
85
85
|
# (run `tl schema pg` once to confirm the live column set).
|
|
86
|
-
# NOTE: For topic / category discovery, prefer the
|
|
86
|
+
# NOTE: For topic / category discovery, prefer the recommender over
|
|
87
87
|
# `content_category` equality — `tl recommender top-channels "<tag>"`
|
|
88
88
|
# returns channels ranked by how strongly they load on the topic, not just
|
|
89
89
|
# rows where the single category code matches exactly.
|
|
@@ -101,22 +101,22 @@ tl db pg "SELECT id, channel_name FROM thoughtleaders_channel
|
|
|
101
101
|
tl channels show 12345
|
|
102
102
|
tl channels show "Economics Explained"
|
|
103
103
|
|
|
104
|
-
# Find similar channels (
|
|
104
|
+
# Find similar channels (recommender, 25 credits, Intelligence plan).
|
|
105
105
|
# msn: is tri-state (default msn:yes): yes = MSN only, no = non-MSN only, both = no filter.
|
|
106
106
|
# tpp: is tri-state (default tpp:both): yes = TPP only, no = non-TPP only, both = no filter.
|
|
107
107
|
# Same ID-or-name resolution rules as `channels show`.
|
|
108
108
|
tl channels similar 12345 --limit 10
|
|
109
109
|
tl channels similar "Tremending girls" min-score:0.85 --limit 5
|
|
110
110
|
|
|
111
|
-
#
|
|
112
|
-
# `tags` is free; `top
|
|
111
|
+
# Recommender — discovery by category/demographic tag (Intelligence plan).
|
|
112
|
+
# `tags` is free; `top-*`, `inspect-*`, `similar-to-profile`, and `similar-brands-to-channel` cost 25 credits flat.
|
|
113
113
|
tl recommender tags # List every tag (free)
|
|
114
114
|
tl recommender tags cooking # Search tag names by substring
|
|
115
115
|
tl recommender top-channels "Cooking" msn:yes --limit 50 # Top channels for a tag
|
|
116
116
|
tl recommender top-profiles "Cooking" mbn:yes --limit 30 # Top brand profiles (one brand → potentially multiple profiles)
|
|
117
117
|
tl recommender top-brands "Cooking" --limit 30 # Top brands (deduped from profiles)
|
|
118
118
|
tl recommender inspect-channel 12345 # Per-tag breakdown of a channel's vector
|
|
119
|
-
tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal
|
|
119
|
+
tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal profile
|
|
120
120
|
tl recommender similar-to-profile 842 # Channels closest to a brand profile
|
|
121
121
|
|
|
122
122
|
# Brand intelligence
|
|
@@ -56,7 +56,7 @@ tl uploads show 1174310:0BehkmVa7ak
|
|
|
56
56
|
|
|
57
57
|
# Search channels via raw SQL — `tl db pg` against thoughtleaders_channel
|
|
58
58
|
# (run `tl schema pg` once to confirm the live column set).
|
|
59
|
-
# NOTE: For topic / category discovery, prefer the
|
|
59
|
+
# NOTE: For topic / category discovery, prefer the recommender over
|
|
60
60
|
# `content_category` equality — `tl recommender top-channels "<tag>"`
|
|
61
61
|
# returns channels ranked by how strongly they load on the topic, not just
|
|
62
62
|
# rows where the single category code matches exactly.
|
|
@@ -74,22 +74,22 @@ tl db pg "SELECT id, channel_name FROM thoughtleaders_channel
|
|
|
74
74
|
tl channels show 12345
|
|
75
75
|
tl channels show "Economics Explained"
|
|
76
76
|
|
|
77
|
-
# Find similar channels (
|
|
77
|
+
# Find similar channels (recommender, 25 credits, Intelligence plan).
|
|
78
78
|
# msn: is tri-state (default msn:yes): yes = MSN only, no = non-MSN only, both = no filter.
|
|
79
79
|
# tpp: is tri-state (default tpp:both): yes = TPP only, no = non-TPP only, both = no filter.
|
|
80
80
|
# Same ID-or-name resolution rules as `channels show`.
|
|
81
81
|
tl channels similar 12345 --limit 10
|
|
82
82
|
tl channels similar "Tremending girls" min-score:0.85 --limit 5
|
|
83
83
|
|
|
84
|
-
#
|
|
85
|
-
# `tags` is free; `top
|
|
84
|
+
# Recommender — discovery by category/demographic tag (Intelligence plan).
|
|
85
|
+
# `tags` is free; `top-*`, `inspect-*`, `similar-to-profile`, and `similar-brands-to-channel` cost 25 credits flat.
|
|
86
86
|
tl recommender tags # List every tag (free)
|
|
87
87
|
tl recommender tags cooking # Search tag names by substring
|
|
88
88
|
tl recommender top-channels "Cooking" msn:yes --limit 50 # Top channels for a tag
|
|
89
89
|
tl recommender top-profiles "Cooking" mbn:yes --limit 30 # Top brand profiles (one brand → potentially multiple profiles)
|
|
90
90
|
tl recommender top-brands "Cooking" --limit 30 # Top brands (deduped from profiles)
|
|
91
91
|
tl recommender inspect-channel 12345 # Per-tag breakdown of a channel's vector
|
|
92
|
-
tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal
|
|
92
|
+
tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal profile
|
|
93
93
|
tl recommender similar-to-profile 842 # Channels closest to a brand profile
|
|
94
94
|
|
|
95
95
|
# Brand intelligence
|
|
@@ -16,7 +16,7 @@ For anything beyond a trivially simple lookup, write a single raw query against
|
|
|
16
16
|
- **Elasticsearch (`tl db es`)** — transcript / brand-mention text search, video-level aggregations, demographic country-share filters that compose with content predicates.
|
|
17
17
|
- **Firebolt (`tl db fb`)** — custom time-series shapes (multi-channel growth comparisons, milestone-age slices). For default shapes, prefer `tl snapshots`.
|
|
18
18
|
|
|
19
|
-
Reserve structured commands for: single-record `show` by ID, plain filtered `list` with one or two filters that the structured vocabulary already supports, `tl channels similar` / `tl brands similar` (
|
|
19
|
+
Reserve structured commands for: single-record `show` by ID, plain filtered `list` with one or two filters that the structured vocabulary already supports, `tl channels similar` / `tl brands similar` (similarity search), `tl reports run`, and `tl snapshots`.
|
|
20
20
|
|
|
21
21
|
One raw query beats N paginated structured walks stitched in `jq`/Python — on cost, latency, and the ES `from+size = 10000` cap.
|
|
22
22
|
|
|
@@ -69,7 +69,7 @@ Then suggest `tl comments add <id> "..."` for each.
|
|
|
69
69
|
### Multi-step research (mix raw + similarity)
|
|
70
70
|
"Find channels similar to the ones Nike sponsors and compare their pricing"
|
|
71
71
|
1. `tl db pg` to find the top channels Nike has sponsored (one aggregation, ranked).
|
|
72
|
-
2. `tl channels similar <top-channel-id> --json --limit 20` per seed —
|
|
72
|
+
2. `tl channels similar <top-channel-id> --json --limit 20` per seed — similarity search is server-side and has no SQL equivalent. The `msn:` filter is tri-state with default `msn:yes` (MSN channels only); use `msn:both` to broaden, `msn:no` for non-MSN only.
|
|
73
73
|
3. Union + dedupe + compile comparison table.
|
|
74
74
|
|
|
75
75
|
### Report comparison (saved reports)
|
|
@@ -50,10 +50,10 @@ All data commands use explicit subcommands: `list`, `show`, `create`/`add`. Runn
|
|
|
50
50
|
| `tl uploads show <id> [<id>...]` | Show upload detail(s) by ID |
|
|
51
51
|
| `tl channels show <id-or-name>` | Channel detail, including active adspots with price/cost/CPM |
|
|
52
52
|
| `tl channels history <id-or-name>` | Sponsorship history (videos with detected sponsors) |
|
|
53
|
-
| `tl channels similar <id-or-name>` |
|
|
53
|
+
| `tl channels similar <id-or-name>` | Similarity recommender. 25 credits; Intelligence plan. Tri-state `msn:` (default `yes`) and `tpp:` (default `both`) filters. Ambiguous names return 400 + candidates list. Hidden `look-alike` alias. |
|
|
54
54
|
| `tl brands show <brand>` | Brand intelligence report |
|
|
55
55
|
| `tl brands history <brand> [--channel <id>]` | Brand sponsorship history; videos where the brand was detected |
|
|
56
|
-
| `tl brands similar <brand>` | Find similar brands (
|
|
56
|
+
| `tl brands similar <brand>` | Find similar brands (similarity search, 25 credits) |
|
|
57
57
|
| `tl snapshots channel <id>` | Channel metrics over time (Firebolt channel_metrics) |
|
|
58
58
|
| `tl snapshots video <id> --channel <id>` | Video view curve (Firebolt article_metrics, --channel required) |
|
|
59
59
|
| `tl comments list <adlink-id>` | List comments on a sponsorship (free) |
|
|
@@ -95,11 +95,28 @@ When querying sponsorship bookings, query by `status:sold` and filter the the da
|
|
|
95
95
|
|
|
96
96
|
Where possible, if searching for a sponsorship match between channels and brands, first search for what do similar brands sponsor / which brands is the channel usually sponsored by. The similarity judgement should be preferably based on similar topics, similar upload frequency, similar channel sizes, and only after all that, on demographics.
|
|
97
97
|
|
|
98
|
-
Use the `tl channels similar` and `tl brands similar` commands to explore 1:1 similarity between known channels or brands. For category- or topic-driven discovery (e.g. "find me Cooking channels", "who scores high on USA share?"), use `tl recommender top-channels "<tag>"` (or `top-brands`/`top-profiles`) against the
|
|
98
|
+
Use the `tl channels similar` and `tl brands similar` commands to explore 1:1 similarity between known channels or brands. For category- or topic-driven discovery (e.g. "find me Cooking channels", "who scores high on USA share?"), use `tl recommender top-channels "<tag>"` (or `top-brands`/`top-profiles`) against the recommender — that's faster, ranked by category-strength. Run `tl recommender tags` to discover the valid tag names.
|
|
99
99
|
|
|
100
100
|
## Workflow
|
|
101
101
|
|
|
102
|
-
At the start of session, always run
|
|
102
|
+
At the start of session, always run `tl --help` to find out which command groups are available, and `tl whoami` to find out what you have access to.
|
|
103
|
+
|
|
104
|
+
### How to discover commands and subcommands
|
|
105
|
+
|
|
106
|
+
The CLI exposes three different discovery surfaces — pick by what you actually need:
|
|
107
|
+
|
|
108
|
+
| You want to know… | Run |
|
|
109
|
+
|---|---|
|
|
110
|
+
| Top-level command groups (`sponsorships`, `channels`, `db`, `recommender`, etc.) | `tl --help` |
|
|
111
|
+
| Subcommands of a group (`tl recommender` → `tags`, `top-channels`, `inspect-brand`, …) | `tl <group> --help` (e.g. `tl recommender --help`, `tl db --help`) |
|
|
112
|
+
| Arguments and flags for a specific leaf command | `tl <group> <subcommand> --help` (e.g. `tl recommender top-channels --help`) |
|
|
113
|
+
| Fields, filters, credit rates for a **data resource** (sponsorships, uploads, snapshots, reports, comments, recommender) | `tl describe show <resource> --json` |
|
|
114
|
+
| The live PG/ES/Firebolt schema for raw `tl db` queries | `tl schema pg` / `tl schema es` / `tl schema fb` |
|
|
115
|
+
|
|
116
|
+
Notes:
|
|
117
|
+
- Use `--help` everywhere — there is no separate `tl help` command. `tl help` returns "No such command 'help'".
|
|
118
|
+
- **`tl describe show channels`** and **`tl describe show brands`** intentionally do not list fields/filters — channel and brand search live in raw SQL (`tl db pg`) and the recommender, not in a structured list endpoint. They print a notice steering you there.
|
|
119
|
+
- `--help` describes **CLI shape**; `tl describe` describes **data shape**. They don't overlap.
|
|
103
120
|
|
|
104
121
|
Unless the user specifically asks for running a specific report or showing the result of a specific report, find the data by using other, low-level commands.
|
|
105
122
|
|
|
@@ -132,18 +149,18 @@ tl uploads list [filters...] # Video uploads from ES — list curve, m
|
|
|
132
149
|
tl uploads show <id> # Upload detail (2 credits)
|
|
133
150
|
tl channels show <id-or-name> # Channel detail (2 credits; accepts numeric ID or name) — for channel search use raw SQL on thoughtleaders_channel
|
|
134
151
|
tl channels history <id-or-name> # Sponsorship history (5 credits/result, linear)
|
|
135
|
-
tl channels similar <id-or-name> #
|
|
152
|
+
tl channels similar <id-or-name> # Similarity recommender (25 credits flat; Intelligence plan)
|
|
136
153
|
tl brands show <id-or-name> # Brand detail (1 credit)
|
|
137
154
|
tl brands history <id-or-name> # Sponsorship history (5 credits/result, linear)
|
|
138
155
|
tl brands history <query> --channel <id> # Brand mentions on specific channel
|
|
139
|
-
tl brands similar <id-or-name> # Find similar brands via
|
|
140
|
-
tl recommender tags [query] # List
|
|
141
|
-
tl recommender top-channels "<tag>" # Top channels loaded on a
|
|
142
|
-
tl recommender top-profiles "<tag>" # Top brand profiles loaded on a
|
|
143
|
-
tl recommender top-brands "<tag>" # Top brands (deduped from profiles) loaded on a
|
|
144
|
-
tl recommender inspect-channel <ref> # Show a channel's
|
|
145
|
-
tl recommender inspect-brand <ref> # Show a brand profile's ideal-
|
|
146
|
-
tl recommender similar-to-profile <id> # Channels closest to a brand profile's ideal
|
|
156
|
+
tl brands similar <id-or-name> # Find similar brands via similarity search (25 credits flat)
|
|
157
|
+
tl recommender tags [query] # List similarity tag names — categories, demographics, formats (free)
|
|
158
|
+
tl recommender top-channels "<tag>" # Top channels loaded on a similarity tag (25 credits; Intelligence)
|
|
159
|
+
tl recommender top-profiles "<tag>" # Top brand profiles loaded on a similarity tag (25 credits)
|
|
160
|
+
tl recommender top-brands "<tag>" # Top brands (deduped from profiles) loaded on a similarity tag (25 credits)
|
|
161
|
+
tl recommender inspect-channel <ref> # Show a channel's similarity-profile breakdown (25 credits; Intelligence)
|
|
162
|
+
tl recommender inspect-brand <ref> # Show a brand profile's ideal similarity-profile breakdown (25 credits; Intelligence)
|
|
163
|
+
tl recommender similar-to-profile <id> # Channels closest to a brand profile's ideal profile (25 credits; Intelligence)
|
|
147
164
|
tl snapshots channel <id> # Channel metrics over time — list curve, mult 1.2 (Firebolt-backed)
|
|
148
165
|
tl snapshots video <id> --channel <id> # Video view curve — list curve, mult 1.2 (--channel required!)
|
|
149
166
|
tl reports # List saved reports — list curve, mult 1.3
|
|
@@ -196,7 +213,7 @@ Structured commands are still the right tool for: single-record `show` by ID, pl
|
|
|
196
213
|
| Custom Firebolt shape (milestone-age slices, multi-channel growth comparisons) | **`tl db fb`** |
|
|
197
214
|
| Single-record detail lookup by ID | `tl <resource> show <id>` |
|
|
198
215
|
| Plain filtered list with one or two simple filters | `tl <resource> list` |
|
|
199
|
-
| Channel/brand similarity (
|
|
216
|
+
| Channel/brand similarity (server-implemented similarity search) | `tl channels similar`, `tl brands similar` |
|
|
200
217
|
| Saved reports | `tl reports`, `tl reports run` |
|
|
201
218
|
| Time-series view-curve / channel growth (default shape with interpolation) | `tl snapshots channel`, `tl snapshots video` |
|
|
202
219
|
|
|
@@ -289,7 +306,7 @@ See [references/business-glossary.md](references/business-glossary.md) for reven
|
|
|
289
306
|
| **AdLink INSERT** with custom price/cost/owner/`weighted_price`/`created_where` | **Unavailable** — `tl sponsorships create` exists but only creates a free *proposal* between a channel and a brand. The `tl db pg` sanitizer accepts SELECT only — no INSERT/UPDATE. | Done in the app or by a human with DB access. |
|
|
290
307
|
| Pre-insert validation queries (joining `adspot ↔ channel ↔ profile ↔ org` to confirm MSN, integration=1, persona, plan) | **Available** via `tl db pg`. | One SELECT joining the four tables. Use `thoughtleaders_channel.media_selling_network_join_date IS NOT NULL` for MSN, `thoughtleaders_adspot.integration = 1` for mention adspots, `thoughtleaders_profile.persona` for the persona code (see persona constants in `references/postgres-schema.md`). |
|
|
291
308
|
| Firebolt cross-table or join queries; filtering on non-indexed columns in WHERE | **Unavailable** — not accepted. | Fetch a wider slice keyed on `channel_id` (and optionally `id`), filter the rest in `jq`/Python. |
|
|
292
|
-
| ES `query_string`, `regexp`, `wildcard`, `fuzzy`, `more_like_this`, parent/child joins; any `script_*`; multiple aggregations in one body | **Unavailable** — not accepted. | Rewrite using `term`/`terms`/`match`/`bool`/`nested`. For multi-agg dashboards, run multiple `tl db es` calls and combine client-side. For "similar"-style queries, try `tl channels similar` / `tl brands similar` (
|
|
309
|
+
| ES `query_string`, `regexp`, `wildcard`, `fuzzy`, `more_like_this`, parent/child joins; any `script_*`; multiple aggregations in one body | **Unavailable** — not accepted. | Rewrite using `term`/`terms`/`match`/`bool`/`nested`. For multi-agg dashboards, run multiple `tl db es` calls and combine client-side. For "similar"-style queries, try `tl channels similar` / `tl brands similar` (server-implemented similarity search). |
|
|
293
310
|
| ES deep pagination beyond `from+size = 10,000` | **Unavailable** via raw — `scroll` and `pit` aren't allowlisted; `search_after` is allowed but `from` is still capped. | Use `search_after` with `sort` to walk past 10k. For huge sweeps, narrow with `publication_date` ranges. |
|
|
294
311
|
| ES index introspection (`_cat/indices`, mappings) | **Unavailable** — only `_search` is wired. | Read [references/elasticsearch-schema.md](references/elasticsearch-schema.md). It's manually maintained — update it when you discover new fields. |
|
|
295
312
|
| Schema introspection on Postgres (`information_schema.columns`, `pg_class`, …) | **Partial** — catalog-resolving casts and many `pg_*` helpers are blocked. | Use `tl schema pg` for the live table/column listing, or read [references/postgres-schema.md](references/postgres-schema.md). |
|
|
@@ -325,14 +342,14 @@ Date filters accept keywords: `today`, `yesterday`, `tomorrow`.
|
|
|
325
342
|
|
|
326
343
|
#### Channel discovery — recommender first, raw SQL second
|
|
327
344
|
|
|
328
|
-
For category- or demographic-driven discovery, **use the
|
|
345
|
+
For category- or demographic-driven discovery, **use the recommender, not `content_category` SQL.** The recommender ranks channels by how strongly they load on a category/demographic tag (similarity scores), instead of forcing exact equality on a single integer code. It also returns the matching brand profiles alongside the channels — useful when the user actually wants to know "who buys this kind of inventory."
|
|
329
346
|
|
|
330
347
|
```bash
|
|
331
348
|
# Discover the right tag name first (free)
|
|
332
349
|
tl recommender tags cooking
|
|
333
350
|
tl recommender tags "usa"
|
|
334
351
|
|
|
335
|
-
# Top channels & profiles loaded on a
|
|
352
|
+
# Top channels & profiles loaded on a similarity tag (25 credits; Intelligence)
|
|
336
353
|
tl recommender top-channels "Cooking" msn:yes --limit 50
|
|
337
354
|
tl recommender top-channels "Tech" --limit 30
|
|
338
355
|
tl recommender top-brands "USA share" mbn:yes --limit 50
|
|
@@ -434,7 +451,7 @@ tl reports run 42 --json
|
|
|
434
451
|
|
|
435
452
|
"Find Cooking channels with US-heavy mobile audiences":
|
|
436
453
|
```bash
|
|
437
|
-
# Use the
|
|
454
|
+
# Use the recommender for the topic, then narrow with structured filters / SQL on the IDs.
|
|
438
455
|
tl recommender top-channels "Cooking" msn:yes --limit 100 --json \
|
|
439
456
|
| jq -r '.results[].channel_id' \
|
|
440
457
|
| paste -sd, - \
|
|
@@ -452,7 +469,7 @@ tl recommender top-channels "Cooking" msn:yes --limit 100 --json \
|
|
|
452
469
|
tl sponsorships list status:sold primary-device:mobile min-us-share:60 --json
|
|
453
470
|
```
|
|
454
471
|
|
|
455
|
-
"Find channels similar to one I know" (
|
|
472
|
+
"Find channels similar to one I know" (similarity recommender, 25 credits per call):
|
|
456
473
|
```bash
|
|
457
474
|
tl channels similar 29834 --limit 10 # by ID (defaults to msn:yes, tpp:both)
|
|
458
475
|
tl channels similar "Tremending girls" --limit 5 # by unique name
|
|
@@ -464,16 +481,16 @@ tl channels similar 29834 min-subs:1000000 exclude:477487 --limit 15 # client-s
|
|
|
464
481
|
```
|
|
465
482
|
**Both `tl channels show` and `tl channels similar` accept either a numeric channel ID or a channel name.** Name arguments are case-insensitive partial matches; if more than one active channel matches, the command prints a candidates table (channel_id, subscribers, name) and exits 1 so you can retry with a specific ID. The `msn` filter on `similar` is tri-state: `yes` (only MSN channels — the default), `no` (only non-MSN channels), `both` (no MSN filter). `tl channels look-alike` is a hidden alias for `similar` that matches the internal "look-alike channels" terminology.
|
|
466
483
|
|
|
467
|
-
"Browse the
|
|
484
|
+
"Browse the recommender" (categories, demographics, formats — `tl recommender tags` is free):
|
|
468
485
|
```bash
|
|
469
486
|
tl recommender tags # Full tag list (free)
|
|
470
487
|
tl recommender tags cooking # Search tag names by substring
|
|
471
|
-
tl recommender top-channels "Cooking" msn:yes --limit 50 # Top channels loaded on a tag (
|
|
488
|
+
tl recommender top-channels "Cooking" msn:yes --limit 50 # Top channels loaded on a tag (25 credits)
|
|
472
489
|
tl recommender top-profiles "Cooking" --limit 30 # Top brand profiles for the tag
|
|
473
490
|
tl recommender top-brands "USA share" mbn:yes --limit 30 # Top brands (deduped) — demographic tag, MBN only
|
|
474
491
|
tl recommender top-channels "Tech" exclude-for-profile:842 # Drop channels already proposed for profile 842
|
|
475
492
|
tl recommender inspect-channel 29834 # Per-tag breakdown of a channel's vector
|
|
476
|
-
tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal
|
|
477
|
-
tl recommender similar-to-profile 842 --limit 30 # Channels closest to a brand profile's ideal
|
|
493
|
+
tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal profile
|
|
494
|
+
tl recommender similar-to-profile 842 --limit 30 # Channels closest to a brand profile's ideal profile
|
|
478
495
|
```
|
|
479
496
|
Use `tl recommender top` for category/topic discovery (it's ranked) and `tl channels similar` / `tl brands similar` for 1:1 lookalike searches.
|
{thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/skills/tl/references/elasticsearch-schema.md
RENAMED
|
@@ -158,11 +158,9 @@ The full table below applies to **channel parent docs only**:
|
|
|
158
158
|
### Other indices
|
|
159
159
|
|
|
160
160
|
- `tl-ingest` — ingestion queue. **Don't query.** Internal pipeline state.
|
|
161
|
-
- `tl-
|
|
161
|
+
- `tl-similarity-profiles-channel`, `tl-similarity-profiles-channel-profile` — channel similarity vectors.
|
|
162
162
|
- `tl-vectors-brand-company-descriptions-*` — brand similarity vectors.
|
|
163
|
-
- `tl-vectors-channel-audience-*`, `tl-vectors-channel-topic-descriptions-*`, `tl-vectors-channel-features` — channel
|
|
164
|
-
|
|
165
|
-
Note: `knn` queries against vector indices are **not currently accepted** as a top-level key. For "find similar" results, use `tl channels similar` / `tl brands similar` — they wrap the vector search server-side.
|
|
163
|
+
- `tl-vectors-channel-audience-*`, `tl-vectors-channel-topic-descriptions-*`, `tl-vectors-channel-features` — channel similarity profiles.
|
|
166
164
|
|
|
167
165
|
## Common Query Patterns
|
|
168
166
|
|
{thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/skills/tl/references/firebolt-schema.md
RENAMED
|
@@ -116,7 +116,7 @@ Every Firebolt workflow has two steps:
|
|
|
116
116
|
**Step 1 — get `channel_id` and (optionally) video IDs from PG/ES.**
|
|
117
117
|
|
|
118
118
|
```bash
|
|
119
|
-
# Channels matching some category (
|
|
119
|
+
# Channels matching some category (recommender — preferred over content_category equality)
|
|
120
120
|
tl recommender top-channels "Tech" msn:yes --limit 50 --json \
|
|
121
121
|
| jq '.results[].channel_id'
|
|
122
122
|
|
|
@@ -125,7 +125,7 @@ SIMILAR_COLUMN_CONFIG = {
|
|
|
125
125
|
|
|
126
126
|
|
|
127
127
|
def _format_score(results: list[dict]) -> list[dict]:
|
|
128
|
-
"""Convert raw
|
|
128
|
+
"""Convert raw similarity score (0.0-1.0) to percentage string."""
|
|
129
129
|
for row in results:
|
|
130
130
|
score = row.get("score")
|
|
131
131
|
if isinstance(score, (int, float)):
|
|
@@ -144,7 +144,7 @@ def similar_cmd(
|
|
|
144
144
|
) -> None:
|
|
145
145
|
"""Find brands similar to a given one (by ID or name).
|
|
146
146
|
|
|
147
|
-
Costs
|
|
147
|
+
Costs 25 credits per call. Intelligence plan required.
|
|
148
148
|
|
|
149
149
|
Examples:
|
|
150
150
|
tl brands similar Nike
|
|
@@ -93,7 +93,7 @@ def _handle_channel_api_error(e: ApiError) -> None:
|
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
def _format_score(results: list[dict]) -> list[dict]:
|
|
96
|
-
"""Convert raw
|
|
96
|
+
"""Convert raw similarity score (0.0-1.0) to percentage string for table/csv/md."""
|
|
97
97
|
for row in results:
|
|
98
98
|
score = row.get("score")
|
|
99
99
|
if isinstance(score, (int, float)):
|
|
@@ -171,14 +171,14 @@ def similar_cmd(
|
|
|
171
171
|
) -> None:
|
|
172
172
|
"""Find channels similar to a given one (by id or name).
|
|
173
173
|
|
|
174
|
-
Costs
|
|
175
|
-
ranked by
|
|
174
|
+
Costs 25 credits per call. Intelligence plan required. Results are
|
|
175
|
+
ranked by similarity and enriched with subscribers, impression,
|
|
176
176
|
total_views, category, and the channel's representative CPM.
|
|
177
177
|
|
|
178
178
|
Server-side filters (pushed to the recommender):
|
|
179
179
|
language:<iso> Restrict to a content language (default: en)
|
|
180
180
|
msn:<true|false> Restrict to Media Selling Network (default: true)
|
|
181
|
-
min-score:<0-1> Minimum
|
|
181
|
+
min-score:<0-1> Minimum similarity (default: 0.5)
|
|
182
182
|
|
|
183
183
|
Client-side post-filters (applied after fetch):
|
|
184
184
|
category:<code> Keep only rows matching this content_category
|
|
@@ -67,6 +67,17 @@ def show_cmd(
|
|
|
67
67
|
"""
|
|
68
68
|
fmt = detect_format(json_output, False, False, toon_output)
|
|
69
69
|
|
|
70
|
+
if resource in {"channels", "brands"}:
|
|
71
|
+
notice = (
|
|
72
|
+
f"Examine the database schema with `tl schema pg` and then perform "
|
|
73
|
+
f"a `tl db pg` query on {resource}."
|
|
74
|
+
)
|
|
75
|
+
if fmt == "json":
|
|
76
|
+
print(json.dumps({"resource": resource, "notice": notice}, indent=2))
|
|
77
|
+
else:
|
|
78
|
+
console.print(notice)
|
|
79
|
+
return
|
|
80
|
+
|
|
70
81
|
client = get_client()
|
|
71
82
|
try:
|
|
72
83
|
data = client.get(f"/describe/{resource}")
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
"""tl recommender —
|
|
1
|
+
"""tl recommender — Recommender introspection and discovery.
|
|
2
2
|
|
|
3
|
-
Surfaces the channel/profile
|
|
4
|
-
"Recommender Insights" web view: list the
|
|
5
|
-
demographics, formats, etc.), find the top channels and profiles
|
|
6
|
-
on a given tag, inspect a single channel or brand
|
|
7
|
-
channels similar to a brand
|
|
3
|
+
Surfaces the channel/profile similarity machinery that powers the
|
|
4
|
+
"Recommender Insights" web view: list the similarity tags (categories,
|
|
5
|
+
demographics, formats, etc.), find the top channels and profiles
|
|
6
|
+
scoring high on a given tag, inspect a single channel or brand
|
|
7
|
+
similarity profile, fetch channels similar to a brand's ideal profile,
|
|
8
|
+
or fetch brands likely to sponsor a given channel.
|
|
8
9
|
|
|
9
10
|
For 1:1 similarity use `tl channels similar` and `tl brands similar`.
|
|
10
11
|
"""
|
|
@@ -19,7 +20,7 @@ from tl_cli.client.http import get_client
|
|
|
19
20
|
from tl_cli.filters import parse_filters
|
|
20
21
|
from tl_cli.output.formatter import detect_format, output, output_single
|
|
21
22
|
|
|
22
|
-
app = typer.Typer(help="
|
|
23
|
+
app = typer.Typer(help="Recommender (similarity tags, top-channels/profiles/brands, similarity-profile inspection, profile→channel and channel→brand similarity)")
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
TOP_CHANNEL_COLUMNS = ["value", "channel_id", "channel_name", "slug"]
|
|
@@ -46,7 +47,7 @@ def _handle_recommender_error(e: ApiError) -> None:
|
|
|
46
47
|
|
|
47
48
|
@app.callback(invoke_without_command=True)
|
|
48
49
|
def recommender(ctx: typer.Context) -> None:
|
|
49
|
-
"""
|
|
50
|
+
"""Recommender."""
|
|
50
51
|
if ctx.invoked_subcommand is None:
|
|
51
52
|
typer.echo(ctx.get_help())
|
|
52
53
|
|
|
@@ -59,10 +60,10 @@ def tags_cmd(
|
|
|
59
60
|
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
60
61
|
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
61
62
|
) -> None:
|
|
62
|
-
"""List
|
|
63
|
+
"""List similarity tag names (free).
|
|
63
64
|
|
|
64
65
|
Use this to discover the tag names accepted by `tl recommender top`.
|
|
65
|
-
Each tag is one
|
|
66
|
+
Each tag is one signal in a channel or brand similarity profile —
|
|
66
67
|
e.g. content categories like "Cooking", demographic buckets like
|
|
67
68
|
"Age 18-24", device shares, country shares.
|
|
68
69
|
|
|
@@ -81,7 +82,7 @@ def tags_cmd(
|
|
|
81
82
|
data,
|
|
82
83
|
fmt,
|
|
83
84
|
columns=["group", "field_name"],
|
|
84
|
-
title="
|
|
85
|
+
title="Similarity tags",
|
|
85
86
|
)
|
|
86
87
|
except ApiError as e:
|
|
87
88
|
handle_api_error(e)
|
|
@@ -127,7 +128,7 @@ def _do_top(kind: str, tag: str, args: list[str], fmt: str, limit: int, columns:
|
|
|
127
128
|
|
|
128
129
|
@app.command("top-channels")
|
|
129
130
|
def top_channels_cmd(
|
|
130
|
-
tag: str = typer.Argument(..., help='
|
|
131
|
+
tag: str = typer.Argument(..., help='Similarity tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
|
|
131
132
|
args: list[str] = typer.Argument(None, help="Filters (key:value pairs)."),
|
|
132
133
|
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
133
134
|
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
@@ -135,9 +136,9 @@ def top_channels_cmd(
|
|
|
135
136
|
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
136
137
|
limit: int = typer.Option(50, "--limit", "-l", help="Max results (1-100)"),
|
|
137
138
|
) -> None:
|
|
138
|
-
"""Top channels
|
|
139
|
+
"""Top channels scoring high on a single similarity tag.
|
|
139
140
|
|
|
140
|
-
Costs
|
|
141
|
+
Costs 25 credits per call. Intelligence plan required.
|
|
141
142
|
|
|
142
143
|
Filters:
|
|
143
144
|
msn:<yes|no|all> MSN membership (default: all)
|
|
@@ -154,7 +155,7 @@ def top_channels_cmd(
|
|
|
154
155
|
|
|
155
156
|
@app.command("top-profiles")
|
|
156
157
|
def top_profiles_cmd(
|
|
157
|
-
tag: str = typer.Argument(..., help='
|
|
158
|
+
tag: str = typer.Argument(..., help='Similarity tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
|
|
158
159
|
args: list[str] = typer.Argument(None, help="Filters (key:value pairs)."),
|
|
159
160
|
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
160
161
|
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
@@ -162,9 +163,9 @@ def top_profiles_cmd(
|
|
|
162
163
|
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
163
164
|
limit: int = typer.Option(50, "--limit", "-l", help="Max results (1-100)"),
|
|
164
165
|
) -> None:
|
|
165
|
-
"""Top brand profiles
|
|
166
|
+
"""Top brand profiles scoring high on a single similarity tag.
|
|
166
167
|
|
|
167
|
-
Costs
|
|
168
|
+
Costs 25 credits per call. Intelligence plan required. Profiles can
|
|
168
169
|
represent the same brand more than once (one brand → multiple
|
|
169
170
|
profiles); use `top-brands` for brand-deduplicated results.
|
|
170
171
|
|
|
@@ -182,7 +183,7 @@ def top_profiles_cmd(
|
|
|
182
183
|
|
|
183
184
|
@app.command("top-brands")
|
|
184
185
|
def top_brands_cmd(
|
|
185
|
-
tag: str = typer.Argument(..., help='
|
|
186
|
+
tag: str = typer.Argument(..., help='Similarity tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
|
|
186
187
|
args: list[str] = typer.Argument(None, help="Filters (key:value pairs)."),
|
|
187
188
|
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
188
189
|
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
@@ -190,9 +191,9 @@ def top_brands_cmd(
|
|
|
190
191
|
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
191
192
|
limit: int = typer.Option(50, "--limit", "-l", help="Max results (1-100)"),
|
|
192
193
|
) -> None:
|
|
193
|
-
"""Top brands
|
|
194
|
+
"""Top brands scoring high on a single similarity tag (deduplicated from profiles).
|
|
194
195
|
|
|
195
|
-
Costs
|
|
196
|
+
Costs 25 credits per call. Intelligence plan required. Server-side
|
|
196
197
|
aggregates the underlying profile rows by brand, keeping the
|
|
197
198
|
highest-scoring profile per brand.
|
|
198
199
|
|
|
@@ -216,10 +217,10 @@ def inspect_channel_cmd(
|
|
|
216
217
|
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
217
218
|
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
218
219
|
) -> None:
|
|
219
|
-
"""Show a channel's
|
|
220
|
+
"""Show a channel's similarity profile grouped by category.
|
|
220
221
|
|
|
221
|
-
Costs
|
|
222
|
-
|
|
222
|
+
Costs 25 credits per call. Intelligence plan required. Returns the
|
|
223
|
+
active similarity tags grouped by category, plus the overall strength.
|
|
223
224
|
|
|
224
225
|
Examples:
|
|
225
226
|
tl recommender inspect-channel 12345
|
|
@@ -245,11 +246,11 @@ def inspect_brand_cmd(
|
|
|
245
246
|
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
246
247
|
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
247
248
|
) -> None:
|
|
248
|
-
"""Show a brand profile's ideal
|
|
249
|
+
"""Show a brand profile's ideal similarity profile grouped by category.
|
|
249
250
|
|
|
250
|
-
Costs
|
|
251
|
+
Costs 25 credits per call. Intelligence plan required. Resolves the
|
|
251
252
|
brand to its (preferred MBN) profile and inspects that profile's
|
|
252
|
-
aggregated
|
|
253
|
+
aggregated similarity tags.
|
|
253
254
|
|
|
254
255
|
Examples:
|
|
255
256
|
tl recommender inspect-brand 287
|
|
@@ -277,9 +278,9 @@ def similar_to_profile_cmd(
|
|
|
277
278
|
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
278
279
|
limit: int = typer.Option(20, "--limit", "-l", help="Max results (1-100)"),
|
|
279
280
|
) -> None:
|
|
280
|
-
"""Channels closest to a brand profile's ideal
|
|
281
|
+
"""Channels closest to a brand profile's ideal similarity profile.
|
|
281
282
|
|
|
282
|
-
Costs
|
|
283
|
+
Costs 25 credits per call. Intelligence plan required. Channels the
|
|
283
284
|
brand has already worked with or been proposed are excluded.
|
|
284
285
|
|
|
285
286
|
Filters:
|
|
@@ -312,3 +313,51 @@ def similar_to_profile_cmd(
|
|
|
312
313
|
handle_api_error(e)
|
|
313
314
|
finally:
|
|
314
315
|
client.close()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@app.command("similar-brands-to-channel")
|
|
319
|
+
def similar_brands_to_channel_cmd(
|
|
320
|
+
channel_ref: str = typer.Argument(..., help="Channel ID (numeric) or name (partial match, must be unique)"),
|
|
321
|
+
args: list[str] = typer.Argument(None, help="Filters (key:value pairs)."),
|
|
322
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
323
|
+
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
324
|
+
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
325
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
326
|
+
limit: int = typer.Option(20, "--limit", "-l", help="Max results (1-100)"),
|
|
327
|
+
) -> None:
|
|
328
|
+
"""Brands most likely to sponsor a given channel.
|
|
329
|
+
|
|
330
|
+
Compares the channel's similarity profile against brand similarity
|
|
331
|
+
profiles and dedupes the results by brand. Costs 25 credits per call.
|
|
332
|
+
Intelligence plan required.
|
|
333
|
+
|
|
334
|
+
Filters:
|
|
335
|
+
mbn:<yes|no|all> MBN membership of the underlying profile (default: all)
|
|
336
|
+
|
|
337
|
+
Examples:
|
|
338
|
+
tl recommender similar-brands-to-channel 12345
|
|
339
|
+
tl recommender similar-brands-to-channel "MrBeast" mbn:yes --limit 30
|
|
340
|
+
"""
|
|
341
|
+
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
342
|
+
filters = parse_filters(args or [])
|
|
343
|
+
params = {k: v for k, v in filters.items() if k in {"mbn"}}
|
|
344
|
+
params["limit"] = str(limit)
|
|
345
|
+
encoded = urllib.parse.quote(channel_ref, safe="")
|
|
346
|
+
client = get_client()
|
|
347
|
+
try:
|
|
348
|
+
data = client.get(f"/recommender/channels/{encoded}/similar-brands", params=params)
|
|
349
|
+
for r in data.get("results", []):
|
|
350
|
+
score = r.get("score")
|
|
351
|
+
if isinstance(score, (int, float)) and fmt in ("table", "md"):
|
|
352
|
+
r["score"] = f"{score * 100:.1f}%"
|
|
353
|
+
output(
|
|
354
|
+
data,
|
|
355
|
+
fmt,
|
|
356
|
+
columns=["score", "brand_id", "brand_name", "website", "mbn", "profile_id"],
|
|
357
|
+
title=f"Brands likely to sponsor channel {channel_ref}",
|
|
358
|
+
column_config={"score": {"justify": "right"}},
|
|
359
|
+
)
|
|
360
|
+
except ApiError as e:
|
|
361
|
+
_handle_recommender_error(e)
|
|
362
|
+
finally:
|
|
363
|
+
client.close()
|
|
@@ -3,16 +3,11 @@
|
|
|
3
3
|
Query sponsorship data, channels, brands, and intelligence.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import re
|
|
7
6
|
import sys
|
|
8
7
|
import traceback
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Optional
|
|
11
8
|
|
|
12
|
-
import click
|
|
13
9
|
import typer
|
|
14
10
|
from rich.console import Console
|
|
15
|
-
from rich.markdown import Markdown
|
|
16
11
|
|
|
17
12
|
from tl_cli import __version__
|
|
18
13
|
from tl_cli import config as tl_config
|
|
@@ -128,61 +123,6 @@ def update_command() -> None:
|
|
|
128
123
|
raise typer.Exit()
|
|
129
124
|
|
|
130
125
|
|
|
131
|
-
def _get_terminology() -> str | None:
|
|
132
|
-
"""Extract the Terminology section from README.md.
|
|
133
|
-
|
|
134
|
-
Tries to locate README.md relative to the package source first,
|
|
135
|
-
then falls back to importlib.metadata.
|
|
136
|
-
"""
|
|
137
|
-
try:
|
|
138
|
-
text = None
|
|
139
|
-
readme = Path(__file__).resolve().parent.parent.parent / "README.md"
|
|
140
|
-
if readme.is_file():
|
|
141
|
-
text = readme.read_text()
|
|
142
|
-
else:
|
|
143
|
-
from importlib.metadata import metadata
|
|
144
|
-
text = metadata("thoughtleaders-cli").get_payload()
|
|
145
|
-
if not text:
|
|
146
|
-
return None
|
|
147
|
-
match = re.search(r"^# Terminology\s*\n(.+?)(?=\n# |\Z)", text, re.DOTALL | re.MULTILINE)
|
|
148
|
-
if not match:
|
|
149
|
-
return None
|
|
150
|
-
return match.group(1).strip()
|
|
151
|
-
except Exception:
|
|
152
|
-
return None
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
@app.command(name="help", hidden=True)
|
|
156
|
-
def help_command(
|
|
157
|
-
ctx: typer.Context,
|
|
158
|
-
command: Optional[str] = typer.Argument(None, help="Command to show help for"),
|
|
159
|
-
) -> None:
|
|
160
|
-
"""Show help for the CLI or a specific command."""
|
|
161
|
-
root_ctx = ctx.parent
|
|
162
|
-
root_cmd = root_ctx.command
|
|
163
|
-
|
|
164
|
-
if command is None:
|
|
165
|
-
click.echo(root_cmd.get_help(root_ctx))
|
|
166
|
-
terminology = _get_terminology()
|
|
167
|
-
if terminology:
|
|
168
|
-
import shutil
|
|
169
|
-
term_width = shutil.get_terminal_size().columns
|
|
170
|
-
console = Console(width=int(term_width * 0.9))
|
|
171
|
-
console.print(Markdown(terminology))
|
|
172
|
-
console.print()
|
|
173
|
-
raise typer.Exit()
|
|
174
|
-
|
|
175
|
-
# Look up the subcommand
|
|
176
|
-
sub_cmd = root_cmd.get_command(root_ctx, command)
|
|
177
|
-
if sub_cmd is None:
|
|
178
|
-
click.echo(f"Unknown command: {command}", err=True)
|
|
179
|
-
raise typer.Exit(1)
|
|
180
|
-
|
|
181
|
-
sub_ctx = click.Context(sub_cmd, info_name=command, parent=root_ctx)
|
|
182
|
-
click.echo(sub_cmd.get_help(sub_ctx))
|
|
183
|
-
raise typer.Exit()
|
|
184
|
-
|
|
185
|
-
|
|
186
126
|
def cli() -> None:
|
|
187
127
|
"""Entry point that wraps the Typer app with top-level error handling.
|
|
188
128
|
|
|
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.4 → thoughtleaders_cli-0.6.6}/skills/tl/references/business-glossary.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.4 → thoughtleaders_cli-0.6.6}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|