thoughtleaders-cli 0.7.15__tar.gz → 0.8.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/AGENTS.md +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/API.md +5 -5
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/PKG-INFO +6 -6
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/README.md +5 -5
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/agents/tl-analyst.md +6 -6
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/pyproject.toml +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl/SKILL.md +24 -18
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl/references/business-glossary.md +13 -14
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl/references/firebolt-schema.md +4 -4
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl/references/postgres-schema.md +16 -16
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/references/peer-cohort.md +2 -2
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/references/red-flags.md +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/analyze_channel.py +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/anomaly_detector.py +2 -2
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/engagement_ratios.py +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/peer_cohort.py +4 -4
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/report.py +2 -2
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/resolve_channel.py +2 -2
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/tl_cli.py +5 -5
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-keyword-research/SKILL.md +2 -2
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-keyword-research/scripts/search_channels.py +2 -2
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/SKILL.md +15 -15
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/columns_sponsorships.md +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/intelligence_filterset_schema.json +5 -5
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/intelligence_widget_schema.json +10 -10
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/report_glossary.md +5 -5
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/sortable_columns.json +4 -4
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/sponsorship_filterset_schema.json +2 -2
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/sponsorship_widget_schema.json +8 -8
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/widgets.md +12 -12
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-top-partnerships/SKILL.md +6 -6
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-top-partnerships/scripts/top_partnerships.py +16 -16
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/__init__.py +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/brands.py +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/channels.py +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/db.py +39 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/schema.py +9 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/snapshots.py +1 -1
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/sponsorships.py +5 -5
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/filters.py +1 -1
- thoughtleaders_cli-0.8.1/src/tl_cli/query_history.py +94 -0
- thoughtleaders_cli-0.8.1/tests/test_db_repeat_warning.py +121 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests/test_filters.py +1 -1
- thoughtleaders_cli-0.8.1/tests/test_sponsorships.py +41 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/uv.lock +1 -1
- thoughtleaders_cli-0.7.15/tests/test_sponsorships.py +0 -41
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/.github/dependabot.yml +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/.github/workflows/ci.yml +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/.github/workflows/cli-integration.yml +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/.gitignore +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/LICENSE +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/agents/youtube-comment-classifier.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/hooks/scripts/load-tl-skill.mjs +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl/references/elasticsearch-schema.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/.gitignore +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/SKILL.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/references/comment-patterns.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/references/scoring.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/_io_utf8.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/comment_analyzer.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/comment_scraper.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/score.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/video_integrity.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-channel-authenticity/scripts/view_curves.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-keyword-research/references/context-classifier.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-keyword-research/scripts/fetch_context.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-keyword-research/scripts/probe.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/columns_brands.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/columns_channels.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-save-report/references/columns_content.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-views-guarantee/SKILL.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl-views-guarantee/scripts/vg.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/_typer_utils.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/_comments_common.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/bulk_import.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/credits.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/describe.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/doctor.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/recommender.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/reports.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/setup.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/uploads.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/commands/whoami.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/config.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/main.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/output/formatter.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/src/tl_cli/self_update.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests/test_brands_winner_channels.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests/test_channels_lookalike.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests/test_describe.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests/test_http_auth.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests/test_reports.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests/test_setup.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests_cli/AGENTS.md +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests_cli/conftest.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests_cli/test_balance.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests_cli/test_db_es.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests_cli/test_db_fb.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests_cli/test_db_pg.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests_cli/test_schema.py +0 -0
- {thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/tests_cli/test_whoami.py +0 -0
|
@@ -52,7 +52,7 @@ This repo is also a Claude Code plugin, and can directly be installed as one.
|
|
|
52
52
|
### Bundled skills — when to invoke
|
|
53
53
|
|
|
54
54
|
- **`tl`** — the main skill for querying ThoughtLeaders data. Default for any sponsorship / channel / brand / upload / report question.
|
|
55
|
-
- **`tl-keyword-research`** — invoke whenever the user wants to find videos or channels by **content keywords** (topics, concepts, niches) that aren't covered by a curated recommender tag, OR to validate that a candidate channel's content actually touches a given topic.
|
|
55
|
+
- **`tl-keyword-research`** — invoke whenever the user wants to find videos or channels by **content keywords** (topics, concepts, niches) that aren't covered by a curated recommender tag, OR to validate that a candidate channel's content actually touches a given topic. It researches the topic by composing a boolean keyword query (`--any` OR-groups AND'd together, `--not` exclusions) over **≥3 refinement rounds**, returning a **CNF (Conjunctive Normal Form) keyword expression** as the reusable headline artifact alongside the ranked, sponsorability-flagged channels it selects. **Interactive by default** — checkpoints with the user after round 3 unless told to "run autonomously" (or invoked with `autonomous` / `--auto`). The keyword-distribution shape (`{operator, keywords:[{keyword,count}]}`) is an **opt-in mode**, produced only when the user explicitly asks for keyword counts / distribution. **Do not compose keyword sets by hand for `tl db es` content searches — delegate to this skill first.** See `skills/tl/SKILL.md` → *Channel & video discovery* for the four-path decision tree and when to use this vs the recommender / raw SQL.
|
|
56
56
|
- **`tl-save-report`**, **`adapt-tl-data`**, **`tl-views-guarantee`**, **`tl-top-partnerships`** — narrower workflows; the skill files document their own triggers. `tl-top-partnerships` is brand-user-facing: ranks a brand's sold sponsorships by live eCPM vs the sold-date projection and delivers a two-tab Google Sheet via `gws`.
|
|
57
57
|
|
|
58
58
|
### Skill content boundaries
|
|
@@ -182,16 +182,16 @@ curl -sS "$TL_API_BASE/raw/pg" \
|
|
|
182
182
|
-H 'X-TL-Auth: API-KEY' \
|
|
183
183
|
-H 'Content-Type: application/json' \
|
|
184
184
|
-d '{
|
|
185
|
-
"query": "SELECT id, channel_name,
|
|
185
|
+
"query": "SELECT id, channel_name, subscribers FROM thoughtleaders_channel WHERE is_tpp = TRUE ORDER BY subscribers DESC LIMIT 5 OFFSET 0"
|
|
186
186
|
}' | jq
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
```python
|
|
190
190
|
sql = """
|
|
191
|
-
SELECT id, channel_name,
|
|
191
|
+
SELECT id, channel_name, subscribers
|
|
192
192
|
FROM thoughtleaders_channel
|
|
193
|
-
WHERE
|
|
194
|
-
ORDER BY
|
|
193
|
+
WHERE is_tpp = TRUE
|
|
194
|
+
ORDER BY subscribers DESC
|
|
195
195
|
LIMIT 5 OFFSET 0
|
|
196
196
|
"""
|
|
197
197
|
print(post('/raw/pg', {'query': sql}))
|
|
@@ -200,7 +200,7 @@ print(post('/raw/pg', {'query': sql}))
|
|
|
200
200
|
```json
|
|
201
201
|
{
|
|
202
202
|
"results": [
|
|
203
|
-
{"id": 12345, "channel_name": "MrBeast", "
|
|
203
|
+
{"id": 12345, "channel_name": "MrBeast", "subscribers": 320000000},
|
|
204
204
|
...
|
|
205
205
|
],
|
|
206
206
|
"total": 5,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtleaders-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
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
|
|
@@ -121,7 +121,7 @@ tl whoami
|
|
|
121
121
|
# profile → profile_brands → brand chain.
|
|
122
122
|
tl db pg "SELECT al.id, al.weighted_price, al.purchase_date
|
|
123
123
|
FROM thoughtleaders_adlink al
|
|
124
|
-
JOIN thoughtleaders_profile p ON p.id = al.
|
|
124
|
+
JOIN thoughtleaders_profile p ON p.id = al.advertiser_profile_id
|
|
125
125
|
JOIN thoughtleaders_profile_brands pb ON pb.profile_id = p.id
|
|
126
126
|
JOIN thoughtleaders_brand b ON b.id = pb.brand_id
|
|
127
127
|
WHERE al.publish_status = 3
|
|
@@ -228,7 +228,7 @@ ThoughtLeaders has its internal terminology that's exposed throughout this tool.
|
|
|
228
228
|
* **Adspots** — types of ads a channel carries (e.g. mention, dedicated video, product placement). Returned by `tl channels show`; each carries price/cost and a computed CPM.
|
|
229
229
|
* **AdLink** — engineering / DB name for the row that backs a sponsorship. Treat as interchangeable with "sponsorship"; the table is `thoughtleaders_adlink`.
|
|
230
230
|
* **MSN** (Media Selling Network) — the ~12k YouTube channels that have opted in to receive sponsorship offers. A channel is in MSN if `channel.media_selling_network_join_date IS NOT NULL`.
|
|
231
|
-
* **TPP** (ThoughtLeaders Partner Program) — TL's closest-partner channels, a strict subset of MSN. A channel is TPP if `channel.
|
|
231
|
+
* **TPP** (ThoughtLeaders Partner Program) — TL's closest-partner channels, a strict subset of MSN. A channel is TPP if `channel.is_tpp = TRUE`. Prefer TPP channels when booking — fastest response, easiest to close.
|
|
232
232
|
* **MBN** (Media Buying Network) — the brand-side counterpart to MSN: profiles that have opted in to receive proposed sponsorships (`profile.media_buying_network_join_date IS NOT NULL`).
|
|
233
233
|
|
|
234
234
|
Sponsorships are the centre of attention in ThoughtLeaders — all other analytics and operations serve to produce or optimise sponsorships. Note that the term "Sponsorship" is wide and encompasses pre-deal stages. The funnel is large at the Sponsorship end and narrowest at the Deal end.
|
|
@@ -250,7 +250,7 @@ Talk naturally in Claude Code:
|
|
|
250
250
|
```
|
|
251
251
|
/tl Which channels did we sponsor in Q1?
|
|
252
252
|
/tl sold sponsorships for Nike in Q1
|
|
253
|
-
/tl show me pending proposals with
|
|
253
|
+
/tl show me pending proposals with scheduled dates in April
|
|
254
254
|
/tl what channels does Nike sponsor?
|
|
255
255
|
/tl find me Cooking creators in the US with mobile-heavy audiences
|
|
256
256
|
/tl check my balance
|
|
@@ -258,7 +258,7 @@ Talk naturally in Claude Code:
|
|
|
258
258
|
|
|
259
259
|
Resource-specific slash commands:
|
|
260
260
|
```
|
|
261
|
-
/tl-sponsorships pending with
|
|
261
|
+
/tl-sponsorships pending with scheduled dates in April
|
|
262
262
|
/tl-reports run my Q1 pipeline
|
|
263
263
|
/tl-balance
|
|
264
264
|
```
|
|
@@ -290,7 +290,7 @@ By default, output is a styled table in the terminal and JSON when piped.
|
|
|
290
290
|
|
|
291
291
|
```bash
|
|
292
292
|
tl sponsorships show 12345 --json | jq '.results'
|
|
293
|
-
tl db pg "SELECT id, channel_name FROM thoughtleaders_channel WHERE
|
|
293
|
+
tl db pg "SELECT id, channel_name FROM thoughtleaders_channel WHERE is_tpp = TRUE
|
|
294
294
|
LIMIT 200 OFFSET 0" --csv > tpp.csv
|
|
295
295
|
tl channels show "MrBeast" --md # markdown table for Slack / docs
|
|
296
296
|
tl channels show "MrBeast" --toon # token-efficient encoding for LLMs
|
|
@@ -93,7 +93,7 @@ tl whoami
|
|
|
93
93
|
# profile → profile_brands → brand chain.
|
|
94
94
|
tl db pg "SELECT al.id, al.weighted_price, al.purchase_date
|
|
95
95
|
FROM thoughtleaders_adlink al
|
|
96
|
-
JOIN thoughtleaders_profile p ON p.id = al.
|
|
96
|
+
JOIN thoughtleaders_profile p ON p.id = al.advertiser_profile_id
|
|
97
97
|
JOIN thoughtleaders_profile_brands pb ON pb.profile_id = p.id
|
|
98
98
|
JOIN thoughtleaders_brand b ON b.id = pb.brand_id
|
|
99
99
|
WHERE al.publish_status = 3
|
|
@@ -200,7 +200,7 @@ ThoughtLeaders has its internal terminology that's exposed throughout this tool.
|
|
|
200
200
|
* **Adspots** — types of ads a channel carries (e.g. mention, dedicated video, product placement). Returned by `tl channels show`; each carries price/cost and a computed CPM.
|
|
201
201
|
* **AdLink** — engineering / DB name for the row that backs a sponsorship. Treat as interchangeable with "sponsorship"; the table is `thoughtleaders_adlink`.
|
|
202
202
|
* **MSN** (Media Selling Network) — the ~12k YouTube channels that have opted in to receive sponsorship offers. A channel is in MSN if `channel.media_selling_network_join_date IS NOT NULL`.
|
|
203
|
-
* **TPP** (ThoughtLeaders Partner Program) — TL's closest-partner channels, a strict subset of MSN. A channel is TPP if `channel.
|
|
203
|
+
* **TPP** (ThoughtLeaders Partner Program) — TL's closest-partner channels, a strict subset of MSN. A channel is TPP if `channel.is_tpp = TRUE`. Prefer TPP channels when booking — fastest response, easiest to close.
|
|
204
204
|
* **MBN** (Media Buying Network) — the brand-side counterpart to MSN: profiles that have opted in to receive proposed sponsorships (`profile.media_buying_network_join_date IS NOT NULL`).
|
|
205
205
|
|
|
206
206
|
Sponsorships are the centre of attention in ThoughtLeaders — all other analytics and operations serve to produce or optimise sponsorships. Note that the term "Sponsorship" is wide and encompasses pre-deal stages. The funnel is large at the Sponsorship end and narrowest at the Deal end.
|
|
@@ -222,7 +222,7 @@ Talk naturally in Claude Code:
|
|
|
222
222
|
```
|
|
223
223
|
/tl Which channels did we sponsor in Q1?
|
|
224
224
|
/tl sold sponsorships for Nike in Q1
|
|
225
|
-
/tl show me pending proposals with
|
|
225
|
+
/tl show me pending proposals with scheduled dates in April
|
|
226
226
|
/tl what channels does Nike sponsor?
|
|
227
227
|
/tl find me Cooking creators in the US with mobile-heavy audiences
|
|
228
228
|
/tl check my balance
|
|
@@ -230,7 +230,7 @@ Talk naturally in Claude Code:
|
|
|
230
230
|
|
|
231
231
|
Resource-specific slash commands:
|
|
232
232
|
```
|
|
233
|
-
/tl-sponsorships pending with
|
|
233
|
+
/tl-sponsorships pending with scheduled dates in April
|
|
234
234
|
/tl-reports run my Q1 pipeline
|
|
235
235
|
/tl-balance
|
|
236
236
|
```
|
|
@@ -262,7 +262,7 @@ By default, output is a styled table in the terminal and JSON when piped.
|
|
|
262
262
|
|
|
263
263
|
```bash
|
|
264
264
|
tl sponsorships show 12345 --json | jq '.results'
|
|
265
|
-
tl db pg "SELECT id, channel_name FROM thoughtleaders_channel WHERE
|
|
265
|
+
tl db pg "SELECT id, channel_name FROM thoughtleaders_channel WHERE is_tpp = TRUE
|
|
266
266
|
LIMIT 200 OFFSET 0" --csv > tpp.csv
|
|
267
267
|
tl channels show "MrBeast" --md # markdown table for Slack / docs
|
|
268
268
|
tl channels show "MrBeast" --toon # token-efficient encoding for LLMs
|
|
@@ -38,7 +38,7 @@ If estimated cost > 200 credits, ask the user to confirm before proceeding.
|
|
|
38
38
|
```sql
|
|
39
39
|
tl db pg "SELECT b.name, SUM(a.weighted_price) AS pipeline, COUNT(*) AS deals
|
|
40
40
|
FROM thoughtleaders_adlink a
|
|
41
|
-
JOIN thoughtleaders_profile p ON a.
|
|
41
|
+
JOIN thoughtleaders_profile p ON a.advertiser_profile_id = p.id
|
|
42
42
|
JOIN thoughtleaders_profile_brands pb ON p.id = pb.profile_id
|
|
43
43
|
JOIN thoughtleaders_brand b ON pb.brand_id = b.id
|
|
44
44
|
WHERE a.publish_status = 3
|
|
@@ -52,16 +52,16 @@ One query → ranked list. No client-side aggregation, no paginated walk.
|
|
|
52
52
|
### Cross-resource analysis (raw PG)
|
|
53
53
|
"Show me deal slippage this month"
|
|
54
54
|
```sql
|
|
55
|
-
tl db pg "SELECT a.id, a.
|
|
55
|
+
tl db pg "SELECT a.id, a.scheduled_date, a.publish_status, b.name AS brand, ch.channel_name
|
|
56
56
|
FROM thoughtleaders_adlink a
|
|
57
57
|
JOIN thoughtleaders_adspot s ON a.ad_spot_id = s.id
|
|
58
58
|
JOIN thoughtleaders_channel ch ON s.channel_id = ch.id
|
|
59
|
-
JOIN thoughtleaders_profile p ON a.
|
|
59
|
+
JOIN thoughtleaders_profile p ON a.advertiser_profile_id = p.id
|
|
60
60
|
JOIN thoughtleaders_profile_brands pb ON p.id = pb.profile_id
|
|
61
61
|
JOIN thoughtleaders_brand b ON pb.brand_id = b.id
|
|
62
62
|
WHERE a.publish_status = 10
|
|
63
|
-
AND a.
|
|
64
|
-
ORDER BY a.
|
|
63
|
+
AND a.scheduled_date < CURRENT_DATE
|
|
64
|
+
ORDER BY a.scheduled_date
|
|
65
65
|
LIMIT 100 OFFSET 0"
|
|
66
66
|
```
|
|
67
67
|
Then suggest `tl sponsorships comment-add <id> "..."` for each.
|
|
@@ -83,7 +83,7 @@ Then suggest `tl sponsorships comment-add <id> "..."` for each.
|
|
|
83
83
|
"Give me a full picture of channel 12345"
|
|
84
84
|
1. `tl channels show 12345 --json` → profile, scores, demographics (structured — wraps several joins already)
|
|
85
85
|
2. `tl snapshots channel 12345 --json` → growth over time (snapshots wrap interpolation logic)
|
|
86
|
-
3. `tl db pg "SELECT id,
|
|
86
|
+
3. `tl db pg "SELECT id, scheduled_date, publish_status, price FROM thoughtleaders_adlink WHERE ad_spot_id IN (SELECT id FROM thoughtleaders_adspot WHERE channel_id = 12345) ORDER BY scheduled_date DESC LIMIT 100 OFFSET 0"` — deal history with the columns you actually want, no over-fetch.
|
|
87
87
|
4. `tl uploads list channel:12345 --json` → recent content
|
|
88
88
|
|
|
89
89
|
### Transcript / brand-mention search (raw ES)
|
|
@@ -37,7 +37,7 @@ If doing a database query, follow this recipe:
|
|
|
37
37
|
```bash
|
|
38
38
|
tl db pg "SELECT al.id, b.name AS brand, al.weighted_price AS price
|
|
39
39
|
FROM thoughtleaders_adlink al
|
|
40
|
-
JOIN thoughtleaders_profile p ON p.id = al.
|
|
40
|
+
JOIN thoughtleaders_profile p ON p.id = al.advertiser_profile_id
|
|
41
41
|
JOIN thoughtleaders_profile_brands pb ON pb.profile_id = p.id
|
|
42
42
|
JOIN thoughtleaders_brand b ON b.id = pb.brand_id
|
|
43
43
|
WHERE al.publish_status = 3
|
|
@@ -87,29 +87,29 @@ Other key concepts:
|
|
|
87
87
|
- **Comments** — notes attached to sponsorships, channels, or brands
|
|
88
88
|
- **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.
|
|
89
89
|
- **Profiles** — actors that own sponsorship records on behalf of either side of a deal. A profile is either buyer-side or seller-side:
|
|
90
|
-
- *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, `
|
|
90
|
+
- *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, `advertiser_profile` is the buyer-side profile, and `advertiser_id` is the buyer-side user who created the record — the advertiser is always the buyer/brand side, never the YouTube creator (the channel hangs off `ad_spot_id`).
|
|
91
91
|
- *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`.
|
|
92
92
|
- **How to tell them apart** — three signals on the `thoughtleaders_profile` row, used in this order:
|
|
93
93
|
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.
|
|
94
94
|
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.
|
|
95
|
-
- Org scoping for sponsorships is profile-mediated: a sponsorship belongs to your org if **either** `
|
|
95
|
+
- Org scoping for sponsorships is profile-mediated: a sponsorship belongs to your org if **either** `advertiser_profile.organization` (brand side) **or** `ad_spot.channel.publication.profile.organization` (publisher side) matches yours.
|
|
96
96
|
- **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.
|
|
97
97
|
- **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.
|
|
98
|
-
- **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.
|
|
98
|
+
- **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_tpp` 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.
|
|
99
99
|
- **`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.
|
|
100
|
-
- **`
|
|
101
|
-
- **`
|
|
100
|
+
- **`subscribers`** (on channels) — the channel's subscriber count.
|
|
101
|
+
- **`projected_views`** (on channels) — projected views per video on that channel. Forward-looking estimate. May be null when not yet computed. ⚠️ NOT actual views and NOT ad-industry "impressions" (ads served).
|
|
102
102
|
- **`views`** (on sponsorships) — actual view count of the sold and published sponsored video, accessible when `article_id` is set.
|
|
103
|
-
- **`
|
|
103
|
+
- **`views_guarantee`** (on sponsorships) — projected/guaranteed impressions for the sponsorship. Numeric.
|
|
104
104
|
- **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.
|
|
105
105
|
- **CPM** has two distinct meanings depending on level — pick the one the user actually wants:
|
|
106
|
-
- **Channel CPM** = `(adspot.price / channel.
|
|
107
|
-
- **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 `
|
|
106
|
+
- **Channel CPM** = `(adspot.price / channel.projected_views) * 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>`.
|
|
107
|
+
- **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 `projected_views` fields.
|
|
108
108
|
- Where possible, calculate the correct CPM in a SQL expression.
|
|
109
109
|
- **Sponsorship dates** — each sponsorship has four distinct dates, useful for different queries:
|
|
110
110
|
- **`created_at`** — when the sponsorship record was created in the system
|
|
111
111
|
- **`purchase_date`** — when the sponsorship was purchased (i.e. when the deal was made); These make up bookings.
|
|
112
|
-
- **`
|
|
112
|
+
- **`scheduled_date`** — the date the video is/was expected to be published (scheduled)
|
|
113
113
|
- **`publish_date`** — the date the video was actually published; These make up live ads.
|
|
114
114
|
- **Credits** — every data query costs credits; use `tl describe` to see rates. Top up with `tl credits buy --amount-usd N` (free; opens a browser checkout). New accounts get a starter balance on first `tl auth login`; the rate is shown by `tl credits pricing`.
|
|
115
115
|
|
|
@@ -240,6 +240,7 @@ tl channels update 12345 '{"demographic_geo": {"US": 60, "UK": 12, "CA": 8}}'
|
|
|
240
240
|
tl channels update 12345 '{"demographic_male_share": 55, "demographic_usa_share": 70}'
|
|
241
241
|
tl channels update 12345 '{"outreach_email": "press@creator.com"}'
|
|
242
242
|
tl channels update 12345 '{"all_emails": {"press@creator.com": {"timestamp": "2026-06-25T12:00:00+00:00", "source": "manual"}}}'
|
|
243
|
+
tl channels update 12345 '{"media_selling_network_join_date": "2026-01-15"}'
|
|
243
244
|
```
|
|
244
245
|
|
|
245
246
|
**Channel contact emails.** Besides demographics, `tl channels update` accepts two
|
|
@@ -254,6 +255,11 @@ contact fields:
|
|
|
254
255
|
`scheduled_rescrape`. Use a descriptive token such as `manual` for hand-added entries.
|
|
255
256
|
An edit **replaces** the whole `all_emails` object, so include every entry you want to keep.
|
|
256
257
|
|
|
258
|
+
**MSN membership.** `tl channels update` also accepts **`media_selling_network_join_date`** —
|
|
259
|
+
the date the channel joined the Media Selling Network, as a `YYYY-MM-DD` string. This date is
|
|
260
|
+
itself the membership flag: set it to add the channel to MSN (it then reads as `is_msn` and
|
|
261
|
+
appears in MSN-filtered queries), or send `null` to remove the channel from the network.
|
|
262
|
+
|
|
257
263
|
### Creating and vetting sponsorships
|
|
258
264
|
|
|
259
265
|
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.
|
|
@@ -404,8 +410,8 @@ tl db fb "SELECT age, view_count, like_count FROM article_metrics
|
|
|
404
410
|
WHERE channel_id = 12345 AND id = 'dQw4w9WgXcQ'
|
|
405
411
|
ORDER BY age"
|
|
406
412
|
|
|
407
|
-
# Channel
|
|
408
|
-
tl db fb "SELECT scrape_date, total_views,
|
|
413
|
+
# Channel subscribers over time
|
|
414
|
+
tl db fb "SELECT scrape_date, total_views, subscribers FROM channel_metrics
|
|
409
415
|
WHERE id = 12345
|
|
410
416
|
ORDER BY scrape_date"
|
|
411
417
|
```
|
|
@@ -418,7 +424,7 @@ See [references/firebolt-schema.md](references/firebolt-schema.md) for accepted-
|
|
|
418
424
|
# Example: Top brands by deal count
|
|
419
425
|
tl db pg "SELECT b.name, COUNT(*) AS deals
|
|
420
426
|
FROM thoughtleaders_adlink a
|
|
421
|
-
JOIN thoughtleaders_profile p ON a.
|
|
427
|
+
JOIN thoughtleaders_profile p ON a.advertiser_profile_id = p.id
|
|
422
428
|
JOIN thoughtleaders_profile_brands pb ON p.id = pb.profile_id
|
|
423
429
|
JOIN thoughtleaders_brand b ON pb.brand_id = b.id
|
|
424
430
|
WHERE a.publish_status = 3
|
|
@@ -557,20 +563,20 @@ Use `tl recommender top` for category/topic discovery (it's ranked) and `tl chan
|
|
|
557
563
|
|
|
558
564
|
Then run the actual content search via `tl db es` (`multi_match` on the `title`, `summary`, `transcript` fields) with the surviving high-count keywords. The skill's full procedure (Phase 1 = seed expansion by you; Phase 2 = the script) is in the `tl-keyword-research` skill file.
|
|
559
565
|
|
|
560
|
-
**Path 4. Pure attribute filter** — user wants channels filtered by metadata like: `
|
|
566
|
+
**Path 4. Pure attribute filter** — user wants channels filtered by metadata like: `is_tpp`, `language`, `demographic_device_primary`, country share in `demographic_geo` JSON, aggregations, joins. Use `tl db pg` with a SELECT on `thoughtleaders_channel`. Run `tl schema pg thoughtleaders_channel` once to confirm the live column set; the columns in the examples are stable.
|
|
561
567
|
|
|
562
568
|
```bash
|
|
563
569
|
# All TPP (TL-managed) channels — pure attribute filter, not a category query
|
|
564
570
|
tl db pg "SELECT id, channel_name, content_category, total_views
|
|
565
571
|
FROM thoughtleaders_channel
|
|
566
|
-
WHERE
|
|
572
|
+
WHERE is_tpp = TRUE
|
|
567
573
|
ORDER BY total_views DESC
|
|
568
574
|
LIMIT 200 OFFSET 0"
|
|
569
575
|
|
|
570
576
|
# Mobile-first non-TPP channels — device share, not topic
|
|
571
577
|
tl db pg "SELECT id, channel_name, demographic_device_primary, total_views
|
|
572
578
|
FROM thoughtleaders_channel
|
|
573
|
-
WHERE
|
|
579
|
+
WHERE is_tpp = FALSE
|
|
574
580
|
AND demographic_device_primary = 'mobile'
|
|
575
581
|
ORDER BY total_views DESC
|
|
576
582
|
LIMIT 100 OFFSET 0"
|
|
@@ -632,7 +638,7 @@ Users only see data their plan allows:
|
|
|
632
638
|
```bash
|
|
633
639
|
tl db pg "SELECT al.id, al.weighted_price, al.purchase_date, b.name AS brand
|
|
634
640
|
FROM thoughtleaders_adlink al
|
|
635
|
-
JOIN thoughtleaders_profile p ON p.id = al.
|
|
641
|
+
JOIN thoughtleaders_profile p ON p.id = al.advertiser_profile_id
|
|
636
642
|
JOIN thoughtleaders_profile_brands pb ON pb.profile_id = p.id
|
|
637
643
|
JOIN thoughtleaders_brand b ON b.id = pb.brand_id
|
|
638
644
|
WHERE al.publish_status = 3
|
|
@@ -734,7 +740,7 @@ tl db es '{
|
|
|
734
740
|
# TL-brokered deal count for the brand (PG, not ES — adlinks where the brand is on the creator profile)
|
|
735
741
|
tl db pg "SELECT COUNT(*) AS tl_brokered
|
|
736
742
|
FROM thoughtleaders_adlink al
|
|
737
|
-
JOIN thoughtleaders_profile p ON p.id = al.
|
|
743
|
+
JOIN thoughtleaders_profile p ON p.id = al.advertiser_profile_id
|
|
738
744
|
JOIN thoughtleaders_profile_brands pb ON pb.profile_id = p.id
|
|
739
745
|
WHERE pb.brand_id = 21416 AND al.article_id IS NOT NULL"
|
|
740
746
|
```
|
{thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl/references/business-glossary.md
RENAMED
|
@@ -18,7 +18,7 @@ Maps business terms to database concepts.
|
|
|
18
18
|
| **Weighted pipeline** | `SUM(weighted_price)` for open opps | `weighted_price` is derived from the brand/channel approval combination on OPEN deals. |
|
|
19
19
|
| **Ad is live** | `publish_date IS NOT NULL` | Until publish_date is set, ad is not on YouTube |
|
|
20
20
|
| **Cancellation risk** | Sold but `publish_date IS NULL` | Sold deals without publish_date can still be canceled |
|
|
21
|
-
| **Immediately bookable** | `
|
|
21
|
+
| **Immediately bookable** | `is_tpp = true` | TPP channels — TL's closest partners. Fastest to respond, easiest to close, prefer when booking. |
|
|
22
22
|
|
|
23
23
|
## Performance Grade (`adlink.performance_grade`)
|
|
24
24
|
|
|
@@ -35,7 +35,7 @@ Maps business terms to database concepts.
|
|
|
35
35
|
|
|
36
36
|
| Field | Description |
|
|
37
37
|
|-------|-------------|
|
|
38
|
-
| `adlink.
|
|
38
|
+
| `adlink.views_guarantee` | The number of views guaranteed for the ad (bigint). 0 or NULL = no guarantee. |
|
|
39
39
|
| `adlink.view_guarantee_hit_date` | Timestamp when the guarantee was met. NULL = not yet hit or no guarantee. |
|
|
40
40
|
| `adlink.projected_views_at_purchase_date` | Projected views at time of purchase (used for CPM estimation). |
|
|
41
41
|
|
|
@@ -65,7 +65,7 @@ Maps business terms to database concepts.
|
|
|
65
65
|
| `owner_sales_id` | `adlink` | **Most important.** Person responsible for closing the deal and for the revenue. Final accountability. |
|
|
66
66
|
| `owner_advertiser_id` | `adlink` | Brand-side owner for this specific deal |
|
|
67
67
|
| `owner_publisher_id` | `adlink` | Channel-side owner for this specific deal |
|
|
68
|
-
| `
|
|
68
|
+
| `advertiser_id` | `adlink` | The brand-side (advertiser/buyer) user account that *created the record*. Record lineage only; for accountability use the `owner_*` fields, for the brand use `advertiser_profile_id`, for the channel go via `ad_spot_id`. |
|
|
69
69
|
| `owner_advertiser_id` | `profile` | **Account owner.** Who owns the brand relationship overall. Often same person as owner_sales on adlinks, but not always. |
|
|
70
70
|
| `owner_publisher_id` | `profile` | Channel relationship owner on the profile level |
|
|
71
71
|
| `owner_sales_id` | `profile` | Sales owner at profile level |
|
|
@@ -77,7 +77,7 @@ Maps business terms to database concepts.
|
|
|
77
77
|
- Channels where TL has **≥80% confidence** they can buy an ad tomorrow
|
|
78
78
|
- Key data: **who is the contact** to buy the ad from
|
|
79
79
|
- `thoughtleaders_channel.media_selling_network_join_date` = when channel joined MSN
|
|
80
|
-
- `thoughtleaders_channel.
|
|
80
|
+
- `thoughtleaders_channel.is_tpp` = TPP channel — TL's closest partners, a strict subset of MSN. **Prefer when booking**: fastest response, easiest to close. Not a gating flag; not a process barrier.
|
|
81
81
|
- **Rule:** Only one active adspot with `integration=mention` per channel at any time
|
|
82
82
|
- MSN quality depends on having current, accurate contact info
|
|
83
83
|
|
|
@@ -87,13 +87,13 @@ Vocabulary that AMs use about channels, mapped to the actual DB encoding. Most o
|
|
|
87
87
|
|
|
88
88
|
| Business Term | DB Concept | Notes |
|
|
89
89
|
|---------------|------------|-------|
|
|
90
|
-
| **Subscribers** | `thoughtleaders_channel.
|
|
91
|
-
| **MSN member** | `media_selling_network_join_date IS NOT NULL` | Whole MSN pool. NOT `
|
|
92
|
-
| **TPP / TL channel** | `
|
|
90
|
+
| **Subscribers** | `thoughtleaders_channel.subscribers` (bigint) | Channel subscriber count. |
|
|
91
|
+
| **MSN member** | `media_selling_network_join_date IS NOT NULL` | Whole MSN pool. NOT `is_tpp = true` — that's the VIP subset only. |
|
|
92
|
+
| **TPP / TL channel** | `is_tpp = true` | TL's closest-partner channels (~144 at 100k+ subscribers). Prefer when booking — fastest to respond, easiest to close. A strict subset of MSN; *don't* use as a general "MSN" proxy — silently drops ~98% of MSN. |
|
|
93
93
|
| **Active channel** | `is_active = true AND last_published >= CURRENT_DATE - INTERVAL '120 days'` | Standard filter for "channel is live and posting." Always include `is_active = true` in channel queries. |
|
|
94
94
|
| **Country / Geo of a deal** | `thoughtleaders_channel.country` (ISO 3166-1 alpha-2) | `thoughtleaders_adlink` has NO geo column. Geo for sponsorships almost always means the channel's country. |
|
|
95
95
|
| **Language of a channel** | `thoughtleaders_channel.language` (short ISO 639 code) | ⚠️ Short ISO 639 codes — NOT BCP-47. Mostly 2-letter ISO 639-1 (`en`, `pt`, `hi`) for major languages; occasionally 3-letter ISO 639-2/3 (`arc`, `arz`, `ase`, `ceb`) for languages without a 2-letter code. Filtering with BCP-47 (`en-US`/`pt-BR`) returns zero. Don't assume `LENGTH(language) = 2`. |
|
|
96
|
-
| **Brand on a deal** | `adlink →
|
|
96
|
+
| **Brand on a deal** | `adlink → advertiser_profile_id → profile_brands.profile_id → profile_brands.brand_id → brand` | 3-table chain. There is NO direct `brand_id` on adlink. See [postgres-schema.md](postgres-schema.md). |
|
|
97
97
|
| **Channel on a deal** | `adlink.ad_spot_id → adspot.channel_id → channel` | NO direct `channel_id` on adlink. |
|
|
98
98
|
| **Brand-virgin / VPN-virgin (etc.)** | Channel has no `adlink` row joined to any of the target brand_ids | Used in candidate sourcing ("never sponsored by any VPN brand"). Caveat: only catches TL-brokered deals; channels that ran the brand directly (no TL involvement) appear "virgin" but aren't — cross-check ES `sponsored_brand_mentions` before final outreach. |
|
|
99
99
|
| **Channel quality score** *(internal-only)* | `sponsorship_score` on the indexed channel doc + `thoughtleaders_channel.sponsorship_score` (PG) | TL-internal composite score combining engagement, fulfillment, and historical sponsorship performance. **Use it internally to rank/tiebreak candidates, but do NOT quote the raw decimal in AM-facing or external output** — the score isn't documented to AMs and the absolute value isn't meaningful without context. In AM-facing prose, translate to qualitative language: "top-quartile fit," "strongest quality score in the candidate set," "high sponsorship-quality signal." |
|
|
@@ -104,11 +104,11 @@ AMs use "PV" loosely. There are three different DB fields, each meaning somethin
|
|
|
104
104
|
|
|
105
105
|
| AM Term | DB Field | What it actually is |
|
|
106
106
|
|---------|----------|---------------------|
|
|
107
|
-
| **PV (channel baseline)** | `thoughtleaders_channel.
|
|
107
|
+
| **PV (channel baseline)** | `thoughtleaders_channel.projected_views` | Channel-level "typical views per video" used as CPM denominator. ⚠️ Coverage and freshness vary; cross-check Firebolt longform median for hero-tier deals. |
|
|
108
108
|
| **PV (deal-specific)** | `thoughtleaders_adlink.projected_views_at_purchase_date` | Snapshot of projected views at the moment the deal was sold. Use this for historical CPM analysis. |
|
|
109
|
-
| **VG (View Guarantee)** | `thoughtleaders_adlink.
|
|
109
|
+
| **VG (View Guarantee)** | `thoughtleaders_adlink.views_guarantee` | The contractual minimum views the brand is guaranteed. 0/NULL = no guarantee. NOT the same as PV — VG is a contractual floor, PV is an estimate. |
|
|
110
110
|
|
|
111
|
-
When an AM says "what's the PV on this channel?" — they almost always mean `channel.
|
|
111
|
+
When an AM says "what's the PV on this channel?" — they almost always mean `channel.projected_views`. When they say "what was the PV on this deal?" — they mean `adlink.projected_views_at_purchase_date`. When they say "did we hit the VG?" — they mean `adlink.view_guarantee_hit_date IS NOT NULL`.
|
|
112
112
|
|
|
113
113
|
## Channel Sponsorship Signals
|
|
114
114
|
|
|
@@ -147,9 +147,8 @@ AMs and brand-facing readers don't know (or care) what Postgres, Elasticsearch,
|
|
|
147
147
|
| `sponsored_brand_mentions` (ES field) | **"tracked sponsorships"** / **"logged sponsored mentions"** / **"sponsored videos we tracked"** | Per-video brand-ID tags showing which brands paid for that video |
|
|
148
148
|
| `organic_brand_mentions` (ES field) | **"organic / unpaid mentions"** | Brand mentioned in a video without a paid sponsorship |
|
|
149
149
|
| `publish_status = 3` | **"sold"** | Already in glossary; never write the integer to AMs |
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
152
|
-
| `impression` (PG column) | **"projected views"** / **"PV"** | Already in glossary; flagged for reliability caveats |
|
|
150
|
+
| `advertiser_profile_id` chain / 3-table join | **"the brand's record"** | Engineering plumbing for brand lookup; AMs just hear "the brand" |
|
|
151
|
+
| `projected_views` (PG column) | **"projected views"** / **"PV"** | Flagged for reliability caveats |
|
|
153
152
|
|
|
154
153
|
### Common translations in context
|
|
155
154
|
|
{thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl/references/firebolt-schema.md
RENAMED
|
@@ -71,7 +71,7 @@ Tracks YouTube video metrics over time. Each row = one scrape of one video on on
|
|
|
71
71
|
|--------|------|-------------|
|
|
72
72
|
| `id` | INT | TL channel ID |
|
|
73
73
|
| `total_views` | INT | Channel total views at time of scrape |
|
|
74
|
-
| `
|
|
74
|
+
| `subscribers` | INT | Subscriber count at time of scrape |
|
|
75
75
|
| `scrape_date` | DATE | When this data point was captured |
|
|
76
76
|
|
|
77
77
|
**Primary Index: `(id)`**
|
|
@@ -124,7 +124,7 @@ tl recommender top-channels "Tech" msn:yes --limit 50 --json \
|
|
|
124
124
|
tl db pg "SELECT al.id, al.article_id, s.channel_id
|
|
125
125
|
FROM thoughtleaders_adlink al
|
|
126
126
|
JOIN thoughtleaders_adspot s ON s.id = al.ad_spot_id
|
|
127
|
-
JOIN thoughtleaders_profile p ON p.id = al.
|
|
127
|
+
JOIN thoughtleaders_profile p ON p.id = al.advertiser_profile_id
|
|
128
128
|
JOIN thoughtleaders_profile_brands pb ON pb.profile_id = p.id
|
|
129
129
|
JOIN thoughtleaders_brand b ON b.id = pb.brand_id
|
|
130
130
|
WHERE al.publish_status = 3
|
|
@@ -182,7 +182,7 @@ tl db fb "SELECT id, age, view_count, like_count, comment_count, duration, publi
|
|
|
182
182
|
### Channel subscriber/view growth over time
|
|
183
183
|
|
|
184
184
|
```bash
|
|
185
|
-
tl db fb "SELECT scrape_date, total_views,
|
|
185
|
+
tl db fb "SELECT scrape_date, total_views, subscribers
|
|
186
186
|
FROM channel_metrics
|
|
187
187
|
WHERE id = 12345
|
|
188
188
|
ORDER BY scrape_date"
|
|
@@ -191,7 +191,7 @@ tl db fb "SELECT scrape_date, total_views, reach
|
|
|
191
191
|
### Compare multiple channels' growth
|
|
192
192
|
|
|
193
193
|
```bash
|
|
194
|
-
tl db fb "SELECT id, scrape_date, total_views,
|
|
194
|
+
tl db fb "SELECT id, scrape_date, total_views, subscribers
|
|
195
195
|
FROM channel_metrics
|
|
196
196
|
WHERE id IN (123, 456, 789)
|
|
197
197
|
ORDER BY id, scrape_date"
|
{thoughtleaders_cli-0.7.15 → thoughtleaders_cli-0.8.1}/skills/tl/references/postgres-schema.md
RENAMED
|
@@ -19,9 +19,9 @@ The main sponsorships table. Each row = one sponsorship between a brand and a Yo
|
|
|
19
19
|
The profile table is tightly coupled with the brand table for media buyers, so many reports that operate on the brand levels must access the profile data first.
|
|
20
20
|
|
|
21
21
|
> 🚨 **Columns that DO NOT exist on `thoughtleaders_adlink` — common hallucinations:**
|
|
22
|
-
> - ❌ `brand_id` — there is NO direct brand FK. Brand is reached via `
|
|
23
|
-
> - ❌ `organization_id` — there is NO direct org FK. Org is reached via `
|
|
24
|
-
> - ❌ `channel_id` — channel is reached via `ad_spot_id → adspot.channel_id → channel`. Do NOT substitute `
|
|
22
|
+
> - ❌ `brand_id` — there is NO direct brand FK. Brand is reached via `advertiser_profile_id → profile → profile_brands → brand`.
|
|
23
|
+
> - ❌ `organization_id` — there is NO direct org FK. Org is reached via `advertiser_profile_id → profile.organization_id → organization`.
|
|
24
|
+
> - ❌ `channel_id` — channel is reached via `ad_spot_id → adspot.channel_id → channel`. Do NOT substitute `advertiser_id` — that's the brand-side user who created the record, not the channel.
|
|
25
25
|
> - ❌ `youtube_id` (on channel) — use `external_channel_id`.
|
|
26
26
|
> - ❌ `msn_join_date` (on channel) — use `media_selling_network_join_date`.
|
|
27
27
|
> - ❌ `mbn_join_date` (on profile) — use `media_buying_network_join_date`.
|
|
@@ -35,12 +35,12 @@ The profile table is tightly coupled with the brand table for media buyers, so m
|
|
|
35
35
|
| `updated_at` | timestamptz | Last modification |
|
|
36
36
|
| `publish_status` | int | Deal status (see constants below) |
|
|
37
37
|
| `ad_spot_id` | int FK | → `thoughtleaders_adspot.id` |
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
38
|
+
| `advertiser_profile_id` | int FK | → `thoughtleaders_profile.id` (the brand/advertiser's profile). ⚠️ The table is named `thoughtleaders_profile`, NOT `advertiser_profile`. |
|
|
39
|
+
| `advertiser_id` | int FK | → `auth_user.id` — the brand-side user account that created the sponsorship record (the advertiser/buyer side). Record lineage only — for the channel use `ad_spot_id → adspot.channel_id`, for the brand use `advertiser_profile_id`, for accountability use the `owner_*` fields. |
|
|
40
40
|
| `owner_advertiser_id` | int FK | → `auth_user.id` (brand-side owner) |
|
|
41
41
|
| `owner_publisher_id` | int FK | → `auth_user.id` (channel-side owner) |
|
|
42
42
|
| `owner_sales_id` | int FK | → `auth_user.id` (sales rep) |
|
|
43
|
-
| `
|
|
43
|
+
| `scheduled_date` | timestamptz | Scheduled send/publish date |
|
|
44
44
|
| `publish_date` | timestamptz | Actual publish date |
|
|
45
45
|
| `outreach_date` | timestamptz | When outreach was sent |
|
|
46
46
|
| `purchase_date` | timestamptz | When deal was purchased/sold |
|
|
@@ -166,9 +166,9 @@ A channel can have multiple adspots (different sellers: talent manager, direct,
|
|
|
166
166
|
| `url` | varchar | Channel URL (external — usually the YouTube URL). |
|
|
167
167
|
| `slug` | varchar | TL-platform-specific slug. Used to build the canonical TL channel URL: `https://app.thoughtleaders.io/youtube/<slug>`. Prefer this over `url` when linking to a channel from any user-facing surface (reports, samples, Slack posts). Fall back to an ID-based TL path if `slug` is NULL; never fall back to the external YouTube URL. |
|
|
168
168
|
| `total_views` | int | Total views for the entire channel |
|
|
169
|
-
| `
|
|
169
|
+
| `subscribers` | bigint | Subscriber count. |
|
|
170
170
|
| `media_selling_network_join_date` | date/timestamptz | When the channel joined the MSN. **MSN membership = this column IS NOT NULL.** |
|
|
171
|
-
| `
|
|
171
|
+
| `is_tpp` | boolean | True = TPP channel — TL's closest-partner channels (~144 at 100k+ subscribers), a strict subset of MSN. Prefer when booking: fastest response, easiest to close. ⚠️ **This is not the MSN flag.** For MSN, use `media_selling_network_join_date IS NOT NULL`. |
|
|
172
172
|
| `content_category` | int | Content category code (1–22), as assigned by YouTube. This assignment is too unreliable, do not use it for discovering channels. **For topic/category discovery, prefer `tl recommender top-channels "<tag>"` |
|
|
173
173
|
| `is_active` | boolean | Whether the channel is active. ⚠️ **Always include `is_active = true` in channel queries** unless explicitly looking for archived rows. |
|
|
174
174
|
| `country` | varchar | Channel's primary country (ISO 3166-1 alpha-2 code, e.g. `US`, `GB`, `BR`). This is often the cleanest answer to "geography" questions on sponsorships. May be NULL or blank. |
|
|
@@ -193,8 +193,8 @@ A channel can have multiple adspots (different sellers: talent manager, direct,
|
|
|
193
193
|
|
|
194
194
|
When composing `SELECT ... FROM thoughtleaders_channel ...`, do not improvise column names from semantic intuition — consult the output of `tl schema pg thoughtleaders_channel` or the column table above. The `tl schema` command is authoritative. Failed guesses return *"column '\<name\>' does not exist"* and cost a round-trip. Recurring problems:
|
|
195
195
|
|
|
196
|
-
- Subscriber count is in the field named `
|
|
197
|
-
- Projected views is in the field named `
|
|
196
|
+
- Subscriber count is in the field named `subscribers`
|
|
197
|
+
- Projected views is in the field named `projected_views`
|
|
198
198
|
- ❌ **Suffix/qualifier variants of date columns** (e.g. an `_max` / `latest_` / `_date` form when the canonical column has neither). Date columns use bare names.
|
|
199
199
|
- ❌ **Platform-name-prefixed ID forms** (e.g. a platform-name prefix when the canonical column uses a neutral `external_` prefix). See the column table for the actual ID column.
|
|
200
200
|
- ❌ **Bare-noun forms without the table-prefix** (e.g. `name` instead of `channel_name`). This table prefixes its display fields with `channel_` to avoid SQL keyword collisions and ambiguity in joins.
|
|
@@ -237,7 +237,7 @@ thoughtleaders_adlink
|
|
|
237
237
|
├── owner_advertiser_id → auth_user.id
|
|
238
238
|
├── owner_publisher_id → auth_user.id
|
|
239
239
|
├── owner_sales_id → auth_user.id
|
|
240
|
-
└──
|
|
240
|
+
└── advertiser_profile_id → thoughtleaders_profile.id
|
|
241
241
|
├── organization_id → thoughtleaders_organization.id
|
|
242
242
|
└── profile_brands.profile_id → brand.id
|
|
243
243
|
|
|
@@ -270,15 +270,15 @@ JOIN thoughtleaders_channel ch ON s.channel_id = ch.id
|
|
|
270
270
|
|
|
271
271
|
**Adlink → Brand name:**
|
|
272
272
|
```sql
|
|
273
|
-
JOIN thoughtleaders_profile p ON a.
|
|
273
|
+
JOIN thoughtleaders_profile p ON a.advertiser_profile_id = p.id
|
|
274
274
|
JOIN thoughtleaders_profile_brands pb ON p.id = pb.profile_id
|
|
275
275
|
JOIN thoughtleaders_brand b ON pb.brand_id = b.id
|
|
276
|
-
-- NEVER: JOIN brand b ON b.id = a.
|
|
276
|
+
-- NEVER: JOIN brand b ON b.id = a.advertiser_profile_id (different ID spaces, returns wrong data)
|
|
277
277
|
```
|
|
278
278
|
|
|
279
279
|
**Adlink → Organization:**
|
|
280
280
|
```sql
|
|
281
|
-
JOIN thoughtleaders_profile p ON a.
|
|
281
|
+
JOIN thoughtleaders_profile p ON a.advertiser_profile_id = p.id
|
|
282
282
|
JOIN thoughtleaders_organization o ON p.organization_id = o.id
|
|
283
283
|
```
|
|
284
284
|
|
|
@@ -323,7 +323,7 @@ LIMIT 100 OFFSET 0
|
|
|
323
323
|
|
|
324
324
|
**Sold deals this month:**
|
|
325
325
|
```sql
|
|
326
|
-
SELECT id, price, purchase_date, ad_spot_id,
|
|
326
|
+
SELECT id, price, purchase_date, ad_spot_id, advertiser_profile_id
|
|
327
327
|
FROM thoughtleaders_adlink
|
|
328
328
|
WHERE publish_status = 3
|
|
329
329
|
AND purchase_date >= date_trunc('month', CURRENT_DATE)
|
|
@@ -346,7 +346,7 @@ SELECT a.id, a.price, a.publish_status, b.name AS brand, ch.channel_name
|
|
|
346
346
|
FROM thoughtleaders_adlink a
|
|
347
347
|
JOIN thoughtleaders_adspot s ON a.ad_spot_id = s.id
|
|
348
348
|
JOIN thoughtleaders_channel ch ON s.channel_id = ch.id
|
|
349
|
-
JOIN thoughtleaders_profile p ON a.
|
|
349
|
+
JOIN thoughtleaders_profile p ON a.advertiser_profile_id = p.id
|
|
350
350
|
JOIN thoughtleaders_profile_brands pb ON p.id = pb.profile_id
|
|
351
351
|
JOIN thoughtleaders_brand b ON pb.brand_id = b.id
|
|
352
352
|
WHERE a.id = 12345
|
|
@@ -11,7 +11,7 @@ being inflated.
|
|
|
11
11
|
1. **Preferred:** `tl channels similar <id> --limit 24` (the recommender).
|
|
12
12
|
Best niche match.
|
|
13
13
|
2. **Fallback** (recommender empty): PG cohort —
|
|
14
|
-
same `content_category` + `language`, `is_active`, `
|
|
14
|
+
same `content_category` + `language`, `is_active`, `subscribers` within ±50%,
|
|
15
15
|
`last_published` within 60 days, excluding the subject channel.
|
|
16
16
|
3. For up to 12 peers, pull each peer's last 10 longforms via `tl db es`,
|
|
17
17
|
require ≥5,000 aggregate views and ≥3 videos (skip dead peers).
|
|
@@ -36,7 +36,7 @@ deleting the cache file or calling `get_baseline(ch, refresh=True)`.
|
|
|
36
36
|
If no usable peers at all (rare — niche too small / all peers dead), a generic
|
|
37
37
|
English-tech floor is used (`like 2%, comment 0.25%`) and `source` is recorded
|
|
38
38
|
as `fallback-generic` in the metrics so the report consumer knows the
|
|
39
|
-
baseline was weak. Prefer widening the
|
|
39
|
+
baseline was weak. Prefer widening the subscribers band over trusting this.
|
|
40
40
|
|
|
41
41
|
## Caveats
|
|
42
42
|
|
|
@@ -16,7 +16,7 @@ label only.
|
|
|
16
16
|
| `A_per_video_outliers` | ≥⅓ of longforms >1.5σ below the channel's own like:view mean | One real audience produces consistent ratios; promoted videos don't. |
|
|
17
17
|
|
|
18
18
|
Peer baseline: niche-matched (`tl channels similar`, fallback PG cohort:
|
|
19
|
-
same content_category+language, active,
|
|
19
|
+
same content_category+language, active, subscribers ±50%, published <60d), median
|
|
20
20
|
of each peer's last-10-longform like/comment rates. Cached 30 days.
|
|
21
21
|
|
|
22
22
|
## Group B — view-curve time-series (`anomaly_detector.py`)
|