thoughtleaders-cli 0.6.44__tar.gz → 0.6.46__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 (93) hide show
  1. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/.claude-plugin/plugin.json +1 -1
  2. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/PKG-INFO +1 -1
  3. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/pyproject.toml +1 -1
  4. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl/SKILL.md +100 -65
  5. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl/references/firebolt-schema.md +12 -3
  6. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl/references/postgres-schema.md +42 -125
  7. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/SKILL.md +2 -4
  8. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/__init__.py +1 -1
  9. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/_comments_common.py +6 -4
  10. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/brands.py +39 -5
  11. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/channels.py +55 -11
  12. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/recommender.py +77 -8
  13. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/.claude-plugin/marketplace.json +0 -0
  14. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/.github/workflows/python-publish.yml +0 -0
  15. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/.gitignore +0 -0
  16. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/AGENTS.md +0 -0
  17. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/CLAUDE.md +0 -0
  18. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/LICENSE +0 -0
  19. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/README.md +0 -0
  20. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/agents/tl-analyst.md +0 -0
  21. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/docs/architecture.md +0 -0
  22. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/hooks/hooks.json +0 -0
  23. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/hooks/scripts/load-tl-skill.mjs +0 -0
  24. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/hooks/scripts/post-usage.sh +0 -0
  25. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/hooks/scripts/pre-check.sh +0 -0
  26. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl/references/business-glossary.md +0 -0
  27. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl/references/elasticsearch-schema.md +0 -0
  28. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-import/SKILL.md +0 -0
  29. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
  30. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/examples/golden_queries.md +0 -0
  31. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/columns_brands.md +0 -0
  32. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/columns_channels.md +0 -0
  33. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/columns_content.md +0 -0
  34. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
  35. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
  36. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
  37. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/report_glossary.md +0 -0
  38. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/sortable_columns.json +0 -0
  39. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
  40. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
  41. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/references/widgets.md +0 -0
  42. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/tools/column_builder.md +0 -0
  43. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/tools/database_query.md +0 -0
  44. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/tools/keyword_research.md +0 -0
  45. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/tools/name_resolver.md +0 -0
  46. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/tools/sample_judge.md +0 -0
  47. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/tools/similar_channels.md +0 -0
  48. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
  49. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/skills/tl-report-builder/tools/widget_builder.md +0 -0
  50. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/_completions.py +0 -0
  51. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/auth/__init__.py +0 -0
  52. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/auth/commands.py +0 -0
  53. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/auth/finalize.py +0 -0
  54. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/auth/login.py +0 -0
  55. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/auth/pkce.py +0 -0
  56. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/auth/token_store.py +0 -0
  57. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/client/__init__.py +0 -0
  58. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/client/errors.py +0 -0
  59. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/client/http.py +0 -0
  60. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/__init__.py +0 -0
  61. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/ask.py +0 -0
  62. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/balance.py +0 -0
  63. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/bulk_import.py +0 -0
  64. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/changelog.py +0 -0
  65. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/credits.py +0 -0
  66. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/db.py +0 -0
  67. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/deals.py +0 -0
  68. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/describe.py +0 -0
  69. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/doctor.py +0 -0
  70. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/matches.py +0 -0
  71. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/proposals.py +0 -0
  72. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/reports.py +0 -0
  73. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/schema.py +0 -0
  74. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/setup.py +0 -0
  75. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/snapshots.py +0 -0
  76. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/sponsorships.py +0 -0
  77. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/uploads.py +0 -0
  78. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/commands/whoami.py +0 -0
  79. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/config.py +0 -0
  80. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/filters.py +0 -0
  81. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/hints.py +0 -0
  82. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/main.py +0 -0
  83. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/output/__init__.py +0 -0
  84. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/output/formatter.py +0 -0
  85. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/src/tl_cli/self_update.py +0 -0
  86. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/tests/__init__.py +0 -0
  87. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/tests/test_auth.py +0 -0
  88. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/tests/test_filters.py +0 -0
  89. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/tests/test_http_auth.py +0 -0
  90. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/tests/test_output.py +0 -0
  91. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/tests/test_reports.py +0 -0
  92. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/tests/test_sponsorships.py +0 -0
  93. {thoughtleaders_cli-0.6.44 → thoughtleaders_cli-0.6.46}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tl-cli",
3
- "version": "0.6.44",
3
+ "version": "0.6.46",
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
  Metadata-Version: 2.4
2
2
  Name: thoughtleaders-cli
3
- Version: 0.6.44
3
+ Version: 0.6.46
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "thoughtleaders-cli"
7
- version = "0.6.44"
7
+ version = "0.6.46"
8
8
  description = "ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,41 +1,47 @@
1
1
  ---
2
2
  name: tl
3
3
  description: |
