Examples & ToolingCLI

CLI (@panelwave/cli)

Install and use the panelwave command line — validate, bundle, diff, and upgrade manifests, with all options, exit codes, and example runs.

@panelwave/cli is the official command-line tool for working with PanelWave manifests. It installs a single binary, panelwave, with four commands: validate, bundle, diff, and upgrade. The CLI is MIT-licensed and validates against the official schema using AJV (JSON Schema 2020-12, all-errors mode, formats enabled).

Installation

The CLI is published on npm as @panelwave/cli. Install it globally:

npm install -g @panelwave/cli

Or run it ad hoc without installing, e.g. in CI:

npx @panelwave/cli validate ./my-comic/panelwave.json
panelwave --version   # prints the CLI version
panelwave --help      # command overview

Releases are built and published from GitHub Actions with npm trusted publishing, so every version carries an npm provenance attestation linking it to the source commit it was built from.

Bundled schema

The CLI ships its own copy of the schema (schema/1.0/panelwave.schema.json inside the package), so validation works offline and never fetches anything from the network. That copy is kept identical to the canonical file in panelwave/schema: the SDK build fails if the bundled schema drifts from the canonical one, or if the version the CLI's upgrade command targets differs from the version the schema declares. In practice this means the schema bundled in a CLI release is exactly the published format version it was built against — 1.7.0 in the current npm release (1.2.0). Older releases bundle older formats and reject newer fields as unknown properties: CLI 1.1.0 bundles 1.6.0 (it rejects an edge's label, a hotspot's display, a balloon's textColor, empty edges and snake_case variable ids), CLI 1.0.0 bundles 1.5.0 (it also rejects a paywall rule's requiredProductIds and paywall.products). Update with npm install -g @panelwave/cli@latest; Versioning → Current releases lists which release bundles which format.

The schema's $id (https://panelwave.org/schema/1.0/panelwave.schema.json) is an identifier, not a download location you can rely on yet — the URL is not publicly served. For offline or programmatic validation, use the CLI's bundled copy or the file in the panelwave/schema repository.

Source and issues

The CLI lives in the panelwave/packages monorepo (npm workspaces, together with @panelwave/types). Report bugs and request features in its issue tracker. To build from source:

git clone https://github.com/panelwave/packages.git
cd packages
npm install
npm run build:cli      # runs the schema drift check, then compiles the CLI

The drift check compares against a sibling ../schema checkout of panelwave/schema (or $PANELWAVE_SCHEMA_DIR); without one it only verifies the version pin and prints a warning. CI always clones panelwave/schema, so published releases are fully checked.

panelwave validate <file>

Validates a manifest against the PanelWave JSON Schema.

OptionDefaultDescription
-s, --schema <version>1.0.0Schema version to validate against. Resolved by major version — any 1.x.y value resolves schema/1.0/panelwave.schema.json.
--strictoffFail (exit 1) when warnings are present, even if the manifest is schema-valid.
--jsonoffPrint the machine-readable result instead of human output.

Schema resolution order: the schema bundled inside the CLI package, then ./schema/<major>.0/panelwave.schema.json relative to the current directory, then the same path walking up to five parent directories (handy in a monorepo checkout).

Besides schema errors, validate emits structural warnings (non-blocking unless --strict):

  • No "assets" section — panels referencing assetIds will fail at runtime.
  • No "characters" defined in meta — speech bubbles with characterId will be unresolved.

Example runs

$ panelwave validate my-comic/panelwave.json

  ✓ my-comic/panelwave.json is valid (schema 1.0.0)
$ panelwave validate broken.json

  ✗ broken.json has 2 error(s)

  /chapters/0/graph
    → must have required property 'entry'
  /meta/default_locale
    → must match pattern "^[A-Za-z]{2,8}(-[A-Za-z0-9]{2,8})*$"

With --json, output is a ValidationResult:

{
  "valid": false,
  "errors": [
    { "path": "/chapters/0/graph", "message": "must have required property 'entry'", "keyword": "required" }
  ],
  "warnings": ["No "assets" section — panels referencing assetIds will fail at runtime."]
}

Exit codes

CodeMeaning
0Valid (and no warnings when --strict).
1Validation errors, file not found, invalid JSON, schema not resolvable, or warnings with --strict.

panelwave bundle <directory>

Assembles a manifest that was split across files back into a single JSON document.

OptionDefaultDescription
-o, --output <file>stdoutWrite the bundle to a file (directories are created as needed).
--minifyoffEmit compact JSON (no indentation).

Supported directory layout:

manifest/
  panelwave.json      # root (used as-is if it already contains everything)
  meta.json           # merged as "meta" if the root lacks it
  assets.json         # …same for assets, variables, settings,
  variables.json      #  extras, paywall, tracking, ui
  chapters/
    ch-01.json        # each file = one chapter object,
    ch-02.json        # sorted by filename into the "chapters" array

Merge rules: a section file is only merged when the root does not already define that key; the chapters/ directory is only used when the root has no chapters.

$ panelwave bundle ./manifest -o dist/panelwave.json

  ✓ Bundled manifest written to dist/panelwave.json

bundle does not validate the result — pipe it through validate afterwards: panelwave bundle ./manifest -o dist/panelwave.json && panelwave validate dist/panelwave.json

Exit codes

CodeMeaning
0Bundle written (or printed to stdout).
1Directory not found, or invalid JSON in any input file.

panelwave diff <fileA> <fileB>

Shows structural differences between two manifests as a recursive deep diff with JSON-path-style locations (e.g. chapters[0].panels.p1.layers[0].opacity).

OptionDefaultDescription
--jsonoffOutput the diff entries as JSON.
--ignore-orderoffSort arrays before comparing, ignoring ordering differences.

Each difference is one of added (in B only), removed (in A only), or changed.

$ panelwave diff v1/panelwave.json v2/panelwave.json

  Comparing v1/panelwave.json ↔ v2/panelwave.json

  3 difference(s) found:

  ~ meta.title.en-US
    - "Night Shift"
    + "Night Shift — Director's Cut"
  + chapters[0].panels.p1-6
    {"title":{"en-US":"New scene"},"layers":[...]}
  - paywall.rules[2]
    {"id":"pw-p3-1","scope":"panel","refId":"p3-1",...}

With --json, output is an array of { "path", "type", "valueA", "valueB" } entries.

Exit codes

CodeMeaning
0Manifests are identical.
1Differences found, file not found, or invalid JSON.

The diff-means-nonzero convention makes diff usable as a CI guard ("did the published manifest change?").

panelwave upgrade <file>

Normalizes a manifest to the CLI's target schema version, applying mechanical migrations.

OptionDefaultDescription
-o, --output <file>overwrite in placeWrite the upgraded manifest elsewhere.
--dry-runoffPrint the planned changes without writing anything.

Migration steps applied (each reported individually):

  1. Add a missing panelwave header (version + schema URI).
  2. Normalize panelwave.version and panelwave.schema to the CLI's target version.
  3. Derive a missing meta.locales array from meta.default_locale.
  4. Rename legacy layer type fields to kind (older drafts used type).
  5. Add a missing graph.edges array to each chapter.
$ panelwave upgrade old-manifest.json --dry-run

  old-manifest.json — 2 upgrade(s):

  ~ panelwave.version — Updated version from "0.9.0" to "1.5.0"
  + chapters[0].graph.edges — Added missing graph.edges array

  --dry-run: no files were modified

The upgrade target is the format version of the CLI's bundled schema — 1.7.0 in the current release (1.2.0; CLI 1.1.0 targeted 1.6.0, CLI 1.0.0 1.5.0). Formats 1.1–1.7 are purely additive or relaxing, so older manifests need no structural migration; upgrade normalizes the header and legacy fields. Because the target always matches the bundled schema (the build enforces it, see Bundled schema), upgrade never rewrites a manifest to a version its own validate would not know. Note that any other declared version — including a newer one — is rewritten to the target, so update the CLI (npm install -g @panelwave/cli@latest) before upgrading manifests written for a later format. Run --dry-run first and review the reported steps before overwriting files.

Exit codes

CodeMeaning
0Already up to date, upgrade written, or --dry-run completed.
1File not found or invalid JSON.