thoughtleaders-cli 0.7.5__py3-none-any.whl → 0.7.7__py3-none-any.whl
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.5.dist-info → thoughtleaders_cli-0.7.7.dist-info}/METADATA +6 -2
- {thoughtleaders_cli-0.7.5.dist-info → thoughtleaders_cli-0.7.7.dist-info}/RECORD +11 -11
- tl_cli/__init__.py +1 -1
- tl_cli/_plugin/.claude-plugin/plugin.json +1 -1
- tl_cli/_plugin/skills/tl/SKILL.md +15 -0
- tl_cli/auth/commands.py +1 -3
- tl_cli/auth/login.py +1 -1
- tl_cli/commands/recommender.py +48 -0
- {thoughtleaders_cli-0.7.5.dist-info → thoughtleaders_cli-0.7.7.dist-info}/WHEEL +0 -0
- {thoughtleaders_cli-0.7.5.dist-info → thoughtleaders_cli-0.7.7.dist-info}/entry_points.txt +0 -0
- {thoughtleaders_cli-0.7.5.dist-info → thoughtleaders_cli-0.7.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtleaders-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.7
|
|
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
|
|
@@ -163,6 +163,7 @@ tl recommender tags cooking # Search tag names by substring
|
|
|
163
163
|
tl recommender top-channels "Cooking" msn:yes --limit 50 # Top channels for a tag
|
|
164
164
|
tl recommender top-profiles "Cooking" mbn:yes --limit 30 # Top brand profiles (one brand → potentially multiple profiles)
|
|
165
165
|
tl recommender top-brands "Cooking" --limit 30 # Top brands (deduped from profiles)
|
|
166
|
+
tl recommender channels-with-tag "Cooking" # ALL channel IDs loaded on a tag (--min defaults to 0.00001; paged; 1 credit/result)
|
|
166
167
|
tl recommender inspect-channel 12345 # Per-tag breakdown of a channel's vector
|
|
167
168
|
tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal profile
|
|
168
169
|
tl recommender channels-for-profile 842 # Channels closest to a specific brand profile
|
|
@@ -277,8 +278,11 @@ Each agent discovers the skill automatically and uses it when you ask about spon
|
|
|
277
278
|
The plugin ships several focused skills (installed by all the `tl setup *` commands):
|
|
278
279
|
|
|
279
280
|
- **`tl`** — the data-analyst skill. Defaults to raw database queries via `tl db pg|fb|es` for anything non-trivial; uses the structured `tl <resource> show` / `find` / `similar` commands for single-record lookups and similarity / ID-resolution special cases. Comes with full schema references for Postgres, Elasticsearch, and Firebolt under `references/`.
|
|
280
|
-
- **`tl-
|
|
281
|
+
- **`tl-keyword-research`** — broadens and ranks content-search keywords by Elasticsearch document count before a `tl db es` content search, so finding videos or channels by topic isn't bottlenecked on hand-guessed terms.
|
|
282
|
+
- **`tl-save-report`** — persists the result set from an in-chat exploration session as a saved TL report ("save this as a report", "turn this into a campaign").
|
|
283
|
+
- **`tl-report-builder`** — builds a brand-new TL report config from scratch (channels / brands / sponsorships / videos) through a guided multi-phase flow. Manual-invocation-only: reach it via `/tl-report-builder` or by naming it explicitly — natural-language report requests route to `tl`, `tl-save-report`, or `tl-import` instead.
|
|
281
284
|
- **`tl-import`** / **`bulk-import`** — superuser-only; bulk-add or exclude lists of channels, brands, videos, or sponsorships against a report.
|
|
285
|
+
- **`tl-channel-authenticity`** — vets a YouTube channel for non-organic views and bot/spam comments before booking (or after delivering) a sponsorship.
|
|
282
286
|
- **`tl-views-guarantee`** — sizes a multi-video sponsorship buy for a channel, returning the video bundle size, views guarantee, and likelihood to hit.
|
|
283
287
|
- **`tl-top-partnerships`** — brand-user performance report. Ranks a brand's sold sponsorships by live eCPM vs the sold-date projection, aggregates per channel, and delivers a two-tab Google Sheet ("By Deal" / "By Channel") via `gws`. Uses only public CLI commands (`tl whoami`, `tl sponsorships list`).
|
|
284
288
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
tl_cli/__init__.py,sha256=
|
|
1
|
+
tl_cli/__init__.py,sha256=h3L6t4qZYlTZLCm42Wl8W0xKRHEl0qObP17gk7Gu6co,112
|
|
2
2
|
tl_cli/_completions.py,sha256=kOyEUqC26vbYvyXWi513WX8fF73qQLR5WWuRSe_wqyk,164
|
|
3
3
|
tl_cli/_typer_utils.py,sha256=ZiZsCVmEznPvBw-dYbr3tu3zWZ0iN6kjoQmK3gMqD28,860
|
|
4
4
|
tl_cli/config.py,sha256=UV_OYTXuQnAIqbi_oVCXx0hhIdZWR678RRapVv51UwQ,1859
|
|
@@ -7,8 +7,8 @@ tl_cli/hints.py,sha256=cT8kuDtkAZqwXkc2RV0Yg_abofK-g9UiXwTTBunX78U,1557
|
|
|
7
7
|
tl_cli/main.py,sha256=A_8b2SQjBKATxrjO7AGC5Ab1QWlP35gGo4TWzYZtlOM,5806
|
|
8
8
|
tl_cli/self_update.py,sha256=akXOWYgBX2otyaVlx9CDl04gG2s_hYigE2Vkpubt0SA,18302
|
|
9
9
|
tl_cli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
tl_cli/auth/commands.py,sha256=
|
|
11
|
-
tl_cli/auth/login.py,sha256=
|
|
10
|
+
tl_cli/auth/commands.py,sha256=CkCaKFb-xUwhCeIL92EC4-odiaSpLI1bmgg5tDCrclM,7387
|
|
11
|
+
tl_cli/auth/login.py,sha256=AxdQ8LOZd1uZhFXPyaiGB_Hk0RiVuX7t37gkjgoEOCM,11568
|
|
12
12
|
tl_cli/auth/pkce.py,sha256=4Q6Ip-TeZFNG9c3swXNi4gH7mdMkltKa62gZZNybt8U,658
|
|
13
13
|
tl_cli/auth/token_store.py,sha256=TcZnUol4-8r0jMEJhOPmABCX12_5RkAln2xfWPNdmHk,3275
|
|
14
14
|
tl_cli/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -28,7 +28,7 @@ tl_cli/commands/describe.py,sha256=Ox2B1hoVuJ6pJc_x5BJjAhEJhlae-el9zwoJKutw3X8,1
|
|
|
28
28
|
tl_cli/commands/doctor.py,sha256=KUKglwhMc7B26XXy_3M0LkHu7wqfFO5T0YPHO1SH1VY,9024
|
|
29
29
|
tl_cli/commands/matches.py,sha256=K5o6B8FLECp7825dU4W3X8n-wuXvGJz57xpQPXeXQ-0,2886
|
|
30
30
|
tl_cli/commands/proposals.py,sha256=khsjorluIfgrJ22DiwzIAFcYD4JbirjkOBz1KuQ0Sdk,2918
|
|
31
|
-
tl_cli/commands/recommender.py,sha256=
|
|
31
|
+
tl_cli/commands/recommender.py,sha256=BZfviKAPhpZah0zaSyH5RDa23fG8QAkUnG5FDhOdi4I,21324
|
|
32
32
|
tl_cli/commands/reports.py,sha256=WcZWtBVRX49z-1Fw4T5iq_xZqks3QmIXlWVg5iVGs58,26532
|
|
33
33
|
tl_cli/commands/schema.py,sha256=GCBEE4fDatQhVasLKKr7bkGhELRZ0scYm_hUCbDYmuA,5985
|
|
34
34
|
tl_cli/commands/setup.py,sha256=QST6DCSxJHLFNX1UUJHwZ3hTDTnySATaV2Tc2JsdYYY,21920
|
|
@@ -39,14 +39,14 @@ tl_cli/commands/whoami.py,sha256=aUXwBRwh1vAGrvz8CKGfHYtEOKJCIDfwrGesKAwYZMk,786
|
|
|
39
39
|
tl_cli/output/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
40
|
tl_cli/output/formatter.py,sha256=pqlKmb2nZ1Z2e1A9m8l5mgVemJinVAP4in1tUzFWHno,22522
|
|
41
41
|
tl_cli/_plugin/.claude-plugin/marketplace.json,sha256=l56PMmyjfGXNGlV30wRyOAe74B6gJNCVNCxgsBbSNxc,446
|
|
42
|
-
tl_cli/_plugin/.claude-plugin/plugin.json,sha256=
|
|
42
|
+
tl_cli/_plugin/.claude-plugin/plugin.json,sha256=8iSJKEv5_GctH0e5WgihxC_sRAUF3ggyt3PgPy30mks,466
|
|
43
43
|
tl_cli/_plugin/agents/tl-analyst.md,sha256=6J3X3NANkWg6OOUCvNirkN4ulIk80KSumPncDUBt75E,6761
|
|
44
44
|
tl_cli/_plugin/agents/youtube-comment-classifier.md,sha256=S5lr_htA98FIX0su8FJ2ntiHfbdK8OB2NQKC4lTnQcw,2178
|
|
45
45
|
tl_cli/_plugin/hooks/hooks.json,sha256=FSWibw1xAjA-suFV3fR8btIb2kQ82LQ08otTr-NpmFw,835
|
|
46
46
|
tl_cli/_plugin/hooks/scripts/load-tl-skill.mjs,sha256=EBsyZ-caei-CBJsRtqzJXJs_20O3H22MuVmDpu96umo,805
|
|
47
47
|
tl_cli/_plugin/hooks/scripts/post-usage.sh,sha256=WVvZLkZik6lbeZ20Kh-wgm4JkRFHFN0Uwl4C8S3Y0sY,759
|
|
48
48
|
tl_cli/_plugin/hooks/scripts/pre-check.sh,sha256=E9KeuXy6yeHEBOnOFW4hDW-Et-Dbp1Oh--3WXKfOX78,898
|
|
49
|
-
tl_cli/_plugin/skills/tl/SKILL.md,sha256=
|
|
49
|
+
tl_cli/_plugin/skills/tl/SKILL.md,sha256=eYxfXFqRuLBh38mxgz74VR08keEVM2T9HCySHfv3W9E,61447
|
|
50
50
|
tl_cli/_plugin/skills/tl/references/business-glossary.md,sha256=FCS-qBOGpdJCmHdglRGRjAuTQAtzpxJNpMkEWThuvlI,17779
|
|
51
51
|
tl_cli/_plugin/skills/tl/references/elasticsearch-schema.md,sha256=OpHvixZ8UcZYJd8GdwgumryFyKwPAxj3AvPkl1QreMY,9316
|
|
52
52
|
tl_cli/_plugin/skills/tl/references/firebolt-schema.md,sha256=KagpSWWEWIRfsAWz271PvAqVbSPvWLoogWhCA_XFSZw,10642
|
|
@@ -110,8 +110,8 @@ tl_cli/_plugin/skills/tl-top-partnerships/SKILL.md,sha256=hvH05hIaGlc0RfTE0GLBtD
|
|
|
110
110
|
tl_cli/_plugin/skills/tl-top-partnerships/scripts/top_partnerships.py,sha256=_13W6-HuD_jtl7AWQQcZQ0SQO9qODMymlcL-1s4-VwU,13248
|
|
111
111
|
tl_cli/_plugin/skills/tl-views-guarantee/SKILL.md,sha256=IH7q1WJDWri9TWJMiga1FMGJO_GKSbWwaDS6CVNZ9c0,9270
|
|
112
112
|
tl_cli/_plugin/skills/tl-views-guarantee/scripts/vg.py,sha256=Qp5poinHEqh9374anq0bLtlxj2YL6ipBicaT960-Cws,15825
|
|
113
|
-
thoughtleaders_cli-0.7.
|
|
114
|
-
thoughtleaders_cli-0.7.
|
|
115
|
-
thoughtleaders_cli-0.7.
|
|
116
|
-
thoughtleaders_cli-0.7.
|
|
117
|
-
thoughtleaders_cli-0.7.
|
|
113
|
+
thoughtleaders_cli-0.7.7.dist-info/METADATA,sha256=oVKTRCUXd5CRJp-aFkXFoiAFLBk0TU8W7Jdn4QRnoxg,19234
|
|
114
|
+
thoughtleaders_cli-0.7.7.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
115
|
+
thoughtleaders_cli-0.7.7.dist-info/entry_points.txt,sha256=umZp-1BkGkHDG0bNZXpTXrjwW0HGf9IDFN40eAWuuvg,39
|
|
116
|
+
thoughtleaders_cli-0.7.7.dist-info/licenses/LICENSE,sha256=RUfdfLsn6jygiyrnnVUHt6r4IPwr2rbDm9Kixgtu8fo,1071
|
|
117
|
+
thoughtleaders_cli-0.7.7.dist-info/RECORD,,
|
tl_cli/__init__.py
CHANGED
|
@@ -50,6 +50,13 @@ The pattern is always: server-side narrowing first (usually by filters in the `t
|
|
|
50
50
|
|
|
51
51
|
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 present in the output of `whoami`.
|
|
52
52
|
|
|
53
|
+
**Counts, totals, and breakdowns: aggregate in the query engine — never page through records to count them.** A "how many / total / average / per-X" question is ONE aggregation query, not N pages of rows summed in your head:
|
|
54
|
+
- `tl db pg` — `SELECT COUNT(*) …`, or `SELECT col, COUNT(*) AS n … GROUP BY col ORDER BY n DESC`. Also `SUM`/`AVG`/`MIN`/`MAX`/`date_trunc`. Returns one/few rows regardless of table size. (`LIMIT`/`OFFSET` still required — an aggregate is one row, so `LIMIT 1 OFFSET 0` is fine.)
|
|
55
|
+
- `tl db es` — aggregation body with `"size": 0` (returns zero hits, only the agg result): `value_count`/`cardinality` for counts, `terms` for per-group, `sum`/`avg` for metrics, `date_histogram` for time series. Add `"track_total_hits": true` to get an exact match count. One aggregation block per body (see ES reference) — run multiple calls for a multi-metric dashboard.
|
|
56
|
+
- Structured list commands and list endpoints already return the full match count as `total` in the response envelope — request `--limit 1` and read `total` instead of fetching every row.
|
|
57
|
+
|
|
58
|
+
Fetching all rows to count/sum/group them is wrong: it is slow, costs credits per row returned, and silently undercounts once you hit the page cap.
|
|
59
|
+
|
|
53
60
|
Retry after 5 seconds if the server returns a "connection denied" or a "server error" on any request.
|
|
54
61
|
|
|
55
62
|
Where possible reference sponsorships, brands, channel by numeric IDs.
|
|
@@ -197,6 +204,7 @@ tl recommender tags [query] # List similarity tag names — categorie
|
|
|
197
204
|
tl recommender top-channels "<tag>" # Top channels loaded on a similarity tag (Intelligence)
|
|
198
205
|
tl recommender top-profiles "<tag>" # Top brand profiles loaded on a similarity tag
|
|
199
206
|
tl recommender top-brands "<tag>" # Top brands (deduped from profiles) loaded on a similarity tag
|
|
207
|
+
tl recommender channels-with-tag "<tag>" [--min <v>] # ALL channel IDs scoring >= v on a tag (--min default 0.00001 drops zero-loading channels; paged, enumerates the full set; 1 credit/result; Intelligence)
|
|
200
208
|
tl recommender inspect-channel <ref> # Show a channel's similarity-profile breakdown (Intelligence)
|
|
201
209
|
tl recommender inspect-brand <ref> # Show a brand profile's ideal similarity-profile breakdown (Intelligence)
|
|
202
210
|
tl recommender channels-for-profile <id> # Find channels closest to a brand profile's ideal profile (Intelligence)
|
|
@@ -360,6 +368,13 @@ cat query.json | tl db es -
|
|
|
360
368
|
|
|
361
369
|
See [references/elasticsearch-schema.md](references/elasticsearch-schema.md) for accepted top-level keys, query types, size/depth limits, scripting/aggregation rules, and the field catalogue.
|
|
362
370
|
|
|
371
|
+
**Article docs in ES carry only `channel.id` — not a usable channel name. Resolve names from PG, in a two-step script.** Whenever you query article/upload docs and the output needs channel names, do NOT hand-map ids in context and do NOT `ILIKE` on names — write a script that:
|
|
372
|
+
1. runs `tl db es … --json` with `channel.id` in `_source`, then collects the **distinct** channel ids;
|
|
373
|
+
2. runs `tl db pg "SELECT id, channel_name FROM thoughtleaders_channel WHERE id IN (<ids>)" --json` to build an `{id: channel_name}` map;
|
|
374
|
+
3. merges the map onto the ES rows by `channel.id` and emits the enriched result.
|
|
375
|
+
|
|
376
|
+
Prefer Python for the script (write it to `/tmp`); a `jq`+`xargs` one-liner is fine for a single page (worked example under *Brand sponsorship history*). Always go ES→PG in this order (PG `IN (...)` on the ids ES returned) — one PG round-trip for the whole page, never one query per article.
|
|
377
|
+
|
|
363
378
|
#### `tl db fb` — Firebolt
|
|
364
379
|
|
|
365
380
|
```bash
|
tl_cli/auth/commands.py
CHANGED
|
@@ -187,9 +187,7 @@ def logout_cmd() -> None:
|
|
|
187
187
|
# the interactive login established. Point the user at Auth0's logout
|
|
188
188
|
# URL so the next `tl auth login` doesn't silently SSO straight back in.
|
|
189
189
|
logout_url = f"https://{get_config().auth0_domain}/logout"
|
|
190
|
-
console.print(
|
|
191
|
-
f"To end your Auth0 browser session, visit: [cyan]{logout_url}[/cyan]"
|
|
192
|
-
)
|
|
190
|
+
console.print(f"To end your Auth0 browser session, visit: [cyan]{logout_url}[/cyan]")
|
|
193
191
|
clear_tokens()
|
|
194
192
|
console.print("[green]Logged out successfully.[/green]")
|
|
195
193
|
|
tl_cli/auth/login.py
CHANGED
|
@@ -129,7 +129,7 @@ def login_device_code() -> StoredTokens:
|
|
|
129
129
|
console.print()
|
|
130
130
|
console.print(f"[bold]And enter the code:[/bold] [cyan bold]{user_code}[/cyan bold]")
|
|
131
131
|
console.print()
|
|
132
|
-
console.print(f"[dim]The code expires in {expires_in // 60} minutes.[/dim]")
|
|
132
|
+
console.print(f"[dim]The code expires in {expires_in // 60} minutes. After you have logged in successfully, please wait until the system is notified.[/dim]")
|
|
133
133
|
|
|
134
134
|
# Poll for token
|
|
135
135
|
deadline = time.time() + expires_in
|
tl_cli/commands/recommender.py
CHANGED
|
@@ -210,6 +210,54 @@ def top_brands_cmd(
|
|
|
210
210
|
_do_top("brands", tag, args or [], fmt, limit, TOP_BRAND_COLUMNS, f"Top brands: {tag}")
|
|
211
211
|
|
|
212
212
|
|
|
213
|
+
@app.command("channels-with-tag")
|
|
214
|
+
def channels_with_tag_cmd(
|
|
215
|
+
tag: str = typer.Argument(..., help='Similarity tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
|
|
216
|
+
min_value: float = typer.Option(0.00001, "--min", help="Inclusive minimum tag value; only channels scoring at or above this are returned. Defaults to 0.00001, which excludes channels with no loading on the tag."),
|
|
217
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
218
|
+
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
219
|
+
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
220
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
221
|
+
limit: int = typer.Option(100, "--limit", "-l", help="Max results per page (1-1000)"),
|
|
222
|
+
offset: int = typer.Option(0, "--offset", help="Pagination offset"),
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Every channel whose value for a similarity tag is at or above a threshold.
|
|
225
|
+
|
|
226
|
+
Unlike `top-channels` (which ranks the strongest few), this walks the
|
|
227
|
+
*entire* match set in pages of up to 1000 — including sets larger than
|
|
228
|
+
the search index's 10k window — so you can enumerate every channel above
|
|
229
|
+
a cutoff. Returns channel IDs only; expand them with `tl channels show`
|
|
230
|
+
or `tl recommender inspect-channel`.
|
|
231
|
+
|
|
232
|
+
`--min` defaults to 0.00001 — just above zero — so a bare call returns
|
|
233
|
+
every channel with any loading on the tag and drops the zero-fill rest.
|
|
234
|
+
Raise it for a stricter cutoff.
|
|
235
|
+
|
|
236
|
+
Costs 1 credit per channel ID returned. Intelligence plan required.
|
|
237
|
+
|
|
238
|
+
Examples:
|
|
239
|
+
tl recommender channels-with-tag "Cooking"
|
|
240
|
+
tl recommender channels-with-tag "Age 18-24" --min 0.3 --limit 1000
|
|
241
|
+
tl recommender channels-with-tag "Cooking" --min 0.5 --offset 1000 --json
|
|
242
|
+
"""
|
|
243
|
+
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
244
|
+
tag = _strip_quotes(tag)
|
|
245
|
+
params = {"tag": tag, "min": str(min_value), "limit": str(limit), "offset": str(offset)}
|
|
246
|
+
client = get_client()
|
|
247
|
+
try:
|
|
248
|
+
data = client.get("/recommender/channels-with-tag", params=params)
|
|
249
|
+
output(
|
|
250
|
+
data,
|
|
251
|
+
fmt,
|
|
252
|
+
columns=["channel_id"],
|
|
253
|
+
title=f"Channels with {tag} >= {min_value}",
|
|
254
|
+
)
|
|
255
|
+
except ApiError as e:
|
|
256
|
+
handle_api_error(e)
|
|
257
|
+
finally:
|
|
258
|
+
client.close()
|
|
259
|
+
|
|
260
|
+
|
|
213
261
|
@app.command("inspect-channel")
|
|
214
262
|
def inspect_channel_cmd(
|
|
215
263
|
channel_ref: str = typer.Argument(..., help="Channel ID (numeric) or name (partial match, must be unique)"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|