npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nexart/cli

v1.3.0

Published

NexArt CLI — Code Mode and AI execution certification

Downloads

419

Readme

@nexart/cli

Version: 1.3.0 (release candidate, not yet published). The CLI verifies both AI Execution record schemas — cer.ai.execution.v1 and cer.ai.execution.v2 — and provides an AIEF v0.2.5 Section 9 projection via --aief. Package versions are release-versioning only; the canonicalisation protocol remains 1.3.1 (jcs-v1).

Command-line interface for NexArt — run, replay, and verify deterministic generative art, plus AI execution certification commands.

NexArt verification is designed so that trust does not depend on NexArt itself.

Independent Verification

A NexArt record (CER bundle) can be verified independently, using only standard cryptographic primitives. You do not need to trust — or run — NexArt to check a record.

Verification requires only:

  • the CER bundle (the record itself)
  • a public key (to check the signature, when one is present)
  • SHA-256 and Ed25519 (standard, widely available primitives)

It does not require:

  • the NexArt SDK
  • a NexArt account
  • a network call (integrity verification is fully offline)

A simple example:

npx nexart verify-bundle ./bundle.json

This command allows a third party to verify record integrity and signer authenticity without relying on NexArt systems or source code.

What this verifies

  • Integrity — the record's certificateHash is recomputed from the record's contents and compared to the stored value. A privacy-redacted public record (input/output content stripped, hashes kept) is reported as AUTHENTIC (REDACTED) rather than failed — see the 3-state model.
  • Authenticity — the Ed25519 signature over the node's receipt is verified, when a signature is present.

Trust boundaries

This command verifies:

  • Record integrity
  • Signature authenticity (when a signature is present)

This command does not verify:

  • Independent timestamp / time-of-existence
  • Completeness of execution
  • Inclusion in any transparency log

These boundaries are deliberate: the command establishes what a record contains and who signed it, and makes no claim beyond that.

protocolVersion: 1.2.0 vs 1.3.0

Verified records carry a protocolVersion that selects how their bytes are canonicalised before hashing — ai verify surfaces it in both human-readable and --json output (cli.protocolVersion).

  • 1.2.0 (legacy, default): the original NexArt canonicalisation (nexart-v1), fully supported and frozen.
  • 1.3.0 (stranger-verifiable): RFC 8785 (JSON Canonicalization Scheme, jcs-v1). Required for independent / external verification by a third party who does not run our exact code.

A record with an unknown or unsupported protocolVersion fails closed (verify code SCHEMA_ERROR, reason code SCHEMA_VERSION_UNSUPPORTED, exit 1) — it is never mis-verified. The CLI adds no fallback or default.

Legacy 1.2.0 records are fully supported. Early 1.2.0 records that serialize snapshot.parameters as null (the canonical form was an empty object {}) verify correctly — ai verify reports VERIFIED (exit 0). This is handled in the SDK without any change to canonicalization or the certificateHash. 1.3.0 remains strict: a null parameters value on a 1.3.0 record fails closed.

Installation

# Global install (recommended)
npm install -g @nexart/cli

# After global install, use the `nexart` command directly:
nexart run sketch.js --seed 12345

Quickstart: Run an Example

The SDK includes example sketches in the examples/ folder:

# 1. Set up authentication
export NEXART_RENDERER_ENDPOINT=https://nexart-canonical-renderer-production.up.railway.app
export NEXART_API_KEY=nx_live_your_key_here

# 2. Run the main example sketch (outputs to current directory)
npx @nexart/cli run ./examples/sketch.js --seed 12345 --include-code --out ./out.png
# Creates: ./out.png and ./out.snapshot.json

# 3. Verify the output is deterministic
npx @nexart/cli verify ./out.snapshot.json
# Output: [nexart] Result: PASS

Output Files

When you run with --out ./out.png, the CLI creates:

  • ./out.png — The rendered PNG image
  • ./out.snapshot.json — Snapshot for replay/verify

Example Sketches

| File | Description | |------|-------------| | examples/sketch.js | Main example — VAR controls + random palette, protocol-safe | | examples/sketch-minimal.js | Simple shapes, no randomness — identical output every run | | examples/sketch-vars.js | Uses VAR + random() — demonstrates determinism with same seed |

Canonical Size

The canonical renderer enforces a fixed canvas size:

| Property | Value | |----------|-------| | Width | 1950 | | Height | 2400 |

Important: Do not pass custom --width or --height to the canonical renderer endpoint. The canonical size is enforced server-side for consistent, verifiable output.

# Correct: use default canonical size
nexart run sketch.js --seed 12345

# Avoid: custom sizes may be rejected by canonical renderer
# nexart run sketch.js --width 800 --height 600  # NOT recommended

Quick Start (npx)

Run without installing globally:

# Using npx (no install required)
npx @nexart/cli run sketch.js --seed 12345
npx @nexart/cli verify out.snapshot.json
npx @nexart/cli replay out.snapshot.json --out replay.png

Overview

The CLI has two command groups:

Code Mode commands — render generative art sketches, create verifiable snapshots, replay and verify them.

AI certification commands — create, certify, and verify tamper-evident Canonical Execution Records (CERs) for AI operations via the NexArt node API. Also includes local verification of project bundles (cer.project.bundle.v1) with nexart ai project-verify.

Authentication

Remote rendering requires an API key for metered access.

Setup

# Set environment variables
export NEXART_RENDERER_ENDPOINT=https://nexart-canonical-renderer-production.up.railway.app
export NEXART_API_KEY=nx_live_your_key_here

# Run with authentication
npx @nexart/cli run sketch.js --seed 12345 --include-code --out out.png

API Key Options

# Via environment variable (recommended)
export NEXART_API_KEY=nx_live_...
nexart run sketch.js --seed 12345

