thoughtleaders-cli 0.5.0__py3-none-any.whl

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 (59) hide show
  1. thoughtleaders_cli-0.5.0.dist-info/METADATA +215 -0
  2. thoughtleaders_cli-0.5.0.dist-info/RECORD +59 -0
  3. thoughtleaders_cli-0.5.0.dist-info/WHEEL +4 -0
  4. thoughtleaders_cli-0.5.0.dist-info/entry_points.txt +2 -0
  5. thoughtleaders_cli-0.5.0.dist-info/licenses/LICENSE +21 -0
  6. tl_cli/__init__.py +3 -0
  7. tl_cli/_completions.py +4 -0
  8. tl_cli/_plugin/.claude-plugin/marketplace.json +17 -0
  9. tl_cli/_plugin/.claude-plugin/plugin.json +12 -0
  10. tl_cli/_plugin/agents/tl-analyst.md +66 -0
  11. tl_cli/_plugin/commands/tl-balance.md +10 -0
  12. tl_cli/_plugin/commands/tl-brands.md +16 -0
  13. tl_cli/_plugin/commands/tl-channels.md +31 -0
  14. tl_cli/_plugin/commands/tl-reports.md +16 -0
  15. tl_cli/_plugin/commands/tl-sponsorships.md +23 -0
  16. tl_cli/_plugin/commands/tl.md +28 -0
  17. tl_cli/_plugin/hooks/hooks.json +26 -0
  18. tl_cli/_plugin/hooks/scripts/post-usage.sh +26 -0
  19. tl_cli/_plugin/hooks/scripts/pre-check.sh +30 -0
  20. tl_cli/_plugin/skills/tl/SKILL.md +413 -0
  21. tl_cli/_plugin/skills/tl/references/business-glossary.md +159 -0
  22. tl_cli/_plugin/skills/tl/references/elasticsearch-schema.md +259 -0
  23. tl_cli/_plugin/skills/tl/references/firebolt-schema.md +208 -0
  24. tl_cli/_plugin/skills/tl/references/postgres-schema.md +269 -0
  25. tl_cli/auth/__init__.py +0 -0
  26. tl_cli/auth/commands.py +49 -0
  27. tl_cli/auth/login.py +328 -0
  28. tl_cli/auth/pkce.py +21 -0
  29. tl_cli/auth/token_store.py +98 -0
  30. tl_cli/client/__init__.py +0 -0
  31. tl_cli/client/errors.py +72 -0
  32. tl_cli/client/http.py +109 -0
  33. tl_cli/commands/__init__.py +0 -0
  34. tl_cli/commands/ask.py +54 -0
  35. tl_cli/commands/balance.py +68 -0
  36. tl_cli/commands/brands.py +174 -0
  37. tl_cli/commands/changelog.py +119 -0
  38. tl_cli/commands/channels.py +291 -0
  39. tl_cli/commands/comments.py +63 -0
  40. tl_cli/commands/db.py +104 -0
  41. tl_cli/commands/deals.py +52 -0
  42. tl_cli/commands/describe.py +166 -0
  43. tl_cli/commands/doctor.py +70 -0
  44. tl_cli/commands/matches.py +69 -0
  45. tl_cli/commands/proposals.py +69 -0
  46. tl_cli/commands/reports.py +346 -0
  47. tl_cli/commands/schema.py +55 -0
  48. tl_cli/commands/setup.py +401 -0
  49. tl_cli/commands/snapshots.py +93 -0
  50. tl_cli/commands/sponsorships.py +193 -0
  51. tl_cli/commands/uploads.py +84 -0
  52. tl_cli/commands/whoami.py +206 -0
  53. tl_cli/config.py +55 -0
  54. tl_cli/filters.py +88 -0
  55. tl_cli/hints.py +53 -0
  56. tl_cli/main.py +209 -0
  57. tl_cli/output/__init__.py +0 -0
  58. tl_cli/output/formatter.py +436 -0
  59. tl_cli/self_update.py +173 -0
