Metadata-Version: 2.4
Name: northnoir
Version: 1.2.0
Summary: Local AI-drawn video: Ruvil 2.1 code-drawn scenes by default, or Ruvil 7.3 hand-drawn frame-by-frame JS/image/copy cels with your own API keys.
Author: North Noir
License: Proprietary
Project-URL: Homepage, https://northnoir.com/cli
Project-URL: Documentation, https://northnoir.com/cli
Project-URL: Source, https://github.com/syver777/NorthNoir/tree/main/packages/northnoir-cli
Keywords: ai-drawn-video,animation,cli,playwright,ffmpeg
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: playwright==1.63.0
Requires-Dist: Pillow==12.3.0
Requires-Dist: numpy==2.5.3; python_version >= "3.12"
Requires-Dist: numpy==2.2.6; python_version < "3.12"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"

# northnoir — the local CLI for AI-drawn video

Make **AI-drawn video** from your terminal. **Ruvil 2.1 (Code-drawn) is the
default**: your coding agent writes `scene.html` (`window.SCENE`), Chromium
paints every frame, and `ffmpeg` encodes H.264. Choose **Ruvil 7.3 (Hand-drawn
frame-by-frame)** explicitly with `northnoir r73`, `northnoir draw-frames`, or
`northnoir draw --model latest` (`latest` → `ruvil_7_3`). It draws individual
cels with JS, AI image generation, or vision-authored copy code. No North Noir
account or cloud tokens are required.

Your agent (Claude Code, OpenCode, Codex, Gemini CLI, Cursor) writes the
drawing code, runs the checks, *looks at its own frames*, fixes what is wrong,
and renders the final MP4. That loop is the product.

## Install

Recommended (isolated, puts `northnoir` on your PATH):

```sh
pipx install northnoir
northnoir doctor --install   # installs Chromium via Playwright, then verifies
northnoir doctor             # Chromium, ffmpeg, pinned libraries (SHA256)
```

Alternatives:

```sh
uv tool install northnoir    # same PyPI package, via uv
pip install northnoir        # into the current Python environment
```

From source: `git clone https://github.com/syver777/NorthNoir.git`, then
`pipx install ./NorthNoir/packages/northnoir-cli`.

The npm package is **not published**. `bin/northnoir.js` in this repository is
a thin Node shim over the Python package: it needs Python 3.10+ with
`playwright`, `Pillow` and `numpy` already installed, so it is not a
stand-alone `npx` installer and is not advertised as one. The PyPI package
above is the one supported distribution.

Your host coding agent and your machine do the work: the agent's own
usage/subscription terms apply to its model use, and rendering uses your local
CPU and disk. The CLI is **free with your own API keys**; North Noir charges
nothing for local work, but image/LLM/audio providers may charge for usage.
Ruvil 2.1 code-drawn rendering requires no provider keys.

Requirements: Python 3.10+, `ffmpeg` on PATH (`brew install ffmpeg` on macOS,
`sudo apt install ffmpeg` on Debian/Ubuntu). Chromium is installed for you by
`northnoir doctor --install` (Playwright). Docker is an optional transparent
fallback when a local `ruvil-js-renderer:local` image exists.

## Quickstart

### Ruvil 7.3 — Hand-drawn frame-by-frame (explicit selection)

```sh
northnoir draw --model latest --ref reference.png --out film --plan-only --no-llm
northnoir draw --model ruvil_7_3 --draw-mode image --ref reference.png --out film --sfx elevenlabs
northnoir r73 --ref reference.png --out film --voice "A long day comes to a close."
```

`r73` and its alias `draw-frames` explicitly select Ruvil 7.3 (no `--model`
needed). The separate `draw` command has `--model latest | ruvil_7_3 |
ruvil_2_1`; omitting `--model` selects **Ruvil 2.1**, not the latest model.
The legacy `ruvil_2_1_js` alias still resolves to `ruvil_2_1`.
The current cel CLI uses a six-section **9-to-7 Day in the Life**
template; `--brief` adds direction, not an arbitrary new story. Each section
has an X-sheet, drawn keys and in-betweens. `--fps 4` (default) draws four
cels/s, held into 24 fps video; defaults are six sections of four seconds.

**When to use which:** use Ruvil 2.1 for an arbitrary scene your agent authors
and animates directly in code, with procedural audio and no provider keys.
Use Ruvil 7.3 for the shipped cel-film template, editable individual frames,
X-sheets, image-based character references, or copying an existing cel sequence.

#### Ruvil 7.3 draw modes

`--draw-mode` (alias `--medium`) is **Ruvil 7.3-only**:

- **`js` (default):** local code-drawn cels from the shipped JS template.
  Saves editable `cels.js` and `scene.html`; no image or planning API call.
  `--ref` is accepted for workflow consistency but does not alter this template.
- **`image`:** AI image-gen cels. Each key is anchored to the reference and
  previous key; in-betweens use the reference plus both surrounding keys.
  Requires your `OPENAI_API_KEY`; optional pose enrichment uses your LLM key.
- **`copy`:** a vision LLM writes a separate `.js` Canvas drawing of each source
  cel. Use `--source-frames <film-or-frames-dir>` instead of `--ref` and your
  `OPENROUTER_API_KEY` (optional `--llm-model`). Zero image API calls, no raster
  embedding or automatic tracing. It is an approximation, not a pixel-exact
  guarantee. Native-resolution copies, comparisons and metrics are retained;
  delivery frames fit the selected output canvas without stretching. Existing
  section audio is reused; `--sfx off` permits a silent copy without source audio.

```sh
northnoir r73 --ref reference.png --draw-mode js --aspect 4:5 --sfx code --out portrait
northnoir draw-frames --ref reference.png --draw-mode image --ar 9:16 --out phone
northnoir r73 --draw-mode copy --source-frames original --aspect 21:9 --sfx off --out copies
```

#### Six aspect ratios

`--aspect` (alias `--ar`) applies to **all three Ruvil 7.3 draw modes**, on
`r73`, `draw-frames` and `draw --model latest`. Unknown values normalise to
Desktop `16:9`. Plans/X-sheets, JS canvases, saved delivery cels and H.264 MP4s
record/use these dimensions; audio remains 48 kHz stereo.

| Format | Aspect | Output pixels |
|---|---|---|
| Desktop (default) | `16:9` | 1920×1080 |
| Phone | `9:16` | 1080×1920 |
| Square | `1:1` | 1080×1080 |
| Portrait feed | `4:5` | 1080×1350 |
| Cinematic | `21:9` | 2520×1080 |
| Retro TV | `4:3` | 1440×1080 |

JS template art and native copy drawings are fitted proportionally on paper,
not stretched. Image mode asks the provider for landscape/portrait/square cels,
then centre-crops to the exact output dimensions. Use a fresh `--out` directory
when changing draw mode or aspect; existing film files are preserved.

Set provider keys in your environment or `~/.config/northnoir/keys.json`.
The file accepts provider ids (`openai`, `openrouter`, `deepinfra`,
`elevenlabs`, `fal`) or environment-variable names; environment values win.
Never put keys in briefs, scene files, reports or source control.

- `OPENAI_API_KEY` is required only for `--draw-mode image`.
- `OPENROUTER_API_KEY` is required for new `--draw-mode copy` vision programs.
- Optional `OPENROUTER_API_KEY` / `DEEPINFRA_API_KEY` enrich poses (OpenAI is
  also supported). `--no-llm` skips enrichment.
- `ELEVENLABS_API_KEY` supplies SFX and `--voice` narration.
  `ELEVENLABS_VOICE_ID` selects the voice. `FAL_KEY` is an SFX fallback.
- `--sfx code` uses deterministic local Foley; `--sfx off` omits sound.
  Failed ElevenLabs SFX fall back to code Foley with the source recorded.
- `--plan-only` writes plans without provider calls (`--no-llm` is also accepted).
  A fully local 7.3 JS run uses `--sfx code` or `--sfx off`, without `--voice`.

Outputs: `plan.json`, per-section `xsheet.json`, `frames/fNNNN.jpg`, contact
sheets, SFX WAVs, `audio.wav`, `section.mp4`, `all-frames/`, and the stitched
`day-in-the-life-r73-js.mp4` (JS), `day-in-the-life-r73.mp4` (image), or
`day-in-the-life-r73-copies.mp4` (copy). Optional `narration.wav` is saved separately, not
muxed into the film. Inspect the saved frames/contact sheets and verify the
film with `ffprobe`. Use `--only <section-slug>` to resume selected sections.

### Ruvil 2.1 — Code-drawn (default)

```sh
northnoir init my-film --title "My Film"     # scaffold scene.html
northnoir check my-film/scene.html --still   # gate 1: frame 0 + report
northnoir check my-film/scene.html           # gate 2: sampled frames + quality
northnoir render my-film/scene.html -o my-film/my-film.mp4
# Equivalent explicit model route (writes output/scene.mp4):
northnoir draw --model ruvil_2_1 --scene my-film/scene.html --out output
# Omitted --model means Ruvil 2.1 too:
northnoir draw --scene my-film/scene.html --out output
```

