CLI Reference

Preview Feature: CLI AI Automation is a preview feature. If you encounter any issues, please report them at support.skipper18.com.

Complete reference for all Skipper CLI verbs and patch operations. Every verb emits a single JSON envelope on stdout.

Verbs

FlagDescription
-cli-helpList available verbs or show detail for one verb
-cli-validateValidate project consistency and migration freshness
-cli-exportFull export: classes + migrations using the project's configured framework
-cli-create-migrationCreate a new migration revision (optionally attaching custom SQL)
-cli-set-migration-sqlAttach custom raw SQL to a migration revision (start/end of up() and down())
-cli-create-projectCreate an empty project with chosen ORM/MVC framework
-cli-schema-summaryDump entire project as structured JSON
-cli-import-projectScan a codebase directory and build a .skipper from discovered files
-cli-import-databaseReverse-engineer a .skipper from a live database
-cli-compareDiff two .skipper files using the migration diff engine
-cli-apply-patchApply a list of explicit operations to a project (RFC 6902 spirit)
-cli-apply-mergeApply a declarative sparse target state (RFC 7396 spirit)
-cli-export-diagramRender schema diagram as PNG or PDF (GUI builds only)
-cli-list-frameworksList available ORM and MVC frameworks
-cli-list-orm-attributesDiscover the framework's settable ORM attributes per object type (read-only)
-cli-export-opsEmit the apply-patch op-list that rebuilds a project's schema (read-only)
-cli-check-updateCheck if a new version is available from the Harbor server
-cli-download-updateDownload the latest version to the user data directory
-cli-apply-updateExtract, backup, and apply a downloaded update
-license-activateActivate a license key on this machine (headless, no GUI required)
-license-infoShow current license status (type, owner, expiration dates)

License Management Commands

These commands manage the Skipper license without launching the GUI. They are useful for headless servers (CI/CD, Docker containers, Linux without X server) where the graphical license dialog is not available.

FlagDescription
-license-activateActivate a license key on this machine (headless, no GUI required)
-license-infoShow current license status (type, owner, expiration dates)

Verb Details

help

Skipper.exe -cli-help [<verb-name>]

Lists all available verbs, or shows detailed usage for one verb. Returns UNKNOWN_VERB if the verb name is not recognised.

validate

Skipper.exe -cli-validate <project.skipper>

Data: { issues: [{severity, code, object, message}], summary: {error_count, warning_count} }
Errors: PROJECT_LOAD_FAILED, VALIDATION_FAILED.

export

Skipper.exe -cli-export <project.skipper>

Data: { files_written: [{path, bytes}], framework: "...", output_dir: "..." }
Errors: PROJECT_LOAD_FAILED, EXPORT_FAILED, VIEWER_LICENSE.
Requires a full license (viewer licenses cannot export code). Files are written relative to the project file location, into each module's export_path. The pipeline generates all files (classes + migrations) in a single pass. To filter by type, check output file paths — migration files live under the module's migrations-path directory (typically database/migrations/).

export-classes

Skipper.exe -cli-export-classes <project.skipper>

Export classes/models only. Currently returns NOT_IMPLEMENTED; use -cli-export for full export.

export-migrations

Skipper.exe -cli-export-migrations <project.skipper>

Export migrations only. Currently returns NOT_IMPLEMENTED; use -cli-export for full export.

create-migration

Skipper.exe -cli-create-migration <project.skipper>
    [-before_sql "..."] [-after_sql "..."] [-before_down_sql "..."] [-after_down_sql "..."]

Data: { created: true, revision: "<uuid>" } (success) or { created: false, reason: "no_changes_pending" } (no changes).
Errors: MIGRATIONS_DISABLED.
Optionally attaches custom SQL to the just-created revision — accepts the same four SQL slot flags as -cli-set-migration-sql (-before_sql / -after_sql at the start/end of up(), -before_down_sql / -after_down_sql at the start/end of down()).

set-migration-sql

Skipper.exe -cli-set-migration-sql <project.skipper> -revision <uuid|latest>
    [-before_sql "..."] [-after_sql "..."] [-before_down_sql "..."] [-after_down_sql "..."]

