trap-cli 0.0.3__tar.gz → 0.0.5__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 (57) hide show
  1. {trap_cli-0.0.3 → trap_cli-0.0.5}/PKG-INFO +1 -2
  2. {trap_cli-0.0.3 → trap_cli-0.0.5}/pyproject.toml +0 -1
  3. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/_version.py +2 -2
  4. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/cli/__init__.py +19 -10
  5. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/cli/_auth.py +2 -4
  6. trap_cli-0.0.5/src/trap/cli/_console.py +22 -0
  7. trap_cli-0.0.5/src/trap/cost/calculator.py +58 -0
  8. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/cost/providers.py +6 -1
  9. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/cost/proxy.py +1 -1
  10. trap_cli-0.0.3/src/trap/cost/calculator.py +0 -41
  11. {trap_cli-0.0.3 → trap_cli-0.0.5}/.gitignore +0 -0
  12. {trap_cli-0.0.3 → trap_cli-0.0.5}/LICENSE +0 -0
  13. {trap_cli-0.0.3 → trap_cli-0.0.5}/README.md +0 -0
  14. {trap_cli-0.0.3 → trap_cli-0.0.5}/examples/echo/task/README.md +0 -0
  15. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/__init__.py +0 -0
  16. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/auth/__init__.py +0 -0
  17. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/auth/client.py +0 -0
  18. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/auth/login.py +0 -0
  19. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/auth/oauth.py +0 -0
  20. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/auth/store.py +0 -0
  21. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/cost/__init__.py +0 -0
  22. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/display/__init__.py +0 -0
  23. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/display/progress.py +0 -0
  24. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/display/submit.py +0 -0
  25. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/environment/__init__.py +0 -0
  26. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/environment/detector.py +0 -0
  27. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/git_ops/__init__.py +0 -0
  28. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/git_ops/base.py +0 -0
  29. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/git_ops/local.py +0 -0
  30. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/git_ops/remote.py +0 -0
  31. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/git_ops/rev.py +0 -0
  32. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/git_ops/url.py +0 -0
  33. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/loader/__init__.py +0 -0
  34. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/loader/errors.py +0 -0
  35. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/loader/trap_yaml.py +0 -0
  36. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/loader/traptask_yaml.py +0 -0
  37. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/models/__init__.py +0 -0
  38. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/models/cost.py +0 -0
  39. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/models/environment.py +0 -0
  40. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/models/provenance.py +0 -0
  41. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/models/report.py +0 -0
  42. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/models/results.py +0 -0
  43. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/models/trap_yaml.py +0 -0
  44. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/models/traptask_yaml.py +0 -0
  45. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/report/__init__.py +0 -0
  46. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/report/base.py +0 -0
  47. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/report/handle.py +0 -0
  48. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/report/json.py +0 -0
  49. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/report/rich.py +0 -0
  50. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/runner/__init__.py +0 -0
  51. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/runner/capture.py +0 -0
  52. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/runner/grader.py +0 -0
  53. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/runner/judge.py +0 -0
  54. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/runner/layout.py +0 -0
  55. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/runner/proc.py +0 -0
  56. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/runner/solution.py +0 -0
  57. {trap_cli-0.0.3 → trap_cli-0.0.5}/src/trap/runner/task.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trap-cli
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: tp — non-invasive CLI testing framework for AI workflows. Submits results to trapstreet.run.
5
5
  Project-URL: Homepage, https://trapstreet.run
6
6
  Project-URL: Repository, https://github.com/trapstreet/trap
@@ -27,7 +27,6 @@ Requires-Dist: py-cpuinfo>=9.0.0
27
27
  Requires-Dist: pydantic>=2.0
28
28
  Requires-Dist: pyyaml>=6.0
29
29
  Requires-Dist: rich>=13.0
30
- Requires-Dist: tokencost>=0.1.26
31
30
  Requires-Dist: typer>=0.12
32
31
  Description-Content-Type: text/markdown
33
32
 
