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.
| Option | Default | Description |
|---|---|---|
-s, --schema <version> | 1.0.0 | Schema version to validate against. Resolved by major version — any 1.x.y value resolves schema/1.0/panelwave.schema.json. |
--strict | off | Fail (exit 1) when warnings are present, even if the manifest is schema-valid. |
--json | off | Print 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
| Code | Meaning |
|---|---|
0 | Valid (and no warnings when --strict). |
1 | Validation 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.
| Option | Default | Description |
|---|---|---|
-o, --output <file> | stdout | Write the bundle to a file (directories are created as needed). |
--minify | off | Emit 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
| Code | Meaning |
|---|---|
0 | Bundle written (or printed to stdout). |
1 | Directory 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).
| Option | Default | Description |
|---|---|---|
--json | off | Output the diff entries as JSON. |
--ignore-order | off | Sort 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
| Code | Meaning |
|---|---|
0 | Manifests are identical. |
1 | Differences 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.
| Option | Default | Description |
|---|---|---|
-o, --output <file> | overwrite in place | Write the upgraded manifest elsewhere. |
--dry-run | off | Print the planned changes without writing anything. |
Migration steps applied (each reported individually):
- Add a missing
panelwaveheader (version + schema URI). - Normalize
panelwave.versionandpanelwave.schemato the CLI's target version. - Derive a missing
meta.localesarray frommeta.default_locale. - Rename legacy layer
typefields tokind(older drafts usedtype). - Add a missing
graph.edgesarray 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
| Code | Meaning |
|---|---|
0 | Already up to date, upgrade written, or --dry-run completed. |
1 | File not found or invalid JSON. |
Related pages
- Validation — validating with AJV directly, error anatomy
- Versioning — format versions 1.0–1.7 and compatibility rules
- Examples — sample manifests to try the commands on
- @panelwave/cli on npm · source on GitHub