thoughtleaders-cli 0.6.33__tar.gz → 0.6.34__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 (95) hide show
  1. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/.claude-plugin/plugin.json +1 -1
  2. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/PKG-INFO +1 -1
  3. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/pyproject.toml +1 -1
  4. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/__init__.py +1 -1
  5. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/doctor.py +98 -11
  6. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/.claude-plugin/marketplace.json +0 -0
  7. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/.github/workflows/python-publish.yml +0 -0
  8. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/.gitignore +0 -0
  9. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/AGENTS.md +0 -0
  10. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/CLAUDE.md +0 -0
  11. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/LICENSE +0 -0
  12. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/README.md +0 -0
  13. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/agents/tl-analyst.md +0 -0
  14. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/commands/tl-balance.md +0 -0
  15. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/commands/tl-reports.md +0 -0
  16. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/commands/tl-sponsorships.md +0 -0
  17. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/commands/tl.md +0 -0
  18. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/docs/architecture.md +0 -0
  19. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/hooks/hooks.json +0 -0
  20. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/hooks/scripts/post-usage.sh +0 -0
  21. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/hooks/scripts/pre-check.sh +0 -0
  22. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl/SKILL.md +0 -0
  23. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl/references/business-glossary.md +0 -0
  24. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl/references/elasticsearch-schema.md +0 -0
  25. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl/references/firebolt-schema.md +0 -0
  26. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl/references/postgres-schema.md +0 -0
  27. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-import/SKILL.md +0 -0
  28. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/SKILL.md +0 -0
  29. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
  30. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/examples/golden_queries.md +0 -0
  31. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/columns_brands.md +0 -0
  32. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/columns_channels.md +0 -0
  33. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/columns_content.md +0 -0
  34. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
  35. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
  36. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
  37. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/report_glossary.md +0 -0
  38. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/sortable_columns.json +0 -0
  39. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
  40. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
  41. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/references/widgets.md +0 -0
  42. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/tools/column_builder.md +0 -0
  43. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/tools/database_query.md +0 -0
  44. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/tools/keyword_research.md +0 -0
  45. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/tools/name_resolver.md +0 -0
  46. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/tools/sample_judge.md +0 -0
  47. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/tools/similar_channels.md +0 -0
  48. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
  49. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/skills/tl-report-builder/tools/widget_builder.md +0 -0
  50. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/_completions.py +0 -0
  51. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/auth/__init__.py +0 -0
  52. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/auth/commands.py +0 -0
  53. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/auth/finalize.py +0 -0
  54. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/auth/login.py +0 -0
  55. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/auth/pkce.py +0 -0
  56. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/auth/token_store.py +0 -0
  57. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/client/__init__.py +0 -0
  58. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/client/errors.py +0 -0
  59. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/client/http.py +0 -0
  60. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/__init__.py +0 -0
  61. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/_comments_common.py +0 -0
  62. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/ask.py +0 -0
  63. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/balance.py +0 -0
  64. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/brands.py +0 -0
  65. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/bulk_import.py +0 -0
  66. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/changelog.py +0 -0
  67. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/channels.py +0 -0
  68. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/credits.py +0 -0
  69. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/db.py +0 -0
  70. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/deals.py +0 -0
  71. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/describe.py +0 -0
  72. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/matches.py +0 -0
  73. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/proposals.py +0 -0
  74. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/recommender.py +0 -0
  75. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/reports.py +0 -0
  76. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/schema.py +0 -0
  77. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/setup.py +0 -0
  78. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/snapshots.py +0 -0
  79. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/sponsorships.py +0 -0
  80. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/uploads.py +0 -0
  81. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/commands/whoami.py +0 -0
  82. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/config.py +0 -0
  83. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/filters.py +0 -0
  84. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/hints.py +0 -0
  85. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/main.py +0 -0
  86. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/output/__init__.py +0 -0
  87. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/output/formatter.py +0 -0
  88. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/src/tl_cli/self_update.py +0 -0
  89. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/tests/__init__.py +0 -0
  90. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/tests/test_auth.py +0 -0
  91. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/tests/test_filters.py +0 -0
  92. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/tests/test_output.py +0 -0
  93. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/tests/test_reports.py +0 -0
  94. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/tests/test_sponsorships.py +0 -0
  95. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.34}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tl-cli",
3
- "version": "0.6.33",
3
+ "version": "0.6.34",
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.33
3
+ Version: 0.6.34
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "thoughtleaders-cli"
7
- version = "0.6.33"
7
+ version = "0.6.34"
8
8
  description = "ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence."""
2
2
 
3
- __version__ = "0.6.33"
3
+ __version__ = "0.6.34"
@@ -2,9 +2,12 @@
2
2
 
3
3
  import platform
4
4
  import shutil
5
+ import statistics
6
+ import time
5
7
 
6
8
  import typer
7
9
  from rich.console import Console
10
+ from rich.table import Table
8
11
 
9
12
  from tl_cli import __version__
10
13
  from tl_cli.auth.token_store import load_tokens
@@ -12,6 +15,18 @@ from tl_cli.client.errors import ApiError
12
15
  from tl_cli.client.http import get_client
13
16
  from tl_cli.config import get_config
14
17
 
18
+ # Free, side-effect-free GET endpoints we time. Picked to cover the auth
19
+ # path (whoami, balance), public-no-auth path (pricing), and the bigger
20
+ # payloads (describe, changelog). All cost zero credits.
21
+ _LATENCY_ENDPOINTS: tuple[str, ...] = (
22
+ "/balance",
23
+ "/whoami",
24
+ "/pricing",
25
+ "/describe",
26
+ "/changelog",
27
+ )
28
+ _LATENCY_ITERATIONS = 3
29
+
15
30
  # Helper tools that AI agents using `tl --json` output frequently reach for.
