rhylthyme-cli-runner 0.2.0a0__tar.gz → 0.2.3a0__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.
Files changed (84) hide show
  1. {rhylthyme_cli_runner-0.2.0a0/src/rhylthyme_cli_runner.egg-info → rhylthyme_cli_runner-0.2.3a0}/PKG-INFO +266 -3
  2. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/README.md +265 -2
  3. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/setup.py +1 -1
  4. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/__init__.py +1 -1
  5. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/cli.py +4 -1
  6. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/llm.py +191 -4
  7. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/program_planner.py +20 -4
  8. rhylthyme_cli_runner-0.2.3a0/src/rhylthyme_cli_runner/remote/checks.py +663 -0
  9. rhylthyme_cli_runner-0.2.3a0/src/rhylthyme_cli_runner/remote/cli.py +644 -0
  10. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/remote/mcp_client.py +69 -16
  11. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0/src/rhylthyme_cli_runner.egg-info}/PKG-INFO +266 -3
  12. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner.egg-info/SOURCES.txt +5 -0
  13. rhylthyme_cli_runner-0.2.3a0/tests/test_eval_providers.py +292 -0
  14. rhylthyme_cli_runner-0.2.3a0/tests/test_mcp_checks.py +441 -0
  15. rhylthyme_cli_runner-0.2.3a0/tests/test_planner_time_strings.py +20 -0
  16. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_remote.py +150 -0
  17. rhylthyme_cli_runner-0.2.3a0/tests/test_skill.py +82 -0
  18. rhylthyme_cli_runner-0.2.0a0/src/rhylthyme_cli_runner/remote/cli.py +0 -282
  19. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/LICENSE +0 -0
  20. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/pyproject.toml +0 -0
  21. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/setup.cfg +0 -0
  22. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/cli.py +0 -0
  23. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/environment_icons.py +0 -0
  24. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/environment_loader.py +0 -0
  25. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/environment_schemas.py +0 -0
  26. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/__init__.py +0 -0
  27. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/compare.py +0 -0
  28. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/gold.py +0 -0
  29. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/harness.py +0 -0
  30. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/matcher.py +0 -0
  31. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/metrics.py +0 -0
  32. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/patterns/__init__.py +0 -0
  33. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/patterns/authoring_guide.md +0 -0
  34. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/patterns/baseline.md +0 -0
  35. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/patterns/baseline.py +0 -0
  36. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/patterns/extract.py +0 -0
  37. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/patterns/four_turn.py +0 -0
  38. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/eval/report.py +0 -0
  39. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/expand_replicates.py +0 -0
  40. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/__init__.py +0 -0
  41. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/calibrate.py +0 -0
  42. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/calibrate_cli.py +0 -0
  43. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/cli.py +0 -0
  44. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/evaluate.py +0 -0
  45. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/evaluate_cli.py +0 -0
  46. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/factors.py +0 -0
  47. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/hash.py +0 -0
  48. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/predict.py +0 -0
  49. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/recorder.py +0 -0
  50. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/render.py +0 -0
  51. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/replay.py +0 -0
  52. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/report.py +0 -0
  53. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/report_cli.py +0 -0
  54. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/store.py +0 -0
  55. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/synth.py +0 -0
  56. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/history/usable.py +0 -0
  57. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/instance_checks.py +0 -0
  58. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/program_runner.py +0 -0
  59. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/remote/__init__.py +0 -0
  60. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/remote/auth.py +0 -0
  61. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner/validate_program.py +0 -0
  62. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner.egg-info/dependency_links.txt +0 -0
  63. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner.egg-info/entry_points.txt +0 -0
  64. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner.egg-info/requires.txt +0 -0
  65. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/src/rhylthyme_cli_runner.egg-info/top_level.txt +0 -0
  66. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_calibrate.py +0 -0
  67. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_cli.py +0 -0
  68. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_eval_baseline.py +0 -0
  69. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_eval_harness.py +0 -0
  70. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_eval_scorer.py +0 -0
  71. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_evaluate.py +0 -0
  72. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_example_validation.py +0 -0
  73. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_examples_integration.py +0 -0
  74. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_expand_replicates.py +0 -0
  75. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_hash_parity.py +0 -0
  76. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_history_report.py +0 -0
  77. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_predict.py +0 -0
  78. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_program_runner.py +0 -0
  79. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_replay.py +0 -0
  80. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_run_recorder.py +0 -0
  81. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_synth.py +0 -0
  82. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_usable.py +0 -0
  83. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/tests/test_validate_examples_ci.py +0 -0
  84. {rhylthyme_cli_runner-0.2.0a0 → rhylthyme_cli_runner-0.2.3a0}/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.0a0
3
+ Version: 0.2.3a0
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
@@ -69,6 +69,106 @@ rhylthyme login
69
69
  rhylthyme generate "roast chicken, potatoes and green beans for 6" -e kitchen --by 19:00 --with "one oven"
