npm package: @nexart/cli
Description: NexArt CLI — Code Mode and AI execution certification
Author: NexArt
Latest version: 0.7.0

# @nexart/cli v0.7.0

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

## Installation

```bash
# 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:

```bash
# 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.

```bash
# 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:

```bash
# 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

```bash
# 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

```bash
# 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:

```bash
# 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 mode (placeholder PNG, no auth required)
nexart run sketch.js --renderer local
```

**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

### verify

Check that a snapshot produces the expected output:

```bash
nexart verify render.snapshot.json

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

**Exit codes:**
- `0` — PASS (hashes match)
- `1` — FAIL (hashes differ or error)

### replay

Re-execute from a snapshot:

```bash
nexart replay render.snapshot.json --out replay.png

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

---

## 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.

```bash
# 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):
```json
{
  "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.

```bash
# 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`):
```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 verify`

Verify a CER bundle **or CER package** locally. No network call — computes and compares the `certificateHash` against the bundle contents. Fully compatible with v0.11.0+ CERs that include `context.signals`, and v0.6.0+ CER packages.

```bash
# 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:
```json
{
  "status": "VERIFIED",
  "checks": {
    "bundleIntegrity": "PASS",
    "nodeSignature": "SKIPPED",
    "receiptConsistency": "SKIPPED"
  },
  "reasonCodes": [],
  "certificateHash": "sha256:a1b2c3...",
  "bundleType": "cer.ai.execution.v1",
  "inputType": "bundle",
  "verifiedAt": "2026-03-25T12:00:00.000Z",
  "verifier": "@nexart/cli"
}
```

**JSON output** (`--json`) — CER package:
```json
{
  "status": "VERIFIED",
  "checks": {
    "bundleIntegrity": "PASS",
    "nodeSignature": "SKIPPED",
    "receiptConsistency": "SKIPPED"
  },
  "reasonCodes": [],
  "certificateHash": "sha256:a1b2c3...",
  "bundleType": "cer.ai.execution.v1",
  "inputType": "package",
  "verifiedAt": "2026-03-25T12:00:00.000Z",
  "verifier": "@nexart/cli",
  "verifiedInnerCer": true,
  "packageTrustLayersVerified": false
}
```

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

**Exit codes:**
- `0` — PASS (inner bundle hash matches)
- `1` — FAIL (hash mismatch, invalid bundle, or unsupported format)

---

### `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.

```bash
# 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`):
```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:

```json
[
  {
    "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

```bash
# 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`:

```bash
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:

```bash
# (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:

```json
{
  "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

```bash
# 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

```bash
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)

```json
{
  "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. |

```bash
# 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](../../docs/core-vs-edges.md) for the NexArt business model.