Or hand the whole job to your agent: install the bundled skill (below), then
ask for a video. The skill teaches the agent the contract and the loop.

## The Ruvil 2.1 4-step agent loop

1. **Author** — write `scene.html`: one self-contained page whose JavaScript
   paints every frame of a 1920×1080 canvas. Frame 0 already shows the
   finished scene.
2. **Still check** — `northnoir check scene.html --still`, then *view*
   `frame-0000.png`. Fix composition, scale and contrast before animating.
3. **Motion check** — `northnoir check scene.html`, view `contact-sheet.png`,
   read the readability/collision/composition flags, fix, re-check (up to 3
   improvement rounds; keep the best version).
4. **Render** — `northnoir render scene.html -o out.mp4`, view the poster,
   verify with `ffprobe` (1920×1080, H.264, declared duration, stereo audio).

## Commands

| Command | What it does |
|---|---|
| `northnoir draw --scene scene.html --out <dir>` | Ruvil 2.1 Code-drawn (default); `--model latest` / `ruvil_7_3` explicitly selects Ruvil 7.3 instead. |
| `northnoir r73` / `northnoir draw-frames --ref <drawing> --out <dir>` | Explicit Ruvil 7.3 Hand-drawn frame-by-frame. `--draw-mode js` (default), `image`, or `copy`; six ratios via `--aspect` / `--ar`. Copy uses `--source-frames` instead of `--ref`. |
| `northnoir doctor [--install] [--json]` | Verify Chromium/Playwright, `ffmpeg`/`ffprobe`, and the pinned scene libraries (SHA256). `--install` runs the guided setup (Playwright Chromium) first. |
| `northnoir init <dir> [--title "..."] [--duration 5] [--fps 24] [--style <id>] [--force] [--json]` | Scaffold `<dir>/scene.html` (working `window.SCENE` starter) + `<dir>/.northnoir/`. |
| `northnoir check <scene.html> [--still] [--frames 0,12,24,...] [--out <dir>] [--json]` | Deterministic observer + quality + audio check. Writes full-res `frame-NNNN.png` files, `contact-sheet.png`, `report.json` (and `audio.wav` when the scene has audio) to `<scene_dir>/.northnoir/check/` by default. Always prints JSON. |
| `northnoir render <scene.html> [-o <output.mp4>] [--fps 24] [--duration <sec>] [--no-audio] [--json]` | Render every frame at 1920×1080, render `SCENE.audio` offline (48 kHz stereo), mux H.264 + AAC. Writes `<output>.poster.png` and `<output>.sheet.png`. Always prints JSON. |
| `northnoir styles [--json]` | List the 31 AnidoodleDraw style ids and the RuvilMarks helpers. |
| `northnoir skill [install] [--install] [--json]` | Print the bundled `northnoir-draw` skill path; `install` copies it into the agent skill folders. |
| `northnoir --version` | Print the CLI version. |

Exit codes: `0` only when the report is `ok`, `1` on scene/encode failure,
`2` on usage errors.

`check` JSON: `ok`, `still_only`, `errors`, `quality` (readability / collision /
composition + `flags`), `cues`, `cue_errors`, `audio_ok`, `metrics`
(deterministic, canvas size, blank/frozen frames), `frame_paths`,
`contact_sheet_path`, `report_path`. Quality flags are review evidence — they
do not by themselves flip `ok`.

`render` JSON: `ok`, `output_mp4`, `poster_png`, `contact_sheet_png`,
`frames_rendered`, `duration_s`, `fps`, `has_audio`, `size_bytes`, `elapsed_s`.

## The `window.SCENE` contract (cheat-sheet)

- Canvas is **1920×1080**. Declare `SCENE.fps`, `SCENE.frames` (or
  `SCENE.duration`), `SCENE.width/height`, and `SCENE.canvas`.
- `SCENE.renderFrame(n)` is a **pure function of `n`**: no unseeded randomness,
  no wall-clock reads for drawing decisions (the renderer re-seeds per frame,
  so renders are deterministic and re-renders are byte-identical).
- `SCENE.init()` bakes textures once and registers subjects/blockers:
  `window.__ruvilSubjects.register(id, role, boxFn)` /
  `window.__ruvilBlockers.register(...)`, keeping the declarative
  `SCENE.subjects` / `SCENE.blockers` mirrors in sync. Draw the main subject
  inside `window.__ruvilSubjects.draw('hero', () => { ... })` so the checker
  can measure it.
- `SCENE.cues`: `[{ t, prompt, seconds }]` — timed sound beats, validated
  against the scene duration.