# Via CLI flag
nexart run sketch.js --seed 12345 --api-key nx_live_...

Error Messages

If authentication fails:

[nexart] Error: Missing API key for remote rendering.
[nexart] Set NEXART_API_KEY environment variable or pass --api-key.
[nexart] Get your API key at: https://nexart.art/dashboard/api

Commands

run

Execute a sketch and create a snapshot:

# With remote renderer (default, requires API key for remote endpoints)
nexart run sketch.js --seed 12345 --out render.png

# With code embedded for standalone verify/replay
nexart run sketch.js --seed 12345 --include-code

# Local deterministic mode (no auth required; requires the optional `canvas` dependency)
nexart run sketch.js --renderer local --include-code

Deterministic Replay (local renderer). With --renderer local, the CLI renders the sketch using the same canonical CodeMode render core as the SDK and hashes the encoded PNG bytes — the identical outputHash definition used by the remote renderer. For controlled CodeMode / canonical render snapshots this is byte-reproducible within the same runtime and render libraries (Node version, platform/arch, and canvas native libs). To make that reproducibility checkable, local snapshots additionally embed an optional, additive environment fingerprint (see below). Reproducibility across different environments is not guaranteed; replay/verify will warn when the environment differs. The local renderer requires the optional canvas package and its native libraries — if they are missing, local rendering fails with a clear, actionable error (never a silent placeholder).

Options: | Flag | Default | Description | |------|---------|-------------| | --out, -o | out.png | Output PNG path | | --seed, -s | random | PRNG seed | | --vars, -v | 0,0,0,0,0,0,0,0,0,0 | VAR values (comma-separated) | | --width, -w | 1950 | Canvas width (use default for canonical) | | --height | 2400 | Canvas height (use default for canonical) | | --renderer | remote | remote or local | | --endpoint | env/localhost:5000 | Remote renderer URL | | --api-key | env | API key for authentication | | --include-code | false | Embed code in snapshot | | --runtime-hash | auto | Override runtime hash |

Outputs:

  • render.png — The rendered image
  • render.snapshot.json — Snapshot for replay/verify

Snapshot environment field (additive, optional). Snapshots produced with --renderer local include an environment object describing the render environment (cliVersion, sdkVersion, protocolVersion, node, platform, arch, canvas). It is descriptive metadata only — it is never hashed and never affects pass/fail. It exists so replay/verify can warn when the replay environment differs from the capture environment. Legacy snapshots (and remote-rendered snapshots) omit this field and continue to verify and replay exactly as before.

verify

Check that a snapshot produces the expected output:

# Verify against the canonical remote renderer (default)
nexart verify render.snapshot.json

# Verify locally with the deterministic CodeMode renderer (requires `canvas`)
nexart verify render.snapshot.json --renderer local

# With external code file
nexart verify render.snapshot.json --code sketch.js

The local path performs a real render (no placeholder) — a missing canvas dependency fails clearly rather than producing a false pass.

Exit codes:

  • 0 — PASS (hashes match)
  • 1 — FAIL (hashes differ or error)

replay

Re-execute from a snapshot, compare the result against the snapshot, and report PASS/FAIL:

# Deterministic local replay with PASS/FAIL evidence
nexart replay render.snapshot.json --renderer local --out replay.png \
  --evidence replay.evidence.json

# With external code file
nexart replay render.snapshot.json --code sketch.js --out replay.png

replay recomputes the output hash and compares it to the snapshot's outputHash, and independently checks that the replayed code matches the snapshot's codeHash. The overall result is PASS only when both match.

Options: | Flag | Default | Description | |------|---------|-------------| | --out, -o | replay.png | Output PNG path | | --code, -c | — | Code file (if not embedded in snapshot) | | --renderer | remote | remote or local (deterministic CodeMode renderer) | | --endpoint | env/localhost:5000 | Remote renderer URL | | --evidence | — | Write a JSON replay-evidence file (PASS/FAIL + hash comparison) | | --api-key | env | API key for authentication |

Exit codes:

  • 0 — PASS (output and code match the snapshot)
  • 1 — FAIL (output or code differs, or render error)

When --evidence <file> is given, replay writes a codemode.replay.evidence.v1 JSON document recording the PASS/FAIL result, the expected vs. actual outputHash and codeHash, the renderer used, and the snapshot vs. current environment fingerprints (with any differing fields).


AI Certification Commands (nexart ai)

These commands interact with the NexArt node API to manage Canonical Execution Records (CERs) for AI operations. CERs are tamper-evident bundles that cryptographically certify an AI execution (input, output, model, parameters).

Environment Variables

