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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thoughtleaders-cli
3
- Version: 0.7.5
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-report-builder`** — builds TL reports (channels / brands / sponsorships / videos) from natural-language requests. Produces an in-chat preview by default; saves a real campaign when the user is explicit ("save", "create the report").
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=42wtAYnJRvJF6uR3-XllRTU49NwpGdrk-2Ap1Lnu_a8,112
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=BiT-87UbZjsOJKtjv2dKcCwbAg4-Dp4oICRM0c97TFg,7409
11
- tl_cli/auth/login.py,sha256=Rf65nhN7sjUNcaHYlsO7oYCiHqhGo8e8XwoGI2S4mgM,11487
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=DIRvnSbV2TwvVUgA5luGJ7uQUOjHx2CULFsZVaqUYw4,18922
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=p-kkTL4_8uxs1HMp_shuo7K82_3aT2bcUp-q980utQ0,466
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=YDpZ3DtP_HjEfZtC17ELG9kk-OixRXNvRL7VWwrrMXI,59099
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.5.dist-info/METADATA,sha256=DrOuWCd6RmuSEdebcd8PU8b4FpRSJejLHQkAVXcvTw0,18452
114
- thoughtleaders_cli-0.7.5.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
115
- thoughtleaders_cli-0.7.5.dist-info/entry_points.txt,sha256=umZp-1BkGkHDG0bNZXpTXrjwW0HGf9IDFN40eAWuuvg,39
116
- thoughtleaders_cli-0.7.5.dist-info/licenses/LICENSE,sha256=RUfdfLsn6jygiyrnnVUHt6r4IPwr2rbDm9Kixgtu8fo,1071
117
- thoughtleaders_cli-0.7.5.dist-info/RECORD,,
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
@@ -1,3 +1,3 @@
1
1
  """ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence."""
2
2
 
3
- __version__ = "0.7.5"
3
+ __version__ = "0.7.7"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tl-cli",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "ThoughtLeaders CLI — query sponsorship deals, channels, brands, uploads, and intelligence from the terminal",
5
5
  "author": {
6
6
  "name": "ThoughtLeaders",
@@ -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
@@ -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)"),