Attaches custom raw SQL to a migration revision. The SQL is injected verbatim into the generated migration: before_sql / after_sql at the start/end of up(), before_down_sql / after_down_sql at the start/end of down(). It is emitted as DB::statement('...') (Laravel) or chained .raw('...') (Knex), and is stored in the .skipper file (survives load/save).
Data: { revision: "<uuid>", slots_set: ["before_sql", ...] }
Errors: MISSING_REQUIRED (no project / no -revision / no slot flag), OBJECT_NOT_FOUND (revision uuid not found), INTERNAL_ERROR (save/serialization failure), PROJECT_LOAD_FAILED.

  • -revision accepts a revision uuid or the literal latest (newest revision). Required.

  • At least one slot flag is required. Replace semantics: a present flag overwrites that slot; passing an empty value (-before_sql "") clears it.

  • Frameworks without migration generation (Doctrine/Propel/CakePHP/Symfony) ignore it; only Laravel and Knex emit it.

  • Multi-module limitation: the SQL is attached to the first module log that contains the revision uuid, and is emitted only into that module's migration. Single-module projects (the common case) are unaffected.

Use this for things Skipper cannot express declaratively — e.g. dropping a leftover PostgreSQL CHECK constraint when changing an enum field to string:

Skipper.exe -cli-set-migration-sql project.skipper -revision latest \
  -before_sql 'ALTER TABLE "hardware" DROP CONSTRAINT IF EXISTS "hardware_state_check"'

create-project

Skipper.exe -cli-create-project -output <path> -orm <name> -mvc <name> [-name <project-name>]

Creates an empty .skipper file with the chosen ORM and MVC framework.
Data: { output_path: "...", orm: "...", mvc: "..." }
Errors: UNKNOWN_FRAMEWORK, IO_ERROR, VIEWER_LICENSE.

schema-summary

Skipper.exe -cli-schema-summary <project.skipper> [-with-visual]

Data: { project: {...} } -- see the Schema JSON Format page for the full structure.

import-project

Skipper.exe -cli-import-project <scan-dir> -output <path> -orm <name> -mvc <name> [-name <project-name>]

Scans a codebase directory and builds a .skipper from discovered files.
Data: { output_path: "...", orm: "...", mvc: "...", modules_created: <int>, entities_imported: <int> }
Errors: IO_ERROR, VIEWER_LICENSE.

import-database

Skipper.exe -cli-import-database -output <path> -db-class <sqlite|mysql|postgres|sqlserver> -connection <conn> -orm <name> -mvc <name> [-name <project-name>] [-skip-initial-migrations]

Reverse-engineers a .skipper from a live database connection.
Errors: DB_CONNECTION_FAILED, DB_IMPORT_FAILED, UNKNOWN_FRAMEWORK, VIEWER_LICENSE.

compare

Skipper.exe -cli-compare <new.skipper> <old.skipper>

Data: { summary: {added, removed, modified, identical}, items: [{action, type, name, details?}] }
Errors: VIEWER_LICENSE.
Requires a full license. Diffs two .skipper files using the migration diff engine.

apply-patch

Skipper.exe -cli-apply-patch <project.skipper> -patch <ops.json> -output <out.skipper>

Data (success): { ops_executed: <int>, output_path: "...", results: [{op, result}] }results is index-aligned with the op list; each result is that op's method JSON (e.g. {success, id, name, ...}), so you get the created/updated object ids back. Use an id for a follow-up set_orm_attribute object:<id> (across invocations) or reference it inside the same patch via $ref (see Patch Operations).
Data (failure): { ops_attempted: <int>, failed_op_index: <int> }
Errors: INVALID_OP, OBJECT_NOT_FOUND, MALFORMED_INPUT_FILE.
On failure, the output file is not written. Retry with the original input file.

apply-merge

Skipper.exe -cli-apply-merge <project.skipper> -merge <state.json> -output <out.skipper>

Data: { objects_changed: <int>, output_path: "..." }
Errors: INVALID_MERGE_INPUT, OBJECT_NOT_FOUND, MALFORMED_INPUT_FILE.
See the Merge Input Format section below for the expected JSON structure.

export-diagram

Skipper.exe -cli-export-diagram <project.skipper> -format <png|pdf> -output <path>

Data: { format: "...", output_path: "...", bytes: <int> }
Errors: INVALID_ARG_VALUE, NOT_IMPLEMENTED (svg). Available in Skipper.exe (GUI build), not in SkipperCli.exe (console build). On Linux set QT_QPA_PLATFORM=offscreen.

list-frameworks

List available ORM and MVC frameworks.

Skipper.exe -cli-list-frameworks

Data: { orm_frameworks: [{name: "..."}], mvc_frameworks: [{name: "..."}] }

list-orm-attributes

Skipper.exe -cli-list-orm-attributes <project.skipper> [-object-type association]

