# NullHub — full documentation > A single Zig binary with an embedded dashboard that installs, supervises, monitors and updates the Null ecosystem. Everything local, under ~/.nullhub. --- # Start: Install > Get the NullHub binary — Docker, prebuilt release, or build from source. NullHub is one self-contained binary: the Zig backend and the Svelte dashboard are compiled together, so there is nothing else to deploy. Pick whichever of the three routes below fits your machine. > [!NOTE] > Pre-1.0: config and CLI may change between releases. Releases are calendar-versioned (v2026.x.y). ## Docker The image serves the dashboard on port 19800 and keeps all state in a volume mounted at `/nullhub-data`: ```bash docker run --rm -p 19800:19800 -v nullhub-data:/nullhub-data ghcr.io/nullclaw/nullhub:latest ``` The default image runs as an unprivileged user (uid 65534). If you need the container to run as root — the Dockerfile calls this "autonomous mode", an explicit opt-in — build the root target: ```bash docker build --target release-root -t nullhub:root . ``` ## Prebuilt binary Every release attaches binaries for seven targets: | Platform | Asset | | --- | --- | | macOS, Apple silicon | `nullhub-macos-aarch64.bin` | | macOS, Intel | `nullhub-macos-x86_64.bin` | | Linux, x86_64 | `nullhub-linux-x86_64.bin` | | Linux, aarch64 | `nullhub-linux-aarch64.bin` | | Linux, riscv64 | `nullhub-linux-riscv64.bin` | | Windows, x86_64 | `nullhub-windows-x86_64.exe` (also `.zip`) | | Windows, aarch64 | `nullhub-windows-aarch64.exe` (also `.zip`) | Download from [GitHub releases](https://github.com/nullclaw/nullhub/releases), make it executable, run it: ```bash curl -fL https://github.com/nullclaw/nullhub/releases/latest/download/nullhub-macos-aarch64.bin -o nullhub chmod +x nullhub ./nullhub ``` There is no Homebrew formula for NullHub yet. (If you only want the agent, [NullClaw](https://claw.nullmenu.ai/) does have one.) ## Build from source Requires Zig 0.16.0 exactly, plus `npm` to build the embedded Svelte UI: ```bash zig build ./zig-out/bin/nullhub ``` The resulting binary includes the built web UI — it does not depend on a runtime `ui/build` directory. ## Prerequisites At runtime NullHub needs two ordinary tools: - `curl` — fetches releases and component binaries - `tar` — extracts UI module bundles If either is missing, NullHub tries to install it automatically through whatever package manager it finds: `apt`, `dnf`, `yum`, `pacman`, `zypper`, `apk`, `brew`, `winget`, or `choco`. ## Run as an OS service Once you are happy with it, register NullHub as a systemd or launchd service so it survives reboots: ```bash nullhub service install nullhub service status ``` `nullhub service uninstall` removes it again. ## Where things live All state — config, instances, downloaded binaries, logs, cached manifests — sits under `~/.nullhub/`. Deleting that directory is a full reset. Next: [Quickstart](/docs/start/quickstart/). --- # Start: Quickstart > Start the server, open the dashboard, install your first component. From zero to a supervised agent in a few commands. This assumes you already have the binary — if not, see [Install](/docs/start/install/). ## 1. Start the server ```bash nullhub ``` This starts the server and opens your browser at the dashboard. NullHub tries a chain of local addresses, in order: 1. `http://nullhub.local:19800` — published via `dns-sd`/Bonjour or `avahi-publish` when available 2. `http://nullhub.localhost:19800` 3. `http://127.0.0.1:19800` If you want the server without the browser popping open, or on a different address: ```bash nullhub serve --host 127.0.0.1 --port 19800 --no-open ``` `serve` also accepts repeated `--allowed-origin ORIGIN` flags to authorize extra CORS origins (a Tailscale domain, for instance). The same list can come from the `NULLHUB_ALLOWED_ORIGINS` environment variable, comma-separated. ## 2. Install a component The kitchen is open; time to hire the chef. The terminal wizard walks you through provider, ports, and keys — the web UI's **Install Component** flow is the same wizard with the same steps: ```bash nullhub install nullclaw ``` NullHub can install and manage four components: [NullClaw](https://claw.nullmenu.ai/), [NullBoiler](https://boiler.nullmenu.ai/), [NullTickets](https://tickets.nullmenu.ai/), and [NullWatch](https://watch.nullmenu.ai/). Details in [Installing components](/docs/guides/install-components/). ## 3. Run it and look at it ```bash # start everything that's installed nullhub start-all # table of all instances, or one instance in detail nullhub status nullhub status nullclaw/main # follow logs live nullhub logs nullclaw/main -f ``` Instances are addressed as `{component}/{instance-name}` everywhere in the CLI and the API. ## 4. Check the dashboard Open the dashboard and you get status cards with periodic health checks, per-instance config editors, live log streaming over SSE, and the component UIs (workflow editor, store browser, Flight Recorder). Tour in [Web dashboard](/docs/guides/web-dashboard/). ## What just happened - The component's binary was downloaded (or built) according to its published `nullhub-manifest.json` — see [Manifests](/docs/reference/manifests/). - Its config, binary, and logs landed under `~/.nullhub/`. - The supervisor now restarts it on crashes, with backoff, and polls its health endpoint. > [!TIP] > Everything the dashboard does goes through the same HTTP API. `nullhub api GET /api/instances/nullclaw/main/status --pretty` shows you the raw JSON — useful for scripting before you reach for the [CLI reference](/docs/reference/cli/). --- # Guides: Installing components > Add NullClaw, NullBoiler, NullTickets and NullWatch through the manifest-driven wizard. NullHub is a generic install engine: each component publishes a `nullhub-manifest.json` that describes how to fetch it, configure it, launch it, and check its health. NullHub interprets the manifest and runs the wizard — it has no component-specific installers baked in. ## What it can install | Component | What you get | | --- | --- | | [NullClaw](https://claw.nullmenu.ai/) | The agent runtime, plus an instance-scoped admin API (status, config, models, cron, channels, skills) | | [NullBoiler](https://boiler.nullmenu.ai/) | Workflow orchestration, with a workflow editor and run monitoring in the dashboard | | [NullTickets](https://tickets.nullmenu.ai/) | Durable task state, with a key-value store browser proxied through NullHub | | [NullWatch](https://watch.nullmenu.ai/) | Tracing and evals, rendered in the Flight Recorder page | That list is the whole list. NullPantry, NullDesk, NullCap and nllclw are not hub-installable today — install those from their own repos. ## Terminal wizard ```bash nullhub install nullclaw ``` The wizard is defined by the component's manifest: select steps, secrets (API keys), text and number fields, toggles — some conditional on earlier answers, some marked advanced. Answer the prompts and NullHub downloads the platform binary (or builds from source when the manifest defines that), writes config, and registers the instance. The web UI's **Install Component** flow runs the same wizard steps in the browser. Pick whichever you prefer; the result is identical. ## Example: a local NullWatch From the README's own walkthrough: 1. Start NullHub (`nullhub serve --no-open` if you don't want a browser). 2. In the web UI, open **Install Component**, select **NullWatch**, keep or set the API port to `7710`, and finish the wizard. 3. The installer starts the NullWatch instance, and NullHub's `/api/nullwatch/*` proxy discovers it automatically — the Flight Recorder page starts rendering run summaries, spans, evals, token usage and cost without further wiring. ## Cross-component linking Manifests declare `depends_on` and `connects_to` relationships, and NullHub acts on them. The concrete case today: installing NullTickets and NullBoiler locally auto-connects them — NullHub generates the native tracker config and lets you inspect queue and orchestrator status from one UI. ## Lifecycle after install ```bash nullhub start nullclaw/main # or stop, restart nullhub start-all # everything at once nullhub status # who's up, who's not nullhub logs nullclaw/main -f # follow logs nullhub uninstall nullclaw/main # remove an instance ``` Uninstalling removes the instance; the component's binary and your other instances are untouched. You can run several instances of one component side by side — see [Multi-instance](/docs/operate/multi-instance/). > [!NOTE] > Pre-1.0: wizard steps and manifest fields may change between releases. The manifest schema as currently parsed is documented in [Manifests](/docs/reference/manifests/). --- # Guides: Web dashboard > The embedded UI — status cards, config editors, logs, component pages, Mission Control. The dashboard is a SvelteKit app compiled into the NullHub binary with `@embedFile` — no separate frontend to deploy, nothing fetched from a CDN. It serves at `http://nullhub.localhost:19800` (or `nullhub.local:19800` when Bonjour/avahi is available, `127.0.0.1:19800` as the last resort). ## The core pages **Status cards.** Every instance gets a card fed by periodic HTTP health checks (default interval 15 s, set per component in its manifest). Start, stop, and restart from the card. **Install Component.** The same manifest-driven wizard as `nullhub install`, in the browser — component-aware steps, secrets, conditional fields. **Config editors.** Structured editors for NullClaw, NullBoiler, NullTickets and NullWatch, plus direct raw JSON editing when the structured view doesn't cover what you need. The CLI equivalent is `nullhub config / --edit`. **Logs.** Live per-instance log streaming over SSE — the browser twin of `nullhub logs / -f`. ## Component UIs These pages render other components' data through NullHub's local reverse proxies. Nothing is sent to hosted services. | Page | Backed by | What it shows | | --- | --- | --- | | NullBoiler | `/api/nullboiler/*` proxy | Workflow editor, poll-based run monitoring, checkpoint forking, encoded workflow/run links | | NullTickets Store | `/api/nulltickets/store/*` proxy | Key-value store browser | | NullWatch Flight Recorder | `/api/nullwatch/*` proxy | Run summaries, span timelines, eval results, token usage, cost, error context | | Mission Control | `/api/mission-control/*` | Deterministic local mission replay with live telemetry | The proxies target managed instances automatically; environment variables override them for external instances: | Variable | Purpose | | --- | --- | | `NULLBOILER_URL`, `NULLBOILER_TOKEN` | NullBoiler REST API target (e.g. `http://localhost:8080`) and optional bearer token | | `NULLTICKETS_URL`, `NULLTICKETS_TOKEN` | NullTickets store target and optional token | | `NULLWATCH_URL`, `NULLWATCH_TOKEN` | Override the managed NullWatch instance target and token | | `NULLHUB_ALLOWED_ORIGINS` | Comma-separated extra CORS origins for the server | ## Mission Control A control room for watching an agent mission fail and recover — reset, launch, failure hold, checkpoint fork, recovered replay — driven by a versioned, embedded replay fixture. It needs no hosted infrastructure and no model secrets, and it hydrates with real NullBoiler workflow evidence and NullWatch trace detail when matching local instances are running. Timeline events carry trace chips that deep-link into the Flight Recorder (`/nullwatch?run_id=...`). Export the current replay as a portable JSON artifact: ```bash curl -fsS http://127.0.0.1:19800/api/mission-control/replay \ -o mission-control-replay.json ``` The **Save Replay** button does the same and additionally stores a durable copy under `~/.nullhub/mission-control/replays/`. ## Access from another machine By default the server answers local origins only. To reach the dashboard across your tailnet or LAN, pass extra origins explicitly: ```bash nullhub serve --host 0.0.0.0 --port 19800 --allowed-origin https://hub.your-tailnet.ts.net ``` > [!WARNING] > NullHub supervises processes and edits their config. If you bind it beyond localhost, put it behind something you trust — bearer-token auth exists (`src/auth.zig`) but is optional. --- # Operate: Updates and rollback > Check for new component versions, update with config migration, roll back on failure. NullHub updates the components it manages: it checks upstream for new releases, downloads them, migrates config, and rolls back when an update fails. You never hand-edit a component's install to move it forward. ## Check what's stale ```bash nullhub check-updates ``` This compares each installed instance against the component's latest release. The dashboard shows the same information on the status cards. ## Update ```bash # one instance nullhub update nullclaw/main # everything at once nullhub update-all ``` An update is three steps, in order: 1. **Download** — the new version's platform binary is fetched into `~/.nullhub/`. 2. **Migrate** — the instance's config is carried over to the new version. 3. **Verify or roll back** — if the update fails, NullHub restores the previous version rather than leaving the instance broken. The web UI exposes the same flow as one-click updates per instance. ## Version numbers The whole family uses calendar versioning — `v2026.5.29` is a date, not a maturity signal. Two consequences worth internalizing: - There is no semver contract. A new release may change config shape or CLI flags; the migration step exists precisely because of this. - "Latest" moves often. `check-updates` is cheap — run it routinely, or script it via `nullhub api`. > [!WARNING] > Pre-1.0: config and CLI may change between releases. Read the component's release notes before `update-all` on a setup you care about. ## Updating NullHub itself `nullhub update` handles managed components, not the hub binary. To update NullHub, replace the binary the same way you installed it — pull the newer Docker tag, download the newer release asset, or rebuild from source. State under `~/.nullhub/` is separate from the binary and survives the swap. If you registered NullHub as an OS service, restart it after the swap: ```bash nullhub service status ``` ## When something goes wrong - `nullhub status /` shows single-instance detail, including health. - `nullhub logs / -f` follows the instance's logs live. - The supervisor restarts crashed instances with backoff, so a bad update that starts-then-dies is visible as a crash loop in the logs rather than a silent absence. A failed update that triggered rollback leaves the previous version running. Fix the cause (often a config value the new version rejects — `nullhub config / --edit`), then update again. --- # Operate: Multi-instance > Run several instances of the same component side by side, addressed as component/name. NullHub can run multiple instances of the same component at once — two NullClaw agents with different providers, a NullBoiler for real work next to one for experiments. Every instance is a separate process with its own config, logs, and lifecycle. ## Addressing Everything in the CLI and the API uses `{component}/{instance-name}`: ```bash nullhub start nullclaw/main nullhub status nullclaw/experiments nullhub logs nullboiler/staging -f nullhub config nullclaw/experiments --edit nullhub uninstall nullclaw/experiments ``` You pick the instance name during the install wizard. Installing the same component again creates a second instance rather than overwriting the first. ## Ports Two instances of one component cannot share a port. Each manifest declares its port specs — name, config key, default, protocol — and the wizard prompts for them, so give the second instance different values there. Health checks read the port from the instance's own config (`port_from_config` in the manifest), so monitoring follows each instance to wherever you put it. ## Bulk operations ```bash nullhub start-all # start every installed instance nullhub stop-all # stop them all nullhub status # one table, all instances nullhub update-all # update everything (see Updates and rollback) ``` `start-all` and `stop-all` operate across components and instances alike — one command brings the whole station up or down. ## Per-instance admin for NullClaw Managed NullClaw installs get an instance-scoped admin API through NullHub — status, config, models, cron, channels, and skills routes. Because the routes are per-instance, two agents stay fully separated: ```bash nullhub api GET /api/instances/nullclaw/main/status --pretty nullhub api GET /api/instances/nullclaw/main/cron --pretty ``` ## Where instance state lives Each instance's config, logs, and binaries live under `~/.nullhub/`. The supervisor tracks instances independently: one crash-looping instance does not affect its siblings, and health checks report per instance on the dashboard cards. > [!TIP] > Multi-instance is the cheap way to test a component update: install a second instance, point it at scratch data, update that one first via `nullhub update /`, and only then touch `main`. > [!NOTE] > Pre-1.0: config and CLI may change between releases. --- # Reference: CLI > Every nullhub command — server, install, lifecycle, updates, config, raw API, OS service. One binary, two modes. Run `nullhub` with no arguments and it becomes a server (HTTP plus supervisor threads); run it with a command and it acts as a CLI — direct calls, stdout, exit. Instances are addressed as `{component}/{instance-name}` everywhere, abbreviated `/` below. > [!NOTE] > Pre-1.0: config and CLI may change between releases. ## Server | Command | What it does | | --- | --- | | `nullhub` | Start server and open the browser at the dashboard | | `nullhub serve [--host H] [--port N] [--allowed-origin ORIGIN] ...` | Start the server; repeat `--allowed-origin` to authorize extra CORS origins | | `nullhub serve --no-open` | Start the server without opening the browser | | `nullhub version` (also `-v`, `--version`) | Print version | The dashboard serves at `http://nullhub.localhost:19800` by default, with `nullhub.local` published via Bonjour/avahi when available and `127.0.0.1` as fallback. Allowed origins can also come from `NULLHUB_ALLOWED_ORIGINS` as a comma-separated list. ## Install and remove | Command | What it does | | --- | --- | | `nullhub install ` | Terminal install wizard (manifest-driven; same steps as the web UI) | | `nullhub uninstall /` | Remove an instance | Valid components today: `nullclaw`, `nullboiler`, `nulltickets`, `nullwatch`. ## Lifecycle | Command | What it does | | --- | --- | | `nullhub start /` | Start an instance | | `nullhub stop /` | Stop an instance | | `nullhub restart /` | Restart an instance | | `nullhub start-all` | Start every installed instance | | `nullhub stop-all` | Stop every installed instance | ## Inspect | Command | What it does | | --- | --- | | `nullhub status` | Table of all instances | | `nullhub status /` | Single-instance detail | | `nullhub logs /` | Tail logs | | `nullhub logs / -f` | Follow logs live | ## Updates | Command | What it does | | --- | --- | | `nullhub check-updates` | Check all instances for new versions | | `nullhub update /` | Update one instance: download, migrate config, rollback on failure | | `nullhub update-all` | Update everything | See [Updates and rollback](/docs/operate/updates-and-rollback/). ## Config | Command | What it does | | --- | --- | | `nullhub config /` | View instance config | | `nullhub config / --edit` | Edit instance config | ## Raw API access `nullhub api` sends a request to the running server and prints the JSON: ```bash nullhub api GET /api/instances/nullclaw/main/status --pretty nullhub api GET /api/instances/nullclaw/main/cron --pretty ``` Everything the dashboard does goes through this same API, so anything you can click, you can script. ## OS service | Command | What it does | | --- | --- | | `nullhub service install` | Register and start NullHub as a systemd (Linux) or launchd (macOS) service | | `nullhub service uninstall` | Remove the OS service | | `nullhub service status` | Show OS service status | ## Environment variables | Variable | Purpose | | --- | --- | | `NULLHUB_ALLOWED_ORIGINS` | Comma-separated extra CORS origins, merged with `--allowed-origin` flags | | `NULLBOILER_URL` | Target for the `/api/nullboiler/*` reverse proxy (e.g. `http://localhost:8080`) | | `NULLBOILER_TOKEN` | Optional bearer token for the NullBoiler proxy | | `NULLTICKETS_URL` | Target for the `/api/nulltickets/store/*` proxy | | `NULLTICKETS_TOKEN` | Optional bearer token for the NullTickets proxy | | `NULLWATCH_URL` | Override the `/api/nullwatch/*` proxy target (external NullWatch instead of the managed one) | | `NULLWATCH_TOKEN` | Override the managed NullWatch instance token | ## Files All state lives under `~/.nullhub/`: config, instances, binaries, logs, cached manifests, and Mission Control replay artifacts under `~/.nullhub/mission-control/replays/`. --- # Reference: Manifests > The nullhub-manifest.json schema — how components describe their install, launch, health and wizard. NullHub has no component-specific installers. Each component publishes a `nullhub-manifest.json` that declares how to fetch it, build it, configure it, launch it, and check its health — NullHub is a generic engine that interprets the manifest. This page documents the schema as parsed by `src/core/manifest.zig` at v2026.5.29. Unknown fields are ignored by the parser. > [!WARNING] > Pre-1.0: the manifest schema may change between releases. The current `schema_version` is `1`. ## Top-level fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `schema_version` | number | yes | Schema version; currently `1` | | `name` | string | yes | Component id, e.g. `nullclaw` | | `display_name` | string | yes | Human-readable name shown in the UI | | `description` | string | yes | One-line description | | `icon` | string | yes | Icon identifier | | `repo` | string | yes | GitHub repo, `owner/name` | | `platforms` | map | yes | Platform target → release asset (see below) | | `build_from_source` | object | no | Fallback build recipe (see below) | | `launch` | object | yes | How to start an instance | | `health` | object | yes | Health-check spec | | `ports` | array | yes | Declared ports | | `wizard` | object | yes | Install-wizard steps | | `depends_on` | array of strings | yes | Component ids this one needs | | `connects_to` | array | yes | Optional integrations (see below) | ## `platforms` Keys are target triples like `aarch64-macos`; values name the release asset and the binary inside it: | Field | Description | | --- | --- | | `asset` | Release asset name, e.g. `nullclaw-macos-aarch64` | | `binary` | Binary name inside the asset | ## `build_from_source` Used when no prebuilt asset matches the platform: | Field | Description | | --- | --- | | `zig_version` | Required Zig version (the family pins `0.16.0`) | | `command` | Build command | | `output` | Path of the produced binary | ## `launch` | Field | Default | Description | | --- | --- | --- | | `command` | — | Subcommand or executable to launch | | `args` | `[]` | Argument list | | `env` | `null` | Environment variables as a JSON object | ## `health` | Field | Default | Description | | --- | --- | --- | | `endpoint` | — | HTTP path to poll, e.g. `/health` | | `port_from_config` | — | Config key holding the port, e.g. `gateway.port` | | `interval_ms` | `15000` | Poll interval | ## `ports` Each entry: `name`, `config_key` (where the value lives in the instance config), `default` (number), `protocol` (e.g. `http`). The wizard prompts for these, which is what makes [multi-instance](/docs/operate/multi-instance/) port separation work. ## `wizard.steps` Each step: | Field | Default | Description | | --- | --- | --- | | `id` | — | Step identifier | | `title` | — | Prompt title | | `description` | `""` | Longer prompt text | | `type` | — | One of `select`, `multi_select`, `secret`, `text`, `number`, `toggle`, `dynamic_select` | | `required` | `true` | Whether the step must be answered | | `options` | `[]` | For selects: `value`, `label`, `description`, `recommended` | | `default_value` | `""` | Pre-filled value | | `dynamic_source` | `null` | For `dynamic_select`: a `command` producing options, plus `depends_on` step ids | | `condition` | `null` | Show the step only when another step's answer matches | | `advanced` | `false` | Tucked behind the advanced toggle | | `group` | `null` | Visual grouping | Conditions reference an earlier step and match with one of `equals`, `not_equals`, `contains`, or `not_in` (comma-separated exclusion list). ## `connects_to` Declares optional integrations between components: `component` (target id), `role`, `description`, and `auto_config` (a JSON object NullHub applies to wire the two together). This is what drives the automatic NullTickets → NullBoiler linking during install. ## Minimal example The parser's own test fixture: ```json { "schema_version": 1, "name": "nullclaw", "display_name": "NullClaw", "description": "AI agent", "icon": "agent", "repo": "nullclaw/nullclaw", "platforms": { "aarch64-macos": { "asset": "nullclaw-macos-aarch64", "binary": "nullclaw" } }, "launch": { "command": "gateway", "args": [] }, "health": { "endpoint": "/health", "port_from_config": "gateway.port", "interval_ms": 15000 }, "ports": [{ "name": "gateway", "config_key": "gateway.port", "default": 3000, "protocol": "http" }], "wizard": { "steps": [] }, "depends_on": [], "connects_to": [] } ``` ## Who publishes manifests NullClaw, NullBoiler, NullTickets and NullWatch all ship manifest support — that set defines what `nullhub install` accepts. Cached manifests live under `~/.nullhub/`.