4
- Query and analyze ThoughtLeaders business data using the `tl` CLI. Default to raw database queries via `tl db pg|fb|es` for anything non-trivial (joins, aggregations, multi-condition filters, anything that would otherwise need post-processing). Use this skill for ANALYTICAL questions about channels, brands and sponsorships: counts, metrics, trends, time-series, distributions, single-record drill-downs, revenue / pipeline-weighting math, view-curve analysis, cross-source business questions. Examples: "How many deals did we close last quarter?", "What's the weighted pipeline by sales owner?", "Show me the view curve for video X", "Find mentions of Surfshark in transcripts", "Investigate this video".
4
+ Query and analyze YouTube sponsorship data using the `tl` CLI. Use this skill for data exploration and questions about channels, brands and sponsorships: counts, metrics, trends, time-series, distributions, single-record drill-downs, revenue / pipeline-weighting math, view-curve analysis, cross-source business questions. Examples: "How many deals did we close last quarter?", "What's the weighted pipeline by sales owner?", "Show me the view curve for video X", "Find mentions of Surfshark in transcripts", "Investigate this video".
5
5
  ---
6
6
 
7
7
  # ThoughtLeaders Data Analyst
8
8
 
9
- Run the `tl` CLI to query ThoughtLeaders' sponsorship platform data. Use it to answer questions about deals, channels, brands, uploads, metrics, etc.
10
-
11
9
  ## Core Principles
12
10
 
13
- **Default to raw database queries.** For anything beyond a trivially simple lookup, reach for `tl db pg|fb|es`. Avoid the structured `tl <resource>` commands (`sponsorships list`, `channels show`, `brands history`, etc.).
14
-
15
- Always run `tl schema pg|fb|es` before writing a raw query.
11
+ Run the `tl` CLI to query ThoughtLeaders' sponsorship platform data. Use it to answer questions about deals, channels, brands, uploads, metrics, etc. Use raw database queries via `tl db pg|fb|es` for everything.
16
12
 
17
- **When you only need the schema of one table, you MUST call `tl schema pg <table>` (or `tl schema fb <table>`) never the unscoped form**, to reduce token counts. ES has no per-table form (the index is a single document shape) `tl schema es` is the only call there.
13
+ Always run `tl schema pg|fb|es` before writing a raw query. When you only need the schema of one table, you MUST call `tl schema pg <table>` (or `tl schema fb <table>`). Avoid calling the unscoped form, to reduce token counts. ES has no per-table form (the index is a single document shape), so `tl schema es` is the only call there.
18
14
 
19
- **Process data with shell tools, not your context window.** Don't pull large result sets into your reasoning context just to filter, sort, count, or extract a field that wastes tokens and slows you down. Pipe `tl … --json` (or `--csv`, or `--toon`) into `jq`, `yq`, `rg`, or `duckdb`, as appropriate, and read only the answer back. Pick the tool by shape:
15
+ **Process data with shell tools, not your context window.** Don't pull large result sets into your reasoning context just to filter, sort, count, or extract a field - that wastes tokens and slows you down. Pipe `tl … --json` (or `--csv`, or `--toon`) into `jq`, `yq`, `rg`, or `duckdb`, as appropriate, and read only the answer back. Pick the tool by shape:
20
16
 
21
17
  - **`jq`** — filter, project, and transform JSON. The default for `tl … --json` post-processing.
22
18
  ```bash
23
- tl sponsorships list status:sold --json | jq '.results[] | select(.price > 5000) | {id, brand, price}'
19
+ tl db pg "SELECT id, weighted_price FROM thoughtleaders_adlink
20
+ WHERE publish_status = 3 AND price > 5000
21
+ LIMIT 500 OFFSET 0" --json \
22
+ | jq '.results[] | {id, price: .weighted_price}'
24
23
  ```
25
24
  - **`yq`** — same idea for YAML/TOML, useful when reading config files or `--md` blocks.
26
- - **`rg`** — fast text search across CLI output, transcripts, and the codebase. Better than `grep` for searching large `--csv` exports or transcript dumps.
25
+ - **`rg`** — fast text search across CLI output, transcripts, and the codebase. Better than `grep` for searching large `--csv` exports or transcript dumps from ES.
27
26
  ```bash
28
27
  tl db es '{"size":500,"query":{"term":{"channel.id":5607}},"_source":["id","transcript"]}' --json | rg -o "NordVPN[^.]*"
29
28
  ```
30
29
  - **`duckdb`** — embedded analytical SQL over CSV/JSON files. Use when you need joins, aggregations, or window functions across multiple `tl` exports without spinning up a database.
31
30
  ```bash
32
- tl deals list purchase-date-start:2026-01 --csv > deals.csv
31
+ tl db pg "SELECT al.id, b.name AS brand, al.weighted_price AS price
32
+ FROM thoughtleaders_adlink al
33
+ JOIN thoughtleaders_profile p ON p.id = al.creator_profile_id
34
+ JOIN thoughtleaders_profile_brands pb ON pb.profile_id = p.id
35
+ JOIN thoughtleaders_brand b ON b.id = pb.brand_id
36
+ WHERE al.publish_status = 3
37
+ AND al.purchase_date >= '2026-01-01'
38
+ LIMIT 500 OFFSET 0" --csv > deals.csv
33
39
  duckdb -c "SELECT brand, SUM(price) AS revenue FROM 'deals.csv' GROUP BY brand ORDER BY revenue DESC LIMIT 10"
34
40
  ```