- `SCENE.audio(ctx, info)`: procedural audio scheduled on the
  `OfflineAudioContext` you are given (48 kHz stereo out). Keep peaks ≤ 0.7,
  seed every noise source, sync hits to your cues.
- Only five pinned library URLs may be loaded — every other network request is
  blocked by the renderer:
  `https://libs.ruvil/p5.min.js`, `rough.min.js`, `simplex-noise.js`,
  `ruvil-marks.js`, `anidoodle-draw.js` (served offline from the package,
  SHA256-verified by `doctor`).

## Kit reference

- **RuvilMarks** (`ruvil-marks.js`): `paper(ctx, o)` (bake the paper ground
  once in `init()`), `wash`, `stipple` (blue-noise dots; density = tone),
  `hatch` (spacing = tone), `rng(seed)` (mulberry32), `hash(a, b)` (boil
  outlines 1–3 px via `hash(boilIndex, strokeId)`).
- **AnidoodleDraw** (`anidoodle-draw.js`): `AnidoodleDraw.draw('<id>', ctx,
  frame, W, H)` plates one of 31 finished style scenes under your scene;
  `AnidoodleDraw.styles` lists the ids at runtime. Run `northnoir styles`.

## The agent skill

```sh
northnoir skill install
```

copies the bundled `northnoir-draw` skill into `.claude/skills/`,
`.agents/skills/`, `~/.config/opencode/skills/` and `~/.claude/skills/`, so
Claude Code, OpenCode, Codex, Gemini CLI and Cursor agents learn the contract,
the 4-step loop, the quality-flag fix table and the craft rules before they
draw. `northnoir skill` prints the bundled path.

## CLI vs Studio vs MCP

| | CLI (this package) | Web Studio | MCP server |
|---|---|---|---|
| Where it runs | Your machine | North Noir cloud | Your chat app |
| The model | Ruvil 2.1 (default); explicitly select Ruvil 7.3 | The model you pick in Studio | The model you pick |
| Cost | Free CLI with your own API keys; provider usage billed by providers | North Noir tokens (free plan: your own API key) | North Noir tokens |
| Best for | Saved cels + BYOK audio, or agent-authored canvas scenes | Timelines, voice/music, multi-section films at scale | Drawing from Claude/Cursor chats |

## Troubleshooting

- **`doctor` says Chromium will not launch** — run `northnoir doctor
  --install`. On Linux you may also need Playwright's system libraries:
  `python3 -m playwright install-deps chromium` (asks for sudo; the CLI never
  runs sudo itself).
- **`ffmpeg` missing** — install it with your OS package manager (above);
  `doctor` prints the exact hint for your platform.
- **The Node shim cannot start Python / deps missing** — the npm package is
  not published; `bin/northnoir.js` is a repo-only thin shim. Use
  `pipx install northnoir` (recommended), or install the pinned deps into the
  Python the shim finds and set `NORTHNOIR_PYTHON` to choose the interpreter.
- **A library SHA256 fails** — the vendored runtime is corrupted or hand-edited.
  Reinstall the package; maintainers refresh it with `tools/sync_runtime.py`
  (single source of truth: `cloudrun/ruvil-js-renderer/` in the repo).
- **Slow renders** — rendering is deterministic software rendering; heavy
  full-canvas grain costs time per frame. Check `avg_ms_per_frame` in the
  check report before rendering long scenes.

## Development

```sh
python3 tools/sync_runtime.py   # refresh northnoir_cli/runtime from cloudrun/
python3 -m pytest tests/ -q     # end-to-end suite (doctor/styles/init/check/render)
python3 northnoir_cli/cli.py doctor   # or: ./bin/northnoir doctor
```

Packaging choice: a **Python package** (the engine is Python + Playwright;
Chromium and ffmpeg are external tools `doctor` verifies). An unpublished
thin Node shim (`bin/northnoir.js`, `private: true` in `package.json`) is kept
for repo use only. A compiled single binary was rejected: it would have
to bundle a Python interpreter plus Playwright plus a browser download, which
is larger, more fragile across platforms, and harder to debug than `pipx`.

## Licenses

The CLI code is proprietary North Noir software (see `pyproject.toml`). The
bundled scene libraries keep their own licenses, shipped beside them in
`northnoir_cli/runtime/libs/`: `LICENSE.p5.txt` (p5.js), `LICENSE.roughjs.txt`
(rough.js), `LICENSE.simplex-noise.txt` (simplex-noise), `LICENSE.anidoodle.txt`
(AnidoodleDraw / RuvilMarks). Their pinned SHA256 digests are in
`northnoir_cli/runtime/libs/SHA256SUMS` and verified by `northnoir doctor`.
