thoughtleaders-cli 0.6.56__tar.gz → 0.7.0__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.56 → thoughtleaders_cli-0.7.0}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/AGENTS.md +17 -33
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/PKG-INFO +7 -6
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/README.md +6 -5
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/pyproject.toml +1 -1
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/SKILL.md +4 -4
- thoughtleaders_cli-0.7.0/skills/channel-authenticity/scripts/_io_utf8.py +53 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/analyze_channel.py +9 -7
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/anomaly_detector.py +3 -1
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/comment_analyzer.py +1 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/comment_scraper.py +2 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/engagement_ratios.py +1 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/peer_cohort.py +4 -2
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/report.py +4 -1
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/resolve_channel.py +2 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/score.py +4 -1
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/tl_cli.py +5 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/video_integrity.py +2 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/view_curves.py +2 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl/SKILL.md +4 -6
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl/references/postgres-schema.md +3 -1
- thoughtleaders_cli-0.7.0/skills/tl-views-guarantee/SKILL.md +117 -0
- thoughtleaders_cli-0.7.0/skills/tl-views-guarantee/scripts/vg.py +371 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/__init__.py +1 -1
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/brands.py +36 -27
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/channels.py +18 -8
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/doctor.py +9 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/.gitignore +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/API.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/LICENSE +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/agents/tl-analyst.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/agents/youtube-comment-classifier.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/hooks/scripts/load-tl-skill.mjs +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/.gitignore +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/references/comment-patterns.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/references/peer-cohort.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/references/red-flags.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/references/scoring.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl/references/business-glossary.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl/references/elasticsearch-schema.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl/references/firebolt-schema.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-import/SKILL.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-keyword-research/SKILL.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-keyword-research/scripts/probe.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/SKILL.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/examples/golden_queries.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/columns_brands.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/columns_channels.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/columns_content.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/report_glossary.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/sortable_columns.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/references/widgets.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/tools/column_builder.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/tools/database_query.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/tools/name_resolver.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/tools/sample_judge.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/tools/similar_channels.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-report-builder/tools/widget_builder.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/SKILL.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/columns_brands.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/columns_channels.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/columns_content.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/columns_sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/intelligence_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/intelligence_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/report_glossary.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/sortable_columns.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/sponsorship_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/sponsorship_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl-save-report/references/widgets.md +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/auth/finalize.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/_comments_common.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/bulk_import.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/credits.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/db.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/describe.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/recommender.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/reports.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/schema.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/setup.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/snapshots.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/uploads.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/commands/whoami.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/config.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/filters.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/main.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/output/formatter.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/src/tl_cli/self_update.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/tests/test_describe.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/tests/test_filters.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/tests/test_http_auth.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/tests/test_reports.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/tests/test_sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Project Overview
|
|
2
2
|
|
|
3
|
-
**tl-cli** is a Python CLI for querying ThoughtLeaders sponsorship data (sponsorships, channels, brands, uploads, snapshots, reports, recommender). Built with Typer + Rich + httpx. Designed as an "agent-first tool" — the CLI handles
|
|
3
|
+
**tl-cli** is a Python CLI for querying ThoughtLeaders sponsorship data (sponsorships, channels, brands, uploads, snapshots, reports, recommender). Built with Typer + Rich + httpx. Designed as an "AI agent-first tool" — the CLI handles data commands and output, while the user's AI agent (Claude) provides decision making.
|
|
4
4
|
|
|
5
5
|
# Architecture
|
|
6
6
|
|
|
@@ -11,25 +11,16 @@
|
|
|
11
11
|
## Command Pattern (all data commands follow this)
|
|
12
12
|
|
|
13
13
|
Every data command in `src/tl_cli/commands/` uses explicit Typer subcommands:
|
|
14
|
-
- `list` — list/search with `key:value` filters as positional args
|
|
15
14
|
- `show` — detail view by ID
|
|
16
15
|
- `history` — historical data list
|
|
17
16
|
- `create` / `add` — create new records (where applicable)
|
|
18
17
|
|
|
19
18
|
When adding a new data command, follow this pattern. See `sponsorships.py` for the reference implementation.
|
|
20
19
|
|
|
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
|
-
|
|
23
|
-
`recommender` (`commands/recommender.py`) wraps the recommender API at `/api/cli/v1/recommender/*` — `tags` (free), `top-channels` / `top-profiles` / `top-brands`, `inspect-channel`, `inspect-brand`, `similar-to-profile` (all 25 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
|
-
|
|
25
|
-
## Filter Parsing (`filters.py`)
|
|
26
|
-
|
|
27
|
-
`parse_filters()` handles `key:value` and `key:"quoted value"` syntax. Returns `dict[str, str]` passed as query params. Date filter keys (listed in `DATE_FILTER_KEYS` — e.g. `since`, `created-at`, `created-at-start`, `publish-date-end`) accept keywords `today`, `yesterday`, `tomorrow`. Sponsorship date fields (`created-at`, `publish-date`, `purchase-date`, `send-date`) each expose three filter shapes: bare `<field>:<date>` matches within that date/period, and `<field>-start:` / `<field>-end:` give inclusive lower/upper bounds (both sides inclusive; partial dates expand to the whole period). Empty-string values result in `IS NULL` queries on the backend.
|
|
28
|
-
|
|
29
20
|
## Auth Flow (`auth/`)
|
|
30
21
|
|
|
31
22
|
- **PKCE + Auth0**: Browser-based login with localhost callback server (`login.py`)
|
|
32
|
-
- **Token Storage** (`token_store.py`): OS keyring primary, `~/.config/tl/credentials.json` fallback (0o600)
|
|
23
|
+
- **Token Storage** (`token_store.py`): OS keyring primary, `~/.config/tl/credentials.json` fallback (chmod 0o600)
|
|
33
24
|
- **Env override**: `TL_API_KEY` env var takes priority over keyring (for CI)
|
|
34
25
|
- **Auto-refresh**: `TLClient` refreshes expired tokens on 401
|
|
35
26
|
|
|
@@ -52,6 +43,8 @@ TTY-aware: Rich tables in terminal, JSON when piped. Flags: `--json`, `--csv`, `
|
|
|
52
43
|
The CLI integrates with AI coding agents via skills, commands, agents, and hooks.
|
|
53
44
|
|
|
54
45
|
- **Claude Code** - `tl setup claude`
|
|
46
|
+
- **Gemini** - `tl setup gemini`
|
|
47
|
+
- **Codex** - `tl setup codex`
|
|
55
48
|
- **OpenCode** - `tl setup opencode`
|
|
56
49
|
|
|
57
50
|
This repo is also a Claude Code plugin, and can directly be installed as one.
|
|
@@ -60,8 +53,7 @@ This repo is also a Claude Code plugin, and can directly be installed as one.
|
|
|
60
53
|
|
|
61
54
|
- **`tl`** — the main skill for querying ThoughtLeaders data. Default for any sponsorship / channel / brand / upload / report question.
|
|
62
55
|
- **`tl-keyword-research`** — invoke whenever the user wants to find videos or channels by **content keywords** (topics, concepts, niches) that aren't covered by a curated recommender tag, OR to validate that a candidate channel's content actually touches a given topic. Returns `{operator, keywords:[{keyword,count}]}` from a ranked ES probe over `title` / `summary` / `transcript`; the caller then runs the actual content search with the surviving high-count terms. **Do not compose keyword sets by hand for `tl db es` content searches — delegate to this skill first.** See `skills/tl/SKILL.md` → *Channel & video discovery* for the four-path decision tree and when to use this vs the recommender / raw SQL.
|
|
63
|
-
- **`tl-
|
|
64
|
-
- **`tl-import`**, **`tl-save-report`**, **`adapt-tl-data`** — narrower workflows; the skill files document their own triggers.
|
|
56
|
+
- **`tl-import`**, **`tl-save-report`**, **`adapt-tl-data`**, **`tl-views-guarantee`** — narrower workflows; the skill files document their own triggers.
|
|
65
57
|
|
|
66
58
|
### Skill content boundaries
|
|
67
59
|
|
|
@@ -73,21 +65,6 @@ Skills under `skills/` are split into a `SKILL.md` and one or more `references/*
|
|
|
73
65
|
|
|
74
66
|
When adding or updating skill content, place the fact in its single home and link from the others. Do not duplicate or "quick-recap" content across files — recaps are the highest drift surface.
|
|
75
67
|
|
|
76
|
-
#### Anti-pattern: skill-local schema references
|
|
77
|
-
|
|
78
|
-
When a dependent skill (e.g. `tl-report-builder`) needs to reference a schema fact (table layout, columns, fetch SQL, hallucinated-column markers), **link to the canonical home in `skills/tl/references/`** — do not create a new `<skill>/references/*.md` file that mirrors or paraphrases that content.
|
|
79
|
-
|
|
80
|
-
Concrete regression marker: an earlier branch added `skills/tl-report-builder/references/data_plane.md` to consolidate the `thoughtleaders_topics` fetch query out of inline tool text. That had the right *shape* (don't restate schema in tool prose) but the wrong *home* — it forked schema facts into a parallel reference file that would silently drift from `skills/tl/references/postgres-schema.md`. The fix was to land the columns + fetch SQL + regression markers in `postgres-schema.md` (and upstream in `tl-data/references/postgres-schema.md`), delete the local file, and rewire the references via Markdown links.
|
|
81
|
-
|
|
82
|
-
Rule of thumb: if you are about to write *"here's the SQL to query this table"* or *"these columns don't exist on this table"* anywhere outside `skills/tl/references/`, stop. Add the fact to the canonical reference, then link to the anchor. Same for business facts and the glossary.
|
|
83
|
-
|
|
84
|
-
Skill-local `references/*.md` ARE appropriate when the content is **skill-shaped**, not schema-shaped:
|
|
85
|
-
- Column metadata for a specific report type (sortable columns, formula templates) — `tl-report-builder/references/columns_*.md`
|
|
86
|
-
- JSON schemas for tool-specific request/response shapes — `tl-report-builder/references/*_schema.json`
|
|
87
|
-
- Disambiguation tables, defaults, and pitfall catalogues that exist only in this skill's flow — `tl-report-builder/references/report_glossary.md`
|
|
88
|
-
|
|
89
|
-
If you are unsure whether a fact is schema-shaped or skill-shaped, ask: "would another TL skill (analyst, finance, mbn-outreach) ever need this fact?" If yes, it's schema/business-shaped — promote it to the canonical home.
|
|
90
|
-
|
|
91
68
|
## API Response Envelope
|
|
92
69
|
|
|
93
70
|
All list endpoints return: `{ results, total, limit, offset, usage: { credits_charged, credit_rate, balance_remaining }, _breadcrumbs }`.
|
|
@@ -109,9 +86,11 @@ The version string is defined in three files and all three must be updated toget
|
|
|
109
86
|
- `.claude-plugin/plugin.json` — `"version": "x.y.z"`
|
|
110
87
|
- `src/tl_cli/__init__.py` — `__version__ = "x.y.z"`
|
|
111
88
|
|
|
112
|
-
##
|
|
89
|
+
## Creating a release
|
|
113
90
|
|
|
114
|
-
|
|
91
|
+
A "release" means using the `gh` command to create a release on GitHub, named like the current package version number.
|
|
92
|
+
|
|
93
|
+
Warn the user if they are creating a release and the latest commit didn't bump the version number, and ask for confirmation before releasing.
|
|
115
94
|
|
|
116
95
|
## Coding
|
|
117
96
|
|
|
@@ -119,14 +98,19 @@ The version string is defined in three files and all three must be updated toget
|
|
|
119
98
|
* Do not let server implementation details into skill files (anything under `skills/`). Skills describe *what the CLI does* from the user's seat — observable command surface, inputs, outputs, examples. Do not say "the server enforces X", "the API validates Y on its side", "the backend rejects Z" — those are mechanism notes that drift the moment the server changes. State the user-visible behaviour ("unknown keys come back as 400") without naming where it's enforced.
|
|
120
99
|
* **All `import` and `from X import Y` statements live at the top of the Python module file** — after the module docstring, before any code. No inline imports inside function bodies, no lazy imports for "speed" or "optional dependency" reasons. `from __future__ import …` goes at the very top (Python requires that). The only legitimate inline-import exception is **platform-conditional imports** that cannot succeed on the other platform (e.g. `import msvcrt` on Linux, `import termios`/`tty` on Windows) — those stay inside their `if sys.platform == …:` guard. If a circular-import problem makes a top-level import impossible, fix the circular dependency rather than working around it with an inline import.
|
|
121
100
|
|
|
101
|
+
# Updating
|
|
102
|
+
|
|
103
|
+
The `tl update --force` command will force an update of the `thoughtleaders-cli` package.
|
|
104
|
+
The auto-update feature keeps the package updated, by checking (cached) on each command invocation.
|
|
105
|
+
|
|
122
106
|
# Git commit rules
|
|
123
107
|
|
|
124
108
|
Do not reference internal architecture of the ThoughtLeaders app in commit messages.
|
|
125
109
|
|
|
126
|
-
When a feature is purely server-side but changes the data the CLI receives (e.g. adding, removing, or renaming a field on a response, changing a credit rate, expanding an enum), make a forced empty commit on the tl-cli repo (`git commit --allow-empty`) describing the change. This keeps the CLI repo's history a complete log of what users see, even when no client code had to change.
|
|
110
|
+
When a feature is purely server-side but changes the data the CLI receives (e.g. adding, removing, or renaming a field on a response, changing a credit rate, expanding an enum), make a forced empty commit on the tl-cli repo (`git commit --allow-empty`) describing the change. This keeps the CLI repo's history a complete log of what users see, even when no client code had to change. The `tl changelog` command will read this log to show to the users.
|
|
127
111
|
|
|
128
112
|
# Be aware of tests
|
|
129
113
|
|
|
130
|
-
For every feature or change, explicitly consider whether tests need to be added or updated — new endpoint, new model field, new CLI command, new validation rule, new error path, anything that changes user-visible behaviour. Don't ship a feature without asking "what test covers this?" If no test does and the surface is non-trivial, write one. This applies across all repos involved in the change (server-side changes that ripple into the CLI need both server tests and CLI tests updated).
|
|
114
|
+
For every feature or change, explicitly consider whether tests need to be added or updated, on this repo or on the server repo — new endpoint, new model field, new CLI command, new validation rule, new error path, anything that changes user-visible behaviour. Don't ship a feature without asking "what test covers this?" If no test does and the surface is non-trivial, write one. This applies across all repos involved in the change (server-side changes that ripple into the CLI need both server tests and CLI tests updated).
|
|
131
115
|
|
|
132
|
-
Be sure to check if tests need to be updated when changing any data structures or function names, in all repos involved in the change
|
|
116
|
+
Be sure to check if tests need to be updated when changing any data structures or function names, in all repos involved in the change
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtleaders-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
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
|
|
@@ -37,7 +37,7 @@ ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligenc
|
|
|
37
37
|
### For account managers and sales
|
|
38
38
|
|
|
39
39
|
- **Pipeline reporting on the fly.** *"How many deals did we close in Q1?"*, *"What's my weighted pipeline by sales owner?"*, *"Which proposals are stuck in `pending` for more than 14 days?"* — one raw SQL or one structured command, instead of waiting on a dashboard.
|
|
40
|
-
- **Brand intelligence in seconds.** *"What channels does Nike sponsor?"*, *"Which brands sponsor `MrBeast`?"*, *"What's Holafly's sponsorship history through us vs. through everyone?"* — answers are one `tl
|
|
40
|
+
- **Brand intelligence in seconds.** *"What channels does Nike sponsor?"*, *"Which brands sponsor `MrBeast`?"*, *"What's Holafly's sponsorship history through us vs. through everyone?"* — answers are one `tl db es` call away.
|
|
41
41
|
- **Vetting candidates before a pitch.** Look up a channel by ID, name, YouTube URL, or `@handle`; pull its adspots, audience demographics, evergreenness score, and detected sponsor history before drafting the IO.
|
|
42
42
|
- **Pre-flight before booking.** Confirm MSN/TPP membership, integration availability, and persona/plan eligibility for a brand profile with one SQL join.
|
|
43
43
|
|
|
@@ -66,6 +66,7 @@ ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligenc
|
|
|
66
66
|
- [jq](https://stedolan.github.io/jq/)
|
|
67
67
|
- [ripgrep](https://github.com/BurntSushi/ripgrep)
|
|
68
68
|
- [duckdb](https://duckdb.org/)
|
|
69
|
+
- [yt-dlp](https://github.com/yt-dlp/yt-dlp)
|
|
69
70
|
|
|
70
71
|
For automated installs on MacOS, prefer installing Homebrew and use the pipx package manager, but ask the user if they have the admin access (sudo) password first. If not, proceed by using `uv` and installing everything locally.
|
|
71
72
|
|
|
@@ -170,8 +171,7 @@ tl recommender brands-for-channel 12345 # Brands most likely to sponsor
|
|
|
170
171
|
|
|
171
172
|
# Brand intelligence
|
|
172
173
|
tl brands show Nike
|
|
173
|
-
tl brands
|
|
174
|
-
tl brands history-stats Nike # Aggregate roll-up (totals, first/last seen, top channels)
|
|
174
|
+
tl brands find Nike # Resolve a string → single brand id
|
|
175
175
|
|
|
176
176
|
# Search videos and transcripts via Elasticsearch
|
|
177
177
|
tl db es '{"size":20,"query":{"term":{"channel.id":12345}},"_source":["title","views"]}'
|
|
@@ -196,7 +196,7 @@ tl balance
|
|
|
196
196
|
|
|
197
197
|
# Health check — auth, connectivity, version, latency, and required external tools.
|
|
198
198
|
# Run this first when something feels off; it surfaces token expiry,
|
|
199
|
-
# missing `jq`/`rg`/`duckdb`, and slow endpoints in one snapshot.
|
|
199
|
+
# missing `jq`/`rg`/`duckdb`/`yt-dlp`, and slow endpoints in one snapshot.
|
|
200
200
|
tl doctor
|
|
201
201
|
```
|
|
202
202
|
|
|
@@ -276,9 +276,10 @@ Each agent discovers the skill automatically and uses it when you ask about spon
|
|
|
276
276
|
|
|
277
277
|
The plugin ships several focused skills (installed by all the `tl setup *` commands):
|
|
278
278
|
|
|
279
|
-
- **`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`
|
|
279
|
+
- **`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
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
281
|
- **`tl-import`** / **`bulk-import`** — superuser-only; bulk-add or exclude lists of channels, brands, videos, or sponsorships against a report.
|
|
282
|
+
- **`tl-views-guarantee`** — sizes a multi-video sponsorship buy for a channel, returning the video bundle size, views guarantee, and likelihood to hit.
|
|
282
283
|
|
|
283
284
|
## Output Formats
|
|
284
285
|
|
|
@@ -9,7 +9,7 @@ ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligenc
|
|
|
9
9
|
### For account managers and sales
|
|
10
10
|
|
|
11
11
|
- **Pipeline reporting on the fly.** *"How many deals did we close in Q1?"*, *"What's my weighted pipeline by sales owner?"*, *"Which proposals are stuck in `pending` for more than 14 days?"* — one raw SQL or one structured command, instead of waiting on a dashboard.
|
|
12
|
-
- **Brand intelligence in seconds.** *"What channels does Nike sponsor?"*, *"Which brands sponsor `MrBeast`?"*, *"What's Holafly's sponsorship history through us vs. through everyone?"* — answers are one `tl
|
|
12
|
+
- **Brand intelligence in seconds.** *"What channels does Nike sponsor?"*, *"Which brands sponsor `MrBeast`?"*, *"What's Holafly's sponsorship history through us vs. through everyone?"* — answers are one `tl db es` call away.
|
|
13
13
|
- **Vetting candidates before a pitch.** Look up a channel by ID, name, YouTube URL, or `@handle`; pull its adspots, audience demographics, evergreenness score, and detected sponsor history before drafting the IO.
|
|
14
14
|
- **Pre-flight before booking.** Confirm MSN/TPP membership, integration availability, and persona/plan eligibility for a brand profile with one SQL join.
|
|
15
15
|
|
|
@@ -38,6 +38,7 @@ ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligenc
|
|
|
38
38
|
- [jq](https://stedolan.github.io/jq/)
|
|
39
39
|
- [ripgrep](https://github.com/BurntSushi/ripgrep)
|
|
40
40
|
- [duckdb](https://duckdb.org/)
|
|
41
|
+
- [yt-dlp](https://github.com/yt-dlp/yt-dlp)
|
|
41
42
|
|
|
42
43
|
For automated installs on MacOS, prefer installing Homebrew and use the pipx package manager, but ask the user if they have the admin access (sudo) password first. If not, proceed by using `uv` and installing everything locally.
|
|
43
44
|
|
|
@@ -142,8 +143,7 @@ tl recommender brands-for-channel 12345 # Brands most likely to sponsor
|
|
|
142
143
|
|
|
143
144
|
# Brand intelligence
|
|
144
145
|
tl brands show Nike
|
|
145
|
-
tl brands
|
|
146
|
-
tl brands history-stats Nike # Aggregate roll-up (totals, first/last seen, top channels)
|
|
146
|
+
tl brands find Nike # Resolve a string → single brand id
|
|
147
147
|
|
|
148
148
|
# Search videos and transcripts via Elasticsearch
|
|
149
149
|
tl db es '{"size":20,"query":{"term":{"channel.id":12345}},"_source":["title","views"]}'
|
|
@@ -168,7 +168,7 @@ tl balance
|
|
|
168
168
|
|
|
169
169
|
# Health check — auth, connectivity, version, latency, and required external tools.
|
|
170
170
|
# Run this first when something feels off; it surfaces token expiry,
|
|
171
|
-
# missing `jq`/`rg`/`duckdb`, and slow endpoints in one snapshot.
|
|
171
|
+
# missing `jq`/`rg`/`duckdb`/`yt-dlp`, and slow endpoints in one snapshot.
|
|
172
172
|
tl doctor
|
|
173
173
|
```
|
|
174
174
|
|
|
@@ -248,9 +248,10 @@ Each agent discovers the skill automatically and uses it when you ask about spon
|
|
|
248
248
|
|
|
249
249
|
The plugin ships several focused skills (installed by all the `tl setup *` commands):
|
|
250
250
|
|
|
251
|
-
- **`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`
|
|
251
|
+
- **`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/`.
|
|
252
252
|
- **`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").
|
|
253
253
|
- **`tl-import`** / **`bulk-import`** — superuser-only; bulk-add or exclude lists of channels, brands, videos, or sponsorships against a report.
|
|
254
|
+
- **`tl-views-guarantee`** — sizes a multi-video sponsorship buy for a channel, returning the video bundle size, views guarantee, and likelihood to hit.
|
|
254
255
|
|
|
255
256
|
## Output Formats
|
|
256
257
|
|
|
@@ -25,10 +25,10 @@ investigations.
|
|
|
25
25
|
view-curves can be hand-waved ("the algorithm", "we ran ads"); reading what
|
|
26
26
|
the audience actually says is the only direct proof. A run without it is
|
|
27
27
|
invalid.
|
|
28
|
-
- **Data access is CLI-only.** Everything goes through `tl_cli.py`
|
|
29
|
-
`tl` CLI (`tl db pg/fb/es`, `tl channels similar`).
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
- **Data access is CLI-only.** Everything goes through `tl_cli.py` and the
|
|
29
|
+
`tl` CLI (`tl db pg/fb/es`, `tl channels similar`).
|
|
30
|
+
- Do all data processing with the "utf-8" encoding explicitly in all scripts
|
|
31
|
+
you create.
|
|
32
32
|
|
|
33
33
|
## Setup check
|
|
34
34
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Shared UTF-8 I/O setup for the channel-authenticity scripts.
|
|
2
|
+
|
|
3
|
+
YouTube data is full of non-ASCII: channel names, comment text, emoji.
|
|
4
|
+
On POSIX the default I/O encoding is already UTF-8, but on Windows the
|
|
5
|
+
console and the default file encoding are the legacy code page (cp1252),
|
|
6
|
+
so printing or writing that text raises ``UnicodeEncodeError`` (or
|
|
7
|
+
silently mojibakes on read-back).
|
|
8
|
+
|
|
9
|
+
Importing this module reconfigures ``stdout``/``stderr`` to UTF-8 on
|
|
10
|
+
Windows (a no-op elsewhere, and idempotent), so every script that does
|
|
11
|
+
``import _io_utf8`` can safely print Unicode. File reads/writes must
|
|
12
|
+
still pass ``encoding="utf-8"`` explicitly — that's done at each call
|
|
13
|
+
site — and child processes get ``child_env()`` so their stdio is UTF-8
|
|
14
|
+
too. ``UTF8`` is exported for call sites that prefer the named constant.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
UTF8 = "utf-8"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _reconfigure_std_streams() -> None:
|
|
25
|
+
"""Force stdout/stderr to UTF-8 on Windows. No-op on POSIX."""
|
|
26
|
+
if sys.platform != "win32":
|
|
27
|
+
return
|
|
28
|
+
for stream in (sys.stdout, sys.stderr):
|
|
29
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
30
|
+
if reconfigure is None:
|
|
31
|
+
continue
|
|
32
|
+
try:
|
|
33
|
+
reconfigure(encoding=UTF8)
|
|
34
|
+
except (ValueError, OSError):
|
|
35
|
+
# Stream already detached / not reconfigurable (e.g. piped to a
|
|
36
|
+
# non-text sink) — leave it as-is rather than crash on import.
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def child_env() -> dict[str, str]:
|
|
41
|
+
"""``os.environ`` with UTF-8 forced, for subprocess children.
|
|
42
|
+
|
|
43
|
+
``PYTHONUTF8=1`` enables UTF-8 mode for Python children; the
|
|
44
|
+
redundant ``PYTHONIOENCODING`` covers tools that read it directly.
|
|
45
|
+
Use as ``subprocess.run(..., env=child_env())``.
|
|
46
|
+
"""
|
|
47
|
+
env = dict(os.environ)
|
|
48
|
+
env["PYTHONUTF8"] = "1"
|
|
49
|
+
env["PYTHONIOENCODING"] = UTF8
|
|
50
|
+
return env
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_reconfigure_std_streams()
|
|
@@ -28,14 +28,16 @@ import sys
|
|
|
28
28
|
import time
|
|
29
29
|
from pathlib import Path
|
|
30
30
|
|
|
31
|
+
import _io_utf8 # noqa: F401 (side effect: forces UTF-8 stdout/stderr on Windows)
|
|
31
32
|
import anomaly_detector
|
|
32
33
|
import comment_analyzer
|
|
33
34
|
import engagement_ratios
|
|
34
35
|
import resolve_channel
|
|
35
36
|
import score as score_mod
|
|
36
|
-
import tl_cli
|
|
37
37
|
import video_integrity
|
|
38
38
|
|
|
39
|
+
import tl_cli
|
|
40
|
+
|
|
39
41
|
OUT_DIR = Path("/tmp")
|
|
40
42
|
|
|
41
43
|
|
|
@@ -74,8 +76,8 @@ def collect(ref: str) -> dict:
|
|
|
74
76
|
}
|
|
75
77
|
state_path = OUT_DIR / f"channel_authenticity_{cid}_{ts}.state.json"
|
|
76
78
|
batch_path = OUT_DIR / f"channel_authenticity_{cid}_{ts}.llmbatch.json"
|
|
77
|
-
state_path.write_text(json.dumps(state, indent=2, default=str))
|
|
78
|
-
batch_path.write_text(json.dumps(gc.get("llm_batch", []), default=str))
|
|
79
|
+
state_path.write_text(json.dumps(state, indent=2, default=str), encoding="utf-8")
|
|
80
|
+
batch_path.write_text(json.dumps(gc.get("llm_batch", []), default=str), encoding="utf-8")
|
|
79
81
|
|
|
80
82
|
return {
|
|
81
83
|
"phase": "collect_done",
|
|
@@ -95,11 +97,11 @@ def collect(ref: str) -> dict:
|
|
|
95
97
|
|
|
96
98
|
|
|
97
99
|
def finalize(state_path: str, llm_paths: list[str]) -> dict:
|
|
98
|
-
state = json.loads(Path(state_path).read_text())
|
|
100
|
+
state = json.loads(Path(state_path).read_text(encoding="utf-8"))
|
|
99
101
|
passes: list[list[dict]] = []
|
|
100
102
|
for lp in llm_paths:
|
|
101
103
|
try:
|
|
102
|
-
c = json.loads(Path(lp).read_text())
|
|
104
|
+
c = json.loads(Path(lp).read_text(encoding="utf-8"))
|
|
103
105
|
if isinstance(c, dict):
|
|
104
106
|
c = c.get("classifications", [])
|
|
105
107
|
if c:
|
|
@@ -116,12 +118,12 @@ def finalize(state_path: str, llm_paths: list[str]) -> dict:
|
|
|
116
118
|
ts = time.strftime("%Y%m%d-%H%M%S")
|
|
117
119
|
final_json = OUT_DIR / f"channel_authenticity_{cid}_{ts}.final.json"
|
|
118
120
|
report_md = OUT_DIR / f"channel_authenticity_{cid}_{ts}.report.md"
|
|
119
|
-
final_json.write_text(json.dumps(state, indent=2, default=str))
|
|
121
|
+
final_json.write_text(json.dumps(state, indent=2, default=str), encoding="utf-8")
|
|
120
122
|
|
|
121
123
|
import report as report_mod
|
|
122
124
|
|
|
123
125
|
md = report_mod.render(state)
|
|
124
|
-
report_md.write_text(md)
|
|
126
|
+
report_md.write_text(md, encoding="utf-8")
|
|
125
127
|
return {"final_json": str(final_json), "report_md": str(report_md), "markdown": md}
|
|
126
128
|
|
|
127
129
|
|
|
@@ -11,9 +11,11 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import statistics
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
import _io_utf8 # noqa: F401 (side effect: forces UTF-8 stdout/stderr on Windows)
|
|
15
15
|
import view_curves
|
|
16
16
|
|
|
17
|
+
import tl_cli
|
|
18
|
+
|
|
17
19
|
PENALTIES = {
|
|
18
20
|
"B_burst_without_engagement": (25, "critical"),
|
|
19
21
|
"B_engagement_incoherence": (25, "critical"),
|
|
@@ -15,6 +15,8 @@ import statistics
|
|
|
15
15
|
import time
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
|
+
import _io_utf8 # noqa: F401 (side effect: forces UTF-8 stdout/stderr on Windows)
|
|
19
|
+
|
|
18
20
|
import tl_cli
|
|
19
21
|
|
|
20
22
|
CACHE = Path(__file__).resolve().parent.parent / "references" / "peer-cohort-cache.json"
|
|
@@ -46,7 +48,7 @@ def _cache_key(channel: dict) -> str:
|
|
|
46
48
|
def _load_cache() -> dict:
|
|
47
49
|
if CACHE.exists():
|
|
48
50
|
try:
|
|
49
|
-
return json.loads(CACHE.read_text())
|
|
51
|
+
return json.loads(CACHE.read_text(encoding="utf-8"))
|
|
50
52
|
except json.JSONDecodeError:
|
|
51
53
|
return {}
|
|
52
54
|
return {}
|
|
@@ -54,7 +56,7 @@ def _load_cache() -> dict:
|
|
|
54
56
|
|
|
55
57
|
def _save_cache(cache: dict) -> None:
|
|
56
58
|
CACHE.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
-
CACHE.write_text(json.dumps(cache, indent=2, default=str))
|
|
59
|
+
CACHE.write_text(json.dumps(cache, indent=2, default=str), encoding="utf-8")
|
|
58
60
|
|
|
59
61
|
|
|
60
62
|
def _peer_ids_via_cli(channel_id: int) -> list[int]:
|
{thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/report.py
RENAMED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
"""Render the markdown narrative report from a finalized state dict."""
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import _io_utf8 # noqa: F401 (side effect: forces UTF-8 stdout/stderr on Windows)
|
|
6
|
+
|
|
5
7
|
SEV_ICON = {"critical": "🔴", "warning": "🟠", "info": "🟡"}
|
|
6
8
|
VERDICT_ICON = {
|
|
7
9
|
"CLEAN": "✅",
|
|
@@ -132,4 +134,5 @@ if __name__ == "__main__":
|
|
|
132
134
|
import json
|
|
133
135
|
import sys
|
|
134
136
|
|
|
135
|
-
|
|
137
|
+
with open(sys.argv[1], encoding="utf-8") as fh:
|
|
138
|
+
print(render(json.load(fh)))
|
{thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/score.py
RENAMED
|
@@ -9,6 +9,8 @@ to "do not book" regardless of the mean.
|
|
|
9
9
|
"""
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import _io_utf8 # noqa: F401 (side effect: forces UTF-8 stdout/stderr on Windows)
|
|
13
|
+
|
|
12
14
|
BANDS = [
|
|
13
15
|
(90, "CLEAN", "Safe to book at standard rates."),
|
|
14
16
|
(70, "MINOR_FLAGS", "Book but note caveats to the AM."),
|
|
@@ -72,5 +74,6 @@ if __name__ == "__main__":
|
|
|
72
74
|
import json
|
|
73
75
|
import sys
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
with open(sys.argv[1], encoding="utf-8") as fh:
|
|
78
|
+
d = json.load(fh)
|
|
76
79
|
print(json.dumps(composite(d["group_a"], d["group_b"], d["group_c"]), indent=2))
|
{thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/channel-authenticity/scripts/tl_cli.py
RENAMED
|
@@ -23,6 +23,8 @@ import os
|
|
|
23
23
|
import shutil
|
|
24
24
|
import subprocess
|
|
25
25
|
|
|
26
|
+
import _io_utf8
|
|
27
|
+
|
|
26
28
|
TL_BIN = os.environ.get("TL_CLI_BIN", "tl")
|
|
27
29
|
|
|
28
30
|
|
|
@@ -67,6 +69,9 @@ def _tl(args: list[str], *, input_text: str | None = None) -> str:
|
|
|
67
69
|
input=input_text,
|
|
68
70
|
capture_output=True,
|
|
69
71
|
text=True,
|
|
72
|
+
encoding="utf-8",
|
|
73
|
+
errors="replace",
|
|
74
|
+
env=_io_utf8.child_env(),
|
|
70
75
|
timeout=180,
|
|
71
76
|
)
|
|
72
77
|
except FileNotFoundError as exc:
|
|
@@ -151,11 +151,11 @@ Unless the user specifically asks for running a specific report or showing the r
|
|
|
151
151
|
|
|
152
152
|
Skip the save offer when the result clearly doesn't fit a report type — a single scalar count, an aggregate roll-up across entity types, view-curve time series, schema introspection output, or anything that isn't a list of channels / brands / videos / sponsorships. A trailing offer on those would just be noise.
|
|
153
153
|
|
|
154
|
-
Prefer writing shell code, `jq` commands, or `duckdb` commands that fetch or analysise large sets of data instead of analysing it yourself. On Mac and Linux, create temporary files in `/tmp` that can be analysed later in different ways. On Windows, create them in
|
|
154
|
+
Prefer writing shell code, `jq` commands, or `duckdb` commands that fetch or analysise large sets of data instead of analysing it yourself. On Mac and Linux, create temporary files in `/tmp` that can be analysed later in different ways. On Windows, create them in the directory pointed to by the `%TEMP%` environment variable. When coding, do it in Python.
|
|
155
155
|
|
|
156
156
|
## Available Flows
|
|
157
157
|
|
|
158
|
-
Note that if you're working on Windows, you must set up UTF-8
|
|
158
|
+
Note that if you're working on Windows, you must set up UTF-8 because all commands take UTF-8 as inputs and output UTF-8 data. If using the `bash` tool, write commands using the Bash syntax, like `export PYTHONIOENCODING=utf-8 tl db es ...`.
|
|
159
159
|
|
|
160
160
|
### Data queries
|
|
161
161
|
|
|
@@ -465,6 +465,8 @@ tl channels find "MrBeast"
|
|
|
465
465
|
tl brands find "NordVPN"
|
|
466
466
|
```
|
|
467
467
|
|
|
468
|
+
If finding channels and brands fail, try variation on the name with or without whitespace.
|
|
469
|
+
|
|
468
470
|
**Path 2. Curated tag / category / demographic** — user named a topic that maps cleanly to a recommender tag (`"Cooking"`, `"Tech"`, `"USA share"`, content categories, format hints). Use the recommender — it ranks channels by how strongly they load on a tag, returning ranked similarity scores instead of forcing exact equality. It also returns matching brand profiles alongside the channels — useful when the user wants to know "who buys this kind of inventory."
|
|
469
471
|
|
|
470
472
|
```bash
|
|
@@ -579,10 +581,6 @@ Users only see data their plan allows:
|
|
|
579
581
|
- **Intelligence plan** required for `tl brands`, the full `tl recommender` surface, and `tl db es` access to full transcript / brand-mention data.
|
|
580
582
|
- **Paid plan** required for `tl snapshots`.
|
|
581
583
|
|
|
582
|
-
## Important: Status Labels
|
|
583
|
-
|
|
584
|
-
When presenting sponsorship status data, always use human-readable labels — never raw codes. The `tl` CLI returns lowercase labels (`sold`, `pending`, `matched`, etc.) — capitalize them for display. Full mapping: proposed, unavailable, pending, sold, advertiser_reject → "Rejected by Advertiser", publisher_reject → "Rejected by Publisher", proposal_approved → "Proposal Approved", matched, outreach → "Reached Out", agency_reject → "Rejected by Agency".
|
|
585
|
-
|
|
586
584
|
## Important: Firebolt Snapshots
|
|
587
585
|
|
|
588
586
|
`tl snapshots video` **always requires** `--channel`. Without it, the query scans 7.4 billion rows and times out. Always provide the channel ID.
|
{thoughtleaders_cli-0.6.56 → thoughtleaders_cli-0.7.0}/skills/tl/references/postgres-schema.md
RENAMED
|
@@ -191,12 +191,14 @@ A channel can have multiple adspots (different sellers: talent manager, direct,
|
|
|
191
191
|
|
|
192
192
|
When composing `SELECT ... FROM thoughtleaders_channel ...`, do not improvise column names from semantic intuition — consult the output of `tl schema pg thoughtleaders_channel` or the column table above. The `tl schema` command is authoritative. Failed guesses return *"column '\<name\>' does not exist"* and cost a round-trip. Recurring problems:
|
|
193
193
|
|
|
194
|
+
- Subscriber count is in the field named `reach`
|
|
195
|
+
- Projected views is in the field named `impression`
|
|
194
196
|
- ❌ **Suffix/qualifier variants of date columns** (e.g. an `_max` / `latest_` / `_date` form when the canonical column has neither). Date columns use bare names.
|
|
195
197
|
- ❌ **Platform-name-prefixed ID forms** (e.g. a platform-name prefix when the canonical column uses a neutral `external_` prefix). See the column table for the actual ID column.
|
|
196
198
|
- ❌ **Bare-noun forms without the table-prefix** (e.g. `name` instead of `channel_name`). This table prefixes its display fields with `channel_` to avoid SQL keyword collisions and ambiguity in joins.
|
|
197
199
|
- ❌ **User-facing-term forms used as SQL column names** (the user-facing word is sometimes different from the SQL column name; consult [business-glossary](business-glossary.md) for the canonical mapping when the two diverge).
|
|
198
200
|
|
|
199
|
-
When the canonical column you need isn't obvious from the
|
|
201
|
+
When the canonical column you need isn't obvious from the previous description, consult the output of `tl schema pg thoughtleaders_channel`. Do **not** rely on a 400 to correct you, and do **not** fall back to `information_schema.columns` as the recovery path — that's a regression marker too.
|
|
200
202
|
|
|
201
203
|
### Key columns for the `auth_user` table (Django Users)
|
|
202
204
|
|