thoughtleaders-cli 0.6.0__tar.gz → 0.6.2__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.0 → thoughtleaders_cli-0.6.2}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/AGENTS.md +1 -1
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/PKG-INFO +10 -6
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/README.md +9 -5
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/commands/tl.md +2 -2
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/docs/architecture.md +4 -4
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/pyproject.toml +1 -1
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/skills/tl/SKILL.md +17 -15
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/skills/tl/references/firebolt-schema.md +2 -2
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/__init__.py +1 -1
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/channels.py +1 -45
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/recommender.py +103 -36
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/.gitignore +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/LICENSE +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/agents/tl-analyst.md +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/commands/tl-balance.md +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/commands/tl-reports.md +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/commands/tl-sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/skills/tl/references/business-glossary.md +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/skills/tl/references/elasticsearch-schema.md +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/skills/tl/references/postgres-schema.md +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/ask.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/brands.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/comments.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/db.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/describe.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/doctor.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/reports.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/schema.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/setup.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/snapshots.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/uploads.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/commands/whoami.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/config.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/filters.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/main.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/output/formatter.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/src/tl_cli/self_update.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/tests/test_filters.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/tests/test_sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/uv.lock +0 -0
|
@@ -20,7 +20,7 @@ When adding a new data command, follow this pattern. See `sponsorships.py` for t
|
|
|
20
20
|
|
|
21
21
|
`deals`, `matches`, and `proposals` are shortcut commands that delegate to sponsorships' `do_list`/`do_show`/`do_create` with a pre-set status filter. They reject explicit `status:` filters — users should use `tl sponsorships list` for finer-grained status filtering.
|
|
22
22
|
|
|
23
|
-
`recommender` (`commands/recommender.py`) wraps the vector-recommender API at `/api/cli/v1/recommender/*` — `tags` (free), `top`, `inspect-channel`, `inspect-brand`, `similar-to-profile` (all 50 credits flat, Intelligence-gated). Channel→channel and brand→brand similarity stay on `tl channels similar` / `tl brands similar`. When updating the SKILL or examples, prefer steering category/topic discovery (e.g. "Cooking channels") to `tl recommender top "<tag>"` rather than `WHERE content_category = <code>` SQL — the recommender is ranked, not equality-based
|
|
23
|
+
`recommender` (`commands/recommender.py`) wraps the vector-recommender API at `/api/cli/v1/recommender/*` — `tags` (free), `top-channels` / `top-profiles` / `top-brands`, `inspect-channel`, `inspect-brand`, `similar-to-profile` (all 50 credits flat, Intelligence-gated). The three `top-*` URLs share one server resolver; `top-brands` dedupes the underlying profile rows by brand. Channel→channel and brand→brand similarity stay on `tl channels similar` / `tl brands similar`. When updating the SKILL or examples, prefer steering category/topic discovery (e.g. "Cooking channels") to `tl recommender top-channels "<tag>"` rather than `WHERE content_category = <code>` SQL — the recommender is ranked, not equality-based. The underlying recommender code uses "element"/"field_name" terminology; the CLI/API layer renames these to "tag" at the boundary.
|
|
24
24
|
|
|
25
25
|
## Filter Parsing (`filters.py`)
|
|
26
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtleaders-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
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
|
|
@@ -83,14 +83,17 @@ tl uploads show 1174310:0BehkmVa7ak
|
|
|
83
83
|
|
|
84
84
|
# Search channels via raw SQL — `tl db pg` against thoughtleaders_channel
|
|
85
85
|
# (run `tl schema pg` once to confirm the live column set).
|
|
86
|
+
# NOTE: For topic / category discovery, prefer the vector recommender over
|
|
87
|
+
# `content_category` equality — `tl recommender top-channels "<tag>"`
|
|
88
|
+
# returns channels ranked by how strongly they load on the topic, not just
|
|
89
|
+
# rows where the single category code matches exactly.
|
|
86
90
|
tl db pg "SELECT id, channel_name, total_views FROM thoughtleaders_channel
|
|
87
91
|
WHERE content_category = <COOKING_CODE> AND total_views >= 100000
|
|
88
92
|
ORDER BY total_views DESC LIMIT 50 OFFSET 0"
|
|
89
93
|
tl db pg "SELECT id, channel_name FROM thoughtleaders_channel
|
|
90
94
|
WHERE is_tl_channel = TRUE LIMIT 200 OFFSET 0" # all TPP channels (~169)
|
|
91
|
-
# MSN status
|
|
92
|
-
#
|
|
93
|
-
# structured filter is the right tool: `tl channels list msn:yes|no`.
|
|
95
|
+
# MSN status: filter on `media_selling_network_join_date IS [NOT] NULL`
|
|
96
|
+
# in the same raw SQL query (column is scrubbed from advertiser sandboxes).
|
|
94
97
|
|
|
95
98
|
# Show channel detail — accepts numeric ID or channel name.
|
|
96
99
|
# Names that match more than one active channel print a candidate list
|
|
@@ -109,8 +112,9 @@ tl channels similar "Tremending girls" min-score:0.85 --limit 5
|
|
|
109
112
|
# `tags` is free; `top`, `inspect-*`, and `similar-to-profile` cost 50 credits flat.
|
|
110
113
|
tl recommender tags # List every tag (free)
|
|
111
114
|
tl recommender tags cooking # Search tag names by substring
|
|
112
|
-
tl recommender top "Cooking" msn:yes --limit 50 # Top channels
|
|
113
|
-
tl recommender top "
|
|
115
|
+
tl recommender top-channels "Cooking" msn:yes --limit 50 # Top channels for a tag
|
|
116
|
+
tl recommender top-profiles "Cooking" mbn:yes --limit 30 # Top brand profiles (one brand → potentially multiple profiles)
|
|
117
|
+
tl recommender top-brands "Cooking" --limit 30 # Top brands (deduped from profiles)
|
|
114
118
|
tl recommender inspect-channel 12345 # Per-tag breakdown of a channel's vector
|
|
115
119
|
tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal vector
|
|
116
120
|
tl recommender similar-to-profile 842 # Channels closest to a brand profile
|
|
@@ -56,14 +56,17 @@ tl uploads show 1174310:0BehkmVa7ak
|
|
|
56
56
|
|
|
57
57
|
# Search channels via raw SQL — `tl db pg` against thoughtleaders_channel
|
|
58
58
|
# (run `tl schema pg` once to confirm the live column set).
|
|
59
|
+
# NOTE: For topic / category discovery, prefer the vector recommender over
|
|
60
|
+
# `content_category` equality — `tl recommender top-channels "<tag>"`
|
|
61
|
+
# returns channels ranked by how strongly they load on the topic, not just
|
|
62
|
+
# rows where the single category code matches exactly.
|
|
59
63
|
tl db pg "SELECT id, channel_name, total_views FROM thoughtleaders_channel
|
|
60
64
|
WHERE content_category = <COOKING_CODE> AND total_views >= 100000
|
|
61
65
|
ORDER BY total_views DESC LIMIT 50 OFFSET 0"
|
|
62
66
|
tl db pg "SELECT id, channel_name FROM thoughtleaders_channel
|
|
63
67
|
WHERE is_tl_channel = TRUE LIMIT 200 OFFSET 0" # all TPP channels (~169)
|
|
64
|
-
# MSN status
|
|
65
|
-
#
|
|
66
|
-
# structured filter is the right tool: `tl channels list msn:yes|no`.
|
|
68
|
+
# MSN status: filter on `media_selling_network_join_date IS [NOT] NULL`
|
|
69
|
+
# in the same raw SQL query (column is scrubbed from advertiser sandboxes).
|
|
67
70
|
|
|
68
71
|
# Show channel detail — accepts numeric ID or channel name.
|
|
69
72
|
# Names that match more than one active channel print a candidate list
|
|
@@ -82,8 +85,9 @@ tl channels similar "Tremending girls" min-score:0.85 --limit 5
|
|
|
82
85
|
# `tags` is free; `top`, `inspect-*`, and `similar-to-profile` cost 50 credits flat.
|
|
83
86
|
tl recommender tags # List every tag (free)
|
|
84
87
|
tl recommender tags cooking # Search tag names by substring
|
|
85
|
-
tl recommender top "Cooking" msn:yes --limit 50 # Top channels
|
|
86
|
-
tl recommender top "
|
|
88
|
+
tl recommender top-channels "Cooking" msn:yes --limit 50 # Top channels for a tag
|
|
89
|
+
tl recommender top-profiles "Cooking" mbn:yes --limit 30 # Top brand profiles (one brand → potentially multiple profiles)
|
|
90
|
+
tl recommender top-brands "Cooking" --limit 30 # Top brands (deduped from profiles)
|
|
87
91
|
tl recommender inspect-channel 12345 # Per-tag breakdown of a channel's vector
|
|
88
92
|
tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal vector
|
|
89
93
|
tl recommender similar-to-profile 842 # Channels closest to a brand profile
|
|
@@ -18,8 +18,8 @@ The user wants to query ThoughtLeaders data. Translate their request into the ri
|
|
|
18
18
|
## Examples
|
|
19
19
|
|
|
20
20
|
- "/tl sold sponsorships for Nike in Q1" → `tl sponsorships list status:sold brand:"Nike" purchase-date-start:2026-01-01 purchase-date-end:2026-03-31`
|
|
21
|
-
- "/tl cooking channels over 100k subs" → `tl
|
|
22
|
-
- "/tl mobile-first US cooking channels" → `tl
|
|
21
|
+
- "/tl cooking channels over 100k subs" → `tl recommender top-channels "cooking" --limit 50` (then post-filter by `subscribers >= 100000` on the resulting IDs)
|
|
22
|
+
- "/tl mobile-first US cooking channels" → `tl recommender top-channels "cooking" --limit 100` (then narrow by `demographic_device_primary = 'mobile'` / `demographic_usa_share >= 50` with raw SQL on the resulting IDs)
|
|
23
23
|
- "/tl Nike's sponsorship activity" → `tl brands show Nike`
|
|
24
24
|
- "/tl run my Q1 report" → `tl reports --json` then `tl reports run <id>`
|
|
25
25
|
- "/tl check my balance" → `tl balance`
|
|
@@ -48,7 +48,6 @@ All data commands use explicit subcommands: `list`, `show`, `create`/`add`. Runn
|
|
|
48
48
|
| `tl proposals create --channel <id> --brand <id>` | Create a proposal (free) |
|
|
49
49
|
| `tl uploads list [filters...]` | List video uploads (ES) |
|
|
50
50
|
| `tl uploads show <id> [<id>...]` | Show upload detail(s) by ID |
|
|
51
|
-
| `tl channels list [filters...]` | Search channels. Responses carry boolean `msn` (Media Selling Network) and `tpp` (TL-managed) fields; filterable via `msn:` / `tpp:` tri-state (`yes` / `no` / `both`, default `both`). |
|
|
52
51
|
| `tl channels show <id-or-name>` | Channel detail, including active adspots with price/cost/CPM |
|
|
53
52
|
| `tl channels history <id-or-name>` | Sponsorship history (videos with detected sponsors) |
|
|
54
53
|
| `tl channels similar <id-or-name>` | Vector-similarity recommender. 50 credits; Intelligence plan. Tri-state `msn:` (default `yes`) and `tpp:` (default `both`) filters. Ambiguous names return 400 + candidates list. Hidden `look-alike` alias. |
|
|
@@ -75,8 +74,10 @@ Filters are passed as `key:value` pairs after `list`:
|
|
|
75
74
|
tl sponsorships list status:sold brand:"Nike" purchase-date:2026-01
|
|
76
75
|
tl sponsorships list status:pending send-date:2026-03
|
|
77
76
|
tl uploads list channel:12345 type:longform since:2026-03
|
|
78
|
-
# Channel discovery is raw SQL —
|
|
79
|
-
#
|
|
77
|
+
# Channel discovery is raw SQL — default to:
|
|
78
|
+
# (For topic/category lookups, `tl recommender top-channels "<tag>"` is a
|
|
79
|
+
# better starting point than `content_category` equality — it ranks by
|
|
80
|
+
# how strongly each channel loads on the topic, not exact-match.)
|
|
80
81
|
tl db pg "SELECT id, channel_name, total_views FROM thoughtleaders_channel
|
|
81
82
|
WHERE content_category = <COOKING_CODE> AND language = 'en'
|
|
82
83
|
AND total_views >= 1000000
|
|
@@ -387,7 +388,6 @@ Most CLI endpoints can reuse existing views/utilities rather than being built fr
|
|
|
387
388
|
| **`POST /api/cli/v1/sponsorships`** | `api/create-bulk-proposal` (`CreateBulkProposalView`) + MCP `save_proposals_for_email` in `mcp/proposals.py` | Adapt for single-proposal creation from CLI params |
|
|
388
389
|
| **`GET /api/cli/v1/uploads`** | `api/articles` (`ArticlesView`) — full ES article search with configurable columns, filters, aggregation. Already handles channel format, brand filters, content type. | Translate CLI filters → ArticlesView params. Restrict to VIDEO format. |
|
|
389
390
|
| **`GET /api/cli/v1/uploads/<id>`** | `api/articles/<id>` (`SingleArticleView`) | Add CLI envelope |
|
|
390
|
-
| **`GET /api/cli/v1/channels`** | `api/v2/external-youtube-thoughtleaders` (`ExternalYoutubeThoughtleadersView`) — rich ES-powered channel search with configurable columns, aggregation. Also `api/v1/channels/dropdown` for simple name search. | Translate CLI filters → existing view params |
|
|
391
391
|
| **`GET /api/cli/v1/channels/<id>`** | `api/v1/channels/<id>` (`ChannelAPIViewSet.retrieve`) — returns channel detail with loyalty metrics | Add CLI envelope + breadcrumbs linking to snapshots |
|
|
392
392
|
| **`GET /api/cli/v1/brands/<query>`** | `api/v1/brands` (`BrandsViewSet`) for brand lookup + `api/articles` with `sponsored_brand_mentions` filter for intelligence data. Also `api/brand/<id>/matcher` (`BrandChannelMatcherAPI`) for channel discovery. | Combine brand lookup + ES brand mention query |
|
|
393
393
|
| **`GET /api/cli/v1/snapshots/channel/<id>`** | `api/v2/channel-history` (`ChannelHistoryView`) — already queries Firebolt `channel_metrics` with pagination, uses `get_firebolt_query_results()` utility. Also `api/v2/external-channel-total-views` for time-series with granularity. | Direct reuse — just translate params + add CLI envelope |
|
|
@@ -95,7 +95,7 @@ When querying sponsorship bookings, query by `status:sold` and filter the the da
|
|
|
95
95
|
|
|
96
96
|
Where possible, if searching for a sponsorship match between channels and brands, first search for what do similar brands sponsor / which brands is the channel usually sponsored by. The similarity judgement should be preferably based on similar topics, similar upload frequency, similar channel sizes, and only after all that, on demographics.
|
|
97
97
|
|
|
98
|
-
Use the `tl channels similar` and `tl brands similar` commands to explore 1:1 similarity between known channels or brands. For category- or topic-driven discovery (e.g. "find me Cooking channels", "who scores high on USA share?"), use `tl recommender top "<tag>"` against the vector recommender — that's faster, ranked by category-strength
|
|
98
|
+
Use the `tl channels similar` and `tl brands similar` commands to explore 1:1 similarity between known channels or brands. For category- or topic-driven discovery (e.g. "find me Cooking channels", "who scores high on USA share?"), use `tl recommender top-channels "<tag>"` (or `top-brands`/`top-profiles`) against the vector recommender — that's faster, ranked by category-strength. Run `tl recommender tags` to discover the valid tag names.
|
|
99
99
|
|
|
100
100
|
## Workflow
|
|
101
101
|
|
|
@@ -130,8 +130,7 @@ tl proposals show <id> # Proposal detail (2 credits)
|
|
|
130
130
|
tl proposals create --channel <id> --brand <id> # Create proposal (free)
|
|
131
131
|
tl uploads list [filters...] # Video uploads from ES — list curve, mult 1.0
|
|
132
132
|
tl uploads show <id> # Upload detail (2 credits)
|
|
133
|
-
tl channels
|
|
134
|
-
tl channels show <id-or-name> # Channel detail (2 credits; accepts numeric ID or name)
|
|
133
|
+
tl channels show <id-or-name> # Channel detail (2 credits; accepts numeric ID or name) — for channel search use raw SQL on thoughtleaders_channel
|
|
135
134
|
tl channels history <id-or-name> # Sponsorship history (5 credits/result, linear)
|
|
136
135
|
tl channels similar <id-or-name> # Vector-similarity recommender (50 credits flat; Intelligence plan)
|
|
137
136
|
tl brands show <id-or-name> # Brand detail (1 credit)
|
|
@@ -139,7 +138,9 @@ tl brands history <id-or-name> # Sponsorship history (5 credits/result,
|
|
|
139
138
|
tl brands history <query> --channel <id> # Brand mentions on specific channel
|
|
140
139
|
tl brands similar <id-or-name> # Find similar brands via profile vector KNN (50 credits flat)
|
|
141
140
|
tl recommender tags [query] # List vector tag names — categories, demographics, formats (free)
|
|
142
|
-
tl recommender top "<tag>"
|
|
141
|
+
tl recommender top-channels "<tag>" # Top channels loaded on a vector tag (50 credits; Intelligence)
|
|
142
|
+
tl recommender top-profiles "<tag>" # Top brand profiles loaded on a vector tag (50 credits)
|
|
143
|
+
tl recommender top-brands "<tag>" # Top brands (deduped from profiles) loaded on a vector tag (50 credits)
|
|
143
144
|
tl recommender inspect-channel <ref> # Show a channel's feature-vector breakdown (50 credits; Intelligence)
|
|
144
145
|
tl recommender inspect-brand <ref> # Show a brand profile's ideal-vector breakdown (50 credits; Intelligence)
|
|
145
146
|
tl recommender similar-to-profile <id> # Channels closest to a brand profile's ideal vector (50 credits; Intelligence)
|
|
@@ -153,7 +154,7 @@ tl comments add <adlink-id> "msg" # Add comment (free)
|
|
|
153
154
|
|
|
154
155
|
**"List curve"** above means non-linear pricing: `cost = 1 + mult × 0.126 × n^1.2`. The flat 1-credit setup applies to every list call; the `mult` reflects per-resource complexity. `tl db {pg,fb,es}` shares the same curve at mult=1.4. Concrete totals:
|
|
155
156
|
|
|
156
|
-
| Rows | mult=1.0 (
|
|
157
|
+
| Rows | mult=1.0 (comments, uploads, sponsorships) | mult=1.2 (snapshots) | mult=1.3 (reports) | mult=1.4 (db.pg / db.fb / db.es) |
|
|
157
158
|
|---:|---:|---:|---:|---:|
|
|
158
159
|
| 1 | 1 | 1 | 1 | 1 |
|
|
159
160
|
| 10 | 3 | 3 | 4 | 4 |
|
|
@@ -332,9 +333,9 @@ tl recommender tags cooking
|
|
|
332
333
|
tl recommender tags "usa"
|
|
333
334
|
|
|
334
335
|
# Top channels & profiles loaded on a vector tag (50 credits; Intelligence)
|
|
335
|
-
tl recommender top "Cooking" msn:yes --limit 50
|
|
336
|
-
tl recommender top "Tech" --limit 30
|
|
337
|
-
tl recommender top "USA share" mbn:yes --limit 50
|
|
336
|
+
tl recommender top-channels "Cooking" msn:yes --limit 50
|
|
337
|
+
tl recommender top-channels "Tech" --limit 30
|
|
338
|
+
tl recommender top-brands "USA share" mbn:yes --limit 50
|
|
338
339
|
```
|
|
339
340
|
|
|
340
341
|
Use `tl db pg` only for predicates the recommender can't express — pure attribute filters (`is_tl_channel`, `language`, `demographic_device_primary`), aggregations, and joins. Run `tl schema pg` once to confirm the live column set; the columns referenced below are stable.
|
|
@@ -358,7 +359,7 @@ tl db pg "SELECT id, channel_name, demographic_device_primary, total_views
|
|
|
358
359
|
|
|
359
360
|
For per-country share beyond the recommender's "USA share" tag, use the `demographic_geo` jsonb in raw SQL: `(demographic_geo->>'gb')::int >= 25`. Same pattern with `demographic_device->>'mobile'` for non-primary device shares.
|
|
360
361
|
|
|
361
|
-
**MSN status (`media_selling_network_join_date`) is scrubbed from the advertiser sandbox view.** Raw SQL can't filter on it from an advertiser context
|
|
362
|
+
**MSN status (`media_selling_network_join_date`) is scrubbed from the advertiser sandbox view.** Raw SQL can't filter on it from an advertiser context. For MSN-only / non-MSN lookups, run the same raw SQL with `media_selling_network_join_date IS [NOT] NULL` from a context that has access to it (full-access role), or rely on the recommender's MSN-aware filters: `tl recommender top-channels "<tag>" msn:yes|no|all`.
|
|
362
363
|
|
|
363
364
|
### Output flags
|
|
364
365
|
- `--json` — structured JSON (use this for parsing)
|
|
@@ -396,7 +397,7 @@ Every query costs credits. Before running expensive queries:
|
|
|
396
397
|
Users only see data their plan allows:
|
|
397
398
|
- **Media buyers** see deals where their org is the brand. They see `price` but never `cost`.
|
|
398
399
|
- **Media sellers** see deals where their org is the publisher. They see `cost` but never `price`.
|
|
399
|
-
- **Intelligence plan** required for `tl brands`, full `tl
|
|
400
|
+
- **Intelligence plan** required for `tl brands`, the full `tl recommender` surface, and full `tl uploads list`.
|
|
400
401
|
- **Paid plan** required for `tl snapshots`.
|
|
401
402
|
|
|
402
403
|
## Important: Status Labels
|
|
@@ -434,8 +435,8 @@ tl reports run 42 --json
|
|
|
434
435
|
"Find Cooking channels with US-heavy mobile audiences":
|
|
435
436
|
```bash
|
|
436
437
|
# Use the vector recommender for the topic, then narrow with structured filters / SQL on the IDs.
|
|
437
|
-
tl recommender top "Cooking" msn:yes --limit 100 --json \
|
|
438
|
-
| jq -r '.results[]
|
|
438
|
+
tl recommender top-channels "Cooking" msn:yes --limit 100 --json \
|
|
439
|
+
| jq -r '.results[].channel_id' \
|
|
439
440
|
| paste -sd, - \
|
|
440
441
|
| xargs -I {} tl db pg "SELECT id, channel_name, total_views, demographic_usa_share
|
|
441
442
|
FROM thoughtleaders_channel
|
|
@@ -467,9 +468,10 @@ tl channels similar 29834 min-subs:1000000 exclude:477487 --limit 15 # client-s
|
|
|
467
468
|
```bash
|
|
468
469
|
tl recommender tags # Full tag list (free)
|
|
469
470
|
tl recommender tags cooking # Search tag names by substring
|
|
470
|
-
tl recommender top "Cooking" msn:yes --limit 50
|
|
471
|
-
tl recommender top "
|
|
472
|
-
tl recommender top "
|
|
471
|
+
tl recommender top-channels "Cooking" msn:yes --limit 50 # Top channels loaded on a tag (50 credits)
|
|
472
|
+
tl recommender top-profiles "Cooking" --limit 30 # Top brand profiles for the tag
|
|
473
|
+
tl recommender top-brands "USA share" mbn:yes --limit 30 # Top brands (deduped) — demographic tag, MBN only
|
|
474
|
+
tl recommender top-channels "Tech" exclude-for-profile:842 # Drop channels already proposed for profile 842
|
|
473
475
|
tl recommender inspect-channel 29834 # Per-tag breakdown of a channel's vector
|
|
474
476
|
tl recommender inspect-brand Nike # Per-tag breakdown of a brand's ideal vector
|
|
475
477
|
tl recommender similar-to-profile 842 --limit 30 # Channels closest to a brand profile's ideal vector
|
{thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/skills/tl/references/firebolt-schema.md
RENAMED
|
@@ -117,8 +117,8 @@ Every Firebolt workflow has two steps:
|
|
|
117
117
|
|
|
118
118
|
```bash
|
|
119
119
|
# Channels matching some category (vector recommender — preferred over content_category equality)
|
|
120
|
-
tl recommender top "Tech" msn:yes --limit 50 --json \
|
|
121
|
-
| jq '.
|
|
120
|
+
tl recommender top-channels "Tech" msn:yes --limit 50 --json \
|
|
121
|
+
| jq '.results[].channel_id'
|
|
122
122
|
|
|
123
123
|
# Or videos for a specific brand's deals (Postgres side, via tl sponsorships)
|
|
124
124
|
tl deals list brand:"Nike" --json --limit 500 \
|
|
@@ -11,7 +11,7 @@ from tl_cli.filters import parse_filters
|
|
|
11
11
|
from tl_cli.hints import detail_hint
|
|
12
12
|
from tl_cli.output.formatter import detect_format, output, output_single
|
|
13
13
|
|
|
14
|
-
app = typer.Typer(help="YouTube channels (
|
|
14
|
+
app = typer.Typer(help="YouTube channels (detail, history, and similar-channel recommendations)")
|
|
15
15
|
|
|
16
16
|
# Columns for the `similar` endpoint result table. The server enriches every
|
|
17
17
|
# row so the user can size up each suggestion without follow-up queries.
|
|
@@ -25,50 +25,6 @@ SIMILAR_COLUMN_CONFIG = {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
@app.callback(invoke_without_command=True)
|
|
29
|
-
def channels(ctx: typer.Context) -> None:
|
|
30
|
-
"""YouTube channels — search and detail."""
|
|
31
|
-
if ctx.invoked_subcommand is None:
|
|
32
|
-
ctx.invoke(list_cmd, args=[], json_output=False, csv_output=False, md_output=False, limit=50, offset=0)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@app.command("list")
|
|
36
|
-
def list_cmd(
|
|
37
|
-
args: list[str] = typer.Argument(None, help="Filters (key:value pairs). Run 'tl describe show channels' for available filters."),
|
|
38
|
-
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
39
|
-
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
40
|
-
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
41
|
-
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
42
|
-
limit: int = typer.Option(50, "--limit", "-l", help="Max results"),
|
|
43
|
-
offset: int = typer.Option(0, "--offset", help="Pagination offset"),
|
|
44
|
-
) -> None:
|
|
45
|
-
"""Search channels with optional filters.
|
|
46
|
-
|
|
47
|
-
Examples:
|
|
48
|
-
tl channels list # List channels
|
|
49
|
-
tl channels list category:cooking min-subs:100k # Search with filters
|
|
50
|
-
"""
|
|
51
|
-
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
52
|
-
filters = parse_filters(args or [])
|
|
53
|
-
|
|
54
|
-
client = get_client()
|
|
55
|
-
try:
|
|
56
|
-
params = {**filters, "limit": str(limit), "offset": str(offset)}
|
|
57
|
-
data = client.get("/channels", params=params)
|
|
58
|
-
for r in data.get("results", []):
|
|
59
|
-
r["channel_id"] = r.pop("id", None)
|
|
60
|
-
output(
|
|
61
|
-
data,
|
|
62
|
-
fmt,
|
|
63
|
-
columns=["channel_id", "name", "url", "msn", "tpp", "subscribers", "gender", "countries", "category", "sponsorship_score", "trend"],
|
|
64
|
-
title="Channels",
|
|
65
|
-
)
|
|
66
|
-
except ApiError as e:
|
|
67
|
-
handle_api_error(e)
|
|
68
|
-
finally:
|
|
69
|
-
client.close()
|
|
70
|
-
|
|
71
|
-
|
|
72
28
|
@app.command("show")
|
|
73
29
|
def show_cmd(
|
|
74
30
|
channel_ref: str = typer.Argument(..., help="Channel ID (numeric) or name (partial match, must be unique)"),
|
|
@@ -19,10 +19,12 @@ from tl_cli.client.http import get_client
|
|
|
19
19
|
from tl_cli.filters import parse_filters
|
|
20
20
|
from tl_cli.output.formatter import detect_format, output, output_single
|
|
21
21
|
|
|
22
|
-
app = typer.Typer(help="Vector recommender (tags, top-
|
|
22
|
+
app = typer.Typer(help="Vector recommender (tags, top-channels/profiles/brands, vector inspection, profile→channel similarity)")
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
TOP_CHANNEL_COLUMNS = ["value", "channel_id", "channel_name", "slug"]
|
|
26
|
+
TOP_PROFILE_COLUMNS = ["value", "profile_id", "profile_email", "brand_name", "brand_slug"]
|
|
27
|
+
TOP_BRAND_COLUMNS = ["value", "brand_slug", "brand_name", "profile_id"]
|
|
26
28
|
TOP_COLUMN_CONFIG = {"value": {"justify": "right"}}
|
|
27
29
|
|
|
28
30
|
|
|
@@ -70,7 +72,7 @@ def tags_cmd(
|
|
|
70
72
|
tl recommender tags "age 18"
|
|
71
73
|
"""
|
|
72
74
|
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
73
|
-
query = " ".join(args or []).strip()
|
|
75
|
+
query = _strip_quotes(" ".join(args or []).strip())
|
|
74
76
|
params = {"q": query} if query else {}
|
|
75
77
|
client = get_client()
|
|
76
78
|
try:
|
|
@@ -78,7 +80,7 @@ def tags_cmd(
|
|
|
78
80
|
output(
|
|
79
81
|
data,
|
|
80
82
|
fmt,
|
|
81
|
-
columns=["group", "field_name"
|
|
83
|
+
columns=["group", "field_name"],
|
|
82
84
|
title="Recommender vector tags",
|
|
83
85
|
)
|
|
84
86
|
except ApiError as e:
|
|
@@ -87,36 +89,21 @@ def tags_cmd(
|
|
|
87
89
|
client.close()
|
|
88
90
|
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
tag: str = typer.Argument(..., help='Vector tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
|
|
93
|
-
args: list[str] = typer.Argument(None, help="Filters (key:value pairs)."),
|
|
94
|
-
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
95
|
-
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
96
|
-
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
97
|
-
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
98
|
-
limit: int = typer.Option(50, "--limit", "-l", help="Max results per group (1-100)"),
|
|
99
|
-
) -> None:
|
|
100
|
-
"""Top channels and profiles loaded on a single vector tag.
|
|
92
|
+
def _strip_quotes(value: str) -> str:
|
|
93
|
+
"""Strip one matching pair of surrounding quotes if present.
|
|
101
94
|
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
Lets users paste an example like `tl recommender top-channels "cooking"`
|
|
96
|
+
where the shell already strips quotes, but also tolerates a layer of
|
|
97
|
+
extra quoting from agents or scripts that re-wrap the literal.
|
|
98
|
+
"""
|
|
99
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
|
|
100
|
+
return value[1:-1]
|
|
101
|
+
return value
|
|
104
102
|
|
|
105
|
-
Filters:
|
|
106
|
-
msn:<yes|no|all> MSN membership for channel rows (default: all)
|
|
107
|
-
mbn:<yes|no|all> MBN membership for profile rows (default: all)
|
|
108
|
-
exclude-for-channel:<id> Drop profiles already proposed for this channel
|
|
109
|
-
exclude-for-profile:<id> Drop channels already proposed for this profile
|
|
110
103
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
tl recommender top "Tech" msn:yes --limit 30
|
|
114
|
-
tl recommender top "USA share" mbn:yes
|
|
115
|
-
tl recommender top "Cooking" exclude-for-profile:842
|
|
116
|
-
"""
|
|
117
|
-
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
104
|
+
def _do_top(kind: str, tag: str, args: list[str], fmt: str, limit: int, columns: list[str], title: str) -> None:
|
|
105
|
+
tag = _strip_quotes(tag)
|
|
118
106
|
filters = parse_filters(args or [])
|
|
119
|
-
|
|
120
107
|
server_keys = {"msn", "mbn", "exclude-for-channel", "exclude-for-profile"}
|
|
121
108
|
params = {k: v for k, v in filters.items() if k in server_keys}
|
|
122
109
|
params["tag"] = tag
|
|
@@ -124,15 +111,12 @@ def top_cmd(
|
|
|
124
111
|
|
|
125
112
|
client = get_client()
|
|
126
113
|
try:
|
|
127
|
-
data = client.get("/recommender/top", params=params)
|
|
128
|
-
rows = data.get("results", [])
|
|
129
|
-
for r in rows:
|
|
130
|
-
r["name"] = r.get("channel_name") or r.get("profile_email") or ""
|
|
114
|
+
data = client.get(f"/recommender/top/{kind}", params=params)
|
|
131
115
|
output(
|
|
132
116
|
data,
|
|
133
117
|
fmt,
|
|
134
|
-
columns=
|
|
135
|
-
title=
|
|
118
|
+
columns=columns,
|
|
119
|
+
title=title,
|
|
136
120
|
column_config=TOP_COLUMN_CONFIG,
|
|
137
121
|
)
|
|
138
122
|
except ApiError as e:
|
|
@@ -141,6 +125,89 @@ def top_cmd(
|
|
|
141
125
|
client.close()
|
|
142
126
|
|
|
143
127
|
|
|
128
|
+
@app.command("top-channels")
|
|
129
|
+
def top_channels_cmd(
|
|
130
|
+
tag: str = typer.Argument(..., help='Vector tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
|
|
131
|
+
args: list[str] = typer.Argument(None, help="Filters (key:value pairs)."),
|
|
132
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
133
|
+
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
134
|
+
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
135
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
136
|
+
limit: int = typer.Option(50, "--limit", "-l", help="Max results (1-100)"),
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Top channels loaded on a single vector tag.
|
|
139
|
+
|
|
140
|
+
Costs 50 credits per call. Intelligence plan required.
|
|
141
|
+
|
|
142
|
+
Filters:
|
|
143
|
+
msn:<yes|no|all> MSN membership (default: all)
|
|
144
|
+
exclude-for-profile:<id> Drop channels already proposed for this profile
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
tl recommender top-channels "Cooking"
|
|
148
|
+
tl recommender top-channels "Tech" msn:yes --limit 30
|
|
149
|
+
tl recommender top-channels "Cooking" exclude-for-profile:842
|
|
150
|
+
"""
|
|
151
|
+
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
152
|
+
_do_top("channels", tag, args or [], fmt, limit, TOP_CHANNEL_COLUMNS, f"Top channels: {tag}")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@app.command("top-profiles")
|
|
156
|
+
def top_profiles_cmd(
|
|
157
|
+
tag: str = typer.Argument(..., help='Vector tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
|
|
158
|
+
args: list[str] = typer.Argument(None, help="Filters (key:value pairs)."),
|
|
159
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
160
|
+
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
161
|
+
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
162
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
163
|
+
limit: int = typer.Option(50, "--limit", "-l", help="Max results (1-100)"),
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Top brand profiles loaded on a single vector tag.
|
|
166
|
+
|
|
167
|
+
Costs 50 credits per call. Intelligence plan required. Profiles can
|
|
168
|
+
represent the same brand more than once (one brand → multiple
|
|
169
|
+
profiles); use `top-brands` for brand-deduplicated results.
|
|
170
|
+
|
|
171
|
+
Filters:
|
|
172
|
+
mbn:<yes|no|all> MBN membership (default: all)
|
|
173
|
+
exclude-for-channel:<id> Drop profiles already proposed for this channel
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
tl recommender top-profiles "Cooking"
|
|
177
|
+
tl recommender top-profiles "USA share" mbn:yes --limit 30
|
|
178
|
+
"""
|
|
179
|
+
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
180
|
+
_do_top("profiles", tag, args or [], fmt, limit, TOP_PROFILE_COLUMNS, f"Top profiles: {tag}")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@app.command("top-brands")
|
|
184
|
+
def top_brands_cmd(
|
|
185
|
+
tag: str = typer.Argument(..., help='Vector tag name (e.g. "Cooking", "Age 18-24"). Run `tl recommender tags` to discover valid names.'),
|
|
186
|
+
args: list[str] = typer.Argument(None, help="Filters (key:value pairs)."),
|
|
187
|
+
json_output: bool = typer.Option(False, "--json", help="JSON output"),
|
|
188
|
+
csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
|
|
189
|
+
md_output: bool = typer.Option(False, "--md", help="Markdown output"),
|
|
190
|
+
toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
|
|
191
|
+
limit: int = typer.Option(50, "--limit", "-l", help="Max results (1-100)"),
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Top brands loaded on a single vector tag (deduplicated from profiles).
|
|
194
|
+
|
|
195
|
+
Costs 50 credits per call. Intelligence plan required. Server-side
|
|
196
|
+
aggregates the underlying profile rows by brand, keeping the
|
|
197
|
+
highest-scoring profile per brand.
|
|
198
|
+
|
|
199
|
+
Filters:
|
|
200
|
+
mbn:<yes|no|all> MBN membership of the underlying profile (default: all)
|
|
201
|
+
exclude-for-channel:<id> Drop brands already proposed for this channel
|
|
202
|
+
|
|
203
|
+
Examples:
|
|
204
|
+
tl recommender top-brands "Cooking"
|
|
205
|
+
tl recommender top-brands "USA share" mbn:yes --limit 30
|
|
206
|
+
"""
|
|
207
|
+
fmt = detect_format(json_output, csv_output, md_output, toon_output)
|
|
208
|
+
_do_top("brands", tag, args or [], fmt, limit, TOP_BRAND_COLUMNS, f"Top brands: {tag}")
|
|
209
|
+
|
|
210
|
+
|
|
144
211
|
@app.command("inspect-channel")
|
|
145
212
|
def inspect_channel_cmd(
|
|
146
213
|
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
|
|
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.0 → thoughtleaders_cli-0.6.2}/skills/tl/references/business-glossary.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/skills/tl/references/elasticsearch-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.0 → thoughtleaders_cli-0.6.2}/skills/tl/references/postgres-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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|