16
31
  # Not required, but life is much better with them.
17
32
  _RECOMMENDED_TOOLS: tuple[tuple[str, str, dict[str, str]], ...] = (
@@ -57,6 +72,66 @@ app = typer.Typer(help="Health check (auth, connectivity, version)")
57
72
  console = Console()
58
73
 
59
74
 
75
+ def _collect_latency_samples(client, samples_by_endpoint: dict[str, list[float]]) -> None:
76
+ """Hit each free endpoint up to _LATENCY_ITERATIONS times and record
77
+ wall-clock latencies. Endpoints that 404 (older server) are dropped
78
+ silently — they just don't appear in the table.
79
+ """
80
+ for path in _LATENCY_ENDPOINTS:
81
+ # The initial /balance call already produced one sample; top it up
82
+ # so every endpoint has the same call count.
83
+ already = len(samples_by_endpoint.get(path, []))
84
+ for _ in range(max(0, _LATENCY_ITERATIONS - already)):
85
+ t0 = time.perf_counter()
86
+ try:
87
+ client.get(path)
88
+ except ApiError as exc:
89
+ if exc.status_code == 404:
90
+ # Endpoint missing on this server — stop probing it.
91
+ break
92
+ # Other errors (5xx, 401 after refresh) still produced a
93
+ # round-trip, so the latency is meaningful — record it.
94
+ except Exception:
95
+ # Network failure mid-probe — bail on this endpoint.
96
+ break
97
+ samples_by_endpoint.setdefault(path, []).append((time.perf_counter() - t0) * 1000)
98
+
99
+
100
+ def _print_latency_table(samples_by_endpoint: dict[str, list[float]]) -> None:
101
+ """Render per-endpoint and overall latency stats."""
102
+ rows = [(path, samples) for path, samples in samples_by_endpoint.items() if samples]
103
+ if not rows:
104
+ return
105
+
106
+ table = Table(title="API latency (ms)", show_lines=False)
107
+ table.add_column("Endpoint")
108
+ table.add_column("Calls", justify="right")
109
+ table.add_column("Min", justify="right")
110
+ table.add_column("Median", justify="right")
111
+ table.add_column("Max", justify="right")
112
+ for path, samples in rows:
113
+ table.add_row(
114
+ path,
115
+ str(len(samples)),
116
+ f"{min(samples):.0f}",
117
+ f"{statistics.median(samples):.0f}",
118
+ f"{max(samples):.0f}",
119
+ )
120
+ console.print()
121
+ console.print(table)
122
+
123
+ all_samples = [s for _, samples in rows for s in samples]
124
+ if len(all_samples) >= 2:
125
+ # Nearest-rank p95 — good enough for a health-check sample of ~15.
126
+ ranked = sorted(all_samples)
127
+ p95 = ranked[min(len(ranked) - 1, int(round(0.95 * len(ranked))) - 1)]
128
+ console.print(
129
+ f" Overall: median={statistics.median(all_samples):.0f}ms "
130
+ f"p95={p95:.0f}ms max={max(all_samples):.0f}ms "
131
+ f"n={len(all_samples)}"
132
+ )
133
+
134
+
60
135
  @app.callback(invoke_without_command=True)
61
136
  def doctor(ctx: typer.Context) -> None:
62
137
  """Check CLI health: version, auth status, API connectivity, credits."""
@@ -78,20 +153,32 @@ def doctor(ctx: typer.Context) -> None:
78
153
  else:
79
154
  console.print(f" Auth: [green]ok[/green] ({tokens.email})")
80
155
 
81
- # Connectivity + balance
156
+ # Connectivity + balance + latency timing. The first /balance call
157
+ # doubles as the connectivity probe; subsequent calls feed the
158
+ # latency stats table.
82
159
  if tokens and not tokens.is_expired:
83
160
  client = get_client()
161
+ samples_by_endpoint: dict[str, list[float]] = {}
84
162
  try:
85
- data = client.get("/balance")
86
- balance_val = data.get("balance", "?")
87
- console.print(f" API: [green]connected[/green]")
88
- console.print(f" Credits: {balance_val}")
89
- except ApiError as e:
90
- console.print(f" API: [red]error ({e.status_code})[/red]")
91
- all_ok = False
92
- except Exception as e:
93
- console.print(f" API: [red]unreachable[/red]")
94
- all_ok = False
163
+ try:
164
+ t0 = time.perf_counter()
165
+ data = client.get("/balance")
166
+ samples_by_endpoint.setdefault("/balance", []).append((time.perf_counter() - t0) * 1000)
167
+ balance_val = data.get("balance", "?")
168
+ console.print(f" API: [green]connected[/green]")
169
+ console.print(f" Credits: {balance_val}")
170
+ except ApiError as e:
171
+ console.print(f" API: [red]error ({e.status_code})[/red]")
172
+ all_ok = False
173
+ except Exception:
174
+ console.print(f" API: [red]unreachable[/red]")
175
+ all_ok = False
176
+
177
+ # Time the remaining endpoints (and pad /balance to N calls so
178
+ # every row in the latency table has the same iteration count).
179
+ if samples_by_endpoint.get("/balance"):
180
+ _collect_latency_samples(client, samples_by_endpoint)
181
+ _print_latency_table(samples_by_endpoint)
95
182
  finally:
96
183
  client.close()
97
184
  else: