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
| Flag | Description |
|---|---|
-cli-help | List available verbs or show detail for one verb |
-cli-validate | Validate project consistency and migration freshness |
-cli-export | Full export: classes + migrations using the project's configured framework |
-cli-create-migration | Create a new migration revision (optionally attaching custom SQL) |
-cli-set-migration-sql | Attach custom raw SQL to a migration revision (start/end of up() and down()) |
-cli-create-project | Create an empty project with chosen ORM/MVC framework |
-cli-schema-summary | Dump entire project as structured JSON |
-cli-import-project | Scan a codebase directory and build a .skipper from discovered files |
-cli-import-database | Reverse-engineer a .skipper from a live database |
-cli-compare | Diff two .skipper files using the migration diff engine |
-cli-apply-patch | Apply a list of explicit operations to a project (RFC 6902 spirit) |
-cli-apply-merge | Apply a declarative sparse target state (RFC 7396 spirit) |
-cli-export-diagram | Render schema diagram as PNG or PDF (GUI builds only) |
-cli-list-frameworks | List available ORM and MVC frameworks |
-cli-list-orm-attributes | Discover the framework's settable ORM attributes per object type (read-only) |
-cli-export-ops | Emit the apply-patch op-list that rebuilds a project's schema (read-only) |
-cli-check-update | Check if a new version is available from the Harbor server |
-cli-download-update | Download the latest version to the user data directory |
-cli-apply-update | Extract, backup, and apply a downloaded update |
-license-activate | Activate a license key on this machine (headless, no GUI required) |
-license-info | Show 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.
| Flag | Description |
|---|---|
-license-activate | Activate a license key on this machine (headless, no GUI required) |
-license-info | Show 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.
-revisionaccepts a revision uuid or the literallatest(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-frameworksData: { 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-infoShows 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-updateQueries 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
| Operation | Arguments | Description |
|---|---|---|
| add_entity | module, 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_entity | entity, [name], [namespace], [description] | Update properties of an existing entity |
| remove_entity | entity | Delete an entity and all its children. Also removes associated fields, associations, indexes, and M:N relationships. |
Field Operations
| Operation | Arguments | Description |
|---|---|---|
| add_field | entity, 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_field | entity, field, [name], [type], [size], [primary], [nullable], [unique], [auto_increment], [unsigned], [default] | Update properties of an existing field |
| remove_field | entity, field | Delete a field from an entity |
| reorder_field | entity, field, sort_order | Change the sort position of a field within its entity |
Association Operations
| Operation | Arguments | Description |
|---|---|---|
| add_association | from, 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_association | id, [owner_alias], [inverse_alias] | Update alias names on an existing association |
| remove_association | id | Delete an association |
Module Operations
| Operation | Arguments | Description |
|---|---|---|
| add_module | name, [namespace], [description], [export_path], [export_format] | Create a new module in the project |
| update_module | module, [name], [namespace], [description], [export_path], [export_format] | Update properties of an existing module |
| remove_module | module | Delete a module and all its contents |
Index Operations
| Operation | Arguments | Description |
|---|---|---|
| add_index | entity, 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_index | entity, index (name), [name], [unique] | Update properties of an existing index. The index argument is the index name (string), not a numeric ID. |
| remove_index | entity, index (name) | Delete an index from an entity. The index argument is the index name (string), not a numeric ID. |
Many-to-Many Operations
| Operation | Arguments | Description |
|---|---|---|
| add_many_to_many | owner, 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_many | id, [owner_alias], [inverse_alias] | Update alias names on an existing many-to-many |
| remove_many_to_many | id | Delete a many-to-many relationship |
Embedded Operations
| Operation | Arguments | Description |
|---|---|---|
| add_embedded | from, to, [owner_alias], [inverse_alias], [orm_attributes] | Create an embedded relationship. orm_attributes is a {path: value} map applied right after creation. |
| update_embedded | id, [owner_alias], [inverse_alias] | Update alias names on an existing embedded |
| remove_embedded | id | Delete an embedded relationship |
Inheritance Operations
| Operation | Arguments | Description |
|---|---|---|
| add_inheritance | parent, child, [type], [discriminator_value] | Create an inheritance relationship between entities. Valid types: SINGLE_TABLE, JOINED, MAPPED_SUPERCLASS. |
| update_inheritance | id, [discriminator_value] | Update the discriminator value of an inheritance |
| remove_inheritance | id | Delete an inheritance relationship |
Region Operations
| Operation | Arguments | Description |
|---|---|---|
| add_region | module, [caption], [namespace], [description] | Create a visual region in a module |
| update_region | id, [caption], [namespace], [description] | Update properties of an existing region |
| remove_region | id | Delete a region |
Comment Operations
| Operation | Arguments | Description |
|---|---|---|
| add_comment | module, [caption], [description] | Create a comment object in a module |
| update_comment | id, [caption], [description] | Update an existing comment |
| remove_comment | id | Delete a comment |
ORM Attribute Operations
| Operation | Arguments | Description |
|---|---|---|
| set_orm_attribute | object, [object_type], path, value | Set a framework-specific ORM attribute value |
| remove_orm_attribute | object, [object_type], path | Remove an ORM attribute |
| add_orm_attribute_item | object, [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_item | object, [object_type], path | Remove 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
| Operation | Arguments | Description |
|---|---|---|
| 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:
| Format | Example | Notes |
|---|---|---|
| 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
| Flag | Effect |
|---|---|
-quiet | Suppress stderr diagnostics |
-verbose | Raise stderr to debug level (trace + progress) |
-include-timing | Add meta.timing_breakdown to envelope |