rhylthyme-cli-runner 0.2.5a0__tar.gz → 0.2.8a0__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.
- {rhylthyme_cli_runner-0.2.5a0/src/rhylthyme_cli_runner.egg-info → rhylthyme_cli_runner-0.2.8a0}/PKG-INFO +15 -4
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/README.md +11 -2
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/setup.py +4 -2
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/__init__.py +1 -1
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/cli.py +67 -4
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/recorder.py +36 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/import_cli.py +105 -3
- rhylthyme_cli_runner-0.2.8a0/src/rhylthyme_cli_runner/instruments.py +258 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/program_planner.py +30 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/program_runner.py +305 -6
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/remote/checks.py +1 -1
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/remote/cli.py +23 -1
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/validate_program.py +19 -7
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0/src/rhylthyme_cli_runner.egg-info}/PKG-INFO +15 -4
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner.egg-info/SOURCES.txt +2 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner.egg-info/requires.txt +4 -1
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_import_cli.py +71 -0
- rhylthyme_cli_runner-0.2.8a0/tests/test_instruments.py +1129 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_remote.py +62 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_validate_program.py +8 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/LICENSE +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/pyproject.toml +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/setup.cfg +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/environment_icons.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/environment_loader.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/environment_schemas.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/cli.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/compare.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/gold.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/harness.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/llm.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/matcher.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/metrics.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/patterns/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/patterns/authoring_guide.md +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/patterns/baseline.md +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/patterns/baseline.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/patterns/extract.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/patterns/four_turn.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/eval/report.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/expand_replicates.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/calibrate.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/calibrate_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/cli.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/evaluate.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/evaluate_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/factors.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/hash.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/predict.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/render.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/replay.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/report.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/report_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/store.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/synth.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/history/usable.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/instance_checks.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/remote/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/remote/auth.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/remote/mcp_client.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/render_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner.egg-info/dependency_links.txt +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner.egg-info/entry_points.txt +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner.egg-info/top_level.txt +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_calibrate.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_eval_baseline.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_eval_harness.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_eval_providers.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_eval_scorer.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_evaluate.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_example_validation.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_examples_integration.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_expand_replicates.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_hash_parity.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_history_report.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_mcp_checks.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_planner_time_strings.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_predict.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_program_runner.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_render_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_replay.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_run_recorder.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_skill.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_synth.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_usable.py +0 -0
- {rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/tests/test_validate_examples_ci.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rhylthyme-cli-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8a0
|
|
4
4
|
Summary: CLI runner for Rhylthyme real-time program schedules
|
|
5
5
|
Home-page: https://github.com/rhylthyme/rhylthyme-cli-runner
|
|
6
6
|
Author: Rhylthyme Team
|
|
@@ -26,7 +26,9 @@ Requires-Dist: click>=8.0.0
|
|
|
26
26
|
Requires-Dist: jsonschema>=4.0.0
|
|
27
27
|
Requires-Dist: pyyaml>=6.0
|
|
28
28
|
Requires-Dist: colorama>=0.4.0
|
|
29
|
-
Requires-Dist: rhylthyme-spec>=0.2.
|
|
29
|
+
Requires-Dist: rhylthyme-spec>=0.2.1a0
|
|
30
|
+
Provides-Extra: galago
|
|
31
|
+
Requires-Dist: rhylthyme-galago>=0.1.0a0; extra == "galago"
|
|
30
32
|
Provides-Extra: dev
|
|
31
33
|
Requires-Dist: pytest>=6.0.0; extra == "dev"
|
|
32
34
|
Requires-Dist: pytest-cov>=2.10.0; extra == "dev"
|
|
@@ -561,8 +563,17 @@ rhylthyme search "pad thai" -i spoonacular # then impo
|
|
|
561
563
|
```
|
|
562
564
|
|
|
563
565
|
**Options:** `-i IMPORTER` (default: chosen from the URL), `-o PATH`,
|
|
564
|
-
`--stdout`, `--publish` (and `--open`), `--no-validate`. `-` reads
|
|
565
|
-
source from stdin (with `-i`).
|
|
566
|
+
`--stdout`, `--publish` (and `--open`), `--no-validate`, `--review`. `-` reads
|
|
567
|
+
pasted source from stdin (with `-i`).
|
|
568
|
+
|
|
569
|
+
`--review` sends the import and its source to a model on the server (GPT-5.6
|
|
570
|
+
Luna, the model that related steps best in the paper's evaluation) and prints
|
|
571
|
+
what looks wrong: a default duration on "simmer until thick", a step the
|
|
572
|
+
parser dropped, steps chained that the source runs in parallel, a total that
|
|
573
|
+
disagrees with the source. It needs `rhylthyme login`, spends one model call
|
|
574
|
+
(capped per day), and changes nothing: the findings are yours to apply. On
|
|
575
|
+
rhylthyme.com the same review runs automatically after every import when you
|
|
576
|
+
are signed in.
|
|
566
577
|
|
|
567
578
|
### `rhylthyme render`
|
|
568
579
|
|
|
@@ -501,8 +501,17 @@ rhylthyme search "pad thai" -i spoonacular # then impo
|
|
|
501
501
|
```
|
|
502
502
|
|
|
503
503
|
**Options:** `-i IMPORTER` (default: chosen from the URL), `-o PATH`,
|
|
504
|
-
`--stdout`, `--publish` (and `--open`), `--no-validate`. `-` reads
|
|
505
|
-
source from stdin (with `-i`).
|
|
504
|
+
`--stdout`, `--publish` (and `--open`), `--no-validate`, `--review`. `-` reads
|
|
505
|
+
pasted source from stdin (with `-i`).
|
|
506
|
+
|
|
507
|
+
`--review` sends the import and its source to a model on the server (GPT-5.6
|
|
508
|
+
Luna, the model that related steps best in the paper's evaluation) and prints
|
|
509
|
+
what looks wrong: a default duration on "simmer until thick", a step the
|
|
510
|
+
parser dropped, steps chained that the source runs in parallel, a total that
|
|
511
|
+
disagrees with the source. It needs `rhylthyme login`, spends one model call
|
|
512
|
+
(capped per day), and changes nothing: the findings are yours to apply. On
|
|
513
|
+
rhylthyme.com the same review runs automatically after every import when you
|
|
514
|
+
are signed in.
|
|
506
515
|
|
|
507
516
|
### `rhylthyme render`
|
|
508
517
|
|
|
@@ -19,7 +19,7 @@ def read_readme():
|
|
|
19
19
|
|
|
20
20
|
setup(
|
|
21
21
|
name="rhylthyme-cli-runner",
|
|
22
|
-
version="0.2.
|
|
22
|
+
version="0.2.8a0",
|
|
23
23
|
description="CLI runner for Rhylthyme real-time program schedules",
|
|
24
24
|
long_description=read_readme(),
|
|
25
25
|
long_description_content_type="text/markdown",
|
|
@@ -38,9 +38,11 @@ setup(
|
|
|
38
38
|
"jsonschema>=4.0.0",
|
|
39
39
|
"pyyaml>=6.0",
|
|
40
40
|
"colorama>=0.4.0",
|
|
41
|
-
"rhylthyme-spec>=0.2.
|
|
41
|
+
"rhylthyme-spec>=0.2.1a0",
|
|
42
42
|
],
|
|
43
43
|
extras_require={
|
|
44
|
+
# Instrument steps on galago-tools lab instruments (--workcell)
|
|
45
|
+
"galago": ["rhylthyme-galago>=0.1.0a0"],
|
|
44
46
|
"dev": [
|
|
45
47
|
# Testing framework
|
|
46
48
|
"pytest>=6.0.0",
|
{rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/__init__.py
RENAMED
|
@@ -6,7 +6,7 @@ This package provides the command-line interface for running and validating
|
|
|
6
6
|
Rhylthyme real-time program schedules.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
__version__ = "0.2.
|
|
9
|
+
__version__ = "0.2.8a0"
|
|
10
10
|
__author__ = "Rhylthyme Team"
|
|
11
11
|
__description__ = "CLI runner for Rhylthyme real-time program schedules"
|
|
12
12
|
|
{rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/cli.py
RENAMED
|
@@ -105,7 +105,18 @@ def cli(ctx, environments_dir):
|
|
|
105
105
|
is_flag=True,
|
|
106
106
|
help="Enforce all tasks must be defined in resourceConstraints (strict mode)",
|
|
107
107
|
)
|
|
108
|
-
|
|
108
|
+
@click.option(
|
|
109
|
+
"--workcell",
|
|
110
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
111
|
+
default=None,
|
|
112
|
+
help=(
|
|
113
|
+
"Workcell file: check instrument steps against its galago tools "
|
|
114
|
+
"(tool names, types, commands and params)"
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
def validate(
|
|
118
|
+
program_files, schema, environment, verbose, json_output, strict, workcell
|
|
119
|
+
):
|
|
109
120
|
"""
|
|
110
121
|
Validate one or more program files against the schema.
|
|
111
122
|
|
|
@@ -115,6 +126,8 @@ def validate(program_files, schema, environment, verbose, json_output, strict):
|
|
|
115
126
|
Use --json to get machine-readable output for CI or scripting.
|
|
116
127
|
Use --strict to require all tasks used in steps/buffers to be defined in resourceConstraints.
|
|
117
128
|
Use -e/--environment to validate against specific environment constraints.
|
|
129
|
+
Use --workcell to check instrument steps against a lab's galago tools;
|
|
130
|
+
without it, commands are checked against each step's toolType.
|
|
118
131
|
"""
|
|
119
132
|
# Set up environment for validation if specified
|
|
120
133
|
if environment:
|
|
@@ -165,7 +178,7 @@ def validate(program_files, schema, environment, verbose, json_output, strict):
|
|
|
165
178
|
all_valid = True
|
|
166
179
|
for program_file in program_files:
|
|
167
180
|
success = validate_program_file(
|
|
168
|
-
program_file, schema, verbose, json_output, strict
|
|
181
|
+
program_file, schema, verbose, json_output, strict, workcell
|
|
169
182
|
)
|
|
170
183
|
if not success:
|
|
171
184
|
all_valid = False
|
|
@@ -256,6 +269,28 @@ def validate(program_files, schema, environment, verbose, json_output, strict):
|
|
|
256
269
|
"answers, which are what the record stores in context.userTags"
|
|
257
270
|
),
|
|
258
271
|
)
|
|
272
|
+
@click.option(
|
|
273
|
+
"--workcell",
|
|
274
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
275
|
+
default=None,
|
|
276
|
+
help=(
|
|
277
|
+
"Workcell file mapping the program's instrument tools to galago-tools "
|
|
278
|
+
"servers; required for programs with instrument steps"
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
@click.option(
|
|
282
|
+
"--live",
|
|
283
|
+
is_flag=True,
|
|
284
|
+
help=(
|
|
285
|
+
"Run instrument steps on real hardware instead of galago's simulated "
|
|
286
|
+
"mode; shows what will run and asks first"
|
|
287
|
+
),
|
|
288
|
+
)
|
|
289
|
+
@click.option(
|
|
290
|
+
"--confirm-live",
|
|
291
|
+
is_flag=True,
|
|
292
|
+
help="Answer the --live question in advance (for scripts)",
|
|
293
|
+
)
|
|
259
294
|
def run(
|
|
260
295
|
program_file,
|
|
261
296
|
schema,
|
|
@@ -270,6 +305,9 @@ def run(
|
|
|
270
305
|
history_file,
|
|
271
306
|
no_history,
|
|
272
307
|
predict_context,
|
|
308
|
+
workcell,
|
|
309
|
+
live,
|
|
310
|
+
confirm_live,
|
|
273
311
|
):
|
|
274
312
|
"""
|
|
275
313
|
Run a program file with the interactive UI.
|
|
@@ -296,6 +334,11 @@ def run(
|
|
|
296
334
|
--history FILE to read a specific corpus, --no-history to turn it off,
|
|
297
335
|
and --predict-context KEY=VALUE to predict for a context other than the
|
|
298
336
|
factor answers.
|
|
337
|
+
|
|
338
|
+
Programs with instrument steps need --workcell FILE (galago-tools servers,
|
|
339
|
+
via rhylthyme-galago). Tools run in galago's simulated mode unless you pass
|
|
340
|
+
--live, which lists the tools and commands and asks you to type 'live'
|
|
341
|
+
before anything is configured; --confirm-live answers for scripts.
|
|
299
342
|
"""
|
|
300
343
|
run_program(
|
|
301
344
|
program_file,
|
|
@@ -311,6 +354,9 @@ def run(
|
|
|
311
354
|
history_file=history_file,
|
|
312
355
|
use_history=not no_history,
|
|
313
356
|
predict_context=predict_context,
|
|
357
|
+
workcell=workcell,
|
|
358
|
+
live=live,
|
|
359
|
+
confirm_live=confirm_live,
|
|
314
360
|
)
|
|
315
361
|
|
|
316
362
|
|
|
@@ -324,7 +370,16 @@ def run(
|
|
|
324
370
|
@click.option(
|
|
325
371
|
"--verbose", "-v", is_flag=True, help="Show detailed planning information"
|
|
326
372
|
)
|
|
327
|
-
|
|
373
|
+
@click.option(
|
|
374
|
+
"--workcell",
|
|
375
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
376
|
+
default=None,
|
|
377
|
+
help=(
|
|
378
|
+
"Workcell file: estimate instrument steps that have no duration from "
|
|
379
|
+
"their tools' EstimateDuration"
|
|
380
|
+
),
|
|
381
|
+
)
|
|
382
|
+
def plan(input_file, output_file, environment, verbose, workcell):
|
|
328
383
|
"""
|
|
329
384
|
Optimize a program schedule to reduce resource contention.
|
|
330
385
|
|
|
@@ -333,9 +388,17 @@ def plan(input_file, output_file, environment, verbose):
|
|
|
333
388
|
starts to reduce contention at critical junctures.
|
|
334
389
|
|
|
335
390
|
The optimized program is saved to the specified output file.
|
|
391
|
+
|
|
392
|
+
Instrument steps without a duration get an estimated one (flagged in
|
|
393
|
+
metadata.durationEstimate): from the tool with --workcell, else from a
|
|
394
|
+
duration-like command param, else a default.
|
|
336
395
|
"""
|
|
337
396
|
success = plan_program(
|
|
338
|
-
input_file,
|
|
397
|
+
input_file,
|
|
398
|
+
output_file,
|
|
399
|
+
verbose,
|
|
400
|
+
environment_file=environment,
|
|
401
|
+
workcell=workcell,
|
|
339
402
|
)
|
|
340
403
|
if not success:
|
|
341
404
|
sys.exit(1)
|
|
@@ -13,6 +13,8 @@ the events it emits are:
|
|
|
13
13
|
``step_aborted`` {step_id, time, reason}
|
|
14
14
|
``program_paused`` {time, wall_time}
|
|
15
15
|
``program_resumed`` {time, wall_time, paused_seconds}
|
|
16
|
+
``program_aborted`` {time, reason} (reason goes to ``context.abortReason``)
|
|
17
|
+
``instrument_reply`` {step_id, time, ok, code, errorMessage, metadata}
|
|
16
18
|
|
|
17
19
|
All ``time`` values are the runner's program clock (an epoch float that
|
|
18
20
|
advances at ``time_scale``); the record stores them as seconds from the
|
|
@@ -217,6 +219,8 @@ class RunRecorder:
|
|
|
217
219
|
self.context: Dict[str, Any] = self._build_context()
|
|
218
220
|
|
|
219
221
|
self.actuals: Dict[str, Dict[str, Any]] = {}
|
|
222
|
+
# step id -> {tool, command, replies: [{attempt, at, code, ...}]}
|
|
223
|
+
self.instrument_log: Dict[str, Dict[str, Any]] = {}
|
|
220
224
|
self.paused_seconds: Dict[str, float] = {sid: 0.0 for sid in self.step_order}
|
|
221
225
|
self._pause_wall: Optional[float] = None
|
|
222
226
|
self._paused_running: List[str] = []
|
|
@@ -271,6 +275,31 @@ class RunRecorder:
|
|
|
271
275
|
entry.setdefault("start", data["time"])
|
|
272
276
|
entry["end"] = data["time"]
|
|
273
277
|
entry["endedBy"] = "abort"
|
|
278
|
+
elif event_type == "instrument_reply":
|
|
279
|
+
sid = data["step_id"]
|
|
280
|
+
step = getattr(self.runner, "steps", {}).get(sid)
|
|
281
|
+
instrument = (getattr(step, "instrument", None) or {}) if step else {}
|
|
282
|
+
log = self.instrument_log.setdefault(
|
|
283
|
+
sid,
|
|
284
|
+
{
|
|
285
|
+
"tool": str(instrument.get("tool", "")),
|
|
286
|
+
"command": str(instrument.get("command", "")),
|
|
287
|
+
"replies": [],
|
|
288
|
+
},
|
|
289
|
+
)
|
|
290
|
+
reply: Dict[str, Any] = {
|
|
291
|
+
"attempt": getattr(step, "instrument_attempts", 0)
|
|
292
|
+
or len(log["replies"]) + 1,
|
|
293
|
+
"at": data["time"],
|
|
294
|
+
"code": str(data.get("code", "")),
|
|
295
|
+
}
|
|
296
|
+
if data.get("errorMessage"):
|
|
297
|
+
reply["errorMessage"] = str(data["errorMessage"])
|
|
298
|
+
if data.get("metadata"):
|
|
299
|
+
reply["metadata"] = copy.deepcopy(data["metadata"])
|
|
300
|
+
log["replies"].append(reply)
|
|
301
|
+
elif event_type == "program_aborted":
|
|
302
|
+
self.context["abortReason"] = data.get("reason") or "aborted"
|
|
274
303
|
elif event_type == "program_paused":
|
|
275
304
|
self._pause_wall = data.get("wall_time", time.time())
|
|
276
305
|
self._paused_running = list(getattr(self.runner, "running_steps", []))
|
|
@@ -344,6 +373,13 @@ class RunRecorder:
|
|
|
344
373
|
entry["endedBy"] = actual.get("endedBy", "executor")
|
|
345
374
|
if "triggerFiredAt" in actual:
|
|
346
375
|
entry["triggerFiredAt"] = rel(actual["triggerFiredAt"])
|
|
376
|
+
log = self.instrument_log.get(sid)
|
|
377
|
+
if log:
|
|
378
|
+
entry["instrument"] = {
|
|
379
|
+
"tool": log["tool"],
|
|
380
|
+
"command": log["command"],
|
|
381
|
+
"replies": [dict(r, at=rel(r["at"])) for r in log["replies"]],
|
|
382
|
+
}
|
|
347
383
|
steps.append(entry)
|
|
348
384
|
|
|
349
385
|
started_iso = _iso_utc(start_epoch) if start_epoch is not None else None
|
{rhylthyme_cli_runner-0.2.5a0 → rhylthyme_cli_runner-0.2.8a0}/src/rhylthyme_cli_runner/import_cli.py
RENAMED
|
@@ -74,6 +74,10 @@ def _import_one(importer, source: str, text: Optional[str]):
|
|
|
74
74
|
if hasattr(importer, "allow_local_files"):
|
|
75
75
|
importer.allow_local_files = True
|
|
76
76
|
return importer.import_from_url(str(path))
|
|
77
|
+
if not source.startswith(("http://", "https://")) and (
|
|
78
|
+
path.suffix or "/" in source
|
|
79
|
+
):
|
|
80
|
+
raise click.ClickException(f"No such file: {source}")
|
|
77
81
|
return importer.import_from_url(source)
|
|
78
82
|
|
|
79
83
|
|
|
@@ -130,8 +134,21 @@ def _validation(program: dict):
|
|
|
130
134
|
@click.option(
|
|
131
135
|
"--no-validate", is_flag=True, help="Skip validation of the imported program."
|
|
132
136
|
)
|
|
137
|
+
@click.option(
|
|
138
|
+
"--review",
|
|
139
|
+
"do_review",
|
|
140
|
+
is_flag=True,
|
|
141
|
+
help="Have a model read the import against its source and report what looks wrong (needs `rhylthyme login`).",
|
|
142
|
+
)
|
|
133
143
|
def import_command(
|
|
134
|
-
source,
|
|
144
|
+
source,
|
|
145
|
+
importer_name,
|
|
146
|
+
output,
|
|
147
|
+
to_stdout,
|
|
148
|
+
do_publish,
|
|
149
|
+
open_url,
|
|
150
|
+
no_validate,
|
|
151
|
+
do_review,
|
|
135
152
|
):
|
|
136
153
|
"""Import a recipe, protocol or deck as a program.
|
|
137
154
|
|
|
@@ -141,6 +158,7 @@ def import_command(
|
|
|
141
158
|
.cook or .pptx file. `-` reads the source text from stdin (with -i).
|
|
142
159
|
rhylthyme import https://www.seriouseats.com/the-best-chili-recipe
|
|
143
160
|
rhylthyme import 52772 -i themealdb --publish
|
|
161
|
+
rhylthyme import recipe.cook -i cooklang --review # a second opinion on durations, order, gaps
|
|
144
162
|
rhylthyme import https://raw.githubusercontent.com/Opentrons/Protocols/develop/protocols/007992/rna_isolation.ot2.apiv2.py
|
|
145
163
|
rhylthyme importers # what is installed
|
|
146
164
|
"""
|
|
@@ -153,8 +171,22 @@ def import_command(
|
|
|
153
171
|
)
|
|
154
172
|
text = sys.stdin.read()
|
|
155
173
|
importer = _pick(registry, source, importer_name)
|
|
174
|
+
token = None
|
|
175
|
+
if do_review:
|
|
176
|
+
# Before any work: the review needs an account, say so now.
|
|
177
|
+
from .remote import auth
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
token = auth.access_token()
|
|
181
|
+
except auth.AuthError as e:
|
|
182
|
+
raise click.ClickException(
|
|
183
|
+
f"{e} (--review sends the import to a model on the server, which needs your account.)"
|
|
184
|
+
)
|
|
156
185
|
click.echo(f"Importing with {importer.name}…", err=True)
|
|
157
186
|
result = _import_one(importer, source, text)
|
|
187
|
+
source_text = (
|
|
188
|
+
text if text is not None else _source_text_for_review(importer, source)
|
|
189
|
+
)
|
|
158
190
|
if not result.success:
|
|
159
191
|
raise click.ClickException(result.error or "import failed")
|
|
160
192
|
program = result.program
|
|
@@ -163,9 +195,10 @@ def import_command(
|
|
|
163
195
|
v = _validation(program)
|
|
164
196
|
errors = v.get("errors") or v.get("logic_errors") or []
|
|
165
197
|
if not v.get("is_valid", v.get("valid", True)):
|
|
166
|
-
for
|
|
198
|
+
for finding in errors[:8]:
|
|
167
199
|
click.echo(
|
|
168
|
-
f" ✗ {
|
|
200
|
+
f" ✗ {finding if isinstance(finding, str) else finding.get('message', finding)}",
|
|
201
|
+
err=True,
|
|
169
202
|
)
|
|
170
203
|
raise click.ClickException(
|
|
171
204
|
"The imported program does not validate; not published. Re-run with --no-validate to keep it anyway."
|
|
@@ -187,6 +220,14 @@ def import_command(
|
|
|
187
220
|
target.write_text(payload + "\n")
|
|
188
221
|
click.echo(f"Saved {target}", err=True)
|
|
189
222
|
|
|
223
|
+
if do_review:
|
|
224
|
+
_review(
|
|
225
|
+
program,
|
|
226
|
+
token,
|
|
227
|
+
source_text=source_text,
|
|
228
|
+
source_url=source if source.startswith(("http://", "https://")) else None,
|
|
229
|
+
)
|
|
230
|
+
|
|
190
231
|
if do_publish:
|
|
191
232
|
from .remote.cli import publish as publish_command
|
|
192
233
|
|
|
@@ -208,6 +249,67 @@ def import_command(
|
|
|
208
249
|
Path(tmp_path).unlink(missing_ok=True)
|
|
209
250
|
|
|
210
251
|
|
|
252
|
+
def _source_text_for_review(importer, source: str) -> Optional[str]:
|
|
253
|
+
"""The text the import came from, when the CLI has it: a local file, or
|
|
254
|
+
a URL the CLI fetched for a text importer. Recipe-site imports do not
|
|
255
|
+
keep the page, so the reviewer gets the URL instead."""
|
|
256
|
+
path = Path(source)
|
|
257
|
+
if path.is_file() and path.suffix.lower() in (".cook", ".py", ".txt", ".md"):
|
|
258
|
+
try:
|
|
259
|
+
return path.read_text()
|
|
260
|
+
except OSError:
|
|
261
|
+
return None
|
|
262
|
+
if (
|
|
263
|
+
source.startswith(("http://", "https://"))
|
|
264
|
+
and hasattr(importer, "import_from_source")
|
|
265
|
+
and not getattr(importer, "supported_domains", None)
|
|
266
|
+
):
|
|
267
|
+
try:
|
|
268
|
+
from .remote.cli import _fetch_bytes
|
|
269
|
+
|
|
270
|
+
return _fetch_bytes(source).decode("utf-8")
|
|
271
|
+
except Exception: # noqa: BLE001
|
|
272
|
+
return None
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _review(
|
|
277
|
+
program: dict, token: str, *, source_text: Optional[str], source_url: Optional[str]
|
|
278
|
+
) -> None:
|
|
279
|
+
from .remote import mcp_client as MC
|
|
280
|
+
|
|
281
|
+
click.echo("Reviewing…", err=True)
|
|
282
|
+
client = MC.McpClient(url=MC.endpoint_for(None))
|
|
283
|
+
args = {"program": program, "token": token}
|
|
284
|
+
if source_text:
|
|
285
|
+
args["source_text"] = source_text
|
|
286
|
+
elif source_url:
|
|
287
|
+
args["source_url"] = source_url
|
|
288
|
+
try:
|
|
289
|
+
result = client.call_tool("review_program", args, timeout=150)
|
|
290
|
+
except MC.ToolError as e:
|
|
291
|
+
raise click.ClickException(f"Review failed: {e.text}")
|
|
292
|
+
except MC.McpError as e:
|
|
293
|
+
raise click.ClickException(f"Review failed: {e}")
|
|
294
|
+
review = result.structured or {}
|
|
295
|
+
findings = review.get("findings") or []
|
|
296
|
+
mark = {"error": "✗", "warning": "!", "note": "·"}
|
|
297
|
+
click.echo("")
|
|
298
|
+
click.echo(
|
|
299
|
+
review.get("summary")
|
|
300
|
+
or ("Review: no problems found." if not findings else "Review:")
|
|
301
|
+
)
|
|
302
|
+
for f in findings:
|
|
303
|
+
where = f" [{f['stepId']}]" if f.get("stepId") else ""
|
|
304
|
+
click.echo(f" {mark.get(f.get('severity'), '·')}{where} {f.get('message')}")
|
|
305
|
+
if f.get("suggestion"):
|
|
306
|
+
click.echo(f" → {f['suggestion']}")
|
|
307
|
+
if findings:
|
|
308
|
+
click.echo(
|
|
309
|
+
"\nThe file is unchanged; edit it and `rhylthyme validate` again.", err=True
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
211
313
|
@click.command("search")
|
|
212
314
|
@click.argument("query")
|
|
213
315
|
@click.option(
|