Discover the framework's declared ORM attributes per object type — so you know what is settable via set_orm_attribute (name, path, type, enum values) without reading the cfg. Read-only.
Without -object-type, lists every object type (entity, field, association, many-to-many, many-to-many-entity, module, index, embedded, region). Pass -object-type project explicitly to list project-level attributes.
Data: { object_types: [{ object_type: "association", attributes: [{ name, path, type, [enum_values], [container] }] }] }

Example — find how to set a FK cascade (Laravel/Knex): -cli-list-orm-attributes p.skipper -object-type association returns onDelete / onUpdate (type enum, values cascade|restrict|set null|no action). Set them with set_orm_attribute object_type:association path:onDelete value:cascade — the exported migration then emits ->onDelete('cascade').

export-ops

Skipper.exe -cli-export-ops <project.skipper>

Emit the apply-patch op-list that rebuilds a project's schema on an empty project — the inverse of apply-patch (and a more actionable form of schema-summary). Useful to copy a schema, learn how a construct is expressed as ops, or seed a new project. Read-only.
Data: { ops: [ { op, args }, ... ] } — feed it straight back into -cli-apply-patch (the {ops:[...]} envelope is accepted) against an empty project to reproduce the schema.
Note: relationship-generated objects are emitted as their relationship op (the FK field, M:N pivot entity, morph_id/morph_type, discriminator are created by add_association / add_many_to_many, not as add_field). Inheritance and id-referenced orm-attributes are not yet emitted.

License Command Details

license-activate

Skipper.exe -license-activate <license-key>
# or
Skipper.exe -license-activate -key <license-key>

Activates a Skipper license on this machine without launching the GUI. Designed for headless servers, Docker containers, and CI/CD environments where no display is available.

Data: { license_group: "...", name: "...", email: "...", valid_to: "...", maintenance_to: "...", activation_id: <int> }
Errors: MISSING_REQUIRED (no key provided), INTERNAL_ERROR (invalid key, already activated, or server error).

Idempotent: If a valid license is already active on this machine, the command returns immediately with "skipped": true and the current license details without contacting the license server. This makes it safe to call on every CI run or Docker entrypoint without generating unnecessary server traffic.

license-info

Skipper.exe -license-info

Shows the current license status without modifying anything. Returns license details if a license is activated, or empty fields if no license is loaded.

Data: { is_loaded: <bool>, license_group: "...", name: "...", email: "...", valid_to: "...", maintenance_to: "...", activation_id: <int> }

Application Update

check-update

Skipper.exe -cli-check-update

Queries the Harbor server to check if a new version is available. Returns version information, download URLs, and changelog notes.

Data: { update_available: <bool>, current_version: "...", latest_version: "...", download_url: "...", changelog: "..." }
Errors: NETWORK_ERROR, UPDATE_CHECK_FAILED.

download-update

Skipper.exe -cli-download-update -output <path>

Downloads the latest version as a ZIP file to the specified output path. Validates HTTPS certificate and file integrity.

Data: { downloaded: <bool>, version: "...", zip_path: "...", zip_size_bytes: <int> }
Errors: DOWNLOAD_FAILED, NETWORK_ERROR, INVALID_OUTPUT_PATH.

apply-update

Skipper.exe -cli-apply-update -source <zip-file>

Extracts the ZIP, backs up the current installation, replaces application files, and returns the command needed to restart. Run this command in your shell to complete the update.

Data: { applied: <bool>, restart_command: "...", new_version: "..." }
Errors: EXTRACT_FAILED, BACKUP_FAILED, INSUFFICIENT_PERMISSIONS.

Patch Operations

The apply-patch verb accepts a JSON file containing an ordered list of operations. The format follows the spirit of RFC 6902 (JSON Patch). Each operation is an object with op (the operation name) and args (an object of arguments). The top-level structure is:

{"ops": [{"op": "name", "args": {...}}, ...]}

There are 36 operations in total. Operations are executed in order. Objects created by earlier ops are immediately available to later ops in the same patch (you can add_entity then add_field referencing it). If any operation fails, execution stops and the failure index is reported. On failure, the output file is not written. Retry with the original input file. Required arguments are shown in bold; optional arguments are in [brackets].

Inline ORM attributes (orm_attributes): the id-addressed creators — add_association, add_index, add_embedded, add_many_to_many — accept an optional orm_attributes object (a {path: value} map applied atomically right after the object is created, using the same path semantics as set_orm_attribute; an unknown path aborts the op). add_many_to_many additionally accepts owner_orm_attributes / inverse_orm_attributes for the two M:N sides (many-to-many-entity) — e.g. Doctrine2 orphan-removal lives on the side, so set it via owner_orm_attributes:{"orphan-removal":"true"}. This avoids needing the object's id for attributes known at creation time.