70
70
  ```
71
71
 
72
+ ## A timeline in five commands: a birthday party
73
+
74
+ Parents collect at 4:00. Working backwards from that: when does the cake go in
75
+ the oven, when do the balloons go up, and do the pizzas and the cake ever want
76
+ the oven at the same time?
77
+
78
+ ```bash
79
+ pip install rhylthyme-cli-runner
80
+
81
+ # 1. a program: the party, the kitchen and the setup crew, 13 steps
82
+ curl -sO https://raw.githubusercontent.com/rhylthyme/rhylthyme-cli-runner/main/examples/recipe/birthday-party.json
83
+
84
+ # 2. is it well formed?
85
+ rhylthyme validate birthday-party.json --strict
86
+
87
+ # 3. any clashes, and what time does everything start if we finish at 4?
88
+ rhylthyme analyze birthday-party.json --finish-at 16:00
89
+ ```
90
+
91
+ ```
92
+ **Makespan:** 3h 10m across 3 tracks, 13 steps.
93
+ **Critical path:** Guests arrive → Party games → Pizza → Candles and cake → Piñata, until it breaks → Party bags and goodbyes
94
+ **Binding constraints:** step dependencies only — no resource or in-flight limit gates the critical path.
95
+ **Peak concurrency:** 2 steps at 10:00.
96
+ **Resource conflicts:** none.
97
+
98
+ **Tracks that finish early:** Kitchen (1h 10m early), Setup crew (50m early)
99
+
100
+ Start Sat 12:50, finish Sat 16:00 (local time)
101
+ Sat 12:50 Bake the cake
102
+ Sat 13:00 Balloons and banner
103
+ Sat 13:25 Cake cools
104
+ Sat 13:30 Hang the piñata
105
+ Sat 13:50 Guests arrive
106
+ Sat 14:05 Frost and decorate
107
+ Sat 14:20 Party games
108
+ Sat 14:35 Pizzas in the oven
109
+ Sat 14:50 Fill party bags
110
+ Sat 14:50 Pizza
111
+ Sat 15:15 Candles and cake
112
+ Sat 15:30 Piñata, until it breaks
113
+ Sat 15:45 Party bags and goodbyes
114
+ ```
115
+
116
+ ```bash
117
+ # 4. a live timeline for the fridge door (or a phone): timers, cues, and you end the piñata when it breaks
118
+ rhylthyme publish birthday-party.json --open --image party-preview.png
119
+
120
+ # 5. a picture for the group chat, on the wall clock (needs Node; nothing to install)
121
+ npx -y github:rhylthyme/rhylthyme-timeline birthday-party.json -o birthday-party.png \
122
+ --style web --palette vivid --label-overflow outside --start-at "$(date +%F)T12:50:00"
123
+ ```
124
+
125
+ ![Eighth birthday party: the party, the kitchen and the setup crew on a wall-clock axis](https://raw.githubusercontent.com/rhylthyme/rhylthyme-cli-runner/main/examples/recipe/birthday-party.png)
126
+
127
+ The pizzas go in 15 minutes after the games *start*, so they come out as the
128
+ games end; the party bags get filled while everyone is eating. Try
129
+ `--finish-at 17:30`, or turn the cake into a two-hour fruitcake (`"seconds": "2h"`
130
+ on `bake`) and `analyze` reports the clash: `oven needs 2 but max is 1 from
131
+ 1:45:00 to 2:00:00 (bake, heat-pizza)`. `rhylthyme run birthday-party.json --time-scale 60` rehearses
132
+ the whole afternoon in the terminal at a minute per second.
133
+
134
+ ## This repository and rhylthyme-mcp
135
+
136
+ Two repositories do related things and are easy to confuse. This one is the
137
+ **command a person types**. [rhylthyme-mcp](https://github.com/rhylthyme/rhylthyme-mcp)
138
+ is the **server an AI assistant talks to**.
139
+
140
+ | | [rhylthyme-mcp](https://github.com/rhylthyme/rhylthyme-mcp) | [rhylthyme-cli-runner](https://github.com/rhylthyme/rhylthyme-cli-runner) |
141
+ |---|---|---|
142
+ | What it is | The MCP **server**: the tools an AI assistant calls | A command-line **program**: the `rhylthyme` command |
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-cli-runner` |
145
+ | Language | JavaScript (Node 20+) | Python 3.12+ |
146
+ | Input | A program the assistant builds in conversation | A program file on disk (JSON or YAML) |
147
+ | Validate a program | `validate_program` | `rhylthyme validate` (works offline) |
148
+ | Timing, conflicts, deadlines | `analyze_schedule` | `rhylthyme analyze` (asks the server) |
149
+ | Publish a live timeline | `visualize_schedule` | `rhylthyme publish` (asks the server) |
150
+ | Run a schedule with timers | no: it hands back a link to the web timeline | `rhylthyme run`, an interactive terminal runner |
151
+ | Recorded runs, calibration | reads runs saved to an account | records runs locally; `rhylthyme runs`, `rhylthyme calibrate` |
152
+ | Catalog search, imports, account library | yes | no |
153
+ | 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
+ How they fit together: this tool is one of the MCP server's clients.
156
+ `rhylthyme analyze`, `publish`, `generate` and `mcp-test` are MCP calls to
157
+ `mcp.rhylthyme.com`; `rhylthyme validate`, `run`, `runs` and `calibrate` never
158
+ touch the network. Each has its own validator for the same program schema
159
+ (Python here, JavaScript there), so a program is checked again when it is
160
+ published.
161
+
162
+ Use this repository if you have a program file and a terminal, want timers in
163
+ the terminal, or keep run records. Use rhylthyme-mcp to connect Claude, ChatGPT
164
+ or another assistant, or to read or self-host the server.
165
+
166
+ Two names to keep apart: `rhylthyme-cli-runner` on PyPI is this tool (command
167
+ `rhylthyme`); `rhylthyme-mcp` on PyPI is the server's stdio bridge (command
168
+ `rhylthyme-mcp`). The program format itself is defined in
169
+ [rhylthyme-spec](https://github.com/rhylthyme/rhylthyme-spec), with examples in
170
+ [rhylthyme-examples](https://github.com/rhylthyme/rhylthyme-examples).
171
+
72
172
  ## Installation
73
173
 
74
174
  ### From Source
@@ -197,9 +297,30 @@ rhylthyme run examples/programs/breakfast_schedule.json --environment kitchen
197
297
  rhylthyme run examples/programs/breakfast_schedule.json --no-validate
198
298
  ```
199
299
 
300
+ ### Analyze and Publish a Program
301
+
302
+ Neither needs an account; both are computed by the hosted MCP server.
303
+
304
+ ```bash
305
+ # Total length, critical path, resource conflicts
306
+ rhylthyme analyze examples/programs/breakfast_schedule.json
307
+
308
+ # When does each step start if breakfast is at 8:30?
309
+ rhylthyme analyze examples/programs/breakfast_schedule.json --finish-at 8:30am
310
+
311
+ # A live, shareable timeline with timers; prints the URL
312
+ rhylthyme publish examples/programs/breakfast_schedule.json
313
+ ```
314
+
315
+ `validate` checks structure only. Two steps that want the only oven at the
316
+ same time pass validation; `analyze` reports them, and `--strict` makes that
317
+ a non-zero exit.
318
+
200
319
  ### Optimize a Program
201
320
 
202
- Create an optimized version of a program to reduce resource contention:
321
+ `plan` is an older stagger heuristic that reads the pre-0.2 program format; on
322
+ programs written with `stepId` and `task` (all current examples) it writes the
323
+ program back unchanged. Use `analyze` to find contention.
203
324
 
204
325
  ```bash
205
326
  # Optimize a program and save to new file
@@ -227,6 +348,39 @@ rhylthyme validate-environments
227
348
  rhylthyme environment-info kitchen
228
349
  ```
229
350
 
351
+ ## Claude Skill
352
+
353
+ [`skills/rhylthyme`](skills/rhylthyme) is an [Agent Skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview)
354
+ that teaches Claude (Claude Code, the Claude apps, the Agent SDK) to turn a
355
+ protocol, recipe or run sheet into a validated program with this CLI, check
356
+ it for conflicts, and hand back a live timeline. It follows the layout of
357
+ [K-Dense scientific skills](https://github.com/K-Dense-AI/claude-scientific-skills):
358
+ a `SKILL.md` plus `references/`.
359
+
360
+ The easiest install is the Claude Code plugin, which also connects the hosted
361
+ MCP server:
362
+
363
+ ```
364
+ /plugin marketplace add rhylthyme/rhylthyme-mcp
365
+ /plugin install rhylthyme@rhylthyme
366
+ ```
367
+
368
+ Or copy the skill by hand:
369
+
370
+ ```bash
371
+ # Claude Code, for one user
372
+ mkdir -p ~/.claude/skills
373
+ cp -r skills/rhylthyme ~/.claude/skills/
374
+
375
+ # or for one project
376
+ mkdir -p .claude/skills && cp -r skills/rhylthyme .claude/skills/
377
+ ```
378
+
379
+ Then ask, for example, "time a Western blot so imaging is at 4 pm" or "two PCR
380
+ protocols, one thermocycler: when do I start each?". `tests/test_skill.py`
381
+ validates every program in the skill and checks that every command it names
382
+ exists.
383
+
230
384
  ## Program File Examples
231
385
 
232
386
  ### Simple Breakfast Schedule
@@ -305,6 +459,39 @@ Environment: `RHYLTHYME_TOKEN` (access token, overrides the stored session),
305
459
  `RHYLTHYME_MCP_URL` (default `https://mcp.rhylthyme.com/mcp`),
306
460
  `RHYLTHYME_SITE_URL` (default `https://www.rhylthyme.com`).
307
461
 
462
+ ### `rhylthyme mcp-test`
463
+
464
+ Smoke-tests a Rhylthyme MCP server and exits 1 if any check fails. By
465
+ default it runs the read-only checks against all five hosted endpoints
466
+ (`/mcp`, `/kitchen/mcp`, `/lab/mcp`, `/events/mcp`, `/gym/mcp`):
467
+
468
+ | Check | What it proves |
469
+ |---|---|
470
+ | `initialize` | server name, protocol version, tools/resources/prompts capabilities, instructions |
471
+ | `tools` | core tools and the endpoint's one-shot tools are listed, each with a description and schema |
472
+ | `validate-good` / `validate-bad` | a valid program passes (including a `type: "compound"` trigger); a dangling step reference is rejected with a fix hint |
473
+ | `analyze` | makespan, critical path and wall-clock itinerary for a known program |
474
+ | `resources` / `prompts` | schema and guides are readable, a bundled example validates, `plan_schedule` substitutes its arguments |
475
+ | `json-accept` | clients that do not accept SSE (`*/*`, `application/json`) get JSON, not 406 |
476
+ | `bad-requests` | unknown method gives -32601, unknown tool gives an error, never a 5xx |
477
+ | `login-gate` | account tools refuse without a token and point at `login` |
478
+ | `catalog` | public search returns entries that load with a URL (empty catalog = warning) |
479
+ | `publish` (`--publish`) | `visualize_schedule` returns a URL on the right site; the page and PNG load |
480
+ | `generate` (`--generate`) | the model-backed `import_text` returns a program that validates (needs `login`) |
481
+
482
+ ```bash
483
+ rhylthyme mcp-test # everything read-only
484
+ rhylthyme mcp-test -e lab --publish # one endpoint, plus a real share
485
+ rhylthyme mcp-test -k catalog -k tools # only some checks
486
+ rhylthyme mcp-test --url http://localhost:3000/mcp -e generic
487
+ rhylthyme mcp-test --json --strict # cron / CI: warnings fail too
488
+ ```
489
+
490
+ Requests carry `mcp-test` in the User-Agent so the hosted server logs the
491
+ errors these checks provoke without alerting anyone. The same suite is
492
+ importable (`rhylthyme_cli_runner.remote.checks.run_suite`), and
493
+ `RHYLTHYME_MCP_LIVE=1 pytest tests/test_mcp_checks.py` runs it live.
494
+
308
495
  ### `rhylthyme login` / `logout` / `whoami`
309
496
 
310
497
  `login` opens the rhylthyme.com sign-in page and receives the session on a
@@ -331,9 +518,35 @@ Runs programs with interactive terminal UI.
331
518
  - `--validate / --no-validate`: Validate before running (default: True)
332
519
  - `--auto-start`: Automatically start without manual trigger
333
520
 
521
+ ### `rhylthyme analyze`
522
+
523
+ Total length, critical path, what gates each link of it, resource conflicts
524
+ and tracks that finish early. No sign-in; nothing is published.
525
+
526
+ **Options:**
527
+ - `--finish-at TEXT`: when everything must be finished (`19:00`, `7:30pm` or ISO 8601); prints local clock times per step
528
+ - `--start-at TEXT`: when the program starts; ignored with `--finish-at`
529
+ - `--strict`: exit non-zero when there are resource conflicts
530
+ - `--json`: the full analysis
531
+
532
+ ### `rhylthyme publish`
533
+
534
+ Publishes a program (a file, an http(s) URL, or `-` for standard input) as a
535
+ live timeline and prints its URL. No sign-in.
536
+ A published timeline is reachable by anyone who has the link.
537
+
538
+ **Options:**
539
+ - `-e, --env`: `generic`, `kitchen`, `lab`, `events`, `gym` (default: from `environmentType`)
540
+ - `-q, --quiet`: print only the URL
541
+ - `--json`: `url`, `shareId`, `imageUrl`, `makespanSeconds`, `warnings`
542
+ - `--image PATH`: also save a PNG of the timeline
543
+ - `--open`: open it in a browser
544
+
334
545
  ### `rhylthyme plan`
335
546
 
336
- Optimizes program schedules to reduce resource contention.
547
+ An older stagger heuristic. It reads the pre-0.2 program format and leaves
548
+ programs written with `stepId` and `task` unchanged; use `analyze` to find
549
+ contention and edit the triggers.
337
550
 
338
551
  **Options:**
339
552
  - `--verbose, -v`: Show detailed planning information
@@ -614,6 +827,56 @@ make test-unit # excludes llm
614
827
  RHYLTHYME_EVAL_LIVE=1 pytest -m llm # one real call, opt in
615
828
  ```
616
829
 
830
+
831
+ ### Other models and a spending cap
832
+
833
+ `--model` also accepts models served through an OpenAI-compatible API. The
834
+ provider is picked from the model id and the key is read from the
835
+ environment:
836
+
837
+ | Model id | Provider | Key |
838
+ |---|---|---|
839
+ | `claude-*` | Anthropic | `ANTHROPIC_API_KEY` |
840
+ | `deepseek-*` (e.g. `deepseek-flash`, `deepseek-v4-pro`) | api.deepseek.com | `DEEPSEEK_API_KEY` |
841
+ | `qwen*` | Alibaba DashScope (international) | `DASHSCOPE_API_KEY` |
842
+ | `kimi-*`, `moonshot-*` | Moonshot | `MOONSHOT_API_KEY` |
843
+ | `glm-*` | Z.ai | `ZAI_API_KEY` |
844
+ | anything with a slash, e.g. `deepseek/deepseek-flash` | OpenRouter | `OPENROUTER_API_KEY` |
845
+ | any id, with `RHYLTHYME_EVAL_BASE_URL` set | that endpoint (vLLM, Ollama, ...) | `RHYLTHYME_EVAL_API_KEY`, optional for localhost |
846
+
847
+ Costs come from the price table in `eval/llm.py` (DeepSeek at its peak
848
+ rates, so estimates are upper bounds). For a model the table does not know,
849
+ set `RHYLTHYME_EVAL_PRICE_IN` and `RHYLTHYME_EVAL_PRICE_OUT` in USD per
850
+ million tokens.
851
+
852
+ `eval/run_model_comparison.py` runs a model under a hard budget. It runs one
853
+ gold program at a time, both prompts per program, records each program's
854
+ cost in `eval/models/spend-ledger.json`, and stops before the next program
855
+ could cross `--cap`. The ledger is cumulative across models and
856
+ invocations, and the script refuses a model with no known price (it would
857
+ be recorded as $0 and the cap would never trip).
858
+
859
+ One OpenRouter key reaches OpenAI, Qwen and Meta models (ids with a vendor
860
+ prefix). Run the cheapest first and raise the cumulative cap as you go, so
861
+ no single model can spend the whole allowance. Reasoning models need a
862
+ higher per-call output ceiling than the default 16,000 tokens:
863
+
864
+ ```bash
865
+ export OPENROUTER_API_KEY=...
866
+ L=eval/models/spend-ledger-openrouter.json
867
+ python eval/run_model_comparison.py --ledger $L --model meta-llama/llama-4-maverick --cap 0.80 --first-guess 0.05
868
+ python eval/run_model_comparison.py --ledger $L --model qwen/qwen3.8-flash --cap 2.00 --first-guess 0.08 --max-tokens 64000
869
+ python eval/run_model_comparison.py --ledger $L --model openai/gpt-5.6-luna --cap 4.50 --first-guess 0.15 --max-tokens 64000
870
+ python eval/compare_models.py
871
+ ```
872
+
873
+ ```bash
874
+ export DEEPSEEK_API_KEY=...
875
+ python eval/run_model_comparison.py --model deepseek-flash --cap 6.70 --dry-run
876
+ python eval/run_model_comparison.py --model deepseek-flash --cap 6.70
877
+ python eval/compare_models.py # like-for-like table across every model run so far
878
+ ```
879
+
617
880
  ## Development
618
881
 
619
882
  1. Clone the repository
@@ -9,6 +9,106 @@ rhylthyme login
9
9
  rhylthyme generate "roast chicken, potatoes and green beans for 6" -e kitchen --by 19:00 --with "one oven"
10
10
  ```
11
11
 
12
+ ## A timeline in five commands: a birthday party
13
+
14
+ Parents collect at 4:00. Working backwards from that: when does the cake go in
15
+ the oven, when do the balloons go up, and do the pizzas and the cake ever want
16
+ the oven at the same time?
17
+
18
+ ```bash
19
+ pip install rhylthyme-cli-runner
20
+
21
+ # 1. a program: the party, the kitchen and the setup crew, 13 steps
22
+ curl -sO https://raw.githubusercontent.com/rhylthyme/rhylthyme-cli-runner/main/examples/recipe/birthday-party.json
23
+
24
+ # 2. is it well formed?
25
+ rhylthyme validate birthday-party.json --strict
26
+
27
+ # 3. any clashes, and what time does everything start if we finish at 4?
28
+ rhylthyme analyze birthday-party.json --finish-at 16:00
29
+ ```
30
+
31
+ ```
32
+ **Makespan:** 3h 10m across 3 tracks, 13 steps.
33
+ **Critical path:** Guests arrive → Party games → Pizza → Candles and cake → Piñata, until it breaks → Party bags and goodbyes
34
+ **Binding constraints:** step dependencies only — no resource or in-flight limit gates the critical path.
35
+ **Peak concurrency:** 2 steps at 10:00.
36
+ **Resource conflicts:** none.
37
+
38
+ **Tracks that finish early:** Kitchen (1h 10m early), Setup crew (50m early)
39
+
40
+ Start Sat 12:50, finish Sat 16:00 (local time)
41
+ Sat 12:50 Bake the cake
42
+ Sat 13:00 Balloons and banner
43
+ Sat 13:25 Cake cools
44
+ Sat 13:30 Hang the piñata
45
+ Sat 13:50 Guests arrive
46
+ Sat 14:05 Frost and decorate
47
+ Sat 14:20 Party games
48
+ Sat 14:35 Pizzas in the oven
49
+ Sat 14:50 Fill party bags
50
+ Sat 14:50 Pizza
51
+ Sat 15:15 Candles and cake
52
+ Sat 15:30 Piñata, until it breaks
53
+ Sat 15:45 Party bags and goodbyes
54
+ ```
55
+
56
+ ```bash
57
+ # 4. a live timeline for the fridge door (or a phone): timers, cues, and you end the piñata when it breaks
58
+ rhylthyme publish birthday-party.json --open --image party-preview.png
59
+
60
+ # 5. a picture for the group chat, on the wall clock (needs Node; nothing to install)
61
+ npx -y github:rhylthyme/rhylthyme-timeline birthday-party.json -o birthday-party.png \
62
+ --style web --palette vivid --label-overflow outside --start-at "$(date +%F)T12:50:00"
63
+ ```
64
+
65
+ ![Eighth birthday party: the party, the kitchen and the setup crew on a wall-clock axis](https://raw.githubusercontent.com/rhylthyme/rhylthyme-cli-runner/main/examples/recipe/birthday-party.png)
66
+
67
+ The pizzas go in 15 minutes after the games *start*, so they come out as the
68
+ games end; the party bags get filled while everyone is eating. Try
69
+ `--finish-at 17:30`, or turn the cake into a two-hour fruitcake (`"seconds": "2h"`
70
+ on `bake`) and `analyze` reports the clash: `oven needs 2 but max is 1 from
71
+ 1:45:00 to 2:00:00 (bake, heat-pizza)`. `rhylthyme run birthday-party.json --time-scale 60` rehearses
72
+ the whole afternoon in the terminal at a minute per second.
73
+
74
+ ## This repository and rhylthyme-mcp
75
+
76
+ Two repositories do related things and are easy to confuse. This one is the
77
+ **command a person types**. [rhylthyme-mcp](https://github.com/rhylthyme/rhylthyme-mcp)
78
+ is the **server an AI assistant talks to**.
79
+
80
+ | | [rhylthyme-mcp](https://github.com/rhylthyme/rhylthyme-mcp) | [rhylthyme-cli-runner](https://github.com/rhylthyme/rhylthyme-cli-runner) |
81
+ |---|---|---|
82
+ | What it is | The MCP **server**: the tools an AI assistant calls | A command-line **program**: the `rhylthyme` command |
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-cli-runner` |
85
+ | Language | JavaScript (Node 20+) | Python 3.12+ |
86
+ | Input | A program the assistant builds in conversation | A program file on disk (JSON or YAML) |
87
+ | Validate a program | `validate_program` | `rhylthyme validate` (works offline) |
88
+ | Timing, conflicts, deadlines | `analyze_schedule` | `rhylthyme analyze` (asks the server) |
89
+ | Publish a live timeline | `visualize_schedule` | `rhylthyme publish` (asks the server) |
90
+ | Run a schedule with timers | no: it hands back a link to the web timeline | `rhylthyme run`, an interactive terminal runner |
91
+ | Recorded runs, calibration | reads runs saved to an account | records runs locally; `rhylthyme runs`, `rhylthyme calibrate` |
92
+ | Catalog search, imports, account library | yes | no |
93
+ | 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
+ How they fit together: this tool is one of the MCP server's clients.
96
+ `rhylthyme analyze`, `publish`, `generate` and `mcp-test` are MCP calls to
97
+ `mcp.rhylthyme.com`; `rhylthyme validate`, `run`, `runs` and `calibrate` never
98
+ touch the network. Each has its own validator for the same program schema
99
+ (Python here, JavaScript there), so a program is checked again when it is
100
+ published.
101
+
102
+ Use this repository if you have a program file and a terminal, want timers in
103
+ the terminal, or keep run records. Use rhylthyme-mcp to connect Claude, ChatGPT
104
+ or another assistant, or to read or self-host the server.
105
+
106
+ Two names to keep apart: `rhylthyme-cli-runner` on PyPI is this tool (command
107
+ `rhylthyme`); `rhylthyme-mcp` on PyPI is the server's stdio bridge (command
108
+ `rhylthyme-mcp`). The program format itself is defined in
109
+ [rhylthyme-spec](https://github.com/rhylthyme/rhylthyme-spec), with examples in
110
+ [rhylthyme-examples](https://github.com/rhylthyme/rhylthyme-examples).
111
+
12
112
  ## Installation
13
113
 
14
114
  ### From Source
@@ -137,9 +237,30 @@ rhylthyme run examples/programs/breakfast_schedule.json --environment kitchen
137
237
  rhylthyme run examples/programs/breakfast_schedule.json --no-validate
138
238
  ```
139
239
 
240
+ ### Analyze and Publish a Program
241
+
242
+ Neither needs an account; both are computed by the hosted MCP server.
243
+
244
+ ```bash
245
+ # Total length, critical path, resource conflicts
246
+ rhylthyme analyze examples/programs/breakfast_schedule.json
247
+
248
+ # When does each step start if breakfast is at 8:30?
249
+ rhylthyme analyze examples/programs/breakfast_schedule.json --finish-at 8:30am
250
+
251
+ # A live, shareable timeline with timers; prints the URL
252
+ rhylthyme publish examples/programs/breakfast_schedule.json
253
+ ```
254
+
255
+ `validate` checks structure only. Two steps that want the only oven at the
256
+ same time pass validation; `analyze` reports them, and `--strict` makes that
257
+ a non-zero exit.
258
+
140
259
  ### Optimize a Program
141
260
 
142
- Create an optimized version of a program to reduce resource contention:
261
+ `plan` is an older stagger heuristic that reads the pre-0.2 program format; on
262
+ programs written with `stepId` and `task` (all current examples) it writes the
263
+ program back unchanged. Use `analyze` to find contention.
143
264
 
144
265
  ```bash
145
266
  # Optimize a program and save to new file
@@ -167,6 +288,39 @@ rhylthyme validate-environments
167
288
  rhylthyme environment-info kitchen
168
289
  ```
169
290
 
291
+ ## Claude Skill
292
+
293
+ [`skills/rhylthyme`](skills/rhylthyme) is an [Agent Skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview)
294
+ that teaches Claude (Claude Code, the Claude apps, the Agent SDK) to turn a
295
+ protocol, recipe or run sheet into a validated program with this CLI, check
296
+ it for conflicts, and hand back a live timeline. It follows the layout of
297
+ [K-Dense scientific skills](https://github.com/K-Dense-AI/claude-scientific-skills):
298
+ a `SKILL.md` plus `references/`.
299
+
300
+ The easiest install is the Claude Code plugin, which also connects the hosted
301
+ MCP server:
302
+
303
+ ```
304
+ /plugin marketplace add rhylthyme/rhylthyme-mcp
305
+ /plugin install rhylthyme@rhylthyme
306
+ ```
307
+
308
+ Or copy the skill by hand:
309
+
310
+ ```bash
311
+ # Claude Code, for one user
312
+ mkdir -p ~/.claude/skills
313
+ cp -r skills/rhylthyme ~/.claude/skills/
314
+
315
+ # or for one project
316
+ mkdir -p .claude/skills && cp -r skills/rhylthyme .claude/skills/
317
+ ```
318
+
319
+ Then ask, for example, "time a Western blot so imaging is at 4 pm" or "two PCR
320
+ protocols, one thermocycler: when do I start each?". `tests/test_skill.py`
321
+ validates every program in the skill and checks that every command it names
322
+ exists.
323
+
170
324
  ## Program File Examples
171
325
 
172
326
  ### Simple Breakfast Schedule
@@ -245,6 +399,39 @@ Environment: `RHYLTHYME_TOKEN` (access token, overrides the stored session),
245
399
  `RHYLTHYME_MCP_URL` (default `https://mcp.rhylthyme.com/mcp`),
246
400
  `RHYLTHYME_SITE_URL` (default `https://www.rhylthyme.com`).
247
401
 
402
+ ### `rhylthyme mcp-test`
403
+
404
+ Smoke-tests a Rhylthyme MCP server and exits 1 if any check fails. By
405
+ default it runs the read-only checks against all five hosted endpoints
406
+ (`/mcp`, `/kitchen/mcp`, `/lab/mcp`, `/events/mcp`, `/gym/mcp`):
407
+
408
+ | Check | What it proves |
409
+ |---|---|
410
+ | `initialize` | server name, protocol version, tools/resources/prompts capabilities, instructions |
411
+ | `tools` | core tools and the endpoint's one-shot tools are listed, each with a description and schema |
412
+ | `validate-good` / `validate-bad` | a valid program passes (including a `type: "compound"` trigger); a dangling step reference is rejected with a fix hint |
413
+ | `analyze` | makespan, critical path and wall-clock itinerary for a known program |
414
+ | `resources` / `prompts` | schema and guides are readable, a bundled example validates, `plan_schedule` substitutes its arguments |
415
+ | `json-accept` | clients that do not accept SSE (`*/*`, `application/json`) get JSON, not 406 |
416
+ | `bad-requests` | unknown method gives -32601, unknown tool gives an error, never a 5xx |
417
+ | `login-gate` | account tools refuse without a token and point at `login` |
418
+ | `catalog` | public search returns entries that load with a URL (empty catalog = warning) |
419
+ | `publish` (`--publish`) | `visualize_schedule` returns a URL on the right site; the page and PNG load |
420
+ | `generate` (`--generate`) | the model-backed `import_text` returns a program that validates (needs `login`) |
421
+
422
+ ```bash
423
+ rhylthyme mcp-test # everything read-only
424
+ rhylthyme mcp-test -e lab --publish # one endpoint, plus a real share
425
+ rhylthyme mcp-test -k catalog -k tools # only some checks
426
+ rhylthyme mcp-test --url http://localhost:3000/mcp -e generic
427
+ rhylthyme mcp-test --json --strict # cron / CI: warnings fail too
428
+ ```
429
+
430
+ Requests carry `mcp-test` in the User-Agent so the hosted server logs the
431
+ errors these checks provoke without alerting anyone. The same suite is
432
+ importable (`rhylthyme_cli_runner.remote.checks.run_suite`), and
433
+ `RHYLTHYME_MCP_LIVE=1 pytest tests/test_mcp_checks.py` runs it live.
434
+
248
435
  ### `rhylthyme login` / `logout` / `whoami`
249
436
 
250
437
  `login` opens the rhylthyme.com sign-in page and receives the session on a
@@ -271,9 +458,35 @@ Runs programs with interactive terminal UI.
271
458
  - `--validate / --no-validate`: Validate before running (default: True)
272
459
  - `--auto-start`: Automatically start without manual trigger
273
460
 
461
+ ### `rhylthyme analyze`
462
+
463
+ Total length, critical path, what gates each link of it, resource conflicts
464
+ and tracks that finish early. No sign-in; nothing is published.
465
+
466
+ **Options:**
467
+ - `--finish-at TEXT`: when everything must be finished (`19:00`, `7:30pm` or ISO 8601); prints local clock times per step
468
+ - `--start-at TEXT`: when the program starts; ignored with `--finish-at`
469
+ - `--strict`: exit non-zero when there are resource conflicts
470
+ - `--json`: the full analysis
471
+
472
+ ### `rhylthyme publish`
473
+
474
+ Publishes a program (a file, an http(s) URL, or `-` for standard input) as a
475
+ live timeline and prints its URL. No sign-in.
476
+ A published timeline is reachable by anyone who has the link.
477
+
478
+ **Options:**
479
+ - `-e, --env`: `generic`, `kitchen`, `lab`, `events`, `gym` (default: from `environmentType`)
480
+ - `-q, --quiet`: print only the URL
481
+ - `--json`: `url`, `shareId`, `imageUrl`, `makespanSeconds`, `warnings`
482
+ - `--image PATH`: also save a PNG of the timeline
483
+ - `--open`: open it in a browser
484
+
274
485
  ### `rhylthyme plan`
275
486
 
276
- Optimizes program schedules to reduce resource contention.
487
+ An older stagger heuristic. It reads the pre-0.2 program format and leaves
488
+ programs written with `stepId` and `task` unchanged; use `analyze` to find
489
+ contention and edit the triggers.
277
490
 
278
491
  **Options:**
279
492
  - `--verbose, -v`: Show detailed planning information
@@ -554,6 +767,56 @@ make test-unit # excludes llm
554
767
  RHYLTHYME_EVAL_LIVE=1 pytest -m llm # one real call, opt in
555
768
  ```
556
769
 
770
+
771
+ ### Other models and a spending cap
772
+
773
+ `--model` also accepts models served through an OpenAI-compatible API. The
774
+ provider is picked from the model id and the key is read from the
775
+ environment:
776
+
777
+ | Model id | Provider | Key |
778
+ |---|---|---|
779
+ | `claude-*` | Anthropic | `ANTHROPIC_API_KEY` |
780
+ | `deepseek-*` (e.g. `deepseek-flash`, `deepseek-v4-pro`) | api.deepseek.com | `DEEPSEEK_API_KEY` |
781
+ | `qwen*` | Alibaba DashScope (international) | `DASHSCOPE_API_KEY` |
782
+ | `kimi-*`, `moonshot-*` | Moonshot | `MOONSHOT_API_KEY` |
783
+ | `glm-*` | Z.ai | `ZAI_API_KEY` |
784
+ | anything with a slash, e.g. `deepseek/deepseek-flash` | OpenRouter | `OPENROUTER_API_KEY` |
785
+ | any id, with `RHYLTHYME_EVAL_BASE_URL` set | that endpoint (vLLM, Ollama, ...) | `RHYLTHYME_EVAL_API_KEY`, optional for localhost |
786
+
787
+ Costs come from the price table in `eval/llm.py` (DeepSeek at its peak
788
+ rates, so estimates are upper bounds). For a model the table does not know,
789
+ set `RHYLTHYME_EVAL_PRICE_IN` and `RHYLTHYME_EVAL_PRICE_OUT` in USD per
790
+ million tokens.
791
+
792
+ `eval/run_model_comparison.py` runs a model under a hard budget. It runs one
793
+ gold program at a time, both prompts per program, records each program's
794
+ cost in `eval/models/spend-ledger.json`, and stops before the next program
795
+ could cross `--cap`. The ledger is cumulative across models and
796
+ invocations, and the script refuses a model with no known price (it would
797
+ be recorded as $0 and the cap would never trip).
798
+
799
+ One OpenRouter key reaches OpenAI, Qwen and Meta models (ids with a vendor
800
+ prefix). Run the cheapest first and raise the cumulative cap as you go, so
801
+ no single model can spend the whole allowance. Reasoning models need a
802
+ higher per-call output ceiling than the default 16,000 tokens:
803
+
804
+ ```bash
805
+ export OPENROUTER_API_KEY=...
806
+ L=eval/models/spend-ledger-openrouter.json
807
+ python eval/run_model_comparison.py --ledger $L --model meta-llama/llama-4-maverick --cap 0.80 --first-guess 0.05
808
+ python eval/run_model_comparison.py --ledger $L --model qwen/qwen3.8-flash --cap 2.00 --first-guess 0.08 --max-tokens 64000
809
+ python eval/run_model_comparison.py --ledger $L --model openai/gpt-5.6-luna --cap 4.50 --first-guess 0.15 --max-tokens 64000
810
+ python eval/compare_models.py
811
+ ```
812
+
813
+ ```bash
814
+ export DEEPSEEK_API_KEY=...
815
+ python eval/run_model_comparison.py --model deepseek-flash --cap 6.70 --dry-run
816
+ python eval/run_model_comparison.py --model deepseek-flash --cap 6.70
817
+ python eval/compare_models.py # like-for-like table across every model run so far
818
+ ```
819
+
557
820
  ## Development
558
821
 
559
822
  1. Clone the repository
@@ -19,7 +19,7 @@ def read_readme():
19
19
 
20
20
  setup(
21
21
  name="rhylthyme-cli-runner",
22
- version="0.2.0a0",
22
+ version="0.2.3a0",
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",
@@ -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.0a0"
9
+ __version__ = "0.2.3a0"
10
10
  __author__ = "Rhylthyme Team"
11
11
  __description__ = "CLI runner for Rhylthyme real-time program schedules"
12
12
 
@@ -321,7 +321,10 @@ def _live(
321
321
  if fake_client_path:
322
322
  with open(fake_client_path, "r", encoding="utf-8") as handle:
323
323
  fake_responses = json.load(handle)
324
- client = make_client(fake_responses)
324
+ try:
325
+ client = make_client(fake_responses, model=model)
326
+ except RuntimeError as exc:
327
+ raise click.UsageError(str(exc))
325
328
 
326
329
  gold_set = _load_gold(gold_dir)
327
330
  out_root = Path(out_dir)