thoughtleaders-cli 0.6.9__tar.gz → 0.6.10__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 (91) hide show
  1. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/.claude-plugin/plugin.json +1 -1
  2. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/PKG-INFO +5 -4
  3. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/README.md +4 -3
  4. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/agents/tl-analyst.md +1 -1
  5. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/docs/architecture.md +9 -7
  6. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/pyproject.toml +1 -1
  7. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl/SKILL.md +7 -17
  8. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/__init__.py +1 -1
  9. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/client/http.py +3 -0
  10. thoughtleaders_cli-0.6.10/src/tl_cli/commands/_comments_common.py +106 -0
  11. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/brands.py +2 -0
  12. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/channels.py +2 -0
  13. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/sponsorships.py +4 -0
  14. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/uploads.py +2 -0
  15. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/main.py +0 -2
  16. thoughtleaders_cli-0.6.9/src/tl_cli/commands/comments.py +0 -63
  17. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/.claude-plugin/marketplace.json +0 -0
  18. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/.github/workflows/python-publish.yml +0 -0
  19. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/.gitignore +0 -0
  20. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/AGENTS.md +0 -0
  21. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/CLAUDE.md +0 -0
  22. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/LICENSE +0 -0
  23. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/commands/tl-balance.md +0 -0
  24. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/commands/tl-reports.md +0 -0
  25. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/commands/tl-sponsorships.md +0 -0
  26. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/commands/tl.md +0 -0
  27. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/hooks/hooks.json +0 -0
  28. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/hooks/scripts/post-usage.sh +0 -0
  29. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/hooks/scripts/pre-check.sh +0 -0
  30. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl/references/business-glossary.md +0 -0
  31. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl/references/elasticsearch-schema.md +0 -0
  32. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl/references/firebolt-schema.md +0 -0
  33. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl/references/postgres-schema.md +0 -0
  34. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/SKILL.md +0 -0
  35. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
  36. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/examples/golden_queries.md +0 -0
  37. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/columns_brands.md +0 -0
  38. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/columns_channels.md +0 -0
  39. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/columns_content.md +0 -0
  40. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
  41. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
  42. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
  43. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/report_glossary.md +0 -0
  44. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/sortable_columns.json +0 -0
  45. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
  46. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
  47. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/references/widgets.md +0 -0
  48. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/tools/column_builder.md +0 -0
  49. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/tools/database_query.md +0 -0
  50. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/tools/keyword_research.md +0 -0
  51. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/tools/name_resolver.md +0 -0
  52. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/tools/sample_judge.md +0 -0
  53. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/tools/similar_channels.md +0 -0
  54. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
  55. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/skills/tl-report-builder/tools/widget_builder.md +0 -0
  56. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/_completions.py +0 -0
  57. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/auth/__init__.py +0 -0
  58. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/auth/commands.py +0 -0
  59. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/auth/login.py +0 -0
  60. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/auth/pkce.py +0 -0
  61. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/auth/token_store.py +0 -0
  62. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/client/__init__.py +0 -0
  63. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/client/errors.py +0 -0
  64. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/__init__.py +0 -0
  65. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/ask.py +0 -0
  66. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/balance.py +0 -0
  67. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/changelog.py +0 -0
  68. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/db.py +0 -0
  69. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/deals.py +0 -0
  70. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/describe.py +0 -0
  71. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/doctor.py +0 -0
  72. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/matches.py +0 -0
  73. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/proposals.py +0 -0
  74. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/recommender.py +0 -0
  75. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/reports.py +0 -0
  76. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/schema.py +0 -0
  77. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/setup.py +0 -0
  78. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/snapshots.py +0 -0
  79. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/commands/whoami.py +0 -0
  80. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/config.py +0 -0
  81. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/filters.py +0 -0
  82. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/hints.py +0 -0
  83. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/output/__init__.py +0 -0
  84. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/output/formatter.py +0 -0
  85. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/src/tl_cli/self_update.py +0 -0
  86. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/tests/__init__.py +0 -0
  87. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/tests/test_auth.py +0 -0
  88. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/tests/test_filters.py +0 -0
  89. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/tests/test_output.py +0 -0
  90. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/tests/test_sponsorships.py +0 -0
  91. {thoughtleaders_cli-0.6.9 → thoughtleaders_cli-0.6.10}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tl-cli",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "description": "ThoughtLeaders CLI — query sponsorship deals, channels, brands, uploads, and intelligence from the terminal",
