Commands for agents
The engine commands a coding agent uses to read, change and verify a Meridian project (describe, guide, docs, help --json, the dev verbs, check, schema plan and apply, render --json, a11y audit, content, seed and mcp), what each returns, what it needs and when to use it.
Every command takes --project <dir> (default: the current directory) and --json for machine-readable output. Exit codes are the same everywhere:
| Exit | Meaning |
|---|---|
0 | Success |
1 | Error: the command could not do its work (a page that answered 400 or more, a database it could not reach) |
2 | Changes pending (schema plan, migrate status), or refused for want of confirmation (a destructive plan) |
3 | Configuration invalid, or a verb’s argument that names something that does not exist (--after names "slug", which is not a field) |
4 | Degraded but running (health) |
An unknown command, an unknown flag or a missing argument exits 1, not 3, although help --json lists bad invocations under 3. Some --help texts cite design documents by file name or number (such as 07-auth-and-permissions.md or ADR-0019); those are not in the handbook and you cannot open them. Search the handbook for the topic instead.
What each command needs
| Needs | Means | Commands |
|---|---|---|
| Nothing | Runs in any directory | docs, help, version, describe fieldtypes, config schema |
| The project | Reads the files; unset ${VAR}s are accepted | describe project, describe type, guide, template api, media transforms, check --no-db, the dev verbs without --scaffold |
| Configuration | Every ${VAR} without a default must be set; a placeholder is enough | config render, config validate, describe template, seed verify, seed write, new, a11y statement, dev route add --scaffold, portal check |
| A database | A migrated database with the schema applied | check (it fails when it cannot connect; use check --no-db without one), schema plan, schema apply, render, describe route, a11y audit, seed apply, content, user, crm, mcp, api resolve |
“A placeholder is enough” means any value of the right form: DATABASE_URL=postgres://placeholder@localhost/placeholder, and once engine.yaml names it, SIGNING_KEY of at least 32 characters (server.security.signing_key must be at least 32 characters otherwise). The engine never reads a .env file; export the variables.
Which command to reach for:
flowchart TD
q{"What do you need?"} --> learn["To know a feature"]
q --> proj["To know this project"]
q --> chg["To change the project"]
q --> ver["To prove a change"]
learn --> docs["The handbook:<br/>llms.txt, then the page"]
proj --> desc["guide, describe project,<br/>describe type"]
chg --> verb{"Is there a dev verb?"}
verb -- yes --> dev["dev ... --apply"]
verb -- no --> yaml["Edit YAML, then check --fix"]
ver --> db{"Database?"}
db -- no --> nodb["check --no-db, seed verify"]
db -- yes --> full["check, render --json,<br/>a11y audit"]Learn the engine and the project
| Command | Returns | Needs | Use it |
|---|---|---|---|
help --json | Every command: path, use, short, arguments, flags (name, type, default, usage), plus global_flags and exit_codes | Nothing | Before running a command you have not used, to get its exact flags |
guide | This project’s rules, the authoring verbs, every site with its domains, locales, routes and endpoints, every type with its fields and ids, and the template globals and filters | The project | At the start of every session, and after changing the model. --out FILE writes it, --skill adds skill front matter |
describe project --json | Networks, sites, locales, types, routes, endpoints, reports, tiers, partner roles and staff roles, with ids | The project | To find an element’s key or id |
describe type <key> --json | A type’s fields at every depth: id, type, parameters, options, groups, blocks, sites, member rules. A core type lists its core fields marked core | The project | Before writing a template or seed file for the type |
describe fieldtypes --json | Every field type: what it stores, its parameters, its default translate mode | Nothing | Before adding a field, to get its parameters right |
describe template <name> | The file a template name resolves to on a site’s chain, what it extends and includes, the type it renders | Configuration | When a site has its own template root and you are unsure which file wins |
describe route <path> --json | Which route answers a path, the template, and the whole template context with real values, plus the functions, filters and components available | A database | Before writing a template: you see exactly what it receives |
template api --json | Every global, filter, tag, field type, type, route and component of a site, with documentation (--site) | The project | To check a function’s arguments |
config schema --file <kind> | The JSON Schema of one configuration file kind: engine, locales, strings, site, routes, endpoints, images, schema, memberships, portal and the rest | Nothing | To check a key before writing it. The argument is the kind, not the file name |
config render | The configuration as the engine resolved it, defaults filled, secrets redacted | Configuration | To see the value a key really has |
version | Version, bundle and modules, the build used for extensions, contract versions | Nothing | To confirm the bundle |
Change the project
The dev verbs
Each verb edits the configuration, keeping comments, writes the new element’s id and checks the configuration, writing nothing when the configuration itself would be invalid. Two things it does not hold back:
- Templates a new element names.
dev route add(with an--index) anddev endpoint addwrite a valid route or endpoint whose templates do not exist yet, andcheckfails until you write them. - A failure after the write.
dev route add --scaffoldwritesroutes.yaml, then loads the whole configuration to write the template; with a${VAR}unset it exits 3 having written the route but not the template, and a second run saysnothing to change. Write the template withengine-dmo new template <type>/entry.html.
So run engine-dmo check after every verb. Every verb takes --dry-run (print the diff, write nothing) and --apply (apply the schema plan afterwards when it is not destructive; a destructive plan exits 2 and is left for a person). After each change the verbs regenerate AGENTS.md and the agent skills when the project has them.
| Verb | Does | Key flags |
|---|---|---|
dev type add <key> | A custom type with title, slug and summary | --label, --plural, --site (one site’s own type), --sites |
dev field add <type> <name> | A field | --type, --label, --required, --index, --translate, --options a,b,c, --param key=value (repeatable), --group, --after, --help-text, --site. On a core type, --after can name only a field in your extends: file, never a core field: added fields always follow the core ones |
dev field set <type> <name> | A field’s label, help text or a parameter | --label, --help-text, --param |
dev field rename <type> <old> <new> | New handle, same id and values; the old handle stays an alias | --no-alias |
dev field remove <type> <name> | Removes a field; values stay stored under its id | |
dev option add <type> <field> <value> | An option on a select field, or a style on a link field | --label, --class |
dev group add <type> <key> | An editor group | --label, --fields |
dev route add <type> <uri> | A route | --index <path>, --site, --scaffold (writes entry.html, never index.html; needs configuration) |
dev site add <key>, dev site rename <old> <new> | A site directory with site.yaml and routes.yaml; a rename keeps the id and redirects old URLs | --name, --domain, --locales, --network |
dev locale add <code> | A locale in locales.yaml, enabled on a site | --label, --fallback, --site |
dev config set <file> <path> <value>, dev config unset <file> <path> | One value in one file, path relative to config/, such as dev config set engine.yaml search.driver meilisearch. List indexes are not supported | |
dev endpoint add <key> | A declared endpoint from a recipe | --from recipe:enquiry or recipe:newsletter, --subject <type>, --site |
dev network add <key> | A network of sites sharing organisations and partners | --name, --site |
dev source rename, dev importer rename, dev webhook rename | Rename an outbound declaration, keeping its id | --site |
dev tier add <key>, dev tier rename | A membership tier (engine-dmo) | --after, --label |
dev role add <key> | A partner role (engine-dmo) | --label, --can |
dev portal screen add <key>, dev portal action add <subject.verb> | A portal screen or action (engine-dmo) | See Partner portal |
dev slot add <key> | A promotion slot (engine-dmo) | --types, --capacity, --price, --currency and more |
dev up | Serves the project in development mode and, on every configuration change, writes missing ids, checks, plans, applies a safe plan and reloads | --listen. Long-running: run it in the background or not at all |
dev status --json | What dev up last found: the check, the plan, live aliases, the address, oversized images | Exits 1 when dev up has not run in the project |
With --json, a verb that fails prints an object with command, exit and error.
Scaffolding
| Command | Writes |
|---|---|
new type <key> | A schema file and its template |
new template <name> | A starter template, such as walk/index.html; --from-shared copies the shared template into a site’s root |
new component <name> | templates/_shared/components/<name>/component.yaml, <name>.html and <name>.css |
new block <key> | A block template and the schema stub declaring it |
Each takes --site <key> for a site’s own template root and --force to overwrite. Each needs configuration. The scaffolds are starting points: new template <type>/index.html writes an empty body block with no h1, which fails the accessibility checks until you fill it.
Ids
check --fix writes an id for every element that has none, keeping comments, then checks. Run it after any hand edit that adds an element.
Verify
| Command | Returns | Needs |
|---|---|---|
check | Every problem in configuration, schema, templates and deployment, each at file:line:col with the source line, a caret, hints and an MRD- code. Exit 0 valid, 3 invalid. --json gives valid, errors, warnings and diagnostics. Template warnings are printed in the text output only: the summary’s warning count and --json leave them out, so read the text | A database; --no-db skips the database checks (drift) |
check --no-db | The same without a database; unset ${VAR}s are accepted | The project |
check --strict | Configuration warnings fail too (exit 3); template warnings still do not | As check |
ENVIRONMENT=production check --no-db | The production rules | Nothing |
schema plan | The changes schema apply would make, one per line with a mark (+ ~ - > = !) and [DESTRUCTIVE: ...] where data or access is lost. Exit 2 when changes are pending. --json gives changes (each op, resource, key, detail) and the plan’s digest | A database |
schema apply | Applies the plan in one transaction; refuses a destructive plan without approval | A database |
seed verify | Whether the seed loads, names only fields and locales that exist, and names only documents the seed has. Field values are not checked: seed apply refuses a bad one | Configuration |
render <path> | The page’s HTML on standard output; status, image report and render errors on standard error. Exit 1 on a status of 400 or more or a render error, and exit 0 on a redirect. --site, --locale (the path is then unprefixed), --host, --fragment | A database |
render <path> --json | status, site, locale, request, kind (entry, index, redirect, not_found …), template, html, errors and warnings (each file, line, column, severity, code, message, source), queries (each statement’s summary and time), assets, a11y (findings as sentences) and images (each image’s transform, ladder, formats and derivatives). A 301 is rendered as a page: it exits 0 with a non-empty a11y, so read status first | A database |
a11y audit | Renders 20 pages spread across the site’s routes, or with --all every page the sitemap would list plus the home page (whether or not seo.sitemap.enabled is on), or the --path pages named (repeatable, query strings allowed), and checks each: a page that answers 400 or more is a failure. A --path that redirects is audited as a page and fails html-lang and page-title, so name the address it redirects to. Exit 1 on any failure. --json gives pages (each path, status, findings) | A database |
a11y statement | An accessibility statement scaffold from site.yaml (--out, --format) | Configuration |
template test | Renders the pages tests/templates/*.test.yaml name and checks each | A seeded database |
portal check | The project’s portal templates against the portal context (engine-dmo) | Configuration |
media transforms | Each image transform resolved, with its width ladder and the formats this build serves | The project |
api resolve <path> | What the content API answers for a path | A database |
Content
| Command | Does | Needs |
|---|---|---|
content create --type <key> '<json>' | Creates a document from field values by handle, validated as the admin validates a save; --publish publishes the locale through the publish checks; --locale, --site; --data - reads standard input. Prints the new id | A database |
content update <id> '<json>' | Merges values over a locale’s working revision; null clears a field; --publish | A database |
content publish --id <id> | Publishes a locale (--locale) or every locale whose checks pass (--all-locales) | A database |
content get <id> --json | A document’s working and published values per locale | A database |
content list --type <key> | One line per document and locale: id, type, locale, status, title | A database |
seed apply | Loads seed/ through the same validation and history as an editor; refuses a database that holds content | A database with the schema applied |
seed reset --confirm | Removes all content, media and analytics; keeps users and configuration. Refused in production | A database |
seed generate --type <key> --count <n> | Valid documents from word lists and placeholder images (--publish, --locale, --site, --near lat,lng, --seed) | A database |
user create --email <e> --name <n> | A staff user and a single-use enrolment link (the first user goes into administrators) | A migrated database |
crm organisation create '<json>', crm membership set <org-id> --tier <t>, crm invite | An organisation, its tier on a site, a portal invitation (engine-dmo) | A database |
An error names the field and its code (nonsense: type page has no field "nonsense" ... (unknown_field)), so you can fix the JSON and run it again.
Two content operations have no command. Scheduling a publish for later is done in the admin, by the content MCP’s publish tool with publish_at, or by a seed document’s schedule:. Unpublishing with a redirect to another address is done through the content MCP; content trash --redirect removes a document and redirects its address.
The content MCP
If your client speaks the Model Context Protocol, engine-dmo mcp --as <staff email> serves the content tools (read, create and update documents, upload media, publish) over standard input and output, acting as that staff user with exactly their permissions. It needs a database and a staff user, and is refused in production. --drafts-only keeps publishing out of reach; --sites, --toolsets and --types narrow it further. engine-dmo mcp tools --json lists the tools and their schemas without serving. See Content MCP.
The dev verbs, check, render and describe are not MCP tools: run them as shell commands.
Workflow: from a brief to a running site
The order of work for a coding agent turning a written brief into a working, accessible, deployable Meridian site, step by step, with the exact command for each step and how to verify it before moving on.
Recipes
Complete, checked examples of the tasks an agent meets most when building a Meridian site, from adding a content type to a DMO directory, a partner portal and an extension, each with the commands, the files and how to verify them.