cosmosforge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cosmosforge/__init__.py +1 -0
- cosmosforge/client/__init__.py +0 -0
- cosmosforge/client/cli.py +105 -0
- cosmosforge/client/config.py +37 -0
- cosmosforge/client/ssh.py +23 -0
- cosmosforge/config.py +14 -0
- cosmosforge/core/__init__.py +0 -0
- cosmosforge/core/oracle_nop.py +108 -0
- cosmosforge/core/report.py +33 -0
- cosmosforge/core/structure.py +23 -0
- cosmosforge/server/__init__.py +0 -0
- cosmosforge/server/cli.py +69 -0
- cosmosforge/server/store.py +33 -0
- cosmosforge-0.1.0.dist-info/METADATA +11 -0
- cosmosforge-0.1.0.dist-info/RECORD +17 -0
- cosmosforge-0.1.0.dist-info/WHEEL +4 -0
- cosmosforge-0.1.0.dist-info/entry_points.txt +3 -0
cosmosforge/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Client CLI (`cf`)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tarfile
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from cosmosforge.client import ssh
|
|
12
|
+
from cosmosforge.client.config import CONFIG_PATH, HostConfig, load_config
|
|
13
|
+
from cosmosforge.config import BUNDLES_DIR, RESULTS_DIR
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(no_args_is_help=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command()
|
|
19
|
+
def init(
|
|
20
|
+
host: str = typer.Option(..., help="Server hostname or IP."),
|
|
21
|
+
user: str = typer.Option(..., help="SSH user on the server."),
|
|
22
|
+
):
|
|
23
|
+
"""Write the server connection config."""
|
|
24
|
+
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
CONFIG_PATH.write_text(f'host = "{host}"\nuser = "{user}"\n')
|
|
26
|
+
typer.echo(f"config written to {CONFIG_PATH}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command()
|
|
30
|
+
def submit(
|
|
31
|
+
task_dir: Path = typer.Argument(..., help="Local harbor task directory."),
|
|
32
|
+
host: str = typer.Option(None),
|
|
33
|
+
user: str = typer.Option(None),
|
|
34
|
+
):
|
|
35
|
+
"""Upload a task bundle to the server."""
|
|
36
|
+
cfg = load_config(host, user)
|
|
37
|
+
from cosmosforge.core.structure import check_structure
|
|
38
|
+
|
|
39
|
+
errors = check_structure(task_dir)
|
|
40
|
+
if errors:
|
|
41
|
+
for e in errors:
|
|
42
|
+
typer.echo(f"structure error: {e}", err=True)
|
|
43
|
+
raise typer.Exit(1)
|
|
44
|
+
|
|
45
|
+
bundle_id = task_dir.resolve().name
|
|
46
|
+
remote_tar = f"{BUNDLES_DIR}/incoming/{bundle_id}.tar.gz"
|
|
47
|
+
with tempfile.TemporaryDirectory(prefix="cf-submit-") as tmp:
|
|
48
|
+
tar_path = Path(tmp) / f"{bundle_id}.tar.gz"
|
|
49
|
+
with tarfile.open(tar_path, "w:gz") as tar:
|
|
50
|
+
tar.add(task_dir, arcname=bundle_id)
|
|
51
|
+
rc = ssh.upload(cfg, tar_path, remote_tar)
|
|
52
|
+
if rc != 0:
|
|
53
|
+
raise typer.Exit(rc)
|
|
54
|
+
rc = ssh.run_remote(
|
|
55
|
+
cfg, f"mkdir -p {BUNDLES_DIR}/incoming && cf-server receive {bundle_id} {remote_tar}"
|
|
56
|
+
)
|
|
57
|
+
if rc == 0:
|
|
58
|
+
typer.echo(f"submitted bundle: {bundle_id}")
|
|
59
|
+
raise typer.Exit(rc)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@app.command()
|
|
63
|
+
def eval(
|
|
64
|
+
bundle_id: str = typer.Argument(..., help="Bundle ID from `cf submit`."),
|
|
65
|
+
host: str = typer.Option(None),
|
|
66
|
+
user: str = typer.Option(None),
|
|
67
|
+
):
|
|
68
|
+
"""Run oracle/nop validation on the server and print the report."""
|
|
69
|
+
cfg = load_config(host, user)
|
|
70
|
+
rc = ssh.run_remote(cfg, f"cf-server eval {bundle_id}")
|
|
71
|
+
raise typer.Exit(rc)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.command()
|
|
75
|
+
def status(
|
|
76
|
+
host: str = typer.Option(None),
|
|
77
|
+
user: str = typer.Option(None),
|
|
78
|
+
):
|
|
79
|
+
"""List bundles and results on the server."""
|
|
80
|
+
cfg = load_config(host, user)
|
|
81
|
+
rc = ssh.run_remote(
|
|
82
|
+
cfg,
|
|
83
|
+
f"ls -1 {BUNDLES_DIR} 2>/dev/null; echo ---; ls -1 {RESULTS_DIR} 2>/dev/null",
|
|
84
|
+
)
|
|
85
|
+
raise typer.Exit(rc)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command()
|
|
89
|
+
def results(
|
|
90
|
+
bundle_id: str = typer.Argument(...),
|
|
91
|
+
host: str = typer.Option(None),
|
|
92
|
+
user: str = typer.Option(None),
|
|
93
|
+
):
|
|
94
|
+
"""Fetch and print the stored result JSON for a bundle."""
|
|
95
|
+
cfg = load_config(host, user)
|
|
96
|
+
rc = ssh.run_remote(cfg, f"cat {RESULTS_DIR}/{bundle_id}.json")
|
|
97
|
+
raise typer.Exit(rc)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def main():
|
|
101
|
+
app()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
main()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Client-side connection config (~/.cosmosforge/config.toml)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
CONFIG_PATH = Path.home() / ".cosmosforge" / "config.toml"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class HostConfig:
|
|
14
|
+
host: str
|
|
15
|
+
user: str
|
|
16
|
+
|
|
17
|
+
def ssh_target(self) -> str:
|
|
18
|
+
return f"{self.user}@{self.host}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_config(host: str | None = None, user: str | None = None) -> HostConfig:
|
|
22
|
+
host = host or os.environ.get("COSMOSFORGE_HOST")
|
|
23
|
+
user = user or os.environ.get("COSMOSFORGE_USER")
|
|
24
|
+
if (not host or not user) and CONFIG_PATH.is_file():
|
|
25
|
+
try:
|
|
26
|
+
import tomllib
|
|
27
|
+
data = tomllib.loads(CONFIG_PATH.read_text())
|
|
28
|
+
except Exception:
|
|
29
|
+
data = {}
|
|
30
|
+
host = host or data.get("host")
|
|
31
|
+
user = user or data.get("user")
|
|
32
|
+
if not host or not user:
|
|
33
|
+
raise SystemExit(
|
|
34
|
+
"No server configured. Run `cf init --host <host> --user <user>`, "
|
|
35
|
+
"or set COSMOSFORGE_HOST / COSMOSFORGE_USER."
|
|
36
|
+
)
|
|
37
|
+
return HostConfig(host=host, user=user)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Thin ssh/scp wrappers. Password auth passes through interactively."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from cosmosforge.client.config import HostConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _run(cmd: list[str]) -> subprocess.CompletedProcess:
|
|
12
|
+
# stdin/stdout are inherited so ssh password prompts reach the user.
|
|
13
|
+
return subprocess.run(cmd, check=False)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_remote(cfg: HostConfig, command: str) -> int:
|
|
17
|
+
proc = _run(["ssh", "-o", "BatchMode=no", cfg.ssh_target(), command])
|
|
18
|
+
return proc.returncode
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upload(cfg: HostConfig, local_path: Path, remote_path: str) -> int:
|
|
22
|
+
proc = _run(["scp", str(local_path), f"{cfg.ssh_target()}:{remote_path}"])
|
|
23
|
+
return proc.returncode
|
cosmosforge/config.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
SERVER_ROOT = Path(os.environ.get("COSMOSFORGE_ROOT", "/data/cosmosforge"))
|
|
5
|
+
BUNDLES_DIR = SERVER_ROOT / "bundles"
|
|
6
|
+
RESULTS_DIR = SERVER_ROOT / "results"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def bundle_dir(bundle_id: str) -> Path:
|
|
10
|
+
return BUNDLES_DIR / bundle_id
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def result_path(bundle_id: str) -> Path:
|
|
14
|
+
return RESULTS_DIR / f"{bundle_id}.json"
|
|
File without changes
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Oracle/nop validation via the harbor framework.
|
|
2
|
+
|
|
3
|
+
Runs both agents in a single harbor job using a job config
|
|
4
|
+
(`harbor run -c`), then parses the job's result.json with harbor's own
|
|
5
|
+
models to get per-agent rewards.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import subprocess
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
from cosmosforge.core.report import RunResult
|
|
16
|
+
from cosmosforge.core.structure import check_structure
|
|
17
|
+
|
|
18
|
+
EXPECTED_REWARDS = {"oracle": 1.0, "nop": 0.0}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _write_job_config(task_dir: Path, config_path: Path) -> None:
|
|
22
|
+
config = {
|
|
23
|
+
"path": str(task_dir),
|
|
24
|
+
"agents": [{"name": name} for name in EXPECTED_REWARDS],
|
|
25
|
+
"job_name": "cosmosforge-eval",
|
|
26
|
+
"n_concurrent": 1,
|
|
27
|
+
"quiet": True,
|
|
28
|
+
}
|
|
29
|
+
config_path.write_text(yaml.safe_dump(config))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _mean_reward(trial_result) -> float | None:
|
|
33
|
+
rewards = trial_result.verifier_result.rewards if trial_result.verifier_result else None
|
|
34
|
+
if not rewards:
|
|
35
|
+
return None
|
|
36
|
+
return sum(float(v) for v in rewards.values()) / len(rewards)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def run_oracle_nop(task_dir: Path, jobs_dir: Path) -> tuple[list[RunResult], str | None]:
|
|
40
|
+
"""Run oracle/nop in one harbor job. Returns (runs, error)."""
|
|
41
|
+
from harbor.models.job.result import JobResult
|
|
42
|
+
|
|
43
|
+
jobs_dir.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
config_path = jobs_dir / "job-config.yaml"
|
|
45
|
+
_write_job_config(task_dir, config_path)
|
|
46
|
+
|
|
47
|
+
cmd = [
|
|
48
|
+
"harbor", "run",
|
|
49
|
+
"-c", str(config_path),
|
|
50
|
+
"-o", str(jobs_dir),
|
|
51
|
+
]
|
|
52
|
+
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=3600, check=False)
|
|
53
|
+
|
|
54
|
+
result_path = jobs_dir / "cosmosforge-eval" / "result.json"
|
|
55
|
+
if not result_path.exists():
|
|
56
|
+
return [], f"harbor run produced no result.json (rc={proc.returncode}): {proc.stderr[-2000:]}"
|
|
57
|
+
|
|
58
|
+
job_result = JobResult.model_validate_json(result_path.read_text())
|
|
59
|
+
|
|
60
|
+
runs: dict[str, RunResult] = {}
|
|
61
|
+
for trial in job_result.trial_results:
|
|
62
|
+
agent = trial.agent_info.name
|
|
63
|
+
reward = _mean_reward(trial)
|
|
64
|
+
error = None
|
|
65
|
+
if trial.exception_info is not None:
|
|
66
|
+
error = f"{trial.exception_info.exception_type}: {trial.exception_info.exception_message}"
|
|
67
|
+
entry = runs.setdefault(agent, RunResult(agent=agent, reward=None, passed=False))
|
|
68
|
+
if reward is not None:
|
|
69
|
+
entry.reward = reward
|
|
70
|
+
if error and not entry.error:
|
|
71
|
+
entry.error = error
|
|
72
|
+
|
|
73
|
+
missing = [a for a in EXPECTED_REWARDS if a not in runs]
|
|
74
|
+
for agent in missing:
|
|
75
|
+
runs[agent] = RunResult(agent=agent, reward=None, passed=False,
|
|
76
|
+
error="no trial result for this agent")
|
|
77
|
+
|
|
78
|
+
error = None
|
|
79
|
+
if proc.returncode != 0:
|
|
80
|
+
error = f"harbor run exited with rc={proc.returncode}"
|
|
81
|
+
return [runs[a] for a in EXPECTED_REWARDS], error
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def evaluate_oracle_nop(bundle_id: str, task_dir: Path, jobs_dir: Path):
|
|
85
|
+
from cosmosforge.core.report import EvaluationReport
|
|
86
|
+
|
|
87
|
+
errors = check_structure(task_dir)
|
|
88
|
+
report = EvaluationReport(
|
|
89
|
+
bundle_id=bundle_id,
|
|
90
|
+
task_name=task_dir.name,
|
|
91
|
+
structure_ok=not errors,
|
|
92
|
+
structure_errors=errors,
|
|
93
|
+
)
|
|
94
|
+
if errors:
|
|
95
|
+
return report
|
|
96
|
+
|
|
97
|
+
runs, error = run_oracle_nop(task_dir, jobs_dir)
|
|
98
|
+
report.runs = runs
|
|
99
|
+
for run in runs:
|
|
100
|
+
expected = EXPECTED_REWARDS.get(run.agent)
|
|
101
|
+
if run.reward == expected and run.error is None:
|
|
102
|
+
run.passed = True
|
|
103
|
+
else:
|
|
104
|
+
run.passed = False
|
|
105
|
+
if run.reward != expected and run.error is None:
|
|
106
|
+
run.error = f"expected reward {expected}, got {run.reward}"
|
|
107
|
+
report.passed = error is None and all(r.passed for r in report.runs)
|
|
108
|
+
return report
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Evaluation report models shared by client and server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import asdict, dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class RunResult:
|
|
11
|
+
agent: str # "oracle" | "nop"
|
|
12
|
+
reward: float | None
|
|
13
|
+
passed: bool
|
|
14
|
+
error: str | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class EvaluationReport:
|
|
19
|
+
bundle_id: str
|
|
20
|
+
task_name: str
|
|
21
|
+
structure_ok: bool
|
|
22
|
+
structure_errors: list[str] = field(default_factory=list)
|
|
23
|
+
runs: list[RunResult] = field(default_factory=list)
|
|
24
|
+
passed: bool = False
|
|
25
|
+
|
|
26
|
+
def to_json(self) -> str:
|
|
27
|
+
return json.dumps(asdict(self), indent=2)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_json(cls, text: str) -> "EvaluationReport":
|
|
31
|
+
data = json.loads(text)
|
|
32
|
+
data["runs"] = [RunResult(**r) for r in data["runs"]]
|
|
33
|
+
return cls(**data)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Harbor task structure validation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
REQUIRED_FILES = [
|
|
8
|
+
"task.toml",
|
|
9
|
+
"instruction.md",
|
|
10
|
+
"environment/Dockerfile",
|
|
11
|
+
"solution/solve.sh",
|
|
12
|
+
"tests/test.sh",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check_structure(task_dir: Path) -> list[str]:
|
|
17
|
+
errors = []
|
|
18
|
+
if not task_dir.is_dir():
|
|
19
|
+
return [f"not a directory: {task_dir}"]
|
|
20
|
+
for rel in REQUIRED_FILES:
|
|
21
|
+
if not (task_dir / rel).is_file():
|
|
22
|
+
errors.append(f"missing required file: {rel}")
|
|
23
|
+
return errors
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Server-side CLI (`cf-server`), invoked over SSH by the client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from cosmosforge import config
|
|
12
|
+
from cosmosforge.core.oracle_nop import evaluate_oracle_nop
|
|
13
|
+
from cosmosforge.server import store
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(no_args_is_help=True)
|
|
16
|
+
|
|
17
|
+
INCOMING_DIR = config.BUNDLES_DIR / "incoming"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.command()
|
|
21
|
+
def receive(
|
|
22
|
+
bundle_id: str = typer.Argument(..., help="Bundle ID (directory name under bundles/)."),
|
|
23
|
+
tar_path: Path = typer.Argument(..., help="Path to uploaded tar.gz."),
|
|
24
|
+
):
|
|
25
|
+
"""Extract an uploaded bundle and validate its structure."""
|
|
26
|
+
task_dir = store.extract_bundle(tar_path, bundle_id)
|
|
27
|
+
from cosmosforge.core.structure import check_structure
|
|
28
|
+
|
|
29
|
+
errors = check_structure(task_dir)
|
|
30
|
+
if errors:
|
|
31
|
+
for e in errors:
|
|
32
|
+
typer.echo(f"structure error: {e}", err=True)
|
|
33
|
+
shutil.rmtree(config.bundle_dir(bundle_id))
|
|
34
|
+
raise typer.Exit(1)
|
|
35
|
+
typer.echo(f"bundle {bundle_id} received, task dir: {task_dir}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.command()
|
|
39
|
+
def eval(
|
|
40
|
+
bundle_id: str = typer.Argument(..., help="Bundle ID to evaluate."),
|
|
41
|
+
):
|
|
42
|
+
"""Run oracle/nop validation and store the report JSON."""
|
|
43
|
+
task_dir = _find_task_dir(bundle_id)
|
|
44
|
+
with tempfile.TemporaryDirectory(prefix="cf-jobs-") as jobs_dir:
|
|
45
|
+
report = evaluate_oracle_nop(bundle_id, task_dir, Path(jobs_dir))
|
|
46
|
+
out = store.save_result(bundle_id, report.to_json())
|
|
47
|
+
typer.echo(report.to_json())
|
|
48
|
+
typer.echo(f"report saved to {out}", err=True)
|
|
49
|
+
if not report.passed:
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _find_task_dir(bundle_id: str) -> Path:
|
|
54
|
+
bundle = config.bundle_dir(bundle_id)
|
|
55
|
+
if not bundle.is_dir():
|
|
56
|
+
typer.echo(f"bundle not found: {bundle_id}", err=True)
|
|
57
|
+
raise typer.Exit(1)
|
|
58
|
+
entries = [p for p in bundle.iterdir() if p.is_dir()]
|
|
59
|
+
if len(entries) == 1:
|
|
60
|
+
return entries[0]
|
|
61
|
+
return bundle
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main():
|
|
65
|
+
app()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Server-side bundle/results storage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tarfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from cosmosforge import config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def extract_bundle(tar_path: Path, bundle_id: str) -> Path:
|
|
12
|
+
dest = config.bundle_dir(bundle_id)
|
|
13
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
with tarfile.open(tar_path, "r:gz") as tar:
|
|
15
|
+
tar.extractall(dest, filter="data")
|
|
16
|
+
# Support both "bundle-id/task-dir/..." and bare "task-dir/..." layouts.
|
|
17
|
+
entries = [p for p in dest.iterdir() if not p.name.startswith(".")]
|
|
18
|
+
if len(entries) == 1 and entries[0].is_dir():
|
|
19
|
+
return entries[0]
|
|
20
|
+
return dest
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def save_result(bundle_id: str, report_json: str) -> Path:
|
|
24
|
+
config.RESULTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
out = config.result_path(bundle_id)
|
|
26
|
+
out.write_text(report_json)
|
|
27
|
+
return out
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def list_bundles() -> list[str]:
|
|
31
|
+
if not config.BUNDLES_DIR.exists():
|
|
32
|
+
return []
|
|
33
|
+
return sorted(p.name for p in config.BUNDLES_DIR.iterdir() if p.is_dir())
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cosmosforge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI and evaluation service for Harbor-format terminal tasks.
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: pyyaml>=6.0
|
|
7
|
+
Requires-Dist: rich>=13.0
|
|
8
|
+
Requires-Dist: typer>=0.12
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# CosmosForge
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
cosmosforge/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
+
cosmosforge/config.py,sha256=wmUZnKCMJIPzve3BQuk6skRYCWXpj0jBOXjlaNYx0uo,353
|
|
3
|
+
cosmosforge/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
cosmosforge/client/cli.py,sha256=tZtjWCZ-mxudmFpCeXRSnOXWXbwabVHTmtpILpIDWzI,2974
|
|
5
|
+
cosmosforge/client/config.py,sha256=rcxM0DnQqtBmeVHDpOxpbs_5emPeV2A6yE1ST5A5Its,1088
|
|
6
|
+
cosmosforge/client/ssh.py,sha256=RQFwY0cBlA5oKbhy_L_F7HhTCU1E86fM-RAmid_1rpA,716
|
|
7
|
+
cosmosforge/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
cosmosforge/core/oracle_nop.py,sha256=xWq1RJtsmWUGzZL5Dm7S8ZCKy2zATYjrVzNsQAb7Dzk,3685
|
|
9
|
+
cosmosforge/core/report.py,sha256=weP8SpSqNNbz74lKh3Z7QFQ-_g1jLa9k0W6f1SW4WaY,812
|
|
10
|
+
cosmosforge/core/structure.py,sha256=CL_9Iu5ZPxbei1sh6nDbR-YL03snhWpxtI55KpPRQzU,536
|
|
11
|
+
cosmosforge/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
cosmosforge/server/cli.py,sha256=luTe8Z9bWHOXV0RDi7vxx5s6aKNeknYXqasNPiRQClM,2006
|
|
13
|
+
cosmosforge/server/store.py,sha256=_CaJui4cFrdKvCU8CaxyktgmOrrIpv5y4tNaOu4N03Y,1007
|
|
14
|
+
cosmosforge-0.1.0.dist-info/METADATA,sha256=VBE8X8iZ2uRe75GGbT5YH5LSSTj7Id0ItM8ezwI-4rs,284
|
|
15
|
+
cosmosforge-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
16
|
+
cosmosforge-0.1.0.dist-info/entry_points.txt,sha256=hAIdwh6vamn8V5VHJCZso8PlrcA6UR3sYww_xhmDx8Y,89
|
|
17
|
+
cosmosforge-0.1.0.dist-info/RECORD,,
|