| Variable | Description | |----------|-------------| | NEXART_NODE_ENDPOINT | NexArt node API URL (default: https://node.nexart.art) | | NEXART_API_KEY | Shared API key for authenticated requests |

nexart ai create

Create a CER bundle from an AI execution record.

# From file
nexart ai create execution.json

# From stdin
cat execution.json | nexart ai create

# With explicit endpoint and API key
nexart ai create execution.json \
  --endpoint https://node.nexart.art \
  --api-key nx_live_...

# Save to file
nexart ai create execution.json --out cer.json

Input format (JSON file or stdin):

{
  "executionId": "exec-001",
  "provider": "openai",
  "model": "gpt-4o",
  "input": "What is 2+2?",
  "output": "4",
  "parameters": { "temperature": 0 }
}

Output: CER bundle JSON printed to stdout (or saved via --out).

Options: | Flag | Default | Description | |------|---------|-------------| | --endpoint | env/https://node.nexart.art | NexArt node URL | | --api-key | env | API key | | --out | stdout | Save bundle to file | | --signals-file | (none) | Path to a JSON array of context signals to attach |

Exit codes:

  • 0 — Success
  • 1 — API error or missing input

nexart ai certify

Certify an AI execution and receive an attested CER bundle. The node API signs the bundle and attaches an attestation.

# From file
nexart ai certify execution.json

# From stdin
cat execution.json | nexart ai certify

# JSON output (machine-readable)
nexart ai certify execution.json --json

# Save output
nexart ai certify execution.json --out certified.json

Default output:

[nexart] CER certified successfully
bundleType : cer.ai.execution.v1
hash       : sha256:a1b2c3...
attestation: present

JSON output (--json):

{
  "ok": true,
  "bundleType": "cer.ai.execution.v1",
  "certificateHash": "sha256:a1b2c3...",
  "hasAttestation": true,
  "bundle": { ... }
}

Options: | Flag | Default | Description | |------|---------|-------------| | --endpoint | env/https://node.nexart.art | NexArt node URL | | --api-key | env | API key | | --out | stdout | Save bundle to file | | --json | false | Machine-readable JSON output | | --signals-file | (none) | Path to a JSON array of context signals to attach |

Exit codes:

  • 0 — Certified successfully
  • 1 — API error (e.g. 401 Unauthorized), missing input, or invalid response

nexart ai seal (v0.8.0+)

Create a Certified Execution Record locally — no node required. Produces an integrity-only bundle suitable for offline workflows, CI, pre-certification, or piping straight into nexart ai verify. All snapshot construction, canonicalization, and certificate hashing is delegated to createSnapshot() + sealCer() from @nexart/ai-execution — the canonical source of truth.

# Seal from file
nexart ai seal execution.json

# Seal from stdin
cat execution.json | nexart ai seal

# Seal then verify in a pipe (--quiet keeps the pipe clean)
cat execution.json | nexart ai seal --quiet | nexart ai verify

# Save sealed bundle to file
nexart ai seal execution.json --out cer.json

Default output (stdout — bundle JSON):

{
  "bundleType": "cer.ai.execution.v1",
  "certificateHash": "sha256:...",
  "createdAt": "2026-04-30T12:00:00.000Z",
  "version": "0.1",
  "snapshot": { ... }
}

Status banner (stderr):

[nexart] Local CER created
[nexart] State: Sealed (integrity only)
[nexart] No node attestation present
[nexart] This record is not certified — use `nexart ai certify` to attest via a node
[nexart] certificateHash: sha256:...

Options: | Flag | Default | Description | |------|---------|-------------| | --out / -o | stdout | Save sealed CER bundle to this file | | --quiet / -q | false | Suppress the stderr banner (clean pipe output) |

Sealed vs Certified:

  • Sealed (ai seal): integrity only. No node receipt, no signature, no envelope. Verifies as VERIFIED with Receipt and Envelope reported as SKIPPED — that is correct, not a failure.
  • Certified (ai certify): integrity + node attestation receipt + signature. Requires a node and an API key.

Exit codes:

  • 0 — Sealed successfully
  • 1 — Missing required fields, invalid input JSON, or stdin error

nexart ai verify

Verify a CER bundle or CER package locally. Supports both record schemas: cer.ai.execution.v1 and cer.ai.execution.v2 (V2 records — including confidential V2 records with committed fields — verify offline exactly like V1; commitments do not require openings for base integrity). For V2 records the human output reports the requested model (model, producer-declared) and, when present, the response-declared model (modelEvidence.responseDeclaredModel, producer-transcribed from the provider response — not independently attested). No network call. All verification logic — bundleType discriminant, package detection / inner-CER unwrap, canonicalization, hash recomputation, and 3-layer status (Integrity / Receipt / Envelope) — is delegated to verifyAiCerBundleDetailed() from @nexart/ai-execution. The CLI owns input routing and output formatting only. Fully compatible with v0.11.0+ CERs that include context.signals, v0.6.0+ CER packages, and v0.8.0+ locally-sealed bundles from nexart ai seal.

# Verify a raw CER bundle from file
nexart ai verify cer.json

# Verify a CER package (inner bundle verified; envelope fields are not verified)
nexart ai verify cer-package.json

# From stdin
cat cer.json | nexart ai verify

# JSON output
nexart ai verify cer.json --json

Default output (PASS — raw bundle):

Verification result: VERIFIED
bundleIntegrity: PASS
nodeSignature: SKIPPED
receiptConsistency: SKIPPED

Default output (PASS — CER package):

Input type: CER package
Verification result: VERIFIED
bundleIntegrity: PASS
nodeSignature: SKIPPED
receiptConsistency: SKIPPED
Note: Verified inner CER bundle only. Package-level receipt/signature/envelope not verified by this command.

Default output (FAIL):

Verification result: FAILED
bundleIntegrity: FAIL
[nexart] Expected hash: sha256:a1b2c3...
[nexart] Computed hash: sha256:d4e5f6...

JSON output (--json) — raw bundle:

{
  "status": "VERIFIED",
  "checks": {
    "bundleIntegrity": "PASS",
    "nodeSignature": "SKIPPED",
    "receiptConsistency": "SKIPPED"
  },
  "reasonCodes": [],
  "certificateHash": "sha256:a1b2c3...",
  "bundleType": "cer.ai.execution.v1",
  "verifiedAt": "2026-04-30T12:00:00.000Z",
  "verifier": "@nexart/ai-execution",
  "cli": {
    "version": "0.9.0",
    "sdkVersion": "0.16.1",
    "inputType": "bundle"
  }
}

JSON output (--json) — CER package:

{
  "status": "VERIFIED",
  "checks": {
    "bundleIntegrity": "PASS",
    "nodeSignature": "SKIPPED",
    "receiptConsistency": "SKIPPED"
  },
  "reasonCodes": [],
  "certificateHash": "sha256:a1b2c3...",
  "bundleType": "cer.ai.execution.v1",
  "verifiedAt": "2026-04-30T12:00:00.000Z",
  "verifier": "@nexart/ai-execution",
  "cli": {
    "version": "0.9.0",
    "sdkVersion": "0.16.1",
    "inputType": "package",
    "verifiedInnerCer": true,
    "packageTrustLayersVerified": false
  }
}

The top-level fields (status, checks, reasonCodes, certificateHash, bundleType, verifiedAt, verifier) are the SDK's CerVerificationResult verbatim. The additive cli envelope carries CLI-only metadata (version, sdkVersion, inputType, and package-specific flags when applicable).

Enterprise trust layers (--anchors, --timestamps): these flags opt in to the non-evidentiary secondary-proof layers. They are reported separately and never change the base verification result or the exit code — they cannot turn a VERIFIED bundle into FAILED or vice-versa.

# Report anchor status (VALID / INVALID / NOT PRESENT)
nexart ai verify cer.json --anchors

# Report anchor + trusted timestamp status with a trust summary
nexart ai verify cer.json --anchors --timestamps

When enabled, the human-readable output adds a Layers: block (Anchoring / Timestamp lines) and a Trust summary block (Signature / Anchors / Timestamp / Protocol Version); the JSON output adds top-level anchorStatus and timestampStatus objects ({ checked, valid, errors? }).

AIEF projection (--aief): projects the verification result into the AIEF v0.2.5 Section 9 interoperability shape. Works for both cer.ai.execution.v1 and cer.ai.execution.v2 records.

# AIEF v0.2.5 §9 projection (JSON output)
nexart ai verify cer.json --aief --json
  • Output is JSON in the §9 shape: result, reason, and a checks object containing schemaSupported, integrityValid, protectedSetValid, chainValid.
  • Exit code 0 when result is PASS, 1 when FAIL.
  • The projection covers artifact integrity only — it does not itself establish Node authenticity (receipt/signature) or RFC 3161 timestamp trust; those remain separate layers.

Options: | Flag | Default | Description | |------|---------|-------------| | --json | false | Machine-readable JSON output | | --aief | false | Emit the AIEF v0.2.5 Section 9 projection (JSON); exit 0 = PASS, 1 = FAIL | | --anchors | false | Verify and report anchor status (non-evidentiary; does not affect exit code) | | --timestamps | false | Verify and report trusted-timestamp status + trust summary (non-evidentiary) |

Exit codes:

  • 0 — PASS (inner bundle hash matches)
  • 1 — FAIL (hash mismatch, invalid bundle, or unsupported format)

V2-aware related commands: nexart ai verify on a CER package unwraps and verifies the inner V1 or V2 CER verbatim; nexart export-verification emits a descriptive export artifact carrying the CER verbatim (never resealed/re-certified), for either schema; nexart ai project-verify verifies cer.project.bundle.v1 project bundles containing V1, V2, or mixed V1/V2 steps under the unchanged Project Hash contract.


nexart ai attach-anchor

Attach an external anchor (transparency log / TSA / blockchain) to a CER bundle. This is an append-only, non-evidentiary operation — it does not re-hash or re-sign the bundle.

# Attach an anchor and verify it in one pipe
nexart ai attach-anchor cer.json anchor.json | nexart ai verify --anchors

nexart export-verification

Produce a self-contained verification package that a third party can verify offline, without the NexArt node or this SDK. It is read-only — nothing is re-hashed or re-signed.

# Print a verification package to stdout
nexart export-verification cer.json

# Resolve the signing public key from a node-keys document and save the package
nexart export-verification cer.json --node-keys keys.json --out pkg.json

# From stdin
cat cer.json | nexart export-verification

The package contains { bundle, publicKey, verificationInstructions }, where verificationInstructions lists the protocolVersion, canonicalMode, certificateHash, signatureAlgorithm, the present layer flags, and ordered verification steps.

Options: | Flag | Default | Description | |------|---------|-------------| | --out, -o | — | Save the package to this file (otherwise printed to stdout) | | --node-keys | — | Path to a NodeKeysDocument JSON to resolve the signing public key | | --public-key | — | Provide the public key directly (takes precedence over --node-keys) |


Independent Verification (nexart verify-bundle)

Standalone, SDK-independent verification of a NexArt record.

nexart verify-bundle verifies a CER bundle (cer.ai.execution.v1) completely independently of NexArt. Unlike nexart ai verify (which delegates to @nexart/ai-execution) and export-verification (which only emits instructions), this command performs the cryptographic checks itself.

All verification logic lives in a single, self-contained file (src/standaloneVerify.ts) that imports only node:crypto — nothing from @nexart/*, no network, no external dependencies. An auditor can read that one file end-to-end and reproduce the result with just the bundle JSON, the signer's public key, and the Node.js standard library.

# Recompute the certificate hash offline (integrity only)
nexart verify-bundle cer.json

# Also verify the node's Ed25519 receipt signature
nexart verify-bundle cer.json --public-key node-keys.json

# Machine-readable output
nexart verify-bundle cer.json --public-key node-keys.json --json

# Show every intermediate step (canonical mode, byte counts, hashes)
nexart verify-bundle cer.json --public-key node-keys.json --verbose

# Additionally verify an embedded RFC 3161 timestamp against trusted TSA roots
nexart verify-bundle cer.json --verify-timestamp --tsa-roots tsa-roots.pem

What it checks

  1. Integrity — recomputes certificateHash from the bundle's evidentiary payload (bundleType, createdAt, snapshot, version, and, when present, context / contextSummary / policyEvaluation) and compares it to the stored value. The whole snapshot is hashed, so an optional snapshot.identity assertion (v0.23.0+) is bound by certificateHash and tampering with it FAILS — no special-casing in the verifier. The identity is PII-safe by construction — only the IdP provider/subject anchor plus sha256 commitments (assertionHash, optional emailHash), never a raw JWT or email — and anonymous mode is simply its absence: records without snapshot.identity verify identically (the report shows Identity: none). Canonicalisation is bound to snapshot.protocolVersion:

    • 1.2.0nexart-v1 (NexArt's original canonical JSON)
    • 1.3.0jcs-v1 (RFC 8785 JSON Canonicalization Scheme)

    An unknown/unsupported protocolVersion fails closed (it is never silently coerced to a default).

  2. Signature — when a signed node attestation receipt is present (at bundle.receipt + bundle.signature, or bundle.meta.attestation.*) and a public key is available, the Ed25519 signature is verified over the canonical JSON of the receipt. The receipt's certificateHash must also match the bundle's. If no receipt is present, or no public key is supplied, the signature check is reported as skipped (integrity can still pass).

    If a signature is not present, integrity can still pass, but authenticity is not established.

  3. Timestamp (RFC 3161) (optional, additive — --verify-timestamp) — when the bundle carries an RFC 3161 timestamp (in trustedTimestamps[] or a singular timestamp), this offline check confirms the token anchors the bundle to a point in time. Both wire shapes are accepted: a full TimeStampResp (the certification node's output) and a bare TimeStampToken (e.g. OpenSSL's -token_out) — the response wrapper is detected and unwrapped automatically. It verifies, in order: (1) the TSA's CMS signature over the embedded TSTInfo; (2) that the token's message imprint equals the bundle's certificateHash digest; (3) that the TSA signing certificate chains to a root supplied via --tsa-roots; (4) that the certificate is valid at the timestamp; and (5) that the timestamp is at or after the bundle's createdAt.

    This layer is strictly additive: it never modifies certificateHash, is never included in canonicalisation, and never affects the Integrity or Signature results or the exit code. The ASN.1 stack (pkijs) is loaded only when --verify-timestamp is passed — ordinary verification stays dependency-light. Without the flag, an existing timestamp is reported as present (not verified); with no timestamp at all, not present.

Output

Human-readable (default):

✔ Integrity: PASS
✔ Signature: PASS
Protocol: 1.3.0
✔ Externally anchored timestamp: valid

Timestamp Details:
  Provider: freeTSA
  Time: 2026-06-17T10:52:12Z
  Imprint: match

The phrasing — "externally anchored timestamp" — reflects that the check proves the bundle was anchored to an independent time authority, not an internal clock. When a token was actually verified, a Timestamp Details block follows with the provider, the token's genTime (sub-second precision stripped), and whether the message imprint matched the bundle's certificateHash.

The status line is one of:

✔ Externally anchored timestamp: valid
✖ Externally anchored timestamp: invalid (signature)
✖ Externally anchored timestamp: invalid (imprint mismatch)
✖ Externally anchored timestamp: untrusted (unknown root)
✖ Externally anchored timestamp: invalid (expired)
✖ Externally anchored timestamp: invalid (time)
✖ Externally anchored timestamp: invalid (malformed token)
○ Externally anchored timestamp: present (not verified)   # token present, flag absent
○ Externally anchored timestamp: not present              # no token in the bundle

Machine-readable (--json):

{
  "status": "verified",
  "integrity": "pass",
  "signature": "pass",
  "protocolVersion": "1.3.0",
  "timestamp": { "status": "valid", "tsa": "freeTSA", "genTime": "2026-06-17T10:52:12.000Z" }
}

status is verified when integrity passes and the signature is not invalid (pass or skipped); otherwise it is failed. The timestamp field is always present and purely informational — it never changes status or the exit code. The process exits 0 when verified and 1 otherwise, so it is safe to use in CI.

Options: | Flag | Default | Description | |------|---------|-------------| | --public-key | — | Path to the Ed25519 public key — PEM, JWK, a NodeKeysDocument JSON, or a raw base64url key | | --kid | — | Key id to select when --public-key is a NodeKeysDocument. Strict — if the requested kid is absent, the signature fails closed (no fallback to another key) | | --json | false | Emit a machine-readable result | | --verbose | false | Print intermediate verification steps | | --verify-timestamp | false | Additionally verify an embedded RFC 3161 timestamp (offline; never affects the exit code) | | --tsa-roots | — | Path to trusted TSA root certificate(s) (PEM) used by --verify-timestamp. Without trusted roots, a present token reports UNTRUSTED |

Note on the command name. This command is named verify-bundle rather than verify because nexart verify <snapshot> already exists for Code Mode snapshots. The standalone CER verifier is purely additive and changes no existing command.

Cryptography. Integrity uses SHA-256 and signatures use Ed25519, both from the Node.js built-in crypto module (RFC 8032 pure Ed25519) — chosen so the verifier needs zero third-party dependencies and stays trivially auditable.


Verifying a full CER record (nexart verify-record)

nexart verify-record verifies a full CER record — the self-contained envelope a relying party receives — rather than a bare bundle. A record bundles the evidentiary payload together with its signed node attestation receipt and an optional externally anchored timestamp:

{
  "bundle": { /* the cer.ai.execution.v1 bundle */ },
  "verification": {
    "receipt": { /* signed node attestation receipt */ },
    "receiptSignature": "base64url-ed25519-signature"
  },
  "timestamp": { "token": "base64-rfc3161-timestamptoken" }  // optional
}

