module.json
Every field of a module manifest — the required five, the two enums, the two regex patterns, and what reads each one.
Five fields are required — id, name, description, status, skill — and additionalProperties
is false, so a field the schema does not know is a failed gate rather than a silent no-op.
The top level
Prop
Type
What status and selection actually do
| Value | Effect |
|---|---|
status: "planned" | The module is in the tree and validated like every other, and ready() filters it out. The picker never offers it, and --modules naming it ends the run with the list of ids that are ready. |
status: "ready" | Offered. |
selection: "default" | Taken by --yes. |
selection: "opt-in" | Offered in the picker with an extra hint, and never taken by --yes — name it in --modules to get it. Two modules use it: auth and admin, both because they need credentials the wizard cannot invent. |
requires is resolved into a closure before anything is written: dependencies first, core always,
and the ids it added are printed. recommends is only ever offered — under --yes or --modules
it is logged and not installed, and a mutual recommendation between two modules is legal where a
requires cycle is not.
hidden
A hidden module contributes no rail item and no /<id> route. The flag lives in the manifest as well
as on the shell descriptor because the wizard writes the agent handoff from manifests alone and would
otherwise print a deep link to a route that does not exist — and the two are compared, so they cannot
drift.
auth is the only module that sets it: it contributes a host integration instead of a page.
railHidden, which keeps the route and only takes the listing away, is a descriptor field with no
manifest counterpart — see the module contract.
skill
Prop
Type
app.env
An array of the variables this module needs. The wizard writes the union across the selected
modules, first declaration wins, preceded by the app's own two (VITE_APP_NAME, VITE_APP_LOGO),
which belong to no manifest.
Prop
Type
Values resolve in this order: a step-resolved value, then the token for CHATFUEL_TOKEN, then the
manifest default, then ''.
resolve documents which step fills the variable; no code reads the field. The steps write into the
answer bag by variable name — the workspace picker sets VITE_CHATFUEL_WORKSPACE_ID and
CHATFUEL_WORKSPACE_ID, the auth setup sets the SUPABASE_* ones, the admin step sets
ADMIN_PASSWORD — so a resolve value that is wrong changes nothing, and a variable nobody writes
comes out empty however it is annotated.
The TypeScript type beside the schema is one value behind: ModuleManifest's resolve union is
'authSetup' | 'workspacePick', while the schema allows adminSetup and modules/admin/module.json
uses it. The schema is the authority and the ajv gate passes; a manifest edited through the
TypeScript type would not type-check.
app.embed
What --embed copies into a host project.
Prop
Type
permissions
Prop
Type
Nothing checks these at runtime. They are read once, when the wizard writes the handoff notes, so the agent that continues the work knows what the token has to be allowed to do.
What the gates check
pnpm validate runs both of these; a failure is a failed build, not a warning.
| Check | Failure |
|---|---|
| ajv against the schema | Reported per field path — a bad pattern, a value outside an enum, a property the schema does not have. |
id vs the directory | id "x" does not match directory name "y". |
skill.installAs | Already used by another module. |
SKILL.md frontmatter | Its name must equal installAs. |
requires / recommends | May not name themselves, or a module that does not exist. |
requires cycles | Detected over requires edges only. |
The shell side, for a ready module with an app block | apps/shell/src/modules/<id>/index.tsx exists and exports moduleDescriptor under that exact name; its hidden matches the manifest's; the id is in the shell registry; modules/<id>/handoff.md exists; every embed root, the playbook and the entry component resolve. |
Scripts in the generated app
Every npm script the scaffolded app ships with — what it runs, when you reach for it, and what has to be true first.
The module contract
The four interfaces a module implements — ModuleDescriptor, ModuleAppProps, HostIntegration, HostRuntime — field by field, and what each one is for.