5
5
  "author": {
6
6
  "name": "ThoughtLeaders",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thoughtleaders-cli
3
- Version: 0.6.9
3
+ Version: 0.6.10
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
@@ -125,9 +125,10 @@ tl brands show Nike
125
125
  # Run a saved report
126
126
  tl reports run 42
127
127
 
128
- # Comments on a sponsorship
129
- tl comments list 12345
130
- tl comments add 12345 "Looks good"
128
+ # Comments — available on sponsorships, channels, brands, and uploads
129
+ tl sponsorships comment-list 12345
130
+ tl sponsorships comment-add 12345 "Looks good"
131
+ tl channels comment-add 7890 "Strong recent winners"
131
132
 
132
133
  # Show information about the logged-in user
133
134
  tl whoami
@@ -98,9 +98,10 @@ tl brands show Nike
98
98
  # Run a saved report
99
99
  tl reports run 42
100
100
 
101
- # Comments on a sponsorship
102
- tl comments list 12345
103
- tl comments add 12345 "Looks good"
101
+ # Comments — available on sponsorships, channels, brands, and uploads
102
+ tl sponsorships comment-list 12345
103
+ tl sponsorships comment-add 12345 "Looks good"
104
+ tl channels comment-add 7890 "Strong recent winners"
104
105
 
105
106
  # Show information about the logged-in user
106
107
  tl whoami
@@ -64,7 +64,7 @@ tl db pg "SELECT a.id, a.send_date, a.publish_status, b.name AS brand, ch.channe
64
64
  ORDER BY a.send_date
65
65
  LIMIT 100 OFFSET 0"
66
66
  ```
67
- Then suggest `tl comments add <id> "..."` for each.
67
+ Then suggest `tl sponsorships comment-add <id> "..."` for each.
68
68
 
69
69
  ### Multi-step research (mix raw + similarity)
70
70
  "Find channels similar to the ones Nike sponsors and compare their pricing"
@@ -56,8 +56,9 @@ All data commands use explicit subcommands: `list`, `show`, `create`/`add`. Runn
56
56
  | `tl brands similar <brand>` | Find similar brands (similarity search, 25 credits) |
57
57
  | `tl snapshots channel <id>` | Channel metrics over time (Firebolt channel_metrics) |
58
58
  | `tl snapshots video <id> --channel <id>` | Video view curve (Firebolt article_metrics, --channel required) |
59
- | `tl comments list <adlink-id>` | List comments on a sponsorship (free) |
60
- | `tl comments add <adlink-id> "message"` | Add a comment (free) |
59
+ | `tl <entity> comment-list <id>` | List comments on a sponsorship/channel/brand/upload (free) |
60
+ | `tl <entity> comment-add <id> "message"` | Add a comment (free) |
61
+ | `tl <entity> comment-edit <comment-id> "message"` | Edit one of your own comments (author or superuser; free) |
61
62
 
62
63
  ### Raw database access (escape hatch for joins / aggregations / complex filters)
63
64
 
@@ -180,7 +181,7 @@ tools: [Bash, Read]
180
181
 
181
182
  What the agent does:
182
183
  - **Multi-step research**: "Find channels similar to the ones Nike sponsors and compare their pricing" → `tl brands show Nike --json` → extract initial channel IDs → `tl channels similar <initial-id> --json` for each (enriched with cpm) → union + dedupe → compile comparison table
183
- - **Cross-resource analysis**: "Show me deal slippage and add comments" → `tl sponsorships status:pending send-date-end:2026-03 --json` → identify slipping sponsorships → `tl comments add <id> "flagged for slippage"` for each
184
+ - **Cross-resource analysis**: "Show me deal slippage and add comments" → `tl sponsorships status:pending send-date-end:2026-03 --json` → identify slipping sponsorships → `tl sponsorships comment-add <id> "flagged for slippage"` for each
184
185
  - **Report comparison**: "Compare my Q1 report to Q4" → `tl reports run <id> --since 2026-01 --until 2026-03 --json` → `tl reports run <id> --since 2025-10 --until 2025-12 --json` → synthesize
185
186
  - **Discovery workflows**: "What's my best performing brand this quarter" → `tl sponsorships status:sold purchase-date-start:2026-01 --json` → aggregate by brand → `tl brands show <top_brand> --json` → full picture
186
187
  - **Credit-aware**: checks balance before multi-query workflows, estimates total cost, asks user to confirm if expensive
@@ -315,7 +316,7 @@ tl-cli/
315
316
  │ │ ├── brands.py # tl brands (show/history/similar)
316
317
  │ │ ├── snapshots.py # tl snapshots (Firebolt metrics)
317
318
  │ │ ├── reports.py # tl reports / tl reports run
318
- │ │ ├── comments.py # tl comments (list/add)
319
+ │ │ ├── _comments_common.py # comment-list/add/edit subcommands shared across entity apps
319
320
  │ │ ├── db.py # tl db pg|fb|es (raw read-only queries)
320
321
  │ │ ├── schema.py # tl schema pg|fb|es (live schema docs)
321
322
  │ │ ├── describe.py # tl describe list/show (schema/filter/pricing discovery)
@@ -394,8 +395,9 @@ Most CLI endpoints can reuse existing views/utilities rather than being built fr
394
395
  | **`GET /api/cli/v1/snapshots/video/<id>`** | `api/v2/article-history` (`ArticleHistoryView`) — already queries Firebolt `article_metrics` with pagination, enforces `channel_id:article_id` format. | Direct reuse — already enforces channel_id requirement |
395
396
  | **`GET /api/cli/v1/reports`** | `api/campaigns` (`CampaignViewSet.list`) — returns user's saved campaigns with ownership filtering | Filter to user's campaigns, add CLI envelope |
396
397
  | **`GET /api/cli/v1/reports/<id>/run`** | `api/campaigns/<id>` detail + the view's existing data loading via `load_campaign_data()` in `data_api_utils.py` — campaigns store their filter config, which gets passed to the appropriate data view (SponsorshipsView, ArticlesView, ThoughtleadersView) | Load campaign config → dispatch to appropriate existing view → wrap results |
397
- | **`GET /api/cli/v1/comments/<adlink_id>`** | `api/comments/adlink/<id>` (`CommentsView` GET) already paginated with read status | Add CLI envelope |
398
- | **`POST /api/cli/v1/comments/<adlink_id>`** | `api/comments/adlink/<id>` (`CommentsView` POST) creates comment | Pass through |
398
+ | **`GET /api/cli/v1/<entity_type>/<entity_id>/comments`** | Same query against the `Comment` table; entity_type {sponsorship, channel, brand, upload} | List comments scoped to caller's organization |
399
+ | **`POST /api/cli/v1/<entity_type>/<entity_id>/comments`** | Insert into `Comment` with the matching FK column | Create comment for the named entity |
400
+ | **`PATCH /api/cli/v1/comment/<comment_id>`** | Update `Comment.content` after author/superuser check | Edit a single comment by ID |
399
401
  | **`GET /api/cli/v1/whoami`** | New — reads Profile, Organization, brands M2M | New (joins Profile → User, Organization → Plan, Profile.brands → ChannelList) |
400
402
  | **`GET /api/cli/v1/balance`** | New — reads `CliCreditAccount` | New (simple model read) |
401
403
  | **`GET /api/cli/v1/describe`** | New — static metadata | New (hardcoded resource definitions) |
@@ -563,7 +565,7 @@ Build each command + its server endpoint together:
563
565
  11. `tl snapshots video abc --channel 12345` returns view curve
564
566
  12. `tl reports` lists saved reports (free)
565
567
  13. `tl reports run 789` executes saved report (credits based on results)
566
- 14. `tl comments list 12345` lists comments (free)
568
+ 14. `tl sponsorships comment-list 12345` lists comments (free) — same shape under `tl channels`, `tl brands`, `tl uploads`
567
569
  15. `tl sponsorships create --channel 1 --brand 2` creates proposal (free)
568
570
  16. `tl setup claude` installs plugin
569
571
  17. Claude Code skill triggers on data questions, uses `tl describe` for discovery
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "thoughtleaders-cli"
7
- version = "0.6.9"
7
+ version = "0.6.10"
8
8
  description = "ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -169,26 +169,16 @@ tl recommender top-brands "<tag>" # Top brands (deduped from profiles) load
169
169
  tl recommender inspect-channel <ref> # Show a channel's similarity-profile breakdown (25 credits; Intelligence)
170
170
  tl recommender inspect-brand <ref> # Show a brand profile's ideal similarity-profile breakdown (25 credits; Intelligence)
171
171
  tl recommender similar-to-profile <id> # Channels closest to a brand profile's ideal profile (25 credits; Intelligence)
172
- tl snapshots channel <id> # Channel metrics over time — list curve, mult 1.2 (Firebolt-backed)
173
- tl snapshots video <id> --channel <id> # Video view curve — list curve, mult 1.2 (--channel required!)
174
- tl reports # List saved reports — list curve, mult 1.3
172
+ tl snapshots channel <id> # Channel metrics over time (Firebolt-backed)
173
+ tl snapshots video <id> --channel <id> # Video view curve (--channel required!)
174
+ tl reports # List saved reports
175
175
  tl reports run <id> # Run a saved report (credits vary)
176
- tl comments list <adlink-id> # List comments list curve, mult 1.0
177
- tl comments add <adlink-id> "msg" # Add comment (free)
176
+ tl <entity> comment-list <id> # List comments on a sponsorship/channel/brand/upload
177
+ tl <entity> comment-add <id> "msg" # Add a comment (free)
178
+ tl <entity> comment-edit <comment-id> "msg" # Edit own comment (author or superuser; free)
178
179
  ```
179
180
 
180
- **"List curve"** above means non-linear pricing: `cost = 1 + mult × 0.126 × n^1.2`. The flat 1-credit setup applies to every list call; the `mult` reflects per-resource complexity. `tl db {pg,fb,es}` shares the same curve at mult=1.4. Concrete totals:
181
-
182
- | Rows | mult=1.0 (comments, uploads, sponsorships) | mult=1.2 (snapshots) | mult=1.3 (reports) | mult=1.4 (db.pg / db.fb / db.es) |
183
- |---:|---:|---:|---:|---:|
184
- | 1 | 1 | 1 | 1 | 1 |
185
- | 10 | 3 | 3 | 4 | 4 |
186
- | 50 | 15 | 18 | 19 | 20 |
187
- | 100 | 33 | 39 | 42 | 45 |
188
- | 200 | 74 | 88 | 96 | 103 |
189
- | 500 | 219 | 263 | 285 | 307 |
190
-
191
- The marginal per-row cost is exactly proportional to `mult` — a 1.4× resource costs 1.4× the row part of a 1.0× resource at any size. Splitting a 500-row pull into ten 50-row calls saves ~30% but burns 10 setup floors instead of 1; "narrow the query" is almost always the better move than "fragment the pagination."
181
+ **Credit costs are server-authoritative run `tl describe` (overview) or `tl describe show <resource>` (one resource) to see the current rates and multipliers for every endpoint. Do not memorise rate values they change.**
192
182
 
193
183
  ### Updating records
194
184
 
@@ -1,3 +1,3 @@
1
1
  """ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence."""
2
2
 
3
- __version__ = "0.6.9"
3
+ __version__ = "0.6.10"
@@ -29,6 +29,9 @@ class TLClient:
29
29
  def post(self, path: str, json_body: dict | None = None) -> dict:
30
30
  return self._request("POST", path, json_body=json_body)
31
31
 
32
+ def patch(self, path: str, json_body: dict | None = None) -> dict:
33
+ return self._request("PATCH", path, json_body=json_body)
34
+
32
35
  def _request(
33
36
  self,
34
37
  method: str,
@@ -0,0 +1,106 @@
1
+ """Shared helpers for entity comment subcommands.
2
+
3
+ Each entity (sponsorships, channels, brands, uploads) exposes
4
+ `comment-add`, `comment-list`, and `comment-edit` subcommands that
5
+ delegate to these helpers. Comments are free (no credits charged).
6
+
7
+ The server-side endpoints are:
8
+ GET/POST /<entity_type>/<entity_id>/comments
9
+ PATCH /comment/<comment_id>
10
+ """
11
+
12
+ import typer
13
+
14
+ from tl_cli.client.errors import ApiError, handle_api_error
15
+ from tl_cli.client.http import get_client
16
+ from tl_cli.output.formatter import detect_format, output, output_single
17
+
18
+ COLUMNS = ["comment_id", "author", "text", "created_at"]
19
+
20
+
21
+ def list_comments(entity_type: str, entity_id: str, json_output: bool, toon_output: bool) -> None:
22
+ fmt = detect_format(json_output, False, False, toon_output)
23
+ client = get_client()
24
+ try:
25
+ data = client.get(f"/{entity_type}/{entity_id}/comments")
26
+ for r in data.get("results", []):
27
+ r["comment_id"] = r.pop("id", None)
28
+ output(
29
+ data,
30
+ fmt,
31
+ columns=COLUMNS,
32
+ title=f"Comments on {entity_type} {entity_id}",
33
+ )
34
+ except ApiError as e:
35
+ handle_api_error(e)
36
+ finally:
37
+ client.close()
38
+
39
+
40
+ def add_comment(entity_type: str, entity_id: str, message: str, json_output: bool, toon_output: bool) -> None:
41
+ fmt = detect_format(json_output, False, False, toon_output)
42
+ client = get_client()
43
+ try:
44
+ data = client.post(f"/{entity_type}/{entity_id}/comments", json_body={"text": message})
45
+ for r in data.get("results", []):
46
+ r["comment_id"] = r.pop("id", None)
47
+ output_single(data, fmt)
48
+ except ApiError as e:
49
+ handle_api_error(e)
50
+ finally:
51
+ client.close()
52
+
53
+
54
+ def edit_comment(comment_id: int, message: str, json_output: bool, toon_output: bool) -> None:
55
+ fmt = detect_format(json_output, False, False, toon_output)
56
+ client = get_client()
57
+ try:
58
+ data = client.patch(f"/comment/{comment_id}", json_body={"text": message})
59
+ for r in data.get("results", []):
60
+ r["comment_id"] = r.pop("id", None)
61
+ output_single(data, fmt)
62
+ except ApiError as e:
63
+ handle_api_error(e)
64
+ finally:
65
+ client.close()
66
+
67
+
68
+ def register_comment_commands(app: typer.Typer, entity_type: str, entity_label: str) -> None:
69
+ """Register comment-list / comment-add / comment-edit subcommands on `app`.
70
+
71
+ `entity_type` matches the server URL segment (sponsorship / channel /
72
+ brand / upload). `entity_label` is the user-facing word shown in help
73
+ text (e.g. "sponsorship", "channel").
74
+ """
75
+
76
+ @app.command("comment-list")
77
+ def comment_list(
78
+ entity_id: str = typer.Argument(..., help=f"{entity_label.capitalize()} ID"),
79
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
80
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
81
+ ) -> None:
82
+ f"""List comments on a {entity_label} (free, no credits)."""
83
+ list_comments(entity_type, entity_id, json_output, toon_output)
84
+
85
+ @app.command("comment-add")
86
+ def comment_add(
87
+ entity_id: str = typer.Argument(..., help=f"{entity_label.capitalize()} ID"),
88
+ message: str = typer.Argument(..., help="Comment text"),
89
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
90
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
91
+ ) -> None:
92
+ f"""Add a comment to a {entity_label} (free, no credits)."""
93
+ add_comment(entity_type, entity_id, message, json_output, toon_output)
94
+
95
+ @app.command("comment-edit")
96
+ def comment_edit(
97
+ comment_id: int = typer.Argument(..., help="Comment ID"),
98
+ message: str = typer.Argument(..., help="New comment text"),
99
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
100
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
101
+ ) -> None:
102
+ """Edit an existing comment (free, no credits).
103
+
104
+ Only the comment's author can edit it (superusers can edit any comment).
105
+ """
106
+ edit_comment(comment_id, message, json_output, toon_output)
@@ -8,10 +8,12 @@ from rich.console import Console
8
8
 
9
9
  from tl_cli.client.errors import ApiError, handle_api_error
10
10
  from tl_cli.client.http import get_client
11
+ from tl_cli.commands._comments_common import register_comment_commands
11
12
  from tl_cli.hints import detail_hint
12
13
  from tl_cli.output.formatter import detect_format, output, output_single
13
14
 
14
15
  app = typer.Typer(help="Brand intelligence (detail, sponsorship history, channel mentions)")
16
+ register_comment_commands(app, "brand", "brand")
15
17
 
16
18
 
17
19
  @app.callback(invoke_without_command=True)
@@ -8,11 +8,13 @@ from rich.console import Console
8
8
 
9
9
  from tl_cli.client.errors import ApiError, handle_api_error
10
10
  from tl_cli.client.http import get_client
11
+ from tl_cli.commands._comments_common import register_comment_commands
11
12
  from tl_cli.filters import parse_filters
12
13
  from tl_cli.hints import detail_hint
13
14
  from tl_cli.output.formatter import detect_format, output, output_single
14
15
 
15
16
  app = typer.Typer(help="YouTube channels (detail, history, and similar-channel recommendations)")
17
+ register_comment_commands(app, "channel", "channel")
16
18
 
17
19
  # Columns for the `similar` endpoint result table. The server enriches every
18
20
  # row so the user can size up each suggestion without follow-up queries.
@@ -8,6 +8,7 @@ from rich.console import Console
8
8
 
9
9
  from tl_cli.client.errors import handle_api_error, ApiError
10
10
  from tl_cli.client.http import get_client
11
+ from tl_cli.commands._comments_common import register_comment_commands
11
12
  from tl_cli.filters import parse_filters
12
13
  from tl_cli.hints import detail_hint
13
14
  from tl_cli.output.formatter import detect_format, output, output_single
@@ -229,3 +230,6 @@ def update_cmd(
229
230
  handle_api_error(e)
230
231
  finally:
231
232
  client.close()
233
+
234
+
235
+ register_comment_commands(app, "sponsorship", "sponsorship")
@@ -4,10 +4,12 @@ import typer
4
4
 
5
5
  from tl_cli.client.errors import ApiError, handle_api_error
6
6
  from tl_cli.client.http import get_client
7
+ from tl_cli.commands._comments_common import register_comment_commands
7
8
  from tl_cli.filters import parse_filters
8
9
  from tl_cli.output.formatter import detect_format, output, output_single
9
10
 
10
11
  app = typer.Typer(help="Video uploads (YouTube content from Elasticsearch)")
12
+ register_comment_commands(app, "upload", "upload")
11
13
 
12
14
 
13
15
  @app.callback(invoke_without_command=True)
@@ -17,7 +17,6 @@ from tl_cli.commands.balance import app as balance_app
17
17
  from tl_cli.commands.changelog import changelog_command
18
18
  from tl_cli.commands.brands import app as brands_app
19
19
  from tl_cli.commands.channels import app as channels_app
20
- from tl_cli.commands.comments import app as comments_app
21
20
  from tl_cli.commands.db import app as db_app
22
21
  from tl_cli.commands.deals import app as deals_app
23
22
  from tl_cli.commands.matches import app as matches_app
@@ -94,7 +93,6 @@ app.add_typer(brands_app, name="brands")
94
93
  app.add_typer(recommender_app, name="recommender")
95
94
  app.add_typer(snapshots_app, name="snapshots")
96
95
  app.add_typer(reports_app, name="reports")
97
- app.add_typer(comments_app, name="comments")
98
96
  app.add_typer(db_app, name="db")
99
97
 
100
98
  # Discoverability
@@ -1,63 +0,0 @@
1
- """tl comments — List and add comments on sponsorships."""
2
-
3
- import typer
4
-
5
- from tl_cli.client.errors import ApiError, handle_api_error
6
- from tl_cli.client.http import get_client
7
- from tl_cli.output.formatter import detect_format, output, output_single
8
-
9
- app = typer.Typer(help="Comments on sponsorships (free, no credits)")
10
-
11
-
12
- @app.command("list")
13
- def list_cmd(
14
- adlink_id: int = typer.Argument(..., help="Sponsorship (adlink) ID"),
15
- json_output: bool = typer.Option(False, "--json", help="JSON output"),
16
- toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
17
- ) -> None:
18
- """List comments on a sponsorship (free, no credits).
19
-
20
- Examples:
21
- tl comments list 12345
22
- """
23
- fmt = detect_format(json_output, False, False, toon_output)
24
-
25
- client = get_client()
26
- try:
27
- data = client.get(f"/comments/{adlink_id}")
28
- for r in data.get("results", []):
29
- r["comment_id"] = r.pop("id", None)
30
- output(
31
- data,
32
- fmt,
33
- columns=["comment_id", "author", "text", "created_at"],
34
- title=f"Comments on Sponsorship #{adlink_id}",
35
- )
36
- except ApiError as e:
37
- handle_api_error(e)
38
- finally:
39
- client.close()
40
-
41
-
42
- @app.command("add")
43
- def add_comment(
44
- adlink_id: int = typer.Argument(..., help="Sponsorship (adlink) ID"),
45
- message: str = typer.Argument(..., help="Comment text"),
46
- json_output: bool = typer.Option(False, "--json", help="JSON output"),
47
- toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
48
- ) -> None:
49
- """Add a comment to a sponsorship (free, no credits).
50
-
51
- Examples:
52
- tl comments add 12345 "Looks good, ready to send"
53
- """
54
- fmt = detect_format(json_output, False, False, toon_output)
55
-
56
- client = get_client()
57
- try:
58
- data = client.post(f"/comments/{adlink_id}", json_body={"text": message})
59
- output_single(data, fmt)
60
- except ApiError as e:
61
- handle_api_error(e)
62
- finally:
63
- client.close()