35
41
 
36
- The pattern is always: server-side narrowing first (filter in the `tl db` query or the structured filters), then shell tool to shape the result, then read only the final summary into context. If `tl doctor` reports any of these as missing, ask the user to install them — `tl-internal setup` installs all four by default.
42
+ The pattern is always: server-side narrowing first (usually by filters in the `tl db` query, but could be from similarity / recommender searches), then shell tool to shape the result, then read only the final summary into context. If `tl doctor` reports any of these as missing, ask the user to install them — `tl-internal setup` installs all four by default.
37
43
 
38
- Always assume there will be more than 1 page of results. You MUST always use `--limit` and `--offset` options in the `tl list` commands to retrieve the entire data set (all pages, until the total records are fetched). You must also always use pagination in scripts you write to collect results. The maximum number of results per page is 500.
44
+ Always assume there will be more than 1 page of results. You MUST always pass `LIMIT` and `OFFSET` to every `tl db pg|fb|es` query (and use the response envelope's `next_offset` / breadcrumbs to walk forward) so the entire data set is retrieved. The maximum number of rows per page is 500.
39
45
 
40
46
  Retry after 5 seconds if the server returns a "connection denied" or a "server error" on any request.
41
47
 
@@ -49,7 +55,7 @@ This section defines business terminology. Any other skill files, command, and p
49
55
 
50
56
  ThoughtLeaders is a sponsorship marketplace connecting **Brands** (advertisers / media buyers) with **Channels** (YouTube creators, podcasters / media sellers).
51
57
 
52
- The centre of the data model is **Sponsorships** — business relationships between brands and channels. Sponsorships have a funnel of types, from broad to narrow:
58
+ The centre of the data model are **Sponsorships** — business relationships between brands and channels. Sponsorships statuses form a sales funnel, from broad to narrow:
53
59
 
54
60
  - **Sponsorships** — the broadest category, encompassing all stages, stored in the `thoughtleaders_adlink` table.
55
61
  - **Matches** — possible brand-channel pairings that ThoughtLeaders thinks could work
@@ -58,33 +64,33 @@ The centre of the data model is **Sponsorships** — business relationships betw
58
64
 
59
65
  Sponsorships are sometimes called "Ads" or "Ad campaigns". **"AdLink"** is another name for the same thing — it's the term the database uses (`thoughtleaders_adlink`) and shows up across internal code, schema docs, and AM Slack threads. Treat "sponsorship" and "adlink" as interchangeable; the user-facing word is "sponsorship," the engineering/DB word is "adlink."
60
66
 
61
- The CLI has shortcut commands for each type: `tl matches`, `tl proposals`, `tl deals`. These filter `tl sponsorships` by status.
62
-
63
67
  Other key concepts:
68
+ - **Channels** — YouTube channels, but could also be podcasts
69
+ - **Brands** — Entities (usually companies / organizations, but could be narrowed down to individual brands of a company)
64
70
  - **Uploads** — YouTube videos indexed from Elasticsearch
65
71
  - **Snapshots** — historical time-series metrics for channels and videos (Firebolt)
66
72
  - **Reports** — saved report configurations that can be re-run
67
- - **Comments** — notes attached to sponsorships
68
- - **Adspots** — types of ads a channel carries (e.g. mention, dedicated video, product placement). Returned by `tl channels show`; each carries price/cost.
69
- - **Profiles** — per-organization actors that own sponsorship records on behalf of either side of a deal. A profile is buyer-side or seller-side:
73
+ - **Comments** — notes attached to sponsorships, channels, or brands
74
+ - **Adspots** — types of ads a channel is willing to publish (e.g. mention, dedicated video, product placement). Returned by `tl channels show`; each carries price/cost.
75
+ - **Profiles** — actors that own sponsorship records on behalf of either side of a deal. A profile is either buyer-side or seller-side:
70
76
  - *Buyer-side (brand) profiles* — represent a sponsoring brand. Each brand profile has an M2M link to at most one `Brand` record (which are the actual advertiser identities). On a sponsorship, `creator_profile` is the buyer-side profile.
71
77
  - *Seller-side (publisher) profiles* — attached to a `Publication`, which in turn owns one or more `Channel` records. A channel's adspots therefore inherit ownership through `channel.publication.profile`.
72
78
  - **How to tell them apart** — three signals on the `thoughtleaders_profile` row, used in this order:
73
79
  1. **`persona`** (canonical) — `1=Brand`, `4=Media Agency`, `3=Talent Manager` are buyer-side; `2=Creator`, `5=Creator Service` are seller-side. May be null on legacy rows.
74
80
  2. **`is_advertiser` / `is_publisher`** booleans — feature flags; either or both can be true for staff-style profiles, but on normal user profiles they reliably mark side.
75
81
  - Org scoping for sponsorships is profile-mediated: a sponsorship belongs to your org if **either** `creator_profile.organization` (brand side) **or** `ad_spot.channel.publication.profile.organization` (publisher side) matches yours.
76
- - **MSN** (Media Selling Network) — the ~11k YouTube channels that have opted in to receive sponsorship offers. A channels is in the MSN group if the `channel.media_selling_network_join_date` field is not null.
82
+ - **MSN** (Media Selling Network) — the ~12k YouTube channels that have opted in to receive sponsorship offers. A channels is in the MSN group if the `channel.media_selling_network_join_date` field is not null.
77
83
  - **MBN** (Media Buying Network) — the brand-side counterpart to MSN: brand profiles that have opted in to receive proposed sponsorships. A profile is in the MBN group if the `profile.media_buying_network_join_date` field is not null.
78
- - **TPP** (ThoughtLeaders Partner Program, a.k.a. "TL channels") — the ~169 channels TL has the closest working relationship with. A channel is in the TPP group if `channel.is_tl_channel` is True. **Prefer TPP channels when booking**: they respond fastest, are the easiest to close, and don't need an outreach round-trip — treat them as immediately bookable. TPP is a strict subset of MSN, so the same booking rules (one active mention adspot, etc.) apply.
79
- - **`demographics_updated_at`** (on channel detail) — ISO timestamp of when demographic screenshots were last uploaded and processed via OCR. If non-null, the channel has demographics screenshots on file. If null, no screenshots have been uploaded. Use this to check whether a channel has demographics data from screenshots.
84
+ - **TPP** (ThoughtLeaders Partner Program, a.k.a. "TL channels") — the ~170 channels TL has the closest working relationship with. A channel is in the TPP group if `channel.is_tl_channel` is True. **Prefer TPP channels when booking**: they respond fastest, are the easiest to close, and don't need an outreach round-trip — treat them as immediately bookable. TPP is a strict subset of MSN, so the same booking rules (one active mention adspot, etc.) apply.
85
+ - **`demographics_updated_at`** (on channels) — If non-null, the channel has demographics screenshots on file. If null, no demographics screenshots have been uploaded. Use this to check whether a channel has demographics data from screenshots.
80
86
  - **`impression`** (on channels) — projected views per video on that channel. Forward-looking estimate. May be null when not yet computed.
81
87
  - **`views`** (on sponsorships) — actual view count of the sold and published sponsored video, accessible when `article_id` is set.
82
- - **`impressions_guarantee`** (on sponsorships) — projected/guaranteed impressions for the sponsorship. Numeric; rounded to int in list output.
83
- - **Sponsorship detail fields** (returned by `tl sponsorships show <id> --json`) — in addition to the list-view columns, the detail payload includes `integration` (raw int), `publish_count`, `common_name`, `outreach_email`, nested `publisher` (`first_name`, `last_name`, `email`), nested `brand_contact` (`first_name`, `last_name`, `email`), and `brand.organization_name`. Use these when generating IOs, contracts, or outreach.
88
+ - **`impressions_guarantee`** (on sponsorships) — projected/guaranteed impressions for the sponsorship. Numeric.
89
+ - **Sponsorship detail fields** (returned by `tl sponsorships show <id> --json`) — the detail payload includes `integration` (raw int), `publish_count`, `common_name`, `outreach_email`, nested `publisher` (`first_name`, `last_name`, `email`), nested `brand_contact` (`first_name`, `last_name`, `email`), and `brand.organization_name`. Use these when generating IOs, contracts, or outreach.
84
90
  - **CPM** has two distinct meanings depending on level — pick the one the user actually wants:
85
91
  - **Channel CPM** = `(adspot.price / channel.impression) * 1000` — projected price per thousand projected views. Used for pricing decisions **before** a sponsorship is sold. Available for channels with active adspots via `tl channels show <channel_id>`.
86
92
  - **Sponsorship CPM** = calculated in either of two ways: if `views` is present, then CPM is `(sponsorship.price / sponsorship.views) × 1000`, meaning realized cost per thousand actual views, computed post-publication. If `views` is null, Compute from the sponsorship's `price` and the channel's `impression` fields.
87
- - **CPM does not have a range filter.** To find sponsorships in a CPM range (e.g. "around $15"), fetch the record set with other filters first, then apply the CPM range in post-processing (jq, Python, etc.) on the returned `cpm` field. Plan queries and pagination accordingly — the server cannot reduce the result count based on CPM.
93
+ - Where possible, calculate the correct CPM in a SQL expression.
88
94
  - **Sponsorship dates** — each sponsorship has four distinct dates, useful for different queries:
89
95
  - **`created_at`** — when the sponsorship record was created in the system
90
96
  - **`purchase_date`** — when the sponsorship was purchased (i.e. when the deal was made); These make up bookings.
@@ -97,13 +103,13 @@ Users see data scoped by their organization and plan:
97
103
  - **Media sellers** see sponsorships where their org is the publisher. They see `cost` but never `price`.
98
104
  - **Intelligence plan** is required for accessing information not strictly related to the user's organisation.
99
105
 
100
- When querying sponsorship bookings, query by `status:sold` and filter the the date range only by `purchase_date`. Otherwise, query for state:sold by `created_at`.
106
+ When querying sponsorship bookings, filter the rows with `publish_status = 3` (sold) and use `purchase_date` for the date range. For all-flow / not-yet-sold inclusive queries, drop the `publish_status` predicate and filter by `created_at` instead.
101
107
 
102
108
  ## Methodology
103
109
 
104
110
  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.
105
111
 
106
- 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.
112
+ Use the `tl channels similar` and `tl brands similar` commands to find channels or brands similar to a particular channel or brand. 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.
107
113
 
108
114
  ## Workflow
109
115
 
@@ -129,35 +135,44 @@ Notes:
129
135
 
130
136
  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.
131
137
 
132
- 1. **Discover first**: Run `tl describe show <resource> --json` to learn available fields, filters, and credit costs before querying. Use `tl schema pg`, `tl schema es`, and `tl schema fb` to find information about the main database (pg), the articles / uploads database (es), and the channel metrics database (fb).
133
- 2. **Check saved reports**: Run `tl reports --json` to see if the user has a saved report that already answers their question
134
- 3. **Check credits**: Run `tl balance --json` before expensive queries. Warn the user if a query will cost many credits.
135
- 4. **Query with filters**: Use `key:value` filter syntax for structured queries
136
- 5. **Always use --json**: Parse JSON output for multi-step analysis.
137
- 6. **Chain commands**: For complex questions, chain multiple `tl` commands
138
- 7. **Format results**: When the user asks for a list or tabular data, present the results as a well-formatted markdown table. Pick the most relevant columns and use clear headers.
138
+ 1. **Discover first**: Use `tl schema pg`, `tl schema es`, and `tl schema fb` to find information about the main database (pg), the articles / uploads database (es), and the channel metrics database (fb).
139
+ 2. **Check credits**: Run `tl balance --json` before expensive queries. Warn the user if a query will cost many credits.
140
+ 3. **Decide the method of discovery**: If the user want to explore certain topics, use the recommender commands. If it's more about filtering, construct a query for PG or ES.
141
+ 4. **Always use --json**: Parse JSON output for multi-step analysis.
142
+ 5. **Chain commands**: For complex questions, chain multiple `tl` commands, shell commands, and other tools.
143
+ 6. **Format results**: When the user asks for a list or tabular data, present the results as a well-formatted markdown table. Pick the most relevant columns and use clear headers. In this case, ask the user if they want to save the list as a report, and invoke the `tl-save-report` skill.
139
144
 
140
- Prefer writing Python code, shell code, or `jq` commands that fetche or analysise large sets of data, instead of analysing it yourself. Create temporary files in `/tmp` that can be analysed later in different ways. Before bulk data analysis by running `jq`, Python or Bash commands, first try fetching just a single result with `--limit 1` without `jq` etc, to see the shape of the data and any error messages.
145
+ Prefer writing shell code, `jq` commands, or `duckdb` commands that fetch or analysise large sets of data instead of analysing it yourself. Create temporary files in `/tmp` that can be analysed later in different ways. Before analysing a potentially large result set, first try fetching just a single result with `LIMIT 1` without `jq` etc, to see the shape of the data and any error messages.
141
146
 
142
147
  ## Available Commands
143
148
 
144
- Note that if you're working on Windows, you need to set up UTF-8 in the console, because all of these commands return UTF-8 data.
149
+ Note that if you're working on Windows, you must set up UTF-8 in the console, because all of these commands return UTF-8 data.
145
150
 
146
151
  ### Data queries
152
+
153
+ **Filtered queries go through `tl db pg|fb|es`.** Write the SELECT/ES body yourself, and freely perform joins and aggregations. The show/create/update commands exist because they target a single record by ID. Where needed, write Python scripts or duckdb queries to join data from different databases.
154
+
155
+ Filter-to-SQL examples (deals/matches/proposals all live on `thoughtleaders_adlink`, differentiated by `publish_status`):
156
+
157
+ | Want | Raw-DB equivalent |
158
+ | --- | --- |
159
+ | All sponsorships matching filters | `tl db pg "SELECT … FROM thoughtleaders_adlink WHERE …"` |
160
+ | Sold deals (`publish_status=3`) | `tl db pg "SELECT … FROM thoughtleaders_adlink WHERE publish_status = 3"` |
161
+ | Matched (`publish_status=7`) | `tl db pg "SELECT … FROM thoughtleaders_adlink WHERE publish_status = 7"` |
162
+ | Proposed (`publish_status=0`) | `tl db pg "SELECT … FROM thoughtleaders_adlink WHERE publish_status = 0"` |
163
+ | Video uploads from ElasticSearch | `tl db es '{"size":N,"query":{"term":{"channel.id":<id>}}}'` |
164
+
165
+ Single-record / mutation commands remain:
166
+
147
167
  ```bash
148
- tl sponsorships list [filters...] # Sponsorships
149
168
  tl sponsorships show <id> # Sponsorship detail
150
169
  tl sponsorships create --channel <id> --brand <id> # Create proposal
151
170
  tl sponsorships update <id> '<json>' # Update a sponsorship
152
- tl deals list [filters...] # Shortcut: agreed-upon sponsorships (status:deal)
153
171
  tl deals show <id> # Deal detail
154
- tl matches list [filters...] # Shortcut: possible brand-channel pairings (status:match)
155
172
  tl matches show <id> # Match detail
156
173
  tl matches create --channel <id> --brand <id> # Create match
157
- tl proposals list [filters...] # Shortcut: proposed matches (status:proposal)
158
174
  tl proposals show <id> # Proposal detail
159
175
  tl proposals create --channel <id> --brand <id> # Create proposal
160
- tl uploads list [filters...] # Video uploads from ES
161
176
  tl uploads show <id> # Upload detail
162
177
  tl channels show <id-or-name> # Channel detail (accepts numeric ID or name) — for channel search use raw SQL on thoughtleaders_channel
163
178
  tl channels find <query> # Resolve a string to {id, name}; accepts name/slug, YouTube URL/handle/ID, video URL (queues a scrape if no match)
@@ -177,7 +192,9 @@ tl recommender top-profiles "<tag>" # Top brand profiles loaded on a similari
177
192
  tl recommender top-brands "<tag>" # Top brands (deduped from profiles) loaded on a similarity tag
178
193
  tl recommender inspect-channel <ref> # Show a channel's similarity-profile breakdown (Intelligence)
179
194
  tl recommender inspect-brand <ref> # Show a brand profile's ideal similarity-profile breakdown (Intelligence)
180
- tl recommender similar-to-profile <id> # Channels closest to a brand profile's ideal profile (Intelligence)
195
+ tl recommender channels-for-profile <id> # Find channels closest to a brand profile's ideal profile (Intelligence)
196
+ tl recommender channels-for-brand <ref> # Same as above but takes a brand ref; uses the brand's newest profile with a vector (Intelligence)
197
+ tl recommender brands-for-channel <ref> # Brands most likely to sponsor a channel; runs the channel's vector against the brand-profile index (Intelligence)
181
198
  tl snapshots channel <id> # Channel metrics over time (Firebolt-backed)
182
199
  tl snapshots video <id> --channel <id> # Video view curve (--channel required!)
183
200
  tl reports # List saved reports
@@ -205,8 +222,6 @@ tl channels update 12345 '{"demographic_geo": {"US": 60, "UK": 12, "CA": 8}}'
205
222
  tl channels update 12345 '{"demographic_male_share": 55, "demographic_usa_share": 70}'
206
223
  ```
207
224
 
208
- Each call costs 2 credits. If a request is rejected with a 400, the response body names the offending key — read it and retry with a smaller body. If the user wants to edit something the API rejects, the change has to be made in the app or by a human with DB access.
209
-
210
225
  ### Creating and vetting sponsorships
211
226
 
212
227
  This is the end-to-end workflow for proposing a sponsorship, then moving it through the funnel as the two sides respond. Three create commands plus `tl sponsorships update` cover every state transition the CLI exposes.
@@ -306,7 +321,7 @@ Reasons to write a raw query (the common case):
306
321
  - **Multi-condition filtering** — compound boolean, `NOT IN`/`EXISTS`, `WHERE col IS NULL` on hidden fields, mixed range + enum + text predicates: write the SQL/ES body, don't over-fetch and post-filter.
307
322
  - **Fields the structured commands don't expose** — e.g. `media_selling_network_join_date` (only the `msn` boolean is surfaced), `weighted_price`, `tx_data`, raw `publish_status` integer, etc.
308
323
 
309
- Structured commands are still the right tool for: single-record `show` by ID, plain filtered `list` (one or two filters that the structured vocabulary already supports), saved `tl reports run`, and `tl snapshots channel|video` (these wrap interpolation logic you'd otherwise reimplement).
324
+ Structured commands are still the right tool for: single-record `show` by ID, saved `tl reports run`, and `tl snapshots channel|video` (these wrap interpolation logic you'd otherwise reimplement). Anything that would have been a "filtered list" goes through `tl db pg|fb|es`.
310
325
 
311
326
  | Need | Use |
312
327
  |---|---|
@@ -317,8 +332,7 @@ Structured commands are still the right tool for: single-record `show` by ID, pl
317
332
  | Transcript / brand-mention search inside video content | **`tl db es`** (no structured equivalent for content text) |
318
333
  | Custom Firebolt shape (milestone-age slices, multi-channel growth comparisons) | **`tl db fb`** |
319
334
  | Single-record detail lookup by ID | `tl <resource> show <id>` |
320
- | Plain filtered list with one or two simple filters | `tl <resource> list` |
321
- | Channel/brand similarity (server-implemented similarity search) | `tl channels similar`, `tl brands similar` |
335
+ | Channel/brand similarity (server-implemented similarity search) | `tl channels similar`, `tl brands similar`, `tl recommender ...` |
322
336
  | Saved reports | `tl reports`, `tl reports run` |
323
337
  | Time-series view-curve / channel growth (default shape with interpolation) | `tl snapshots channel`, `tl snapshots video` |
324
338
 
@@ -435,17 +449,6 @@ tl changelog since v0.4.10 # Notes from v0.4.10 to latest
435
449
  tl changelog --md > CHANGELOG.md # Capture for a doc
436
450
  ```
437
451
 
438
- `tl changelog` summaries are LLM-generated server-side from full commit messages and cached per version, so repeat calls are fast and don't re-bill the LLM. The release date and a 2–4 sentence prose summary come back per version.
439
-
440
- ### Filter syntax
441
- Structured list commands accept `key:value` filters (use them for trivially simple lookups):
442
- ```bash
443
- tl sponsorships list status:sold brand:"Nike" purchase-date:2026-01
444
- tl uploads list channel:12345 type:longform
445
- ```
446
-
447
- Date filters accept keywords: `today`, `yesterday`, `tomorrow`.
448
-
449
452
  #### Channel discovery — recommender first, raw SQL second
450
453
 
451
454
  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."
@@ -455,7 +458,7 @@ For category- or demographic-driven discovery, **use the recommender, not `conte
455
458
  tl recommender tags cooking
456
459
  tl recommender tags "usa"
457
460
 
458
- # Top channels & profiles loaded on a similarity tag (25 credits; Intelligence)
461
+ # Top channels & profiles loaded on a similarity tag (Intelligence)
459
462
  tl recommender top-channels "Cooking" msn:yes --limit 50
460
463
  tl recommender top-channels "Tech" --limit 30
461
464
  tl recommender top-brands "USA share" mbn:yes --limit 50
@@ -511,8 +514,8 @@ While analysing results, you must always examine the `results` field in the JSON
511
514
 
512
515
  Every query costs credits. Before running expensive queries:
513
516
  1. Check the credit rate: `tl describe show <resource> --json | jq '.credits'` and the user balance.
514
- 2. **List endpoints (sponsorships/channels/uploads/snapshots/comments/reports/db) are priced non-linearly:** `cost = 1 + mult × 0.126 × n^1.2`, where `mult` is the per-resource complexity factor (1.0 for cheap reads, 1.2 for snapshots, 1.3 for reports, 1.4 for raw db). Detail/history/similar endpoints are linear (`rate × results`). See the table in the command list above.
515
- 3. Estimate cost from the formula or the table; for non-list endpoints use `results × rate`.
517
+ 2. **Multi-row endpoints (snapshots, comments, reports, `tl db pg|fb|es`) are priced non-linearly:** `cost = 1 + mult × 0.126 × n^1.2`, where `mult` is the per-resource complexity factor (1.0 for cheap reads, 1.2 for snapshots, 1.3 for reports, 1.4 for raw db). Detail/history/similar endpoints are linear (`rate × results`).
518
+ 3. Estimate cost from the formula or the table; for non-row-priced endpoints use `results × rate`.
516
519
  4. If estimated cost is more than 10% of the remaining balance, ask the user to confirm the operation before running.
517
520
 
518
521
  ## Data Scoping
@@ -520,7 +523,7 @@ Every query costs credits. Before running expensive queries:
520
523
  Users only see data their plan allows:
521
524
  - **Media buyers** see deals where their org is the brand. They see `price` but never `cost`.
522
525
  - **Media sellers** see deals where their org is the publisher. They see `cost` but never `price`.
523
- - **Intelligence plan** required for `tl brands`, the full `tl recommender` surface, and full `tl uploads list`.
526
+ - **Intelligence plan** required for `tl brands`, the full `tl recommender` surface, and `tl db es` access to full transcript / brand-mention data.
524
527
  - **Paid plan** required for `tl snapshots`.
525
528
 
526
529
  ## Important: Status Labels
@@ -535,7 +538,15 @@ When presenting sponsorship status data, always use human-readable labels — ne
535
538
 
536
539
  "Show me my sold sponsorships this quarter":
537
540
  ```bash
538
- tl deals list purchase-date-start:2026-01-01 --json
541
+ tl db pg "SELECT al.id, al.weighted_price, al.purchase_date, b.name AS brand
542
+ FROM thoughtleaders_adlink al
543
+ JOIN thoughtleaders_profile p ON p.id = al.creator_profile_id
544
+ JOIN thoughtleaders_profile_brands pb ON pb.profile_id = p.id
545
+ JOIN thoughtleaders_brand b ON b.id = pb.brand_id
546
+ WHERE al.publish_status = 3
547
+ AND al.purchase_date >= '2026-01-01'
548
+ ORDER BY al.purchase_date DESC
549
+ LIMIT 500 OFFSET 0" --json
539
550
  ```
540
551
 
541
552
  "What channels does Nike sponsor?":
@@ -587,7 +598,14 @@ tl recommender top-channels "Cooking" msn:yes --limit 100 --json \
587
598
 
588
599
  "Show sold sponsorships targeting mobile US audiences":
589
600
  ```bash
590
- tl sponsorships list status:sold primary-device:mobile min-us-share:60 --json
601
+ tl db pg "SELECT al.id, c.channel_name, c.demographic_device_primary, c.demographic_usa_share, al.weighted_price
602
+ FROM thoughtleaders_adlink al
603
+ JOIN thoughtleaders_adspot s ON s.id = al.ad_spot_id
604
+ JOIN thoughtleaders_channel c ON c.id = s.channel_id
605
+ WHERE al.publish_status = 3
606
+ AND c.demographic_device_primary = 'mobile'
607
+ AND c.demographic_usa_share >= 60
608
+ LIMIT 500 OFFSET 0" --json
591
609
  ```
592
610
 
593
611
  "Find channels similar to one I know" (similarity recommender, 25 credits per call):
@@ -612,6 +630,23 @@ tl recommender top-brands "USA share" mbn:yes --limit 30 # Top brands (ded
612
630
  tl recommender top-channels "Tech" exclude-for-profile:842 # Drop channels already proposed for profile 842
613
631
  tl recommender inspect-channel 29834 # Per-tag breakdown of a channel's vector
614
632
  tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal profile
615
- tl recommender similar-to-profile 842 --limit 30 # Channels closest to a brand profile's ideal profile
633
+ tl recommender channels-for-profile 842 --limit 30 # Channels closest to a brand profile's ideal profile
634
+ tl recommender channels-for-profile 842 msn:yes language:en # Same, filtered to English MSN channels
635
+ tl recommender channels-for-brand Nike --limit 30 # Same, but takes a brand ref (uses the brand's newest profile with a vector)
636
+ tl recommender channels-for-brand 6037 msn:yes language:en --limit 30
637
+ tl recommender brands-for-channel 29834 --limit 30 # Brands likely to sponsor this channel
638
+ tl recommender brands-for-channel "MrBeast" mbn:yes --limit 30 # Same, restricted to MBN brand profiles
616
639
  ```
640
+
641
+ **Filters on the recommender commands:**
642
+
643
+ | Command | Filters |
644
+ | --- | --- |
645
+ | `top-channels` | `msn:<yes\|no\|all>` (default all), `exclude-for-profile:<id>` |
646
+ | `top-profiles` | `mbn:<yes\|no\|all>` (default all), `exclude-for-channel:<id>` |
647
+ | `top-brands` | `mbn:<yes\|no\|all>` (default all) |
648
+ | `channels-for-profile` | `language:<iso>` (default `en`), `msn:<yes\|no>` (default `no`) |
649
+ | `channels-for-brand` | same as `channels-for-profile` |
650
+ | `brands-for-channel` | `mbn:<yes\|no\|all>` (default `all`) |
651
+
617
652
  Use `tl recommender top` for category/topic discovery (it's ranked) and `tl channels similar` / `tl brands similar` for 1:1 lookalike searches.
@@ -120,9 +120,18 @@ Every Firebolt workflow has two steps:
120
120
  tl recommender top-channels "Tech" msn:yes --limit 50 --json \
121
121
  | jq '.results[].channel_id'
122
122
 
123
- # Or videos for a specific brand's deals (Postgres side, via tl sponsorships)
124
- tl deals list brand:"Nike" --json --limit 500 \
125
- | jq -r '.results[] | select(.article_id != null) | "\(.channel_id):\(.article_id)"'
123
+ # Or videos for a specific brand's deals (Postgres side, via raw SQL)
124
+ tl db pg "SELECT al.id, al.article_id, s.channel_id
125
+ FROM thoughtleaders_adlink al
126
+ JOIN thoughtleaders_adspot s ON s.id = al.ad_spot_id
127
+ JOIN thoughtleaders_profile p ON p.id = al.creator_profile_id
128
+ JOIN thoughtleaders_profile_brands pb ON pb.profile_id = p.id
129
+ JOIN thoughtleaders_brand b ON b.id = pb.brand_id
130
+ WHERE al.publish_status = 3
131
+ AND b.name = 'Nike'
132
+ AND al.article_id IS NOT NULL
133
+ LIMIT 500 OFFSET 0" --json \
134
+ | jq -r '.results[] | "\(.channel_id):\(.article_id)"'
126
135
 
127
136
  # Or videos via Elasticsearch content search
128
137
  tl db es '{