Rules an agent can rely on
The rules this CLI is built to, so that an agent can drive it without guessing.
- Self-describing. Every command’s
--helpsays what it does, what it needs, what it returns, and what to do next. - Structured output always available. Every command takes
--json. Nothing has to be parsed out of human text. - Errors say how to fix them. Every error carries a
fixfield: an operative sentence, not a diagnosis. - Idempotent and safe by default. Every
POSTthe CLI makes carries anIdempotency-Key. Destructive operations need--yes. - Test and live separated and visible. Every output declares its
environment. The default is test; live needs--live, and a live key used without it is refused before any request leaves the process. - No hidden interactivity. Every prompt has a flag that replaces it. When stdout is not a
terminal the CLI never asks: it fails with a
fixinstead of hanging. - Verifiability. Every action has a way to check its outcome:
bookrail diffafter a push,bookrail doctorwhen something is wrong,bookrail <entity> getafter a write.
Recommended order of operations
Section titled “Recommended order of operations”bookrail doctor --json # what is configured, what is missingbookrail init --template <vertical> --json # write bookrail.config.tsbookrail push --dry-run --json # read `data.plan` before applyingbookrail push --json # applybookrail diff --json # data.has_changes must be falsebookrail services list --json # read back the svc_ ids
bookrail availability --service svc_... --from ... --to ... --json # what is bookablebookrail availability --service svc_... --from ... --to ... --explain --json # and why notbookrail bookings create --service svc_... --start ... --customer-email ... --jsonbookrail bookings get bk_... --json # close the loop on every writebookrail schema config --json gives the JSON Schema to write a config against.
bookrail examples <vertical> --json gives a complete, working model plus the calls that
follow it. bookrail docs <topic> --markdown prints this documentation offline.
Mapping a business onto the model
Section titled “Mapping a business onto the model”Ask four questions, in this order:
- What is sold? That is a Service, and it needs exactly one duration form.
- What has to be free for it to happen? Those are Resources, and the Service’s requirements. If several must be free at the same time, list several requirements.
- How many at once? That is
capacityon the resource, andquantityon the booking. If the count lives on one thing (the seats of a class) put the capacity there and make the other requirementsconsumes: "whole". - What are the rules about money and time? That is a Policy.
Do not model a “slot”: slots are computed, never stored.
Things that will bite
Section titled “Things that will bite”- Ids in a config are logical. The
svc_...identifier only exists after a push. - A push never touches an object with no
metadata.config_id. If you created objects through the API,bookrail pullwill show them but pushing that file will create copies. To take them over, usebookrail pull --adopt(stamps all of them) orbookrail push --adopt <kind>:<config_id>=<remote_id>(one, named by its remote id). Nothing is ever adopted by name: names are not unique. align_toandslot_intervaldecide which instants exist. A service with no grid accepts any instant inside the opening hours.- Amounts are integers in the minor unit:
3000is 30.00 EUR. - Instants in must carry an explicit offset; instants out are UTC. A bare date is refused by the CLI before the request: midnight is not the same instant in every time zone.
bookrail webhooks testexits 0 even when the endpoint answers 500. The delivery happened, and that is the answer: branch ondata.status(succeeded/failed). The same rule applies tobookrail diffanddata.has_changes.--followandwebhooks listenneed--maxor--durationwhen combined with--json: one envelope cannot be printed by a loop that never ends.