Meridian handbook
For agents

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:

ExitMeaning
0Success
1Error: the command could not do its work (a page that answered 400 or more, a database it could not reach)
2Changes pending (schema plan, migrate status), or refused for want of confirmation (a destructive plan)
3Configuration invalid, or a verb’s argument that names something that does not exist (--after names "slug", which is not a field)
4Degraded 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

NeedsMeansCommands
NothingRuns in any directorydocs, help, version, describe fieldtypes, config schema
The projectReads the files; unset ${VAR}s are accepteddescribe project, describe type, guide, template api, media transforms, check --no-db, the dev verbs without --scaffold
ConfigurationEvery ${VAR} without a default must be set; a placeholder is enoughconfig render, config validate, describe template, seed verify, seed write, new, a11y statement, dev route add --scaffold, portal check
A databaseA migrated database with the schema appliedcheck (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

CommandReturnsNeedsUse it
help --jsonEvery command: path, use, short, arguments, flags (name, type, default, usage), plus global_flags and exit_codesNothingBefore running a command you have not used, to get its exact flags
guideThis 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 filtersThe projectAt the start of every session, and after changing the model. --out FILE writes it, --skill adds skill front matter
describe project --jsonNetworks, sites, locales, types, routes, endpoints, reports, tiers, partner roles and staff roles, with idsThe projectTo find an element’s key or id
describe type <key> --jsonA type’s fields at every depth: id, type, parameters, options, groups, blocks, sites, member rules. A core type lists its core fields marked coreThe projectBefore writing a template or seed file for the type
describe fieldtypes --jsonEvery field type: what it stores, its parameters, its default translate modeNothingBefore 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 rendersConfigurationWhen a site has its own template root and you are unsure which file wins
describe route <path> --jsonWhich route answers a path, the template, and the whole template context with real values, plus the functions, filters and components availableA databaseBefore writing a template: you see exactly what it receives
template api --jsonEvery global, filter, tag, field type, type, route and component of a site, with documentation (--site)The projectTo 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 restNothingTo check a key before writing it. The argument is the kind, not the file name
config renderThe configuration as the engine resolved it, defaults filled, secrets redactedConfigurationTo see the value a key really has
versionVersion, bundle and modules, the build used for extensions, contract versionsNothingTo 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) and dev endpoint add write a valid route or endpoint whose templates do not exist yet, and check fails until you write them.
  • A failure after the write. dev route add --scaffold writes routes.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 says nothing to change. Write the template with engine-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.

VerbDoesKey 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 renameRename an outbound declaration, keeping its id--site
dev tier add <key>, dev tier renameA 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 upServes 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 --jsonWhat dev up last found: the check, the plan, live aliases, the address, oversized imagesExits 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

CommandWrites
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

CommandReturnsNeeds
checkEvery 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 textA database; --no-db skips the database checks (drift)
check --no-dbThe same without a database; unset ${VAR}s are acceptedThe project
check --strictConfiguration warnings fail too (exit 3); template warnings still do notAs check
ENVIRONMENT=production check --no-dbThe production rulesNothing
schema planThe 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 digestA database
schema applyApplies the plan in one transaction; refuses a destructive plan without approvalA database
seed verifyWhether 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 oneConfiguration
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, --fragmentA database
render <path> --jsonstatus, 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 firstA database
a11y auditRenders 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 statementAn accessibility statement scaffold from site.yaml (--out, --format)Configuration
template testRenders the pages tests/templates/*.test.yaml name and checks eachA seeded database
portal checkThe project’s portal templates against the portal context (engine-dmo)Configuration
media transformsEach image transform resolved, with its width ladder and the formats this build servesThe project
api resolve <path>What the content API answers for a pathA database

Content

CommandDoesNeeds
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 idA database
content update <id> '<json>'Merges values over a locale’s working revision; null clears a field; --publishA database
content publish --id <id>Publishes a locale (--locale) or every locale whose checks pass (--all-locales)A database
content get <id> --jsonA document’s working and published values per localeA database
content list --type <key>One line per document and locale: id, type, locale, status, titleA database
seed applyLoads seed/ through the same validation and history as an editor; refuses a database that holds contentA database with the schema applied
seed reset --confirmRemoves all content, media and analytics; keeps users and configuration. Refused in productionA 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 inviteAn 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.