$ref — reference an earlier op's id within the same patch: any op may declare a top-level "ref": "<handle>"; a later op may use the string value "$ref:<handle>" in any arg, resolved at execution to the producer op's result id. This lets you create an object and act on it (by id) in one patch without a CLI round-trip. The $ref: prefix is reserved in string arg values. Unknown / forward / duplicate handles are errors (INVALID_OP).

[
  {"op":"add_many_to_many","args":{"owner":"Post","inverse":"Tag"},"ref":"m1"},
  {"op":"set_orm_attribute","args":{"object":"$ref:m1","object_type":"many-to-many","path":"...","value":"..."}}
]

Entity Operations

OperationArgumentsDescription
add_entitymodule, name, [namespace], [description], [region]Create a new entity in the specified module. Auto-creates an id integer PK field from the framework template. Do not add PK manually. region places the entity inside one of the module's regions (by the region's caption).
update_entityentity, [name], [namespace], [description]Update properties of an existing entity
remove_entityentityDelete an entity and all its children. Also removes associated fields, associations, indexes, and M:N relationships.

Field Operations

OperationArgumentsDescription
add_fieldentity, name, [type], [size], [primary], [nullable], [unique], [auto_increment], [unsigned], [default]Add a new field to an entity. If a field with the same name already exists, the operation is silently skipped. Fields are NOT NULL by default; pass nullable: true explicitly. For an unsigned integer column use a real type plus unsigned: true (e.g. {type: "bigInteger", unsigned: true}) — there is no unsignedBigInteger type.
update_fieldentity, field, [name], [type], [size], [primary], [nullable], [unique], [auto_increment], [unsigned], [default]Update properties of an existing field
remove_fieldentity, fieldDelete a field from an entity
reorder_fieldentity, field, sort_orderChange the sort position of a field within its entity

Association Operations

OperationArgumentsDescription
add_associationfrom, to, [type], [required], [owner_alias], [inverse_alias], [orm_attributes]Create a new association between two entities. orm_attributes is a {path: value} map applied right after creation (e.g. association onDelete / onUpdate).
update_associationid, [owner_alias], [inverse_alias]Update alias names on an existing association
remove_associationidDelete an association

Module Operations

OperationArgumentsDescription
add_modulename, [namespace], [description], [export_path], [export_format]Create a new module in the project
update_modulemodule, [name], [namespace], [description], [export_path], [export_format]Update properties of an existing module
remove_modulemoduleDelete a module and all its contents

Index Operations

OperationArgumentsDescription
add_indexentity, name, [unique], [fields: ["f1", "f2"]], [orm_attributes]Create a new index on an entity. orm_attributes is a {path: value} map applied right after creation.
update_indexentity, index (name), [name], [unique]Update properties of an existing index. The index argument is the index name (string), not a numeric ID.
remove_indexentity, index (name)Delete an index from an entity. The index argument is the index name (string), not a numeric ID.

Many-to-Many Operations

OperationArgumentsDescription
add_many_to_manyowner, inverse, [mn_entity], [owner_alias], [inverse_alias], [orm_attributes], [owner_orm_attributes], [inverse_orm_attributes]Create a many-to-many relationship between two entities. Automatically creates a join entity with composite PK foreign key fields. orm_attributes applies to the M:N; owner_orm_attributes / inverse_orm_attributes apply to the two sides (many-to-many-entity) — e.g. Doctrine2 orphan-removal.
update_many_to_manyid, [owner_alias], [inverse_alias]Update alias names on an existing many-to-many
remove_many_to_manyidDelete a many-to-many relationship

Embedded Operations

OperationArgumentsDescription
add_embeddedfrom, to, [owner_alias], [inverse_alias], [orm_attributes]Create an embedded relationship. orm_attributes is a {path: value} map applied right after creation.
update_embeddedid, [owner_alias], [inverse_alias]Update alias names on an existing embedded
remove_embeddedidDelete an embedded relationship

Inheritance Operations

OperationArgumentsDescription
add_inheritanceparent, child, [type], [discriminator_value]Create an inheritance relationship between entities. Valid types: SINGLE_TABLE, JOINED, MAPPED_SUPERCLASS.
update_inheritanceid, [discriminator_value]Update the discriminator value of an inheritance
remove_inheritanceidDelete an inheritance relationship

Region Operations

