teardrop-cli 0.3.5__tar.gz → 0.4.1__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.
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/PKG-INFO +13 -3
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/README.md +10 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/docs/cli-reference.md +44 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/pyproject.toml +3 -3
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/_fixtures.py +4 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/cli.py +1 -0
- teardrop_cli-0.4.1/src/teardrop_cli/commands/agent_registration.py +120 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/llm_config.py +63 -5
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/marketplace.py +3 -0
- teardrop_cli-0.4.1/src/teardrop_cli/commands/scorecards.py +190 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/config.py +41 -21
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/conftest.py +43 -0
- teardrop_cli-0.4.1/tests/test_agent_registration.py +211 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_credential_priority.py +11 -0
- teardrop_cli-0.4.1/tests/test_lazy.py +39 -0
- teardrop_cli-0.4.1/tests/test_llm_config.py +209 -0
- teardrop_cli-0.4.1/tests/test_scorecards.py +266 -0
- teardrop_cli-0.3.5/tests/test_llm_config.py +0 -53
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/.github/workflows/publish.yml +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/.github/workflows/test.yml +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/.gitignore +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/.vscode/settings.json +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/LICENSE +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/docs/cli-json-schema.md +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/_lazy.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/_templates.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/__init__.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/agent_tools.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/auth.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/balance.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/chat.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/config_cmd.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/earnings.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/event_triggers.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/mcp.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/models.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/quickstart.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/run.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/schedules.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/tools.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/usage.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/formatting.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/pricing.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/README.md +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/__init__.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/conftest.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_auth_e2e.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_billing_e2e.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_marketplace_e2e.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_run_e2e.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_signup_e2e.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_siwe_login_e2e.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/__init__.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/conftest.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/test_config_persistence.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/test_keyring_integration.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/test_perf_lazygroup.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/test_siwe_signing.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_auth.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_billing.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_chat.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_config_cmd.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_earnings.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_event_triggers.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_init_and_byok.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_marketplace.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_mcp.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_models.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_quickstart.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_run.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_schedules.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_signup.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_token_expiry_fallback.py +0 -0
- {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: teardrop-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Command-line interface for the Teardrop AI agent platform
|
|
5
5
|
Project-URL: Homepage, https://github.com/teardrop-ai/teardrop-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/teardrop-ai/teardrop-cli
|
|
@@ -45,9 +45,9 @@ Requires-Dist: httpx>=0.27
|
|
|
45
45
|
Requires-Dist: keyring>=25.0
|
|
46
46
|
Requires-Dist: platformdirs>=4.0
|
|
47
47
|
Requires-Dist: rich>=13.0
|
|
48
|
-
Requires-Dist: teardrop-sdk>=0.
|
|
48
|
+
Requires-Dist: teardrop-sdk>=0.6.0
|
|
49
49
|
Requires-Dist: tomli-w>=1.0
|
|
50
|
-
Requires-Dist: typer
|
|
50
|
+
Requires-Dist: typer<0.26,>=0.12
|
|
51
51
|
Provides-Extra: dev
|
|
52
52
|
Requires-Dist: hatch>=1.12; extra == 'dev'
|
|
53
53
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
@@ -82,6 +82,10 @@ teardrop --version
|
|
|
82
82
|
|
|
83
83
|
Requires Python ≥ 3.11.
|
|
84
84
|
|
|
85
|
+
Typer is constrained to versions below 0.26 because the CLI's lazy Click root
|
|
86
|
+
loads Typer subcommands. Typer 0.26+ vendors Click and no longer supports this
|
|
87
|
+
integration; mixing the two breaks error handling and exit codes.
|
|
88
|
+
|
|
85
89
|
---
|
|
86
90
|
|
|
87
91
|
## 60-Second Quickstart
|
|
@@ -319,9 +323,15 @@ teardrop marketplace reputation
|
|
|
319
323
|
teardrop marketplace reputation acme/weather --json
|
|
320
324
|
teardrop marketplace subscribe acme/weather
|
|
321
325
|
teardrop marketplace subscriptions
|
|
326
|
+
teardrop marketplace agent-registration preview --agent-url https://agent.example/a2a
|
|
327
|
+
teardrop marketplace agent-registration test --agent-url https://agent.example/a2a
|
|
322
328
|
```
|
|
323
329
|
|
|
324
330
|
Browsing requires no authentication. Subscribed tools become immediately available to your agents.
|
|
331
|
+
Agent registration checks require authentication: `preview` validates a fresh agent card without
|
|
332
|
+
writing registration data, while `test` sends one real but unpaid and unbilled message. Nothing is
|
|
333
|
+
signed or settled; a priced agent's expected HTTP 402 is not a charge to you. Add `--json` to either
|
|
334
|
+
command for the complete SDK response.
|
|
325
335
|
|
|
326
336
|
---
|
|
327
337
|
|
|
@@ -22,6 +22,10 @@ teardrop --version
|
|
|
22
22
|
|
|
23
23
|
Requires Python ≥ 3.11.
|
|
24
24
|
|
|
25
|
+
Typer is constrained to versions below 0.26 because the CLI's lazy Click root
|
|
26
|
+
loads Typer subcommands. Typer 0.26+ vendors Click and no longer supports this
|
|
27
|
+
integration; mixing the two breaks error handling and exit codes.
|
|
28
|
+
|
|
25
29
|
---
|
|
26
30
|
|
|
27
31
|
## 60-Second Quickstart
|
|
@@ -259,9 +263,15 @@ teardrop marketplace reputation
|
|
|
259
263
|
teardrop marketplace reputation acme/weather --json
|
|
260
264
|
teardrop marketplace subscribe acme/weather
|
|
261
265
|
teardrop marketplace subscriptions
|
|
266
|
+
teardrop marketplace agent-registration preview --agent-url https://agent.example/a2a
|
|
267
|
+
teardrop marketplace agent-registration test --agent-url https://agent.example/a2a
|
|
262
268
|
```
|
|
263
269
|
|
|
264
270
|
Browsing requires no authentication. Subscribed tools become immediately available to your agents.
|
|
271
|
+
Agent registration checks require authentication: `preview` validates a fresh agent card without
|
|
272
|
+
writing registration data, while `test` sends one real but unpaid and unbilled message. Nothing is
|
|
273
|
+
signed or settled; a priced agent's expected HTTP 402 is not a charge to you. Add `--json` to either
|
|
274
|
+
command for the complete SDK response.
|
|
265
275
|
|
|
266
276
|
---
|
|
267
277
|
|
|
@@ -14,6 +14,7 @@ This document covers every flag, env var, and subcommand. For the quick path, st
|
|
|
14
14
|
- [Billing & Credits](#billing--credits)
|
|
15
15
|
- [Tool Management](#tool-management)
|
|
16
16
|
- [Marketplace](#marketplace)
|
|
17
|
+
- [Scorecards](#scorecards)
|
|
17
18
|
- [Earnings & Withdrawals](#earnings--withdrawals)
|
|
18
19
|
- [LLM Configuration](#llm-configuration)
|
|
19
20
|
- [MCP Servers](#mcp-servers)
|
|
@@ -330,10 +331,44 @@ teardrop marketplace subscribe acme/weather
|
|
|
330
331
|
teardrop marketplace subscribe acme/weather --yes # skip confirmation
|
|
331
332
|
teardrop marketplace subscriptions
|
|
332
333
|
teardrop marketplace unsubscribe acme/weather
|
|
334
|
+
teardrop marketplace agent-registration preview --agent-url https://agent.example/a2a
|
|
335
|
+
teardrop marketplace agent-registration preview --agent-url https://agent.example/a2a --json
|
|
336
|
+
teardrop marketplace agent-registration test --agent-url https://agent.example/a2a
|
|
333
337
|
```
|
|
334
338
|
|
|
335
339
|
Browsing requires no authentication.
|
|
336
340
|
|
|
341
|
+
Agent registration checks require authentication and use the authenticated organization.
|
|
342
|
+
`preview` fetches a fresh agent card and applies the registration validation without writing
|
|
343
|
+
registration data. Its response includes `registrable`, `detail` (null when registrable), and
|
|
344
|
+
`price_per_task_usdc` when present.
|
|
345
|
+
|
|
346
|
+
`test` sends one real message to the agent, but it is unpaid and unbilled; nothing is signed or
|
|
347
|
+
settled. A priced agent is expected to return HTTP 402, which is not a charge to the CLI user.
|
|
348
|
+
Both commands support `--json` for the complete typed SDK response, and false `registrable` or
|
|
349
|
+
`passed` results are shown as results rather than transport errors.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Scorecards
|
|
354
|
+
|
|
355
|
+
Scorecard tasks, leaderboards, and subject results are public and read-only; no
|
|
356
|
+
authentication is required. Leaderboard and subject queries default to a
|
|
357
|
+
90-day window. `--json` returns the complete SDK response, including prediction
|
|
358
|
+
schemas, task configuration, calibration, and any `null` metrics.
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
teardrop scorecards tasks
|
|
362
|
+
teardrop scorecards tasks --json
|
|
363
|
+
teardrop scorecards leaderboard <definition-key> <version>
|
|
364
|
+
teardrop scorecards leaderboard <definition-key> <version> --window-days 30 --json
|
|
365
|
+
teardrop scorecards show <definition-key> <version> <subject>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
The tables show eligibility, sample count, coverage, adjusted Brier, and
|
|
369
|
+
accuracy. Lower adjusted Brier is better. Unavailable values remain labeled as
|
|
370
|
+
unavailable rather than being shown as zero.
|
|
371
|
+
|
|
337
372
|
---
|
|
338
373
|
|
|
339
374
|
## Earnings & Withdrawals
|
|
@@ -384,6 +419,12 @@ teardrop llm-config set \
|
|
|
384
419
|
**Routing:** `default` · `cost` · `speed` · `quality`
|
|
385
420
|
**Validation:** temperature 0.0–2.0 · max tokens 1–200,000 · timeout ≥ 1 s
|
|
386
421
|
|
|
422
|
+
**Reasoning:** `--reasoning-effort` sets the org-wide default (`none`, `minimal`,
|
|
423
|
+
`low`, `medium`, or `high`). Repeat `--model-reasoning-effort
|
|
424
|
+
provider:model=effort` for per-model overrides; these take precedence. Use
|
|
425
|
+
`--clear-reasoning-effort` to send an explicit null. If either setting is
|
|
426
|
+
omitted, it is left out of the update. `llm-config get` displays both settings.
|
|
427
|
+
|
|
387
428
|
### Bring-your-own-key (BYOK)
|
|
388
429
|
|
|
389
430
|
```bash
|
|
@@ -403,6 +444,9 @@ teardrop llm-config set --provider anthropic --model claude-sonnet-4-6 --clear-k
|
|
|
403
444
|
|
|
404
445
|
BYOK keys are encrypted by the platform and used only for the configured provider/model. Teardrop still charges the orchestration fee through credits or x402; it does not charge Teardrop for the provider's model usage.
|
|
405
446
|
|
|
447
|
+
`--clear-key` explicitly clears the stored BYOK key. Omitting both key flags
|
|
448
|
+
preserves the existing key.
|
|
449
|
+
|
|
406
450
|
---
|
|
407
451
|
|
|
408
452
|
## MCP Servers
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "teardrop-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "Command-line interface for the Teardrop AI agent platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -24,8 +24,8 @@ classifiers = [
|
|
|
24
24
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
|
-
"teardrop-sdk>=0.
|
|
28
|
-
"typer>=0.12",
|
|
27
|
+
"teardrop-sdk>=0.6.0",
|
|
28
|
+
"typer>=0.12,<0.26",
|
|
29
29
|
"rich>=13.0",
|
|
30
30
|
"platformdirs>=4.0",
|
|
31
31
|
"keyring>=25.0",
|
|
@@ -119,6 +119,8 @@ def make_llm_config(
|
|
|
119
119
|
temperature: float = 0.0,
|
|
120
120
|
timeout_seconds: int = 120,
|
|
121
121
|
routing_preference: str = "default",
|
|
122
|
+
reasoning_effort: str | None = None,
|
|
123
|
+
model_reasoning_effort: dict[str, str] | None = None,
|
|
122
124
|
is_byok: bool = False,
|
|
123
125
|
) -> MagicMock:
|
|
124
126
|
"""Return a minimal OrgLlmConfig-like mock."""
|
|
@@ -132,6 +134,8 @@ def make_llm_config(
|
|
|
132
134
|
"temperature": temperature,
|
|
133
135
|
"timeout_seconds": timeout_seconds,
|
|
134
136
|
"routing_preference": routing_preference,
|
|
137
|
+
"reasoning_effort": reasoning_effort,
|
|
138
|
+
"model_reasoning_effort": model_reasoning_effort,
|
|
135
139
|
"is_byok": is_byok,
|
|
136
140
|
"created_at": "2026-04-16T10:00:00Z",
|
|
137
141
|
"updated_at": "2026-04-16T10:00:00Z",
|
|
@@ -43,6 +43,7 @@ _force_utf8_stdio()
|
|
|
43
43
|
"config": "teardrop_cli.commands.config_cmd:app",
|
|
44
44
|
"run": "teardrop_cli.commands.run:app",
|
|
45
45
|
"schedules": "teardrop_cli.commands.schedules:app",
|
|
46
|
+
"scorecards": "teardrop_cli.commands.scorecards:app",
|
|
46
47
|
"balance": "teardrop_cli.commands.balance:app",
|
|
47
48
|
"usage": "teardrop_cli.commands.usage:app",
|
|
48
49
|
"agent-tools": "teardrop_cli.commands.agent_tools:app",
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Marketplace agent registration validation and test commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from json import JSONDecodeError
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
name="agent-registration",
|
|
12
|
+
help="Validate and test your agent endpoint before publishing.",
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _make_request(agent_url: str):
|
|
18
|
+
from pydantic import ValidationError
|
|
19
|
+
from teardrop import MarketplaceAgentRegistrationRequest
|
|
20
|
+
|
|
21
|
+
from teardrop_cli.formatting import print_error
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
return MarketplaceAgentRegistrationRequest(agent_url=agent_url)
|
|
25
|
+
except ValidationError as exc:
|
|
26
|
+
print_error("--agent-url must contain between 1 and 2048 characters.")
|
|
27
|
+
raise typer.Exit(2) from exc
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command()
|
|
31
|
+
def preview(
|
|
32
|
+
agent_url: Annotated[
|
|
33
|
+
str, typer.Option("--agent-url", help="Agent endpoint URL (1 to 2048 characters).")
|
|
34
|
+
],
|
|
35
|
+
as_json: Annotated[
|
|
36
|
+
bool, typer.Option("--json", help="Output the complete response as JSON.")
|
|
37
|
+
] = False,
|
|
38
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Validate agent registration without writing registration data."""
|
|
41
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
42
|
+
from teardrop import TeardropError
|
|
43
|
+
|
|
44
|
+
from teardrop_cli import config
|
|
45
|
+
from teardrop_cli.formatting import print_error, print_json, print_table, spinner
|
|
46
|
+
|
|
47
|
+
request = _make_request(agent_url)
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
with spinner("Previewing agent registration…"), config.get_sync_client(base_url) as client:
|
|
51
|
+
response = client.preview_agent_registration(request)
|
|
52
|
+
except (JSONDecodeError, PydanticValidationError, TeardropError) as exc:
|
|
53
|
+
print_error(f"Unable to preview agent registration: {exc}")
|
|
54
|
+
raise typer.Exit(1) from exc
|
|
55
|
+
|
|
56
|
+
if as_json:
|
|
57
|
+
print_json(response.model_dump(mode="json"))
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
rows = [
|
|
61
|
+
["registrable", "true" if response.registrable else "false"],
|
|
62
|
+
["detail", response.detail if response.detail is not None else "null"],
|
|
63
|
+
]
|
|
64
|
+
if response.price_per_task_usdc is not None:
|
|
65
|
+
rows.append(["price_per_task_usdc", str(response.price_per_task_usdc)])
|
|
66
|
+
print_table(["Field", "Value"], rows, title="Agent Registration Preview")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.command()
|
|
70
|
+
def test(
|
|
71
|
+
agent_url: Annotated[
|
|
72
|
+
str, typer.Option("--agent-url", help="Agent endpoint URL (1 to 2048 characters).")
|
|
73
|
+
],
|
|
74
|
+
as_json: Annotated[
|
|
75
|
+
bool, typer.Option("--json", help="Output the complete response as JSON.")
|
|
76
|
+
] = False,
|
|
77
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Send one real, unpaid message and show the agent's test checks."""
|
|
80
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
81
|
+
from teardrop import TeardropError
|
|
82
|
+
|
|
83
|
+
from teardrop_cli import config
|
|
84
|
+
from teardrop_cli.formatting import (
|
|
85
|
+
print_error,
|
|
86
|
+
print_json,
|
|
87
|
+
print_table,
|
|
88
|
+
print_warning,
|
|
89
|
+
spinner,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
request = _make_request(agent_url)
|
|
93
|
+
client = config.get_sync_client(base_url)
|
|
94
|
+
print_warning(
|
|
95
|
+
"This sends one real message, but it is unpaid and unbilled. Nothing is signed or "
|
|
96
|
+
"settled; a priced agent's expected HTTP 402 is not a charge to you."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
with spinner("Testing agent registration…"), client:
|
|
101
|
+
response = client.test_agent_registration(request)
|
|
102
|
+
except (JSONDecodeError, PydanticValidationError, TeardropError) as exc:
|
|
103
|
+
print_error(f"Unable to test agent registration: {exc}")
|
|
104
|
+
raise typer.Exit(1) from exc
|
|
105
|
+
|
|
106
|
+
if as_json:
|
|
107
|
+
print_json(response.model_dump(mode="json"))
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
print_table(
|
|
111
|
+
["Field", "Value"],
|
|
112
|
+
[["passed", "true" if response.passed else "false"]],
|
|
113
|
+
title="Agent Registration Test",
|
|
114
|
+
)
|
|
115
|
+
if response.checks:
|
|
116
|
+
print_table(
|
|
117
|
+
["Name", "Status", "Detail"],
|
|
118
|
+
[[check.name, check.status, check.detail] for check in response.checks],
|
|
119
|
+
title="Checks",
|
|
120
|
+
)
|
|
@@ -17,6 +17,7 @@ app = typer.Typer(
|
|
|
17
17
|
|
|
18
18
|
_SUPPORTED_PROVIDERS = ["anthropic", "openai", "google", "openrouter"]
|
|
19
19
|
_SUPPORTED_ROUTINGS = ["default", "cost", "speed", "quality"]
|
|
20
|
+
_SUPPORTED_REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high"]
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _mask_api_key(key: str) -> str:
|
|
@@ -25,6 +26,10 @@ def _mask_api_key(key: str) -> str:
|
|
|
25
26
|
return f"{prefix}•••••"
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
def _display_optional(value: object) -> str:
|
|
30
|
+
return "(none)" if value is None else str(value)
|
|
31
|
+
|
|
32
|
+
|
|
28
33
|
# ---------------------------------------------------------------------------
|
|
29
34
|
# get
|
|
30
35
|
# ---------------------------------------------------------------------------
|
|
@@ -83,6 +88,8 @@ def get(
|
|
|
83
88
|
["Temperature", data.get("temperature", "—")],
|
|
84
89
|
["Timeout", f"{data.get('timeout_seconds', '—')}s"],
|
|
85
90
|
["Routing Preference", data.get("routing_preference", "—")],
|
|
91
|
+
["Reasoning Effort", _display_optional(data.get("reasoning_effort"))],
|
|
92
|
+
["Model Reasoning Effort", _display_optional(data.get("model_reasoning_effort"))],
|
|
86
93
|
["BYOK", "true" if data.get("is_byok") else "false"],
|
|
87
94
|
["Updated", data.get("updated_at", "—")],
|
|
88
95
|
],
|
|
@@ -118,6 +125,27 @@ def set_config(
|
|
|
118
125
|
timeout_seconds: Annotated[
|
|
119
126
|
int | None, typer.Option("--timeout-seconds", help="Timeout in seconds (≥1).")
|
|
120
127
|
] = None,
|
|
128
|
+
reasoning_effort: Annotated[
|
|
129
|
+
str | None,
|
|
130
|
+
typer.Option(
|
|
131
|
+
"--reasoning-effort",
|
|
132
|
+
help="Org-wide reasoning effort (none, minimal, low, medium, high).",
|
|
133
|
+
),
|
|
134
|
+
] = None,
|
|
135
|
+
model_reasoning_effort: Annotated[
|
|
136
|
+
list[str] | None,
|
|
137
|
+
typer.Option(
|
|
138
|
+
"--model-reasoning-effort",
|
|
139
|
+
help="Per-model override as provider:model=effort; may be repeated.",
|
|
140
|
+
),
|
|
141
|
+
] = None,
|
|
142
|
+
clear_reasoning_effort: Annotated[
|
|
143
|
+
bool,
|
|
144
|
+
typer.Option(
|
|
145
|
+
"--clear-reasoning-effort",
|
|
146
|
+
help="Clear the org-wide reasoning effort.",
|
|
147
|
+
),
|
|
148
|
+
] = False,
|
|
121
149
|
byok_key: Annotated[
|
|
122
150
|
str | None,
|
|
123
151
|
typer.Option("--byok-key", help="Bring-your-own API key. Pass '-' to read from stdin."),
|
|
@@ -169,6 +197,27 @@ def set_config(
|
|
|
169
197
|
if timeout_seconds is not None and timeout_seconds < 1:
|
|
170
198
|
print_error("timeout-seconds must be ≥ 1.")
|
|
171
199
|
raise typer.Exit(1)
|
|
200
|
+
if reasoning_effort is not None and reasoning_effort not in _SUPPORTED_REASONING_EFFORTS:
|
|
201
|
+
print_error(
|
|
202
|
+
f"Invalid reasoning effort: {reasoning_effort!r}.",
|
|
203
|
+
hint=f"Supported: {', '.join(_SUPPORTED_REASONING_EFFORTS)}",
|
|
204
|
+
)
|
|
205
|
+
raise typer.Exit(1)
|
|
206
|
+
if clear_reasoning_effort and reasoning_effort is not None:
|
|
207
|
+
print_error("--clear-reasoning-effort cannot be combined with --reasoning-effort.")
|
|
208
|
+
raise typer.Exit(1)
|
|
209
|
+
|
|
210
|
+
model_reasoning_efforts: dict[str, str] = {}
|
|
211
|
+
for override in model_reasoning_effort or []:
|
|
212
|
+
provider_model, equals, effort = override.partition("=")
|
|
213
|
+
provider_name, colon, model_id = provider_model.partition(":")
|
|
214
|
+
if not equals or not colon or not provider_name or not model_id:
|
|
215
|
+
print_error(
|
|
216
|
+
f"Invalid model reasoning override: {override!r}.",
|
|
217
|
+
hint="Expected format: provider:model=effort.",
|
|
218
|
+
)
|
|
219
|
+
raise typer.Exit(1)
|
|
220
|
+
model_reasoning_efforts[f"{provider_name}:{model_id}"] = effort
|
|
172
221
|
|
|
173
222
|
# BYOK key resolution
|
|
174
223
|
resolved_key: str | None = None
|
|
@@ -215,13 +264,20 @@ def set_config(
|
|
|
215
264
|
)
|
|
216
265
|
raise typer.Exit(1)
|
|
217
266
|
|
|
267
|
+
kwargs = dict(merged)
|
|
268
|
+
if clear_key:
|
|
269
|
+
kwargs["api_key"] = None
|
|
270
|
+
elif resolved_key is not None:
|
|
271
|
+
kwargs["api_key"] = resolved_key
|
|
272
|
+
if clear_reasoning_effort:
|
|
273
|
+
kwargs["reasoning_effort"] = None
|
|
274
|
+
elif reasoning_effort is not None:
|
|
275
|
+
kwargs["reasoning_effort"] = reasoning_effort
|
|
276
|
+
if model_reasoning_efforts:
|
|
277
|
+
kwargs["model_reasoning_effort"] = model_reasoning_efforts
|
|
278
|
+
|
|
218
279
|
async def _apply():
|
|
219
280
|
try:
|
|
220
|
-
if clear_key:
|
|
221
|
-
return await client.set_llm_config(**merged)
|
|
222
|
-
kwargs = dict(merged)
|
|
223
|
-
if resolved_key is not None:
|
|
224
|
-
kwargs["api_key"] = resolved_key
|
|
225
281
|
return await client.set_llm_config(**kwargs)
|
|
226
282
|
finally:
|
|
227
283
|
await client.close()
|
|
@@ -247,6 +303,8 @@ def set_config(
|
|
|
247
303
|
["Provider", data.get("provider", "—")],
|
|
248
304
|
["Model", data.get("model", "—")],
|
|
249
305
|
["Routing", data.get("routing_preference", "—")],
|
|
306
|
+
["Reasoning Effort", _display_optional(data.get("reasoning_effort"))],
|
|
307
|
+
["Model Reasoning Effort", _display_optional(data.get("model_reasoning_effort"))],
|
|
250
308
|
]
|
|
251
309
|
if data.get("api_base"):
|
|
252
310
|
summary_rows.append(["Self-Hosted URL", data["api_base"]])
|
|
@@ -8,11 +8,14 @@ from typing import Annotated
|
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
11
|
+
from teardrop_cli.commands.agent_registration import app as agent_registration_app
|
|
12
|
+
|
|
11
13
|
app = typer.Typer(
|
|
12
14
|
name="marketplace",
|
|
13
15
|
help="Marketplace — browse and subscribe to published tools.",
|
|
14
16
|
no_args_is_help=True,
|
|
15
17
|
)
|
|
18
|
+
app.add_typer(agent_registration_app, name="agent-registration")
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
def _cost_dollars(atomic: int | None) -> str:
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Read-only public scorecard queries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from json import JSONDecodeError
|
|
7
|
+
from typing import TYPE_CHECKING, Annotated, Any, TypeVar
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from teardrop.models.scorecards import ScorecardItem
|
|
13
|
+
|
|
14
|
+
_Response = TypeVar("_Response")
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(
|
|
17
|
+
name="scorecards",
|
|
18
|
+
help="Browse public scorecards.",
|
|
19
|
+
no_args_is_help=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
_SCORECARD_COLUMNS = [
|
|
23
|
+
"Subject",
|
|
24
|
+
"Eligible",
|
|
25
|
+
("Samples", {"justify": "right"}),
|
|
26
|
+
("Coverage", {"justify": "right"}),
|
|
27
|
+
("Adjusted Brier", {"justify": "right"}),
|
|
28
|
+
("Accuracy", {"justify": "right"}),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _query_scorecards(
|
|
33
|
+
base_url: str | None,
|
|
34
|
+
operation: str,
|
|
35
|
+
query: Callable[[Any], _Response],
|
|
36
|
+
) -> _Response:
|
|
37
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
38
|
+
from teardrop import TeardropClient, TeardropError
|
|
39
|
+
|
|
40
|
+
from teardrop_cli import config
|
|
41
|
+
from teardrop_cli.formatting import print_error
|
|
42
|
+
|
|
43
|
+
url = base_url if base_url is not None else config.get_base_url()
|
|
44
|
+
try:
|
|
45
|
+
# Public scorecard queries must not inherit configured credentials.
|
|
46
|
+
with TeardropClient(url) as client:
|
|
47
|
+
return query(client.scorecards)
|
|
48
|
+
except (JSONDecodeError, PydanticValidationError, TeardropError) as exc:
|
|
49
|
+
print_error(f"Unable to {operation}: {exc}")
|
|
50
|
+
raise typer.Exit(1) from exc
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _format_percentage(value: float | None) -> str:
|
|
54
|
+
return "Unavailable" if value is None else f"{value:.1%}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _format_brier(value: float | None) -> str:
|
|
58
|
+
return "Unavailable" if value is None else f"{value:.4f}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _scorecard_row(item: ScorecardItem) -> list[str]:
|
|
62
|
+
return [
|
|
63
|
+
item.subject,
|
|
64
|
+
"Yes" if item.eligible else "No",
|
|
65
|
+
str(item.n_scored),
|
|
66
|
+
_format_percentage(item.coverage),
|
|
67
|
+
_format_brier(item.adjusted_brier),
|
|
68
|
+
_format_percentage(item.accuracy),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _print_scorecard_table(rows: list[list[str]], *, title: str) -> None:
|
|
73
|
+
from teardrop_cli.formatting import data_console, print_table
|
|
74
|
+
|
|
75
|
+
print_table(_SCORECARD_COLUMNS, rows, title=title)
|
|
76
|
+
data_console.print("[dim]Lower adjusted Brier is better.[/dim]")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.command()
|
|
80
|
+
def tasks(
|
|
81
|
+
as_json: Annotated[
|
|
82
|
+
bool, typer.Option("--json", help="Output the complete response as JSON.")
|
|
83
|
+
] = False,
|
|
84
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""List public scorecard tasks."""
|
|
87
|
+
from teardrop_cli.formatting import data_console, print_json, print_table, spinner
|
|
88
|
+
|
|
89
|
+
with spinner("Fetching public scorecard tasks..."):
|
|
90
|
+
response = _query_scorecards(
|
|
91
|
+
base_url,
|
|
92
|
+
"fetch scorecard tasks",
|
|
93
|
+
lambda scorecards: scorecards.list_tasks(),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if as_json:
|
|
97
|
+
print_json(response.model_dump(mode="json"))
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
if not response.items:
|
|
101
|
+
data_console.print("[dim]No scorecard tasks available.[/dim]")
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
rows = [
|
|
105
|
+
[task.definition_key, task.definition_version, task.definition_sha256[:12]]
|
|
106
|
+
for task in response.items
|
|
107
|
+
]
|
|
108
|
+
print_table(
|
|
109
|
+
["Definition key", "Version", "SHA-256 prefix"],
|
|
110
|
+
rows,
|
|
111
|
+
title="Public Scorecard Tasks",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@app.command()
|
|
116
|
+
def leaderboard(
|
|
117
|
+
definition_key: Annotated[str, typer.Argument(help="Scorecard definition key.")],
|
|
118
|
+
version: Annotated[int, typer.Argument(help="Integer definition version.")],
|
|
119
|
+
window_days: Annotated[
|
|
120
|
+
int, typer.Option("--window-days", help="Score evaluation window in days.")
|
|
121
|
+
] = 90,
|
|
122
|
+
as_json: Annotated[
|
|
123
|
+
bool, typer.Option("--json", help="Output the complete response as JSON.")
|
|
124
|
+
] = False,
|
|
125
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Show the public leaderboard for a scorecard definition."""
|
|
128
|
+
from teardrop_cli.formatting import data_console, print_json, spinner
|
|
129
|
+
|
|
130
|
+
with spinner("Fetching scorecard leaderboard..."):
|
|
131
|
+
response = _query_scorecards(
|
|
132
|
+
base_url,
|
|
133
|
+
"fetch the scorecard leaderboard",
|
|
134
|
+
lambda scorecards: scorecards.get_leaderboard(
|
|
135
|
+
definition_key,
|
|
136
|
+
version,
|
|
137
|
+
window_days=window_days,
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if as_json:
|
|
142
|
+
print_json(response.model_dump(mode="json"))
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
if not response.items:
|
|
146
|
+
data_console.print("[dim]No scorecard results available for this window.[/dim]")
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
_print_scorecard_table(
|
|
150
|
+
[_scorecard_row(item) for item in response.items],
|
|
151
|
+
title=f"Leaderboard: {definition_key} v{version} ({window_days} days)",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@app.command()
|
|
156
|
+
def show(
|
|
157
|
+
definition_key: Annotated[str, typer.Argument(help="Scorecard definition key.")],
|
|
158
|
+
version: Annotated[int, typer.Argument(help="Integer definition version.")],
|
|
159
|
+
subject: Annotated[str, typer.Argument(help="Subject to look up.")],
|
|
160
|
+
window_days: Annotated[
|
|
161
|
+
int, typer.Option("--window-days", help="Score evaluation window in days.")
|
|
162
|
+
] = 90,
|
|
163
|
+
as_json: Annotated[
|
|
164
|
+
bool, typer.Option("--json", help="Output the complete response as JSON.")
|
|
165
|
+
] = False,
|
|
166
|
+
base_url: Annotated[str | None, typer.Option("--base-url", hidden=True)] = None,
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Show one public subject scorecard."""
|
|
169
|
+
from teardrop_cli.formatting import print_json, spinner
|
|
170
|
+
|
|
171
|
+
with spinner("Fetching subject scorecard..."):
|
|
172
|
+
response = _query_scorecards(
|
|
173
|
+
base_url,
|
|
174
|
+
"fetch the subject scorecard",
|
|
175
|
+
lambda scorecards: scorecards.get_scorecard(
|
|
176
|
+
definition_key,
|
|
177
|
+
version,
|
|
178
|
+
subject,
|
|
179
|
+
window_days=window_days,
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if as_json:
|
|
184
|
+
print_json(response.model_dump(mode="json"))
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
_print_scorecard_table(
|
|
188
|
+
[_scorecard_row(response.item)],
|
|
189
|
+
title=f"Scorecard: {definition_key} v{version}",
|
|
190
|
+
)
|