valkyrie-cli 2026.11.9b1__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.
- valkyrie/cli/agent/__init__.py +27 -0
- valkyrie/cli/agent/lifecycle.py +130 -0
- valkyrie/cli/benchmark/__init__.py +29 -0
- valkyrie/cli/benchmark/definitions.py +73 -0
- valkyrie/cli/benchmark/services.py +147 -0
- valkyrie/cli/benchmark/tasks.py +65 -0
- valkyrie/cli/client.py +50 -0
- valkyrie/cli/dataset.py +335 -0
- valkyrie/cli/display.py +158 -0
- valkyrie/cli/entry.py +12 -0
- valkyrie/cli/lazy.py +38 -0
- valkyrie/cli/logging.py +20 -0
- valkyrie/cli/login.py +115 -0
- valkyrie/cli/main.py +62 -0
- valkyrie/cli/org.py +121 -0
- valkyrie/cli/project.py +337 -0
- valkyrie/cli/provider.py +68 -0
- valkyrie/cli/queue.py +105 -0
- valkyrie/cli/resource.py +92 -0
- valkyrie/cli/run/__init__.py +51 -0
- valkyrie/cli/run/analyze.py +81 -0
- valkyrie/cli/run/errors.py +113 -0
- valkyrie/cli/run/fetch.py +46 -0
- valkyrie/cli/run/filter_options.py +18 -0
- valkyrie/cli/run/list_runs.py +217 -0
- valkyrie/cli/run/logs.py +109 -0
- valkyrie/cli/run/outputs.py +111 -0
- valkyrie/cli/run/progress.py +286 -0
- valkyrie/cli/run/rescore.py +28 -0
- valkyrie/cli/run/results.py +150 -0
- valkyrie/cli/run/resume.py +168 -0
- valkyrie/cli/run/retag.py +28 -0
- valkyrie/cli/run/shed.py +48 -0
- valkyrie/cli/run/snapshot.py +144 -0
- valkyrie/cli/run/start.py +478 -0
- valkyrie/cli/run/status.py +88 -0
- valkyrie/cli/run/stop.py +59 -0
- valkyrie/cli/run/task_ids.py +51 -0
- valkyrie/cli/run/tasks.py +98 -0
- valkyrie/cli/run/update.py +25 -0
- valkyrie/cli/secret.py +65 -0
- valkyrie/cli/ui.py +20 -0
- valkyrie/display.py +36 -0
- valkyrie/tui/__init__.py +1 -0
- valkyrie/tui/app.py +97 -0
- valkyrie/tui/browse.py +167 -0
- valkyrie/tui/datasets.py +217 -0
- valkyrie/tui/errors.py +128 -0
- valkyrie/tui/fake.py +451 -0
- valkyrie/tui/fixtures/runs.json +128 -0
- valkyrie/tui/run.py +692 -0
- valkyrie/tui/runs.py +310 -0
- valkyrie/tui/source.py +137 -0
- valkyrie/tui/theme.py +126 -0
- valkyrie/tui/valkyrie.tcss +185 -0
- valkyrie/tui/widgets.py +214 -0
- valkyrie_cli-2026.11.9b1.dist-info/METADATA +105 -0
- valkyrie_cli-2026.11.9b1.dist-info/RECORD +61 -0
- valkyrie_cli-2026.11.9b1.dist-info/WHEEL +4 -0
- valkyrie_cli-2026.11.9b1.dist-info/entry_points.txt +3 -0
- valkyrie_cli-2026.11.9b1.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from valkyrie.cli.lazy import LazyGroup
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.group(
|
|
7
|
+
cls=LazyGroup,
|
|
8
|
+
lazy_commands={
|
|
9
|
+
"install": (
|
|
10
|
+
"valkyrie.cli.agent.lifecycle:install",
|
|
11
|
+
"Install a GitHub agent through Tracker, replacing an existing alias.",
|
|
12
|
+
),
|
|
13
|
+
"push": (
|
|
14
|
+
"valkyrie.cli.agent.lifecycle:push",
|
|
15
|
+
"Upload a local agent through Tracker, replacing an existing alias.",
|
|
16
|
+
),
|
|
17
|
+
"remove": ("valkyrie.cli.agent.lifecycle:agent_remove", "Remove an installed agent."),
|
|
18
|
+
"download": ("valkyrie.cli.agent.lifecycle:download", "Download an installed agent."),
|
|
19
|
+
"list": ("valkyrie.cli.agent.lifecycle:list_installed_agents", "List installed agents."),
|
|
20
|
+
},
|
|
21
|
+
help="Install, upload, list, download, and remove agents.",
|
|
22
|
+
)
|
|
23
|
+
def agent():
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = ["agent"]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from valkyrie.sdk import ValkyrieClient
|
|
7
|
+
|
|
8
|
+
from valkyrie.display import terminal_safe
|
|
9
|
+
|
|
10
|
+
from valkyrie.cli.client import call
|
|
11
|
+
from valkyrie.cli.display import format_option, format_table, local_time, paginate_cli_pages
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command(name="install", help="Install a GitHub agent through Tracker, replacing an existing alias.")
|
|
15
|
+
@click.argument("github_url", type=str)
|
|
16
|
+
@click.option(
|
|
17
|
+
"--name",
|
|
18
|
+
"-n",
|
|
19
|
+
type=str,
|
|
20
|
+
required=False,
|
|
21
|
+
help="Agent name (defaults to the contract name).",
|
|
22
|
+
)
|
|
23
|
+
def install(github_url: str, name: str | None):
|
|
24
|
+
entry = call(lambda client: client.agents.install(github_url, name=name))
|
|
25
|
+
click.echo(click.style(f"✓ Agent '{entry.name}' installed successfully!", fg="green", bold=True))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.command(name="push", help="Upload a local agent through Tracker, replacing an existing alias.")
|
|
29
|
+
@click.argument("agent_path", type=click.Path(exists=True, path_type=Path, file_okay=False, dir_okay=True))
|
|
30
|
+
@click.option(
|
|
31
|
+
"--name",
|
|
32
|
+
"-n",
|
|
33
|
+
type=str,
|
|
34
|
+
required=False,
|
|
35
|
+
help="Agent name (defaults to the contract name).",
|
|
36
|
+
)
|
|
37
|
+
def push(agent_path: Path, name: str | None):
|
|
38
|
+
entry = call(lambda client: client.agents.push(agent_path, name=name or None))
|
|
39
|
+
click.echo(click.style(f"✓ Agent '{entry.name}' pushed successfully!", fg="green", bold=True))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@click.command(name="remove", help="Remove an installed agent.")
|
|
43
|
+
@click.argument("agent_name", type=str)
|
|
44
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip the confirmation prompt.")
|
|
45
|
+
def agent_remove(agent_name: str, yes: bool):
|
|
46
|
+
if not yes and not click.confirm(f"Are you sure you want to remove agent '{agent_name}'?"):
|
|
47
|
+
click.echo("Cancelled.")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
call(lambda client: client.agents.remove(agent_name))
|
|
51
|
+
click.echo(click.style(f"✓ Agent '{agent_name}' removed successfully!", fg="green", bold=True))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@click.command(name="download", help="Download an installed agent.")
|
|
55
|
+
@click.argument("agent_name", type=str)
|
|
56
|
+
@click.option(
|
|
57
|
+
"--output-dir",
|
|
58
|
+
"-o",
|
|
59
|
+
type=click.Path(path_type=Path, file_okay=False, dir_okay=True),
|
|
60
|
+
default=None,
|
|
61
|
+
required=False,
|
|
62
|
+
help="Output directory for downloaded agent (default: current directory).",
|
|
63
|
+
)
|
|
64
|
+
@click.option("--overwrite", is_flag=True, help="Replace an existing agent directory after validating the download.")
|
|
65
|
+
def download(agent_name: str, output_dir: Path | None, overwrite: bool):
|
|
66
|
+
call(lambda client: client.agents.download(agent_name, output_dir, overwrite=overwrite))
|
|
67
|
+
click.echo(click.style(f"✓ Agent '{agent_name}' downloaded successfully!", fg="green", bold=True))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@click.command(name="list", help="List installed agents.")
|
|
71
|
+
@click.option("--all", "all_agents", is_flag=True, help="Show every agent without interactive paging.")
|
|
72
|
+
@format_option("Output format. JSON includes every agent without interactive paging.")
|
|
73
|
+
def list_installed_agents(all_agents: bool, output_format: str):
|
|
74
|
+
|
|
75
|
+
async def list_agents(client: ValkyrieClient) -> list[tuple[str, datetime | None]]:
|
|
76
|
+
response = await client.agents.list()
|
|
77
|
+
try:
|
|
78
|
+
return [
|
|
79
|
+
(agent.name, datetime.fromisoformat(agent.last_modified) if agent.last_modified else None)
|
|
80
|
+
for agent in response.agents
|
|
81
|
+
]
|
|
82
|
+
except ValueError as error:
|
|
83
|
+
raise click.ClickException(terminal_safe(str(error), preserve_newlines=True)) from error
|
|
84
|
+
|
|
85
|
+
agents = call(list_agents)
|
|
86
|
+
|
|
87
|
+
if output_format == "json":
|
|
88
|
+
click.echo(
|
|
89
|
+
json.dumps(
|
|
90
|
+
{
|
|
91
|
+
"agents": [
|
|
92
|
+
{"name": name, "last_modified": modified.isoformat() if modified else None}
|
|
93
|
+
for name, modified in agents
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
if not agents:
|
|
101
|
+
click.echo(click.style("No agents found.", fg="yellow"))
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
if all_agents:
|
|
105
|
+
_format_agents_response(agents, 1, 1, len(agents))
|
|
106
|
+
else:
|
|
107
|
+
paginate_agents(agents)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _format_agents_response(
|
|
111
|
+
agents: list[tuple[str, datetime | None]],
|
|
112
|
+
current_page: int,
|
|
113
|
+
total_pages: int,
|
|
114
|
+
total_count: int,
|
|
115
|
+
) -> None:
|
|
116
|
+
rows = [
|
|
117
|
+
{"Agent": name, "Last Modified": local_time(last_modified) if last_modified else "-"}
|
|
118
|
+
for name, last_modified in agents
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
format_table(rows, ["Agent", "Last Modified"], current_page, total_pages, total_count, "agent")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def paginate_agents(agents: list[tuple[str, datetime | None]], limit: int = 10) -> None:
|
|
125
|
+
paginate_cli_pages(
|
|
126
|
+
lambda offset, page_limit: (len(agents), agents[offset : offset + page_limit]),
|
|
127
|
+
_format_agents_response,
|
|
128
|
+
limit=limit,
|
|
129
|
+
render_empty=lambda: click.echo(click.style("No agents found.", fg="yellow")),
|
|
130
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from valkyrie.cli.lazy import LazyGroup
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.group(
|
|
7
|
+
cls=LazyGroup,
|
|
8
|
+
lazy_commands={
|
|
9
|
+
"tasks": ("valkyrie.cli.benchmark.tasks:tasks", "Export the task IDs of a benchmark dataset."),
|
|
10
|
+
"service": (
|
|
11
|
+
"valkyrie.cli.benchmark.services:service",
|
|
12
|
+
"Save benchmark service URLs that your runs use instead of the hosted services.",
|
|
13
|
+
),
|
|
14
|
+
},
|
|
15
|
+
hidden_commands={
|
|
16
|
+
"create": "valkyrie.cli.benchmark.definitions:create_benchmark",
|
|
17
|
+
"list": "valkyrie.cli.benchmark.definitions:list_benchmarks",
|
|
18
|
+
"show": "valkyrie.cli.benchmark.definitions:show_benchmark",
|
|
19
|
+
"update": "valkyrie.cli.benchmark.definitions:update_benchmark",
|
|
20
|
+
"remove": "valkyrie.cli.benchmark.definitions:remove_benchmark",
|
|
21
|
+
"restore": "valkyrie.cli.benchmark.definitions:restore_benchmark",
|
|
22
|
+
},
|
|
23
|
+
help="Export the task IDs in a benchmark dataset, and save benchmark services.",
|
|
24
|
+
)
|
|
25
|
+
def benchmark():
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = ["benchmark"]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from valkyrie.cli.client import call
|
|
4
|
+
from valkyrie.cli.resource import INCLUDE_ARCHIVED, RESOURCE_NAME, list_resources, show_resource
|
|
5
|
+
from valkyrie.cli.display import format_option, one_line
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command(
|
|
9
|
+
name="create",
|
|
10
|
+
help=(
|
|
11
|
+
"Create a benchmark that datasets can belong to.\n\n"
|
|
12
|
+
"NAME is lowercase letters and digits with single '.', '_' or '-' separators, such as swe-bench.\n\n"
|
|
13
|
+
"Example:\n"
|
|
14
|
+
' valkyrie benchmark create swe-bench --description "SWE-bench tasks"'
|
|
15
|
+
),
|
|
16
|
+
)
|
|
17
|
+
@click.argument("name", metavar="NAME", type=RESOURCE_NAME)
|
|
18
|
+
@click.option("--description", default="", help="What the benchmark covers.")
|
|
19
|
+
def create_benchmark(name: str, description: str) -> None:
|
|
20
|
+
created = call(lambda client: client.benchmark_definitions.create(name, description=description))
|
|
21
|
+
click.echo(click.style(f"✓ Benchmark created: {one_line(created.name)}", fg="green", bold=True))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.command(name="list", help="List your organization's benchmarks.")
|
|
25
|
+
@INCLUDE_ARCHIVED
|
|
26
|
+
@format_option()
|
|
27
|
+
def list_benchmarks(include_archived: bool, output_format: str) -> None:
|
|
28
|
+
response = call(lambda client: client.benchmark_definitions.list(include_archived=include_archived))
|
|
29
|
+
list_resources(response, response.benchmarks, output_format, "benchmark")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@click.command(name="show", help="Show one benchmark, archived or not.")
|
|
33
|
+
@click.argument("name", metavar="NAME", type=RESOURCE_NAME)
|
|
34
|
+
@format_option()
|
|
35
|
+
def show_benchmark(name: str, output_format: str) -> None:
|
|
36
|
+
found = call(lambda client: client.benchmark_definitions.get(name))
|
|
37
|
+
show_resource(found, output_format)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@click.command(
|
|
41
|
+
name="update",
|
|
42
|
+
help=(
|
|
43
|
+
"Change a benchmark's description. Its name never changes.\n\n"
|
|
44
|
+
"Example:\n"
|
|
45
|
+
' valkyrie benchmark update swe-bench --description "SWE-bench family"'
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
@click.argument("name", metavar="NAME", type=RESOURCE_NAME)
|
|
49
|
+
@click.option("--description", required=True, help="The description the benchmark now has.")
|
|
50
|
+
def update_benchmark(name: str, description: str) -> None:
|
|
51
|
+
updated = call(lambda client: client.benchmark_definitions.update(name, description=description))
|
|
52
|
+
click.echo(click.style(f"✓ Benchmark updated: {one_line(updated.name)}", fg="green", bold=True))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@click.command(
|
|
56
|
+
name="remove",
|
|
57
|
+
help="Archive a benchmark once it has no active datasets. It stays readable, keeps its name, and can be restored.",
|
|
58
|
+
)
|
|
59
|
+
@click.argument("name", metavar="NAME", type=RESOURCE_NAME)
|
|
60
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip the confirmation prompt.")
|
|
61
|
+
def remove_benchmark(name: str, yes: bool) -> None:
|
|
62
|
+
if not yes and not click.confirm(f"Archive benchmark '{name}'?"):
|
|
63
|
+
click.echo("Cancelled.")
|
|
64
|
+
return
|
|
65
|
+
call(lambda client: client.benchmark_definitions.remove(name))
|
|
66
|
+
click.echo(click.style(f"✓ Benchmark '{name}' archived.", fg="green", bold=True))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@click.command(name="restore", help="Restore an archived benchmark so it can change again.")
|
|
70
|
+
@click.argument("name", metavar="NAME", type=RESOURCE_NAME)
|
|
71
|
+
def restore_benchmark(name: str) -> None:
|
|
72
|
+
restored = call(lambda client: client.benchmark_definitions.restore(name))
|
|
73
|
+
click.echo(click.style(f"✓ Benchmark restored: {one_line(restored.name)}", fg="green", bold=True))
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from collections.abc import Callable, Sequence
|
|
2
|
+
from urllib.parse import urlparse
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from valkyrie.sdk import BenchmarkServiceEntry, BenchmarkServiceHealth
|
|
6
|
+
|
|
7
|
+
from valkyrie.cli.client import call
|
|
8
|
+
from valkyrie.cli.display import format_table, paginate_cli_pages
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group(help="Save benchmark service URLs that your runs use instead of the hosted services.")
|
|
12
|
+
def service() -> None:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@service.command(
|
|
17
|
+
"set",
|
|
18
|
+
help=(
|
|
19
|
+
"Use a service URL for your runs of a benchmark.\n"
|
|
20
|
+
"\n"
|
|
21
|
+
"Replaces the URL and credential saved before. The Tracker must be able to reach the URL.\n"
|
|
22
|
+
"\n"
|
|
23
|
+
"Example:\n"
|
|
24
|
+
" valkyrie benchmark service set swebench https://my-tunnel.ngrok.io --auth-secret swebench-key"
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
@click.argument("benchmark")
|
|
28
|
+
@click.argument("url")
|
|
29
|
+
@click.option(
|
|
30
|
+
"--auth-secret",
|
|
31
|
+
metavar="SECRET",
|
|
32
|
+
help="Organization secret whose value the Tracker sends to the service. Create it with `valkyrie secret set`.",
|
|
33
|
+
)
|
|
34
|
+
@click.option("--auth-header", metavar="HEADER", help="Header that carries the secret. Defaults to Authorization.")
|
|
35
|
+
def service_set(benchmark: str, url: str, auth_secret: str | None, auth_header: str | None) -> None:
|
|
36
|
+
settings = call(
|
|
37
|
+
lambda client: client.settings.set_benchmark_service(
|
|
38
|
+
benchmark, url, auth_secret=auth_secret, auth_header=auth_header
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
saved = settings.benchmark_services[benchmark]
|
|
42
|
+
message = f"Runs of {benchmark} now use {saved.url}"
|
|
43
|
+
if saved.auth_secret_name:
|
|
44
|
+
message += f", sending the secret {saved.auth_secret_name} as {saved.auth_header_name}"
|
|
45
|
+
click.echo(f"{message}.")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@service.command(
|
|
49
|
+
"remove",
|
|
50
|
+
help=(
|
|
51
|
+
"Go back to the hosted service for your runs of a benchmark.\n"
|
|
52
|
+
"\n"
|
|
53
|
+
"Example:\n"
|
|
54
|
+
" valkyrie benchmark service remove swebench"
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
@click.argument("benchmark")
|
|
58
|
+
def service_remove(benchmark: str) -> None:
|
|
59
|
+
call(lambda client: client.settings.remove_benchmark_service(benchmark))
|
|
60
|
+
click.echo(f"Runs of {benchmark} now use the hosted service.")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@service.command(
|
|
64
|
+
"list",
|
|
65
|
+
help=(
|
|
66
|
+
"List hosted benchmark services and your saved URLs, with their latency.\n"
|
|
67
|
+
"\n"
|
|
68
|
+
"A URL you saved replaces the hosted service with the same name."
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
def service_list() -> None:
|
|
72
|
+
saved = call(lambda client: client.settings.get()).benchmark_services
|
|
73
|
+
|
|
74
|
+
catalog = call(lambda client: client.services.catalog())
|
|
75
|
+
services_by_name = {service.name: service for service in catalog.services}
|
|
76
|
+
services_by_name.update({name: BenchmarkServiceEntry(name=name, url=entry.url) for name, entry in saved.items()})
|
|
77
|
+
if not services_by_name:
|
|
78
|
+
click.echo(click.style("No benchmark services configured.", fg="yellow"))
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
paginate_services(
|
|
82
|
+
list(services_by_name.values()),
|
|
83
|
+
check_services=lambda entries: call(lambda client: client.services.check(entries)).services,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _service_source_domain(service: BenchmarkServiceHealth) -> str:
|
|
88
|
+
host = urlparse(service.url).netloc or service.url
|
|
89
|
+
return host.removeprefix(f"{service.name}.")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _health_checked_page(
|
|
93
|
+
services: Sequence[BenchmarkServiceEntry],
|
|
94
|
+
cache: dict[str, BenchmarkServiceHealth],
|
|
95
|
+
check_services: Callable[[list[BenchmarkServiceEntry]], list[BenchmarkServiceHealth]],
|
|
96
|
+
) -> list[BenchmarkServiceHealth]:
|
|
97
|
+
missing = [service for service in services if service.name not in cache]
|
|
98
|
+
if missing:
|
|
99
|
+
cache.update({service.name: service for service in check_services(missing)})
|
|
100
|
+
for service in missing:
|
|
101
|
+
cache.setdefault(
|
|
102
|
+
service.name, BenchmarkServiceHealth(name=service.name, url=service.url, healthy=False, latency_ms=None)
|
|
103
|
+
)
|
|
104
|
+
return [cache[service.name] for service in services]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def paginate_services(
|
|
108
|
+
services: Sequence[BenchmarkServiceEntry],
|
|
109
|
+
check_services: Callable[[list[BenchmarkServiceEntry]], list[BenchmarkServiceHealth]],
|
|
110
|
+
limit: int = 10,
|
|
111
|
+
) -> None:
|
|
112
|
+
health_cache: dict[str, BenchmarkServiceHealth] = {}
|
|
113
|
+
|
|
114
|
+
def load_page(offset: int, page_limit: int) -> tuple[int, list[BenchmarkServiceHealth]]:
|
|
115
|
+
page_services = _health_checked_page(services[offset : offset + page_limit], health_cache, check_services)
|
|
116
|
+
return len(services), page_services
|
|
117
|
+
|
|
118
|
+
def render_page(
|
|
119
|
+
page_services: list[BenchmarkServiceHealth],
|
|
120
|
+
current_page: int,
|
|
121
|
+
total_pages: int,
|
|
122
|
+
total_count: int,
|
|
123
|
+
) -> None:
|
|
124
|
+
rows = [
|
|
125
|
+
{
|
|
126
|
+
"Benchmark": service.name,
|
|
127
|
+
"Service URL": service.url,
|
|
128
|
+
"Source": _service_source_domain(service),
|
|
129
|
+
"Latency": f"{service.latency_ms} ms" if service.latency_ms is not None else "-",
|
|
130
|
+
}
|
|
131
|
+
for service in page_services
|
|
132
|
+
]
|
|
133
|
+
format_table(
|
|
134
|
+
rows,
|
|
135
|
+
["Benchmark", "Service URL", "Source", "Latency"],
|
|
136
|
+
current_page,
|
|
137
|
+
total_pages,
|
|
138
|
+
total_count,
|
|
139
|
+
"service",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
paginate_cli_pages(
|
|
143
|
+
load_page,
|
|
144
|
+
render_page,
|
|
145
|
+
limit=limit,
|
|
146
|
+
render_empty=lambda: click.echo(click.style("No benchmark services found.", fg="yellow")),
|
|
147
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from valkyrie.cli.client import call
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command(
|
|
9
|
+
name="tasks",
|
|
10
|
+
help=(
|
|
11
|
+
"Export the task IDs of a benchmark dataset.\n"
|
|
12
|
+
"\n"
|
|
13
|
+
"Example:\n"
|
|
14
|
+
" valkyrie benchmark tasks swebench --dataset default"
|
|
15
|
+
),
|
|
16
|
+
)
|
|
17
|
+
@click.argument("benchmark_name", type=str)
|
|
18
|
+
@click.option(
|
|
19
|
+
"--dataset",
|
|
20
|
+
type=str,
|
|
21
|
+
required=False,
|
|
22
|
+
default=None,
|
|
23
|
+
help="Dataset name to fetch from the benchmark service (defaults to 'default').",
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
"--header",
|
|
27
|
+
"-H",
|
|
28
|
+
"headers",
|
|
29
|
+
multiple=True,
|
|
30
|
+
nargs=2,
|
|
31
|
+
type=(str, str),
|
|
32
|
+
help="Custom header for benchmark service requests (e.g., -H Authorization my-credential).",
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--ignore-custom-services",
|
|
36
|
+
"--ics",
|
|
37
|
+
is_flag=True,
|
|
38
|
+
help="Use the hosted benchmark service instead of the URL saved with `valkyrie benchmark service set`.",
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--output",
|
|
42
|
+
"-o",
|
|
43
|
+
type=click.Path(path_type=Path, file_okay=True, dir_okay=False),
|
|
44
|
+
required=False,
|
|
45
|
+
default=None,
|
|
46
|
+
help="Path to save task IDs, one per line. Defaults to <benchmark>-<dataset>-tasks.txt.",
|
|
47
|
+
)
|
|
48
|
+
def tasks(
|
|
49
|
+
benchmark_name: str,
|
|
50
|
+
dataset: str | None,
|
|
51
|
+
headers: tuple[tuple[str, str]],
|
|
52
|
+
ignore_custom_services: bool,
|
|
53
|
+
output: Path | None,
|
|
54
|
+
):
|
|
55
|
+
task_ids = call(
|
|
56
|
+
lambda client: client.services.task_ids(
|
|
57
|
+
benchmark_name,
|
|
58
|
+
dataset=dataset,
|
|
59
|
+
service_headers=dict(headers),
|
|
60
|
+
ignore_custom_services=ignore_custom_services,
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
output_path = output or Path(f"{benchmark_name}-{dataset or 'default'}-tasks.txt")
|
|
64
|
+
output_path.write_text("\n".join(task_ids) + "\n", encoding="utf-8")
|
|
65
|
+
click.echo(f"Wrote {len(task_ids)} task IDs to {output_path}")
|
valkyrie/cli/client.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import traceback
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from valkyrie.sdk import SessionAuth, ValkyrieAPIError, ValkyrieClient, ValkyrieConfig, ValkyrieSDKError
|
|
8
|
+
|
|
9
|
+
from valkyrie.display import terminal_safe
|
|
10
|
+
|
|
11
|
+
Result = TypeVar("Result")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def call(operation: Callable[[ValkyrieClient], Awaitable[Result]], auth: SessionAuth | None = None) -> Result:
|
|
15
|
+
|
|
16
|
+
async def run() -> Result:
|
|
17
|
+
async with ValkyrieClient(auth=auth) as client:
|
|
18
|
+
return await operation(client)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
return asyncio.run(run())
|
|
22
|
+
except ValkyrieAPIError as error:
|
|
23
|
+
if error.status_code == 401:
|
|
24
|
+
raise click.ClickException(
|
|
25
|
+
_unauthorized(auth is not None or ValkyrieConfig().http_auth() is not None)
|
|
26
|
+
) from error
|
|
27
|
+
message = f"{_describe(error.detail)} ({error.status_code})" if isinstance(error.detail, list) else str(error)
|
|
28
|
+
raise click.ClickException(terminal_safe(message, preserve_newlines=True)) from error
|
|
29
|
+
except (ValkyrieSDKError, ValueError, OSError) as error:
|
|
30
|
+
if isinstance(error, ValueError) and not _rejected_by_sdk(error):
|
|
31
|
+
raise
|
|
32
|
+
raise click.ClickException(terminal_safe(str(error), preserve_newlines=True)) from error
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _rejected_by_sdk(error: ValueError) -> bool:
|
|
36
|
+
return isinstance(error, ValkyrieSDKError) or any(
|
|
37
|
+
frame.f_globals.get("__name__", "").startswith("valkyrie.sdk")
|
|
38
|
+
for frame, _ in traceback.walk_tb(error.__traceback__)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _unauthorized(has_credentials: bool) -> str:
|
|
43
|
+
problem = "The Tracker rejected your credentials." if has_credentials else "You are not logged in."
|
|
44
|
+
return f"{problem} Run `valkyrie login`."
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _describe(detail: Any) -> Any:
|
|
48
|
+
if isinstance(detail, list):
|
|
49
|
+
return "; ".join(f"{error['loc'][-1]}: {error['msg']}" for error in detail)
|
|
50
|
+
return detail
|