thoughtleaders-cli 0.7.7__tar.gz → 0.7.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/.claude-plugin/plugin.json +1 -1
  2. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/.github/workflows/python-publish.yml +4 -4
  3. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/PKG-INFO +3 -3
  4. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/README.md +2 -2
  5. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/pyproject.toml +1 -1
  6. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl/SKILL.md +2 -2
  7. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/__init__.py +1 -1
  8. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/describe.py +20 -19
  9. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/output/formatter.py +13 -7
  10. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/tests/test_describe.py +19 -18
  11. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/tests/test_output.py +7 -7
  12. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/.claude-plugin/marketplace.json +0 -0
  13. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/.gitignore +0 -0
  14. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/AGENTS.md +0 -0
  15. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/API.md +0 -0
  16. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/CLAUDE.md +0 -0
  17. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/LICENSE +0 -0
  18. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/agents/tl-analyst.md +0 -0
  19. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/agents/youtube-comment-classifier.md +0 -0
  20. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/hooks/hooks.json +0 -0
  21. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/hooks/scripts/load-tl-skill.mjs +0 -0
  22. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/hooks/scripts/post-usage.sh +0 -0
  23. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/hooks/scripts/pre-check.sh +0 -0
  24. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl/references/business-glossary.md +0 -0
  25. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl/references/elasticsearch-schema.md +0 -0
  26. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl/references/firebolt-schema.md +0 -0
  27. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl/references/postgres-schema.md +0 -0
  28. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/.gitignore +0 -0
  29. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/SKILL.md +0 -0
  30. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/references/comment-patterns.md +0 -0
  31. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/references/peer-cohort.md +0 -0
  32. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/references/red-flags.md +0 -0
  33. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/references/scoring.md +0 -0
  34. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/_io_utf8.py +0 -0
  35. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/analyze_channel.py +0 -0
  36. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/anomaly_detector.py +0 -0
  37. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/comment_analyzer.py +0 -0
  38. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/comment_scraper.py +0 -0
  39. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/engagement_ratios.py +0 -0
  40. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/peer_cohort.py +0 -0
  41. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/report.py +0 -0
  42. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/resolve_channel.py +0 -0
  43. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/score.py +0 -0
  44. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/tl_cli.py +0 -0
  45. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/video_integrity.py +0 -0
  46. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-channel-authenticity/scripts/view_curves.py +0 -0
  47. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-import/SKILL.md +0 -0
  48. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-keyword-research/SKILL.md +0 -0
  49. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-keyword-research/scripts/probe.py +0 -0
  50. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/SKILL.md +0 -0
  51. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
  52. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/examples/golden_queries.md +0 -0
  53. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/columns_brands.md +0 -0
  54. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/columns_channels.md +0 -0
  55. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/columns_content.md +0 -0
  56. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
  57. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
  58. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
  59. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/report_glossary.md +0 -0
  60. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/sortable_columns.json +0 -0
  61. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
  62. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
  63. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/references/widgets.md +0 -0
  64. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/tools/column_builder.md +0 -0
  65. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/tools/database_query.md +0 -0
  66. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/tools/name_resolver.md +0 -0
  67. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/tools/sample_judge.md +0 -0
  68. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/tools/similar_channels.md +0 -0
  69. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
  70. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-report-builder/tools/widget_builder.md +0 -0
  71. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/SKILL.md +0 -0
  72. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/columns_brands.md +0 -0
  73. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/columns_channels.md +0 -0
  74. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/columns_content.md +0 -0
  75. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/columns_sponsorships.md +0 -0
  76. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/intelligence_filterset_schema.json +0 -0
  77. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/intelligence_widget_schema.json +0 -0
  78. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/report_glossary.md +0 -0
  79. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/sortable_columns.json +0 -0
  80. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/sponsorship_filterset_schema.json +0 -0
  81. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/sponsorship_widget_schema.json +0 -0
  82. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-save-report/references/widgets.md +0 -0
  83. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-top-partnerships/SKILL.md +0 -0
  84. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-top-partnerships/scripts/top_partnerships.py +0 -0
  85. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-views-guarantee/SKILL.md +0 -0
  86. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/skills/tl-views-guarantee/scripts/vg.py +0 -0
  87. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/_completions.py +0 -0
  88. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/_typer_utils.py +0 -0
  89. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/auth/__init__.py +0 -0
  90. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/auth/commands.py +0 -0
  91. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/auth/login.py +0 -0
  92. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/auth/pkce.py +0 -0
  93. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/auth/token_store.py +0 -0
  94. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/client/__init__.py +0 -0
  95. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/client/errors.py +0 -0
  96. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/client/http.py +0 -0
  97. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/__init__.py +0 -0
  98. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/_comments_common.py +0 -0
  99. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/balance.py +0 -0
  100. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/brands.py +0 -0
  101. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/bulk_import.py +0 -0
  102. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/changelog.py +0 -0
  103. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/channels.py +0 -0
  104. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/credits.py +0 -0
  105. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/db.py +0 -0
  106. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/deals.py +0 -0
  107. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/doctor.py +0 -0
  108. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/matches.py +0 -0
  109. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/proposals.py +0 -0
  110. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/recommender.py +0 -0
  111. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/reports.py +0 -0
  112. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/schema.py +0 -0
  113. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/setup.py +0 -0
  114. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/snapshots.py +0 -0
  115. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/sponsorships.py +0 -0
  116. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/uploads.py +0 -0
  117. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/commands/whoami.py +0 -0
  118. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/config.py +0 -0
  119. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/filters.py +0 -0
  120. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/hints.py +0 -0
  121. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/main.py +0 -0
  122. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/output/__init__.py +0 -0
  123. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/src/tl_cli/self_update.py +0 -0
  124. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/tests/__init__.py +0 -0
  125. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/tests/test_auth.py +0 -0
  126. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/tests/test_filters.py +0 -0
  127. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/tests/test_http_auth.py +0 -0
  128. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/tests/test_reports.py +0 -0
  129. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/tests/test_setup.py +0 -0
  130. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/tests/test_sponsorships.py +0 -0
  131. {thoughtleaders_cli-0.7.7 → thoughtleaders_cli-0.7.8}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tl-cli",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "description": "ThoughtLeaders CLI — query sponsorship deals, channels, brands, uploads, and intelligence from the terminal",