Fully automatic since v0.16.0. Give it a record file and nothing else — the command verifies integrity, the receipt signature, and the RFC 3161 timestamp automatically, and prints an audit-grade report. It is self-contained: a third party needs only the record and this CLI.

# Full automatic verification: integrity + signature + timestamp
nexart verify-record record.json

# Machine-readable report
nexart verify-record record.json --json

# Read the record from stdin (use "-" as the path)
curl -s https://node.example/records/abc.json | nexart verify-record -

# One-line verdict only (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)
nexart verify-record record.json --short

# Fully offline, with an explicit signing key
nexart verify-record record.json --offline --public-key node-keys.json

Flexible input. The bundle is taken from record.bundle when present, or the record root is treated as the bundle itself (a certified bundle with its receipt and signature embedded works directly). There is no "missing bundle" error — a structurally invalid record simply reports a clean Integrity: FAIL.

Three-state model (since v0.16.1). A public record served from a node may be privacy-redacted: the snapshot's input / output content is stripped while the inputHash / outputHash anchors are kept. Because certificateHash is sealed over the full snapshot, it can no longer be recomputed from a redacted snapshot — but that is not tampering. verify-record (and verify-url / verify-hash) detect this case and report a third outcome:

| Outcome | When | Integrity line | Exit | | --- | --- | --- | --- | | VERIFIED | content present, certificateHash recomputes, signature not invalid | ✔ Integrity: PASS | 0 | | AUTHENTIC (REDACTED) | content stripped (hashes kept), recompute not possible, signature passes | ⚠ Integrity: NOT RECOMPUTABLE (redacted) | 0 | | FAILED | a real integrity mismatch (content present but hash differs) or an invalid signature | ✖ Integrity: FAIL | 1 |

