thoughtleaders-cli 0.6.19__tar.gz → 0.6.21__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.19 → thoughtleaders_cli-0.6.21}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/AGENTS.md +17 -2
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/PKG-INFO +2 -1
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/pyproject.toml +2 -1
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl/SKILL.md +8 -1
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl/references/postgres-schema.md +41 -1
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/SKILL.md +127 -19
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/report_glossary.md +7 -5
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/tools/topic_matcher.md +4 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/__init__.py +1 -1
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/schema.py +72 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/.gitignore +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/LICENSE +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/README.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/agents/tl-analyst.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/commands/tl-balance.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/commands/tl-reports.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/commands/tl-sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/commands/tl.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/docs/architecture.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl/references/business-glossary.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl/references/elasticsearch-schema.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl/references/firebolt-schema.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/examples/golden_queries.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/columns_brands.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/columns_channels.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/columns_content.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/sortable_columns.json +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/references/widgets.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/tools/column_builder.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/tools/database_query.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/tools/keyword_research.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/tools/name_resolver.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/tools/sample_judge.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/tools/similar_channels.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl-report-builder/tools/widget_builder.md +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/_comments_common.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/ask.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/brands.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/channels.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/db.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/describe.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/doctor.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/recommender.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/reports.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/setup.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/snapshots.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/uploads.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/whoami.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/config.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/filters.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/main.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/output/formatter.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/self_update.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/tests/test_filters.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/tests/test_reports.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/tests/test_sponsorships.py +0 -0
- {thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/uv.lock +0 -0
|
@@ -61,11 +61,26 @@ This repo is also a Claude Code plugin, and can directly be installed as one.
|
|
|
61
61
|
Skills under `skills/` are split into a `SKILL.md` and one or more `references/*.md` files. To prevent drift, each fact has exactly one home:
|
|
62
62
|
|
|
63
63
|
- **CLI-shaped facts live in `SKILL.md`** — command surface, flags, filter syntax, output shapes, workflow, credit-cost curve, status-label mapping the CLI emits.
|
|
64
|
-
- **Schema-shaped facts live in `references/`** — table/column catalogues, accepted-query rules for raw DB engines (PG/ES/Firebolt), index constraints, field types, ID formats.
|
|
65
|
-
- **Business-shaped facts live in `references/business-glossary.md`** (or the equivalent glossary file) — revenue/pipeline definitions, performance grades, ownership semantics, MSN/TPP meaning, team rosters.
|
|
64
|
+
- **Schema-shaped facts live in `skills/tl/references/`** — table/column catalogues, accepted-query rules for raw DB engines (PG/ES/Firebolt), index constraints, field types, ID formats. This directory is the **single canonical home** for schema facts inside this plugin. It is a managed sync of the upstream `thoughtleaders-skills/tl-data/references/` (the source of truth across all TL agent surfaces); changes that originate here should be propagated upstream, and vice versa.
|
|
65
|
+
- **Business-shaped facts live in `skills/tl/references/business-glossary.md`** (or the equivalent glossary file) — revenue/pipeline definitions, performance grades, ownership semantics, MSN/TPP meaning, team rosters.
|
|
66
66
|
|
|
67
67
|
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.
|
|
68
68
|
|
|
69
|
+
#### Anti-pattern: skill-local schema references
|
|
70
|
+
|
|
71
|
+
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.
|
|
72
|
+
|
|
73
|
+
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.
|
|
74
|
+
|
|
75
|
+
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.
|
|
76
|
+
|
|
77
|
+
Skill-local `references/*.md` ARE appropriate when the content is **skill-shaped**, not schema-shaped:
|
|
78
|
+
- Column metadata for a specific report type (sortable columns, formula templates) — `tl-report-builder/references/columns_*.md`
|
|
79
|
+
- JSON schemas for tool-specific request/response shapes — `tl-report-builder/references/*_schema.json`
|
|
80
|
+
- Disambiguation tables, defaults, and pitfall catalogues that exist only in this skill's flow — `tl-report-builder/references/report_glossary.md`
|
|
81
|
+
|
|
82
|
+
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.
|
|
83
|
+
|
|
69
84
|
## API Response Envelope
|
|
70
85
|
|
|
71
86
|
All list endpoints return: `{ results, total, limit, offset, usage: { credits_charged, credit_rate, balance_remaining }, _breadcrumbs }`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtleaders-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.21
|
|
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,7 @@ Requires-Python: >=3.12
|
|
|
20
20
|
Requires-Dist: authlib>=1.3
|
|
21
21
|
Requires-Dist: httpx>=0.27
|
|
22
22
|
Requires-Dist: keyring>=25.0
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
24
|
Requires-Dist: rich>=13.0
|
|
24
25
|
Requires-Dist: toon-format==0.9.0b1
|
|
25
26
|
Requires-Dist: typer>=0.16
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "thoughtleaders-cli"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.21"
|
|
8
8
|
description = "ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -36,6 +36,7 @@ dependencies = [
|
|
|
36
36
|
# the beta, but uv refuses pre-releases under `>=` when a stable exists.
|
|
37
37
|
# An exact pin is the one form uv will resolve without `--prerelease=allow`.
|
|
38
38
|
"toon-format==0.9.0b1",
|
|
39
|
+
"pyyaml>=6.0",
|
|
39
40
|
]
|
|
40
41
|
|
|
41
42
|
[project.scripts]
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tl
|
|
3
|
-
description:
|
|
3
|
+
description: |
|
|
4
|
+
Query and analyze ThoughtLeaders business data using the `tl` CLI. Default to raw database queries via `tl db pg|fb|es` for anything non-trivial (joins, aggregations, multi-condition filters, anything that would otherwise need post-processing); use the structured resource commands (sponsorships, deals, channels, brands, uploads, snapshots, reports) only for trivially simple lookups (single-record show by ID, plain filtered lists). You ARE the AI layer — do not use `tl ask`.
|
|
5
|
+
|
|
6
|
+
**Use this skill for ANALYTICAL questions**: counts, metrics, trends, time-series, distributions, single-record drill-downs, revenue / pipeline-weighting math, view-curve analysis, cross-source business questions. *"How many deals did we close last quarter?"*, *"What's the weighted pipeline by sales owner?"*, *"Show me the view curve for video X"*, *"Find mentions of Surfshark in transcripts"*, *"Investigate this video"*.
|
|
7
|
+
|
|
8
|
+
**DEFER to `tl-cli:tl-report-builder`** when the user wants a **LIST of entities with filters** — channels, videos, brands, or sponsorships shaped as a report deliverable, regardless of whether they say "report" or "campaign". *"Show me partnerships from last quarter for beauty creators"*, *"Find me gaming channels with 100K+ subs"*, *"List the brands flagged as Managed Services"*, *"All sponsorships for channel X"*, *"Build me a TPP fintech list"* — every one of these goes to `tl-cli:tl-report-builder`, not here. The report-builder owns the four report types (content / brands / channels / sponsorships) and the preview/save flow; using this skill instead produces ad-hoc data dumps that bypass the saved-report system.
|
|
9
|
+
|
|
10
|
+
Quick routing test: *"would the answer to this prompt be a TL report (a list of entities I'd want to come back to)?"* — if yes, route to `tl-cli:tl-report-builder`. If no (it's a number, a chart, a single record, or an exploratory analysis), use this skill.
|
|
4
11
|
---
|
|
5
12
|
|
|
6
13
|
# ThoughtLeaders Data Analyst
|
{thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl/references/postgres-schema.md
RENAMED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# ThoughtLeaders PostgreSQL Schema Reference
|
|
2
2
|
|
|
3
|
+
> **Canonical home (within this plugin).** This file is the single source of truth for TL Postgres schema facts inside `tl-cli` (tables, columns, fetch SQL, hallucinated-column markers, join paths). Dependent skills here — most notably `tl-report-builder` — must **link to entries in this file** rather than restate columns / fetch SQL / "do not exist" markers in their own `references/*.md`. Forking schema content into a parallel `<skill>/references/*.md` produces silent drift; that anti-pattern is what this preamble exists to prevent. Upstream source of truth is `thoughtleaders-skills/tl-data/references/postgres-schema.md`; this file is a managed sync.
|
|
4
|
+
|
|
3
5
|
## How to query
|
|
4
6
|
|
|
5
7
|
```bash
|
|
@@ -191,7 +193,8 @@ A channel can have multiple adspots (different sellers: talent manager, direct,
|
|
|
191
193
|
| `id` | int | Primary key |
|
|
192
194
|
| `channel_name` | varchar | Display name. ⚠️ The column is `channel_name`, NOT `name`. |
|
|
193
195
|
| `external_channel_id` | varchar | YouTube channel ID (e.g., `UCxxxxxx`). ⚠️ There is NO `youtube_id` column — use this one. |
|
|
194
|
-
| `url` | varchar | Channel URL |
|
|
196
|
+
| `url` | varchar | Channel URL (external — usually the YouTube URL). |
|
|
197
|
+
| `slug` | varchar | TL platform slug. Used to build the canonical TL channel URL: `https://app.thoughtleaders.io/youtube/<slug>`. Prefer this over `url` when linking to a channel from any AM-facing surface (reports, samples, Slack posts) — the TL URL keeps the user inside the platform and is the company's hyperlink contract. Falls back to an ID-based TL path if `slug` is NULL; never fall back to the external YouTube URL. |
|
|
195
198
|
| `reach` | bigint | Subscriber count. ⚠️ There is NO `subscribers` column — `reach` is the subscriber count. Many internal docs and outputs use the word "subscribers"; in SQL, always query `reach`. |
|
|
196
199
|
| `media_selling_network_join_date` | date/timestamptz | When channel joined MSN. **MSN membership = this column IS NOT NULL.** |
|
|
197
200
|
| `is_tl_channel` | boolean | True = TPP/VIP channel (the small VIP subset, ~144 channels at 100k+ reach). ⚠️ **`is_tl_channel` is NOT the MSN flag.** Naive `WHERE is_tl_channel = true` as an "MSN filter" silently drops ~98% of the MSN pool (8,652 → 144 at 100k+). For MSN, use `media_selling_network_join_date IS NOT NULL`. |
|
|
@@ -233,6 +236,43 @@ Source of truth: `thoughtleaders.taxonomies.ContentCategory` (Django `IntEnum` i
|
|
|
233
236
|
| 21 | HEALTH_FITNESS | Health & Fitness |
|
|
234
237
|
| 22 | MUSIC | Music |
|
|
235
238
|
|
|
239
|
+
### `thoughtleaders_topics` (Curated Topic Taxonomy)
|
|
240
|
+
|
|
241
|
+
A small (under 20 rows), live-edited taxonomy of curated topics. Each row bundles a topic name with a one-paragraph description and a `keywords` JSONB array of head + long-tail terms. The report-builder skill's `topic_matcher` tool consumes this table to match a user's natural-language report query against curated topics; downstream filter-building uses the matched topics' `keywords` arrays as keyword groups. The taxonomy is **actively migrating** — content drifts week to week — so consumers must fetch live, never bundle a snapshot.
|
|
242
|
+
|
|
243
|
+
#### Fetch query (canonical — use verbatim)
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
tl db pg --json "SELECT id, name, description, keywords FROM thoughtleaders_topics ORDER BY id LIMIT 100 OFFSET 0"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The table has fewer than 20 rows; client-side filtering after a full fetch is free. **Do not push name-pattern WHERE clauses into the SQL** — the agent has guessed `WHERE is_active = TRUE` and `WHERE name ILIKE ANY(...)` in past runs and burnt round-trips on hallucinated columns.
|
|
250
|
+
|
|
251
|
+
| Column | Type | Description |
|
|
252
|
+
|--------|------|-------------|
|
|
253
|
+
| `id` | int | Primary key |
|
|
254
|
+
| `name` | varchar | Topic display name (e.g. "Artificial Intelligence", "PC Games") |
|
|
255
|
+
| `description` | varchar | One-paragraph human-readable description; the matcher uses it for tie-breaks |
|
|
256
|
+
| `keywords` | jsonb | Array of curated keyword strings — the matcher's primary signal. Mixes head terms (`"cooking"`) with long-tail (`"5-ingredient meals"`); long-tail matches are still strong signals, don't downgrade them. |
|
|
257
|
+
| `created_at` | timestamptz | Rarely needed |
|
|
258
|
+
| `updated_at` | timestamptz | Rarely needed |
|
|
259
|
+
| `source` | varchar | Provenance, rarely needed |
|
|
260
|
+
|
|
261
|
+
#### Columns that DO NOT exist on `thoughtleaders_topics`
|
|
262
|
+
|
|
263
|
+
Common hallucinations the agent has tried in real runs (each wasted a round-trip). All return *"column '\<name\>' does not exist"*:
|
|
264
|
+
|
|
265
|
+
- ❌ `is_active`
|
|
266
|
+
- ❌ `type` (topics are not subtyped at the schema level)
|
|
267
|
+
- ❌ `parent_id` (topics are flat, not hierarchical)
|
|
268
|
+
- ❌ `slug`, `topic_id` (the PK is `id`), `archived`, `is_published`
|
|
269
|
+
|
|
270
|
+
Cited regression markers from real runs:
|
|
271
|
+
- AI/marketing channels run: tried `thoughtleaders_topic` (singular — table doesn't exist), then `WHERE is_active = TRUE`. Three round-trips before consulting `information_schema`.
|
|
272
|
+
- Travel/digital-nomad run: tried `SELECT id, name, type, parent_id FROM thoughtleaders_topics WHERE name ILIKE ANY(...)`.
|
|
273
|
+
|
|
274
|
+
If a query against this table errors with *"column '\<X\>' does not exist"*, that's the regression marker — go back to the verbatim fetch above.
|
|
275
|
+
|
|
236
276
|
### `auth_user` (Django Users)
|
|
237
277
|
|
|
238
278
|
Standard Django user table. Used for owner lookups.
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tl-report-builder
|
|
3
|
-
description:
|
|
3
|
+
description: |
|
|
4
|
+
Build TL reports from natural-language requests. Produces an in-chat preview (sample-rows table + filter summary + takeaways) by default, or auto-saves a TL report when the user's wording is explicit about it ("save", "create the report", "make a campaign for me to come back to"). Covers the four report types: content/videos (1), brands (2), channels (3), sponsorships/deals (8).
|
|
5
|
+
|
|
6
|
+
**Use this skill — NOT `tl-cli:tl` — for ANY request that returns a list of channels, videos, brands, or sponsorships with filters applied**, even when the user doesn't say "report" or "campaign". This is the canonical path for list-shaped requests.
|
|
7
|
+
|
|
8
|
+
Triggers on every variant of "list me / find me / show me / give me / pull me / build me / make me X with filters Y", including:
|
|
9
|
+
- **Channels**: "Find me gaming channels with 100K+ subs", "show me TPP fintech creators in MSN", "channels we haven't pitched to <brand>", "look-alike channels to X", "non-MSN travel channels", "build me a list of <niche> creators", "channels matching <criteria>".
|
|
10
|
+
- **Brands**: "all brands flagged as Managed Services", "brand activity report for these specific brands: ...", "brands sponsoring <channel> in the past 6 months", "competitor brands of X".
|
|
11
|
+
- **Sponsorships / deals**: "**show me partnerships from last quarter** for <niche> creators", "Q1 2026 sold sponsorships for personal investing", "all proposal_approved deals owned by <user>", "list sponsorships with status sold and send_date 2026-05-07", "sponsorships for channel <name>".
|
|
12
|
+
- **Videos / uploads**: "videos sponsored by <brand>", "wellness videos but exclude anything sponsored by Nike or Adidas".
|
|
13
|
+
|
|
14
|
+
Save-intent variants ("save a campaign of …", "create the report …", "make a TL report for …") trigger auto-save; everything else previews. Off-taxonomy keywords ("crypto / Web3"), brand-exclusion logic ("not pitched to X"), demographic floors ("US audience ≥30%"), TPP/MSN scoping, and competitive-pitch shapes are all this skill's job — not the general `tl-cli:tl` data-analyst skill.
|
|
15
|
+
|
|
16
|
+
**Skip this skill** only for: counts, metrics, trends, single-record show-by-ID lookups, raw exploratory queries, or analytical questions that aren't shaped as "give me a list". Those go to `tl-cli:tl`.
|
|
4
17
|
---
|
|
5
18
|
|
|
6
19
|
# TL Report Builder Skill
|
|
@@ -36,6 +49,43 @@ Internally this skill thinks in phases (1–4), report types (1, 2, 3, 8), tool
|
|
|
36
49
|
- Report-type numbers (`Type 1`, `Type 3`, `Type 8`) — say "channels report", "deals report", etc.
|
|
37
50
|
- Identifier-shaped names from `tools/` and `references/` — anything that reads like a code symbol (the `snake_case` tool / step / metadata names defined in this skill, the JSON keys you see in `references/*_schema.json`, internal data-layer model names). If a term reads like a programmer typed it, it doesn't belong in front of the user.
|
|
38
51
|
- JSON-y decision codes and classification codes the user has no reason to recognize (verdict strings emitted from validation, count-bucket labels emitted alongside them — anything that's a literal value in the validation output JSON).
|
|
52
|
+
- **Internal mechanism phrases** that describe HOW the skill works rather than WHAT the user is getting. Forbidden examples (verbatim regression markers — never say any of these to the user):
|
|
53
|
+
- *"held in working memory"*
|
|
54
|
+
- *"per the skill's rules"*
|
|
55
|
+
- *"in working memory"*
|
|
56
|
+
- *"the campaign config (held in working memory; not echoed to chat per the skill's rules)"*
|
|
57
|
+
- *"campaign config JSON"* / *"the config"* / *"the JSON"* — when describing what the report contains, name the FILTERS, not the storage shape
|
|
58
|
+
- *"per the policy"* / *"the orchestration"* / *"the FilterSet"*
|
|
59
|
+
- **`Campaign` / `Campaign #N` / `campaign_id`** — these are Django model jargon. The TL platform calls these **reports**. Always say "**TL report**" / "**report #N**" / "**report id**" in user-facing text (chat replies, save-success messages, the save tail). The internal data model is named `Campaign` for historical reasons; the user has never heard that name. *"Report saved. … (Campaign #23801)"* is a leak — write *"TL report saved. … (report #23801)"* instead.
|
|
60
|
+
- **`reach` / "by reach" / "Reach"** — internal SQL column name. The user-facing term is **subscribers** (canonical mapping lives in `thoughtleaders-skills/tl-data/references/business-glossary.md`: *"AMs say subscribers, SQL says reach"*). Use `reach_from` / `reach_to` when emitting the FilterSet, but **always narrate as "subscribers"** — in sample-table column headers ("Subscribers", not "Reach"), in distribution stats ("By subscribers: 1M+ → 2", not "By reach: …"), in filter summaries ("only channels with 100K+ subscribers"), in takeaways. *"By reach: 1M+ → 2 · 100K–1M → 57 · 10K–100K → 128"* is a leak.
|
|
61
|
+
- **Raw internal IDs appended to names in sample-table rows** — e.g. `"Crypto Journey (id 1178513)"`, `"Altcoin Daily (id 28151)"`, `"FRÉ Skincare (brand_id 14625)"`. The numeric ID is implementation detail; the user is browsing channels/brands by name, not by primary key. **Show the name only**, hyperlinked per rule 20a (`[Crypto Journey](https://app.thoughtleaders.io/youtube/crypto-journey)`). The Markdown link is the addressable identifier — no raw ID needed alongside it. **Exception**: include the ID inline only when the user explicitly asked for it (*"give me the IDs too"*, *"include channel IDs"*) or when there's a real disambiguation case (two same-named channels in the sample). Otherwise the parenthetical `(id N)` is noise. *"Crypto Journey (id 1178513)"* in a normal sample row is a leak; write *"[Crypto Journey](https://app.thoughtleaders.io/youtube/crypto-journey)"*.
|
|
62
|
+
|
|
63
|
+
These are internal terms from this SKILL.md. They describe the skill's own implementation, not the user's report. If you find yourself about to type any of these, stop and re-write the sentence as a plain-English summary of what the report does (see "Filter summary" pattern below).
|
|
64
|
+
|
|
65
|
+
**Filter summary pattern** — when narrating WHAT the report (saved or previewed) actually contains, use **outcome-focused plain English**, not "the config". Translate each filter into a sentence describing what the user will see:
|
|
66
|
+
|
|
67
|
+
| Internal field | User-facing summary phrasing |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `topics: [98]` | "results will be focused on the gaming/PC games topic" |
|
|
70
|
+
| `keywords` / `keyword_groups[]` | "results will be filtered to channels mentioning <keywords>" |
|
|
71
|
+
| `reach_from: 100000` | "only channels with 100K+ subscribers will be included" |
|
|
72
|
+
| `languages: ["en"]` | "only English-speaking channels will be included" |
|
|
73
|
+
| `creator_countries: ["US"]` | "results will be limited to US creators" |
|
|
74
|
+
| `min_demographic_usa_share: 50` | "only channels with strong US audiences will be included" |
|
|
75
|
+
| `channel_formats: [4]` | "only YouTube long-form video channels will be included" (omit if it's the default) |
|
|
76
|
+
| `msn_channels_only: true` | "results will be limited to MSN channels" |
|
|
77
|
+
| `is_tl_channel = TRUE` (resolved into `channels` M2M) | "results will be limited to TPP channels" |
|
|
78
|
+
| `outreach_email: "exists"` | "only outreach-ready creators (with email on file) will be included" |
|
|
79
|
+
| `tl_sponsorships_only: true` | "results will prioritise creators with proven TL sponsorship history" |
|
|
80
|
+
| `cross_references[].exclude_proposed_to_brand: ["Webull"]` | "channels already pitched to Webull will automatically be excluded" |
|
|
81
|
+
| `cross_references[].include_sponsored_by_mbn` | "results will be limited to creators MBN brands are working with" |
|
|
82
|
+
| `sort: "-reach"` | "results will be sorted by largest subscriber count first" |
|
|
83
|
+
| `sort: "-mentions_count"` | "results will be sorted by strongest sponsorship performance" |
|
|
84
|
+
| `start_date` / `end_date` / `days_ago` | "results will cover <date range / last N days>" |
|
|
85
|
+
| `columns: { ... }` (the chosen column set) | "outreach-ready columns will be included automatically" — don't list them by code-name; describe the focus (outreach / discovery / pricing / pipeline) |
|
|
86
|
+
| `widgets: [...]` | "performance widgets will be added automatically" — describe the focus, not the aggregator names |
|
|
87
|
+
|
|
88
|
+
Compose 4–7 of these into a short bulleted summary directly in chat. Use the user's own brand and keyword wording verbatim where possible. Don't list every filter — only the ones that meaningfully shape what the user will see.
|
|
39
89
|
|
|
40
90
|
**Allowed**: specific channel / brand / video / advertiser names from the data, the user's own keywords, plain words like "results", "matches", "sample", "noise", "filter", "search", "report", "column", "chart". Plain-English words that happen to coincide with an internal label *as English* (e.g. "the result is narrow", "a normal-size result") are fine — the test is whether the user reads it as English or as a code symbol. The same word as `count_classification: "narrow"` is forbidden; in "the result is narrow" it's fine.
|
|
41
91
|
|
|
@@ -55,9 +105,9 @@ Internally this skill thinks in phases (1–4), report types (1, 2, 3, 8), tool
|
|
|
55
105
|
| Phase 4 — widget builder | "Choosing the charts and dashboards…" |
|
|
56
106
|
| Phase 4 — final composition | "Putting the final report together…" |
|
|
57
107
|
| Preview path (default) — show takeaways + sample table | "Here's what matches…" / "Found N channels — top by reach:" / "Top videos that match:" |
|
|
58
|
-
| Preview tail (ambiguous middle — close with this) | *"If you want this saved as a
|
|
59
|
-
| Save step (write JSON
|
|
60
|
-
| Save success (only after the CLI command returns success) | "
|
|
108
|
+
| Preview tail (ambiguous middle — close with this) | *"If you want this saved as a TL report you can come back to, say save."* |
|
|
109
|
+
| Save step (resolve portable temp path → write JSON → verify file exists → `tl reports create --config-file <path> --yes`) | "Saving the report…" |
|
|
110
|
+
| Save success (only after the CLI command returns success) | "**TL report saved.**" + link from the CLI response, optionally with "(report #N)" using the campaign_id rendered as a *report* number — never as "Campaign #N". Do NOT echo the JSON config back; do NOT say "saved as <path>.json" (temp file is transport, not the deliverable); do NOT say "Campaign #..." (Django model jargon — say "report"). |
|
|
61
111
|
| Save failure | "Couldn't save the report: <plain-English reason>" — surface the CLI's stderr verbatim if it's user-readable, otherwise summarise |
|
|
62
112
|
| User says "save" / "yes save it" / "save it" after a preview | "Saving…" — re-use the config from working memory; do NOT re-run Phases 1–4 |
|
|
63
113
|
| Mode B follow-up (looks_wrong) | "The top results don't look right — here are your options…" |
|
|
@@ -108,7 +158,7 @@ Here's a real request and the gap between leaky narration (the failure mode) and
|
|
|
108
158
|
>
|
|
109
159
|
> Putting the final report together now — outreach-focused columns (deal history, contact info, demographic fit) and the standard channels-report charts. *(saving…)*
|
|
110
160
|
>
|
|
111
|
-
> **
|
|
161
|
+
> **TL report saved.** [Investing — unpitched investing channels](https://app.thoughtleaders.io/...) (report #12345)
|
|
112
162
|
>
|
|
113
163
|
> **Worth knowing**:
|
|
114
164
|
> - 668 channels already pitched to investing.com are excluded.
|
|
@@ -151,9 +201,9 @@ Same architecture, different intent. The prompt is exploratory; the policy says
|
|
|
151
201
|
> - Topic anchored on the curated investing keyword set; the spot-check looked clean (1 Pokémon channel slipped in via a "trading" keyword match — easy to filter further if needed).
|
|
152
202
|
> - Sort is most-recently-active first so dormant channels don't crowd the top.
|
|
153
203
|
>
|
|
154
|
-
> *If you want this saved as a
|
|
204
|
+
> *If you want this saved as a TL report you can come back to, just say save.*
|
|
155
205
|
|
|
156
|
-
If the user replies *"yes save it"* or *"save"* → run the save step (write
|
|
206
|
+
If the user replies *"yes save it"* or *"save"* → run the save step (resolve a portable temp path → write → verify → invoke `tl reports create --config-file <that-exact-path> --yes`; see Save-or-preview policy step 1+2 for the full mechanics) using the **same config that's already in working memory**. Don't re-run Phases 1–4. The follow-up reply is just the takeaways + saved-report link.
|
|
157
207
|
|
|
158
208
|
What changes between save-mode and preview-mode:
|
|
159
209
|
|
|
@@ -162,7 +212,7 @@ What changes between save-mode and preview-mode:
|
|
|
162
212
|
| Phases 1–4 run? | Yes | Yes (identical) |
|
|
163
213
|
| Campaign row in DB? | Yes | No |
|
|
164
214
|
| What ends in chat | Takeaways + saved-report URL | Takeaways + sample table + "say save" tail |
|
|
165
|
-
|
|
|
215
|
+
| Portable-temp transport file (`<system-temp>/tl-report-builder-<slug>.json`) written? | Yes (transport for `tl reports create`) | No (config stays in working memory) |
|
|
166
216
|
| `tl reports create` invoked? | Yes (`--config-file <path> --yes`) | No |
|
|
167
217
|
| Campaign-config JSON in chat? | **No** | **No** |
|
|
168
218
|
|
|
@@ -309,10 +359,32 @@ There is no fifth phase. Phase 4's output IS the deliverable. The skill itself n
|
|
|
309
359
|
> - **Default to preview**, then close the reply with one line: *"If you want to save this as a campaign you can come back to, just say save."*
|
|
310
360
|
> - Conservative move — never persist on ambiguity. If the user wanted it saved they will say so.
|
|
311
361
|
>
|
|
312
|
-
> **Save mechanics** (when save is triggered):
|
|
362
|
+
> **Save mechanics** (when save is triggered): three strict steps. **Step 1 alone is not the save** — the file write is just transport for step 3. Saying "Saved as foo.json" or "Saved to <path>" after only doing step 1 is a regression bug.
|
|
313
363
|
>
|
|
314
|
-
> 1. **
|
|
315
|
-
>
|
|
364
|
+
> 1. **Resolve a portable temp path FIRST** — never hardcode `/tmp/`. Use `Bash` to query the system temp directory at runtime so the path works on Linux, macOS, AND Windows:
|
|
365
|
+
> ```bash
|
|
366
|
+
> python -c "import tempfile, os; print(os.path.join(tempfile.gettempdir(), 'tl-report-builder-<short-slug>.json'))"
|
|
367
|
+
> ```
|
|
368
|
+
> Capture the printed path verbatim. On Linux/macOS this resolves to something like `/tmp/tl-report-builder-foo.json`; on Windows it resolves to `C:\Users\<user>\AppData\Local\Temp\tl-report-builder-foo.json`. **Hardcoding `/tmp/` on Windows silently fails** — the Write tool may report success but the file lands somewhere the CLI can't read in step 3. The `python -c "import tempfile..."` pattern works on every platform Claude Code runs on.
|
|
369
|
+
> 2. **Write the JSON to that resolved path via the `Write` tool, then verify it landed.** Immediately after the write, run `Bash`:
|
|
370
|
+
> ```bash
|
|
371
|
+
> test -f "<resolved-path>" && wc -c "<resolved-path>" || echo "MISSING"
|
|
372
|
+
> ```
|
|
373
|
+
> If the verification reports `MISSING` (or the byte count is 0), STOP and surface a clean error to the user — **do NOT instruct them to save it themselves** (that would conflict with rule 15's ban on user self-save fallbacks). Phrase it as a bug-report-shaped message acknowledging the save couldn't run, with the JSON attached as a recovery artifact (not as a save instruction):
|
|
374
|
+
>
|
|
375
|
+
> ```
|
|
376
|
+
> Couldn't save the report — the temp directory at <resolved-path>
|
|
377
|
+
> isn't writable, so I couldn't stage the config for the CLI. This
|
|
378
|
+
> is a bug in the skill / environment, not something you need to do.
|
|
379
|
+
>
|
|
380
|
+
> The validated config is below as a recovery artifact in case you
|
|
381
|
+
> want to retry from a different machine. I haven't sent it to TL.
|
|
382
|
+
>
|
|
383
|
+
> <inline JSON in a code block, fenced>
|
|
384
|
+
> ```
|
|
385
|
+
>
|
|
386
|
+
> Do not invoke the CLI in this branch; that would just produce a confusing "No such file or directory" error. The inline JSON is a fallback **artifact**, not an instruction — the user is not expected to run anything themselves.
|
|
387
|
+
> 3. **Invoke `tl reports create --config-file <that-same-resolved-path> --yes`** via the `Bash` tool. This is what actually saves the report. Read the CLI's response: success returns a `campaign_id` and `report_url` to echo to the user; failure returns a non-zero exit and an error message — surface that error verbatim, do NOT silently mark the report as saved. **Use the EXACT same path string** the verification step in (2) confirmed; don't paraphrase or convert slashes between Unix/Windows styles. Never write to the user's current working directory or any project path — the file is a transport, not a deliverable.
|
|
316
388
|
>
|
|
317
389
|
> **Preview mechanics** (default): show **the sample-rows table FIRST**, then takeaways, then the closing "say save" tail. The table is the deliverable in preview mode — takeaways describe it, but the table itself is what the user asked for. **Skipping the table is a regression bug** (Phase 4 hard rule 14). Use the `db_sample` rows Phase 2 already collected (top 5–10 by sort key) and format as a tight Markdown table with 2–4 type-relevant columns:
|
|
318
390
|
> - Type 3 (channels): `Channel | Subscribers | Last published`
|
|
@@ -320,11 +392,15 @@ There is no fifth phase. Phase 4's output IS the deliverable. The skill itself n
|
|
|
320
392
|
> - Type 2 (brands): `Brand | Mentions | Channels`
|
|
321
393
|
> - Type 8 (deals/sponsorships): `Channel | Brand | Status | Send date`
|
|
322
394
|
>
|
|
323
|
-
> After the table, give 2–4 takeaways (count, niche fit, noise warnings, sort note). Then close with
|
|
395
|
+
> After the table, give 2–4 takeaways (count, niche fit, noise warnings, sort note). Then close with the **save tail**: *"If you want this saved as a TL report you can come back to, just say save."*
|
|
396
|
+
>
|
|
397
|
+
> **The save tail is MANDATORY in every preview reply** — including when the user's wording sounds informational ("find me…", "show me…", "are there any…"). The previous "skip when purely informational" exemption was too easy to over-apply: a real run for *"Find creators for FRÉ Skincare — should be female creator, US-based, majority female audience, filter out everyone already submitted, include a CPM column, min 2,000 projected views"* produced a polished preview with notes-for-the-AM and follow-up refinement options — but no save tail — even though the prompt was clearly designing a TL report (specific filters, custom column, brand-exclusion intent). Cost of including the tail when the user didn't want it: one ignorable line. Cost of skipping it when they did: they don't know the option exists. Always include it.
|
|
398
|
+
>
|
|
399
|
+
> If the user's preview-intent prompt happens to also include implicit save signals (specific column requests, structural design choices, request for a "list" they intend to act on), append a slightly more directive variant of the tail: *"If you want this as a saved TL report, just say save."* Same outcome; the tail is always there.
|
|
324
400
|
>
|
|
325
|
-
> **The JSON config never appears in chat in either path.** In save mode it
|
|
401
|
+
> **The JSON config never appears in chat in either path.** In save mode it lives in the portable-temp transport file; in preview mode it stays in working memory. JSON in chat is implementation noise and a regression we already shipped a fix for once.
|
|
326
402
|
>
|
|
327
|
-
> **Edits** to a saved report use `tl reports update <id> '<json>'` — same shell-quoting caveat as save: when the patch contains apostrophes, write to a
|
|
403
|
+
> **Edits** to a saved report use `tl reports update <id> '<json>'` — same shell-quoting caveat as save: when the patch contains apostrophes, write to a portable temp file (resolved at runtime per step 1) and use `tl reports update <id> "$(cat <that-path>)"`. Don't tell users to paste JSON into the platform UI; that's an obsolete pre-v0.6.12 fallback.
|
|
328
404
|
>
|
|
329
405
|
> **Reads via `tl db es` / `tl db pg` (engine routed by report type — see Step 2.V1), writes via the CLI** is the architectural split.
|
|
330
406
|
|
|
@@ -398,6 +474,7 @@ Each tool fires only when its criteria are explicitly met (no automatic / specul
|
|
|
398
474
|
### T1 — `tools/topic_matcher.md`
|
|
399
475
|
**Fires when**: `ReportType ∈ {1, 2, 3}` AND USER_QUERY mentions a topic concept that could plausibly map to a curated topic in `thoughtleaders_topics`.
|
|
400
476
|
**Skipped when**: `ReportType == 8` (sponsorships don't use topic matching at the SQL level) OR USER_QUERY is purely an entity-name lookup ("emails for these channels").
|
|
477
|
+
**How to fetch the live topics**: see the `tl-cli:tl` skill's Postgres-schema reference — [`tl/references/postgres-schema.md` → `thoughtleaders_topics`](../tl/references/postgres-schema.md#thoughtleaders_topics-curated-topic-taxonomy). That's the canonical home for the fetch query, column list, and "do not guess" regression markers. Don't restate the SQL here.
|
|
401
478
|
**Output**: per-topic verdicts (strong/weak/none) + summary. If `summary.strong_matches` non-empty, the topic's curated `keywords[]` array drives the FilterSet's `keywords` field (with per-position `content_fields` set via `keyword_content_fields_map` when a keyword targets a non-default match surface). Phase 2 may also emit the matched topic IDs directly via the FilterSet's `topics` field — both paths are valid; pick by intent.
|
|
402
479
|
|
|
403
480
|
### T2 — `tools/keyword_research.md`
|
|
@@ -1406,10 +1483,10 @@ Pseudo-shape (not runnable JSON — `<int>`, `|`-unions, and `/* notes */` are p
|
|
|
1406
1483
|
5. **Takeaways cite specifics.** Numbers, names, intent labels. Vague takeaways ("the report looks good") add no value.
|
|
1407
1484
|
6. **No new filters or columns in Phase 4.** Phase 4 doesn't reshape the FilterSet or add columns — it picks widgets, validates, and composes. Reshape requires looping back to Phase 2 or 3.
|
|
1408
1485
|
7. **Type-8 axis consistency.** Both `_over_<axis>` histograms in the same type-8 report use the SAME axis (per `sponsorship_widget_schema.json`'s `_tl_axis_branching`).
|
|
1409
|
-
8. **Don't echo `campaign_config_json` back to chat — ever.** In save mode the JSON lives in the
|
|
1486
|
+
8. **Don't echo `campaign_config_json` back to chat — ever.** In save mode the JSON lives in the portable-temp transport file passed to `tl reports create --config-file <path> --yes` (path resolved at runtime per Save-or-preview policy step 1). In preview mode it stays in working memory. **There is no flow where the campaign-config JSON belongs in the chat output.** See the Save-or-preview policy at the top of this file for the full split between save mode and preview mode.
|
|
1410
1487
|
9. **When saving, use `--config-file <path>`, not `--config '<json>'`.** Passing JSON inline through a single-quoted shell argument breaks the moment any string value contains an apostrophe (which is common — "McDonald's", "L'Oréal", channel/title text). The temp-file transport sidesteps shell quoting entirely.
|
|
1411
|
-
10. **Temp file MUST be under
|
|
1412
|
-
11. **Writing the file is NOT saving the report.** The save happens when `tl reports create --config-file <path> --yes` returns success. Until that command's exit code is read, the report does not exist. **Never tell the user "saved as <path>.json"** — that confuses the transport file (which is throwaway) with the saved
|
|
1488
|
+
10. **Temp file MUST be under the system temp directory** — resolved at runtime via `python -c "import tempfile, os; print(os.path.join(tempfile.gettempdir(), '<name>'))"` so the path is correct on every platform (Linux/macOS: typically `/tmp/...`; Windows: `C:\Users\<user>\AppData\Local\Temp\...`). Never hardcode `/tmp/` — that fails silently on Windows. Never write the transport file to the user's current working directory, project root, repo, or any other path they might be looking at. Pollution of cwd with `foo_report.json` is a regression bug.
|
|
1489
|
+
11. **Writing the file is NOT saving the report.** The save happens when `tl reports create --config-file <path> --yes` returns success. Until that command's exit code is read, the report does not exist. **Never tell the user "saved as <path>.json"** — that confuses the transport file (which is throwaway) with the saved TL report (which is what they asked for). The save-success message must come from the CLI response: a `campaign_id` (rendered to the user as **"report #N"**, NOT "Campaign #N") and `report_url`.
|
|
1413
1490
|
12. **Default to preview, not save.** Phases 1–4 always run, but the chat output is takeaways + a sample-rows table by default. **Only save when the user's prompt contains explicit save intent** — see the Save-or-preview policy near the top for the trigger word lists. Ambiguous middle ("build a report on X", "create a campaign for Y") → preview + the closing "say save" tail. Save is the explicit, opt-in path; preview is the conservative default.
|
|
1414
1491
|
13. **In preview mode the agent does not invoke `tl reports create`** and does not write a temp file. The campaign config stays in working memory. If the user follows up with "save" / "yes" / "go ahead", re-use that same in-memory config — do not re-run Phases 1–4.
|
|
1415
1492
|
14. **Preview output MUST include a sample-rows table.** Use the `db_sample` rows Phase 2 already collected (top 5–10 by sort key) and render them as a tight Markdown table with type-specific columns per the Save-or-preview policy:
|
|
@@ -1418,14 +1495,45 @@ Pseudo-shape (not runnable JSON — `<int>`, `|`-unions, and `/* notes */` are p
|
|
|
1418
1495
|
- Type 2 (brands): `Brand | Mentions | Channels`
|
|
1419
1496
|
- Type 8 (deals/sponsorships): `Channel | Brand | Status | Send date`
|
|
1420
1497
|
**Takeaways alone are not a preview** — the user asked for results; takeaways describe the result, the table IS the result. Skipping the sample table because the result feels narrow, or because the prompt felt "report-y", is a regression bug. The table comes from data Phase 2 already pulled; it costs nothing extra to render.
|
|
1421
|
-
15. **When save intent is detected, the agent MUST invoke `tl reports create` itself.** Telling the user "Save it via POST to the report-creation API endpoint when ready" or "to save, run `tl reports create --config '<json>'`" or any other form of "you save it yourself" is a regression bug — that's the obsolete pre-v0.6.12 fallback. If the prompt contains any save-intent word (see Save-or-preview policy: "save", "create the report", "create a campaign", "make a campaign for me to come back to", "publish", "persist") the flow is:
|
|
1498
|
+
15. **When save intent is detected, the agent MUST invoke `tl reports create` itself.** Telling the user "Save it via POST to the report-creation API endpoint when ready" or "to save, run `tl reports create --config '<json>'`" or any other form of "you save it yourself" is a regression bug — that's the obsolete pre-v0.6.12 fallback. If the prompt contains any save-intent word (see Save-or-preview policy: "save", "create the report", "create a campaign", "make a campaign for me to come back to", "publish", "persist") the flow is the three steps in Save-or-preview policy step 1+2+3: **resolve a portable temp path → Write the JSON → verify the file exists → invoke `tl reports create --config-file <that-exact-path> --yes`** → echo the campaign_id + report_url from the CLI's response. The user never sees the JSON, never gets told to do something themselves. If the CLI returns an error, surface it; do not fall back to "here's the JSON, you do it".
|
|
1422
1499
|
16. **Forbidden phrases** (these are regression markers — if you see yourself about to type any of these, stop and re-read rule 15):
|
|
1423
1500
|
- "Save it via POST to the report-creation API endpoint when ready"
|
|
1424
1501
|
- "Save it via the report-creation API endpoint when ready"
|
|
1425
1502
|
- "to save, run `tl reports create --config '<json>'`"
|
|
1426
1503
|
- "Saved as <path>.json" (without a campaign_id from the CLI)
|
|
1427
1504
|
- "Saved to <path>" (without a campaign_id)
|
|
1505
|
+
- "held in working memory; not echoed to chat per the skill's rules"
|
|
1506
|
+
- "the campaign config (held in working memory…)"
|
|
1507
|
+
- "per the skill's rules" / "per the policy"
|
|
1428
1508
|
- Any instruction telling the user to take a save action themselves when the original prompt was a save-intent prompt.
|
|
1509
|
+
17. **Always render a plain-English filter summary in the user-facing reply** — both in save mode and preview mode. The summary is 4–7 short bullets describing **what the report contains**, not how it's stored. Use the "Filter summary pattern" translation table in the user-facing-language section near the top of this file. Mention only the filters that meaningfully shape what the user will see; skip platform defaults (e.g. don't bullet `channel_formats: [4]` when it's the type-3 default). Use the user's own brand and keyword wording verbatim where it fits. Example: *"results will be focused on fintech creators in MSN; only English-speaking channels with strong US audiences will be included; channels already pitched to Webull will be automatically excluded; results will prioritise creators with proven sponsorship history; outreach-ready columns and performance widgets will be added automatically"*. **Don't describe the report as "the config" or "the JSON" or "held in working memory"** — those are internal terms; the user wants to know what the report does.
|
|
1510
|
+
18. **Save-mode preflight on the temp file is mandatory.** Per the Save-or-preview policy step 1+2: resolve a portable temp path via `python -c "import tempfile, os; print(...)"` BEFORE writing, then verify with `test -f <path>` AFTER writing. Hardcoding `/tmp/` on Windows fails silently. If the verification fails, surface a clean error explaining what happened (path, why) and offer the user the inline JSON as a fallback. Do not invoke `tl reports create --config-file` if the file isn't confirmed to exist — that just produces a confusing "No such file or directory" error.
|
|
1511
|
+
19. **Narrate at phase-outcome level, not tool-call level.** The user doesn't need to see "Ran 19 commands, read 2 files" enumerated, or the raw text of every `tl db pg` query the skill issued during validation. Surface the phase outcomes in plain English: "Looking up StoryBlocks in the brand list… found it (47 deals on file)." not "Ran tl db pg --json 'SELECT id, name FROM thoughtleaders_brand WHERE name ILIKE %StoryBlocks%' which returned: {results: [{id: 868, name: 'StoryBlocks'}], total: 1, ...}". The harness shows tool-call detail in collapsible UI; the skill's narration is the high-level story alongside it.
|
|
1512
|
+
20. **Save tail is mandatory in every preview reply.** The previous "skip when the prompt is purely informational" exemption was over-applied (a live FRÉ Skincare run skipped the tail even though the prompt was clearly designing a TL report — specific filters, custom column, brand-exclusion logic; a live aviation/non-MSN run skipped it again, closing only with a refinement offer — *"If you want me to tighten to fixed-wing-only or drop drones, say the word and I'll re-filter."* — which is **not** the same thing). Always close the preview with *"If you want this as a saved TL report, just say save."* The line is one ignorable sentence if the user didn't want a save; if they did, it's the only signal that telling the agent to save is even an option.
|
|
1513
|
+
|
|
1514
|
+
**Refinement offers do NOT substitute for the save tail.** Both can appear in the same closing — refinements first ("Want to tighten to fixed-wing only? Drop drones? Add a CPM column?"), then the save tail on its own line ("If you want this as a saved TL report, just say save."). The save tail is **always last** so it's the line the user sees most recently when they read the reply bottom-up.
|
|
1515
|
+
20a. **Channel/video/brand names in the sample-rows table MUST be hyperlinked to the TL platform page** (not to YouTube). The user is browsing the result *in TL*; the link is the affordance to drill into a row's full TL profile. URL patterns:
|
|
1516
|
+
|
|
1517
|
+
| Sample-table column | Link target | Slug source |
|
|
1518
|
+
|---|---|---|
|
|
1519
|
+
| **Channel** (type 3 / type 8) | `https://app.thoughtleaders.io/youtube/<slug>` | `thoughtleaders_channel.slug` (resolve in Phase 2 alongside the sample) |
|
|
1520
|
+
| **Brand** (type 2) | `https://app.thoughtleaders.io/brands/<slug>` | brand-side equivalent slug |
|
|
1521
|
+
| **Title** (type 1 / videos) | `https://app.thoughtleaders.io/articles/<id>` (or whatever the platform's video-detail URL is) | the article id |
|
|
1522
|
+
|
|
1523
|
+
Render as Markdown links in the table cell — *not* the bare ID, *not* the YouTube URL, *not* both. Example for type 3:
|
|
1524
|
+
|
|
1525
|
+
```
|
|
1526
|
+
| Channel | Subscribers | Last published |
|
|
1527
|
+
|--------------------------|------------:|----------------|
|
|
1528
|
+
| [Jubilee](https://app.thoughtleaders.io/youtube/jubilee) | 12.4M | 2 days ago |
|
|
1529
|
+
| [PewDiePie](https://app.thoughtleaders.io/youtube/pewdiepie) | 110M | yesterday |
|
|
1530
|
+
```
|
|
1531
|
+
|
|
1532
|
+
If the slug is missing or empty for a row, fall back to the ID-based path the platform exposes (e.g. `https://app.thoughtleaders.io/youtube/id-<channel_id>`); never fall back to the YouTube URL — that takes the user *away* from TL. The Phase 2 sample query must include the slug column alongside the rendered fields, otherwise the table can't link properly.
|
|
1533
|
+
|
|
1534
|
+
21. **No side-channel deliverables.** The skill produces exactly two output shapes: (a) a saved TL Campaign + a campaign URL (save mode), or (b) an in-chat preview with the sample-rows table + takeaways + save tail (preview mode). It does NOT write CSVs, Markdown reports, or any other "data dump" file to disk as a deliverable. A real run for FRÉ Skincare wrote a CSV to `<temp>\fre-skincare-shortlist.csv` and pointed the user at it as the "full list" — that's a fabricated alternative deliverable that bypasses the TL report-creation flow. If the user wants more than the preview shows, the answer is "save it as a campaign and run it" — not "I'll dump CSV". The only filesystem write the skill is allowed to make is the `<system-temp>/tl-report-builder-<slug>.json` transport file used in step 1 of the save mechanics, and even that is a transport (deleted whenever) — never a deliverable.
|
|
1535
|
+
22. **Phases 1–4 always run; the skill never short-circuits to a chat-only data answer.** When the skill is invoked, the output is **always** a Campaign (save mode) or a Phase-4 preview (preview mode). Bypassing Phase 1–4 to produce a verification table, an analyst summary, a list cross-check, or any other "I'll just answer this directly in chat" deliverable is a regression bug. Real example to internalise: a prompt of *"Brands sponsoring Linus Tech Tips in the past 6 months: dbrand, Private Internet Access, Squarespace, Vessi, Secretlab, UGREEN, Odoo, Dell, Razer, Saily"* should route through Phase 1 → Type 2 brands report scoped to channel 1788 + last 180 days → Phases 2/3/4 → preview with the user's seed brands as a starting filter and the takeaways calling out *"your seed list is accurate but incomplete — TL data shows 60 distinct sponsors over 131 videos; top missing are War Thunder (7), Boot.dev (6), DeleteMe (6)…"*. Instead, a recent run produced exactly that analytical content **as a free-floating markdown table in chat** — no FilterSet emitted, no columns picked, no widgets, no save option. The analytical insight is welcome as a takeaway; it is **not** a substitute for the report. If you find yourself replying with a markdown table directly, ask: am I about to ship a Phase-4 preview, or am I bypassing the phases? The answer must always be the former.
|
|
1536
|
+
23. **No ad-hoc data-engineering pipelines.** The skill does NOT write Python consolidation scripts, multi-stage CSV merge tools, dedupe scripts, false-positive filters as standalone files, or any other custom data pipeline as part of producing the deliverable. The data plane is fixed: `tl db pg` (PG), `tl db es` (ES), `tl db fb` (Firebolt). Phase 2 issues queries against these directly to compose a FilterSet and validate it; that's the entire data-side surface. A real aviation/non-MSN run produced this anti-pattern: the agent issued five separate PG queries each writing a CSV (`/tmp/aviation_by_name.csv`, `/tmp/aviation_desc.csv`, `/tmp/aviation_desc2.csv`, `/tmp/aviation_desc3.csv`, `/tmp/aviation_pilot_desc.csv`), wrote a `consolidate_aviation.py` script to merge + dedupe + filter false positives, hit a Windows-vs-Linux `/tmp/` path mismatch, debugged it with `cygpath`, eventually rewrote the script to use `%LOCALAPPDATA%\Temp`, then produced `aviation_consolidated.csv` as the "full list". **None of this is the skill's job.** The right shape: one ES query with `terms` / `bool.should` filters covering the niche keywords + the `creator_countries` filter + `msn_channels_only: false` + `is_active: true` → get count + sample → emit the FilterSet → preview. If the skill's narration is starting to read like a data engineer's bash session ("Run consolidation script", "Try /tmp path resolution", "Resolve /tmp via cygpath", "Find where /tmp files actually are"), stop — the skill has gone off the rails. Restart from Phase 1 with a single composed query.
|
|
1429
1537
|
|
|
1430
1538
|
## Follow-Up Interactions
|
|
1431
1539
|
|
|
@@ -1480,7 +1588,7 @@ USER: Build me a report of gaming channels with 100K+ subscribers in English
|
|
|
1480
1588
|
|
|
1481
1589
|
Claude follows this SKILL.md, executing each phase in order. No external command needed — the skill IS the orchestration; `tl db pg` is invoked from within Phase 2/3/4 as needed; tools fire conditionally per their criteria.
|
|
1482
1590
|
|
|
1483
|
-
> **Save vs preview**: by default the skill runs Phases 1–4 and replies with takeaways + a sample-rows table — **no save**. Only when the user's prompt contains explicit save intent ("save", "create the report", "make a campaign for me to come back to") does the skill (1)
|
|
1591
|
+
> **Save vs preview**: by default the skill runs Phases 1–4 and replies with takeaways + a sample-rows table — **no save**. Only when the user's prompt contains explicit save intent ("save", "create the report", "make a campaign for me to come back to") does the skill run the three save-mechanics steps: (1) **resolve a portable temp path** via `python -c "import tempfile, os; print(...)"`, (2) **Write** the JSON to that path and **verify** with `test -f`, (3) run `tl reports create --config-file <that-exact-path> --yes` via `Bash`. The file transport is shell-safe; passing the JSON inline as `--config '<json>'` breaks the moment any value contains an apostrophe ("McDonald's", "L'Oréal"). Hardcoding `/tmp/` fails on Windows. The user sees the takeaways and (in save mode) the resulting campaign link. **The JSON config never appears in chat in either mode.** For edits to an existing saved report, use `tl reports update <report_id> '<json patch>'` (same shell-quoting caveat — use a portable temp file when the patch contains apostrophes). Do NOT tell users to paste into the platform UI — that's an obsolete fallback from before the CLI commands existed. See the Save-or-preview policy near the top for the full trigger word lists.
|
|
1484
1592
|
|
|
1485
1593
|
## Reference Files
|
|
1486
1594
|
|
|
@@ -115,13 +115,15 @@ When two fields look similar, use this table to pick.
|
|
|
115
115
|
| "Channels created on TL since X" | `createdat_from` (+ `createdat_to`) | This is the TL-side AdLink/Channel record creation, not the YouTube publish date. |
|
|
116
116
|
| Sponsorship send/publish | `start_date` / `end_date` (type 8 reuses these for send_date) | Type 8's date semantics shift — the schema docstring explains. |
|
|
117
117
|
|
|
118
|
-
###
|
|
118
|
+
### Channel-size signals
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
> **Vocabulary**: SQL/internal term is `reach`, user-facing term is **subscribers**. See `thoughtleaders-skills/tl-data/references/business-glossary.md` for the canonical mapping (line 149: *"AMs say subscribers, SQL says reach"*). When emitting the FilterSet, use the field name `reach_from` / `reach_to`. When narrating to the user (chat replies, sample-table column headers, takeaways, filter summaries), say **"subscribers"** — never **"reach"**. *"By reach: 1M+ → 2 · 100K–1M → 57"* is a leak; write *"By subscribers: 1M+ → 2 · 100K–1M → 57"*.
|
|
121
|
+
|
|
122
|
+
| User intent | Field to emit | Narrate as |
|
|
121
123
|
|---|---|---|
|
|
122
|
-
| "Big channels" / "
|
|
123
|
-
| "Channels expecting >X projected views per video" | `projected_views_from` (+ `projected_views_to`) | PV is a forward-looking estimate; better than
|
|
124
|
-
| Raw YouTube views per video | `youtube_views_from` (+ `youtube_views_to`) |
|
|
124
|
+
| "Big channels" / "100K+ subscribers" / size floor | `reach_from` (+ `reach_to`) | "subscribers" / "channel size" |
|
|
125
|
+
| "Channels expecting >X projected views per video" | `projected_views_from` (+ `projected_views_to`) | "projected views" — PV is a forward-looking estimate; better than subscribers when intent is sponsor-deal pricing |
|
|
126
|
+
| Raw YouTube views per video | `youtube_views_from` (+ `youtube_views_to`) | "views per video" — per-upload metric, only meaningful for type 1 (CONTENT) |
|
|
125
127
|
|
|
126
128
|
### Demographic shares
|
|
127
129
|
|
|
@@ -24,6 +24,10 @@ The orchestration injects two values:
|
|
|
24
24
|
```
|
|
25
25
|
The topics list changes over time; never assume a fixed count or fixed IDs.
|
|
26
26
|
|
|
27
|
+
### How to fetch the topics
|
|
28
|
+
|
|
29
|
+
The fetch query, the column list, and the negative-column regression markers all live in the canonical Postgres-schema reference in the `tl-cli:tl` skill: **[`tl/references/postgres-schema.md` → `thoughtleaders_topics`](../../tl/references/postgres-schema.md#thoughtleaders_topics-curated-topic-taxonomy)**. Schema-shaped facts belong in that reference, not in tool text. Use the verbatim fetch query documented there. **Do not restate or paraphrase the schema here.** If you find yourself about to type `SELECT … FROM thoughtleaders_topics …` from memory, stop and consult the reference file instead. This tool's job is to score topics against the user query; the schema reference's job is to say what the underlying table looks like.
|
|
30
|
+
|
|
27
31
|
---
|
|
28
32
|
|
|
29
33
|
## Output schema (strict)
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"""tl schema — Show raw-db schema documentation for `tl db pg|fb|es`."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import re
|
|
4
5
|
|
|
5
6
|
import typer
|
|
7
|
+
import yaml
|
|
6
8
|
from rich.console import Console
|
|
7
9
|
from rich.markdown import Markdown
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
from rich.tree import Tree
|
|
8
12
|
|
|
9
13
|
from tl_cli.client.errors import ApiError, handle_api_error
|
|
10
14
|
from tl_cli.client.http import get_client
|
|
@@ -12,6 +16,68 @@ from tl_cli.client.http import get_client
|
|
|
12
16
|
app = typer.Typer(help="Show schema documentation for raw db queries (`tl db pg|fb|es`)")
|
|
13
17
|
console = Console()
|
|
14
18
|
|
|
19
|
+
# Pulls the YAML body out of the server's ```yaml … ``` fenced block. Any
|
|
20
|
+
# non-YAML preface (e.g. error / empty-set markdown) won't match and is
|
|
21
|
+
# rendered through the markdown fallback path.
|
|
22
|
+
_YAML_FENCE_RE = re.compile(r'```yaml\n(.*?)\n```', re.DOTALL)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _try_render_yaml_tree(content: str) -> bool:
|
|
26
|
+
"""Render the YAML schema as a Rich Tree, one tree per table.
|
|
27
|
+
|
|
28
|
+
Returns True if rendering succeeded. Returns False on any failure
|
|
29
|
+
(no fence, parse error, unexpected shape) so the caller falls back
|
|
30
|
+
to plain markdown rendering.
|
|
31
|
+
"""
|
|
32
|
+
match = _YAML_FENCE_RE.search(content)
|
|
33
|
+
if not match:
|
|
34
|
+
return False
|
|
35
|
+
try:
|
|
36
|
+
data = yaml.safe_load(match.group(1))
|
|
37
|
+
except yaml.YAMLError:
|
|
38
|
+
return False
|
|
39
|
+
if not isinstance(data, dict) or not data:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
rendered = False
|
|
43
|
+
for table_name, fields in data.items():
|
|
44
|
+
if not isinstance(fields, dict):
|
|
45
|
+
continue
|
|
46
|
+
rendered = True
|
|
47
|
+
|
|
48
|
+
# Table-level metadata sits alongside columns under `__`-prefixed
|
|
49
|
+
# keys; surface them in the tree label, never as children.
|
|
50
|
+
table_comment = fields.get('__comment')
|
|
51
|
+
primary_index = fields.get('__primary_index')
|
|
52
|
+
label = Text()
|
|
53
|
+
label.append(str(table_name), style='bold cyan')
|
|
54
|
+
if table_comment:
|
|
55
|
+
label.append(f' — {table_comment}', style='italic dim')
|
|
56
|
+
if primary_index:
|
|
57
|
+
label.append(f' — primary index {primary_index}', style='italic dim')
|
|
58
|
+
|
|
59
|
+
tree = Tree(label, guide_style='dim')
|
|
60
|
+
for fname, fval in fields.items():
|
|
61
|
+
if isinstance(fname, str) and fname.startswith('__'):
|
|
62
|
+
continue
|
|
63
|
+
if isinstance(fval, dict):
|
|
64
|
+
ftype = str(fval.get('type', '?'))
|
|
65
|
+
fcomment = fval.get('comment')
|
|
66
|
+
else:
|
|
67
|
+
ftype = str(fval)
|
|
68
|
+
fcomment = None
|
|
69
|
+
line = Text()
|
|
70
|
+
line.append(str(fname), style='yellow')
|
|
71
|
+
line.append(': ')
|
|
72
|
+
line.append(ftype, style='green')
|
|
73
|
+
if fcomment:
|
|
74
|
+
line.append(f' — {fcomment}', style='dim')
|
|
75
|
+
tree.add(line)
|
|
76
|
+
console.print(tree)
|
|
77
|
+
console.print()
|
|
78
|
+
|
|
79
|
+
return rendered
|
|
80
|
+
|
|
15
81
|
|
|
16
82
|
def _show(db: str, json_output: bool, table: str | None = None) -> None:
|
|
17
83
|
client = get_client()
|
|
@@ -23,6 +89,12 @@ def _show(db: str, json_output: bool, table: str | None = None) -> None:
|
|
|
23
89
|
return
|
|
24
90
|
content = data.get("content", "")
|
|
25
91
|
if console.is_terminal:
|
|
92
|
+
# PG/FB output is a fenced YAML mapping; render as a Rich Tree
|
|
93
|
+
# for terminals. ES is still markdown prose, and error / empty
|
|
94
|
+
# responses also have no fence — both fall through to the
|
|
95
|
+
# markdown renderer.
|
|
96
|
+
if db in ("pg", "fb") and _try_render_yaml_tree(content):
|
|
97
|
+
return
|
|
26
98
|
console.print(Markdown(content))
|
|
27
99
|
else:
|
|
28
100
|
print(content)
|
|
File without changes
|
{thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/.github/workflows/python-publish.yml
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
|
{thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl/references/business-glossary.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/skills/tl/references/elasticsearch-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/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
|
{thoughtleaders_cli-0.6.19 → thoughtleaders_cli-0.6.21}/src/tl_cli/commands/_comments_common.py
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
|