techtree 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.
- techtree/__init__.py +35 -0
- techtree/__main__.py +14 -0
- techtree/canonical.py +239 -0
- techtree/catalog/__init__.py +25 -0
- techtree/catalog/repository.py +400 -0
- techtree/catalog/service.py +419 -0
- techtree/cli/__init__.py +1 -0
- techtree/cli/app.py +416 -0
- techtree/cli/commands/__init__.py +1 -0
- techtree/cli/commands/climb.py +1223 -0
- techtree/cli/commands/doctor.py +147 -0
- techtree/cli/commands/engine.py +207 -0
- techtree/cli/commands/proof.py +556 -0
- techtree/cli/commands/publish.py +447 -0
- techtree/cli/commands/release.py +303 -0
- techtree/cli/commands/run.py +1067 -0
- techtree/cli/commands/setup.py +181 -0
- techtree/cli/commands/skill.py +221 -0
- techtree/cli/commands/uplift.py +698 -0
- techtree/cli/commands/withdraw.py +212 -0
- techtree/cli/confirm.py +47 -0
- techtree/cli/context.py +96 -0
- techtree/cli/invoke.py +220 -0
- techtree/cli/output.py +280 -0
- techtree/constants.py +138 -0
- techtree/crypto.py +128 -0
- techtree/doctor/__init__.py +1 -0
- techtree/doctor/checks.py +675 -0
- techtree/doctor/execution_checks.py +435 -0
- techtree/doctor/service.py +326 -0
- techtree/drafts/__init__.py +32 -0
- techtree/drafts/source.py +146 -0
- techtree/drafts/store.py +992 -0
- techtree/engines/__init__.py +1 -0
- techtree/engines/bundle.py +251 -0
- techtree/engines/installer.py +679 -0
- techtree/engines/registry.py +235 -0
- techtree/engines/runner.py +170 -0
- techtree/errors.py +262 -0
- techtree/fs.py +234 -0
- techtree/harness.py +108 -0
- techtree/identity/__init__.py +41 -0
- techtree/identity/models.py +113 -0
- techtree/identity/service.py +199 -0
- techtree/identity/store.py +263 -0
- techtree/ids.py +85 -0
- techtree/manifests/__init__.py +39 -0
- techtree/manifests/builder.py +433 -0
- techtree/manifests/compare.py +376 -0
- techtree/models/__init__.py +282 -0
- techtree/models/base.py +201 -0
- techtree/models/campaign.py +484 -0
- techtree/models/catalog.py +227 -0
- techtree/models/cli.py +151 -0
- techtree/models/climb.py +254 -0
- techtree/models/data_policy.py +130 -0
- techtree/models/engine.py +156 -0
- techtree/models/episode_receipt.py +130 -0
- techtree/models/evaluation_backend.py +113 -0
- techtree/models/experiment.py +154 -0
- techtree/models/run.py +214 -0
- techtree/models/skill.py +156 -0
- techtree/models/uplift_report.py +158 -0
- techtree/models/validation.py +299 -0
- techtree/paths.py +116 -0
- techtree/presentation/__init__.py +31 -0
- techtree/presentation/build.py +1242 -0
- techtree/presentation/compact.py +246 -0
- techtree/presentation/evidence.py +169 -0
- techtree/presentation/models.py +358 -0
- techtree/presentation/rich.py +312 -0
- techtree/presentation/sanitize.py +156 -0
- techtree/publication/__init__.py +44 -0
- techtree/publication/address.py +180 -0
- techtree/publication/coordinates.py +26 -0
- techtree/publication/journal.py +212 -0
- techtree/publication/keccak.py +183 -0
- techtree/publication/models.py +209 -0
- techtree/publication/offer.py +35 -0
- techtree/publication/service.py +618 -0
- techtree/publication/transport.py +296 -0
- techtree/publication/verify.py +242 -0
- techtree/publication/withdraw.py +156 -0
- techtree/py.typed +0 -0
- techtree/receipts/__init__.py +52 -0
- techtree/receipts/bundle.py +578 -0
- techtree/receipts/compare.py +1065 -0
- techtree/receipts/episode.py +672 -0
- techtree/receipts/execution.py +630 -0
- techtree/receipts/observed.py +474 -0
- techtree/receipts/set.py +336 -0
- techtree/receipts/uplift.py +655 -0
- techtree/receipts/verify.py +1055 -0
- techtree/release/__init__.py +9 -0
- techtree/release/bootstrap.py +509 -0
- techtree/release/checks.py +376 -0
- techtree/release/document.py +125 -0
- techtree/release/generate.py +221 -0
- techtree/release/models.py +293 -0
- techtree/release/provenance.py +109 -0
- techtree/resources/catalog/campaigns/hello-world-climb.json +1 -0
- techtree/resources/catalog/catalog.json +32 -0
- techtree/resources/catalog/climbs/hello-world-climb.json +1 -0
- techtree/resources/catalog/data-policies/hello-world-climb.json +1 -0
- techtree/resources/catalog/taskset-validations/hello-world-climb.json +1 -0
- techtree/resources/catalog/validation-evidence/hello-world-climb.json +1 -0
- techtree/resources/engines/default/engine.json +20 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/__init__.py +7 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/algorithm.py +136 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/dataset.py +156 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/env.py +48 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/taskset.py +163 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/pyproject.toml +13 -0
- techtree/resources/engines/default/pyproject.toml +23 -0
- techtree/resources/engines/default/tools/inspect_taskset.py +124 -0
- techtree/resources/engines/default/tools/normalize_eval_output.py +470 -0
- techtree/resources/engines/default/tools/normalize_validation.py +222 -0
- techtree/resources/engines/default/uv.lock +1758 -0
- techtree/resources/harness/hermes-agent-0.19.0.json +69 -0
- techtree/resources/release/build-provenance.json +4 -0
- techtree/resources/release/release-core.json +24 -0
- techtree/runs/__init__.py +31 -0
- techtree/runs/artifacts.py +750 -0
- techtree/runs/child_registry.py +228 -0
- techtree/runs/events.py +478 -0
- techtree/runs/executor.py +140 -0
- techtree/runs/fake.py +741 -0
- techtree/runs/launcher.py +253 -0
- techtree/runs/machine.py +489 -0
- techtree/runs/real.py +789 -0
- techtree/runs/service.py +616 -0
- techtree/runs/store.py +555 -0
- techtree/runs/validation.py +259 -0
- techtree/runs/variants.py +684 -0
- techtree/settings.py +143 -0
- techtree/skills/__init__.py +14 -0
- techtree/skills/archive.py +282 -0
- techtree/skills/policy.py +62 -0
- techtree/skills/scanner.py +394 -0
- techtree/skills/service.py +752 -0
- techtree/skills/starter.py +434 -0
- techtree/tasksets/__init__.py +1 -0
- techtree/tasksets/membership.py +269 -0
- techtree/tasksets/provider.py +207 -0
- techtree/tasksets/resolver.py +311 -0
- techtree/tasksets/service.py +484 -0
- techtree/tasksets/verifiers_cli.py +538 -0
- techtree/uplift/__init__.py +20 -0
- techtree/uplift/context.py +544 -0
- techtree/uplift/derive.py +203 -0
- techtree/uplift/public_tasks.py +151 -0
- techtree/uplift/service.py +719 -0
- techtree/uplift/source.py +160 -0
- techtree/verifiers/__init__.py +31 -0
- techtree/verifiers/budget.py +219 -0
- techtree/verifiers/child.py +633 -0
- techtree/verifiers/compiler.py +432 -0
- techtree/verifiers/config.py +365 -0
- techtree/verifiers/credentials.py +321 -0
- techtree/verifiers/image.py +126 -0
- techtree/verifiers/models.py +527 -0
- techtree/verifiers/outputs.py +368 -0
- techtree/verifiers/progress.py +192 -0
- techtree/verifiers/supervisor.py +341 -0
- techtree/verifiers/verify.py +782 -0
- techtree/version.py +39 -0
- techtree/worker/__init__.py +18 -0
- techtree/worker/execute.py +487 -0
- techtree/worker/main.py +57 -0
- techtree-0.1.0.dist-info/METADATA +344 -0
- techtree-0.1.0.dist-info/RECORD +174 -0
- techtree-0.1.0.dist-info/WHEEL +4 -0
- techtree-0.1.0.dist-info/entry_points.txt +3 -0
- techtree-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""``techtree doctor``. Spec section 12.5.
|
|
2
|
+
|
|
3
|
+
The command decides nothing. It asks the Doctor service what it found, turns
|
|
4
|
+
warnings into messages, turns blocking failures into a typed prerequisite
|
|
5
|
+
error, and hands the whole thing to the envelope machinery. Whether a check
|
|
6
|
+
blocks, and which repairs are worth offering, are the service's calls.
|
|
7
|
+
|
|
8
|
+
A blocking failure is reported as a failure — non-zero exit, ``ok`` false — and
|
|
9
|
+
the report still travels in ``data``. A caller that asked what is wrong with
|
|
10
|
+
its machine should get the answer along with the verdict, not instead of it.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import typer
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
from rich.text import Text
|
|
19
|
+
|
|
20
|
+
from techtree.cli.commands.climb import build_catalog_service
|
|
21
|
+
from techtree.cli.context import CliContext, cli_context
|
|
22
|
+
from techtree.cli.invoke import CommandResult, invoke_command
|
|
23
|
+
from techtree.doctor.service import DoctorReport, DoctorService
|
|
24
|
+
from techtree.errors import PrerequisiteError
|
|
25
|
+
from techtree.models.campaign import CampaignSpec
|
|
26
|
+
from techtree.models.cli import CheckStatus, CliMessage, DoctorCheck, MessageLevel
|
|
27
|
+
|
|
28
|
+
__all__ = ["doctor_command", "render_doctor_report"]
|
|
29
|
+
|
|
30
|
+
COMMAND = "doctor"
|
|
31
|
+
|
|
32
|
+
#: How each status is coloured for a person. Ignored entirely when colour is
|
|
33
|
+
#: off or stdout is not a terminal.
|
|
34
|
+
_STATUS_STYLE: dict[CheckStatus, str] = {
|
|
35
|
+
CheckStatus.PASS: "green",
|
|
36
|
+
CheckStatus.WARN: "yellow",
|
|
37
|
+
CheckStatus.FAIL: "red",
|
|
38
|
+
CheckStatus.SKIP: "dim",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def doctor_command(
|
|
43
|
+
ctx: typer.Context,
|
|
44
|
+
for_evaluation: bool = typer.Option(
|
|
45
|
+
False,
|
|
46
|
+
"--for-evaluation",
|
|
47
|
+
help=(
|
|
48
|
+
"Also check what running a Climb for real needs, and treat anything "
|
|
49
|
+
"missing as a reason to stop rather than a note."
|
|
50
|
+
),
|
|
51
|
+
),
|
|
52
|
+
climb: str | None = typer.Option(
|
|
53
|
+
None,
|
|
54
|
+
"--climb",
|
|
55
|
+
metavar="REFERENCE",
|
|
56
|
+
help=(
|
|
57
|
+
"Check the subject a particular Climb would run: its model "
|
|
58
|
+
"credential and its container image. Implies --for-evaluation."
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Run DoctorService and emit checks plus repair actions."""
|
|
63
|
+
context = cli_context(ctx)
|
|
64
|
+
|
|
65
|
+
def action() -> CommandResult[DoctorReport]:
|
|
66
|
+
service = DoctorService(context.paths, context.settings)
|
|
67
|
+
checks = service.run(
|
|
68
|
+
for_evaluation=for_evaluation or climb is not None,
|
|
69
|
+
campaign=_campaign_for(context, climb),
|
|
70
|
+
)
|
|
71
|
+
blocking = service.blocking_failures(checks)
|
|
72
|
+
|
|
73
|
+
return CommandResult(
|
|
74
|
+
data=service.report(checks),
|
|
75
|
+
messages=[
|
|
76
|
+
CliMessage(
|
|
77
|
+
level=MessageLevel.INFO,
|
|
78
|
+
code="doctor_summary",
|
|
79
|
+
text=_summary(len(checks), len(blocking)),
|
|
80
|
+
)
|
|
81
|
+
],
|
|
82
|
+
warnings=[
|
|
83
|
+
CliMessage(level=MessageLevel.WARNING, code=check.id, text=check.detail)
|
|
84
|
+
for check in service.warnings(checks)
|
|
85
|
+
],
|
|
86
|
+
next_actions=service.next_actions(checks),
|
|
87
|
+
error=_blocking_error(blocking) if blocking else None,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
invoke_command(context, COMMAND, action, render_data=_render)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _campaign_for(context: CliContext, reference: str | None) -> CampaignSpec | None:
|
|
94
|
+
"""Resolve the Campaign a named Climb would execute, if one was named.
|
|
95
|
+
|
|
96
|
+
Without a Climb, Doctor can only ask the machine-level questions. Which
|
|
97
|
+
model a subject would authenticate as, and which image it would run in, are
|
|
98
|
+
properties of a particular experiment rather than of the host.
|
|
99
|
+
"""
|
|
100
|
+
if reference is None:
|
|
101
|
+
return None
|
|
102
|
+
return build_catalog_service(context).get_climb(reference).campaign
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def render_doctor_report(report: DoctorReport, console: Console) -> None:
|
|
106
|
+
"""Print the environment facts and the check table."""
|
|
107
|
+
console.print(f"Techtree {report.versions['package_version']}")
|
|
108
|
+
console.print(f"Home: {report.techtree_home}")
|
|
109
|
+
console.print(f"Host platform: {report.host_platform or 'unsupported'}")
|
|
110
|
+
if report.docker_platform is not None:
|
|
111
|
+
console.print(f"Docker platform: {report.docker_platform}")
|
|
112
|
+
|
|
113
|
+
table = Table(box=None, pad_edge=False, padding=(0, 2))
|
|
114
|
+
table.add_column("Status", no_wrap=True)
|
|
115
|
+
table.add_column("Check", no_wrap=True)
|
|
116
|
+
table.add_column("Detail", overflow="fold")
|
|
117
|
+
|
|
118
|
+
for check in report.checks:
|
|
119
|
+
table.add_row(
|
|
120
|
+
Text(check.status.value, style=_STATUS_STYLE[check.status]),
|
|
121
|
+
check.label,
|
|
122
|
+
check.detail,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
console.print()
|
|
126
|
+
console.print(table)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _render(data: object, console: Console) -> None:
|
|
130
|
+
"""Adapt the report to the renderer signature the envelope layer uses."""
|
|
131
|
+
if isinstance(data, DoctorReport):
|
|
132
|
+
render_doctor_report(data, console)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _summary(total: int, blocking: int) -> str:
|
|
136
|
+
if blocking:
|
|
137
|
+
return f"{blocking} of {total} checks block Techtree from working here."
|
|
138
|
+
return f"All {total} checks ran and none of them block Techtree."
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _blocking_error(blocking: list[DoctorCheck]) -> PrerequisiteError:
|
|
142
|
+
identifiers = [check.id for check in blocking]
|
|
143
|
+
return PrerequisiteError(
|
|
144
|
+
"this host is not ready: " + ", ".join(identifiers),
|
|
145
|
+
code="environment_not_ready",
|
|
146
|
+
details={"failed_checks": list(identifiers)},
|
|
147
|
+
)
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""``techtree engine install|status|verify``. Spec section 12.8.
|
|
2
|
+
|
|
3
|
+
The commands are plumbing. Which engine this build ships, what installing one
|
|
4
|
+
involves, and what counts as verified are the engine subsystem's decisions;
|
|
5
|
+
these three functions choose an engine, call it, and hand the result to the
|
|
6
|
+
envelope machinery.
|
|
7
|
+
|
|
8
|
+
Choosing an engine follows one rule: an explicit digest wins, then the active
|
|
9
|
+
engine, then the engine this build ships. That order is what makes
|
|
10
|
+
``techtree engine status`` answer the question a person actually asked —
|
|
11
|
+
"what am I about to run things with?" — without them having to name it.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Annotated
|
|
17
|
+
|
|
18
|
+
import typer
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
|
|
21
|
+
from techtree.canonical import validate_digest
|
|
22
|
+
from techtree.cli.context import CliContext, cli_context
|
|
23
|
+
from techtree.cli.invoke import CommandResult, invoke_command
|
|
24
|
+
from techtree.engines.bundle import default_engine_digest
|
|
25
|
+
from techtree.engines.installer import EngineInstaller, find_uv
|
|
26
|
+
from techtree.engines.registry import EngineRegistry
|
|
27
|
+
from techtree.models.base import Digest
|
|
28
|
+
from techtree.models.cli import CliMessage, MessageLevel, NextAction
|
|
29
|
+
from techtree.models.engine import EngineStatus
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"install_engine_command",
|
|
33
|
+
"render_engine_status",
|
|
34
|
+
"status_engine_command",
|
|
35
|
+
"verify_engine_command",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
DigestArgument = Annotated[
|
|
39
|
+
str | None,
|
|
40
|
+
typer.Argument(
|
|
41
|
+
metavar="[DIGEST]",
|
|
42
|
+
help="The engine to act on. Defaults to the active or shipped engine.",
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def install_engine_command(ctx: typer.Context, digest: DigestArgument = None) -> None:
|
|
48
|
+
"""Install the selected or default embedded engine."""
|
|
49
|
+
context = cli_context(ctx)
|
|
50
|
+
|
|
51
|
+
def action() -> CommandResult[EngineStatus]:
|
|
52
|
+
registry = EngineRegistry(context.paths, context.settings)
|
|
53
|
+
installer = EngineInstaller(context.paths, registry, find_uv())
|
|
54
|
+
# Reported before the install, which is what discards it. Decisions
|
|
55
|
+
# 0004, ratified as 0007 R7.
|
|
56
|
+
interrupted = installer.interrupted_installs()
|
|
57
|
+
status = installer.install(_requested(digest))
|
|
58
|
+
|
|
59
|
+
return CommandResult(
|
|
60
|
+
data=status,
|
|
61
|
+
messages=[
|
|
62
|
+
CliMessage(
|
|
63
|
+
level=MessageLevel.INFO,
|
|
64
|
+
code="engine_installed",
|
|
65
|
+
text=(
|
|
66
|
+
f"Evaluation engine {status.digest} is installed and "
|
|
67
|
+
f"verified at {status.path}."
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
],
|
|
71
|
+
warnings=[
|
|
72
|
+
CliMessage(
|
|
73
|
+
level=MessageLevel.WARNING,
|
|
74
|
+
code="engine_install_interrupted",
|
|
75
|
+
text=(
|
|
76
|
+
f"An earlier install of evaluation engine "
|
|
77
|
+
f"{install.digest} did not finish. What it left behind "
|
|
78
|
+
"was removed and the engine was installed again."
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
for install in interrupted
|
|
82
|
+
],
|
|
83
|
+
next_actions=[_activate_or_browse(registry, status)],
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
invoke_command(context, "engine install", action, render_data=_render)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def status_engine_command(ctx: typer.Context, digest: DigestArgument = None) -> None:
|
|
90
|
+
"""Return installation and activation status."""
|
|
91
|
+
context = cli_context(ctx)
|
|
92
|
+
|
|
93
|
+
def action() -> CommandResult[EngineStatus]:
|
|
94
|
+
registry = EngineRegistry(context.paths, context.settings)
|
|
95
|
+
status = registry.status(_selected(context, digest))
|
|
96
|
+
|
|
97
|
+
return CommandResult(
|
|
98
|
+
data=status,
|
|
99
|
+
messages=[
|
|
100
|
+
CliMessage(
|
|
101
|
+
level=MessageLevel.INFO,
|
|
102
|
+
code="engine_status",
|
|
103
|
+
text=f"Evaluation engine {status.digest} is {status.detail}.",
|
|
104
|
+
)
|
|
105
|
+
],
|
|
106
|
+
next_actions=[
|
|
107
|
+
_activate_or_browse(registry, status)
|
|
108
|
+
if status.installed
|
|
109
|
+
else _install_action()
|
|
110
|
+
],
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
invoke_command(context, "engine status", action, render_data=_render)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def verify_engine_command(ctx: typer.Context, digest: DigestArgument = None) -> None:
|
|
117
|
+
"""Recompute bundle and live-environment checks."""
|
|
118
|
+
context = cli_context(ctx)
|
|
119
|
+
|
|
120
|
+
def action() -> CommandResult[EngineStatus]:
|
|
121
|
+
registry = EngineRegistry(context.paths, context.settings)
|
|
122
|
+
installer = EngineInstaller(context.paths, registry, find_uv())
|
|
123
|
+
status = installer.verify(_selected(context, digest))
|
|
124
|
+
|
|
125
|
+
return CommandResult(
|
|
126
|
+
data=status,
|
|
127
|
+
messages=[
|
|
128
|
+
CliMessage(
|
|
129
|
+
level=MessageLevel.INFO,
|
|
130
|
+
code="engine_verified",
|
|
131
|
+
text=(
|
|
132
|
+
f"Evaluation engine {status.digest} holds the files it "
|
|
133
|
+
"was installed with and runs the pinned validator."
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
],
|
|
137
|
+
next_actions=[_activate_or_browse(registry, status)],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
invoke_command(context, "engine verify", action, render_data=_render)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def render_engine_status(status: EngineStatus, console: Console) -> None:
|
|
144
|
+
"""Print one engine's state for a person."""
|
|
145
|
+
console.print(f"Engine: {status.digest}")
|
|
146
|
+
console.print(f"Location: {status.path}")
|
|
147
|
+
console.print(f"Installed: {'yes' if status.installed else 'no'}")
|
|
148
|
+
console.print(f"Verified: {'yes' if status.verified else 'no'}")
|
|
149
|
+
console.print(f"Active: {'yes' if status.active else 'no'}")
|
|
150
|
+
if status.python_executable is not None:
|
|
151
|
+
console.print(f"Python: {status.python_executable}")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _render(data: object, console: Console) -> None:
|
|
155
|
+
if isinstance(data, EngineStatus):
|
|
156
|
+
render_engine_status(data, console)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _requested(digest: str | None) -> Digest | None:
|
|
160
|
+
"""Validate an explicitly requested digest."""
|
|
161
|
+
return None if digest is None else validate_digest(digest)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _selected(context: CliContext, digest: str | None) -> Digest:
|
|
165
|
+
"""Return the engine a command without an argument is about."""
|
|
166
|
+
explicit = _requested(digest)
|
|
167
|
+
if explicit is not None:
|
|
168
|
+
return explicit
|
|
169
|
+
active = context.settings.active_engine_digest
|
|
170
|
+
if active is not None:
|
|
171
|
+
return active
|
|
172
|
+
return default_engine_digest()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _install_action() -> NextAction:
|
|
176
|
+
return NextAction(
|
|
177
|
+
id="install_engine",
|
|
178
|
+
label="Install the managed evaluation engine",
|
|
179
|
+
reason="Preparing or starting a Climb needs an installed, active engine.",
|
|
180
|
+
cli=["techtree", "engine", "install"],
|
|
181
|
+
hermes_tool=None,
|
|
182
|
+
hermes_args=None,
|
|
183
|
+
requires_user_confirmation=False,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _activate_or_browse(registry: EngineRegistry, status: EngineStatus) -> NextAction:
|
|
188
|
+
"""Offer setup when the new engine is not the active one, else browsing."""
|
|
189
|
+
if registry.active_digest() == status.digest:
|
|
190
|
+
return NextAction(
|
|
191
|
+
id="list_climbs",
|
|
192
|
+
label="Browse the available Climbs",
|
|
193
|
+
reason="The evaluation engine is ready.",
|
|
194
|
+
cli=["techtree", "climb", "list"],
|
|
195
|
+
hermes_tool=None,
|
|
196
|
+
hermes_args=None,
|
|
197
|
+
requires_user_confirmation=False,
|
|
198
|
+
)
|
|
199
|
+
return NextAction(
|
|
200
|
+
id="run_setup",
|
|
201
|
+
label="Finish setting this machine up",
|
|
202
|
+
reason="The engine is installed but is not the active one yet.",
|
|
203
|
+
cli=["techtree", "setup"],
|
|
204
|
+
hermes_tool=None,
|
|
205
|
+
hermes_args=None,
|
|
206
|
+
requires_user_confirmation=False,
|
|
207
|
+
)
|