@@ -29,7 +29,6 @@ dependencies = [
29
29
  "pyyaml>=6.0",
30
30
  "rich>=13.0",
31
31
  "httpx>=0.27",
32
- "tokencost>=0.1.26",
33
32
  "gitpython>=3.1",
34
33
  "psutil>=7.2.2",
35
34
  "py-cpuinfo>=9.0.0",
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.0.3'
22
- __version_tuple__ = version_tuple = (0, 0, 3)
21
+ __version__ = version = '0.0.5'
22
+ __version_tuple__ = version_tuple = (0, 0, 5)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -8,10 +8,11 @@ from pathlib import Path
8
8
  from typing import Annotated
9
9
 
10
10
  import typer
11
- from rich.console import Console
12
11
 
12
+ from trap import __version__
13
13
  from trap.auth import DEFAULT_SERVER, ApiClient, ApiError, AuthStore
14
14
  from trap.cli._auth import auth_app
15
+ from trap.cli._console import _die, _env_truthy, console, err_console
15
16
  from trap.display import CaseProgress, render_submit_result
16
17
  from trap.environment import EnvironmentDetector
17
18
  from trap.git_ops import GitOpsError, LocalRepo, ParsedGitUrl
@@ -22,19 +23,27 @@ from trap.runner import TaskRunner
22
23
 
23
24
  app = typer.Typer(help="AI prompt / agent / workflow / testing framework.")
24
25
  app.add_typer(auth_app, name="auth")
25
- console = Console()
26
- # Warnings go to stderr so `-o json` stdout stays machine-parseable.
27
- err_console = Console(stderr=True)
28
26
 
29
27
 
30
- def _die(msg: object) -> typer.Exit:
31
- """Print an error and return an Exit(2) to raise."""
32
- console.print(f"[red]error[/red]: {msg}")
33
- return typer.Exit(code=2)
28
+ def _version_callback(value: bool) -> None:
29
+ if value:
30
+ console.print(f"tp {__version__}")
31
+ raise typer.Exit()
34
32
 
35
33
 
36
- def _env_truthy(name: str) -> bool:
37
- return os.environ.get(name, "").strip().lower() in ("1", "true", "yes", "on")
34
+ @app.callback()
35
+ def _main(
36
+ version: Annotated[
37
+ bool,
38
+ typer.Option(
39
+ "--version",
40
+ help="Show the trap version and exit.",
41
+ callback=_version_callback,
42
+ is_eager=True,
43
+ ),
44
+ ] = False,
45
+ ) -> None:
46
+ """AI prompt / agent / workflow / testing framework."""
38
47
 
39
48
 
40
49
  def _confirm_remote(url: str, *, trust: bool) -> None:
@@ -4,12 +4,11 @@ import sys
4
4
  from typing import Annotated
5
5
 
6
6
  import typer
7
- from rich.console import Console
8
7
 
9
8
  from trap.auth import DEFAULT_SERVER, ApiClient, ApiError, AuthStore, BrowserProvider, TokenProvider
9
+ from trap.cli._console import _die, console
10
10
 
11
11
  auth_app = typer.Typer(help="Manage authentication.")
12
- console = Console()
13
12
 
14
13
 
15
14
  @auth_app.command("login")
@@ -50,8 +49,7 @@ def auth_login(
50
49
  console.print(provider.pre_message)
51
50
  auth_data = provider.acquire()
52
51
  except (ValueError, TimeoutError) as e:
53
- console.print(f"[red]error[/red]: {e}")
54
- raise typer.Exit(code=2) from None
52
+ raise _die(e) from None
55
53
 
56
54
  path = AuthStore().save(auth_data)
57
55
  console.print(
@@ -0,0 +1,22 @@
1
+ """Shared CLI console objects and small helpers used across command modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ import typer
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+ # Warnings go to stderr so `-o json` stdout stays machine-parseable.
12
+ err_console = Console(stderr=True)
13
+
14
+
15
+ def _die(msg: object) -> typer.Exit:
16
+ """Print an error and return an Exit(2) to raise."""
17
+ console.print(f"[red]error[/red]: {msg}")
18
+ return typer.Exit(code=2)
19
+
20
+
21
+ def _env_truthy(name: str) -> bool:
22
+ return os.environ.get(name, "").strip().lower() in ("1", "true", "yes", "on")
@@ -0,0 +1,58 @@
1
+ """Cost calculation for LLM token usage.
2
+
3
+ Isolated so this logic can be moved server-side without touching the proxy infrastructure.
4
+ The only public symbol is :func:`calculate_call_cost`.
5
+
6
+ Prices are maintained here rather than pulled from a third-party table: the previous
7
+ dependency (tokencost) lagged behind provider releases, silently pricing current models
8
+ at 0 or at a stale predecessor's rate. An explicit table with a NaN fallback keeps the
9
+ failure mode honest — an unknown cost is reported as unknown, never as a wrong number.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import math
15
+
16
+ # USD per million tokens (input, output), keyed by model-id prefix so one entry covers
17
+ # both a version alias ("claude-haiku-4-5") and the dated full id the API reports
18
+ # ("claude-haiku-4-5-20251001"). First match wins: keep more-specific prefixes
19
+ # ("gpt-5.5-pro") ahead of the prefixes they extend ("gpt-5.5").
20
+ _PRICES_PER_MTOK: list[tuple[str, float, float]] = [
21
+ # Anthropic
22
+ ("claude-fable-5", 10.0, 50.0),
23
+ ("claude-mythos-5", 10.0, 50.0),
24
+ # Introductory pricing through 2026-08-31; reverts to 3.0 / 15.0 after.
25
+ ("claude-sonnet-5", 2.0, 10.0),
26
+ ("claude-opus-4-8", 5.0, 25.0),
27
+ ("claude-opus-4-7", 5.0, 25.0),
28
+ ("claude-opus-4-6", 5.0, 25.0),
29
+ ("claude-opus-4-5", 5.0, 25.0),
30
+ ("claude-opus-4-1", 15.0, 75.0),
31
+ ("claude-sonnet-4-6", 3.0, 15.0),
32
+ ("claude-sonnet-4-5", 3.0, 15.0),
33
+ ("claude-haiku-4-5", 1.0, 5.0),
34
+ # OpenAI
35
+ ("gpt-5.6-sol", 5.0, 30.0),
36
+ ("gpt-5.6-terra", 2.5, 15.0),
37
+ ("gpt-5.6-luna", 1.0, 6.0),
38
+ ("gpt-5.5-pro", 30.0, 180.0),
39
+ ("gpt-5.5", 5.0, 30.0),
40
+ ("gpt-5.4-pro", 30.0, 180.0),
41
+ ("gpt-5.4-mini", 0.75, 4.5),
42
+ ("gpt-5.4-nano", 0.2, 1.25),
43
+ ("gpt-5.4", 2.5, 15.0),
44
+ ]
45
+
46
+
47
+ def calculate_call_cost(prompt_tokens: int, completion_tokens: int, model: str | None) -> float:
48
+ """Return the USD cost for one API call.
49
+
50
+ Returns NaN when the model is unknown or absent from the pricing table — an
51
+ unknown cost is not a zero cost (pydantic serialises the NaN as JSON null)."""
52
+ if model is None:
53
+ return math.nan
54
+ name = model.lower()
55
+ for prefix, input_per_mtok, output_per_mtok in _PRICES_PER_MTOK:
56
+ if name.startswith(prefix):
57
+ return (prompt_tokens * input_per_mtok + completion_tokens * output_per_mtok) / 1_000_000
58
+ return math.nan
@@ -23,7 +23,12 @@ class _ProtocolStyle(enum.StrEnum):
23
23
  case (_ProtocolStyle.OPENAI_COMPATIBLE, True):
24
24
  return self._parse_openai_style_sse(body.decode("utf-8", errors="replace"))
25
25
  case (_ProtocolStyle.OPENAI_COMPATIBLE, False):
26
- return self._parse_json(body, "prompt_tokens", "completion_tokens")
26
+ parsed = self._parse_json(body, "prompt_tokens", "completion_tokens")
27
+ if parsed[0] or parsed[1]:
28
+ return parsed
29
+ # The OpenAI Responses API names its usage fields input_/output_tokens
30
+ # (like Anthropic), not the Chat Completions prompt_/completion_tokens.
31
+ return self._parse_json(body, "input_tokens", "output_tokens")
27
32
  case _: # pragma: no cover - exhaustive over the two styles above
28
33
  raise ValueError(f"Unsupported protocol style: {self!r}")
29
34
 
@@ -65,7 +65,7 @@ class CostProxy:
65
65
  ) -> None:
66
66
  if not (prompt_tokens or completion_tokens):
67
67
  return
68
- call_cost = _calc_cost(prompt_tokens, completion_tokens, model) if model else 0.0
68
+ call_cost = _calc_cost(prompt_tokens, completion_tokens, model)
69
69
  with self._lock:
70
70
  key = (provider, model)
71
71
  entry = self._cost_buckets.get(key)
@@ -1,41 +0,0 @@
1
- """Cost calculation for LLM token usage.
2
-
3
- Isolated so this logic can be moved server-side without touching the proxy infrastructure.
4
- The only public symbol is :func:`calculate_call_cost`.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from tokencost import TOKEN_COSTS as _TOKEN_COSTS # type: ignore[import-untyped]
10
- from tokencost import calculate_cost_by_tokens as _calc_cost # type: ignore[import-untyped]
11
- from tokencost import register_model_pattern as _register_pattern # type: ignore[import-untyped]
12
-
13
-
14
- def _register_anthropic_version_patterns() -> None:
15
- # Anthropic now returns version-based names (e.g. "claude-sonnet-4-6") but tokencost only
16
- # has date-based entries (e.g. "claude-sonnet-4-20250514"). Register wildcard patterns so
17
- # calculate_cost_by_tokens can resolve them via _normalize_model_for_pricing.
18
- for pattern, canonical in [
19
- ("claude-sonnet-4-*", "claude-sonnet-4-20250514"),
20
- ("claude-opus-4-*", "claude-opus-4-20250514"),
21
- ]:
22
- entry = _TOKEN_COSTS.get(canonical)
23
- if entry: # pragma: no branch - both canonicals ship in tokencost
24
- _register_pattern(
25
- pattern,
26
- float(entry["input_cost_per_token"]) * 1000,
27
- float(entry["output_cost_per_token"]) * 1000,
28
- )
29
-
30
-
31
- _register_anthropic_version_patterns()
32
-
33
-
34
- def calculate_call_cost(prompt_tokens: int, completion_tokens: int, model: str) -> float:
35
- """Return the USD cost for one API call. Returns 0.0 if the model is not in the pricing table."""
36
- try:
37
- return float(
38
- _calc_cost(prompt_tokens, model, "input") + _calc_cost(completion_tokens, model, "output")
39
- )
40
- except KeyError:
41
- return 0.0
File without changes
File without changes
File without changes
File without changes