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.
Files changed (74) hide show
  1. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/PKG-INFO +13 -3
  2. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/README.md +10 -0
  3. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/docs/cli-reference.md +44 -0
  4. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/pyproject.toml +3 -3
  5. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/_fixtures.py +4 -0
  6. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/cli.py +1 -0
  7. teardrop_cli-0.4.1/src/teardrop_cli/commands/agent_registration.py +120 -0
  8. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/llm_config.py +63 -5
  9. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/marketplace.py +3 -0
  10. teardrop_cli-0.4.1/src/teardrop_cli/commands/scorecards.py +190 -0
  11. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/config.py +41 -21
  12. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/conftest.py +43 -0
  13. teardrop_cli-0.4.1/tests/test_agent_registration.py +211 -0
  14. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_credential_priority.py +11 -0
  15. teardrop_cli-0.4.1/tests/test_lazy.py +39 -0
  16. teardrop_cli-0.4.1/tests/test_llm_config.py +209 -0
  17. teardrop_cli-0.4.1/tests/test_scorecards.py +266 -0
  18. teardrop_cli-0.3.5/tests/test_llm_config.py +0 -53
  19. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/.github/workflows/publish.yml +0 -0
  20. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/.github/workflows/test.yml +0 -0
  21. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/.gitignore +0 -0
  22. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/.vscode/settings.json +0 -0
  23. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/LICENSE +0 -0
  24. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/docs/cli-json-schema.md +0 -0
  25. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/_lazy.py +0 -0
  26. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/_templates.py +0 -0
  27. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/__init__.py +0 -0
  28. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/agent_tools.py +0 -0
  29. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/auth.py +0 -0
  30. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/balance.py +0 -0
  31. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/chat.py +0 -0
  32. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/config_cmd.py +0 -0
  33. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/earnings.py +0 -0
  34. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/event_triggers.py +0 -0
  35. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/mcp.py +0 -0
  36. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/models.py +0 -0
  37. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/quickstart.py +0 -0
  38. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/run.py +0 -0
  39. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/schedules.py +0 -0
  40. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/tools.py +0 -0
  41. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/commands/usage.py +0 -0
  42. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/formatting.py +0 -0
  43. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/src/teardrop_cli/pricing.py +0 -0
  44. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/README.md +0 -0
  45. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/__init__.py +0 -0
  46. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/conftest.py +0 -0
  47. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_auth_e2e.py +0 -0
  48. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_billing_e2e.py +0 -0
  49. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_marketplace_e2e.py +0 -0
  50. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_run_e2e.py +0 -0
  51. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_signup_e2e.py +0 -0
  52. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/e2e/test_siwe_login_e2e.py +0 -0
  53. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/__init__.py +0 -0
  54. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/conftest.py +0 -0
  55. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/test_config_persistence.py +0 -0
  56. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/test_keyring_integration.py +0 -0
  57. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/test_perf_lazygroup.py +0 -0
  58. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/smoke/test_siwe_signing.py +0 -0
  59. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_auth.py +0 -0
  60. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_billing.py +0 -0
  61. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_chat.py +0 -0
  62. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_config_cmd.py +0 -0
  63. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_earnings.py +0 -0
  64. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_event_triggers.py +0 -0
  65. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_init_and_byok.py +0 -0
  66. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_marketplace.py +0 -0
  67. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_mcp.py +0 -0
  68. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_models.py +0 -0
  69. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_quickstart.py +0 -0
  70. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_run.py +0 -0
  71. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_schedules.py +0 -0
  72. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_signup.py +0 -0
  73. {teardrop_cli-0.3.5 → teardrop_cli-0.4.1}/tests/test_token_expiry_fallback.py +0 -0
  74. {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.5
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.5.0
48
+ Requires-Dist: teardrop-sdk>=0.6.0
49
49
  Requires-Dist: tomli-w>=1.0
50
- Requires-Dist: typer>=0.12
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.3.5"
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.5.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
+ )