Skip to main content
In Omnibeds, every action on travel data (search tools, quotes, bookings, trips, travellers, agency settings) is a command defined with defineCommand in lib/app/commands/. The command carries its own checks, so the web app, a scheduled job and a future AI assistant all get exactly the same rules.

What a command declares

Risk levels

The risk level is there for the AI assistant: it may run read commands on its own, anything above needs the person’s confirmation, and money and irreversible always come after a preview the person has seen.

The checks execute() runs

Every call goes through execute() in lib/app/command.ts, in this order. The first check that fails stops the call.
1

The module is on

If the command belongs to a module the agency turned off, it answers MODULE_OFF (403).
2

The user has the permission

Otherwise it answers FORBIDDEN (403). Owners have every permission; admins have all but turning modules on and off; agents have travel.use.
3

The input matches the schema

Otherwise it answers INVALID_REQUEST (400), naming the first bad field and keeping every issue.
4

Presenter mode, for confidential suppliers

Inside the agency’s scope, if confidential(input) is true and presenter mode isn’t unlocked, it answers PRESENTER_REQUIRED (403).
5

Run

run(input, ctx) does the work inside the agency’s scope. ctx holds the actor, whether presenter mode is on, and who is acting: ui, job or agent.

Routes are one line

A route only connects a URL to a command:
app/api/cancel/route.ts
commandRoute reads the input from the body (or the query string for GET), merges in route params such as :reference, runs the command and returns JSON. Two options cover the rest: from chooses where the input comes from, and respond shapes the answer (a 201 for something created, or a PDF for a document). When one URL serves several steps, such as preview, book and cancel for a rebooking, stepRoute("step", { ... }) picks the command by a body field.

Add a command

1

Define it

Add it to the right file in lib/app/commands/ with defineCommand, a name, a risk, an input schema and a run that calls the domain service.
2

Give it a route

Add a one-line route file that calls commandRoute (or add it to an existing stepRoute).
3

Register it for screens

Add its method and path to ROUTES in lib/client/api.ts. The boundaries test fails until every command has a route there.
4

Call it from a screen

Use api("yourCommand", input, doing). The input and answer types come from the command itself.

The catalogue

lib/app/catalog.ts lists every command (COMMANDS) and finds one by name (commandNamed). It’s the list a scheduled job or an assistant uses to know what it can do and how risky each action is.