# CLI reference

> runek init / add / list / info / validate / check-world / preview / skill: pull editable component source into your project and check your worlds.

The `runek` CLI copies component **source** from the registry into your project — you own and edit the files. Run it with `npx @runek/cli <command>`.

## init

```bash
npx @runek/cli init [options]
```

Writes `runek.config.json` and creates the install directory. Run it once per project.

## add

```bash
npx @runek/cli add <name...> [options]
```

Pulls one or more components. For each, it:

1. resolves **registry dependencies** recursively (e.g. `house` also pulls its walls, floor, roof, door, and window as source),
2. writes the source into your install directory verbatim (components import `@runek/core` from npm, so there's no import to rewrite),
3. installs the npm **dependencies**, including `@runek/core`, with your package manager (auto-detected from the lockfile).

```bash
npx @runek/cli add player terrain bookshelf
```

## list

```bash
npx @runek/cli list [options]
```

Prints the catalog, grouped by category. `--json` prints the registry index items instead (`name`, `title`, `type`, `category`, `description`, `files`).

## info

```bash
npx @runek/cli info <name> [options]
```

Shows one component without installing it: its description, the `add` command, the npm and registry dependencies it pulls, every prop with its type, default, and doc (code-only props such as callbacks are marked), the nested types it takes (like `Person`'s `PersonGarment`), and a minimal world node and JSX line to start from. `<name>` is the registry name (`bookshelf`) or the world-data type (`Bookshelf`), in any case; a near miss suggests the closest name. It reads only the registry, so it works before `init`.

```bash
npx @runek/cli info bench
#   Bench (bench, interiors)
#   ...
#   Props
#     length      number     = 1.6
#                 Length, in units.
#     back        boolean    = true
#                 Include a backrest.
#     color       color      = palette.wood
#   World node
#     {"type":"Bench","props":{"position":[0,0,0]}}
```

`--json` prints the same card as one object: `name`, `title`, `kind`, `category`, `description`, `doc`, `add`, `dependencies`, `registryDependencies`, `props` (`name`, `type`, `optional`, `default`, `palette`, `computed`, `doc`, `codeOnly`), `types`, and `example` (`node`, `jsx`).

## validate

```bash
npx @runek/cli validate <world.json> [options]
```

Checks a world file against the components' prop schema (served at `/r/props.json`): unknown world fields, node fields and component types, props a component doesn't have, and values of the wrong type or outside an enum, each with a "did you mean" when a name is close. Invalid JSON is reported with its line and column. Components that are not in the registry but have a source file in your install directory (your own `MyThing.tsx`) are accepted as is.

```bash
npx @runek/cli validate public/world.json
#   error   avatar Avatar is "first", "third" or "overhead".
#   warning nodes[1].props.colr Bookshelf has no prop "colr". Did you mean "color"?
#   warning nodes[2].type Unknown component "Benchh". Did you mean "Bench"?
```

Errors (the world can't load) exit non-zero; warnings (it renders, maybe not as meant) only with `--strict`. `--json` prints `{ valid, errors, warnings, issues: [{ path, pathString, severity, message }] }`, where `path` is the JSON path as an array (`["nodes", 1, "props", "colr"]`). It uses `validateWorld` from your installed `@runek/core` (0.14 or newer). Editors can check the same rules as you type through the world JSON Schema: see [Worlds as data](https://runek.nullorder.org/docs/worlds-as-data.md).

## check-world

```bash
npx @runek/cli check-world <world.json> [options]
```

Finds nodes that are **buried** (below the terrain) or **floating** (above the highest surface), and open water that stands above its shore. It reads the ground from your own component source (the `surfaces/*.ts` modules that ship with `terrain`, `dock`, `floor`, `slab`, `shore`, and `lake`) and the ground math from your installed `@runek/core`, so it checks exactly what your app renders. Only components marked `groundSitting` are checked, and anchored nodes pass by construction.

```bash
npx @runek/cli check-world public/world.json
#   buried Tent [70] at (-28, 0.42, 110) is 1.8 below the ground (2.22); set position[1] to 2.216, or anchor it
```

`--fix` writes each suggested Y into the file; `--strict` exits non-zero when anything is found, so an app can run it in CI. `--json` prints `{ surfaces, issues }` (plus `fixed` with `--fix`), each issue with its `kind`, ground-index `path`, `type`, `id`, `at`, `ground`, and suggested `fix`. Loading `.ts` surfaces needs Node 22.18 or newer. See [Placing things on uneven ground](https://runek.nullorder.org/docs/placing-things-on-uneven-ground.md).

## preview

```bash
npx @runek/cli preview <world.json> [--open]
```

Prints a [workshop](https://runek.nullorder.org/docs/the-workshop.md) link that carries the whole world in its URL, the same as the workshop's own share links, so you (or whoever you send it to) can see a world file rendered without running an app. `--open` opens it in your browser. Very large worlds make long links; past 8,000 characters it warns. With a registry served by a Runek docs site (a URL ending in `/r`), the link points at that site's workshop.

## skill

```bash
npx @runek/cli skill [--out <path>] [--overwrite]
```

Installs the Runek agent skill (served at `/r/agents/SKILL.md`): how to add components, write and check world files, and place things, for coding agents working in your project. It writes `.claude/skills/runek/SKILL.md` by default; `--out` puts it anywhere else, such as another agent's rules folder. An existing file is kept unless you pass `--overwrite`. See [Runek for agents](https://runek.nullorder.org/docs/for-agents.md).

## Options

| Option | Commands | Description |
|---|---|---|
| `--registry <url\|path>` | all | Registry base. Defaults to the configured one (`https://runek.nullorder.org/r`). A local path works for development. |
| `--core <version\|latest>` | add, list, info, validate, skill | Read the registry for this `@runek/core` instead of the project's (see [Versions](#versions)). `latest` is the newest release. |
| `--dir <path>` | init, add, validate, check-world | Install directory (default `src/runek`). |
| `--overwrite` | add, skill | Replace files that already exist (otherwise they're skipped). |
| `--no-install` | add | Print the dependency install command instead of running it. |
| `--force` | init | Overwrite an existing `runek.config.json`. |
| `--strict` | validate, check-world | Exit non-zero on any issue (validate: warnings too). |
| `--fix` | check-world | Write the suggested Y values into the world file. |
| `--tolerance <units>` | check-world | Allowed gap before a node is flagged (default `0.08`). |
| `--json` | list, info, validate, check-world | Machine-readable JSON on stdout, for scripts and agents. |
| `--out <path>` | skill | Where to write the skill (default `.claude/skills/runek/SKILL.md`). |
| `--open` | preview | Open the link in your browser. |
| `-v, --version` | — | Print the CLI version (also `runek version`): a pixel banner in a terminal, the bare version when piped. |
| `-h, --help` | — | Show help. |

## runek.config.json

```json
{
  "$schema": "https://runek.nullorder.org/registry/config-schema.json",
  "registry": "https://runek.nullorder.org/r",
  "dir": "src/runek"
}
```

- **registry** — where components are fetched from: the base URL or path, without a version (the CLI adds it).
- **dir** — where component source is written. The runtime comes from the `@runek/core` npm package, not copied here.

## Registry index

The default registry is served at [`https://runek.nullorder.org/r`](https://runek.nullorder.org/r). Its index lists every component (name, type, description); each entry resolves to a self-contained manifest under `components/<name>.json`. Point `--registry` at it (the default) or at a local copy for development.

[https://runek.nullorder.org/r/registry.json](https://runek.nullorder.org/r/registry.json): The catalog the CLI reads first: every component name, type, and description.

## Versions

Component source is written against a particular `@runek/core`, so the registry keeps one snapshot per release: `/r/0.15.0/registry.json`, `/r/0.15.0/components/bookshelf.json`, and so on. [`/r/versions.json`](https://runek.nullorder.org/r/versions.json) lists them, and `/r` itself serves the latest release.

Every command that reads the registry picks the snapshot that matches your project's `@runek/core`, and says which on stderr:

```text
$ npx @runek/cli add bookshelf
registry 0.15.0 (matches installed @runek/core 0.15.0)
```

- It reads the installed `node_modules/@runek/core`, else the version range in your `package.json`. With neither (a new project), it uses the latest release, and `add` installs the matching core.
- With no exact snapshot it takes the newest older one in the same release line (`0.15.3` → `0.15.2`). Before 1.0 a minor can break, so it never crosses into another minor.
- A component that only exists in a newer release fails with the fix: `--core latest` pulls from the newest release and upgrades `@runek/core` to match. Components you added earlier came from the old snapshot, so re-add them (`--overwrite`) if the new core breaks them.
- Releases before 0.14.0 serve only components, not the prop schema or the agent skill, so `info`, `validate` and `skill` ask you to upgrade there.
- A registry without `versions.json` (a local checkout, your own fork) is used as-is, so `--registry ./registry` behaves as before.