A redacted record is reclassified only when the snapshot is genuinely content-stripped and the signature still verifies — a true content mismatch is never softened to redacted, an invalid signature is always FAILED, and a redacted record whose hash still recomputes simply stays VERIFIED. The certificateHash computation, canonicalisation, and signature/timestamp logic are unchanged; this is purely a classification and presentation layer.

What AUTHENTIC (REDACTED) does and does not prove. It proves the record carries a valid node signature over the sealed certificateHash — i.e. it is authentic. It does not recompute the full snapshot, so it cannot prove the content behind the kept inputHash / outputHash was not altered before redaction. Redaction detection is structural (content absent, hashes present), so treat AUTHENTIC (REDACTED) as trustworthy only when the record comes from an authenticated node source (e.g. the node's public endpoint over TLS). For the strongest guarantee, obtain the full, un-redacted record and re-run verify-record to get VERIFIED.

Signing key resolution (by priority).

  1. --public-key <file> — an explicit override (PEM, JWK, NodeKeysDocument, or raw base64url).
  2. Auto-fetch — otherwise the node's published key set is fetched from https://node.nexart.io/.well-known/nexart-node.json and the key whose kid matches the receipt's attestorKeyId is used. This is the only network call in the verify path; it lives entirely in the CLI layer (the auditable core never touches the network) and fails gracefully — a network error just falls through to the next source. Override the URL with NEXART_NODE_WELLKNOWN_URL, or disable the fetch entirely with --offline.
  3. Embedded key — a key embedded in the bundle, if any.

Automatic timestamp verification. An RFC 3161 token at verification.timestamp.token, timestamp.token, or embedded in the bundle is verified automatically and offline against an embedded DigiCert trust store (DigiCert Trusted Root G4 + DigiCert Assured ID Root CA). Override the trusted roots with --tsa-roots <file> (PEM). The ASN.1 stack (pkijs / asn1js) loads only when a token is actually present. The timestamp layer is strictly additive: it never modifies certificateHash, is never canonicalised, and never affects the Integrity / Signature result or the exit code (which is 0 only when integrity passes and the signature is not invalid, 1 otherwise) — an untrusted-root, imprint-mismatch, or unreadable---tsa-roots outcome is reported as a timestamp status, never a crash.

Input from a file or stdin. Pass a file path, or - to read the record from standard input — handy for pipelines such as curl -s … | nexart verify-record -.

Report. The default output is an audit-grade summary rendered in a fixed section order — Signature, Integrity, Timestamp (VERIFIED / INVALID (<reason>) / PRESENT BUT NOT VERIFIED / NOT INCLUDED, plus generation time and authority), Node (node id, key id, key source, operator), Identity (asserted identity, or none), and Execution (protocol, provider, model, input/output hashes, parameters, created-at). A present-but-unverifiable token (a parse failure, or verification not attempted) is shown as a caution — ⚠ Timestamp: PRESENT BUT NOT VERIFIED — never as an error, and the timestamp never affects FINAL STATUS. The report ends with a single headline line — FINAL STATUS: VERIFIED EVIDENCE RECORD, FINAL STATUS: AUTHENTIC (REDACTED), or FINAL STATUS: FAILED. For a redacted record the Signature line is shown before the Integrity line and a tight context block follows the headline:

FINAL STATUS: AUTHENTIC (REDACTED)
Mode: Public Verification (Redacted)
Redaction: Applied by certification node (privacy mode)
! Full integrity verification requires access to original execution data

--json emits the same information as a structured object (with status of verified / authentic-redacted / failed, integrity including redacted, and a redacted boolean); --short prints only ✔ VERIFIED, ⚠ AUTHENTIC (REDACTED), or ✖ FAILED.

| Option | Description | | --- | --- | | --public-key <file> | Override the signing key. Default: auto-fetched from the node .well-known key set. | | --offline | Do not contact the network; use --public-key or a bundle-embedded key only. | | --kid <id> | Key id to select from the key set (default: the receipt's attestorKeyId). | | --tsa-roots <file> | Override the default DigiCert TSA trust roots with custom root certificate(s) (PEM). | | --json | Emit a machine-readable report. | | --short | Print only a single verdict line (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED). | | --verbose | Show intermediate verification steps. |

verify-bundle is unchanged. The redesigned output and automatic key/timestamp resolution apply to verify-record only; verify-bundle keeps its existing behaviour, flags, and byte-for-byte output.


Offline verification (nexart verify-offline)

nexart verify-offline <record.json> is a convenience alias for verify-record --offline for relying parties who want a single, unambiguous "verify this locally, never touch the network" command. It runs the exact same verification core — integrity (certificate-hash recompute), Ed25519 receipt signature, and timestamp reporting — and never contacts a NexArt node. The signing key must come from --keys (an alias for --public-key) or be embedded in the record.

# Verify a record locally with an explicit node key file
nexart verify-offline record.json --keys node-keys.json

# Verify a record that embeds its own signing key
nexart verify-offline record.json

# Read the record from stdin
curl -s https://node.example/records/abc.json | nexart verify-offline - --keys node-keys.json

It differs from verify-record only in presentation: a passing record reports FINAL STATUS: AUTHENTIC (OFFLINE) with an explicit Mode: Offline Verification line (the redaction note is still appended for a privacy-redacted record), while a real mismatch or invalid signature stays FINAL STATUS: FAILED with exit 1. --json adds "mode": "offline". Hashing, canonicalisation, the 3-state core, and exit codes are identical to verify-record — this is a UX wrapper, not new verification logic. As with verify-record, a missing or unverifiable timestamp is reported but never changes the verdict or exit code.

This verifies authenticity and integrity, not re-execution. A pass confirms the record was signed by a NexArt node and has not been tampered with; it does not re-run the workload.


Verifying from a URL (nexart verify-url)

nexart verify-url <url> is the one-command verification experience: it fetches a CER record (or a bare bundle) as JSON from a URL and verifies it with the exact same automatic key/timestamp resolution and audit-grade renderer as verify-record.

# Fetch and fully verify a published record
nexart verify-url https://node.example/records/abc.json

# One-line verdict (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)
nexart verify-url https://node.example/records/abc.json --short

# Machine-readable report
nexart verify-url https://node.example/records/abc.json --json

The response is auto-detected as a full record (has verification.receipt) or a raw bundle; both route through the same rich verifier (a bare bundle is accepted directly). The URL fetch is the record source and always happens; --offline here only disables the separate signing-key auto-fetch (so pair it with --public-key or rely on a bundle-embedded key). A non-2xx response, a network error, or a non-JSON body fails cleanly with exit 1. All other options match verify-record (--public-key, --kid, --tsa-roots, --json, --short, --verbose).


Verifying by certificate hash (nexart verify-hash)

nexart verify-hash <certificate_hash> resolves a CER record straight from its certificate hash. It constructs the public node lookup URL (https://node.nexart.io/v1/cer/public?certificate_hash=<hash>) and then verifies it through verify-url, so the output is identical to verify-url.

# Resolve and fully verify a record by certificate hash
nexart verify-hash sha256:abc123…

# One-line verdict (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)
nexart verify-hash sha256:abc123… --short

# Machine-readable report
nexart verify-hash sha256:abc123… --json

The hash must include the canonical sha256: prefix; a missing prefix fails cleanly with exit 1 (before any network call). Network failures behave exactly like verify-url. Options match verify-url (--public-key, --kid, --tsa-roots, --json, --short, --offline, --verbose); as with verify-url, --offline only disables the signing-key auto-fetch — the record itself is always fetched.

Note: this is distinct from nexart verify <snapshot>, which verifies that a snapshot reproduces its expected output.


nexart ai project-verify (v0.7.0+)

Verify a project bundle (cer.project.bundle.v1) locally. No network required. Delegates all verification logic to verifyProjectBundle() from @nexart/ai-execution — the canonical source of truth for project bundle semantics. The CLI provides the command surface and output formatting.

# From file
nexart ai project-verify project-bundle.json

# From stdin
cat project-bundle.json | nexart ai project-verify

# Machine-readable JSON output
nexart ai project-verify project-bundle.json --json

Checks performed (in order, via @nexart/ai-execution):

  1. bundleType discriminant: must be cer.project.bundle.v1
  2. Required fields: version, protocolVersion, projectBundleId, projectTitle, startedAt, completedAt, integrity block shape
  3. stepRegistry is a non-empty array
  4. embeddedBundles is a plain object keyed by stepId
  5. totalSteps must equal stepRegistry.length
  6. Per-step: embedded bundle exists; executionId and certificateHash match the registry entry; inner CER certificateHash is independently valid
  7. integrity.projectHash is recomputed from all material project metadata and compared to the declared value

Default output (PASS):

Verification result: VERIFIED
bundleType: cer.project.bundle.v1
projectBundleId: proj-abc123
projectTitle: My Project
totalSteps: 3
passedSteps: 3
failedSteps: 0
projectHashIntegrity: PASS
stepIntegrity: PASS

Default output (FAIL):

Verification result: FAILED
bundleType: cer.project.bundle.v1
projectBundleId: proj-abc123
projectTitle: My Project
totalSteps: 3
passedSteps: 2
failedSteps: 1
projectHashIntegrity: FAIL
stepIntegrity: FAIL
[nexart] Error: integrity.projectHash mismatch: expected sha256:..., got sha256:...

JSON output (--json):

{
  "status": "VERIFIED",
  "bundleType": "cer.project.bundle.v1",
  "projectBundleId": "proj-abc123",
  "projectTitle": "My Project",
  "totalSteps": 3,
  "passedSteps": 3,
  "failedSteps": 0,
  "projectHashValid": true,
  "checks": {
    "structuralValid": true,
    "projectHashIntegrity": "PASS",
    "stepIntegrity": "PASS"
  },
  "steps": [
    {
      "stepId": "step-001",
      "sequence": 0,
      "executionId": "exec-abc",
      "certificateHash": "sha256:...",
      "ok": true,
      "errors": []
    }
  ],
  "errors": [],
  "verifiedAt": "2026-04-06T12:00:00.000Z",
  "verifier": "@nexart/cli"
}

Options: | Flag | Default | Description | |------|---------|-------------| | --json | false | Machine-readable JSON output |

Exit codes:

  • 0 — VERIFIED (all checks pass)
  • 1 — FAILED (any structural, step, or hash error)

Context Signals (v0.5.0+)

Context signals attach upstream evidence — CI approvals, webhook events, human-review records — to a CER. They are included in the context.signals array and are tamper-evident: the certificateHash covers the full context block, so any post-creation modification to signals is detected by nexart ai verify.

Preparing a signals file

A signals file is a plain JSON array of signal objects:

[
  {
    "type": "approval",
    "source": "github-actions",
    "step": 0,
    "timestamp": "2026-03-15T12:00:00.000Z",
    "actor": "alice",
    "status": "ok",
    "payload": { "pr": 42 }
  }
]

Attaching signals when creating / certifying

# Create CER with signals
nexart ai create execution.json --signals-file signals.json --out cer.json

# Certify with signals
nexart ai certify execution.json --signals-file signals.json

The CLI reads the JSON array from --signals-file, merges it into the request body as { signals: [...] }, and sends it to the node API. The node then embeds the signals in the CER under context.signals and includes them in the certificateHash.

Verifying a signals-containing CER

No extra flags are needed — nexart ai verify handles both plain CERs and CERs with context.signals:

nexart ai verify cer.json
# Verification result: VERIFIED
# bundleIntegrity: PASS

If any signal field is altered after creation, the hash will mismatch and verification fails:

# (tampered context.signals)
nexart ai verify tampered-cer.json
# Verification result: FAILED
# bundleIntegrity: FAIL

CER Packages (v0.6.0+)

A CER package is an envelope object that wraps a raw CER bundle with optional metadata layers — receipt, signature, and custom envelope fields:

{
  "cer": {
    "bundleType": "cer.ai.execution.v1",
    "certificateHash": "sha256:...",
    "version": "1.0.0",
    "createdAt": "...",
    "snapshot": { ... }
  },
  "receipt": { ... },
  "signature": "base64url-sig=="
}

nexart ai verify automatically detects the format:

  • Raw bundle — input has bundleType === 'cer.ai.execution.v1' at the top level. Behavior is identical to pre-v0.6.0.
  • CER package — input has a cer field whose inner bundleType === 'cer.ai.execution.v1'. The CLI verifies only the inner bundle; receipt, signature, and other envelope fields are not verified.

Detection is transparent — no flags are needed. Both formats use the same nexart ai verify command.

What IS verified (inner bundle)

  • certificateHash is recomputed from the protected set (bundleType, version, createdAt, snapshot) and the optional context block (signals). A mismatch reports BUNDLE_HASH_MISMATCH.

What is NOT verified (package envelope)

  • receipt fields (attestationId, nodeRuntimeHash, etc.)
  • signature (cryptographic envelope signature)
  • Custom envelope fields

Full package trust verification (receipt + envelope signature) is planned for a future release.


Remote Renderer

The CLI calls a canonical Node.js renderer endpoint for real PNG generation.

Endpoint Configuration

# Via environment variable
export NEXART_RENDERER_ENDPOINT=https://nexart-canonical-renderer-production.up.railway.app
export NEXART_API_KEY=nx_live_...

# Via CLI flags
nexart run sketch.js --endpoint https://render.nexart.io --api-key nx_live_...

Expected API

POST /api/render
Content-Type: application/json
Authorization: Bearer <api_key>

{
  "code": "...",
  "seed": 12345,
  "VAR": [0,0,0,0,0,0,0,0,0,0],
  "width": 1950,
  "height": 2400,
  "protocolVersion": "1.2.0"
}

Response: image/png (binary)
Headers:
  X-Runtime-Hash: <hash>

Equivalent curl

curl -X POST https://nexart-canonical-renderer-production.up.railway.app/api/render \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer nx_live_..." \
  -d '{"code":"createCanvas(1950,2400);\nbackground(0);\n","seed":12345,"VAR":[0,0,0,0,0,0,0,0,0,0],"width":1950,"height":2400,"protocolVersion":"1.2.0"}' \
  --output render.png

Snapshot Format (v1)

{
  "protocol": "nexart",
  "protocolVersion": "1.2.0",
  "runtime": "canonical",
  "runtimeHash": "<sha256>",
  "codeHash": "<sha256>",
  "seed": 12345,
  "VAR": [0,0,0,0,0,0,0,0,0,0],
  "canvas": { "width": 1950, "height": 2400 },
  "outputHash": "<sha256>",
  "createdAt": "2026-01-25T...",
  "code": "..." // optional, if --include-code
}

Hash definitions:

  • outputHash = SHA-256 of PNG bytes
  • codeHash = SHA-256 of normalized code
  • runtimeHash = From renderer or SHA-256 of SDK version

Renderer Modes

| Mode | Description | |------|-------------| | --renderer remote | Default. Calls canonical renderer with auth, produces real PNG output. | | --renderer local | NOT implemented yet. Outputs a 1x1 placeholder image only. No auth required. |

# Remote (default) — real PNG output, requires API key for remote endpoints
nexart run sketch.js --seed 12345

# Local — NOT implemented, placeholder only, no auth
nexart run sketch.js --renderer local

Real local/offline rendering is planned for a future release.

Environment Variables

| Variable | Description | |----------|-------------| | NEXART_RENDERER_ENDPOINT | Remote renderer URL (default: http://localhost:5000) | | NEXART_NODE_ENDPOINT | NexArt node API URL (default: https://node.nexart.art) | | NEXART_API_KEY | API key for authenticated requests |

License

MIT — Free for all use including commercial.

See Core vs Edges for the NexArt business model.