thoughtleaders-cli 0.6.33__tar.gz → 0.6.35__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.35}/.claude-plugin/plugin.json +1 -1
  2. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/PKG-INFO +1 -1
  3. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/pyproject.toml +1 -1
  4. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/__init__.py +1 -1
  5. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/credits.py +15 -11
  6. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/doctor.py +99 -11
  7. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/.claude-plugin/marketplace.json +0 -0
  8. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/.github/workflows/python-publish.yml +0 -0
  9. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/.gitignore +0 -0
  10. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/AGENTS.md +0 -0
  11. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/CLAUDE.md +0 -0
  12. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/LICENSE +0 -0
  13. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/README.md +0 -0
  14. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/agents/tl-analyst.md +0 -0
  15. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/commands/tl-balance.md +0 -0
  16. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/commands/tl-reports.md +0 -0
  17. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/commands/tl-sponsorships.md +0 -0
  18. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/commands/tl.md +0 -0
  19. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/docs/architecture.md +0 -0
  20. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/hooks/hooks.json +0 -0
  21. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/hooks/scripts/post-usage.sh +0 -0
  22. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/hooks/scripts/pre-check.sh +0 -0
  23. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl/SKILL.md +0 -0
  24. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl/references/business-glossary.md +0 -0
  25. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl/references/elasticsearch-schema.md +0 -0
  26. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl/references/firebolt-schema.md +0 -0
  27. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl/references/postgres-schema.md +0 -0
  28. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-import/SKILL.md +0 -0
  29. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/SKILL.md +0 -0
  30. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/examples/e2e_findings.md +0 -0
  31. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/examples/golden_queries.md +0 -0
  32. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/columns_brands.md +0 -0
  33. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/columns_channels.md +0 -0
  34. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/columns_content.md +0 -0
  35. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/columns_sponsorships.md +0 -0
  36. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/intelligence_filterset_schema.json +0 -0
  37. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/intelligence_widget_schema.json +0 -0
  38. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/report_glossary.md +0 -0
  39. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/sortable_columns.json +0 -0
  40. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/sponsorship_filterset_schema.json +0 -0
  41. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/sponsorship_widget_schema.json +0 -0
  42. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/references/widgets.md +0 -0
  43. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/tools/column_builder.md +0 -0
  44. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/tools/database_query.md +0 -0
  45. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/tools/keyword_research.md +0 -0
  46. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/tools/name_resolver.md +0 -0
  47. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/tools/sample_judge.md +0 -0
  48. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/tools/similar_channels.md +0 -0
  49. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/tools/topic_matcher.md +0 -0
  50. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/skills/tl-report-builder/tools/widget_builder.md +0 -0
  51. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/_completions.py +0 -0
  52. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/auth/__init__.py +0 -0
  53. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/auth/commands.py +0 -0
  54. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/auth/finalize.py +0 -0
  55. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/auth/login.py +0 -0
  56. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/auth/pkce.py +0 -0
  57. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/auth/token_store.py +0 -0
  58. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/client/__init__.py +0 -0
  59. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/client/errors.py +0 -0
  60. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/client/http.py +0 -0
  61. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/__init__.py +0 -0
  62. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/_comments_common.py +0 -0
  63. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/ask.py +0 -0
  64. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/balance.py +0 -0
  65. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/brands.py +0 -0
  66. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/bulk_import.py +0 -0
  67. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/changelog.py +0 -0
  68. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/channels.py +0 -0
  69. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/db.py +0 -0
  70. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/deals.py +0 -0
  71. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/describe.py +0 -0
  72. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/matches.py +0 -0
  73. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/proposals.py +0 -0
  74. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/recommender.py +0 -0
  75. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/reports.py +0 -0
  76. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/schema.py +0 -0
  77. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/setup.py +0 -0
  78. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/snapshots.py +0 -0
  79. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/sponsorships.py +0 -0
  80. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/uploads.py +0 -0
  81. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/commands/whoami.py +0 -0
  82. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/config.py +0 -0
  83. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/filters.py +0 -0
  84. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/hints.py +0 -0
  85. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/main.py +0 -0
  86. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/output/__init__.py +0 -0
  87. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/output/formatter.py +0 -0
  88. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/src/tl_cli/self_update.py +0 -0
  89. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/tests/__init__.py +0 -0
  90. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/tests/test_auth.py +0 -0
  91. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/tests/test_filters.py +0 -0
  92. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/tests/test_output.py +0 -0
  93. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/tests/test_reports.py +0 -0
  94. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/tests/test_sponsorships.py +0 -0
  95. {thoughtleaders_cli-0.6.33 → thoughtleaders_cli-0.6.35}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tl-cli",
