thoughtleaders-cli 0.6.5__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.
Files changed (68) hide show
  1. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/.claude-plugin/plugin.json +1 -1
  2. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/AGENTS.md +2 -2
  3. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/PKG-INFO +6 -6
  4. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/README.md +5 -5
  5. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/agents/tl-analyst.md +2 -2
  6. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/docs/architecture.md +2 -2
  7. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/pyproject.toml +1 -1
  8. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/skills/tl/SKILL.md +38 -21
  9. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/skills/tl/references/elasticsearch-schema.md +2 -4
  10. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/skills/tl/references/firebolt-schema.md +1 -1
  11. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/__init__.py +1 -1
  12. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/brands.py +2 -2
  13. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/channels.py +4 -4
  14. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/recommender.py +77 -28
  15. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/main.py +0 -60
  16. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/uv.lock +1 -1
  17. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/.claude-plugin/marketplace.json +0 -0
  18. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/.github/workflows/python-publish.yml +0 -0
  19. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/.gitignore +0 -0
  20. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/CLAUDE.md +0 -0
  21. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/LICENSE +0 -0
  22. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/commands/tl-balance.md +0 -0
  23. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/commands/tl-reports.md +0 -0
  24. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/commands/tl-sponsorships.md +0 -0
  25. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/commands/tl.md +0 -0
  26. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/hooks/hooks.json +0 -0
  27. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/hooks/scripts/post-usage.sh +0 -0
  28. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/hooks/scripts/pre-check.sh +0 -0
  29. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/skills/tl/references/business-glossary.md +0 -0
  30. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/skills/tl/references/postgres-schema.md +0 -0
  31. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/_completions.py +0 -0
  32. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/auth/__init__.py +0 -0
  33. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/auth/commands.py +0 -0
  34. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/auth/login.py +0 -0
  35. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/auth/pkce.py +0 -0
  36. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/auth/token_store.py +0 -0
  37. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/client/__init__.py +0 -0
  38. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/client/errors.py +0 -0
  39. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/client/http.py +0 -0
  40. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/__init__.py +0 -0
  41. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/ask.py +0 -0
  42. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/balance.py +0 -0
  43. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/changelog.py +0 -0
  44. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/comments.py +0 -0
  45. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/db.py +0 -0
  46. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/deals.py +0 -0
  47. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/describe.py +0 -0
  48. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/doctor.py +0 -0
  49. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/matches.py +0 -0
  50. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/proposals.py +0 -0
  51. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/reports.py +0 -0
  52. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/schema.py +0 -0
  53. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/setup.py +0 -0
  54. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/snapshots.py +0 -0
  55. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/sponsorships.py +0 -0
  56. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/uploads.py +0 -0
  57. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/commands/whoami.py +0 -0
  58. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/config.py +0 -0
  59. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/filters.py +0 -0
  60. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/hints.py +0 -0
  61. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/output/__init__.py +0 -0
  62. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/output/formatter.py +0 -0
  63. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/src/tl_cli/self_update.py +0 -0
  64. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/tests/__init__.py +0 -0
  65. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/tests/test_auth.py +0 -0
  66. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/tests/test_filters.py +0 -0
  67. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/tests/test_output.py +0 -0
  68. {thoughtleaders_cli-0.6.5 → thoughtleaders_cli-0.6.6}/tests/test_sponsorships.py +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tl-cli",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "ThoughtLeaders CLI — query sponsorship deals, channels, brands, uploads, and intelligence from the terminal",
5
5
  "author": {
6
6
  "name": "ThoughtLeaders",
@@ -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, vector 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.
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 vector-recommender API at `/api/cli/v1/recommender/*` — `tags` (free), `top-channels` / `top-profiles` / `top-brands`, `inspect-channel`, `inspect-brand`, `similar-to-profile` (all 50 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.
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.5
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 vector recommender over
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 (vector recommender, 50 credits, Intelligence plan).
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
- # Vector recommender — discovery by category/demographic tag (Intelligence plan).
112
- # `tags` is free; `top`, `inspect-*`, and `similar-to-profile` cost 50 credits flat.
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 vector
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 vector recommender over
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 (vector recommender, 50 credits, Intelligence plan).
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
- # Vector recommender — discovery by category/demographic tag (Intelligence plan).
85
- # `tags` is free; `top`, `inspect-*`, and `similar-to-profile` cost 50 credits flat.
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 vector
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` (vector KNN), `tl reports run`, and `tl snapshots`.
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 — vector KNN 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.
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>` | Vector-similarity recommender. 50 credits; Intelligence plan. Tri-state `msn:` (default `yes`) and `tpp:` (default `both`) filters. Ambiguous names return 400 + candidates list. Hidden `look-alike` alias. |
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 (profile vector KNN, 50 credits) |
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) |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "thoughtleaders-cli"
7
- version = "0.6.5"
7
+ version = "0.6.6"
8
8
  description = "ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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 vector recommender — that's faster, ranked by category-strength. Run `tl recommender tags` to discover the valid tag names.
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 a `tl help` command to find out which commands are available, and the `tl whoami` command to find out what you have access to.
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> # Vector-similarity recommender (50 credits flat; Intelligence plan)
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 profile vector KNN (50 credits flat)
140
- tl recommender tags [query] # List vector tag names — categories, demographics, formats (free)
141
- tl recommender top-channels "<tag>" # Top channels loaded on a vector tag (50 credits; Intelligence)
142
- tl recommender top-profiles "<tag>" # Top brand profiles loaded on a vector tag (50 credits)
143
- tl recommender top-brands "<tag>" # Top brands (deduped from profiles) loaded on a vector tag (50 credits)
144
- tl recommender inspect-channel <ref> # Show a channel's feature-vector breakdown (50 credits; Intelligence)
145
- tl recommender inspect-brand <ref> # Show a brand profile's ideal-vector breakdown (50 credits; Intelligence)
146
- tl recommender similar-to-profile <id> # Channels closest to a brand profile's ideal vector (50 credits; Intelligence)
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 (vector KNN, server-implemented) | `tl channels similar`, `tl brands similar` |
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` (vector KNN, server-implemented). |
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 vector recommender, not `content_category` SQL.** The recommender ranks channels by how strongly they load on a category/demographic tag (cosine-style 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."
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 vector tag (50 credits; Intelligence)
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 vector recommender for the topic, then narrow with structured filters / SQL on the IDs.
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" (vector-similarity recommender, 50 credits per call):
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 vector recommender" (categories, demographics, formats — `tl recommender tags` is free):
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 (50 credits)
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 vector
477
- tl recommender similar-to-profile 842 --limit 30 # Channels closest to a brand profile's ideal vector
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.
@@ -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-feature-vectors-channel`, `tl-feature-vectors-channel-profile` — channel similarity vectors.
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 feature vectors.
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
 
@@ -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 (vector recommender — preferred over content_category equality)
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
 
@@ -1,3 +1,3 @@
1
1
  """ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence."""
2
2
 
3
- __version__ = "0.6.5"
3
+ __version__ = "0.6.6"
@@ -125,7 +125,7 @@ SIMILAR_COLUMN_CONFIG = {
125
125
 
126
126
 
127
127
  def _format_score(results: list[dict]) -> list[dict]:
128
- """Convert raw cosine score (0.0-1.0) to percentage string."""
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 50 credits per call. Intelligence plan required.
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 cosine score (0.0-1.0) to percentage string for table/csv/md."""
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 50 credits per call. Intelligence plan required. Results are
175
- ranked by cosine similarity and enriched with subscribers, impression,
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 cosine similarity (default: 0.5)
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
@@ -1,10 +1,11 @@
1
- """tl recommender — Vector-recommender introspection and discovery.
1
+ """tl recommender — Recommender introspection and discovery.
2
2
 
3
- Surfaces the channel/profile feature-vector machinery that powers the
4
- "Recommender Insights" web view: list the vector tags (categories,
5
- demographics, formats, etc.), find the top channels and profiles loaded
6
- on a given tag, inspect a single channel or brand vector, or fetch
7
- channels similar to a brand profile's ideal vector.
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="Vector recommender (tags, top-channels/profiles/brands, vector inspection, profile→channel similarity)")
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
- """Vector recommender."""
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 vector tag names (free).
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 dimension of a channel/profile feature vector
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="Recommender vector tags",
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='Vector tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
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 loaded on a single vector tag.
139
+ """Top channels scoring high on a single similarity tag.
139
140
 
140
- Costs 50 credits per call. Intelligence plan required.
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='Vector tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
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 loaded on a single vector tag.
166
+ """Top brand profiles scoring high on a single similarity tag.
166
167
 
167
- Costs 50 credits per call. Intelligence plan required. Profiles can
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='Vector tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
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 loaded on a single vector tag (deduplicated from profiles).
194
+ """Top brands scoring high on a single similarity tag (deduplicated from profiles).
194
195
 
195
- Costs 50 credits per call. Intelligence plan required. Server-side
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 feature vector grouped by category.
220
+ """Show a channel's similarity profile grouped by category.
220
221
 
221
- Costs 50 credits per call. Intelligence plan required. Returns the
222
- grouped sparse vector (active dimensions only) and the magnitude.
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 feature vector grouped by category.
249
+ """Show a brand profile's ideal similarity profile grouped by category.
249
250
 
250
- Costs 50 credits per call. Intelligence plan required. Resolves the
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 vector.
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 vector.
281
+ """Channels closest to a brand profile's ideal similarity profile.
281
282
 
282
- Costs 50 credits per call. Intelligence plan required. Channels the
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
 
@@ -388,7 +388,7 @@ wheels = [
388
388
 
389
389
  [[package]]
390
390
  name = "thoughtleaders-cli"
391
- version = "0.6.5"
391
+ version = "0.6.6"
392
392
  source = { editable = "." }
393
393
  dependencies = [
394
394
  { name = "authlib" },