rhylthyme-cli-runner 0.2.3a0__tar.gz → 0.2.5a0__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.5a0}/PKG-INFO +41 -8
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/README.md +40 -7
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/setup.py +1 -1
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/__init__.py +1 -1
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/cli.py +20 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/render.py +9 -0
- rhylthyme_cli_runner-0.2.5a0/src/rhylthyme_cli_runner/import_cli.py +262 -0
- rhylthyme_cli_runner-0.2.5a0/src/rhylthyme_cli_runner/render_cli.py +41 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0/src/rhylthyme_cli_runner.egg-info}/PKG-INFO +41 -8
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner.egg-info/SOURCES.txt +4 -0
- rhylthyme_cli_runner-0.2.5a0/tests/test_import_cli.py +242 -0
- rhylthyme_cli_runner-0.2.5a0/tests/test_render_cli.py +33 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/LICENSE +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/pyproject.toml +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/setup.cfg +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/environment_icons.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/environment_loader.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/environment_schemas.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/compare.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/gold.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/harness.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/llm.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/matcher.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/metrics.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/patterns/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/patterns/authoring_guide.md +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/patterns/baseline.md +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/patterns/baseline.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/patterns/extract.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/patterns/four_turn.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/eval/report.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/expand_replicates.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/calibrate.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/calibrate_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/evaluate.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/evaluate_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/factors.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/hash.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/predict.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/recorder.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/replay.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/report.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/report_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/store.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/synth.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/history/usable.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/instance_checks.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/program_planner.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/program_runner.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/remote/__init__.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/remote/auth.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/remote/checks.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/remote/cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/remote/mcp_client.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner/validate_program.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner.egg-info/dependency_links.txt +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner.egg-info/entry_points.txt +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner.egg-info/requires.txt +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/src/rhylthyme_cli_runner.egg-info/top_level.txt +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_calibrate.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_cli.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_eval_baseline.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_eval_harness.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_eval_providers.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_eval_scorer.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_evaluate.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_example_validation.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_examples_integration.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_expand_replicates.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_hash_parity.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_history_report.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_mcp_checks.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_planner_time_strings.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_predict.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_program_runner.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_remote.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_replay.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_run_recorder.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_skill.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_synth.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_usable.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_validate_examples_ci.py +0 -0
- {rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/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.5a0
|
|
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
|
|
@@ -64,7 +64,7 @@ Command-line interface for validating and running Rhylthyme real-time program sc
|
|
|
64
64
|
and for turning a plain-language request into one through the hosted Rhylthyme MCP server.
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
pip install rhylthyme
|
|
67
|
+
pip install rhylthyme
|
|
68
68
|
rhylthyme login
|
|
69
69
|
rhylthyme generate "roast chicken, potatoes and green beans for 6" -e kitchen --by 19:00 --with "one oven"
|
|
70
70
|
```
|
|
@@ -76,7 +76,7 @@ the oven, when do the balloons go up, and do the pizzas and the cake ever want
|
|
|
76
76
|
the oven at the same time?
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
|
-
pip install rhylthyme
|
|
79
|
+
pip install rhylthyme
|
|
80
80
|
|
|
81
81
|
# 1. a program: the party, the kitchen and the setup crew, 13 steps
|
|
82
82
|
curl -sO https://raw.githubusercontent.com/rhylthyme/rhylthyme-cli-runner/main/examples/recipe/birthday-party.json
|
|
@@ -117,8 +117,8 @@ Start Sat 12:50, finish Sat 16:00 (local time)
|
|
|
117
117
|
# 4. a live timeline for the fridge door (or a phone): timers, cues, and you end the piñata when it breaks
|
|
118
118
|
rhylthyme publish birthday-party.json --open --image party-preview.png
|
|
119
119
|
|
|
120
|
-
# 5. a picture for the group chat, on the wall clock (needs Node
|
|
121
|
-
|
|
120
|
+
# 5. a picture for the group chat, on the wall clock (needs Node.js)
|
|
121
|
+
rhylthyme render birthday-party.json -o birthday-party.png \
|
|
122
122
|
--style web --palette vivid --label-overflow outside --start-at "$(date +%F)T12:50:00"
|
|
123
123
|
```
|
|
124
124
|
|
|
@@ -141,15 +141,16 @@ is the **server an AI assistant talks to**.
|
|
|
141
141
|
|---|---|---|
|
|
142
142
|
| What it is | The MCP **server**: the tools an AI assistant calls | A command-line **program**: the `rhylthyme` command |
|
|
143
143
|
| Who uses it | Claude, ChatGPT, Cursor or any MCP client, on a person's behalf | A person at a terminal, a script, or CI |
|
|
144
|
-
| Where it runs | Hosted at `mcp.rhylthyme.com`; nothing to install | On your machine: `pip install rhylthyme
|
|
144
|
+
| Where it runs | Hosted at `mcp.rhylthyme.com`; nothing to install | On your machine: `pip install rhylthyme` |
|
|
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.
|
|
@@ -188,7 +189,8 @@ pip install -e ".[dev]"
|
|
|
188
189
|
### From PyPI
|
|
189
190
|
|
|
190
191
|
```bash
|
|
191
|
-
pip install rhylthyme
|
|
192
|
+
pip install rhylthyme # this package, the importers and the renderer
|
|
193
|
+
pip install rhylthyme-cli-runner # this package alone
|
|
192
194
|
```
|
|
193
195
|
|
|
194
196
|
## Getting the Examples
|
|
@@ -542,6 +544,37 @@ A published timeline is reachable by anyone who has the link.
|
|
|
542
544
|
- `--image PATH`: also save a PNG of the timeline
|
|
543
545
|
- `--open`: open it in a browser
|
|
544
546
|
|
|
547
|
+
### `rhylthyme import`
|
|
548
|
+
|
|
549
|
+
Imports a recipe, protocol or slide deck as a program, validates it, and
|
|
550
|
+
writes `<programId>.json`. Needs `rhylthyme-importers` (included in
|
|
551
|
+
`pip install rhylthyme`).
|
|
552
|
+
|
|
553
|
+
```bash
|
|
554
|
+
rhylthyme import https://www.seriouseats.com/the-best-chili-recipe # any of ~580 recipe sites
|
|
555
|
+
rhylthyme import 52772 -i themealdb --publish # a TheMealDB id, straight to a live timeline
|
|
556
|
+
rhylthyme import https://www.protocols.io/view/western-blot-... # needs PROTOCOLS_IO_TOKEN
|
|
557
|
+
rhylthyme import https://raw.githubusercontent.com/Opentrons/Protocols/develop/protocols/007992/rna_isolation.ot2.apiv2.py # an Opentrons protocol
|
|
558
|
+
rhylthyme import "Neapolitan Pizza.cook" -i cooklang
|
|
559
|
+
rhylthyme importers # what is installed
|
|
560
|
+
rhylthyme search "pad thai" -i spoonacular # then import a hit's URL
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
**Options:** `-i IMPORTER` (default: chosen from the URL), `-o PATH`,
|
|
564
|
+
`--stdout`, `--publish` (and `--open`), `--no-validate`. `-` reads pasted
|
|
565
|
+
source from stdin (with `-i`).
|
|
566
|
+
|
|
567
|
+
### `rhylthyme render`
|
|
568
|
+
|
|
569
|
+
Draws a program as an SVG, PNG or PDF figure through the `rhylthyme-timeline`
|
|
570
|
+
package (included in `pip install rhylthyme`; needs Node.js 18+). Every
|
|
571
|
+
option of `rhylthyme-render` is accepted: `rhylthyme render --help`.
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
rhylthyme render dinner.json -o dinner.png --style web --palette vivid --color-by task
|
|
575
|
+
rhylthyme render blot.json -o figure.pdf --style publication --legend right
|
|
576
|
+
```
|
|
577
|
+
|
|
545
578
|
### `rhylthyme plan`
|
|
546
579
|
|
|
547
580
|
An older stagger heuristic. It reads the pre-0.2 program format and leaves
|
|
@@ -4,7 +4,7 @@ Command-line interface for validating and running Rhylthyme real-time program sc
|
|
|
4
4
|
and for turning a plain-language request into one through the hosted Rhylthyme MCP server.
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
|
-
pip install rhylthyme
|
|
7
|
+
pip install rhylthyme
|
|
8
8
|
rhylthyme login
|
|
9
9
|
rhylthyme generate "roast chicken, potatoes and green beans for 6" -e kitchen --by 19:00 --with "one oven"
|
|
10
10
|
```
|
|
@@ -16,7 +16,7 @@ the oven, when do the balloons go up, and do the pizzas and the cake ever want
|
|
|
16
16
|
the oven at the same time?
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
pip install rhylthyme
|
|
19
|
+
pip install rhylthyme
|
|
20
20
|
|
|
21
21
|
# 1. a program: the party, the kitchen and the setup crew, 13 steps
|
|
22
22
|
curl -sO https://raw.githubusercontent.com/rhylthyme/rhylthyme-cli-runner/main/examples/recipe/birthday-party.json
|
|
@@ -57,8 +57,8 @@ Start Sat 12:50, finish Sat 16:00 (local time)
|
|
|
57
57
|
# 4. a live timeline for the fridge door (or a phone): timers, cues, and you end the piñata when it breaks
|
|
58
58
|
rhylthyme publish birthday-party.json --open --image party-preview.png
|
|
59
59
|
|
|
60
|
-
# 5. a picture for the group chat, on the wall clock (needs Node
|
|
61
|
-
|
|
60
|
+
# 5. a picture for the group chat, on the wall clock (needs Node.js)
|
|
61
|
+
rhylthyme render birthday-party.json -o birthday-party.png \
|
|
62
62
|
--style web --palette vivid --label-overflow outside --start-at "$(date +%F)T12:50:00"
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -81,15 +81,16 @@ is the **server an AI assistant talks to**.
|
|
|
81
81
|
|---|---|---|
|
|
82
82
|
| What it is | The MCP **server**: the tools an AI assistant calls | A command-line **program**: the `rhylthyme` command |
|
|
83
83
|
| Who uses it | Claude, ChatGPT, Cursor or any MCP client, on a person's behalf | A person at a terminal, a script, or CI |
|
|
84
|
-
| Where it runs | Hosted at `mcp.rhylthyme.com`; nothing to install | On your machine: `pip install rhylthyme
|
|
84
|
+
| Where it runs | Hosted at `mcp.rhylthyme.com`; nothing to install | On your machine: `pip install rhylthyme` |
|
|
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.
|
|
@@ -128,7 +129,8 @@ pip install -e ".[dev]"
|
|
|
128
129
|
### From PyPI
|
|
129
130
|
|
|
130
131
|
```bash
|
|
131
|
-
pip install rhylthyme
|
|
132
|
+
pip install rhylthyme # this package, the importers and the renderer
|
|
133
|
+
pip install rhylthyme-cli-runner # this package alone
|
|
132
134
|
```
|
|
133
135
|
|
|
134
136
|
## Getting the Examples
|
|
@@ -482,6 +484,37 @@ A published timeline is reachable by anyone who has the link.
|
|
|
482
484
|
- `--image PATH`: also save a PNG of the timeline
|
|
483
485
|
- `--open`: open it in a browser
|
|
484
486
|
|
|
487
|
+
### `rhylthyme import`
|
|
488
|
+
|
|
489
|
+
Imports a recipe, protocol or slide deck as a program, validates it, and
|
|
490
|
+
writes `<programId>.json`. Needs `rhylthyme-importers` (included in
|
|
491
|
+
`pip install rhylthyme`).
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
rhylthyme import https://www.seriouseats.com/the-best-chili-recipe # any of ~580 recipe sites
|
|
495
|
+
rhylthyme import 52772 -i themealdb --publish # a TheMealDB id, straight to a live timeline
|
|
496
|
+
rhylthyme import https://www.protocols.io/view/western-blot-... # needs PROTOCOLS_IO_TOKEN
|
|
497
|
+
rhylthyme import https://raw.githubusercontent.com/Opentrons/Protocols/develop/protocols/007992/rna_isolation.ot2.apiv2.py # an Opentrons protocol
|
|
498
|
+
rhylthyme import "Neapolitan Pizza.cook" -i cooklang
|
|
499
|
+
rhylthyme importers # what is installed
|
|
500
|
+
rhylthyme search "pad thai" -i spoonacular # then import a hit's URL
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**Options:** `-i IMPORTER` (default: chosen from the URL), `-o PATH`,
|
|
504
|
+
`--stdout`, `--publish` (and `--open`), `--no-validate`. `-` reads pasted
|
|
505
|
+
source from stdin (with `-i`).
|
|
506
|
+
|
|
507
|
+
### `rhylthyme render`
|
|
508
|
+
|
|
509
|
+
Draws a program as an SVG, PNG or PDF figure through the `rhylthyme-timeline`
|
|
510
|
+
package (included in `pip install rhylthyme`; needs Node.js 18+). Every
|
|
511
|
+
option of `rhylthyme-render` is accepted: `rhylthyme render --help`.
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
rhylthyme render dinner.json -o dinner.png --style web --palette vivid --color-by task
|
|
515
|
+
rhylthyme render blot.json -o figure.pdf --style publication --legend right
|
|
516
|
+
```
|
|
517
|
+
|
|
485
518
|
### `rhylthyme plan`
|
|
486
519
|
|
|
487
520
|
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.5a0",
|
|
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.5a0}/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.5a0"
|
|
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.5a0}/src/rhylthyme_cli_runner/cli.py
RENAMED
|
@@ -544,6 +544,26 @@ 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
|
+
|
|
557
|
+
def _register_render_command():
|
|
558
|
+
"""Attach `render` (rhylthyme_cli_runner.render_cli)."""
|
|
559
|
+
from .render_cli import register as register_render
|
|
560
|
+
|
|
561
|
+
register_render(cli)
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
_register_render_command()
|
|
565
|
+
|
|
566
|
+
|
|
547
567
|
def main():
|
|
548
568
|
"""Entry point for the CLI."""
|
|
549
569
|
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,262 @@
|
|
|
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
|
+
# An importer that works on text (Opentrons) gets a URL's body fetched
|
|
53
|
+
# for it; the URL-native importers fetch for themselves.
|
|
54
|
+
url_native = bool(getattr(importer, "supported_domains", None))
|
|
55
|
+
if (
|
|
56
|
+
text is None
|
|
57
|
+
and source.startswith(("http://", "https://"))
|
|
58
|
+
and hasattr(importer, "import_from_source")
|
|
59
|
+
and not url_native
|
|
60
|
+
):
|
|
61
|
+
from .remote.cli import _fetch_bytes
|
|
62
|
+
|
|
63
|
+
text = _fetch_bytes(source).decode("utf-8")
|
|
64
|
+
if text is not None and hasattr(importer, "import_from_source"):
|
|
65
|
+
return importer.import_from_source(text)
|
|
66
|
+
if text is not None and hasattr(importer, "import_from_content"):
|
|
67
|
+
return importer.import_from_content(
|
|
68
|
+
text, source_name=Path(source).stem if source != "-" else "recipe"
|
|
69
|
+
)
|
|
70
|
+
path = Path(source)
|
|
71
|
+
if path.is_file():
|
|
72
|
+
# A local file was named explicitly; that is the command line's job
|
|
73
|
+
# to allow (a server never does this).
|
|
74
|
+
if hasattr(importer, "allow_local_files"):
|
|
75
|
+
importer.allow_local_files = True
|
|
76
|
+
return importer.import_from_url(str(path))
|
|
77
|
+
return importer.import_from_url(source)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _validation(program: dict):
|
|
81
|
+
from .validate_program import validate_program_file_structured
|
|
82
|
+
|
|
83
|
+
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as tmp:
|
|
84
|
+
json.dump(program, tmp)
|
|
85
|
+
tmp_path = tmp.name
|
|
86
|
+
try:
|
|
87
|
+
from .cli import _default_schema_path
|
|
88
|
+
|
|
89
|
+
return validate_program_file_structured(
|
|
90
|
+
tmp_path, schema_file=_default_schema_path()
|
|
91
|
+
)
|
|
92
|
+
finally:
|
|
93
|
+
Path(tmp_path).unlink(missing_ok=True)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@click.command("import")
|
|
97
|
+
@click.argument("source", metavar="URL_OR_ID_OR_FILE")
|
|
98
|
+
@click.option(
|
|
99
|
+
"-i",
|
|
100
|
+
"--importer",
|
|
101
|
+
"importer_name",
|
|
102
|
+
default=None,
|
|
103
|
+
help="Which importer to use (default: chosen from the URL).",
|
|
104
|
+
)
|
|
105
|
+
@click.option(
|
|
106
|
+
"-o",
|
|
107
|
+
"--output",
|
|
108
|
+
type=click.Path(dir_okay=False),
|
|
109
|
+
default=None,
|
|
110
|
+
help="Write the program here (default: <name>.json in the current directory).",
|
|
111
|
+
)
|
|
112
|
+
@click.option(
|
|
113
|
+
"--stdout",
|
|
114
|
+
"to_stdout",
|
|
115
|
+
is_flag=True,
|
|
116
|
+
help="Print the program instead of writing a file.",
|
|
117
|
+
)
|
|
118
|
+
@click.option(
|
|
119
|
+
"--publish",
|
|
120
|
+
"do_publish",
|
|
121
|
+
is_flag=True,
|
|
122
|
+
help="Also publish it as a live timeline and print the URL.",
|
|
123
|
+
)
|
|
124
|
+
@click.option(
|
|
125
|
+
"--open",
|
|
126
|
+
"open_url",
|
|
127
|
+
is_flag=True,
|
|
128
|
+
help="With --publish: open the timeline in a browser.",
|
|
129
|
+
)
|
|
130
|
+
@click.option(
|
|
131
|
+
"--no-validate", is_flag=True, help="Skip validation of the imported program."
|
|
132
|
+
)
|
|
133
|
+
def import_command(
|
|
134
|
+
source, importer_name, output, to_stdout, do_publish, open_url, no_validate
|
|
135
|
+
):
|
|
136
|
+
"""Import a recipe, protocol or deck as a program.
|
|
137
|
+
|
|
138
|
+
\b
|
|
139
|
+
URL_OR_ID_OR_FILE is a recipe-site or protocols.io URL, a TheMealDB or
|
|
140
|
+
Spoonacular id, a .cook or GitHub .cook URL, or a local .py (Opentrons),
|
|
141
|
+
.cook or .pptx file. `-` reads the source text from stdin (with -i).
|
|
142
|
+
rhylthyme import https://www.seriouseats.com/the-best-chili-recipe
|
|
143
|
+
rhylthyme import 52772 -i themealdb --publish
|
|
144
|
+
rhylthyme import https://raw.githubusercontent.com/Opentrons/Protocols/develop/protocols/007992/rna_isolation.ot2.apiv2.py
|
|
145
|
+
rhylthyme importers # what is installed
|
|
146
|
+
"""
|
|
147
|
+
registry = _registry()
|
|
148
|
+
text = None
|
|
149
|
+
if source == "-":
|
|
150
|
+
if not importer_name:
|
|
151
|
+
raise click.ClickException(
|
|
152
|
+
"Reading from stdin needs -i to say which importer."
|
|
153
|
+
)
|
|
154
|
+
text = sys.stdin.read()
|
|
155
|
+
importer = _pick(registry, source, importer_name)
|
|
156
|
+
click.echo(f"Importing with {importer.name}…", err=True)
|
|
157
|
+
result = _import_one(importer, source, text)
|
|
158
|
+
if not result.success:
|
|
159
|
+
raise click.ClickException(result.error or "import failed")
|
|
160
|
+
program = result.program
|
|
161
|
+
|
|
162
|
+
if not no_validate:
|
|
163
|
+
v = _validation(program)
|
|
164
|
+
errors = v.get("errors") or v.get("logic_errors") or []
|
|
165
|
+
if not v.get("is_valid", v.get("valid", True)):
|
|
166
|
+
for e in errors[:8]:
|
|
167
|
+
click.echo(
|
|
168
|
+
f" ✗ {e if isinstance(e, str) else e.get('message', e)}", err=True
|
|
169
|
+
)
|
|
170
|
+
raise click.ClickException(
|
|
171
|
+
"The imported program does not validate; not published. Re-run with --no-validate to keep it anyway."
|
|
172
|
+
)
|
|
173
|
+
for w in (v.get("warnings") or [])[:5]:
|
|
174
|
+
click.echo(
|
|
175
|
+
f" ! {w if isinstance(w, str) else w.get('message', w)}", err=True
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
payload = json.dumps(program, indent=2, ensure_ascii=False)
|
|
179
|
+
if to_stdout:
|
|
180
|
+
click.echo(payload)
|
|
181
|
+
else:
|
|
182
|
+
target = (
|
|
183
|
+
Path(output)
|
|
184
|
+
if output
|
|
185
|
+
else Path(f"{program.get('programId') or 'program'}.json")
|
|
186
|
+
)
|
|
187
|
+
target.write_text(payload + "\n")
|
|
188
|
+
click.echo(f"Saved {target}", err=True)
|
|
189
|
+
|
|
190
|
+
if do_publish:
|
|
191
|
+
from .remote.cli import publish as publish_command
|
|
192
|
+
|
|
193
|
+
ctx = click.get_current_context()
|
|
194
|
+
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as tmp:
|
|
195
|
+
tmp.write(payload)
|
|
196
|
+
tmp_path = tmp.name
|
|
197
|
+
try:
|
|
198
|
+
ctx.invoke(
|
|
199
|
+
publish_command,
|
|
200
|
+
program_file=tmp_path,
|
|
201
|
+
environment=None,
|
|
202
|
+
open_url=open_url,
|
|
203
|
+
json_output=False,
|
|
204
|
+
quiet=True,
|
|
205
|
+
image_path=None,
|
|
206
|
+
)
|
|
207
|
+
finally:
|
|
208
|
+
Path(tmp_path).unlink(missing_ok=True)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@click.command("search")
|
|
212
|
+
@click.argument("query")
|
|
213
|
+
@click.option(
|
|
214
|
+
"-i",
|
|
215
|
+
"--importer",
|
|
216
|
+
"importer_name",
|
|
217
|
+
default="themealdb",
|
|
218
|
+
show_default=True,
|
|
219
|
+
help="Which catalogue to search.",
|
|
220
|
+
)
|
|
221
|
+
@click.option("--json", "json_output", is_flag=True)
|
|
222
|
+
def search_command(query, importer_name, json_output):
|
|
223
|
+
"""Search a recipe or protocol source; import a hit with `rhylthyme import <url>`."""
|
|
224
|
+
registry = _registry()
|
|
225
|
+
importer = registry.get(importer_name)
|
|
226
|
+
if importer is None:
|
|
227
|
+
names = ", ".join(sorted(i["name"] for i in registry.list_importers()))
|
|
228
|
+
raise click.ClickException(
|
|
229
|
+
f"Unknown importer '{importer_name}'. Installed: {names}."
|
|
230
|
+
)
|
|
231
|
+
hits = importer.search(query)
|
|
232
|
+
if json_output:
|
|
233
|
+
click.echo(json.dumps(hits, indent=2, ensure_ascii=False))
|
|
234
|
+
return
|
|
235
|
+
if not hits:
|
|
236
|
+
click.echo("No results.")
|
|
237
|
+
return
|
|
238
|
+
for h in hits:
|
|
239
|
+
click.echo(f"{h.get('name')}\n {h.get('url')}")
|
|
240
|
+
if h.get("description"):
|
|
241
|
+
click.echo(f" {h['description']}")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@click.command("importers")
|
|
245
|
+
def importers_command():
|
|
246
|
+
"""List the installed importers."""
|
|
247
|
+
registry = _registry()
|
|
248
|
+
for info in registry.list_importers():
|
|
249
|
+
domains = info.get("supported_domains") or []
|
|
250
|
+
where = (
|
|
251
|
+
", ".join(domains[:4])
|
|
252
|
+
+ (f", … ({len(domains)} sites)" if len(domains) > 4 else "")
|
|
253
|
+
if domains
|
|
254
|
+
else "files, ids or pasted text"
|
|
255
|
+
)
|
|
256
|
+
click.echo(f"{info['name']:18} {info['description']} [{where}]")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def register(cli_group: click.Group) -> None:
|
|
260
|
+
cli_group.add_command(import_command)
|
|
261
|
+
cli_group.add_command(search_command)
|
|
262
|
+
cli_group.add_command(importers_command)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""``rhylthyme render``: the timeline renderer from rhylthyme-timeline,
|
|
2
|
+
reached from the one command people already have. Every option of the
|
|
3
|
+
``rhylthyme-render`` command is accepted unchanged."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
INSTALL_HINT = (
|
|
10
|
+
"The renderer is a separate package: pip install rhylthyme-timeline "
|
|
11
|
+
"(or pip install rhylthyme, which includes it). It runs on Node.js 18 or newer."
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command(
|
|
16
|
+
"render",
|
|
17
|
+
context_settings={
|
|
18
|
+
"ignore_unknown_options": True,
|
|
19
|
+
"allow_extra_args": True,
|
|
20
|
+
"help_option_names": [],
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
24
|
+
@click.pass_context
|
|
25
|
+
def render_command(ctx, args):
|
|
26
|
+
"""Draw a program as an SVG, PNG or PDF figure (needs Node.js).
|
|
27
|
+
|
|
28
|
+
\b
|
|
29
|
+
rhylthyme render dinner.json -o dinner.png --style web --palette vivid
|
|
30
|
+
rhylthyme render blot.json -o figure.pdf --style publication --legend right
|
|
31
|
+
rhylthyme render --help # every option
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
from rhylthyme_timeline import main as render_main
|
|
35
|
+
except ImportError:
|
|
36
|
+
raise click.ClickException(INSTALL_HINT)
|
|
37
|
+
ctx.exit(render_main(list(args)))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def register(cli_group: click.Group) -> None:
|
|
41
|
+
cli_group.add_command(render_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.5a0
|
|
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
|
|
@@ -64,7 +64,7 @@ Command-line interface for validating and running Rhylthyme real-time program sc
|
|
|
64
64
|
and for turning a plain-language request into one through the hosted Rhylthyme MCP server.
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
pip install rhylthyme
|
|
67
|
+
pip install rhylthyme
|
|
68
68
|
rhylthyme login
|
|
69
69
|
rhylthyme generate "roast chicken, potatoes and green beans for 6" -e kitchen --by 19:00 --with "one oven"
|
|
70
70
|
```
|
|
@@ -76,7 +76,7 @@ the oven, when do the balloons go up, and do the pizzas and the cake ever want
|
|
|
76
76
|
the oven at the same time?
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
|
-
pip install rhylthyme
|
|
79
|
+
pip install rhylthyme
|
|
80
80
|
|
|
81
81
|
# 1. a program: the party, the kitchen and the setup crew, 13 steps
|
|
82
82
|
curl -sO https://raw.githubusercontent.com/rhylthyme/rhylthyme-cli-runner/main/examples/recipe/birthday-party.json
|
|
@@ -117,8 +117,8 @@ Start Sat 12:50, finish Sat 16:00 (local time)
|
|
|
117
117
|
# 4. a live timeline for the fridge door (or a phone): timers, cues, and you end the piñata when it breaks
|
|
118
118
|
rhylthyme publish birthday-party.json --open --image party-preview.png
|
|
119
119
|
|
|
120
|
-
# 5. a picture for the group chat, on the wall clock (needs Node
|
|
121
|
-
|
|
120
|
+
# 5. a picture for the group chat, on the wall clock (needs Node.js)
|
|
121
|
+
rhylthyme render birthday-party.json -o birthday-party.png \
|
|
122
122
|
--style web --palette vivid --label-overflow outside --start-at "$(date +%F)T12:50:00"
|
|
123
123
|
```
|
|
124
124
|
|
|
@@ -141,15 +141,16 @@ is the **server an AI assistant talks to**.
|
|
|
141
141
|
|---|---|---|
|
|
142
142
|
| What it is | The MCP **server**: the tools an AI assistant calls | A command-line **program**: the `rhylthyme` command |
|
|
143
143
|
| Who uses it | Claude, ChatGPT, Cursor or any MCP client, on a person's behalf | A person at a terminal, a script, or CI |
|
|
144
|
-
| Where it runs | Hosted at `mcp.rhylthyme.com`; nothing to install | On your machine: `pip install rhylthyme
|
|
144
|
+
| Where it runs | Hosted at `mcp.rhylthyme.com`; nothing to install | On your machine: `pip install rhylthyme` |
|
|
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.
|
|
@@ -188,7 +189,8 @@ pip install -e ".[dev]"
|
|
|
188
189
|
### From PyPI
|
|
189
190
|
|
|
190
191
|
```bash
|
|
191
|
-
pip install rhylthyme
|
|
192
|
+
pip install rhylthyme # this package, the importers and the renderer
|
|
193
|
+
pip install rhylthyme-cli-runner # this package alone
|
|
192
194
|
```
|
|
193
195
|
|
|
194
196
|
## Getting the Examples
|
|
@@ -542,6 +544,37 @@ A published timeline is reachable by anyone who has the link.
|
|
|
542
544
|
- `--image PATH`: also save a PNG of the timeline
|
|
543
545
|
- `--open`: open it in a browser
|
|
544
546
|
|
|
547
|
+
### `rhylthyme import`
|
|
548
|
+
|
|
549
|
+
Imports a recipe, protocol or slide deck as a program, validates it, and
|
|
550
|
+
writes `<programId>.json`. Needs `rhylthyme-importers` (included in
|
|
551
|
+
`pip install rhylthyme`).
|
|
552
|
+
|
|
553
|
+
```bash
|
|
554
|
+
rhylthyme import https://www.seriouseats.com/the-best-chili-recipe # any of ~580 recipe sites
|
|
555
|
+
rhylthyme import 52772 -i themealdb --publish # a TheMealDB id, straight to a live timeline
|
|
556
|
+
rhylthyme import https://www.protocols.io/view/western-blot-... # needs PROTOCOLS_IO_TOKEN
|
|
557
|
+
rhylthyme import https://raw.githubusercontent.com/Opentrons/Protocols/develop/protocols/007992/rna_isolation.ot2.apiv2.py # an Opentrons protocol
|
|
558
|
+
rhylthyme import "Neapolitan Pizza.cook" -i cooklang
|
|
559
|
+
rhylthyme importers # what is installed
|
|
560
|
+
rhylthyme search "pad thai" -i spoonacular # then import a hit's URL
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
**Options:** `-i IMPORTER` (default: chosen from the URL), `-o PATH`,
|
|
564
|
+
`--stdout`, `--publish` (and `--open`), `--no-validate`. `-` reads pasted
|
|
565
|
+
source from stdin (with `-i`).
|
|
566
|
+
|
|
567
|
+
### `rhylthyme render`
|
|
568
|
+
|
|
569
|
+
Draws a program as an SVG, PNG or PDF figure through the `rhylthyme-timeline`
|
|
570
|
+
package (included in `pip install rhylthyme`; needs Node.js 18+). Every
|
|
571
|
+
option of `rhylthyme-render` is accepted: `rhylthyme render --help`.
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
rhylthyme render dinner.json -o dinner.png --style web --palette vivid --color-by task
|
|
575
|
+
rhylthyme render blot.json -o figure.pdf --style publication --legend right
|
|
576
|
+
```
|
|
577
|
+
|
|
545
578
|
### `rhylthyme plan`
|
|
546
579
|
|
|
547
580
|
An older stagger heuristic. It reads the pre-0.2 program format and leaves
|
|
@@ -8,9 +8,11 @@ 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
|
|
15
|
+
src/rhylthyme_cli_runner/render_cli.py
|
|
14
16
|
src/rhylthyme_cli_runner/validate_program.py
|
|
15
17
|
src/rhylthyme_cli_runner.egg-info/PKG-INFO
|
|
16
18
|
src/rhylthyme_cli_runner.egg-info/SOURCES.txt
|
|
@@ -67,11 +69,13 @@ tests/test_examples_integration.py
|
|
|
67
69
|
tests/test_expand_replicates.py
|
|
68
70
|
tests/test_hash_parity.py
|
|
69
71
|
tests/test_history_report.py
|
|
72
|
+
tests/test_import_cli.py
|
|
70
73
|
tests/test_mcp_checks.py
|
|
71
74
|
tests/test_planner_time_strings.py
|
|
72
75
|
tests/test_predict.py
|
|
73
76
|
tests/test_program_runner.py
|
|
74
77
|
tests/test_remote.py
|
|
78
|
+
tests/test_render_cli.py
|
|
75
79
|
tests/test_replay.py
|
|
76
80
|
tests/test_run_recorder.py
|
|
77
81
|
tests/test_skill.py
|
|
@@ -0,0 +1,242 @@
|
|
|
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
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_a_text_importer_gets_a_urls_body(fake, monkeypatch):
|
|
232
|
+
from rhylthyme_cli_runner.remote import cli as remote
|
|
233
|
+
|
|
234
|
+
monkeypatch.setattr(
|
|
235
|
+
remote, "_fetch_bytes", lambda url, limit=0: b"def run(protocol): pass\n"
|
|
236
|
+
)
|
|
237
|
+
fake.supported_domains = [] # a file/text importer, like Opentrons
|
|
238
|
+
r = CliRunner().invoke(
|
|
239
|
+
cli, ["import", "https://fake.example/protocol.py", "-i", "fake", "--stdout"]
|
|
240
|
+
)
|
|
241
|
+
assert r.exit_code == 0, r.output
|
|
242
|
+
assert fake.calls[-1] == ("text", "def run(protocol): pass\n", None)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""`rhylthyme render` hands its arguments to the rhylthyme-timeline package."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import types
|
|
5
|
+
|
|
6
|
+
from click.testing import CliRunner
|
|
7
|
+
|
|
8
|
+
from rhylthyme_cli_runner.cli import cli
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_arguments_pass_through_unchanged(monkeypatch):
|
|
12
|
+
seen = {}
|
|
13
|
+
module = types.ModuleType("rhylthyme_timeline")
|
|
14
|
+
module.main = lambda argv: seen.setdefault("argv", argv) and 0
|
|
15
|
+
monkeypatch.setitem(sys.modules, "rhylthyme_timeline", module)
|
|
16
|
+
r = CliRunner().invoke(
|
|
17
|
+
cli, ["render", "p.json", "-o", "p.png", "--style", "web", "--help"]
|
|
18
|
+
)
|
|
19
|
+
assert r.exit_code == 0, r.output
|
|
20
|
+
assert seen["argv"] == ["p.json", "-o", "p.png", "--style", "web", "--help"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_exit_code_is_the_renderers(monkeypatch):
|
|
24
|
+
module = types.ModuleType("rhylthyme_timeline")
|
|
25
|
+
module.main = lambda argv: 2
|
|
26
|
+
monkeypatch.setitem(sys.modules, "rhylthyme_timeline", module)
|
|
27
|
+
assert CliRunner().invoke(cli, ["render", "p.json"]).exit_code == 2
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_without_the_package_the_message_says_how_to_get_it(monkeypatch):
|
|
31
|
+
monkeypatch.setitem(sys.modules, "rhylthyme_timeline", None)
|
|
32
|
+
r = CliRunner().invoke(cli, ["render", "p.json"])
|
|
33
|
+
assert r.exit_code != 0 and "pip install rhylthyme-timeline" 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.5a0}/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.5a0}/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.5a0}/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.5a0}/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.5a0}/tests/test_example_validation.py
RENAMED
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_examples_integration.py
RENAMED
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/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.5a0}/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.5a0}/tests/test_validate_examples_ci.py
RENAMED
|
File without changes
|
{rhylthyme_cli_runner-0.2.3a0 → rhylthyme_cli_runner-0.2.5a0}/tests/test_validate_program.py
RENAMED
|
File without changes
|