@@ -0,0 +1,166 @@
1
+ """tl describe — Schema and filter discovery for resources."""
2
+
3
+ import json
4
+
5
+ import typer
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+ from tl_cli.client.errors import ApiError, handle_api_error
10
+ from tl_cli.client.http import get_client
11
+ from tl_cli.output.formatter import detect_format
12
+
13
+ app = typer.Typer(help="Discover available resources, fields, filters, and credit costs")
14
+ console = Console()
15
+
16
+
17
+ @app.callback(invoke_without_command=True)
18
+ def describe(ctx: typer.Context) -> None:
19
+ """Discover resources, fields, filters, and credit costs (free)."""
20
+ if ctx.invoked_subcommand is None:
21
+ ctx.invoke(list_cmd, json_output=False)
22
+
23
+
24
+ @app.command("list")
25
+ def list_cmd(
26
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
27
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
28
+ ) -> None:
29
+ """List all available resources with credit costs.
30
+
31
+ Examples:
32
+ tl describe list
33
+ tl describe list --json
34
+ """
35
+ fmt = detect_format(json_output, False, False, toon_output)
36
+
37
+ client = get_client()
38
+ try:
39
+ data = client.get("/describe")
40
+
41
+ if fmt == "json":
42
+ print(json.dumps(data, indent=2, default=str))
43
+ return
44
+
45
+ _print_resource_list(data)
46
+
47
+ except ApiError as e:
48
+ handle_api_error(e)
49
+ finally:
50
+ client.close()
51
+
52
+
53
+ @app.command("show")
54
+ def show_cmd(
55
+ resource: str = typer.Argument(..., help="Resource name (sponsorships, channels, etc.)"),
56
+ filters_only: bool = typer.Option(False, "--filters", help="Show only available filters"),
57
+ fields_only: bool = typer.Option(False, "--fields", help="Show only available fields"),
58
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
59
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
60
+ ) -> None:
61
+ """Show fields, filters, and credit costs for a specific resource.
62
+
63
+ Examples:
64
+ tl describe show sponsorships
65
+ tl describe show sponsorships --filters
66
+ tl describe show sponsorships --json
67
+ """
68
+ fmt = detect_format(json_output, False, False, toon_output)
69
+
70
+ client = get_client()
71
+ try:
72
+ data = client.get(f"/describe/{resource}")
73
+
74
+ if fmt == "json":
75
+ target = data
76
+ if filters_only and "filters" in data:
77
+ target = data["filters"]
78
+ elif fields_only and "fields" in data:
79
+ target = data["fields"]
80
+ print(json.dumps(target, indent=2, default=str))
81
+ return
82
+
83
+ _print_resource_detail(data, filters_only, fields_only)
84
+
85
+ except ApiError as e:
86
+ handle_api_error(e)
87
+ finally:
88
+ client.close()
89
+
90
+
91
+ def _credit_str(credits: dict, key: str) -> str:
92
+ value = credits.get(key, "free")
93
+ is_free = value == 0 or value == "free"
94
+ if is_free and credits.get("credits_vary"):
95
+ return "*"
96
+ assert not credits.get("credits_vary"), \
97
+ f"credits_vary must not be set alongside a fixed non-zero rate ({key}={value})"
98
+ return str(value)
99
+
100
+
101
+ def _print_resource_list(data: dict) -> None:
102
+ """Print all available resources."""
103
+ resources = data.get("resources", [])
104
+ has_variable = any(r.get("credits", {}).get("credits_vary") for r in resources)
105
+
106
+ table = Table(title="Available Resources")
107
+ table.add_column("Resource", style="bold cyan")
108
+ table.add_column("Description")
109
+ table.add_column("Credits (list)", justify="right")
110
+ table.add_column("Credits (detail)", justify="right")
111
+
112
+ for r in resources:
113
+ credits = r.get("credits", {})
114
+ table.add_row(
115
+ r["name"],
116
+ r.get("description", ""),
117
+ _credit_str(credits, "list"),
118
+ _credit_str(credits, "detail"),
119
+ )
120
+
121
+ console.print(table)
122
+ if has_variable:
123
+ console.print("[dim]* Variable pricing depending on the complexity of the report.[/dim]")
124
+
125
+
126
+ def _print_resource_detail(data: dict, filters_only: bool, fields_only: bool) -> None:
127
+ """Print fields and/or filters for a resource."""
128
+ name = data.get("resource", "")
129
+ desc = data.get("description", "")
130
+ credits = data.get("credits", {})
131
+
132
+ if not filters_only:
133
+ console.print(f"\n[bold]{name}[/bold] — {desc}")
134
+ console.print(
135
+ f"Credits: [cyan]{credits.get('list', 'free')}[/cyan]/result, "
136
+ f"[cyan]{credits.get('detail', 'free')}[/cyan]/detail\n"
137
+ )
138
+
139
+ if not filters_only:
140
+ fields = data.get("fields", [])
141
+ if fields:
142
+ table = Table(title="Fields")
143
+ table.add_column("Name", style="bold")
144
+ table.add_column("Type")
145
+ table.add_column("Description")
146
+ for f in fields:
147
+ table.add_row(f["name"], f.get("type", ""), f.get("description", ""))
148
+ console.print(table)
149
+
150
+ if not fields_only:
151
+ filters = data.get("filters", [])
152
+ if filters:
153
+ table = Table(title="Filters")
154
+ table.add_column("Name", style="bold cyan")
155
+ table.add_column("Type")
156
+ table.add_column("Description")
157
+ table.add_column("Values")
158
+ for f in filters:
159
+ values = ", ".join(f.get("values", [])) if "values" in f else ""
160
+ table.add_row(
161
+ f["name"],
162
+ f.get("type", ""),
163
+ f.get("description", ""),
164
+ values,
165
+ )
166
+ console.print(table)
@@ -0,0 +1,70 @@
1
+ """tl doctor — Health check for auth, connectivity, and version."""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+
6
+ from tl_cli import __version__
7
+ from tl_cli.auth.token_store import load_tokens
8
+ from tl_cli.client.errors import ApiError
9
+ from tl_cli.client.http import get_client
10
+ from tl_cli.config import get_config
11
+
12
+ app = typer.Typer(help="Health check (auth, connectivity, version)")
13
+ console = Console()
14
+
15
+
16
+ @app.callback(invoke_without_command=True)
17
+ def doctor(ctx: typer.Context) -> None:
18
+ """Check CLI health: version, auth status, API connectivity, credits."""
19
+ console.print(f"\n[bold]tl-cli[/bold] v{__version__}\n")
20
+ config = get_config()
21
+ all_ok = True
22
+
23
+ # API URL
24
+ console.print(f" API: {config.cli_api_base}")
25
+
26
+ # Auth
27
+ tokens = load_tokens()
28
+ if not tokens:
29
+ console.print(" Auth: [red]not logged in[/red]")
30
+ all_ok = False
31
+ elif tokens.is_expired:
32
+ console.print(f" Auth: [yellow]token expired[/yellow] ({tokens.email})")
33
+ all_ok = False
34
+ else:
35
+ console.print(f" Auth: [green]ok[/green] ({tokens.email})")
36
+
37
+ # Connectivity + balance
38
+ if tokens and not tokens.is_expired:
39
+ client = get_client()
40
+ try:
41
+ data = client.get("/balance")
42
+ balance_val = data.get("balance", "?")
43
+ console.print(f" API: [green]connected[/green]")
44
+ console.print(f" Credits: {balance_val}")
45
+ except ApiError as e:
46
+ console.print(f" API: [red]error ({e.status_code})[/red]")
47
+ all_ok = False
48
+ except Exception as e:
49
+ console.print(f" API: [red]unreachable[/red]")
50
+ all_ok = False
51
+ finally:
52
+ client.close()
53
+ else:
54
+ console.print(" API: [dim]skipped (not authenticated)[/dim]")
55
+
56
+ # Plugin/skill versions
57
+ from tl_cli.commands.setup import check_plugin_version
58
+ warnings = check_plugin_version()
59
+ if warnings:
60
+ for warn in warnings:
61
+ console.print(f" Plugin: [yellow]{warn}[/yellow]")
62
+ all_ok = False
63
+ else:
64
+ console.print(" Plugin: [green]ok[/green]")
65
+
66
+ console.print()
67
+ if all_ok:
68
+ console.print("[green]Everything looks good.[/green]")
69
+ else:
70
+ console.print("[yellow]Issues found.[/yellow]")
@@ -0,0 +1,69 @@
1
+ """tl matches — Shortcut for matched sponsorships."""
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from tl_cli.commands.sponsorships import do_create, do_list, do_show
8
+ from tl_cli.output.formatter import detect_format
9
+
10
+ app = typer.Typer(help="Matches — possible brand-channel pairings (shortcut for sponsorships status:match)")
11
+
12
+
13
+ @app.callback(invoke_without_command=True)
14
+ def matches(ctx: typer.Context) -> None:
15
+ """Matches — possible brand-channel pairings."""
16
+ if ctx.invoked_subcommand is None:
17
+ ctx.invoke(list_cmd, args=[], json_output=False, csv_output=False, md_output=False, limit=50, offset=0)
18
+
19
+
20
+ @app.command("list")
21
+ def list_cmd(
22
+ args: list[str] = typer.Argument(None, help="Filters (key:value pairs). Run 'tl describe show sponsorships' for available filters."),
23
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
24
+ csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
25
+ md_output: bool = typer.Option(False, "--md", help="Markdown output"),
26
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
27
+ limit: int = typer.Option(50, "--limit", "-l", help="Max results"),
28
+ offset: int = typer.Option(0, "--offset", help="Pagination offset"),
29
+ ) -> None:
30
+ """List matches with optional filters.
31
+
32
+ Examples:
33
+ tl matches list # List recent matches
34
+ tl matches list brand:"Nike" # Filter matches
35
+ """
36
+ fmt = detect_format(json_output, csv_output, md_output, toon_output)
37
+ do_list(args or [], fmt, limit, offset, default_status="match", title="Matches")
38
+
39
+
40
+ @app.command("show")
41
+ def show_cmd(
42
+ item_id: str = typer.Argument(..., help="Sponsorship ID"),
43
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
44
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
45
+ ) -> None:
46
+ """Show match detail by ID.
47
+
48
+ Examples:
49
+ tl matches show 12345
50
+ """
51
+ fmt = detect_format(json_output, False, False, toon_output)
52
+ do_show(item_id, fmt)
53
+
54
+
55
+ @app.command("create")
56
+ def create_cmd(
57
+ channel: int = typer.Option(..., "--channel", "-c", help="Channel ID"),
58
+ brand: int = typer.Option(..., "--brand", "-b", help="Brand ID"),
59
+ price: Optional[float] = typer.Option(None, "--price", "-p", help="Deal price"),
60
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
61
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
62
+ ) -> None:
63
+ """Create a new match (free, no credits charged).
64
+
65
+ Examples:
66
+ tl matches create --channel 1 --brand 2
67
+ """
68
+ fmt = detect_format(json_output, False, False, toon_output)
69
+ do_create(channel, brand, price, fmt, status="matched")
@@ -0,0 +1,69 @@
1
+ """tl proposals — Shortcut for proposed sponsorships."""
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from tl_cli.commands.sponsorships import do_create, do_list, do_show
8
+ from tl_cli.output.formatter import detect_format
9
+
10
+ app = typer.Typer(help="Proposals — matches proposed to both sides (shortcut for sponsorships status:proposal)")
11
+
12
+
13
+ @app.callback(invoke_without_command=True)
14
+ def proposals(ctx: typer.Context) -> None:
15
+ """Proposals — matches proposed to both sides."""
16
+ if ctx.invoked_subcommand is None:
17
+ ctx.invoke(list_cmd, args=[], json_output=False, csv_output=False, md_output=False, limit=50, offset=0)
18
+
19
+
20
+ @app.command("list")
21
+ def list_cmd(
22
+ args: list[str] = typer.Argument(None, help="Filters (key:value pairs). Run 'tl describe show sponsorships' for available filters."),
23
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
24
+ csv_output: bool = typer.Option(False, "--csv", help="CSV output"),
25
+ md_output: bool = typer.Option(False, "--md", help="Markdown output"),
26
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
27
+ limit: int = typer.Option(50, "--limit", "-l", help="Max results"),
28
+ offset: int = typer.Option(0, "--offset", help="Pagination offset"),
29
+ ) -> None:
30
+ """List proposals with optional filters.
31
+
32
+ Examples:
33
+ tl proposals list # List recent proposals
34
+ tl proposals list brand:"Nike" # Filter proposals
35
+ """
36
+ fmt = detect_format(json_output, csv_output, md_output, toon_output)
37
+ do_list(args or [], fmt, limit, offset, default_status="proposal", title="Proposals")
38
+
39
+
40
+ @app.command("show")
41
+ def show_cmd(
42
+ item_id: str = typer.Argument(..., help="Sponsorship ID"),
43
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
44
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
45
+ ) -> None:
46
+ """Show proposal detail by ID.
47
+
48
+ Examples:
49
+ tl proposals show 12345
50
+ """
51
+ fmt = detect_format(json_output, False, False, toon_output)
52
+ do_show(item_id, fmt)
53
+
54
+
55
+ @app.command("create")
56
+ def create_cmd(
57
+ channel: int = typer.Option(..., "--channel", "-c", help="Channel ID"),
58
+ brand: int = typer.Option(..., "--brand", "-b", help="Brand ID"),
59
+ price: Optional[float] = typer.Option(None, "--price", "-p", help="Deal price"),
60
+ json_output: bool = typer.Option(False, "--json", help="JSON output"),
61
+ toon_output: bool = typer.Option(False, "--toon", help="TOON output (token-efficient for LLMs)"),
62
+ ) -> None:
63
+ """Create a new proposal (free, no credits charged).
64
+
65
+ Examples:
66
+ tl proposals create --channel 1 --brand 2
67
+ """
68
+ fmt = detect_format(json_output, False, False, toon_output)
69
+ do_create(channel, brand, price, fmt, status="proposed")