trap-cli 0.0.1__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.
- trap/__init__.py +6 -0
- trap/_version.py +24 -0
- trap/auth/__init__.py +15 -0
- trap/auth/client.py +56 -0
- trap/auth/login.py +39 -0
- trap/auth/oauth.py +82 -0
- trap/auth/store.py +39 -0
- trap/cli/__init__.py +274 -0
- trap/cli/_auth.py +103 -0
- trap/cost/__init__.py +3 -0
- trap/cost/calculator.py +41 -0
- trap/cost/providers.py +122 -0
- trap/cost/proxy.py +186 -0
- trap/display/__init__.py +4 -0
- trap/display/progress.py +61 -0
- trap/display/submit.py +20 -0
- trap/environment/__init__.py +3 -0
- trap/environment/detector.py +81 -0
- trap/git_ops/__init__.py +13 -0
- trap/git_ops/base.py +10 -0
- trap/git_ops/local.py +60 -0
- trap/git_ops/remote.py +63 -0
- trap/git_ops/rev.py +119 -0
- trap/git_ops/url.py +98 -0
- trap/loader/__init__.py +5 -0
- trap/loader/errors.py +7 -0
- trap/loader/trap_yaml.py +99 -0
- trap/loader/traptask_yaml.py +85 -0
- trap/models/__init__.py +36 -0
- trap/models/cost.py +36 -0
- trap/models/environment.py +24 -0
- trap/models/provenance.py +20 -0
- trap/models/report.py +61 -0
- trap/models/results.py +17 -0
- trap/models/trap_yaml.py +64 -0
- trap/models/traptask_yaml.py +58 -0
- trap/report/__init__.py +19 -0
- trap/report/base.py +8 -0
- trap/report/handle.py +54 -0
- trap/report/json.py +11 -0
- trap/report/rich.py +116 -0
- trap/runner/__init__.py +5 -0
- trap/runner/capture.py +34 -0
- trap/runner/grader.py +36 -0
- trap/runner/judge.py +50 -0
- trap/runner/layout.py +36 -0
- trap/runner/proc.py +122 -0
- trap/runner/solution.py +80 -0
- trap/runner/task.py +102 -0
- trap_cli-0.0.1.dist-info/METADATA +88 -0
- trap_cli-0.0.1.dist-info/RECORD +54 -0
- trap_cli-0.0.1.dist-info/WHEEL +4 -0
- trap_cli-0.0.1.dist-info/entry_points.txt +2 -0
- trap_cli-0.0.1.dist-info/licenses/LICENSE +21 -0
trap/runner/solution.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from trap.cost import CostProxy
|
|
7
|
+
from trap.models import CaseResult
|
|
8
|
+
from trap.models.cost import CaseCost
|
|
9
|
+
from trap.runner.proc import CapturedSubprocess
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from trap.runner.layout import CaseLayout
|
|
13
|
+
from trap.runner.task import TaskRunner
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SolutionRunner:
|
|
17
|
+
def __init__(self, runner: TaskRunner, case_id: str, layout: CaseLayout) -> None:
|
|
18
|
+
self.runner = runner
|
|
19
|
+
self.case_id = case_id
|
|
20
|
+
self.case_inputs_dir = runner.task_inputs_dir / case_id # task-repo side
|
|
21
|
+
self.layout = layout # workspace side
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def _stdin(self) -> str:
|
|
25
|
+
stdin = self.runner.trap_config.stdin
|
|
26
|
+
if stdin:
|
|
27
|
+
return (self.case_inputs_dir / stdin).read_text()
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def _manifest(self) -> str:
|
|
32
|
+
return json.dumps(
|
|
33
|
+
{
|
|
34
|
+
"inputs_dir": str(self.case_inputs_dir.resolve()),
|
|
35
|
+
"outputs_dir": str(self.layout.outputs_dir.resolve()),
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def run(self) -> CaseResult:
|
|
40
|
+
self.layout.outputs_dir.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
|
|
42
|
+
proxy: CostProxy | None = None
|
|
43
|
+
proxy_env: dict[str, str] = {}
|
|
44
|
+
if self.runner.cost_enabled:
|
|
45
|
+
try:
|
|
46
|
+
proxy = CostProxy()
|
|
47
|
+
proxy.start()
|
|
48
|
+
proxy_env = proxy.env_overrides
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
# A timeout is folded into the result (exit 124, partial output) rather than
|
|
53
|
+
# raised — for the solution it counts as "did not complete", never a crash.
|
|
54
|
+
config = self.runner.trap_config
|
|
55
|
+
proc = CapturedSubprocess(
|
|
56
|
+
config.cmd,
|
|
57
|
+
manifest_envvar=config.manifest_envvar,
|
|
58
|
+
timeout=config.timeout,
|
|
59
|
+
cwd=self.runner.trap_dir,
|
|
60
|
+
manifest=self._manifest,
|
|
61
|
+
capture=self.layout.solution_capture,
|
|
62
|
+
stdin=self._stdin,
|
|
63
|
+
env_extra=proxy_env,
|
|
64
|
+
)
|
|
65
|
+
case_cost: CaseCost | None = None
|
|
66
|
+
try:
|
|
67
|
+
result = proc.run()
|
|
68
|
+
finally:
|
|
69
|
+
if proxy is not None:
|
|
70
|
+
partial = proxy.stop()
|
|
71
|
+
if partial.calls > 0:
|
|
72
|
+
case_cost = partial
|
|
73
|
+
|
|
74
|
+
return CaseResult(
|
|
75
|
+
case_id=self.case_id,
|
|
76
|
+
exit_code=result.exit_code,
|
|
77
|
+
duration=result.duration,
|
|
78
|
+
metrics=None,
|
|
79
|
+
cost=case_cost,
|
|
80
|
+
)
|
trap/runner/task.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from functools import cached_property
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from trap.models import CaseResult, TrapConfig, TraptaskCase, TraptaskConfig
|
|
10
|
+
from trap.runner.grader import GraderRunner
|
|
11
|
+
from trap.runner.judge import JudgeRunner
|
|
12
|
+
from trap.runner.layout import CaseLayout
|
|
13
|
+
from trap.runner.solution import SolutionRunner
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TaskRunner:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
trap_config: TrapConfig,
|
|
20
|
+
trap_dir: Path,
|
|
21
|
+
traptask_dir: Path,
|
|
22
|
+
traptask_config: TraptaskConfig,
|
|
23
|
+
run_dir: Path,
|
|
24
|
+
cost_enabled: bool = True,
|
|
25
|
+
) -> None:
|
|
26
|
+
self.trap_config = trap_config
|
|
27
|
+
self.trap_dir = trap_dir
|
|
28
|
+
self.traptask_config = traptask_config
|
|
29
|
+
self.traptask_dir = traptask_dir
|
|
30
|
+
self.run_dir = run_dir
|
|
31
|
+
self.cost_enabled = cost_enabled
|
|
32
|
+
|
|
33
|
+
@cached_property
|
|
34
|
+
def task_inputs_dir(self) -> Path:
|
|
35
|
+
"""The task's inputs/ dir (traptask_dir / dirs.inputs), resolved once on first use."""
|
|
36
|
+
return (self.traptask_dir / self.traptask_config.dirs.inputs).resolve()
|
|
37
|
+
|
|
38
|
+
@cached_property
|
|
39
|
+
def task_expected_dir(self) -> Path:
|
|
40
|
+
"""The task's expected/ dir (traptask_dir / dirs.expected), resolved once on first use."""
|
|
41
|
+
return (self.traptask_dir / self.traptask_config.dirs.expected).resolve()
|
|
42
|
+
|
|
43
|
+
def _update_latest(self) -> None:
|
|
44
|
+
latest = self.run_dir.parent / "latest"
|
|
45
|
+
if latest.is_symlink():
|
|
46
|
+
latest.unlink()
|
|
47
|
+
elif latest.exists():
|
|
48
|
+
# Path exists but isn't a symlink — likely an interrupted prior run
|
|
49
|
+
# or a sync tool that materialized the symlink as a real directory.
|
|
50
|
+
# Move it aside (non-destructive) so subsequent runs self-heal.
|
|
51
|
+
suffix = datetime.now().isoformat(timespec="microseconds")
|
|
52
|
+
latest.rename(latest.with_name(f"latest.broken.{suffix}"))
|
|
53
|
+
latest.symlink_to(self.run_dir.name)
|
|
54
|
+
|
|
55
|
+
def _iter_cases(
|
|
56
|
+
self,
|
|
57
|
+
cases: Iterable[TraptaskCase],
|
|
58
|
+
*,
|
|
59
|
+
fail_fast: bool = False,
|
|
60
|
+
on_case_start: Callable[[str], None] | None = None,
|
|
61
|
+
on_case_done: Callable[[CaseResult], None] | None = None,
|
|
62
|
+
) -> Iterator[CaseResult]:
|
|
63
|
+
# TODO: parallelize case runs, but judge cases sequentially in the same order as case runs
|
|
64
|
+
for case in cases:
|
|
65
|
+
if on_case_start is not None:
|
|
66
|
+
on_case_start(case.id)
|
|
67
|
+
layout = CaseLayout.for_case(self.run_dir, case.id)
|
|
68
|
+
case_result = SolutionRunner(self, case.id, layout).run()
|
|
69
|
+
if self.traptask_config.judge is not None:
|
|
70
|
+
# A broken judge is folded into an error metric by the runner (never
|
|
71
|
+
# crashes the run); we just attach whatever it returns.
|
|
72
|
+
metrics = JudgeRunner(self, case.id, layout).run()
|
|
73
|
+
case_result = case_result.model_copy(update={"metrics": metrics})
|
|
74
|
+
if on_case_done is not None:
|
|
75
|
+
on_case_done(case_result)
|
|
76
|
+
yield case_result
|
|
77
|
+
if fail_fast and case_result.exit_code != 0:
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
def run(
|
|
81
|
+
self,
|
|
82
|
+
cases: Iterable[TraptaskCase],
|
|
83
|
+
*,
|
|
84
|
+
fail_fast: bool = False,
|
|
85
|
+
on_case_start: Callable[[str], None] | None = None,
|
|
86
|
+
on_case_done: Callable[[CaseResult], None] | None = None,
|
|
87
|
+
) -> tuple[tuple[CaseResult, ...], Any]:
|
|
88
|
+
|
|
89
|
+
case_results = tuple(
|
|
90
|
+
self._iter_cases(
|
|
91
|
+
cases, fail_fast=fail_fast, on_case_start=on_case_start, on_case_done=on_case_done
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
grader_metrics = None
|
|
96
|
+
if self.traptask_config.grader is not None:
|
|
97
|
+
# A broken grader is folded into an error metric by the runner (the run
|
|
98
|
+
# still completes and the report still saves).
|
|
99
|
+
grader_metrics = GraderRunner(self, case_results).run()
|
|
100
|
+
|
|
101
|
+
self._update_latest()
|
|
102
|
+
return case_results, grader_metrics
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trap-cli
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: tp — non-invasive CLI testing framework for AI workflows. Submits results to trapstreet.run.
|
|
5
|
+
Project-URL: Homepage, https://trapstreet.run
|
|
6
|
+
Project-URL: Repository, https://github.com/trapstreet/trap
|
|
7
|
+
Project-URL: Issues, https://github.com/trapstreet/trap/issues
|
|
8
|
+
Author-email: "trapstreet.run" <founder@trapstreet.run>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent,ai,benchmark,eval,llm,trapstreet,workflow
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Requires-Python: >=3.13
|
|
23
|
+
Requires-Dist: gitpython>=3.1
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: psutil>=7.2.2
|
|
26
|
+
Requires-Dist: py-cpuinfo>=9.0.0
|
|
27
|
+
Requires-Dist: pydantic>=2.0
|
|
28
|
+
Requires-Dist: pyyaml>=6.0
|
|
29
|
+
Requires-Dist: rich>=13.0
|
|
30
|
+
Requires-Dist: tokencost>=0.1.26
|
|
31
|
+
Requires-Dist: typer>=0.12
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# trap
|
|
35
|
+
|
|
36
|
+
[](https://github.com/trapstreet/trap/actions/workflows/ci.yml)
|
|
37
|
+
[](https://pypi.org/project/trap-cli/)
|
|
38
|
+
[](https://pypi.org/project/trap-cli/)
|
|
39
|
+
[](https://opensource.org/licenses/MIT)
|
|
40
|
+
|
|
41
|
+
**Non-invasive CLI testing framework for AI prompts, agents, and workflows.**
|
|
42
|
+
|
|
43
|
+
trap treats any solution as a black box — it invokes it as a subprocess, captures outputs, then optionally scores them through a language-agnostic judge and grader. The solution doesn't need to import trap or know it exists.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# requires uv — https://docs.astral.sh/uv/getting-started/installation/
|
|
49
|
+
|
|
50
|
+
# from PyPI
|
|
51
|
+
uv tool install trap-cli
|
|
52
|
+
|
|
53
|
+
# from git (latest main)
|
|
54
|
+
uv tool install "git+https://github.com/trapstreet/trap.git"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The command is `tp`.
|
|
58
|
+
|
|
59
|
+
## How it works
|
|
60
|
+
|
|
61
|
+
trap runs your solution as a subprocess with one env var (`TRAP_MANIFEST` — an input dir and
|
|
62
|
+
an output dir), captures what it writes, then optionally scores it through a judge (per case)
|
|
63
|
+
and a grader (overall). See the [full flow and IO contract](docs/index.md).
|
|
64
|
+
|
|
65
|
+
Two roles, two directories, one IO contract:
|
|
66
|
+
|
|
67
|
+
- **Solution author** — writes `trap.yaml` and the solution code
|
|
68
|
+
- **Task author** — writes `traptask.yaml`, `inputs/`, `expected/`, and optional judge/grader scripts
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# from examples/echo/solution/
|
|
74
|
+
tp run # run all cases
|
|
75
|
+
tp run -t smoke # run only cases tagged `smoke`
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Documentation
|
|
79
|
+
|
|
80
|
+
- [Quick start](docs/quickstart.md)
|
|
81
|
+
- [Writing a solution](docs/guides/writing-solution.md)
|
|
82
|
+
- [Writing a task](docs/guides/writing-task.md)
|
|
83
|
+
- [CLI reference](docs/reference/cli.md)
|
|
84
|
+
- [IO contract](docs/reference/io-contract.md)
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
trap/__init__.py,sha256=UA2VQ2W2CY2JiBDDO7nQeYJUJuniitUWzKzFVXKGzBI,184
|
|
2
|
+
trap/_version.py,sha256=8OsTLsIVB9D0HdPTmt5rVwyVUBe9xTVGkRslXicxzkM,520
|
|
3
|
+
trap/auth/__init__.py,sha256=5bkDjtwT8GwDe_yof0i5RK35mmqDKw60UQjkVc1J0Ns,394
|
|
4
|
+
trap/auth/client.py,sha256=6p-kJmOstolFbZ0CUgxrISxOprUawvNdhtDtBEFfC2Y,2059
|
|
5
|
+
trap/auth/login.py,sha256=GfK2vxWZu3Zbj6U4QS0Wr1nmMSsYk5vbZpqdapj5UTc,1283
|
|
6
|
+
trap/auth/oauth.py,sha256=yDqiwV4PX0xRzMta1P2o0bHpIvTAtgqhc_5EyOAKebE,2726
|
|
7
|
+
trap/auth/store.py,sha256=GAw1HdxaQWPJG7ybHx1FriB71Y2ZnS81OKA5TwGG49s,1010
|
|
8
|
+
trap/cli/__init__.py,sha256=VuLDC4t9uOhsqBUwXc8vtavyO44xagjKJbG2X-WLC-k,10728
|
|
9
|
+
trap/cli/_auth.py,sha256=pg-0UonfqUOpVrzyPzMjTvu1v3wxNi4s-GL6r3dBwrE,3615
|
|
10
|
+
trap/cost/__init__.py,sha256=dZgUNIwI_1TJGNXOy7LSRx0rWwGnVDDW2dl2mkEKcX0,54
|
|
11
|
+
trap/cost/calculator.py,sha256=jo4CQnqEr_UTn4aNjv7n7UUnIPzplfs3leZ4nhRLep4,1710
|
|
12
|
+
trap/cost/providers.py,sha256=BeRIwjsEDi7ASxQ9CIBwOKme9IAPF1MAjcnhat7PbFM,4965
|
|
13
|
+
trap/cost/proxy.py,sha256=LJPqcYkHrWJBMuzC0P-8ozfm2nOI5wJV-3O1Mq8JtLg,7246
|
|
14
|
+
trap/display/__init__.py,sha256=3AFDEPoPvDRFeyn94IAWdw6DZIHNCuAGx7982Ez_Uto,152
|
|
15
|
+
trap/display/progress.py,sha256=FCM1BuEb-z5QwvCwlJ9xnUgZVEmNmAIRLrdVgGl9s68,1954
|
|
16
|
+
trap/display/submit.py,sha256=rA12JI9zIRxybRTEzZd3BMfEsVzCZHY9VbdcgZlYOgY,719
|
|
17
|
+
trap/environment/__init__.py,sha256=TYHi2fnhRAZ3Am-GJo5-J89XY5QpKZEKYRHX1Er7j9c,77
|
|
18
|
+
trap/environment/detector.py,sha256=QJLaQSWtaQEc2Vk5ew2-AEPulamEjXybY0iAB2mUwwc,2632
|
|
19
|
+
trap/git_ops/__init__.py,sha256=eA9RWw-vFK4B2x4NAkwXgckrEDpDgb64gtOigJQ1pAg,293
|
|
20
|
+
trap/git_ops/base.py,sha256=hcbcjU6gvl3XtITvwwa5hpbjylfh2Q2rE2w5jungUkQ,252
|
|
21
|
+
trap/git_ops/local.py,sha256=2ANpyeJ7mOnIlqFLw4398WoVAYHCeRQ4RnKbrdm7d8k,2470
|
|
22
|
+
trap/git_ops/remote.py,sha256=L_tBQVgQ4ReMKWJ6fUEAtoKHGg1m5_did6DuYw_I0co,2596
|
|
23
|
+
trap/git_ops/rev.py,sha256=IxnvU_NsiO9YI2f9XAz3MONF2jL14hXMQnVkek59Y0g,4402
|
|
24
|
+
trap/git_ops/url.py,sha256=6NqpWixb6v2e8ultqwcmTn-yfs0QpPOkoZNSkJiK8Eg,3861
|
|
25
|
+
trap/loader/__init__.py,sha256=cFfdpu4_LR4wSPEk9Q5f5hr-fmyzcMjyk0S_DV3dO9Q,167
|
|
26
|
+
trap/loader/errors.py,sha256=rGe4VT_06mVp_CgVKj-JbvLAEQIOXaJFlM3it7fYdbs,325
|
|
27
|
+
trap/loader/trap_yaml.py,sha256=-IVHh8pAVT2IhznddUJlHOiFZkDXcBf5eXZCyCBUge0,4218
|
|
28
|
+
trap/loader/traptask_yaml.py,sha256=3ITDiJ5fVYZgVjVh_udwH3RFxlxcWtN9394v4Fezp7A,4168
|
|
29
|
+
trap/models/__init__.py,sha256=RDpYjr_E7s0ynFajfdPSqaneCtRX03hPQ6nU2y4rGPI,755
|
|
30
|
+
trap/models/cost.py,sha256=K-FciMrWB2tXu6PEFVM-ZKfk8EZnR3_MS_hIpaxpC4Y,831
|
|
31
|
+
trap/models/environment.py,sha256=1C2e3Dp-XsUZLH2fgVrIpn3REBauDtpLaHw-VTEzceE,747
|
|
32
|
+
trap/models/provenance.py,sha256=fPUWa2Si5rT08vA5LtOZ5cvPRg7ZvVb-zEM-QMfHakE,705
|
|
33
|
+
trap/models/report.py,sha256=ojI8zXMSxtsN1Lh1T1dtWS1WKD9GQLOAFG7u_DOIVg0,2374
|
|
34
|
+
trap/models/results.py,sha256=_Q8PT8gOFZNxMarEJyFML8LIbmxbDMyVmcuWD_z0c_A,432
|
|
35
|
+
trap/models/trap_yaml.py,sha256=xRYmwjknYJmMqDk8Y6S6kNxpcQrPZPRBI26xsjlHc14,2977
|
|
36
|
+
trap/models/traptask_yaml.py,sha256=htD7G6zJcpOPFH8LcrRejr1tmS8hG52VNyj5PIImMPQ,2404
|
|
37
|
+
trap/report/__init__.py,sha256=snW1TEmWZYdbGXkvlXMF0b14rOXYXNolP6jJxzry5Nc,547
|
|
38
|
+
trap/report/base.py,sha256=UGXSHiD3Te3_0UyzkMADpSdw8Rs7yFFGuz5KhfEPgiY,175
|
|
39
|
+
trap/report/handle.py,sha256=Piitl2yOjgMwMtJMD8jrrwD7BLc5ht0SPYa4YvV75XQ,1676
|
|
40
|
+
trap/report/json.py,sha256=9RUtQFGjyAOei-q8C7qi95-XrlogoA3gfMjC0KTaOLY,273
|
|
41
|
+
trap/report/rich.py,sha256=CbSd3i0hBJSOtYSXJiqj-HK7-Lzz89syg_hgfmTwiHA,5023
|
|
42
|
+
trap/runner/__init__.py,sha256=2VleaPrx206KgFUdBEe2CjCIcNrTZMOCBkZ8mij3O4A,102
|
|
43
|
+
trap/runner/capture.py,sha256=pMHsGUZvGS-l9Ngi9XPcZlBa-64p_eTBaRXViO1wGVY,1017
|
|
44
|
+
trap/runner/grader.py,sha256=zaRK_tBtzBtLAMI54moA4_yxaS5My1tJWKK743PXzqA,1241
|
|
45
|
+
trap/runner/judge.py,sha256=jnzwNp-IPYBCNTb7iCmXoKjUaVJd4DDOIVUp0Giv5Ng,1844
|
|
46
|
+
trap/runner/layout.py,sha256=em-q1hZHSbAYaJWwBNmU-GhmFgbIt20Bvsm6kyvFmDA,1118
|
|
47
|
+
trap/runner/proc.py,sha256=GaAWsxdIpvNmeupwk9MVKkHhG-zDdzo3AVqp6X_zbuk,4972
|
|
48
|
+
trap/runner/solution.py,sha256=AWG0d2hr4yS9yFfpSF0ycTTKJTDOwsQg6Q4Q2eQA1-g,2498
|
|
49
|
+
trap/runner/task.py,sha256=2j6TInZhHKCANG4XogyIgydw2BQjdZxJ0agssVgF49Y,4033
|
|
50
|
+
trap_cli-0.0.1.dist-info/METADATA,sha256=LYN_Yn8E-gFgFjpotNcE9TmSaYvhvIRHIGUPW48-qgg,3188
|
|
51
|
+
trap_cli-0.0.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
52
|
+
trap_cli-0.0.1.dist-info/entry_points.txt,sha256=wLIOVpx-cjscwnuIRPTuuXLyXs7K31r68xA7Gwt-Bmk,36
|
|
53
|
+
trap_cli-0.0.1.dist-info/licenses/LICENSE,sha256=ZZ7IeDjFVJJOC6qoVFoHWcysqbip79BElVZvAb5ScTY,1071
|
|
54
|
+
trap_cli-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 trapstreet.run
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|