thoughtleaders-cli 0.6.6__tar.gz → 0.6.8__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.6.6 → thoughtleaders_cli-0.6.8}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/PKG-INFO +1 -1
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/pyproject.toml +1 -1
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/skills/tl/SKILL.md +38 -2
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/skills/tl/references/postgres-schema.md +2 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/__init__.py +1 -1
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/brands.py +44 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/channels.py +38 -0
- thoughtleaders_cli-0.6.8/src/tl_cli/commands/schema.py +83 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/sponsorships.py +38 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/uv.lock +1 -1
- thoughtleaders_cli-0.6.6/src/tl_cli/commands/schema.py +0 -55
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/.gitignore +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/AGENTS.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/LICENSE +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/README.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/agents/tl-analyst.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/commands/tl-balance.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/commands/tl-reports.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/commands/tl-sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/commands/tl.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/docs/architecture.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/skills/tl/references/business-glossary.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/skills/tl/references/elasticsearch-schema.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/skills/tl/references/firebolt-schema.md +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/ask.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/comments.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/db.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/describe.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/doctor.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/recommender.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/reports.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/setup.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/snapshots.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/uploads.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/commands/whoami.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/config.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/filters.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/main.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/output/formatter.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/src/tl_cli/self_update.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/tests/test_filters.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/tests/test_sponsorships.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtleaders-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
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
|
|
@@ -20,6 +20,8 @@ Decision rule:
|
|
|
20
20
|
|
|
21
21
|
Always run `tl describe show <resource>` before using a structured command, and `tl schema pg|fb|es` before writing a raw query.
|
|
22
22
|
|
|
23
|
+
**When you only need the schema of one table, you MUST call `tl schema pg <table>` (or `tl schema fb <table>`) — never the unscoped form.** The unscoped `tl schema pg` returns *every* table visible to your role, which is dozens of tables and tens of thousands of tokens; the single-table form returns only the section you need, in the same markdown layout. Reaching for the unscoped form when you already know the table name is a tokens/latency tax with zero benefit. ES has no per-table form (the index is a single document shape) — `tl schema es` is the only call there.
|
|
24
|
+
|
|
23
25
|
**Process data with shell tools, not your context window.** Don't pull large result sets into your reasoning context just to filter, sort, count, or extract a field — that wastes tokens and slows you down. Pipe `tl … --json` (or `--csv`) into `jq`, `yq`, `rg`, or `duckdb` and read only the answer back. Pick the tool by shape:
|
|
24
26
|
|
|
25
27
|
- **`jq`** — filter, project, and transform JSON. The default for `tl … --json` post-processing.
|
|
@@ -67,6 +69,7 @@ Other key concepts:
|
|
|
67
69
|
- **Comments** — notes attached to sponsorships
|
|
68
70
|
- **Adspots** — types of ads a channel carries (e.g. mention, dedicated video, product placement). Returned by `tl channels show`; each carries price/cost.
|
|
69
71
|
- **MSN** (Media Selling Network) — the ~11k YouTube channels that have opted in to receive sponsorship offers. A channels is in the MSN group if the `channel.media_selling_network_join_date` field is not null.
|
|
72
|
+
- **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.
|
|
70
73
|
- **TPP** (ThoughtLeaders Partner Program, a.k.a. "TL channels") — the smaller, exclusive ~169 channels TL manages directly. A channel is in the TPP group if the `channel.is_tl_channel` is True.
|
|
71
74
|
- **`demographics_updated_at`** (on channel detail) — ISO timestamp of when demographic screenshots were last uploaded and processed via OCR. If non-null, the channel has demographics screenshots on file. If null, no screenshots have been uploaded. Use this to check whether a channel has demographics data from screenshots.
|
|
72
75
|
- **`impression`** (on channels) — projected views per video on that channel. Forward-looking estimate. May be null when not yet computed.
|
|
@@ -112,6 +115,7 @@ The CLI exposes three different discovery surfaces — pick by what you actually
|
|
|
112
115
|
| Arguments and flags for a specific leaf command | `tl <group> <subcommand> --help` (e.g. `tl recommender top-channels --help`) |
|
|
113
116
|
| Fields, filters, credit rates for a **data resource** (sponsorships, uploads, snapshots, reports, comments, recommender) | `tl describe show <resource> --json` |
|
|
114
117
|
| The live PG/ES/Firebolt schema for raw `tl db` queries | `tl schema pg` / `tl schema es` / `tl schema fb` |
|
|
118
|
+
| The schema of a **single** PG / Firebolt table | **`tl schema pg <table>`** / **`tl schema fb <table>`** — strongly preferred when you only need one |
|
|
115
119
|
|
|
116
120
|
Notes:
|
|
117
121
|
- Use `--help` everywhere — there is no separate `tl help` command. `tl help` returns "No such command 'help'".
|
|
@@ -137,6 +141,7 @@ Prefer writing Python code, shell code, or `jq` commands that fetche or analysis
|
|
|
137
141
|
tl sponsorships list [filters...] # Sponsorships — list curve, mult 1.0
|
|
138
142
|
tl sponsorships show <id> # Sponsorship detail (2 credits)
|
|
139
143
|
tl sponsorships create --channel <id> --brand <id> # Create proposal (free)
|
|
144
|
+
tl sponsorships update <id> '<json>' # Update whitelisted fields (2 credits) — only `publish_status` editable; non-full-access users can only edit sponsorships in their own org
|
|
140
145
|
tl deals list [filters...] # Shortcut: agreed-upon sponsorships (status:deal); same curve as sponsorships list
|
|
141
146
|
tl deals show <id> # Deal detail (2 credits)
|
|
142
147
|
tl matches list [filters...] # Shortcut: possible brand-channel pairings (status:match); same curve
|
|
@@ -148,11 +153,14 @@ tl proposals create --channel <id> --brand <id> # Create proposal (free)
|
|
|
148
153
|
tl uploads list [filters...] # Video uploads from ES — list curve, mult 1.0
|
|
149
154
|
tl uploads show <id> # Upload detail (2 credits)
|
|
150
155
|
tl channels show <id-or-name> # Channel detail (2 credits; accepts numeric ID or name) — for channel search use raw SQL on thoughtleaders_channel
|
|
156
|
+
tl channels update <id> '<json>' # Update whitelisted demographic fields (2 credits; full-access only)
|
|
151
157
|
tl channels history <id-or-name> # Sponsorship history (5 credits/result, linear)
|
|
152
158
|
tl channels similar <id-or-name> # Similarity recommender (25 credits flat; Intelligence plan)
|
|
153
159
|
tl brands show <id-or-name> # Brand detail (1 credit)
|
|
154
160
|
tl brands history <id-or-name> # Sponsorship history (5 credits/result, linear)
|
|
155
161
|
tl brands history <query> --channel <id> # Brand mentions on specific channel
|
|
162
|
+
tl brands history-stats <id-or-name> # Aggregate roll-up: counts, total/avg/median views, first/last seen, by-year, top channels (5 credits flat)
|
|
163
|
+
tl brands history-stats <q> --channel <id> # Same roll-up, narrowed to one channel
|
|
156
164
|
tl brands similar <id-or-name> # Find similar brands via similarity search (25 credits flat)
|
|
157
165
|
tl recommender tags [query] # List similarity tag names — categories, demographics, formats (free)
|
|
158
166
|
tl recommender top-channels "<tag>" # Top channels loaded on a similarity tag (25 credits; Intelligence)
|
|
@@ -182,6 +190,30 @@ tl comments add <adlink-id> "msg" # Add comment (free)
|
|
|
182
190
|
|
|
183
191
|
The marginal per-row cost is exactly proportional to `mult` — a 1.4× resource costs 1.4× the row part of a 1.0× resource at any size. Splitting a 500-row pull into ten 50-row calls saves ~30% but burns 10 setup floors instead of 1; "narrow the query" is almost always the better move than "fragment the pagination."
|
|
184
192
|
|
|
193
|
+
### Updating records
|
|
194
|
+
|
|
195
|
+
A narrow write surface is exposed for two resources. Each command takes the record id and a single JSON object with the fields to change; the server enforces a hard-coded field whitelist and rejects anything else with a 400. Each call costs 2 credits.
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
tl sponsorships update <id> '<json>' # Edit a sponsorship (adlink)
|
|
199
|
+
tl channels update <id> '<json>' # Edit a channel
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Sponsorships** — only `publish_status` is editable. Accepts either an int code or a status label (`proposed`, `pending`, `sold`, `matched`, `outreach`, `proposal_approved`, `advertiser_reject`, `publisher_reject`, `agency_reject`, `unavailable`). Non-full-access users may only update sponsorships tied to their own organization (either through `creator_profile` or through the channel's `publication`). Trying to edit a sponsorship outside the user's org returns 403.
|
|
203
|
+
|
|
204
|
+
**Channels** — only the demographic fields are editable: `demographic_usa_share` and `demographic_male_share` (integers 0–100), `demographic_age` / `demographic_device` / `demographic_geo` (JSON objects with numeric values). Requires full-access permission; non-full-access users get a 403. The `demographics_updated_at` timestamp is refreshed automatically when any whitelisted demographic field changes.
|
|
205
|
+
|
|
206
|
+
Examples:
|
|
207
|
+
```bash
|
|
208
|
+
tl sponsorships update 98765 '{"publish_status": "sold"}'
|
|
209
|
+
tl sponsorships update 98765 '{"publish_status": 3}'
|
|
210
|
+
tl channels update 12345 '{"demographic_male_share": 62}'
|
|
211
|
+
tl channels update 12345 '{"demographic_geo": {"US": 60, "UK": 12, "CA": 8}}'
|
|
212
|
+
tl channels update 12345 '{"demographic_male_share": 55, "demographic_usa_share": 70}'
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Anything outside these whitelists — price, cost, owner, channel name, etc. — is not editable through the CLI and must be done in the app or by a human with DB access.
|
|
216
|
+
|
|
185
217
|
### Raw queries (`tl db`)
|
|
186
218
|
|
|
187
219
|
`tl db pg|fb|es` is the default tool. Reach for it whenever the question is anything beyond a trivially simple lookup — and use the structured commands only for those trivial cases (single-record `show`, plain filtered `list`). Don't paginate-and-reduce in your head when one SQL or ES body would do it server-side.
|
|
@@ -304,6 +336,8 @@ See [references/business-glossary.md](references/business-glossary.md) for reven
|
|
|
304
336
|
| Arbitrary read-only `SELECT` on Postgres | **Available** via `tl db pg`. | SELECT-only, mandatory `LIMIT ≤ 500` + `OFFSET`, only certain SQL forms are allowed. See `references/postgres-schema.md`. |
|
|
305
337
|
| Cross-reference helpers ("channels proposed to brand X", "channels sponsored by MBN brands in last N days") | **Available** via `tl db pg`. | Write the join: `thoughtleaders_adlink` ↔ `adspot` ↔ `channel` ↔ `profile` ↔ `profile_brands` ↔ `brand`. Filter by `publish_status` for proposed/sold and by date range as needed. See `references/postgres-schema.md` for the exact column names. |
|
|
306
338
|
| **AdLink INSERT** with custom price/cost/owner/`weighted_price`/`created_where` | **Unavailable** — `tl sponsorships create` exists but only creates a free *proposal* between a channel and a brand. The `tl db pg` sanitizer accepts SELECT only — no INSERT/UPDATE. | Done in the app or by a human with DB access. |
|
|
339
|
+
| **AdLink UPDATE** of any field other than `publish_status` (price, cost, owner, send_date, …) | **Unavailable** — `tl sponsorships update` only accepts `publish_status` and only for sponsorships in the user's org (full-access bypasses the org check). | Done in the app or by a human with DB access. |
|
|
340
|
+
| **Channel UPDATE** of any field other than the demographic fields (`demographic_usa_share`, `demographic_male_share`, `demographic_age`, `demographic_device`, `demographic_geo`) | **Unavailable** — `tl channels update` only accepts those fields, and only for full-access users. | Done in the app or by a human with DB access. |
|
|
307
341
|
| Pre-insert validation queries (joining `adspot ↔ channel ↔ profile ↔ org` to confirm MSN, integration=1, persona, plan) | **Available** via `tl db pg`. | One SELECT joining the four tables. Use `thoughtleaders_channel.media_selling_network_join_date IS NOT NULL` for MSN, `thoughtleaders_adspot.integration = 1` for mention adspots, `thoughtleaders_profile.persona` for the persona code (see persona constants in `references/postgres-schema.md`). |
|
|
308
342
|
| Firebolt cross-table or join queries; filtering on non-indexed columns in WHERE | **Unavailable** — not accepted. | Fetch a wider slice keyed on `channel_id` (and optionally `id`), filter the rest in `jq`/Python. |
|
|
309
343
|
| ES `query_string`, `regexp`, `wildcard`, `fuzzy`, `more_like_this`, parent/child joins; any `script_*`; multiple aggregations in one body | **Unavailable** — not accepted. | Rewrite using `term`/`terms`/`match`/`bool`/`nested`. For multi-agg dashboards, run multiple `tl db es` calls and combine client-side. For "similar"-style queries, try `tl channels similar` / `tl brands similar` (server-implemented similarity search). |
|
|
@@ -317,8 +351,10 @@ If a user asks for one of the **Unavailable** items, say so explicitly and propo
|
|
|
317
351
|
```bash
|
|
318
352
|
tl describe # List all resources with credit costs (free)
|
|
319
353
|
tl describe show <resource> --json # Fields, filters, credit rates (free)
|
|
320
|
-
tl schema pg # PostgreSQL schema reference for `tl db pg` (free)
|
|
321
|
-
tl schema
|
|
354
|
+
tl schema pg # PostgreSQL schema reference for `tl db pg` (free) — every visible table
|
|
355
|
+
tl schema pg <table> # PostgreSQL schema for a SINGLE table (free) — same markdown shape
|
|
356
|
+
tl schema fb # Live Firebolt tables and column types for `tl db fb` (free) — both tables
|
|
357
|
+
tl schema fb <table> # Firebolt schema for a SINGLE table (free) — `article_metrics` or `channel_metrics`
|
|
322
358
|
tl schema es # Elasticsearch document shape for `tl db es` (free)
|
|
323
359
|
tl balance --json # Credit balance (free)
|
|
324
360
|
tl whoami # Current user, org, brands (free)
|
{thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/skills/tl/references/postgres-schema.md
RENAMED
|
@@ -27,6 +27,8 @@ The main deals table. Each row = one sponsorship deal between a brand and a YouT
|
|
|
27
27
|
> - ❌ `organization_id` — there is NO direct org FK. Org is reached via `creator_profile_id → profile.organization_id → organization`.
|
|
28
28
|
> - ❌ `channel_id` — channel is reached via `ad_spot_id → adspot.channel_id → channel`.
|
|
29
29
|
> - ❌ `youtube_id` (on channel) — use `external_channel_id`.
|
|
30
|
+
> - ❌ `msn_join_date` (on channel) — use `media_selling_network_join_date`.
|
|
31
|
+
> - ❌ `mbn_join_date` (on profile) — use `media_buying_network_join_date`.
|
|
30
32
|
|
|
31
33
|
#### Key Columns
|
|
32
34
|
|
|
@@ -118,6 +118,50 @@ def history_cmd(
|
|
|
118
118
|
client.close()
|
|
119
119
|
|
|
120
120
|
|
|
121
|
+
@app.command("history-stats")
|
|
122
|
+
def history_stats_cmd(
|
|
123
|
+
query: str = typer.Argument(..., help="Brand name or numeric ID"),
|
|
124
|
+
channel: int | None = typer.Option(None, "--channel", "-c", help="Restrict the roll-up to a specific channel"),
|
|
125
|
+
top_channels: int = typer.Option(10, "--top-channels", help="How many top-by-count channels to include in the roll-up (1-50)"),
|
|
126
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
127
|
+
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
128
|
+
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
129
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Aggregate roll-up of a brand's sponsorship history (no per-row output).
|
|
132
|
+
|
|
133
|
+
Same scope as `tl brands history` (videos where the brand is in
|
|
134
|
+
`sponsored_brand_mentions`), but returned as a single summary
|
|
135
|
+
record: total sponsored videos, view sums/avg/median, first/last
|
|
136
|
+
seen dates, per-year buckets, top channels by count, and
|
|
137
|
+
TL-brokered adlink counts. Computed via one ES aggregation +
|
|
138
|
+
one PG count — cost is flat regardless of how prolific the
|
|
139
|
+
brand is.
|
|
140
|
+
|
|
141
|
+
Requires an Intelligence plan. Costs 5 credits flat.
|
|
142
|
+
|
|
143
|
+
Examples:
|
|
144
|
+
tl brands history-stats Nike
|
|
145
|
+
tl brands history-stats 21416 --top-channels 25
|
|
146
|
+
tl brands history-stats Nike --channel 12345 --json
|
|
147
|
+
"""
|
|
148
|
+
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
149
|
+
|
|
150
|
+
params: dict[str, str] = {"top-channels": str(top_channels)}
|
|
151
|
+
if channel is not None:
|
|
152
|
+
params["channel_id"] = str(channel)
|
|
153
|
+
|
|
154
|
+
encoded_query = urllib.parse.quote(query, safe="")
|
|
155
|
+
client = get_client()
|
|
156
|
+
try:
|
|
157
|
+
data = client.get(f"/brands/{encoded_query}/history-stats", params=params)
|
|
158
|
+
output_single(data, fmt)
|
|
159
|
+
except ApiError as e:
|
|
160
|
+
handle_api_error(e)
|
|
161
|
+
finally:
|
|
162
|
+
client.close()
|
|
163
|
+
|
|
164
|
+
|
|
121
165
|
SIMILAR_COLUMNS = ["score", "brand_id", "brand_name", "website", "mbn"]
|
|
122
166
|
SIMILAR_COLUMN_CONFIG = {
|
|
123
167
|
"score": {"justify": "right"},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""tl channels — Search and show YouTube channels."""
|
|
2
2
|
|
|
3
|
+
import json as _json
|
|
3
4
|
import urllib.parse
|
|
4
5
|
|
|
5
6
|
import typer
|
|
@@ -232,6 +233,43 @@ def history_cmd(
|
|
|
232
233
|
client.close()
|
|
233
234
|
|
|
234
235
|
|
|
236
|
+
@app.command("update")
|
|
237
|
+
def update_cmd(
|
|
238
|
+
channel_id: int = typer.Argument(..., help="Channel ID (numeric)"),
|
|
239
|
+
fields: str = typer.Argument(..., help='JSON object of fields to update, e.g. \'{"demographic_male_share": 62}\''),
|
|
240
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
241
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
242
|
+
) -> None:
|
|
243
|
+
"""Update whitelisted channel fields (demographics; full-access only).
|
|
244
|
+
|
|
245
|
+
Editable fields: demographic_usa_share, demographic_male_share,
|
|
246
|
+
demographic_age, demographic_device, demographic_geo. The
|
|
247
|
+
demographics_updated_at timestamp is refreshed automatically.
|
|
248
|
+
|
|
249
|
+
Examples:
|
|
250
|
+
tl channels update 12345 '{"demographic_male_share": 62}'
|
|
251
|
+
tl channels update 12345 '{"demographic_geo": {"US": 60, "UK": 12}}'
|
|
252
|
+
"""
|
|
253
|
+
fmt = detect_format(json_output, False, False, toon_output)
|
|
254
|
+
try:
|
|
255
|
+
body = _json.loads(fields)
|
|
256
|
+
except _json.JSONDecodeError as exc:
|
|
257
|
+
Console(stderr=True).print(f"[red]Error:[/red] fields argument must be a JSON object: {exc}")
|
|
258
|
+
raise typer.Exit(1)
|
|
259
|
+
if not isinstance(body, dict):
|
|
260
|
+
Console(stderr=True).print("[red]Error:[/red] fields argument must be a JSON object.")
|
|
261
|
+
raise typer.Exit(1)
|
|
262
|
+
|
|
263
|
+
client = get_client()
|
|
264
|
+
try:
|
|
265
|
+
data = client.post(f"/channels/{channel_id}/edit", json_body=body)
|
|
266
|
+
output_single(data, fmt)
|
|
267
|
+
except ApiError as e:
|
|
268
|
+
handle_api_error(e)
|
|
269
|
+
finally:
|
|
270
|
+
client.close()
|
|
271
|
+
|
|
272
|
+
|
|
235
273
|
@app.command("look-alike", hidden=True)
|
|
236
274
|
def look_alike_cmd(
|
|
237
275
|
channel_ref: str = typer.Argument(..., help="Channel ID or name"),
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""tl schema — Show raw-db schema documentation for `tl db pg|fb|es`."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.markdown import Markdown
|
|
8
|
+
|
|
9
|
+
from tl_cli.client.errors import ApiError, handle_api_error
|
|
10
|
+
from tl_cli.client.http import get_client
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="Show schema documentation for raw db queries (`tl db pg|fb|es`)")
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _show(db: str, json_output: bool, table: str | None = None) -> None:
|
|
17
|
+
client = get_client()
|
|
18
|
+
try:
|
|
19
|
+
params = {"table": table} if table else {}
|
|
20
|
+
data = client.get(f"/raw/{db}/schema", params=params)
|
|
21
|
+
if json_output:
|
|
22
|
+
print(json.dumps(data, indent=2, default=str))
|
|
23
|
+
return
|
|
24
|
+
content = data.get("content", "")
|
|
25
|
+
if console.is_terminal:
|
|
26
|
+
console.print(Markdown(content))
|
|
27
|
+
else:
|
|
28
|
+
print(content)
|
|
29
|
+
except ApiError as e:
|
|
30
|
+
handle_api_error(e)
|
|
31
|
+
finally:
|
|
32
|
+
client.close()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command("pg")
|
|
36
|
+
def pg_cmd(
|
|
37
|
+
table: str = typer.Argument(None, help="Optional table name. When given, prints only that table's section in the same markdown format."),
|
|
38
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Show PostgreSQL schema reference (for `tl db pg`).
|
|
41
|
+
|
|
42
|
+
With no argument: lists every table visible to your role.
|
|
43
|
+
With a table name: prints only that table's column listing.
|
|
44
|
+
|
|
45
|
+
**Strongly preferred for single-table lookups.** Listing every
|
|
46
|
+
table just to read one is wasteful — pass the table name and the
|
|
47
|
+
server returns only that section.
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
tl schema pg
|
|
51
|
+
tl schema pg thoughtleaders_channel
|
|
52
|
+
tl schema pg thoughtleaders_adlink --json
|
|
53
|
+
"""
|
|
54
|
+
_show("pg", json_output, table=table)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command("fb")
|
|
58
|
+
def fb_cmd(
|
|
59
|
+
table: str = typer.Argument(None, help="Optional table name (`article_metrics` or `channel_metrics`). When given, prints only that table's section."),
|
|
60
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Show Firebolt schema (live: tables and column types) for `tl db fb`.
|
|
63
|
+
|
|
64
|
+
With no argument: lists both accepted tables.
|
|
65
|
+
With a table name: prints only that table's columns + primary index.
|
|
66
|
+
|
|
67
|
+
**Strongly preferred for single-table lookups.** Pass the table
|
|
68
|
+
name to skip the other one.
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
tl schema fb
|
|
72
|
+
tl schema fb article_metrics
|
|
73
|
+
tl schema fb channel_metrics --json
|
|
74
|
+
"""
|
|
75
|
+
_show("fb", json_output, table=table)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@app.command("es")
|
|
79
|
+
def es_cmd(
|
|
80
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Show Elasticsearch document shape for `tl db es`."""
|
|
83
|
+
_show("es", json_output)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""tl sponsorships — List, show, and create sponsorships."""
|
|
2
2
|
|
|
3
|
+
import json as _json
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
5
6
|
import typer
|
|
@@ -191,3 +192,40 @@ def create_cmd(
|
|
|
191
192
|
"""
|
|
192
193
|
fmt = detect_format(json_output, False, False, toon_output)
|
|
193
194
|
do_create(channel, brand, price, fmt, status="proposed")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@app.command("update")
|
|
198
|
+
def update_cmd(
|
|
199
|
+
sponsorship_id: int = typer.Argument(..., help="Sponsorship (adlink) ID"),
|
|
200
|
+
fields: str = typer.Argument(..., help='JSON object of fields to update, e.g. \'{"publish_status": "sold"}\''),
|
|
201
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
202
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
203
|
+
) -> None:
|
|
204
|
+
"""Update whitelisted sponsorship fields.
|
|
205
|
+
|
|
206
|
+
Editable fields: publish_status (int code or status label, e.g. 'sold',
|
|
207
|
+
'pending', 'matched'). Non-full-access users may only update sponsorships
|
|
208
|
+
tied to their own organization.
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
tl sponsorships update 98765 '{"publish_status": "sold"}'
|
|
212
|
+
tl sponsorships update 98765 '{"publish_status": 3}'
|
|
213
|
+
"""
|
|
214
|
+
fmt = detect_format(json_output, False, False, toon_output)
|
|
215
|
+
try:
|
|
216
|
+
body = _json.loads(fields)
|
|
217
|
+
except _json.JSONDecodeError as exc:
|
|
218
|
+
Console(stderr=True).print(f"[red]Error:[/red] fields argument must be a JSON object: {exc}")
|
|
219
|
+
raise typer.Exit(1)
|
|
220
|
+
if not isinstance(body, dict):
|
|
221
|
+
Console(stderr=True).print("[red]Error:[/red] fields argument must be a JSON object.")
|
|
222
|
+
raise typer.Exit(1)
|
|
223
|
+
|
|
224
|
+
client = get_client()
|
|
225
|
+
try:
|
|
226
|
+
data = client.post(f"/sponsorships/{sponsorship_id}/edit", json_body=body)
|
|
227
|
+
output_single(data, fmt)
|
|
228
|
+
except ApiError as e:
|
|
229
|
+
handle_api_error(e)
|
|
230
|
+
finally:
|
|
231
|
+
client.close()
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"""tl schema — Show raw-db schema documentation for `tl db pg|fb|es`."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
|
|
5
|
-
import typer
|
|
6
|
-
from rich.console import Console
|
|
7
|
-
from rich.markdown import Markdown
|
|
8
|
-
|
|
9
|
-
from tl_cli.client.errors import ApiError, handle_api_error
|
|
10
|
-
from tl_cli.client.http import get_client
|
|
11
|
-
|
|
12
|
-
app = typer.Typer(help="Show schema documentation for raw db queries (`tl db pg|fb|es`)")
|
|
13
|
-
console = Console()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _show(db: str, json_output: bool) -> None:
|
|
17
|
-
client = get_client()
|
|
18
|
-
try:
|
|
19
|
-
data = client.get(f"/raw/{db}/schema")
|
|
20
|
-
if json_output:
|
|
21
|
-
print(json.dumps(data, indent=2, default=str))
|
|
22
|
-
return
|
|
23
|
-
content = data.get("content", "")
|
|
24
|
-
if console.is_terminal:
|
|
25
|
-
console.print(Markdown(content))
|
|
26
|
-
else:
|
|
27
|
-
print(content)
|
|
28
|
-
except ApiError as e:
|
|
29
|
-
handle_api_error(e)
|
|
30
|
-
finally:
|
|
31
|
-
client.close()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@app.command("pg")
|
|
35
|
-
def pg_cmd(
|
|
36
|
-
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
37
|
-
) -> None:
|
|
38
|
-
"""Show PostgreSQL schema reference (for `tl db pg`)."""
|
|
39
|
-
_show("pg", json_output)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@app.command("fb")
|
|
43
|
-
def fb_cmd(
|
|
44
|
-
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
45
|
-
) -> None:
|
|
46
|
-
"""Show Firebolt schema (live: tables and column types) for `tl db fb`."""
|
|
47
|
-
_show("fb", json_output)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@app.command("es")
|
|
51
|
-
def es_cmd(
|
|
52
|
-
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
53
|
-
) -> None:
|
|
54
|
-
"""Show Elasticsearch document shape for `tl db es`."""
|
|
55
|
-
_show("es", json_output)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/skills/tl/references/business-glossary.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/skills/tl/references/elasticsearch-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.6 → thoughtleaders_cli-0.6.8}/skills/tl/references/firebolt-schema.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|