rhylthyme-cli-runner 0.2.3a0__tar.gz → 0.2.4a0__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.3a0/src/rhylthyme_cli_runner.egg-info → rhylthyme_cli_runner-0.2.4a0}/PKG-INFO +23 -2
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/README.md +22 -1
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/setup.py +1 -1
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/__init__.py +1 -1
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/cli.py +10 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/render.py +9 -0
- rhylthyme_cli_runner-0.2.4a0/src/rhylthyme_cli_runner/import_cli.py +250 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0/src/rhylthyme_cli_runner.egg-info}/PKG-INFO +23 -2
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner.egg-info/SOURCES.txt +2 -0
- rhylthyme_cli_runner-0.2.4a0/tests/test_import_cli.py +228 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/LICENSE +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/pyproject.toml +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/setup.cfg +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/environment_icons.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/environment_loader.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/environment_schemas.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/compare.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/gold.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/harness.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/llm.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/matcher.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/metrics.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/patterns/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/patterns/authoring_guide.md +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/patterns/baseline.md +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/patterns/baseline.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/patterns/extract.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/patterns/four_turn.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/report.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/expand_replicates.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/calibrate.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/calibrate_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/evaluate.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/evaluate_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/factors.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/hash.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/predict.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/recorder.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/replay.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/report.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/report_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/store.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/synth.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/history/usable.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/instance_checks.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/program_planner.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/program_runner.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/remote/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/remote/auth.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/remote/checks.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/remote/cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/remote/mcp_client.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/validate_program.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner.egg-info/dependency_links.txt +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner.egg-info/entry_points.txt +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner.egg-info/requires.txt +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner.egg-info/top_level.txt +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_calibrate.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_eval_baseline.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_eval_harness.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_eval_providers.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_eval_scorer.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_evaluate.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_example_validation.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_examples_integration.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_expand_replicates.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_hash_parity.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_history_report.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_mcp_checks.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_planner_time_strings.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_predict.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_program_runner.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_remote.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_replay.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_run_recorder.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_skill.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_synth.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_usable.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_validate_examples_ci.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_validate_program.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.4a0
|
|
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
|
|
@@ -145,11 +145,12 @@ is the **server an AI assistant talks to**.
|
|
|
145
145
|
| Language | JavaScript (Node 20+) | Python 3.12+ |
|
|
146
146
|
| Input | A program the assistant builds in conversation | A program file on disk (JSON or YAML) |
|
|
147
147
|
| Validate a program | `validate_program` | `rhylthyme validate` (works offline) |
|
|
148
|
+
| Import a recipe or protocol | `import_from_source` | `rhylthyme import` |
|
|
148
149
|
| Timing, conflicts, deadlines | `analyze_schedule` | `rhylthyme analyze` (asks the server) |
|
|
149
150
|
| Publish a live timeline | `visualize_schedule` | `rhylthyme publish` (asks the server) |
|
|
150
151
|
| Run a schedule with timers | no: it hands back a link to the web timeline | `rhylthyme run`, an interactive terminal runner |
|
|
151
152
|
| Recorded runs, calibration | reads runs saved to an account | records runs locally; `rhylthyme runs`, `rhylthyme calibrate` |
|
|
152
|
-
| Catalog search,
|
|
153
|
+
| Catalog search, account library | yes | no |
|
|
153
154
|
| Also in the repository | the `rhylthyme-mcp` PyPI package (a stdio bridge to the hosted server), the Claude plugin marketplace | the Claude skill's source, the prompt-evaluation harness and its results, `rhylthyme mcp-test` |
|
|
154
155
|
|
|
155
156
|
How they fit together: this tool is one of the MCP server's clients.
|
|
@@ -542,6 +543,26 @@ A published timeline is reachable by anyone who has the link.
|
|
|
542
543
|
- `--image PATH`: also save a PNG of the timeline
|
|
543
544
|
- `--open`: open it in a browser
|
|
544
545
|
|
|
546
|
+
### `rhylthyme import`
|
|
547
|
+
|
|
548
|
+
Imports a recipe, protocol or slide deck as a program, validates it, and
|
|
549
|
+
writes `<programId>.json`. Needs `rhylthyme-importers` (included in
|
|
550
|
+
`pip install rhylthyme`).
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
rhylthyme import https://www.seriouseats.com/the-best-chili-recipe # any of ~580 recipe sites
|
|
554
|
+
rhylthyme import 52772 -i themealdb --publish # a TheMealDB id, straight to a live timeline
|
|
555
|
+
rhylthyme import https://www.protocols.io/view/western-blot-... # needs PROTOCOLS_IO_TOKEN
|
|
556
|
+
rhylthyme import protocol.py -o protocol.json # an Opentrons file
|
|
557
|
+
rhylthyme import "Neapolitan Pizza.cook" -i cooklang
|
|
558
|
+
rhylthyme importers # what is installed
|
|
559
|
+
rhylthyme search "pad thai" -i spoonacular # then import a hit's URL
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**Options:** `-i IMPORTER` (default: chosen from the URL), `-o PATH`,
|
|
563
|
+
`--stdout`, `--publish` (and `--open`), `--no-validate`. `-` reads pasted
|
|
564
|
+
source from stdin (with `-i`).
|
|
565
|
+
|
|
545
566
|
### `rhylthyme plan`
|
|
546
567
|
|
|
547
568
|
An older stagger heuristic. It reads the pre-0.2 program format and leaves
|
|
@@ -85,11 +85,12 @@ is the **server an AI assistant talks to**.
|
|
|
85
85
|
| Language | JavaScript (Node 20+) | Python 3.12+ |
|
|
86
86
|
| Input | A program the assistant builds in conversation | A program file on disk (JSON or YAML) |
|
|
87
87
|
| Validate a program | `validate_program` | `rhylthyme validate` (works offline) |
|
|
88
|
+
| Import a recipe or protocol | `import_from_source` | `rhylthyme import` |
|
|
88
89
|
| Timing, conflicts, deadlines | `analyze_schedule` | `rhylthyme analyze` (asks the server) |
|
|
89
90
|
| Publish a live timeline | `visualize_schedule` | `rhylthyme publish` (asks the server) |
|
|
90
91
|
| Run a schedule with timers | no: it hands back a link to the web timeline | `rhylthyme run`, an interactive terminal runner |
|
|
91
92
|
| Recorded runs, calibration | reads runs saved to an account | records runs locally; `rhylthyme runs`, `rhylthyme calibrate` |
|
|
92
|
-
| Catalog search,
|
|
93
|
+
| Catalog search, account library | yes | no |
|
|
93
94
|
| Also in the repository | the `rhylthyme-mcp` PyPI package (a stdio bridge to the hosted server), the Claude plugin marketplace | the Claude skill's source, the prompt-evaluation harness and its results, `rhylthyme mcp-test` |
|
|
94
95
|
|
|
95
96
|
How they fit together: this tool is one of the MCP server's clients.
|
|
@@ -482,6 +483,26 @@ A published timeline is reachable by anyone who has the link.
|
|
|
482
483
|
- `--image PATH`: also save a PNG of the timeline
|
|
483
484
|
- `--open`: open it in a browser
|
|
484
485
|
|
|
486
|
+
### `rhylthyme import`
|
|
487
|
+
|
|
488
|
+
Imports a recipe, protocol or slide deck as a program, validates it, and
|
|
489
|
+
writes `<programId>.json`. Needs `rhylthyme-importers` (included in
|
|
490
|
+
`pip install rhylthyme`).
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
rhylthyme import https://www.seriouseats.com/the-best-chili-recipe # any of ~580 recipe sites
|
|
494
|
+
rhylthyme import 52772 -i themealdb --publish # a TheMealDB id, straight to a live timeline
|
|
495
|
+
rhylthyme import https://www.protocols.io/view/western-blot-... # needs PROTOCOLS_IO_TOKEN
|
|
496
|
+
rhylthyme import protocol.py -o protocol.json # an Opentrons file
|
|
497
|
+
rhylthyme import "Neapolitan Pizza.cook" -i cooklang
|
|
498
|
+
rhylthyme importers # what is installed
|
|
499
|
+
rhylthyme search "pad thai" -i spoonacular # then import a hit's URL
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Options:** `-i IMPORTER` (default: chosen from the URL), `-o PATH`,
|
|
503
|
+
`--stdout`, `--publish` (and `--open`), `--no-validate`. `-` reads pasted
|
|
504
|
+
source from stdin (with `-i`).
|
|
505
|
+
|
|
485
506
|
### `rhylthyme plan`
|
|
486
507
|
|
|
487
508
|
An older stagger heuristic. It reads the pre-0.2 program format and leaves
|
|
@@ -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.4a0",
|
|
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",
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/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.4a0"
|
|
10
10
|
__author__ = "Rhylthyme Team"
|
|
11
11
|
__description__ = "CLI runner for Rhylthyme real-time program schedules"
|
|
12
12
|
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/cli.py
RENAMED
|
@@ -544,6 +544,16 @@ def _register_remote_commands():
|
|
|
544
544
|
_register_remote_commands()
|
|
545
545
|
|
|
546
546
|
|
|
547
|
+
def _register_import_commands():
|
|
548
|
+
"""Attach `import`, `search`, `importers` (rhylthyme_cli_runner.import_cli)."""
|
|
549
|
+
from .import_cli import register as register_import
|
|
550
|
+
|
|
551
|
+
register_import(cli)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
_register_import_commands()
|
|
555
|
+
|
|
556
|
+
|
|
547
557
|
def main():
|
|
548
558
|
"""Entry point for the CLI."""
|
|
549
559
|
cli()
|
|
@@ -76,6 +76,15 @@ def find_renderer() -> Optional[Path]:
|
|
|
76
76
|
candidate = Path(explicit).expanduser()
|
|
77
77
|
# Absolute: `node -e` has no script directory to resolve against.
|
|
78
78
|
return candidate.resolve() if candidate.is_file() else None
|
|
79
|
+
# The pip package (rhylthyme-timeline) carries the renderer.
|
|
80
|
+
try:
|
|
81
|
+
from rhylthyme_timeline import renderer_path
|
|
82
|
+
|
|
83
|
+
packaged = renderer_path()
|
|
84
|
+
if packaged.is_file():
|
|
85
|
+
return packaged
|
|
86
|
+
except ImportError:
|
|
87
|
+
pass
|
|
79
88
|
here = Path(__file__).resolve()
|
|
80
89
|
for parent in here.parents:
|
|
81
90
|
for relative in _RENDERER_PATHS:
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""``rhylthyme import`` and ``rhylthyme search``: the importers from
|
|
2
|
+
rhylthyme-importers, reached from the one command people already have.
|
|
3
|
+
|
|
4
|
+
``rhylthyme-import`` (the importers' own command) still exists; this is the
|
|
5
|
+
same work with the CLI's validation and publishing on the end of it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
INSTALL_HINT = (
|
|
19
|
+
"The importers are a separate package: pip install rhylthyme-importers "
|
|
20
|
+
"(or pip install rhylthyme, which includes them)."
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _registry():
|
|
25
|
+
try:
|
|
26
|
+
from rhylthyme_importers import ImporterRegistry
|
|
27
|
+
except ImportError:
|
|
28
|
+
raise click.ClickException(INSTALL_HINT)
|
|
29
|
+
return ImporterRegistry
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _pick(registry, source: str, importer_name: Optional[str]):
|
|
33
|
+
if importer_name:
|
|
34
|
+
importer = registry.get(importer_name)
|
|
35
|
+
if importer is None:
|
|
36
|
+
names = ", ".join(sorted(i["name"] for i in registry.list_importers()))
|
|
37
|
+
raise click.ClickException(
|
|
38
|
+
f"Unknown importer '{importer_name}'. Installed: {names}."
|
|
39
|
+
)
|
|
40
|
+
return importer
|
|
41
|
+
importer = registry.find_for_url(source)
|
|
42
|
+
if importer is None:
|
|
43
|
+
names = ", ".join(sorted(i["name"] for i in registry.list_importers()))
|
|
44
|
+
raise click.ClickException(
|
|
45
|
+
f"No importer recognises {source!r}. Pass -i with one of: {names}. "
|
|
46
|
+
"For a file on disk, name its importer (-i opentrons, -i cooklang, -i slidedeck)."
|
|
47
|
+
)
|
|
48
|
+
return importer
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _import_one(importer, source: str, text: Optional[str]):
|
|
52
|
+
if text is not None and hasattr(importer, "import_from_source"):
|
|
53
|
+
return importer.import_from_source(text)
|
|
54
|
+
if text is not None and hasattr(importer, "import_from_content"):
|
|
55
|
+
return importer.import_from_content(
|
|
56
|
+
text, source_name=Path(source).stem if source != "-" else "recipe"
|
|
57
|
+
)
|
|
58
|
+
path = Path(source)
|
|
59
|
+
if path.is_file():
|
|
60
|
+
# A local file was named explicitly; that is the command line's job
|
|
61
|
+
# to allow (a server never does this).
|
|
62
|
+
if hasattr(importer, "allow_local_files"):
|
|
63
|
+
importer.allow_local_files = True
|
|
64
|
+
return importer.import_from_url(str(path))
|
|
65
|
+
return importer.import_from_url(source)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _validation(program: dict):
|
|
69
|
+
from .validate_program import validate_program_file_structured
|
|
70
|
+
|
|
71
|
+
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as tmp:
|
|
72
|
+
json.dump(program, tmp)
|
|
73
|
+
tmp_path = tmp.name
|
|
74
|
+
try:
|
|
75
|
+
from .cli import _default_schema_path
|
|
76
|
+
|
|
77
|
+
return validate_program_file_structured(
|
|
78
|
+
tmp_path, schema_file=_default_schema_path()
|
|
79
|
+
)
|
|
80
|
+
finally:
|
|
81
|
+
Path(tmp_path).unlink(missing_ok=True)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@click.command("import")
|
|
85
|
+
@click.argument("source", metavar="URL_OR_ID_OR_FILE")
|
|
86
|
+
@click.option(
|
|
87
|
+
"-i",
|
|
88
|
+
"--importer",
|
|
89
|
+
"importer_name",
|
|
90
|
+
default=None,
|
|
91
|
+
help="Which importer to use (default: chosen from the URL).",
|
|
92
|
+
)
|
|
93
|
+
@click.option(
|
|
94
|
+
"-o",
|
|
95
|
+
"--output",
|
|
96
|
+
type=click.Path(dir_okay=False),
|
|
97
|
+
default=None,
|
|
98
|
+
help="Write the program here (default: <name>.json in the current directory).",
|
|
99
|
+
)
|
|
100
|
+
@click.option(
|
|
101
|
+
"--stdout",
|
|
102
|
+
"to_stdout",
|
|
103
|
+
is_flag=True,
|
|
104
|
+
help="Print the program instead of writing a file.",
|
|
105
|
+
)
|
|
106
|
+
@click.option(
|
|
107
|
+
"--publish",
|
|
108
|
+
"do_publish",
|
|
109
|
+
is_flag=True,
|
|
110
|
+
help="Also publish it as a live timeline and print the URL.",
|
|
111
|
+
)
|
|
112
|
+
@click.option(
|
|
113
|
+
"--open",
|
|
114
|
+
"open_url",
|
|
115
|
+
is_flag=True,
|
|
116
|
+
help="With --publish: open the timeline in a browser.",
|
|
117
|
+
)
|
|
118
|
+
@click.option(
|
|
119
|
+
"--no-validate", is_flag=True, help="Skip validation of the imported program."
|
|
120
|
+
)
|
|
121
|
+
def import_command(
|
|
122
|
+
source, importer_name, output, to_stdout, do_publish, open_url, no_validate
|
|
123
|
+
):
|
|
124
|
+
"""Import a recipe, protocol or deck as a program.
|
|
125
|
+
|
|
126
|
+
\b
|
|
127
|
+
URL_OR_ID_OR_FILE is a recipe-site or protocols.io URL, a TheMealDB or
|
|
128
|
+
Spoonacular id, a .cook or GitHub .cook URL, or a local .py (Opentrons),
|
|
129
|
+
.cook or .pptx file. `-` reads the source text from stdin (with -i).
|
|
130
|
+
rhylthyme import https://www.seriouseats.com/the-best-chili-recipe
|
|
131
|
+
rhylthyme import 52772 -i themealdb --publish
|
|
132
|
+
rhylthyme import protocol.py -o protocol.json
|
|
133
|
+
rhylthyme importers # what is installed
|
|
134
|
+
"""
|
|
135
|
+
registry = _registry()
|
|
136
|
+
text = None
|
|
137
|
+
if source == "-":
|
|
138
|
+
if not importer_name:
|
|
139
|
+
raise click.ClickException(
|
|
140
|
+
"Reading from stdin needs -i to say which importer."
|
|
141
|
+
)
|
|
142
|
+
text = sys.stdin.read()
|
|
143
|
+
importer = _pick(registry, source, importer_name)
|
|
144
|
+
click.echo(f"Importing with {importer.name}…", err=True)
|
|
145
|
+
result = _import_one(importer, source, text)
|
|
146
|
+
if not result.success:
|
|
147
|
+
raise click.ClickException(result.error or "import failed")
|
|
148
|
+
program = result.program
|
|
149
|
+
|
|
150
|
+
if not no_validate:
|
|
151
|
+
v = _validation(program)
|
|
152
|
+
errors = v.get("errors") or v.get("logic_errors") or []
|
|
153
|
+
if not v.get("is_valid", v.get("valid", True)):
|
|
154
|
+
for e in errors[:8]:
|
|
155
|
+
click.echo(
|
|
156
|
+
f" ✗ {e if isinstance(e, str) else e.get('message', e)}", err=True
|
|
157
|
+
)
|
|
158
|
+
raise click.ClickException(
|
|
159
|
+
"The imported program does not validate; not published. Re-run with --no-validate to keep it anyway."
|
|
160
|
+
)
|
|
161
|
+
for w in (v.get("warnings") or [])[:5]:
|
|
162
|
+
click.echo(
|
|
163
|
+
f" ! {w if isinstance(w, str) else w.get('message', w)}", err=True
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
payload = json.dumps(program, indent=2, ensure_ascii=False)
|
|
167
|
+
if to_stdout:
|
|
168
|
+
click.echo(payload)
|
|
169
|
+
else:
|
|
170
|
+
target = (
|
|
171
|
+
Path(output)
|
|
172
|
+
if output
|
|
173
|
+
else Path(f"{program.get('programId') or 'program'}.json")
|
|
174
|
+
)
|
|
175
|
+
target.write_text(payload + "\n")
|
|
176
|
+
click.echo(f"Saved {target}", err=True)
|
|
177
|
+
|
|
178
|
+
if do_publish:
|
|
179
|
+
from .remote.cli import publish as publish_command
|
|
180
|
+
|
|
181
|
+
ctx = click.get_current_context()
|
|
182
|
+
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as tmp:
|
|
183
|
+
tmp.write(payload)
|
|
184
|
+
tmp_path = tmp.name
|
|
185
|
+
try:
|
|
186
|
+
ctx.invoke(
|
|
187
|
+
publish_command,
|
|
188
|
+
program_file=tmp_path,
|
|
189
|
+
environment=None,
|
|
190
|
+
open_url=open_url,
|
|
191
|
+
json_output=False,
|
|
192
|
+
quiet=True,
|
|
193
|
+
image_path=None,
|
|
194
|
+
)
|
|
195
|
+
finally:
|
|
196
|
+
Path(tmp_path).unlink(missing_ok=True)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@click.command("search")
|
|
200
|
+
@click.argument("query")
|
|
201
|
+
@click.option(
|
|
202
|
+
"-i",
|
|
203
|
+
"--importer",
|
|
204
|
+
"importer_name",
|
|
205
|
+
default="themealdb",
|
|
206
|
+
show_default=True,
|
|
207
|
+
help="Which catalogue to search.",
|
|
208
|
+
)
|
|
209
|
+
@click.option("--json", "json_output", is_flag=True)
|
|
210
|
+
def search_command(query, importer_name, json_output):
|
|
211
|
+
"""Search a recipe or protocol source; import a hit with `rhylthyme import <url>`."""
|
|
212
|
+
registry = _registry()
|
|
213
|
+
importer = registry.get(importer_name)
|
|
214
|
+
if importer is None:
|
|
215
|
+
names = ", ".join(sorted(i["name"] for i in registry.list_importers()))
|
|
216
|
+
raise click.ClickException(
|
|
217
|
+
f"Unknown importer '{importer_name}'. Installed: {names}."
|
|
218
|
+
)
|
|
219
|
+
hits = importer.search(query)
|
|
220
|
+
if json_output:
|
|
221
|
+
click.echo(json.dumps(hits, indent=2, ensure_ascii=False))
|
|
222
|
+
return
|
|
223
|
+
if not hits:
|
|
224
|
+
click.echo("No results.")
|
|
225
|
+
return
|
|
226
|
+
for h in hits:
|
|
227
|
+
click.echo(f"{h.get('name')}\n {h.get('url')}")
|
|
228
|
+
if h.get("description"):
|
|
229
|
+
click.echo(f" {h['description']}")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@click.command("importers")
|
|
233
|
+
def importers_command():
|
|
234
|
+
"""List the installed importers."""
|
|
235
|
+
registry = _registry()
|
|
236
|
+
for info in registry.list_importers():
|
|
237
|
+
domains = info.get("supported_domains") or []
|
|
238
|
+
where = (
|
|
239
|
+
", ".join(domains[:4])
|
|
240
|
+
+ (f", … ({len(domains)} sites)" if len(domains) > 4 else "")
|
|
241
|
+
if domains
|
|
242
|
+
else "files, ids or pasted text"
|
|
243
|
+
)
|
|
244
|
+
click.echo(f"{info['name']:18} {info['description']} [{where}]")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def register(cli_group: click.Group) -> None:
|
|
248
|
+
cli_group.add_command(import_command)
|
|
249
|
+
cli_group.add_command(search_command)
|
|
250
|
+
cli_group.add_command(importers_command)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rhylthyme-cli-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4a0
|
|
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
|
|
@@ -145,11 +145,12 @@ is the **server an AI assistant talks to**.
|
|
|
145
145
|
| Language | JavaScript (Node 20+) | Python 3.12+ |
|
|
146
146
|
| Input | A program the assistant builds in conversation | A program file on disk (JSON or YAML) |
|
|
147
147
|
| Validate a program | `validate_program` | `rhylthyme validate` (works offline) |
|
|
148
|
+
| Import a recipe or protocol | `import_from_source` | `rhylthyme import` |
|
|
148
149
|
| Timing, conflicts, deadlines | `analyze_schedule` | `rhylthyme analyze` (asks the server) |
|
|
149
150
|
| Publish a live timeline | `visualize_schedule` | `rhylthyme publish` (asks the server) |
|
|
150
151
|
| Run a schedule with timers | no: it hands back a link to the web timeline | `rhylthyme run`, an interactive terminal runner |
|
|
151
152
|
| Recorded runs, calibration | reads runs saved to an account | records runs locally; `rhylthyme runs`, `rhylthyme calibrate` |
|
|
152
|
-
| Catalog search,
|
|
153
|
+
| Catalog search, account library | yes | no |
|
|
153
154
|
| Also in the repository | the `rhylthyme-mcp` PyPI package (a stdio bridge to the hosted server), the Claude plugin marketplace | the Claude skill's source, the prompt-evaluation harness and its results, `rhylthyme mcp-test` |
|
|
154
155
|
|
|
155
156
|
How they fit together: this tool is one of the MCP server's clients.
|
|
@@ -542,6 +543,26 @@ A published timeline is reachable by anyone who has the link.
|
|
|
542
543
|
- `--image PATH`: also save a PNG of the timeline
|
|
543
544
|
- `--open`: open it in a browser
|
|
544
545
|
|
|
546
|
+
### `rhylthyme import`
|
|
547
|
+
|
|
548
|
+
Imports a recipe, protocol or slide deck as a program, validates it, and
|
|
549
|
+
writes `<programId>.json`. Needs `rhylthyme-importers` (included in
|
|
550
|
+
`pip install rhylthyme`).
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
rhylthyme import https://www.seriouseats.com/the-best-chili-recipe # any of ~580 recipe sites
|
|
554
|
+
rhylthyme import 52772 -i themealdb --publish # a TheMealDB id, straight to a live timeline
|
|
555
|
+
rhylthyme import https://www.protocols.io/view/western-blot-... # needs PROTOCOLS_IO_TOKEN
|
|
556
|
+
rhylthyme import protocol.py -o protocol.json # an Opentrons file
|
|
557
|
+
rhylthyme import "Neapolitan Pizza.cook" -i cooklang
|
|
558
|
+
rhylthyme importers # what is installed
|
|
559
|
+
rhylthyme search "pad thai" -i spoonacular # then import a hit's URL
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**Options:** `-i IMPORTER` (default: chosen from the URL), `-o PATH`,
|
|
563
|
+
`--stdout`, `--publish` (and `--open`), `--no-validate`. `-` reads pasted
|
|
564
|
+
source from stdin (with `-i`).
|
|
565
|
+
|
|
545
566
|
### `rhylthyme plan`
|
|
546
567
|
|
|
547
568
|
An older stagger heuristic. It reads the pre-0.2 program format and leaves
|
|
@@ -8,6 +8,7 @@ src/rhylthyme_cli_runner/environment_icons.py
|
|
|
8
8
|
src/rhylthyme_cli_runner/environment_loader.py
|
|
9
9
|
src/rhylthyme_cli_runner/environment_schemas.py
|
|
10
10
|
src/rhylthyme_cli_runner/expand_replicates.py
|
|
11
|
+
src/rhylthyme_cli_runner/import_cli.py
|
|
11
12
|
src/rhylthyme_cli_runner/instance_checks.py
|
|
12
13
|
src/rhylthyme_cli_runner/program_planner.py
|
|
13
14
|
src/rhylthyme_cli_runner/program_runner.py
|
|
@@ -67,6 +68,7 @@ tests/test_examples_integration.py
|
|
|
67
68
|
tests/test_expand_replicates.py
|
|
68
69
|
tests/test_hash_parity.py
|
|
69
70
|
tests/test_history_report.py
|
|
71
|
+
tests/test_import_cli.py
|
|
70
72
|
tests/test_mcp_checks.py
|
|
71
73
|
tests/test_planner_time_strings.py
|
|
72
74
|
tests/test_predict.py
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""`rhylthyme import`, `search` and `importers`, with a fake importer registry
|
|
2
|
+
so nothing is fetched."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
import types
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from click.testing import CliRunner
|
|
10
|
+
|
|
11
|
+
from rhylthyme_cli_runner.cli import cli
|
|
12
|
+
|
|
13
|
+
PROGRAM = {
|
|
14
|
+
"schemaVersion": "0.3.0-alpha",
|
|
15
|
+
"programId": "fake-dinner",
|
|
16
|
+
"name": "Fake dinner",
|
|
17
|
+
"environmentType": "kitchen",
|
|
18
|
+
"tracks": [
|
|
19
|
+
{
|
|
20
|
+
"trackId": "a",
|
|
21
|
+
"name": "A",
|
|
22
|
+
"steps": [
|
|
23
|
+
{
|
|
24
|
+
"stepId": "s1",
|
|
25
|
+
"name": "Cook",
|
|
26
|
+
"task": "stove",
|
|
27
|
+
"duration": {"type": "fixed", "seconds": 600},
|
|
28
|
+
"startTrigger": {"type": "programStart"},
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"resourceConstraints": [{"task": "stove", "maxConcurrent": 1}],
|
|
34
|
+
}
|
|
35
|
+
BAD = dict(
|
|
36
|
+
PROGRAM,
|
|
37
|
+
tracks=[
|
|
38
|
+
{
|
|
39
|
+
"trackId": "a",
|
|
40
|
+
"name": "A",
|
|
41
|
+
"steps": [
|
|
42
|
+
{
|
|
43
|
+
"stepId": "s1",
|
|
44
|
+
"name": "Cook",
|
|
45
|
+
"task": "stove",
|
|
46
|
+
"duration": {"type": "fixed", "seconds": 600},
|
|
47
|
+
"startTrigger": {"type": "afterStep", "stepId": "nope"},
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Result:
|
|
56
|
+
def __init__(self, program=None, error=None):
|
|
57
|
+
self.success = program is not None
|
|
58
|
+
self.program = program
|
|
59
|
+
self.error = error
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class FakeImporter:
|
|
63
|
+
name = "fake"
|
|
64
|
+
description = "Fake source"
|
|
65
|
+
supported_domains = ["fake.example"]
|
|
66
|
+
allow_local_files = False
|
|
67
|
+
|
|
68
|
+
def __init__(self):
|
|
69
|
+
self.calls = []
|
|
70
|
+
|
|
71
|
+
def can_import(self, s):
|
|
72
|
+
return "fake.example" in s
|
|
73
|
+
|
|
74
|
+
def search(self, q):
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
"name": f"Hit for {q}",
|
|
78
|
+
"url": "https://fake.example/1",
|
|
79
|
+
"description": "d",
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
def import_from_url(self, url):
|
|
84
|
+
self.calls.append(("url", url, self.allow_local_files))
|
|
85
|
+
return Result(BAD if "bad" in url else PROGRAM)
|
|
86
|
+
|
|
87
|
+
def import_from_source(self, text):
|
|
88
|
+
self.calls.append(("text", text, None))
|
|
89
|
+
return Result(PROGRAM)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.fixture
|
|
93
|
+
def fake(monkeypatch):
|
|
94
|
+
importer = FakeImporter()
|
|
95
|
+
|
|
96
|
+
class Registry:
|
|
97
|
+
@staticmethod
|
|
98
|
+
def get(name):
|
|
99
|
+
return importer if name == "fake" else None
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def find_for_url(url):
|
|
103
|
+
return importer if importer.can_import(url) else None
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def list_importers():
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
"name": "fake",
|
|
110
|
+
"description": "Fake source",
|
|
111
|
+
"supported_domains": ["fake.example"],
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
module = types.ModuleType("rhylthyme_importers")
|
|
116
|
+
module.ImporterRegistry = Registry
|
|
117
|
+
monkeypatch.setitem(sys.modules, "rhylthyme_importers", module)
|
|
118
|
+
return importer
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_import_by_url_writes_a_named_file(fake, tmp_path, monkeypatch):
|
|
122
|
+
monkeypatch.chdir(tmp_path)
|
|
123
|
+
r = CliRunner().invoke(cli, ["import", "https://fake.example/dinner"])
|
|
124
|
+
assert r.exit_code == 0, r.output
|
|
125
|
+
assert (
|
|
126
|
+
json.loads((tmp_path / "fake-dinner.json").read_text())["name"] == "Fake dinner"
|
|
127
|
+
)
|
|
128
|
+
assert "Importing with fake" in r.output
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_explicit_importer_output_path_and_stdout(fake, tmp_path):
|
|
132
|
+
out = tmp_path / "d.json"
|
|
133
|
+
r = CliRunner().invoke(cli, ["import", "12345", "-i", "fake", "-o", str(out)])
|
|
134
|
+
assert (
|
|
135
|
+
r.exit_code == 0 and json.loads(out.read_text())["programId"] == "fake-dinner"
|
|
136
|
+
)
|
|
137
|
+
r = CliRunner().invoke(cli, ["import", "12345", "-i", "fake", "--stdout"])
|
|
138
|
+
assert r.exit_code == 0 and '"programId": "fake-dinner"' in r.output
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_a_local_file_is_allowed_from_the_command_line(fake, tmp_path):
|
|
142
|
+
f = tmp_path / "protocol.py"
|
|
143
|
+
f.write_text("def run(protocol): pass\n")
|
|
144
|
+
r = CliRunner().invoke(cli, ["import", str(f), "-i", "fake", "--stdout"])
|
|
145
|
+
assert r.exit_code == 0, r.output
|
|
146
|
+
assert fake.calls[-1] == (
|
|
147
|
+
"url",
|
|
148
|
+
str(f),
|
|
149
|
+
True,
|
|
150
|
+
), "the CLI opts in to local files; servers never do"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_stdin_needs_an_importer_and_goes_to_import_from_source(fake):
|
|
154
|
+
r = CliRunner().invoke(cli, ["import", "-"], input="text")
|
|
155
|
+
assert r.exit_code != 0 and "-i" in r.output
|
|
156
|
+
r = CliRunner().invoke(
|
|
157
|
+
cli, ["import", "-", "-i", "fake", "--stdout"], input="def run(p): pass"
|
|
158
|
+
)
|
|
159
|
+
assert r.exit_code == 0 and fake.calls[-1][0] == "text"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_an_invalid_import_is_reported_and_not_published(fake, tmp_path):
|
|
163
|
+
r = CliRunner().invoke(
|
|
164
|
+
cli,
|
|
165
|
+
[
|
|
166
|
+
"import",
|
|
167
|
+
"https://fake.example/bad",
|
|
168
|
+
"-o",
|
|
169
|
+
str(tmp_path / "x.json"),
|
|
170
|
+
"--publish",
|
|
171
|
+
],
|
|
172
|
+
)
|
|
173
|
+
assert r.exit_code != 0
|
|
174
|
+
assert "does not validate" in r.output and "nope" in r.output
|
|
175
|
+
assert not (tmp_path / "x.json").exists()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_publish_after_import(fake, tmp_path, monkeypatch):
|
|
179
|
+
from rhylthyme_cli_runner.remote import cli as remote
|
|
180
|
+
|
|
181
|
+
seen = {}
|
|
182
|
+
|
|
183
|
+
def fake_publish(
|
|
184
|
+
program_file, environment, open_url, json_output, quiet, image_path
|
|
185
|
+
):
|
|
186
|
+
seen["program"] = json.loads(open(program_file).read())
|
|
187
|
+
import click
|
|
188
|
+
|
|
189
|
+
click.echo("https://kitchen.rhylthyme.com?share=abc")
|
|
190
|
+
|
|
191
|
+
monkeypatch.setattr(remote.publish, "callback", fake_publish)
|
|
192
|
+
r = CliRunner().invoke(
|
|
193
|
+
cli,
|
|
194
|
+
[
|
|
195
|
+
"import",
|
|
196
|
+
"https://fake.example/dinner",
|
|
197
|
+
"-o",
|
|
198
|
+
str(tmp_path / "d.json"),
|
|
199
|
+
"--publish",
|
|
200
|
+
],
|
|
201
|
+
)
|
|
202
|
+
assert r.exit_code == 0, r.output
|
|
203
|
+
assert seen["program"]["programId"] == "fake-dinner"
|
|
204
|
+
assert "share=abc" in r.output
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_unknown_importer_and_unrecognised_source(fake):
|
|
208
|
+
r = CliRunner().invoke(cli, ["import", "x", "-i", "nope"])
|
|
209
|
+
assert r.exit_code != 0 and "Unknown importer" in r.output and "fake" in r.output
|
|
210
|
+
r = CliRunner().invoke(cli, ["import", "https://example.org/x"])
|
|
211
|
+
assert r.exit_code != 0 and "No importer recognises" in r.output
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_search_and_importers(fake):
|
|
215
|
+
r = CliRunner().invoke(cli, ["search", "chili", "-i", "fake"])
|
|
216
|
+
assert (
|
|
217
|
+
r.exit_code == 0
|
|
218
|
+
and "Hit for chili" in r.output
|
|
219
|
+
and "https://fake.example/1" in r.output
|
|
220
|
+
)
|
|
221
|
+
r = CliRunner().invoke(cli, ["importers"])
|
|
222
|
+
assert r.exit_code == 0 and "fake" in r.output and "fake.example" in r.output
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_without_the_importers_package_the_message_says_how_to_get_it(monkeypatch):
|
|
226
|
+
monkeypatch.setitem(sys.modules, "rhylthyme_importers", None)
|
|
227
|
+
r = CliRunner().invoke(cli, ["import", "https://fake.example/dinner"])
|
|
228
|
+
assert r.exit_code != 0 and "pip install rhylthyme-importers" in r.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/cli.py
RENAMED
|
File without changes
|
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/gold.py
RENAMED
|
File without changes
|
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/eval/llm.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/src/rhylthyme_cli_runner/remote/cli.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_example_validation.py
RENAMED
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_examples_integration.py
RENAMED
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_expand_replicates.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_planner_time_strings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_validate_examples_ci.py
RENAMED
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.4a0}/tests/test_validate_program.py
RENAMED
|
File without changes
|