CLI

Scaffolding

Add models and routes to a contract bundle with the CLI.

Use accorudo model add and accorudo route add to grow a bundle file by file. The commands create missing directories, append types in the right place, and register new routes in api.ts.

Hand-writing contracts is always valid. Scaffolding is for speed and consistency when you already know the shape.

Field specs

Both commands accept repeatable field specs:

name:type[@modifier[@modifier...]]
ExampleMeaning
name:stringRequired string property
limit?:numberOptional number property
id:number@integer:int64Number with @openapi.type integer and @openapi.format int64
description?:string@nullableOptional nullable string
id:string@format=uuidString with UUID format
status:string@enum=draft,publishedString enum

Modifiers map directly to OpenAPI annotations.

Add a model

accorudo model add WidgetAttributes \
  --bundle contracts/main \
  --component \
  --field id:number@integer:int64 \
  --field name:string \
  --field "description?:string@nullable"

Writes to models/widget.ts (domain derived from the type name) unless you override it:

FlagDescription
-b, --bundle <dir>Bundle directory (default: contracts/main)
-d, --domain <name>File stem under models/ (default: derived from type name)
--componentAdd @openapi.component
--type <expression>Custom TypeScript type (alternative to --field)
--field <spec>Object property (repeatable)
--dry-runPrint generated type without writing

Use --field for object shapes the CLI builds for you. Use --type when you need full control: generics, unions, utility types, or project-specific wrappers:

accorudo model add WidgetEntity \
  --component \
  --type 'Entity<"widget", WidgetAttributes>'

Referenced types are imported automatically from models/ and types/ in the bundle. Quote the expression for the shell. Prefer single quotes when it contains double-quoted string literals.

Generated output:

/**
 * @openapi.component
 */
export type WidgetAttributes = {
  /**
   * @openapi.type integer
   * @openapi.format int64
   */
  id: number;
  name: string;
  /** @openapi.nullable */
  description?: string | null;
};

With --type:

/**
 * @openapi.component
 */
export type WidgetEntity = Entity<"widget", WidgetAttributes>;

Add a route

accorudo route add listWidgets \
  --method get \
  --path /widget \
  --tag widget \
  --summary "List widgets" \
  --query limit?:number \
  --query offset?:number \
  --query name?:string \
  --response 'CollectionSuccess<WidgetEntity>'

Creates or updates routes/widget.ts and registers listWidgets in api.ts. Response and body types are imported automatically from models/ and types/.

FlagDescription
<key>Registry key in camelCase (e.g. listWidgets)
-m, --methodHTTP method
-p, --pathPath with :params (e.g. /widget/:id)
-t, --tagTag and routes file name
-r, --responsePrimary response type
-b, --bundle <dir>Bundle directory
--summary <text>OpenAPI summary
--operation-id <id>OpenAPI operationId (default: registry key)
--query <spec>Query parameter field (repeatable)
--param <spec>Path parameter override (repeatable; :id defaults to string)
--body <type>Existing request body type
--body-field <spec>Inline request body fields (repeatable)
--on <status:type>Extra response, e.g. 404:NotFoundResponse
--status <code>Primary response status (default: 200)
--multipartEmit MultipartRoute
--security <name>Security scheme (repeatable)
--import <path:Types>Override auto-import with an explicit path (repeatable)
--dry-runPreview generated files

Types referenced in --response, --body, and --on are resolved automatically by scanning models/ and types/ in the bundle. Use --import only when you need to override the default path.

Path parameters

:params in --path become typed path params automatically:

accorudo route add getWidget \
  --method get \
  --path /widget/:id \
  --tag widget \
  --param id:string@format=uuid \
  --response 'Success<WidgetEntity>' \
  --on 404:NotFoundResponse

Adds WidgetByIdRouteFragment, GetWidgetRoute, and a registry entry. Success, WidgetEntity, and NotFoundResponse are imported from scanned model files.

Request body

Reference an existing type:

accorudo route add createWidget \
  --method post \
  --path /widget \
  --tag widget \
  --body CreateWidgetRequestBody \
  --status 201 \
  --response 'Success<WidgetEntity>'

Or define the body inline:

accorudo route add createWidget \
  --method post \
  --path /widget \
  --tag widget \
  --body-field name:string \
  --body-field enabled:boolean \
  --status 201 \
  --response 'Success<WidgetEntity>'

Typical flow

accorudo init

accorudo model add WidgetAttributes --component \
  --field id:number@integer:int64 \
  --field name:string

accorudo model add WidgetEntity --component \
  --field type:widget@enum=widget \
  --field attributes:WidgetAttributes

accorudo route add listWidgets --method get --path /widget --tag widget \
  --response 'CollectionSuccess<WidgetEntity>'

accorudo export contracts/main --out openapi/main.yaml

Use --dry-run on any command to inspect output before files are written.

See CLI overview for install, init, and sync.

Copyright © 2026