OperationArgumentsDescription
add_regionmodule, [caption], [namespace], [description]Create a visual region in a module
update_regionid, [caption], [namespace], [description]Update properties of an existing region
remove_regionidDelete a region

Comment Operations

OperationArgumentsDescription
add_commentmodule, [caption], [description]Create a comment object in a module
update_commentid, [caption], [description]Update an existing comment
remove_commentidDelete a comment

ORM Attribute Operations

OperationArgumentsDescription
set_orm_attributeobject, [object_type], path, valueSet a framework-specific ORM attribute value
remove_orm_attributeobject, [object_type], pathRemove an ORM attribute
add_orm_attribute_itemobject, [object_type], path, [key], [values]Add an item to a list-type ORM attribute. The values parameter accepts child key-value pairs (e.g. lifecycle callbacks).
remove_orm_attribute_itemobject, [object_type], pathRemove an item from a list-type ORM attribute

The object_type argument defaults to "entity". Supported types for ORM attribute write operations (all 10): project, entity, field, module, association, index, many-to-many, many-to-many-entity, embedded, region. Name-addressed types (entity / field / module / project) take a name in object; id-addressed types (association / index / many-to-many / many-to-many-entity / embedded / region) take the numeric id returned by the object's add_* op in object (capture it from the apply-patch results, or reference it in the same patch via $ref). For attributes known at creation time, prefer the inline orm_attributes arg on the add_* op instead.

Visual Layout Operations

OperationArgumentsDescription
reset_layout{} (no args)Resets visual positions on all entities/modules/regions/comments so auto-arranger runs on next GUI open

Field ORM Attribute Addressing

For object_type: "field", use one of these formats to identify the field:

FormatExampleNotes
Dotted notation (recommended)"object": "Customer.email"Entity.field format
With entity parameter"object": "email", "entity": "Customer"Separate entity lookup
Field name only"object": "email"Searches all entities; fails if ambiguous

Examples:

{"op": "set_orm_attribute", "args": {"object": "Product.price", "object_type": "field", "path": "precision", "value": "10"}}
{"op": "set_orm_attribute", "args": {"object": "price", "object_type": "field", "entity": "Product", "path": "scale", "value": "2"}}
{"op": "add_orm_attribute_item", "args": {"object": "Product", "path": "lifecycle-callbacks", "values": {"type": "prePersist", "method": "onPrePersist"}}}

Attribute Paths

Use attribute names without framework prefix. Write table not orm:table. Use / to separate nested paths (e.g. options/charset). The orm: prefix is auto-stripped if present, but should be avoided.

{"op": "set_orm_attribute", "args": {"object": "Product", "path": "table", "value": "app_products"}}
{"op": "set_orm_attribute", "args": {"object": "Product", "path": "options/charset", "value": "utf8mb4"}}

Name Resolution

Entity and module references accept either the full qualified name (\App\Crm\Address) or a short name (Address, matched against LocalName). The full qualified name is the primary, recommended form — it is unambiguous. Prefer it generally in scripted / generated patches.

Ambiguity: short names are a convenience. If two objects share the same short name (legitimately — e.g. \App\Crm\Address and \App\EShop\Address in different modules / namespaces), a short name resolves to the first match found and the others are silently ignored, which can target the wrong object. Duplicate short names across modules are valid (the full namespace disambiguates them), so always use the full qualified name when an object's short name may not be unique. You can also reference an object by the numeric id returned in the apply-patch results (or via $ref) — also unambiguous.

Index and Association Referencing

update_index and remove_index use the index name (string), not a numeric ID. Example: {"op": "remove_index", "args": {"entity": "Customer", "index": "idx_email"}}

update_association, remove_association, update_many_to_many, and remove_many_to_many require a numeric id from -cli-schema-summary output. IDs are project-scoped auto-increment numbers.

Merge Input Format

The -cli-apply-merge verb accepts a JSON file with a project root key mirroring schema-summary structure. Only include objects you want to change — unmentioned objects are left as-is. Merge updates existing fields by name and adds new ones.

{
  "project": {
    "modules": [
      {
        "name": "MainBundle",
        "entities": [
          {
            "name": "Customer",
            "fields": [
              {"name": "email", "type": "string", "size": 320},
              {"name": "middleName", "type": "string", "size": 100, "nullable": true}
            ]
          }
        ]
      }
    ]
  }
}

Global Flags

FlagEffect
-quietSuppress stderr diagnostics
-verboseRaise stderr to debug level (trace + progress)
-include-timingAdd meta.timing_breakdown to envelope