Command-line interface¶
Installing harel puts a single harel command on your PATH that wraps the static tooling,
an in-memory run, the formatter, and the language server.
harel new FILE [NAME] [--force] # scaffold a starter .stm (validates + runs as-is)
harel validate FILE [NAME] # parse + validate; exit 1 on errors
harel render FILE [NAME] [--mermaid] # PlantUML (default) or Mermaid
harel list FILE # machines / fragments / events in a file
harel hash FILE [NAME] # each machine's version and source digest
harel publish DIR # publish a directory's machines to the store
harel run FILE [NAME] [-e KIND[:JSON]] # drive a machine with events (in-memory)
harel fmt FILES... [--check|--diff] # format .stm files
harel lsp # start the DSL language server (stdio)
harel monitor [--definitions-dir DIR] # the monitoring TUI (needs the `tui` extra) — see Monitor
harel purge --older-than AGE [...] # delete finished executions older than AGE (real store)
harel --version
NAME selects the machine when a file declares more than one.
Starting from scratch¶
harel new writes a small, commented machine that validates and runs with no setup —
zero to a working state machine in one command:
$ harel new approval.stm
created approval.stm (machine approval)
next:
harel validate approval.stm
harel run approval.stm -e Submit -e Approve
$ harel run approval.stm -e Submit -e Approve
(start) -> Draft
Submit -> Review
Approve -> Approved
status: DONE outcome: success
The machine name defaults to the file name (sanitised to a valid identifier); pass NAME
to override, and --force to overwrite an existing file.
Examples¶
Validate a machine and render it:
$ harel validate examples/place_order/order.stm order
order: ok
$ harel render examples/place_order/order.stm order --mermaid
stateDiagram-v2
[*] --> Cart
...
Drive a machine with a sequence of events (each -e is one event; attach data as
KIND:'{...}' for guarded transitions):
$ harel run examples/place_order/order.stm order \
-e PlaceOrder -e PaymentAuthorized -e Picked -e Packed -e Dispatched -e Delivered
(start) -> Cart
PlaceOrder -> AwaitingPayment
PaymentAuthorized -> Fulfilling.Picking
...
status: DONE outcome: success
run resolves a machine’s action modules from the working directory (for package-qualified
paths like pkg.mod.fn, run it from your project root) and from the .stm file’s own
directory. Seed the initial context with --seed '{"items": [...]}', and add --validate to
check the machine before running.
Versions: hash and publish¶
A machine changed while executions run on it is a new version (machine order version 2), and
the store keeps every version that was published — see
changing a machine and the
tutorial.
harel hash prints each machine’s version and the digest of its source — its block, its file’s
events, guards, bindings and fragments, and the files it imports; not comments or layout. A
digest that changed under the same version is a change that needs a bump:
$ harel hash definitions/order.stm
order version 2 digest f8dfd89d190503cc
harel publish DIR publishes the machines of every *.stm at the top of DIR — each with the
files it imports — to the store the HAREL_STORE_* environment names, as a worker does when it
starts. A version stored already with the same source is left as it is; one stored with other
content is an error, and the exit code is 1:
$ harel publish definitions/
order version 2 digest f8dfd89d190503cc
$ harel publish definitions/ # after editing order.stm without a bump
error: machine 'order' version 2 is published with other content (digest f8dfd89d190503cc, here 452adb0e5ef64291): give the changed machine a new version
Run it in the deploy pipeline before the new workers roll out: a conflict stops the deploy there. The workers publish the same way when they start, so the step is a safeguard, not a requirement.
Purging finished executions¶
harel purge permanently deletes the finished execution trees (root, regions, invokes and all
their store rows) whose root finished at least AGE ago — 30d, 12h, 90m, 45s, 2w. It
works on the real store, configured from the same HAREL_STORE_* environment as the worker and
the monitor, so run it as a scheduled job alongside the workers rather than inside them:
$ harel purge --older-than 30d --dry-run # report only
would purge: 1284
$ harel purge --older-than 30d --archive /backups/harel.jsonl
purged: 1284
--archive PATHappends each tree to a JSONL file (one fsynced line per tree) before deleting it.--status done/--status cancellednarrows the candidates (default: both).FAILEDdead letters are never purged —terminateone to abandon it first.--limit Ncaps one run;-vlists the purged root ids.Executions written before
finished_atwas recorded carry none, and are skipped (and counted) unless--include-undated.A tree that can’t be purged (a member still live, or changed concurrently) is reported on stderr and skipped; the exit code is 1 if any was.
See purge for the semantics.
fmt and lsp are passthroughs: harel fmt --check **/*.stm and harel lsp behave exactly
like the standalone harel-fmt / harel-lsp entry points.
Verified¶
The commands behave as shown — exercised in CI:
from harel.cli import main
assert main(["list", "test/data/order.stm"]) == 0
assert main(["validate", "test/data/order.stm", "order"]) == 0
assert main(["render", "test/data/order.stm", "order", "--mermaid"]) == 0
assert main(["hash", "test/data/order.stm"]) == 0