teardrop-cli 0.1.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.
- teardrop_cli/__init__.py +1 -0
- teardrop_cli/_fixtures.py +132 -0
- teardrop_cli/_lazy.py +67 -0
- teardrop_cli/cli.py +41 -0
- teardrop_cli/commands/__init__.py +0 -0
- teardrop_cli/commands/agent.py +190 -0
- teardrop_cli/commands/auth.py +270 -0
- teardrop_cli/commands/billing.py +58 -0
- teardrop_cli/commands/llm_config.py +376 -0
- teardrop_cli/commands/marketplace.py +192 -0
- teardrop_cli/commands/mcp.py +202 -0
- teardrop_cli/commands/models.py +193 -0
- teardrop_cli/commands/tools.py +207 -0
- teardrop_cli/config.py +240 -0
- teardrop_cli/formatting.py +243 -0
- teardrop_cli-0.1.0.dist-info/METADATA +384 -0
- teardrop_cli-0.1.0.dist-info/RECORD +20 -0
- teardrop_cli-0.1.0.dist-info/WHEEL +4 -0
- teardrop_cli-0.1.0.dist-info/entry_points.txt +2 -0
- teardrop_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""marketplace commands: balance, earnings, withdraw, publish."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
name="marketplace",
|
|
12
|
+
help="Marketplace — balance, earnings, withdrawals, and publishing.",
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# balance
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.command()
|
|
23
|
+
def balance(
|
|
24
|
+
as_json: Annotated[bool, typer.Option("--json", help="Output as JSON.")] = False,
|
|
25
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Show your marketplace balance."""
|
|
28
|
+
from teardrop_cli import config
|
|
29
|
+
from teardrop_cli.formatting import print_json, print_table, spinner
|
|
30
|
+
|
|
31
|
+
async def _fetch():
|
|
32
|
+
try:
|
|
33
|
+
return await client.get_marketplace_balance()
|
|
34
|
+
finally:
|
|
35
|
+
await client.close()
|
|
36
|
+
|
|
37
|
+
client = config.get_client(base_url)
|
|
38
|
+
|
|
39
|
+
with spinner("Fetching balance…"):
|
|
40
|
+
data = asyncio.run(_fetch())
|
|
41
|
+
|
|
42
|
+
if as_json:
|
|
43
|
+
print_json(data)
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
rows = [[k, v] for k, v in data.items()]
|
|
47
|
+
print_table(
|
|
48
|
+
[("Field", {"style": "bold cyan"}), "Value"],
|
|
49
|
+
rows,
|
|
50
|
+
title="Marketplace Balance",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# earnings
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.command()
|
|
60
|
+
def earnings(
|
|
61
|
+
limit: Annotated[int, typer.Option("--limit", "-l", help="Max entries to return.")] = 20,
|
|
62
|
+
cursor: Annotated[str | None, typer.Option("--cursor", help="Pagination cursor.")] = None,
|
|
63
|
+
as_json: Annotated[bool, typer.Option("--json", help="Output as JSON.")] = False,
|
|
64
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""List marketplace earnings."""
|
|
67
|
+
from teardrop_cli import config
|
|
68
|
+
from teardrop_cli.formatting import print_json, print_table, spinner
|
|
69
|
+
|
|
70
|
+
async def _fetch():
|
|
71
|
+
try:
|
|
72
|
+
return await client.get_earnings(limit=limit, cursor=cursor)
|
|
73
|
+
finally:
|
|
74
|
+
await client.close()
|
|
75
|
+
|
|
76
|
+
client = config.get_client(base_url)
|
|
77
|
+
|
|
78
|
+
with spinner("Fetching earnings…"):
|
|
79
|
+
data = asyncio.run(_fetch())
|
|
80
|
+
|
|
81
|
+
if as_json:
|
|
82
|
+
print_json(data)
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
items: list[dict] = data.get("items", data) if isinstance(data, dict) else data
|
|
86
|
+
rows = [
|
|
87
|
+
[
|
|
88
|
+
e.get("id", ""),
|
|
89
|
+
e.get("amount", ""),
|
|
90
|
+
e.get("currency", ""),
|
|
91
|
+
e.get("created_at", ""),
|
|
92
|
+
e.get("description", ""),
|
|
93
|
+
]
|
|
94
|
+
for e in items
|
|
95
|
+
]
|
|
96
|
+
print_table(
|
|
97
|
+
["ID", ("Amount", {"justify": "right"}), "Currency", "Date", "Description"],
|
|
98
|
+
rows,
|
|
99
|
+
title="Earnings",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
# withdraw
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@app.command()
|
|
109
|
+
def withdraw(
|
|
110
|
+
amount_usdc: Annotated[
|
|
111
|
+
int, typer.Option("--amount-usdc", "-a", help="Amount to withdraw in USDC (integer).")
|
|
112
|
+
],
|
|
113
|
+
payout_address: Annotated[
|
|
114
|
+
str, typer.Option("--payout-address", help="Wallet address to send funds to.")
|
|
115
|
+
],
|
|
116
|
+
yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompt.")] = False,
|
|
117
|
+
as_json: Annotated[bool, typer.Option("--json", help="Output as JSON.")] = False,
|
|
118
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Withdraw marketplace earnings to a wallet address."""
|
|
121
|
+
from teardrop import WithdrawRequest
|
|
122
|
+
|
|
123
|
+
from teardrop_cli import config
|
|
124
|
+
from teardrop_cli.formatting import confirm, print_json, print_success, print_table, spinner
|
|
125
|
+
|
|
126
|
+
if not yes and not confirm(f"Withdraw {amount_usdc} USDC to {payout_address}?"):
|
|
127
|
+
raise typer.Abort()
|
|
128
|
+
|
|
129
|
+
async def _fetch():
|
|
130
|
+
try:
|
|
131
|
+
return await client.withdraw(
|
|
132
|
+
WithdrawRequest(amount_usdc=amount_usdc, payout_address=payout_address)
|
|
133
|
+
)
|
|
134
|
+
finally:
|
|
135
|
+
await client.close()
|
|
136
|
+
|
|
137
|
+
client = config.get_client(base_url)
|
|
138
|
+
|
|
139
|
+
with spinner("Processing withdrawal…"):
|
|
140
|
+
result = asyncio.run(_fetch())
|
|
141
|
+
|
|
142
|
+
if as_json:
|
|
143
|
+
print_json(result)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
print_success(f"Withdrawal initiated: {amount_usdc} USDC → {payout_address}")
|
|
147
|
+
if isinstance(result, dict):
|
|
148
|
+
rows = [[k, v] for k, v in result.items()]
|
|
149
|
+
print_table([("Field", {"style": "bold cyan"}), "Value"], rows, title="Withdrawal")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# publish
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@app.command()
|
|
158
|
+
def publish(
|
|
159
|
+
payout_address: Annotated[
|
|
160
|
+
str,
|
|
161
|
+
typer.Option(
|
|
162
|
+
"--payout-address",
|
|
163
|
+
"-p",
|
|
164
|
+
help="Wallet address to receive marketplace earnings.",
|
|
165
|
+
),
|
|
166
|
+
],
|
|
167
|
+
as_json: Annotated[bool, typer.Option("--json", help="Output as JSON.")] = False,
|
|
168
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
169
|
+
) -> None:
|
|
170
|
+
"""Set your marketplace author config (payout address)."""
|
|
171
|
+
from teardrop_cli import config
|
|
172
|
+
from teardrop_cli.formatting import print_json, print_success, print_table, spinner
|
|
173
|
+
|
|
174
|
+
async def _fetch():
|
|
175
|
+
try:
|
|
176
|
+
return await client.set_author_config(payout_address)
|
|
177
|
+
finally:
|
|
178
|
+
await client.close()
|
|
179
|
+
|
|
180
|
+
client = config.get_client(base_url)
|
|
181
|
+
|
|
182
|
+
with spinner("Updating author config…"):
|
|
183
|
+
result = asyncio.run(_fetch())
|
|
184
|
+
|
|
185
|
+
if as_json:
|
|
186
|
+
print_json(result.model_dump() if hasattr(result, "model_dump") else result)
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
print_success(f"Payout address set to [bold]{payout_address}[/bold].")
|
|
190
|
+
if hasattr(result, "model_dump"):
|
|
191
|
+
rows = [[k, v] for k, v in result.model_dump().items() if v is not None]
|
|
192
|
+
print_table([("Field", {"style": "bold cyan"}), "Value"], rows, title="Author Config")
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""mcp commands: list, add, discover, remove."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
name="mcp",
|
|
12
|
+
help="MCP servers — list, add, discover tools, and remove.",
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# list
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.command(name="list")
|
|
23
|
+
def list_servers(
|
|
24
|
+
as_json: Annotated[bool, typer.Option("--json", help="Output as JSON.")] = False,
|
|
25
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
"""List connected MCP servers."""
|
|
28
|
+
from teardrop_cli import config
|
|
29
|
+
from teardrop_cli.formatting import data_console, print_json, print_table, spinner
|
|
30
|
+
|
|
31
|
+
client = config.get_client(base_url)
|
|
32
|
+
|
|
33
|
+
async def _fetch():
|
|
34
|
+
try:
|
|
35
|
+
return await client.list_mcp_servers()
|
|
36
|
+
finally:
|
|
37
|
+
await client.close()
|
|
38
|
+
|
|
39
|
+
with spinner("Fetching MCP servers…"):
|
|
40
|
+
servers = asyncio.run(_fetch())
|
|
41
|
+
|
|
42
|
+
if as_json:
|
|
43
|
+
print_json([s.model_dump() for s in servers])
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
if not servers:
|
|
47
|
+
data_console.print("[dim]No MCP servers configured.[/dim]")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
rows = [
|
|
51
|
+
[
|
|
52
|
+
s.id,
|
|
53
|
+
s.name,
|
|
54
|
+
getattr(s, "url", ""),
|
|
55
|
+
getattr(s, "auth_type", ""),
|
|
56
|
+
str(len(getattr(s, "tools", []) or [])),
|
|
57
|
+
]
|
|
58
|
+
for s in servers
|
|
59
|
+
]
|
|
60
|
+
print_table(
|
|
61
|
+
["ID", "Name", "URL", "Auth Type", "Tools"],
|
|
62
|
+
rows,
|
|
63
|
+
title="MCP Servers",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# add
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@app.command()
|
|
73
|
+
def add(
|
|
74
|
+
name: Annotated[str, typer.Option("--name", "-n", help="Server name.")],
|
|
75
|
+
url: Annotated[str, typer.Option("--url", "-u", help="Server URL.")],
|
|
76
|
+
auth_type: Annotated[
|
|
77
|
+
str | None,
|
|
78
|
+
typer.Option("--auth-type", help="Auth type (e.g. none, bearer, basic)."),
|
|
79
|
+
] = None,
|
|
80
|
+
auth_token: Annotated[
|
|
81
|
+
str | None,
|
|
82
|
+
typer.Option("--auth-token", help="Auth token / secret.", hide_input=True),
|
|
83
|
+
] = None,
|
|
84
|
+
as_json: Annotated[bool, typer.Option("--json", help="Output as JSON.")] = False,
|
|
85
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Add a new MCP server."""
|
|
88
|
+
from teardrop import CreateMcpServerRequest
|
|
89
|
+
|
|
90
|
+
from teardrop_cli import config
|
|
91
|
+
from teardrop_cli.formatting import print_json, print_success, print_table, spinner
|
|
92
|
+
|
|
93
|
+
req_kwargs: dict = {"name": name, "url": url}
|
|
94
|
+
if auth_type:
|
|
95
|
+
req_kwargs["auth_type"] = auth_type
|
|
96
|
+
if auth_token:
|
|
97
|
+
req_kwargs["auth_token"] = auth_token
|
|
98
|
+
|
|
99
|
+
client = config.get_client(base_url)
|
|
100
|
+
|
|
101
|
+
async def _fetch():
|
|
102
|
+
try:
|
|
103
|
+
return await client.create_mcp_server(CreateMcpServerRequest(**req_kwargs))
|
|
104
|
+
finally:
|
|
105
|
+
await client.close()
|
|
106
|
+
|
|
107
|
+
with spinner(f"Adding MCP server [bold]{name}[/bold]…"):
|
|
108
|
+
server = asyncio.run(_fetch())
|
|
109
|
+
|
|
110
|
+
if as_json:
|
|
111
|
+
print_json(server.model_dump())
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
print_success(f"MCP server [bold]{server.name}[/bold] added (id: {server.id}).")
|
|
115
|
+
rows = [[k, v] for k, v in server.model_dump().items() if v is not None]
|
|
116
|
+
print_table([("Field", {"style": "bold cyan"}), "Value"], rows, title="New MCP Server")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# discover
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@app.command()
|
|
125
|
+
def discover(
|
|
126
|
+
server_id: Annotated[str, typer.Argument(help="MCP server ID.")],
|
|
127
|
+
as_json: Annotated[bool, typer.Option("--json", help="Output as JSON.")] = False,
|
|
128
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Discover tools exposed by an MCP server."""
|
|
131
|
+
from teardrop_cli import config
|
|
132
|
+
from teardrop_cli.formatting import data_console, print_json, print_table, spinner
|
|
133
|
+
|
|
134
|
+
client = config.get_client(base_url)
|
|
135
|
+
|
|
136
|
+
async def _fetch():
|
|
137
|
+
try:
|
|
138
|
+
return await client.discover_mcp_server_tools(server_id)
|
|
139
|
+
finally:
|
|
140
|
+
await client.close()
|
|
141
|
+
|
|
142
|
+
with spinner(f"Discovering tools for [bold]{server_id}[/bold]…"):
|
|
143
|
+
result = asyncio.run(_fetch())
|
|
144
|
+
|
|
145
|
+
tools = getattr(result, "tools", []) or []
|
|
146
|
+
|
|
147
|
+
if as_json:
|
|
148
|
+
data = result.model_dump() if hasattr(result, "model_dump") else {"tools": tools}
|
|
149
|
+
print_json(data)
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
if not tools:
|
|
153
|
+
data_console.print("[dim]No tools discovered.[/dim]")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
rows = [
|
|
157
|
+
[
|
|
158
|
+
getattr(t, "name", ""),
|
|
159
|
+
getattr(t, "description", ""),
|
|
160
|
+
str(len(getattr(t, "parameters", {}).get("properties", {})))
|
|
161
|
+
if isinstance(getattr(t, "parameters", None), dict)
|
|
162
|
+
else "—",
|
|
163
|
+
]
|
|
164
|
+
for t in tools
|
|
165
|
+
]
|
|
166
|
+
print_table(
|
|
167
|
+
["Name", "Description", "Params"],
|
|
168
|
+
rows,
|
|
169
|
+
title=f"Tools on {server_id}",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# remove
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@app.command()
|
|
179
|
+
def remove(
|
|
180
|
+
server_id: Annotated[str, typer.Argument(help="MCP server ID to remove.")],
|
|
181
|
+
yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompt.")] = False,
|
|
182
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Remove an MCP server."""
|
|
185
|
+
from teardrop_cli import config
|
|
186
|
+
from teardrop_cli.formatting import confirm, print_success, spinner
|
|
187
|
+
|
|
188
|
+
if not yes and not confirm(f"Remove MCP server [bold]{server_id}[/bold]?"):
|
|
189
|
+
raise typer.Abort()
|
|
190
|
+
|
|
191
|
+
client = config.get_client(base_url)
|
|
192
|
+
|
|
193
|
+
async def _fetch():
|
|
194
|
+
try:
|
|
195
|
+
await client.delete_mcp_server(server_id)
|
|
196
|
+
finally:
|
|
197
|
+
await client.close()
|
|
198
|
+
|
|
199
|
+
with spinner(f"Removing [bold]{server_id}[/bold]…"):
|
|
200
|
+
asyncio.run(_fetch())
|
|
201
|
+
|
|
202
|
+
print_success(f"MCP server [bold]{server_id}[/bold] removed.")
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""models commands: benchmarks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
name="models",
|
|
12
|
+
help="Model catalogue and benchmarks.",
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.callback()
|
|
18
|
+
def _callback() -> None:
|
|
19
|
+
"""Model catalogue and benchmarks."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# benchmarks
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command()
|
|
28
|
+
def benchmarks(
|
|
29
|
+
org: Annotated[
|
|
30
|
+
str | None,
|
|
31
|
+
typer.Option("--org", help="Org ID for org-scoped metrics (requires auth)."),
|
|
32
|
+
] = None,
|
|
33
|
+
no_cache: Annotated[
|
|
34
|
+
bool,
|
|
35
|
+
typer.Option("--no-cache", "--force-refresh", help="Bypass the local 10-minute cache."),
|
|
36
|
+
] = False,
|
|
37
|
+
as_json: Annotated[bool, typer.Option("--json", help="Output as JSON.")] = False,
|
|
38
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Display model catalogue with operational metrics.
|
|
41
|
+
|
|
42
|
+
Without --org: public benchmarks (no auth required).
|
|
43
|
+
With --org ORG_ID: org-scoped metrics (auth required).
|
|
44
|
+
"""
|
|
45
|
+
from teardrop import AuthenticationError
|
|
46
|
+
|
|
47
|
+
from teardrop_cli import config
|
|
48
|
+
from teardrop_cli.formatting import (
|
|
49
|
+
data_console,
|
|
50
|
+
print_error,
|
|
51
|
+
print_json,
|
|
52
|
+
spinner,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
client = config.get_client(base_url)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
|
|
59
|
+
async def _fetch():
|
|
60
|
+
try:
|
|
61
|
+
if org:
|
|
62
|
+
return await client.get_org_model_benchmarks(org_id=org)
|
|
63
|
+
else:
|
|
64
|
+
return await client.get_model_benchmarks(no_cache=no_cache)
|
|
65
|
+
finally:
|
|
66
|
+
await client.close()
|
|
67
|
+
|
|
68
|
+
label = "Fetching org model benchmarks…" if org else "Fetching model benchmarks…"
|
|
69
|
+
with spinner(label):
|
|
70
|
+
response = asyncio.run(_fetch())
|
|
71
|
+
except AuthenticationError:
|
|
72
|
+
print_error(
|
|
73
|
+
"Not authenticated.",
|
|
74
|
+
hint="Run: `teardrop auth login`",
|
|
75
|
+
)
|
|
76
|
+
raise typer.Exit(1) from None
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
status = getattr(exc, "status_code", None) or getattr(exc, "status", None)
|
|
79
|
+
if status == 401:
|
|
80
|
+
print_error("Not authenticated.", hint="Run: `teardrop auth login`")
|
|
81
|
+
raise typer.Exit(1) from None
|
|
82
|
+
print_error(f"Unexpected error: {exc}")
|
|
83
|
+
raise typer.Exit(1) from None
|
|
84
|
+
|
|
85
|
+
data = response if isinstance(response, dict) else response.model_dump()
|
|
86
|
+
models_list: list[dict] = data.get("models", [])
|
|
87
|
+
|
|
88
|
+
if as_json:
|
|
89
|
+
print_json(data)
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
if not models_list:
|
|
93
|
+
if org:
|
|
94
|
+
data_console.print(
|
|
95
|
+
f"[dim]No benchmark data for {org}. Run some agent tasks first.[/dim]"
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
data_console.print("[dim]No benchmark data available.[/dim]")
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
if org:
|
|
102
|
+
_print_org_benchmarks(models_list, org_id=org)
|
|
103
|
+
else:
|
|
104
|
+
_print_public_benchmarks(models_list)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# Internal renderers
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _print_public_benchmarks(models: list[dict]) -> None:
|
|
113
|
+
"""Render the global model catalogue table."""
|
|
114
|
+
from teardrop_cli.formatting import print_table
|
|
115
|
+
|
|
116
|
+
rows = []
|
|
117
|
+
for m in models:
|
|
118
|
+
benchmarks = m.get("benchmarks") or {}
|
|
119
|
+
pricing = m.get("pricing") or {}
|
|
120
|
+
p95 = benchmarks.get("p95_latency_ms")
|
|
121
|
+
cost_in = pricing.get("tokens_in_cost_per_1k")
|
|
122
|
+
cost_out = pricing.get("tokens_out_cost_per_1k")
|
|
123
|
+
runs = benchmarks.get("total_runs_7d")
|
|
124
|
+
|
|
125
|
+
rows.append(
|
|
126
|
+
[
|
|
127
|
+
m.get("provider", "—"),
|
|
128
|
+
m.get("model", "—"),
|
|
129
|
+
m.get("display_name", "—"),
|
|
130
|
+
str(m.get("quality_tier", "—")),
|
|
131
|
+
f"{int(p95)} ms" if p95 is not None else "—",
|
|
132
|
+
f"${cost_in:.4f}/1k" if cost_in is not None else "—",
|
|
133
|
+
f"${cost_out:.4f}/1k" if cost_out is not None else "—",
|
|
134
|
+
str(runs) if runs is not None else "—",
|
|
135
|
+
]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
print_table(
|
|
139
|
+
[
|
|
140
|
+
"Provider",
|
|
141
|
+
"Model",
|
|
142
|
+
"Display Name",
|
|
143
|
+
("Quality", {"justify": "right"}),
|
|
144
|
+
("Latency P95", {"justify": "right"}),
|
|
145
|
+
("Cost (in)", {"justify": "right"}),
|
|
146
|
+
("Cost (out)", {"justify": "right"}),
|
|
147
|
+
("Runs 7d", {"justify": "right"}),
|
|
148
|
+
],
|
|
149
|
+
rows,
|
|
150
|
+
title="Model Catalogue",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _print_org_benchmarks(models: list[dict], *, org_id: str) -> None:
|
|
155
|
+
"""Render the org-scoped benchmark table."""
|
|
156
|
+
from teardrop_cli.formatting import data_console, print_table
|
|
157
|
+
|
|
158
|
+
data_console.print(f"[bold]Your org's model usage (7-day aggregate) — {org_id}[/bold]")
|
|
159
|
+
|
|
160
|
+
rows = []
|
|
161
|
+
for m in models:
|
|
162
|
+
b = m.get("benchmarks") or {}
|
|
163
|
+
runs = b.get("total_runs_7d")
|
|
164
|
+
avg_cost = b.get("avg_cost_usdc_per_run")
|
|
165
|
+
total_cost = (runs * avg_cost) if (runs is not None and avg_cost is not None) else None
|
|
166
|
+
rows.append(
|
|
167
|
+
[
|
|
168
|
+
m.get("provider", "—"),
|
|
169
|
+
m.get("model", "—"),
|
|
170
|
+
str(runs) if runs is not None else "—",
|
|
171
|
+
f"{b.get('avg_latency_ms', '—')} ms"
|
|
172
|
+
if b.get("avg_latency_ms") is not None
|
|
173
|
+
else "—",
|
|
174
|
+
f"${avg_cost:.2f}" if avg_cost is not None else "—",
|
|
175
|
+
f"${total_cost:.2f}" if total_cost is not None else "—",
|
|
176
|
+
f"{b.get('avg_tokens_per_sec', '—'):.1f}"
|
|
177
|
+
if b.get("avg_tokens_per_sec") is not None
|
|
178
|
+
else "—",
|
|
179
|
+
]
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
print_table(
|
|
183
|
+
[
|
|
184
|
+
"Provider",
|
|
185
|
+
"Model",
|
|
186
|
+
("Runs", {"justify": "right"}),
|
|
187
|
+
("Avg Latency", {"justify": "right"}),
|
|
188
|
+
("Avg Cost/Run", {"justify": "right"}),
|
|
189
|
+
("Total Cost", {"justify": "right"}),
|
|
190
|
+
("Tokens/sec", {"justify": "right"}),
|
|
191
|
+
],
|
|
192
|
+
rows,
|
|
193
|
+
)
|