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.
- thoughtleaders_cli-0.5.0.dist-info/METADATA +215 -0
- thoughtleaders_cli-0.5.0.dist-info/RECORD +59 -0
- thoughtleaders_cli-0.5.0.dist-info/WHEEL +4 -0
- thoughtleaders_cli-0.5.0.dist-info/entry_points.txt +2 -0
- thoughtleaders_cli-0.5.0.dist-info/licenses/LICENSE +21 -0
- tl_cli/__init__.py +3 -0
- tl_cli/_completions.py +4 -0
- tl_cli/_plugin/.claude-plugin/marketplace.json +17 -0
- tl_cli/_plugin/.claude-plugin/plugin.json +12 -0
- tl_cli/_plugin/agents/tl-analyst.md +66 -0
- tl_cli/_plugin/commands/tl-balance.md +10 -0
- tl_cli/_plugin/commands/tl-brands.md +16 -0
- tl_cli/_plugin/commands/tl-channels.md +31 -0
- tl_cli/_plugin/commands/tl-reports.md +16 -0
- tl_cli/_plugin/commands/tl-sponsorships.md +23 -0
- tl_cli/_plugin/commands/tl.md +28 -0
- tl_cli/_plugin/hooks/hooks.json +26 -0
- tl_cli/_plugin/hooks/scripts/post-usage.sh +26 -0
- tl_cli/_plugin/hooks/scripts/pre-check.sh +30 -0
- tl_cli/_plugin/skills/tl/SKILL.md +413 -0
- tl_cli/_plugin/skills/tl/references/business-glossary.md +159 -0
- tl_cli/_plugin/skills/tl/references/elasticsearch-schema.md +259 -0
- tl_cli/_plugin/skills/tl/references/firebolt-schema.md +208 -0
- tl_cli/_plugin/skills/tl/references/postgres-schema.md +269 -0
- tl_cli/auth/__init__.py +0 -0
- tl_cli/auth/commands.py +49 -0
- tl_cli/auth/login.py +328 -0
- tl_cli/auth/pkce.py +21 -0
- tl_cli/auth/token_store.py +98 -0
- tl_cli/client/__init__.py +0 -0
- tl_cli/client/errors.py +72 -0
- tl_cli/client/http.py +109 -0
- tl_cli/commands/__init__.py +0 -0
- tl_cli/commands/ask.py +54 -0
- tl_cli/commands/balance.py +68 -0
- tl_cli/commands/brands.py +174 -0
- tl_cli/commands/changelog.py +119 -0
- tl_cli/commands/channels.py +291 -0
- tl_cli/commands/comments.py +63 -0
- tl_cli/commands/db.py +104 -0
- tl_cli/commands/deals.py +52 -0
- tl_cli/commands/describe.py +166 -0
- tl_cli/commands/doctor.py +70 -0
- tl_cli/commands/matches.py +69 -0
- tl_cli/commands/proposals.py +69 -0
- tl_cli/commands/reports.py +346 -0
- tl_cli/commands/schema.py +55 -0
- tl_cli/commands/setup.py +401 -0
- tl_cli/commands/snapshots.py +93 -0
- tl_cli/commands/sponsorships.py +193 -0
- tl_cli/commands/uploads.py +84 -0
- tl_cli/commands/whoami.py +206 -0
- tl_cli/config.py +55 -0
- tl_cli/filters.py +88 -0
- tl_cli/hints.py +53 -0
- tl_cli/main.py +209 -0
- tl_cli/output/__init__.py +0 -0
- tl_cli/output/formatter.py +436 -0
- 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")
|