3
- "version": "0.6.33",
3
+ "version": "0.6.35",
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.35
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.35"
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.35"
@@ -19,6 +19,7 @@ from decimal import Decimal, InvalidOperation
19
19
 
20
20
  import typer
21
21
  from rich.console import Console
22
+ from rich.prompt import Prompt
22
23
  from rich.table import Table
23
24
 
24
25
  from tl_cli.client.errors import ApiError, handle_api_error
@@ -59,14 +60,13 @@ def pricing_cmd(
59
60
  @app.command("buy")
60
61
  def buy_cmd(
61
62
  amount_usd: str = typer.Option(..., "--amount-usd", help="Amount to top up, in USD."),
62
- no_browser: bool = typer.Option(False, "--no-browser", help="Don't open a browser, just print the checkout URL."),
63
63
  poll: bool = typer.Option(True, "--poll/--no-poll", help="Poll balance after opening the checkout page."),
64
64
  ) -> None:
65
65
  """Start a credit top-up.
66
66
 
67
- Calls the server to create a pending purchase, opens the web checkout
68
- URL, and (by default) polls `tl balance` until the credits land or you
69
- Ctrl-C out.
67
+ Calls the server to create a pending purchase, prints the checkout URL,
68
+ then asks whether to open it in a browser. Polls `tl balance` (unless
69
+ `--no-poll`) until the credits land or you Ctrl-C out.
70
70
  """
71
71
  try:
72
72
  Decimal(amount_usd)
@@ -96,13 +96,17 @@ def buy_cmd(
96
96
  console.print(
97
97
  f"\n[bold]Started top-up:[/bold] ${result['usd_amount']} → {credits} credits"
98
98
  )
99
- console.print(f"Checkout: {checkout_url}")
100
-
101
- if checkout_url and not no_browser:
102
- try:
103
- webbrowser.open(checkout_url)
104
- except Exception:
105
- pass
99
+ if checkout_url:
100
+ console.print(f"[bold]Checkout URL:[/bold] {checkout_url}\n")
101
+ console.print("How would you like to continue?")
102
+ console.print(" [cyan]1[/cyan] — Open the URL in a browser on this machine (default)")
103
+ console.print(" [cyan]2[/cyan] — I'll open it manually")
104
+ choice = Prompt.ask("Choose", choices=["1", "2"], default="1", console=console)
105
+ if choice == "1":
106
+ try:
107
+ webbrowser.open(checkout_url)
108
+ except Exception:
109
+ console.print("[yellow]Could not launch a browser. Open the URL above manually.[/yellow]")
106
110
 
107
111
  if not poll or initial_balance is None:
108
112
  console.print("[dim]Run `tl balance` to confirm once payment completes.[/dim]")
@@ -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,31 @@ 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 that's printed at the end of the report.
159
+ samples_by_endpoint: dict[str, list[float]] = {}
82
160
  if tokens and not tokens.is_expired:
83
161
  client = get_client()
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
+ # Pad /balance to N calls and time the remaining free endpoints.
178
+ # The table itself is printed at the bottom, just above the verdict.
179
+ if samples_by_endpoint.get("/balance"):
180
+ _collect_latency_samples(client, samples_by_endpoint)
95
181
  finally:
96
182
  client.close()
97
183
  else:
@@ -127,6 +213,8 @@ def doctor(ctx: typer.Context) -> None:
127
213
  if hint:
128
214
  console.print(f" Install: {hint}")
129
215
 
216
+ _print_latency_table(samples_by_endpoint)
217
+
130
218
  console.print()
131
219
  if all_ok:
132
220
  console.print("[green]Everything looks good.[/green]")