5
5
  "author": {
6
6
  "name": "ThoughtLeaders",
@@ -11,8 +11,8 @@ jobs:
11
11
  release-build:
12
12
  runs-on: ubuntu-latest
13
13
  steps:
14
- - uses: actions/checkout@v4
15
- - uses: actions/setup-python@v5
14
+ - uses: actions/checkout@v6
15
+ - uses: actions/setup-python@v6
16
16
  with:
17
17
  python-version: "3.12"
18
18
 
@@ -30,7 +30,7 @@ jobs:
30
30
  python -m pip install --upgrade build
31
31
  python -m build
32
32
 
33
- - uses: actions/upload-artifact@v4
33
+ - uses: actions/upload-artifact@v7
34
34
  with:
35
35
  name: release-dists
36
36
  path: dist/
@@ -44,7 +44,7 @@ jobs:
44
44
  name: pypi
45
45
  url: https://pypi.org/project/thoughtleaders-cli/${{ github.event.release.name }}
46
46
  steps:
47
- - uses: actions/download-artifact@v4
47
+ - uses: actions/download-artifact@v8
48
48
  with:
49
49
  name: release-dists
50
50
  path: dist/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thoughtleaders-cli
3
- Version: 0.7.7
3
+ Version: 0.7.8
4
4
  Summary: ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence
5
5
  Project-URL: Homepage, https://thoughtleaders.io
6
6
  Project-URL: Repository, https://github.com/ThoughtLeaders-io/thoughtleaders-cli
@@ -211,9 +211,9 @@ tl describe show sponsorships --filters # Available filters for sponsorships
211
211
  tl balance # Your credit balance
212
212
  ```
213
213
 
214
- `tl db pg` is priced **per-query**: a base rate plus a multiplier extra for every expensive table referenced, plus a flat per-row charge for every expensive column read. Sensitive fields (demographics, channel outreach emails) are expensive. Run `tl describe show db --json` to see the live `pg_expensive` map, and check `usage.credit_rate` in the response envelope after a query to see what your query was actually charged.
214
+ `tl db pg` is priced **per-row**: the per-row rate is the **sum of the rates of the tables the query touches** (default 1.0/row; some tables are cheaper or dearer), plus a flat per-row charge for every expensive column read (demographics, channel outreach emails), all times the rows returned. Aggregate queries (`count`/`GROUP BY`) add a surcharge proportional to the rows they aggregate. Run `tl describe show db --json` to see the live `pg_pricing` map, and check `usage.credit_rate` in the response envelope after a query to see what your query was actually charged.
215
215
 
216
- To preview a query's cost **before** running it, add `--pricing`: `tl db pg "SELECT … LIMIT 100" --pricing` runs only the planner's `EXPLAIN`, prints the cost breakdown and an upper-bound estimate (at the query's `LIMIT`), and costs a flat **1 credit** — the query itself never executes. Works with `--json` too. `--pricing` is also available on `tl db fb` and `tl db es`; those backends are flat-rate (no per-column charges), so the estimate is the volume curve at the query's row ceiling (`LIMIT` for Firebolt, `size` — or the aggregation doc cap — for Elasticsearch).
216
+ To preview a query's cost **before** running it, add `--pricing`: `tl db pg "SELECT … LIMIT 100" --pricing` runs only the planner's `EXPLAIN`, prints the cost breakdown and an upper-bound estimate (at the query's `LIMIT`), and costs a flat **1 credit** — the query itself never executes. Works with `--json` too. `--pricing` is also available on `tl db fb` and `tl db es`; those backends have no per-table or per-column charges, so the estimate is the flat per-row rate at the query's row ceiling (`LIMIT` for Firebolt, `size` — or the aggregation doc cap — for Elasticsearch).
217
217
 
218
218
  # Terminology
219
219
 
@@ -183,9 +183,9 @@ tl describe show sponsorships --filters # Available filters for sponsorships
183
183
  tl balance # Your credit balance
184
184
  ```
185
185
 
186
- `tl db pg` is priced **per-query**: a base rate plus a multiplier extra for every expensive table referenced, plus a flat per-row charge for every expensive column read. Sensitive fields (demographics, channel outreach emails) are expensive. Run `tl describe show db --json` to see the live `pg_expensive` map, and check `usage.credit_rate` in the response envelope after a query to see what your query was actually charged.
186
+ `tl db pg` is priced **per-row**: the per-row rate is the **sum of the rates of the tables the query touches** (default 1.0/row; some tables are cheaper or dearer), plus a flat per-row charge for every expensive column read (demographics, channel outreach emails), all times the rows returned. Aggregate queries (`count`/`GROUP BY`) add a surcharge proportional to the rows they aggregate. Run `tl describe show db --json` to see the live `pg_pricing` map, and check `usage.credit_rate` in the response envelope after a query to see what your query was actually charged.
187
187
 
188
- To preview a query's cost **before** running it, add `--pricing`: `tl db pg "SELECT … LIMIT 100" --pricing` runs only the planner's `EXPLAIN`, prints the cost breakdown and an upper-bound estimate (at the query's `LIMIT`), and costs a flat **1 credit** — the query itself never executes. Works with `--json` too. `--pricing` is also available on `tl db fb` and `tl db es`; those backends are flat-rate (no per-column charges), so the estimate is the volume curve at the query's row ceiling (`LIMIT` for Firebolt, `size` — or the aggregation doc cap — for Elasticsearch).
188
+ To preview a query's cost **before** running it, add `--pricing`: `tl db pg "SELECT … LIMIT 100" --pricing` runs only the planner's `EXPLAIN`, prints the cost breakdown and an upper-bound estimate (at the query's `LIMIT`), and costs a flat **1 credit** — the query itself never executes. Works with `--json` too. `--pricing` is also available on `tl db fb` and `tl db es`; those backends have no per-table or per-column charges, so the estimate is the flat per-row rate at the query's row ceiling (`LIMIT` for Firebolt, `size` — or the aggregation doc cap — for Elasticsearch).
189
189
 
190
190
  # Terminology
191
191
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "thoughtleaders-cli"
7
- version = "0.7.7"
7
+ version = "0.7.8"
8
8
  description = "ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -415,9 +415,9 @@ tl db pg "SELECT b.name, COUNT(*) AS deals
415
415
 
416
416
  If unsure about what information to find where, read the [references/postgresql-schema.md](references/postgresql-schema.md) file for instructions. Use just `tl pg schema` to see the entire SQL schema.
417
417
 
418
- **PG cost is per-query.** The credit cost for a `tl db pg` call is a base rate plus a multiplier extra for every expensive table referenced, plus a **flat per-row charge** for every expensive column read (an expensive column costs its configured value for every row returned). Most tables and columns are not expensive; sensitive ones (e.g. demographics, channel outreach emails) cost more. Run `tl describe show db --json` to see the live `pg_expensive` map, and check `usage.credit_rate` / `usage.pricing` in the response envelope after a query to see what your query was actually charged.
418
+ **PG cost is per-row.** The credit cost for a `tl db pg` call is its per-row rate the **sum of the rates of the tables the query touches** (default 1.0/row; some tables are cheaper or dearer) plus a **flat per-row charge** for every expensive column read — times the rows returned. So a join pays for each table it reads, and an expensive column costs its configured value for every row returned. Aggregate queries (`count`/`GROUP BY`) add a surcharge proportional to the estimated rows aggregated. Sensitive columns (e.g. demographics, channel outreach emails) cost more per row. Run `tl describe show db --json` to see the live `pg_pricing` map, and check `usage.credit_rate` / `usage.pricing` in the response envelope after a query to see what your query was actually charged.
419
419
 
420
- **Preview cost before running.** Add `--pricing` to estimate a query's cost without executing it: `tl db pg "SELECT … LIMIT 100" --pricing` runs only `EXPLAIN`, prints the multiplier + per-row breakdown and an upper-bound cost (at the query's LIMIT), and costs a flat 1 credit. Use this before large or expensive-column queries. Works with `--json`. `--pricing` also works on `tl db fb` and `tl db es` — those backends have no per-column charges, so the estimate is just the volume curve at the row ceiling (`LIMIT` for Firebolt; `size`, or the aggregation doc cap, for Elasticsearch).
420
+ **Preview cost before running.** Add `--pricing` to estimate a query's cost without executing it: `tl db pg "SELECT … LIMIT 100" --pricing` runs only `EXPLAIN`, prints the per-row rate + per-row breakdown and an upper-bound cost (at the query's LIMIT), and costs a flat 1 credit. Use this before large or expensive-column queries. Works with `--json`. `--pricing` also works on `tl db fb` and `tl db es` — those backends have no per-table or per-column charges, so the estimate is just the flat per-row rate at the row ceiling (`LIMIT` for Firebolt; `size`, or the aggregation doc cap, for Elasticsearch).
421
421
 
422
422
  ### Three sources, each authoritative for different things
423
423
 
@@ -1,3 +1,3 @@
1
1
  """ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence."""
2
2
 
3
- __version__ = "0.7.7"
3
+ __version__ = "0.7.8"
@@ -124,11 +124,11 @@ def _summarise_modes(credits: dict) -> tuple[str, str, bool]:
124
124
  - 'free' → "free"
125
125
  - 'flat' → "<rate> per call"
126
126
  - 'linear-per-result' (one mode) → "<rate> × n (per result)"
127
- - 'curve' (one mode, mult=R) → "curve (×R)"
127
+ - 'per-row' (one mode, rate=R) → "R/row"
128
128
  - mixed (e.g. channels has detail / history / similar at different rates)
129
129
  → per-mode "<mode> R" joined with commas
130
130
 
131
- The typical-cost column uses the n=100 example for curve/per-result and
131
+ The typical-cost column uses the n=100 example for per-row/per-result and
132
132
  the flat rate for flat. Free shows '-'.
133
133
  """
134
134
  modes = _modes_block(credits)
@@ -163,8 +163,8 @@ def _format_single_mode_label(mode_name: str, payload: dict, *, terse: bool = Fa
163
163
  return f"{_fmt_credits(rate)}/call" if terse else f"{_fmt_credits(rate)} per call"
164
164
  if model == "linear-per-result":
165
165
  return f"{_fmt_credits(rate)}×n" if terse else f"{_fmt_credits(rate)} × n (per result)"
166
- if model == "curve":
167
- return f"curve ×{rate}"
166
+ if model == "per-row":
167
+ return f"{_fmt_credits(rate)}/row"
168
168
  return f"{model} ({_fmt_credits(rate)})"
169
169
 
170
170
 
@@ -256,25 +256,25 @@ def _print_pricing_section(credits: dict) -> None:
256
256
  "Estimate using the examples above before running with a large limit."
257
257
  )
258
258
 
259
- # Surface live PG expensive-items pricing when the server included it
260
- # (db resource only).
261
- _print_pg_expensive_section(credits.get("pg_expensive"))
259
+ # Surface live PG per-table / per-column pricing when the server included
260
+ # it (db resource only).
261
+ _print_pg_pricing_section(credits.get("pg_pricing"))
262
262
 
263
263
 
264
- def _print_pg_expensive_section(expensive: object) -> None:
265
- """Render the `credits.pg_expensive` block as a flat dotted-path table.
264
+ def _print_pg_pricing_section(pricing: object) -> None:
265
+ """Render the `credits.pg_pricing` block as a flat dotted-path table.
266
266
 
267
267
  The server emits a three-level nested structure
268
268
  ``{base: {pg: float}, tables: {name: float}, columns: {"t.c": float}}``;
269
- flattening each leaf to ``<section>.<key>`` keeps the live extras
270
- visible in one sorted scan, with the base rate clearly distinguished
271
- from the per-table and per-column extras a query may or may not
272
- incur.
269
+ flattening each leaf to ``<section>.<key>`` keeps the live rates visible
270
+ in one sorted scan, with the default per-row rate (``default.pg``)
271
+ distinguished from the per-table rates and per-column extras a query
272
+ may or may not incur.
273
273
  """
274
- if not isinstance(expensive, dict) or not expensive:
274
+ if not isinstance(pricing, dict) or not pricing:
275
275
  return
276
276
  rows: list[tuple[str, float]] = []
277
- for section, body in expensive.items():
277
+ for section, body in pricing.items():
278
278
  if not isinstance(body, dict):
279
279
  # Forward-compat: an unexpected leaf type — surface as-is
280
280
  # under the section name rather than dropping it silently.
@@ -284,15 +284,16 @@ def _print_pg_expensive_section(expensive: object) -> None:
284
284
  rows.append((f"{section}.{key}", val))
285
285
  if not rows:
286
286
  return
287
- sub = Table(title="PG expensive items (live)")
287
+ sub = Table(title="PG per-row pricing (live)")
288
288
  sub.add_column("Path", style="bold")
289
- sub.add_column("Extra", justify="right")
289
+ sub.add_column("Rate", justify="right")
290
290
  for path, val in sorted(rows):
291
291
  sub.add_row(path, _fmt_credits(val))
292
292
  console.print(sub)
293
293
  console.print(
294
- "[dim]These are the rates, not a per-query total. For the actual cost "
295
- "of a specific query (before running it), use[/dim] "
294
+ "[dim]These are the per-table / per-column rates, not a per-query total. "
295
+ "A query's per-row rate is the sum of the rates of the tables it touches. "
296
+ "For the actual cost of a specific query (before running it), use[/dim] "
296
297
  "[cyan]tl db pg \"SELECT ...\" --pricing[/cyan][dim].[/dim]"
297
298
  )
298
299
 
@@ -540,7 +540,7 @@ def output_pricing_estimate(data: dict, fmt: str) -> None:
540
540
 
541
541
  Firebolt and Elasticsearch have no per-column extras — their estimate
542
542
  carries `per_row_extra=0` and empty expensive-item maps, so the
543
- breakdown table is skipped and only the volume-curve cost shows.
543
+ breakdown table is skipped and only the per-row cost shows.
544
544
  A `limit`/cost of `None` (e.g. a Firebolt query with no `LIMIT`) means
545
545
  the row count is unbounded and the cost can't be pinned ahead of time.
546
546
  """
@@ -567,24 +567,30 @@ def output_pricing_estimate(data: dict, fmt: str) -> None:
567
567
  else:
568
568
  console.print(
569
569
  " Estimated cost: [yellow]depends on rows returned[/yellow] "
570
- "(no row limit set — cost scales with the volume curve)"
570
+ "(no row limit set — cost is linear in rows returned)"
571
571
  )
572
- console.print(f" Multiplier (base + expensive tables): {multiplier}")
572
+ console.print(f" Per-row rate (sum of table rates): {multiplier}")
573
573
  console.print(f" Per-row extra (expensive columns): {per_row}")
574
+ agg_surcharge = est.get("agg_surcharge")
575
+ if agg_surcharge:
576
+ console.print(
577
+ f" Aggregate surcharge (flat): {agg_surcharge} "
578
+ f"[dim](≈{est.get('aggregated_rows', 0):,} rows aggregated)[/dim]"
579
+ )
574
580
  if planner_rows is not None:
575
581
  console.print(
576
582
  f" [dim]Planner row estimate (pre-LIMIT): {planner_rows:,}[/dim]"
577
583
  )
578
584
 
579
- tables = est.get("expensive_tables") or {}
585
+ tables = est.get("table_rates") or {}
580
586
  columns = est.get("expensive_columns") or {}
581
587
  if tables or columns:
582
- sub = Table(title="Expensive items this query touches")
588
+ sub = Table(title="Per-row rates this query touches")
583
589
  sub.add_column("Item", style="bold")
584
590
  sub.add_column("Kind")
585
- sub.add_column("Extra", justify="right")
591
+ sub.add_column("Rate", justify="right")
586
592
  for name, val in sorted(tables.items()):
587
- sub.add_row(name, "table (multiplier)", _fmt_credits(val))
593
+ sub.add_row(name, "table (per row)", f"{_fmt_credits(val)}/row")
588
594
  for path, val in sorted(columns.items()):
589
595
  sub.add_row(path, "column (per row)", f"{_fmt_credits(val)}/row")
590
596
  console.print(sub)
@@ -1,13 +1,13 @@
1
1
  """Tests for `tl describe` output helpers."""
2
2
 
3
- from tl_cli.commands.describe import _print_pg_expensive_section
3
+ from tl_cli.commands.describe import _print_pg_pricing_section
4
4
 
5
5
 
6
- # Sample shape that the server actually emits under `credits.pg_expensive`:
7
- # three sections (base / tables / columns), each a flat dict of leaves.
8
- _LIVE_PG_EXPENSIVE = {
9
- "base": {"pg": 1.4},
10
- "tables": {"thoughtleaders_channel": 3.0},
6
+ # Sample shape that the server actually emits under `credits.pg_pricing`:
7
+ # three sections (default / tables / columns), each a flat dict of leaves.
8
+ _LIVE_PG_PRICING = {
9
+ "default": {"pg": 1.0},
10
+ "tables": {"thoughtleaders_adlink": 0.1, "thoughtleaders_channel": 3.0},
11
11
  "columns": {
12
12
  "thoughtleaders_channel.outreach_email": 80.0,
13
13
  "thoughtleaders_channel.demographic_male_share": 50.0,
@@ -18,21 +18,22 @@ _LIVE_PG_EXPENSIVE = {
18
18
  }
19
19
 
20
20
 
21
- class TestPrintPgExpensiveSection:
21
+ class TestPrintPgPricingSection:
22
22
  def test_missing_block_renders_nothing(self, capsys):
23
- _print_pg_expensive_section(None)
23
+ _print_pg_pricing_section(None)
24
24
  assert capsys.readouterr().out == ""
25
25
 
26
26
  def test_empty_dict_renders_nothing(self, capsys):
27
- _print_pg_expensive_section({})
27
+ _print_pg_pricing_section({})
28
28
  assert capsys.readouterr().out == ""
29
29
 
30
30
  def test_nested_block_flattens_to_dotted_paths(self, capsys):
31
- _print_pg_expensive_section(_LIVE_PG_EXPENSIVE)
31
+ _print_pg_pricing_section(_LIVE_PG_PRICING)
32
32
  out = capsys.readouterr().out
33
33
  # Section headers prefix every leaf key.
34
- assert "base.pg" in out
34
+ assert "default.pg" in out
35
35
  assert "tables.thoughtleaders_channel" in out
36
+ assert "tables.thoughtleaders_adlink" in out
36
37
  assert "columns.thoughtleaders_channel.outreach_email" in out
37
38
  assert "columns.thoughtleaders_channel.demographic_male_share" in out
38
39
  # The numeric values pass through `_fmt_credits` (integer-valued
@@ -41,10 +42,10 @@ class TestPrintPgExpensiveSection:
41
42
  assert "50" in out
42
43
  assert "3" in out
43
44
  # Title appears.
44
- assert "PG expensive items (live)" in out
45
+ assert "PG per-row pricing (live)" in out
45
46
 
46
47
  def test_directs_user_to_pricing_flag(self, capsys):
47
- _print_pg_expensive_section(_LIVE_PG_EXPENSIVE)
48
+ _print_pg_pricing_section(_LIVE_PG_PRICING)
48
49
  out = capsys.readouterr().out
49
50
  # The note points at the per-query estimator and clarifies these
50
51
  # are rates, not a total.
@@ -53,18 +54,18 @@ class TestPrintPgExpensiveSection:
53
54
 
54
55
  def test_sections_sort_into_dotted_order(self, capsys):
55
56
  """Entries sort lexicographically by their flattened dotted path,
56
- so `base.*` rows precede `columns.*`, which precede `tables.*`."""
57
- _print_pg_expensive_section(_LIVE_PG_EXPENSIVE)
57
+ so `columns.*` rows precede `default.*`, which precede `tables.*`."""
58
+ _print_pg_pricing_section(_LIVE_PG_PRICING)
58
59
  out = capsys.readouterr().out
59
- base_pos = out.index("base.pg")
60
60
  cols_pos = out.index("columns.thoughtleaders_channel.outreach_email")
61
+ default_pos = out.index("default.pg")
61
62
  tables_pos = out.index("tables.thoughtleaders_channel")
62
- assert base_pos < cols_pos < tables_pos
63
+ assert cols_pos < default_pos < tables_pos
63
64
 
64
65
  def test_unexpected_leaf_type_surfaces_under_section_name(self, capsys):
65
66
  """Forward-compat: if a future server adds a non-dict section
66
67
  we render it as-is rather than dropping it silently."""
67
- _print_pg_expensive_section({"scalar_section": 42, "tables": {"t": 1.0}})
68
+ _print_pg_pricing_section({"scalar_section": 42, "tables": {"t": 1.0}})
68
69
  out = capsys.readouterr().out
69
70
  assert "scalar_section" in out
70
71
  assert "tables.t" in out
@@ -377,10 +377,10 @@ class TestPgPricingEstimate:
377
377
 
378
378
  _SAMPLE = {
379
379
  "pricing_estimate": {
380
- "base": 1.4,
380
+ "default": 1.4,
381
381
  "multiplier": 4.4,
382
382
  "per_row_extra": 280.0,
383
- "expensive_tables": {"thoughtleaders_channel": 3.0},
383
+ "table_rates": {"thoughtleaders_channel": 3.0},
384
384
  "expensive_columns": {
385
385
  "thoughtleaders_channel.outreach_email": 80.0,
386
386
  "thoughtleaders_channel.demographic_male_share": 50.0,
@@ -404,7 +404,7 @@ class TestPgPricingEstimate:
404
404
  assert "280" in out # per-row extra
405
405
  assert "thoughtleaders_channel.outreach_email" in out
406
406
  assert "80/row" in out
407
- assert "table (multiplier)" in out
407
+ assert "table (per row)" in out
408
408
 
409
409
  def test_json_mode_dumps_full_envelope(self, capsys):
410
410
  import json
@@ -430,8 +430,8 @@ class TestPgPricingEstimate:
430
430
  from tl_cli.output.formatter import output_pricing_estimate
431
431
  data = {
432
432
  "pricing_estimate": {
433
- "base": 1.4, "multiplier": 1.4, "per_row_extra": 0.0,
434
- "expensive_tables": {}, "expensive_columns": {},
433
+ "default": 1.4, "multiplier": 1.4, "per_row_extra": 0.0,
434
+ "table_rates": {}, "expensive_columns": {},
435
435
  "limit": 10, "planner_estimated_rows": 3,
436
436
  "estimated_cost_at_limit": 3.8,
437
437
  },
@@ -447,8 +447,8 @@ class TestPgPricingEstimate:
447
447
  from tl_cli.output.formatter import output_pricing_estimate
448
448
  data = {
449
449
  "pricing_estimate": {
450
- "base": 1.4, "multiplier": 1.4, "per_row_extra": 0.0,
451
- "expensive_tables": {}, "expensive_columns": {},
450
+ "default": 1.4, "multiplier": 1.4, "per_row_extra": 0.0,
451
+ "table_rates": {}, "expensive_columns": {},
452
452
  "limit": None, "planner_estimated_rows": None,
453
453
  "estimated_cost_at_limit": None,
454
454
  },