CLI commands
Every user-facing noemata command and its flags. The CLI follows a noemata <command> [options] shape; run noemata <command> --help for the authoritative, up-to-date flag list of any command.
Global usage
noemata <command> [options]- No command runs
up. Barenoematais the same asnoemata up. noemata --help(or-h) — list the available commands.noemata --version(or-v) — print the installed version.--project <path>(or-p) — almost every command takes this to target a specific project directory. It defaults to the nearest project (the closestnoemata.jsonin the current directory’s ancestry).
Running the stack
up
noemata up [--only <services> | --except <services>] [--detach] [--dev | --prod] [--open | --no-open] [--init] [--accept-ours] [<configuration flags>]Regenerate the materialized service configs and start every managed service — ClickHouse, the OpenTelemetry collector, and the Noemata UI. External services are skipped. When the project isn’t configured yet, up runs the setup wizard first (see Configuration flags).
--only <services>— start only the listed services instead of all managed ones. Comma-separated; one or more ofdb,collector,server(e.g.--only db,collector). Other managed services are left untouched.--except <services>— the inverse: start every managed service except the listed ones (e.g.--except dbto skip ClickHouse). Same names as--only; mutually exclusive with it.--detach(-d) — spawn a detached supervisor per service instead of running attached to your terminal.--dev/--prod— force development mode (serve the UI from source with hot-reload; monorepo-only) or production mode (the prebuilt server bundle). Mutually exclusive; auto-detected when neither is given.--open/--no-open— open the UI in your browser once it’s ready. On by default in a terminal, off otherwise (agents/CI).--config(-c) — re-run the configuration wizard before starting.--init— create a new project here instead of adopting the nearest existing one; fails if one already exists.--accept-ours— non-interactively resolve any initial-sync conflicts in favor of the local workspace (keep what’s on disk).
down
noemata down [--project <path>]Stop every running service for the project, including supervisors started under a previous config. To hunt stray processes across all worktrees, use kill.
status
noemata status [--project <path>]Show whether each managed service is currently running.
kill
noemata kill [--all] [--project <path>]Find and stop stray Noemata processes across every project and worktree by scanning the process table — no PID files needed, so it catches daemons left behind by a deleted worktree or a backgrounded up.
--all— also stop top-levelnoemata upsessions, not just detached supervisors.--project <path>— only stop processes belonging to this project (defaults to every project).
Project lifecycle
init
noemata init [--project <path>] [<configuration flags>]Create a new project — failing if one already exists at the target directory — and run the interactive setup wizard. Equivalent to config --init. Here --project pre-fills the project-directory prompt (defaults to the current directory).
config
noemata config [--init] [--project <path>] [<configuration flags>]Run the interactive setup wizard against an existing project: it prompts for the database, collector, integrations (including whether their dashboards are Noemata-managed or user-owned), whether integrations may edit their own config outside the project, the agent panel, and HTTPS, then writes the project config and the managed service configs.
--init— create a new project instead of adopting the nearest existing one.
reconcile
noemata reconcile [--project <path>] [--no-validate] [--validate-online] [<configuration flags>]Apply the current noemata.json to disk without starting anything: regenerate the managed service configs and reconcile the enabled integrations’ frame packs, skills, and edits to user-owned config. It reads the config and never writes it — config is where settings change, and it finishes by running this same reconcile. up runs it too, before starting the services.
Reach for it after hand-editing noemata.json, or to pull the current CLI version’s packs into a project without a restart.
ls
noemata lsList every project registered in the nearest noemata.projects.json, plus the project containing the current directory, with each one’s configured state and any running services.
rm
noemata rm [--project <path>] [--force]Unlink the project from noemata.projects.json and, when confirmed, delete the files Noemata owns in the project directory (config, state, .env, and on-disk workspace content). Refuses to run while services are alive unless --force is set.
--force(-f) — stop running services and skip the confirm prompts, taking their defaults (unlink and delete Noemata’s files).
Authoring
run
noemata run <frame-id|path> [--page <json|file|->] [--param <k=v>]... [--url <k=v>]... [--window <duration>] [--timeout <ms>] [--out <path>] [--json] [--project <path>]Render one frame or page against the live backend and report its runtime errors and query performance — durations, rows and bytes scanned, server time, peak memory and cache outcome, grouped under the block that issued each query. Where validate --online sweeps the whole workspace for errors, run is the tool for working on a single dashboard.
The target is a route id (services/checkout) or a workspace path (@frames/services/checkout.frame.json, or a path relative to your shell). Static validation runs first: a statically invalid frame is reported and not rendered, because rendering one produces downstream errors that hide the real cause. Each of the view’s tables is then resolved against the backend, so a view whose columns don’t resolve is reported even though the frame would still render over it.
A run fails on any of: a static error, a runtime error, a query the backend rejected, a view table that doesn’t resolve, or a render that never settled. Rejected queries count even when the frame rendered cleanly over them — a block is free to catch a failed query and degrade, and that silent failure is exactly what this command exists to surface.
--sql <sql|->— run semantic SQL against the view: its tables and columns by name (FROM traces, view scalars as columns), compiled through the view exactly as an authored query is, so the view’s WHERE, scalars and the page’s timerange all apply. The result renders as a table and the first page is printed.--expr <json|->— the same, for a table-valued@expr/*(@expr/query,@expr/timeseries_query, …) given as inline JSON.--page <json|file|->— render this page against the target’s view instead of the page it declares, without writing anything to the workspace. A value starting with{is inline JSON,-reads stdin, anything else is a file path. Accepts either a block ({"@block/stat": …}) or a page definition ({page, templates}). The frame’s view, params, route id and relative@block/usereferences are all unchanged — only the page differs, so a view-only frame ("page": null) can be given one for the run.--param <column=value>— a route param, e.g.ServiceName=checkout. Required params you leave unset are sampled from live data. Repeatable.--url <key=value>— URL state seeded before the render, for pinning state the page reads from the URL. Repeatable.--window <duration>— render at this window (e.g.1h,15m). Without it the route’s ownsettings.default_timerangeapplies, which is the window a user would actually see. The report says which was used.--timeout <ms>— how long to wait for the render to settle. Defaults to30000.--out <path>— where to write the JSON report, resolved against your working directory. Defaults to<project>/.local/runs/<route>-<timestamp>.json. The path is reported relative to your working directory when the file is under it, and absolute otherwise, so it can be opened as printed.--json— write the JSON report to stdout as well, and suppress everything else.
--sql, --expr and --page are mutually exclusive — each replaces the page.
ClickHouse’s query caches are disabled for every noemata run (use_query_condition_cache, which defaults to on, and use_query_cache), because the command exists to measure. Without that, a second run of the same command reads a fraction of the rows and looks faster — which is exactly the “did my change help?” comparison you would be making. Cold numbers are also the representative ones: the timerange compiles to millisecond-precision literals, so a real page view is never a cache hit either. noemata validate --online is unaffected — it reports errors, not timings.
noemata run @opentelemetry/views/traces --sql 'SELECT ServiceName, count() AS n FROM traces GROUP BY ServiceName ORDER BY n DESC'Every run writes a JSON report (errors, per-query stats, the result preview, and the full telemetry tree) and prints its path. The per-query figures come from telemetry, so a project with telemetry.enabled: false gets a warning saying those checks did not run, rather than an all-zero report. Every run also mints a run id that is stamped on every span and log record it emits as noemata.run.id, so once the telemetry is ingested the whole run can be retrieved from the backend:
SELECT * FROM otel_traces WHERE SpanAttributes['noemata.run.id'] = '<run id>'Blocks appear in the report under their declared id when they have one, and under their positional path otherwise — so naming the blocks you care about makes the report readable. Queries served by the in-process fast path are counted in the summary but left out of the tree, where they would bury the ones that actually cost something; the JSON report keeps every query.
Exits 0 when the frame settles with no runtime errors, 1 otherwise (static failure, runtime errors, a render that never settled, or an unreachable backend).
validate
noemata validate [--online] [--files <path|glob>]... [--case <json>]... [--window <duration>] [--project <path>]Validate every frame in the workspace and report any issues.
--online— render each frame against the live backend and collect runtime errors, not just static ones.--files <path|glob>— validate only the frames a change to these files invalidates in the dependency graph: the files themselves plus every frame that transitively references them (editing a shared view frame or a templates file revalidates every frame that uses it, along the route chain too). Each value is a concrete file path or a glob matched against workspace paths (@frames/…) —{and}match literally, so a glob can span a parameterized frame path ('@frames/**/{ServiceName}/**'). Offline by default; add--onlineto render. Repeatable. Without it, the whole workspace is validated.--case <json>— a specific state combination to validate, e.g.'{"frame":"services/checkout","params":{"ServiceName":"checkout"}}'. Repeatable.--window <duration>— render at this window (e.g.1h,15m). Without it each frame renders at thesettings.default_timerangeits route resolves — the window a user would see it at.
Per-edit validation hook
When the Claude Code integration is installed, noemata up writes a PostToolUse hook that runs the tree’s noemata CLI with validate --hook after every edit — re-validating just the frames the edit invalidates (the same dependency-graph resolution as --files) and feeding any errors back to the agent so it fixes them before finishing. It’s written to the repo/workspace root’s .claude/settings.json — the git repo root, else the noemata.projects.json directory, else the project directory — since that’s where Claude Code resolves settings from, so the hook is loaded even when the project is a subdir; one root hook serves every project in the repo (the mode is resolved per edit from the edited file’s project). Because that hook is shared, it stays installed while any project in the repo wants it and is removed only once none do. The hook is merged alongside your own hooks (never replacing them) and left untouched if the file isn’t valid JSON. When that root is outside the project directory, --no-external-edits skips the write. The hook invokes the CLI that belongs to the tree it validates, not whichever one is on PATH — a PATH install is a different build whose block schema drifts from the tree’s. When the root owns the running CLI — a source checkout that tracks its entrypoint, or the root’s own node_modules/noemata — the command addresses it through $CLAUDE_PROJECT_DIR: portable in a committed settings.json, resolved per checkout, and pinned to the version the project declares. A root with no CLI of its own uses the portable noemata on PATH, falling back to the running CLI’s own path when nothing is installed there. Merely sitting inside the root earns nothing: a built bundle, a version-pinned store path or a package-manager shim exists only on the machine that produced it, so naming one would break every other clone. Control it per project with collect.integrations.installed.claude_code.hooks.validate_frames_on_edit in project config: offline (default, static-only, no backend needed), online (also renders against the live stack), or none (don’t install it).
Configuration flags
up, init, config, and reconcile share the flags that drive the setup wizard. They let you configure non-interactively and narrow what gets applied. (reconcile runs no wizard, so --yes and --tls don’t apply to it.)
--yes(-y) — accept the default settings without prompting: a fully local stack (local ClickHouse, no authentication, files synced to disk, self-signed HTTPS, all detected integrations). Required to configure without a terminal; with it, the wizard runs no prompts — including the one asking whether integrations may edit their own config outside the project, so that stays on unless you pass--no-external-editsor setcollect.integrations.external_editsyourself.--tls <mode>— the TLS backend used when configuring (requires--yes):self-signed(default; encrypted HTTPS/HTTP-2 with a browser warning),mkcert(browser-trusted, but the CA install needs a terminal), ornone(plain HTTP).--no-integrations— skip reconciling integrations entirely (frame packs, skills, and edits to user-owned config like~/.claude/settings.json); regenerate only the service configs.--no-skills— reconcile everything except installing integrations’ skill packs into the project (.claude/skills/,.agents/skills/, …).--no-external-edits— keep every write inside the project directory; don’t touch files outside it like~/.claude/settings.json. This overrides the project’scollect.integrations.external_editsconfig for the run. The wizard asks the same question once — whenever an integration you enable writes there — and records a decline in that config, so you normally set it there rather than per-run. Whatever the source of the permission, a reconcile that does change a file outside the project warns and names each one.--validate/--no-validate— statically validate the frames the reconcile wrote, plus everything that references them (the same dependency-graph resolution asvalidate --files), before finishing. Needs no running backend. On by default forreconcile,init, andconfig; off forup, which reconciles on every start and shouldn’t pay for it each time. A failure exits non-zero and leaves the written files in place.--validate-online— additionally render those frames against the live backend to catch runtime errors. Off by default, and requires the stack to already be running: an unreachable database fails the run rather than skipping. Not accepted byup, whose reconcile happens before the services start — runnoemata reconcile --validate-onlineonce the stack is healthy instead.--managed/--no-managed— whether Noemata manages the integrations’ frame packs. Managed (the default) keeps each pack under@frames/@<integration>/matching source and out of version control, overwriting local edits.--no-managedtakes ownership: local changes are preserved per file — upstream still reaches everything you haven’t touched — and the packs are tracked by git like hand-authored frames. This overrides the project’scollect.integrations.managedconfig for the run. The wizard asks the same question, so you normally set it there rather than per-run.