Synadia Insights

CLI

The insights binary is a single command with several subcommands. Running insights with no subcommand starts the server, the same as insights serve (run is a kept alias). web, mcp, http, and prometheus each run their own long-lived process. The client subcommands (db, checks, ops, system list, node list) talk to an already-running node over NATS and exit. The config subcommands read the configuration and the local data directory and need no running server.

The collector build, released as the separate insights-collector binary, carries only serve, ops pprof, config check, config migrate, system list, and node list. Its serve runs the collector and is the default subcommand. In that build the config subcommands take the collector's flags rather than the flags of the full insights serve.

Synopsis

insights [flags] [subcommand] [subcommand-flags]
SubcommandPurpose
(default) serve (alias run)Start the Insights server.
webRun only the web tier, as a NATS client of a remote indexer/scraper node.
prometheusServe one Prometheus /metrics endpoint for every node in the deployment.
mcpRun a read-only Model Context Protocol server exposing the db and checks tools to AI agents.
httpRun a stateless HTTP gateway fronting the read-only DB query API for HTTP-only clients.
db queryRun a SQL query against the embedded DuckDB over the NATS API. query is a hidden alias.
db explainPlan a query with EXPLAIN; --analyze executes and profiles it.
db schemas / tables / columns / macrosDiscover the queryable schemas, tables, columns, and macros.
checks listList all available checks grouped by category.
checks info <CODE>Show full metadata and configurable parameters for one check.
checks findings <CODE>Run one check and return its findings.
ops backup saveBuild a local copy of the database by streaming it table by table. save is the default, so ops backup [flags] runs it.
ops backup view <file>Serve the web UI against a backup file, opened read-only.
ops sizingRecommend a DuckDB memory_limit / threads pair for the running instance.
ops memoryShow the live DuckDB memory profile (current and peak by component).
ops pprofCapture a runtime profile from the running instance for diagnostics.
config checkValidate the configuration and report state the data directory holds that the configuration no longer claims.
config migrateMove state onto the ids the configuration uses, or forget a system it no longer names.
system listDiscover the systems the deployment holds over $INS.ping.
node listDiscover the reachable Insights nodes over $INS.ping.
--versionPrint the binary version and exit.

Configuration precedence

When a setting is defined in more than one place, the highest-priority source wins:

  1. Command-line flags
  2. Environment variables (INSIGHTS_*)
  3. YAML config file (--config / -c, INSIGHTS_CONFIG, or a default search path)
  4. Built-in defaults

Environment variables mirror the flag path: dots become underscores, everything is uppercased, and the INSIGHTS_ prefix is added. For example, --web.port becomes INSIGHTS_WEB_PORT, and --sys.server becomes INSIGHTS_SYS_SERVER.

A subcommand's own flags follow the same rule without the subcommand's name, so --format reads INSIGHTS_FORMAT and --timeout reads INSIGHTS_TIMEOUT on every subcommand that has them. insights <subcommand> --help prints each flag's variable. The top-level connection flags are the exception: they read INSIGHTS_NATS_*.

Without --config or INSIGHTS_CONFIG, the config file is read from <user-config-dir>/insights/config.yaml or ./insights.yaml. See the Configuration reference.

Top-level flags

These flags apply to every subcommand.

FlagEnvDefaultDescription
--config / -cINSIGHTS_CONFIG(search paths)Path to a YAML config file. Applies to every subcommand, including db query and the other API clients.
--log-levelINSIGHTS_LOG_LEVELinfoLog level: debug, info, warn, error.
--serverINSIGHTS_NATS_SERVER(the selected context's, else nats://127.0.0.1:4222)NATS server URL of the Insights API: where the client subcommands and the web, mcp, http, and prometheus processes send their requests.
--credsINSIGHTS_NATS_CREDS(empty)Path to a NATS credentials file.
--contextINSIGHTS_NATS_CONTEXT(empty)NATS CLI context name (resolved via ~/.config/nats/).
--user / --passwordINSIGHTS_NATS_USER / INSIGHTS_NATS_PASSWORD(empty)Basic auth.
--nkey / --jwtINSIGHTS_NATS_NKEY / INSIGHTS_NATS_JWT(empty)NKey seed and user JWT. A JWT on its own is sent as a bearer token.
--tls-cert / --tls-key / --tls-ca-certINSIGHTS_NATS_TLS_CERT / INSIGHTS_NATS_TLS_KEY / INSIGHTS_NATS_TLS_CA_CERT(empty)Client certificate, key, and CA certificate paths.
--tls-firstINSIGHTS_NATS_TLS_FIRSTfalsePerform the TLS handshake before the NATS protocol handshake.
--socks-proxyINSIGHTS_NATS_SOCKS_PROXY(empty)SOCKS proxy URL.
--inbox-prefixINSIGHTS_NATS_INBOX_PREFIX(empty, meaning _INBOX)Request/reply inbox prefix, for an account not granted _INBOX.>. When set, the account must allow subscribing to <prefix>.>.

These are nats-CLI-style flags. Each also parses as a hidden --nats.* alias (for example --nats.server), and the YAML nats: section maps onto them.

Client subcommands resolve the currently selected NATS CLI context when no context name is supplied. Select it once with nats context select <name>, then run insights db, insights checks, insights ops, insights system list, insights node list, insights mcp, insights http, and insights prometheus without connection flags. Use --context only to override that selection for one invocation. Long-running insights serve does not inherit the selected context; configure its URLs and credentials explicitly, or name a context explicitly.

Selecting a system or node

A node can host several systems, each with its own database and a scoped API subtree ($INS.sys.<id>.*). Every client subcommand that reads a system's data takes a shared flag to say which:

FlagEnvYAMLDescription
--system <id>INSIGHTS_SYSTEMsystem.idSystem to address. On db, checks, ops backup save, ops sizing, and ops memory. The http and mcp gateways take it per request instead.
--node <id>INSIGHTS_NODEnode.idInsights node (process) to address, for process diagnostics. On ops pprof.

Resolution order is flag, then environment, then config file. The YAML keys are the same system: / node: identity blocks a single-system node is configured with, so one config file can drive both the server and the CLI.

With nothing naming a target, the CLI runs one $INS.ping discovery round before sending the request. When at least one Insights node answers, discovery waits its full two-second window so every responder can reply, which adds roughly two seconds to each unnamed command. Discovery makes the choice explicit:

  • Exactly one system can answer queries: the request goes to it automatically.
  • Several can: an error lists them (the same table as insights system list) so you pick one with --system.
  • None advertised a catalog: the unscoped subject is tried once, for compatibility with an Insights server that predates discovery. That fallback cannot tell apart several such servers sharing one NATS account; upgrade them before combining them on one account.

A named target skips discovery and never falls back: if --system core-prod gets no answer, that is the error. Set --system / --node (or their environment or config forms) for the lowest-latency repeated use. Unnamed discovery needs permission to publish $INS.ping and receive the replies; a named call needs only its own endpoint's permissions.

The http and mcp gateways take no --system. Each request names its own, so one gateway serves every system: on http the system is a path segment (/sys/<id>/db/query, with GET /systems listing what is addressable); on mcp it is a required system argument on every tool, and the systems tool tells an agent how to resolve it: one result is the answer, several means ask the user. Neither caches topology, so neither can route on a stale view.

insights config check

Validates the configuration and compares it with the identity snapshot the data directory holds, so a renamed or decommissioned system is settled before a restart rather than discovered in the log after one.

It talks to nothing: it reads the config file and the local data directory, and can run while the instance is stopped. It takes the same flags serve does and resolves them from the same file, environment, and flags, so what it validates is what serve would run.

insights config check -c /etc/insights/config.yaml
configuration is valid

  config     /etc/insights/config.yaml
  node       node-a
  systems    production
  data dir   ./data
  disabled   production: account-012

unclaimed state

  system "prod" is no longer configured
    rename  insights config migrate --rename system:prod=<new-id>
    prune   insights config migrate --prune system:prod

An invalid configuration exits non-zero. Unclaimed state does not, because leaving a decommissioned system's data in place is a legitimate end state; pass --strict to fail on it, which makes the command usable as a deploy gate.

A check code matching no registered check is an error in disabled-checks and check-thresholds alike, and serve refuses to start on one: a silently dropped disabled-checks code would leave a check on, and a dropped check-thresholds key would revert to the default. An unknown or renamed parameter name under check-thresholds is an error too. A retired check's code is accepted with a warning that the check is no longer evaluated, and its thresholds are ignored. Every problem is reported in one run, attributed to where it was written, and a code spelled the old way is named with its replacement:

configuration is not valid: invalid check configuration:
  - disabled-checks: check code "ACCOUNT_012" was renamed to "account-012"
  - systems[edge-west].disabled-checks: unknown check code "stream-999"
run 'insights checks list' for the valid codes and 'insights checks info <code>' for a check's parameters

Checking runs serve's license check, so a trial build fails the same way when no license is configured, and it provisions the data directory as serve would. A temporary data directory it created because --data-dir was unset is removed again.

A node id change is reported without a command: nothing on disk carries a node's name, so there is nothing to rename or prune.

insights config migrate

Performs what config check reports: it moves each catalog whose name changed onto the name the configuration uses and records the result, so the id in use owns the history the old one accumulated.

The instance must be stopped, because this renames database files a running instance holds open.

Run with no flags, it migrates the naming scheme for every system whose id stayed the same while the names beneath it moved, which is the ordinary upgrade case. A change of ids has to be declared, because a rename and a decommission-plus-onboard look the same in the snapshot:

insights config migrate -c /etc/insights/config.yaml --rename system:west=north
insights config migrate -c /etc/insights/config.yaml --prune system:west

Both flags repeat, so several systems migrate in one run:

insights config migrate -c config.yaml --rename system:west=north --rename system:staging=south
moved
  systems/staging.db -> systems/south.db
  systems/west.db -> systems/north.db

not carried over
  system "staging" scrape buffer
  system "west" scrape buffer

--prune drops the record and nothing else: the catalog stays on disk, so a system pruned by mistake is recovered by putting its id back in the configuration. Only a system can be pruned.

"Not carried over" means the scrape stream is dropped rather than moved. It is a bounded buffer and the catalog is the record, so at most the last unindexed epoch goes missing, and the next scrape produces a new one. Webhook delivery history stays under the old name and ages out on its own.

Every move is checked before any runs, and a move onto an existing file is refused, so a data directory is never left half-migrated.

insights serve

Default subcommand. Starts the Insights server: scraper, indexer, web UI, sink, and the NATS API endpoints. run is a backward-compatible alias, so insights run and a bare insights both start the server.

insights serve [flags]

Because serve is the default, you can omit the subcommand name:

insights --config /etc/insights/config.yaml

Flags

serve owns every server-only subsystem flag. Rather than duplicate them all here, see the Configuration reference. Each subsystem (scraper, indexer, web, sink, simulator, updater, database, Prometheus, license, and so on) has its own section there. A bare insights --help lists only the global flags; run insights serve --help for the full server set.

Each subsystem is toggled with a shorthand on/off flag (--web, --indexer, --scraper, --simulator, --prometheus, --updater, --telemetry) or the matching environment variable (INSIGHTS_WEB, INSIGHTS_UPDATER, and so on). The --<subsystem>.enabled / INSIGHTS_<SUBSYSTEM>_ENABLED form is also accepted but hidden from --help; the shorthand wins if both are given. In YAML, use the section's enabled: key.

insights web and insights-collector serve expose only the toggles that apply to them: --prometheus, --updater, and --telemetry on web; --updater and --telemetry on the collector.

Frequently used flags:

FlagEnvDefaultDescription
--data-dirINSIGHTS_DATA_DIR(temp dir)Base directory for DuckDB and JetStream data.
--simulatorINSIGHTS_SIMULATORfalseRun against the built-in simulator instead of a real NATS system. A trial build skips license validation when enabled.
--sys.serverINSIGHTS_SYS_SERVER(empty)NATS URL of the target system to scrape.
--sys.contextINSIGHTS_SYS_CONTEXT(empty)NATS CLI context for the target system; an alternative to --sys.server / --sys.creds.
--sys.credsINSIGHTS_SYS_CREDS(empty)NATS credentials file for the target system.
--web.hostnameINSIGHTS_WEB_HOSTNAME127.0.0.1Web UI bind address.
--web.portINSIGHTS_WEB_PORT8080Web UI bind port.
--license.tokenINSIGHTS_LICENSE_TOKEN(empty)License JWT string, for a trial build.
--license.fileINSIGHTS_LICENSE_FILE(empty)Path to a file containing the license JWT, for a trial build.

Example

insights \
  --config /etc/insights/config.yaml \
  --web.hostname 0.0.0.0

insights web

Runs only the web tier, with no indexer, scraper, simulator, or embedded NATS server, so the process holds no local state and serves the UI entirely from data it reaches over NATS. Point it at the node running the indexer and scraper with --server (or --context); that node owns the DuckDB and serves the $INS.db.* / $INS.ops.* endpoints this tier consumes. The instance's capabilities (geo enrichment, the realtime advisory feed, the scrape interval, retention, and the license) are discovered from that node over $INS.ops.info (see the API reference), so none of it needs to be configured locally. A web-only node validates no license of its own; it renders the backend's.

Because it is a pure client of the API, it exposes only the flags a web tier uses: the web server (--web.*), the Prometheus endpoint (--prometheus.*), the updater (--updater.*), --telemetry (whether what people do in its UI is passed to the nodes that report product telemetry), and the top-level connection flags. The full server configuration lives on insights serve.

insights web [flags]

Example

# Node A — indexer + scraper, no web, embedded NATS on a fixed port:
insights --web=false --sink.port=4222 --data-dir /var/lib/insights

# Node B — stateless web tier pointing at Node A:
insights web --server nats://node-a:4222 --web.hostname 0.0.0.0 --web.port 8080

insights-collector serve

Runs a collector: it scrapes one NATS system and writes each epoch to a JetStream stream, serving only the parts of that system's $INS.sys.<id>.* API that need no database (scraper control, diagnostics, events) and $INS.node.<id>.ops.pprof. It has no DuckDB, no indexer, no web tier, no Prometheus endpoint, and no embedded NATS server. A full Insights node elsewhere mirrors the stream, indexes it, and serves queries and the UI for the system.

This command exists only in the insights-collector binary; the full insights binary has no collector command. serve is the collector binary's default command, so insights-collector -c collector.yaml runs it.

insights-collector serve [flags]

The stream lives on an external NATS server, typically a leafnode local to the collector, named with the top-level connection flags (--server, --context, and so on) or --scraper.nats.*. With none of them set, the command refuses to start. --sink.embed is listed in --help but always forced off.

Flags

The collector's serve takes the subset of the full insights serve flags a collector uses: --data-dir, --node.id, --system.id, --sys.* (the scraped system), --scraper.*, --sink.*, --license.*, --updater.*, and --telemetry.*, plus the --updater and --telemetry toggles. Each is described in the Configuration reference; run insights-collector serve --help for the full list.

Example

# --server/--creds: the local NATS that holds the stream and serves the API.
# --sys.*: the monitored system's $SYS-account connection, which is scraped.
insights-collector serve \
  --server nats://localhost:4222 \
  --creds /etc/insights/collector.creds \
  --system.id edge-west \
  --sys.server nats://monitored-system:4222 \
  --sys.creds /etc/insights/sys.creds

insights mcp

Runs a read-only Model Context Protocol server that exposes eleven tools over the db, checks, instance-info, and system-discovery endpoints for AI agents. Like db query, it owns no database: it forwards each tool call to a running Insights node over NATS and is read-only by construction. Every tool except systems takes a required system argument. For client setup (Claude Desktop, Claude Code, Cursor), see the AI Agents guide.

insights mcp [flags]

Flags

FlagEnvDefaultDescription
--transportINSIGHTS_MCP_TRANSPORTstdiostdio for a client-spawned subprocess, or http for a long-running Streamable HTTP service.
--httpINSIGHTS_MCP_HTTP127.0.0.1:8090Listen address when --transport http.
--timeoutINSIGHTS_MCP_TIMEOUT2mPer-tool NATS request timeout.
--http-tlsINSIGHTS_MCP_HTTP_TLSfalseServe the http transport over HTTPS. Requires --http-tls-cert and --http-tls-key. Ignored for stdio.
--http-tls-cert / --http-tls-keyINSIGHTS_MCP_HTTP_TLS_CERT / INSIGHTS_MCP_HTTP_TLS_KEY(empty)Paths to the TLS certificate and private key (PEM). Both required when --http-tls is set.

Plus the top-level connection flags for reaching the node. The http transport binds loopback by default and applies no authorization of its own; keep it local or front it with your own auth.

Example

# Local stdio server, launched by an MCP client.
# Uses the selected NATS context; add --context to override it:
insights mcp

# Long-running HTTP service over TLS:
insights mcp --transport http --http-tls --http-tls-cert cert.pem --http-tls-key key.pem

insights http

Runs a stateless HTTP gateway that fronts the read-only db query API over plain HTTP, so an HTTP-only client (a BI or dashboard datasource, a script, another service) can query Insights without a NATS client. Like web, it owns no database: each HTTP request is translated into the matching $INS.db.* NATS request and the reply is returned as-is, so the read-only guard, the row cap, and per-account NATS auth all stay enforced upstream. Routes are system-scoped (POST /sys/<id>/db/query, and so on), and GET /systems lists the systems it can address. See the HTTP gateway section of the API reference for the route map.

Point it at the node serving the $INS.db.* endpoints with --server (or --context), exactly like db query. It adds a bearer token at its own edge and refuses to start without one unless --allow-unauthenticated is given.

insights http [flags]

Flags

FlagEnvDefaultDescription
--listenINSIGHTS_HTTP_LISTEN127.0.0.1:8080HTTP listen address. Use 0.0.0.0:<port> to accept remote connections (e.g. behind a TLS-terminating ingress).
--auth-tokenINSIGHTS_HTTP_AUTH_TOKEN(empty)Bearer token required on every request except /healthz (Authorization: Bearer <token>). Required unless --allow-unauthenticated.
--allow-unauthenticatedINSIGHTS_HTTP_ALLOW_UNAUTHENTICATEDfalseRun without a bearer token. Only for trusted, network-isolated deployments, never for public exposure.
--timeoutINSIGHTS_HTTP_TIMEOUT1mPer-request NATS request timeout.
--max-body-bytesINSIGHTS_HTTP_MAX_BODY_BYTES1048576Maximum request body size in bytes (1 MiB).

Plus the top-level connection flags for reaching the node that serves $INS.db.*.

Example

# Run the gateway pointed at an Insights node, with a bearer token:
export INSIGHTS_HTTP_AUTH_TOKEN=$(openssl rand -hex 32)
insights http \
  --server nats://insights.example.com:4222 \
  --creds ~/.config/nats/query.creds \
  --listen 0.0.0.0:8080

# Query one system over HTTP:
curl -s localhost:8080/sys/prod-us/db/query \
  -H "Authorization: Bearer $INSIGHTS_HTTP_AUTH_TOKEN" \
  -d '{"sql":"SELECT name, cpu FROM hx.servers ORDER BY cpu DESC LIMIT 10"}'

insights prometheus

Serves one Prometheus /metrics endpoint for every Insights node in the deployment. On each scrape it discovers the nodes over $INS.ping, asks each for its metrics, and serves the merged set with a source label naming the node each series came from. It holds no database and runs no indexer: it serves what a node with prometheus.aggregate: true serves, for when Prometheus can reach this process but not the nodes themselves. Scrape exactly one aggregating endpoint, or every series is reported once per aggregator. The series and labels are listed in Metrics.

insights prometheus [flags]

Flags

FlagEnvDefaultDescription
--hostnameINSIGHTS_HOSTNAME127.0.0.1Bind hostname for the /metrics server.
--portINSIGHTS_PORT9091Bind port for the /metrics server.
--timeoutINSIGHTS_TIMEOUT2sBudget for one collection: node discovery, then the per-node requests. Must be shorter than the scrape timeout of whatever reads this endpoint.

Plus the top-level connection flags for reaching the deployment's NATS.

Example

insights prometheus --server nats://hub:4222 --hostname 0.0.0.0 --port 9091

insights db query

Send a SQL query to the running Insights server over NATS and write the result to stdout. See the Search reference for the Insights query language. Use db query for arbitrary DuckDB SQL against the hx schema.

insights db query [flags] [SQL]

insights query is a hidden alias for db query: it takes the same arguments and flags but is not listed in --help. Prefer db query in scripts and documentation.

The SQL statement can be passed as a positional argument or piped on stdin. If both are omitted, the command exits with an error.

Results are streamed from the server as Apache Arrow chunks and rendered locally, so large results work without buffering the whole result in memory on either side. Against a server without the streaming endpoint, the command falls back to the buffered one. Timestamps are RFC 3339, NULL is an empty CSV field or JSON null, and BLOB is base64.

--format text renders the result as a table for reading at a terminal:

insights db query --format text "SELECT name, cluster, connections, subscriptions, cpu, memory FROM hx.servers WHERE epoch = (SELECT max(epoch) FROM hx.server_stats) ORDER BY name"
Query Result (3 rows)
╭─────────────┬───────────┬─────────────┬───────────────┬───────┬───────────╮
│ NAME        │ CLUSTER   │ CONNECTIONS │ SUBSCRIPTIONS │   CPU │    MEMORY │
├─────────────┼───────────┼─────────────┼───────────────┼───────┼───────────┤
│ simulator-1 │ simulator │           6 │           257 │ 5.175 │ 648232960 │
│ simulator-2 │ simulator │           0 │           253 │ 5.175 │ 648232960 │
│ simulator-3 │ simulator │           5 │           257 │ 5.175 │ 648232960 │
╰─────────────┴───────────┴─────────────┴───────────────┴───────┴───────────╯

Numeric columns are right-aligned based on the result's column types, so a column of digits that is not a number, such as a port or an account ID, still reads as text. Unlike csv and json, text has to buffer the whole result on the client, because a table's column widths are only known once the last row arrives. It is capped at 10,000 rows and fails with an error naming --format csv past that, rather than printing a truncated table. csv is the default.

Flags

FlagEnvDefaultDescription
--format / -fINSIGHTS_FORMATcsvOutput format: csv, json, or text. text renders a table and is capped at 10,000 rows.
--timeoutINSIGHTS_TIMEOUT1mPer-message receive timeout while streaming. Must exceed the server's stream batch timeout.
--systemINSIGHTS_SYSTEM(discovered)Target system on a multi-system deployment. See Selecting a system or node.

Plus the top-level connection flags for reaching the server.

Examples

Inline SQL:

insights db query "SELECT name, cpu FROM hx.servers ORDER BY cpu DESC LIMIT 10"

SQL via stdin:

cat query.sql | insights db query --format json

Against a remote Insights server:

insights --server nats://insights.example.com:4222 \
  --creds ~/.config/nats/ops.creds \
  db query "SELECT COUNT(*) FROM hx.conns"

insights db

Inspect the database without writing a full query: validate and plan (or profile) a statement, and discover the queryable schemas, tables, columns, and macros. These subcommands mirror the $INS.db.* discovery endpoints (see the API reference) and are scoped to the allowlisted schemas (hx, main, checks, contexts). The discovery commands read catalog metadata only and scan no data; db explain --analyze is the exception, since it executes the query to gather runtime timing.

Discovery output defaults to a text table; pass --format json for the raw endpoint response (which also includes each object's table_comment) or --format csv.

insights db schemas
Schemas (4 rows)
╭──────────┬────────┬───────┬────────┬─────────────────────────────────────────────────────────────╮
│ SCHEMA   │ TABLES │ VIEWS │ MACROS │ DESCRIPTION                                                 │
├──────────┼────────┼───────┼────────┼─────────────────────────────────────────────────────────────┤
│ checks   │      0 │     0 │    176 │ Checks framework: one table macro per check code            │
│          │        │       │        │ (server-001 is checks.server_001) returning (code, entity,  │
│          │        │       │        │ ...) finding tuples, plus the shared helper macros they     │
│          │        │       │        │ call.                                                       │
├──────────┼────────┼───────┼────────┼─────────────────────────────────────────────────────────────┤
│ contexts │      0 │     0 │     35 │ Context macros behind the evidence panels shown with a      │
│          │        │       │        │ finding: cluster peer comparisons, raft peers, top streams  │
│          │        │       │        │ and similar supporting views.                               │
├──────────┼────────┼───────┼────────┼─────────────────────────────────────────────────────────────┤
│ hx       │     40 │    13 │      0 │ Historical entity tables (*_ident/*_opts/*_stats) and the   │
│          │        │       │        │ pre-joined views (servers, accounts, streams, consumers,    │
│          │        │       │        │ ...) built on them. Each *_stats/view row is per entity per │
│          │        │       │        │ epoch, so ALWAYS scope by epoch — e.g. WHERE epoch =        │
│          │        │       │        │ (SELECT max(epoch) FROM <entity>_stats) — and/or add a      │
│          │        │       │        │ LIMIT; an unscoped SELECT * scans all retained history.     │
│          │        │       │        │ Fully commented.                                            │
├──────────┼────────┼───────┼────────┼─────────────────────────────────────────────────────────────┤
│ main     │      1 │     0 │      0 │ Default (unqualified) schema. Holds the geo-IP enrichment   │
│          │        │       │        │ table `ips`.                                                │
╰──────────┴────────┴───────┴────────┴─────────────────────────────────────────────────────────────╯

Every text table the CLI prints (these discovery commands, checks, db query --format text, and the system list / node list listings) uses the same layout, in the collector build as well. A table fits the terminal it is printed to: prose columns wrap at word boundaries and value columns keep their width. Output that is not going to a terminal is laid out to 100 columns. Table headers, and the severity column of checks findings, are colored at a terminal; redirecting or piping the output, or setting NO_COLOR, makes it plain, and FORCE_COLOR=1 colors it regardless.

insights db explain [flags] [SQL]
insights db schemas [flags]
insights db tables  [flags]
insights db columns [flags]
insights db macros  [flags]

Flags

FlagDefaultDescription
--format / -ftextOutput format. explain: text or json; the discovery commands also accept csv.
--analyze / -afalse(explain) Execute the query with EXPLAIN ANALYZE and include per-operator runtime timing.
--timeout30sNATS request timeout.
--schema(none)(tables, columns, macros) Restrict to one schema. Omit to span all allowlisted schemas.
--table(none)(columns) Restrict to one table or view. Omit to span every object in scope.
--system(discovered)Target system on a multi-system deployment (env INSIGHTS_SYSTEM). See Selecting a system or node.

Plus the top-level connection flags. explain takes the SQL as a positional argument or on stdin, like insights db query.

Examples

Discover schemas, then drill into one:

insights db schemas
insights db tables --schema hx
insights db columns --schema hx --table servers
insights db macros --schema checks

Plan a query before running it, or execute and profile it with --analyze:

insights db explain "SELECT name, cpu FROM hx.servers WHERE epoch = (SELECT max(epoch) FROM hx.server_stats)"
insights db explain --analyze "SELECT name, cpu FROM hx.servers WHERE epoch = (SELECT max(epoch) FROM hx.server_stats)"

Machine-readable column metadata (includes table_comment):

insights db columns --schema hx --table servers --format json

insights checks list

List every check the server knows about, grouped by category. Default output is one text table per category. --format json prints the endpoint's response as-is, an array of categories each with a label and its checks; --format csv flattens the grouping.

insights checks list [flags]

When the addressed system has any check listed under disabled-checks, the text tables gain a Disabled column marking them; the column is absent otherwise. The CSV columns are always code,name,disabled. In JSON, a disabled check carries "disabled": true and an enabled one omits the field.

Flags

FlagEnvDefaultDescription
--format / -fINSIGHTS_FORMATtextOutput format: text, csv, or json.
--timeoutINSIGHTS_TIMEOUT30sNATS request timeout.
--systemINSIGHTS_SYSTEM(discovered)Target system on a multi-system deployment. See Selecting a system or node.

Example

insights checks list --format json | jq '.[].label'

insights checks info

Show the full metadata for a single check: description, remediation, scope, severity, category, any tunable parameters with their default and currently resolved values, and any extra detail columns.

insights checks info [flags] <CODE>

The text output always includes a Disabled line: whether the addressed system turned this check off, in which case it still runs but is left out of the health grades and never delivered as a notification.

Flags

FlagEnvDefaultDescription
--format / -fINSIGHTS_FORMATtextOutput format: text or json.
--timeoutINSIGHTS_TIMEOUT30sNATS request timeout.
--systemINSIGHTS_SYSTEM(discovered)Target system on a multi-system deployment. See Selecting a system or node.

Example

insights checks info server-003

insights checks findings

Run one check by code over a time window and return the resulting findings.

insights checks findings [flags] <CODE>

<CODE> is a check code such as server-001 or stream-010. See the Checks Reference for the full catalogue.

Flags

FlagEnvDefaultDescription
--format / -fINSIGHTS_FORMATtextOutput format: text, csv, or json. The JSON response includes pagination metadata.
--timeoutINSIGHTS_TIMEOUT30sNATS request timeout.
--durationINSIGHTS_DURATION(empty)Relative window as a DuckDB interval (e.g. 1 hour, 15 minutes, 7 days).
--startINSIGHTS_START(empty)Absolute start time (UTC, YYYY-MM-DDTHH:MM:SS). When omitted, derived from --duration.
--endINSIGHTS_END(empty, meaning "live")Absolute end time (UTC).
--pageINSIGHTS_PAGE1Page number (1-based) when results exceed one page.
--systemINSIGHTS_SYSTEM(discovered)Target system on a multi-system deployment. See Selecting a system or node.

Example

Run server-003 (High CPU Usage) against the last hour:

insights checks findings server-003 --duration "1 hour"

The text table carries only the columns the check declares, right-aligning the numeric ones. Severity appears as a column only for checks that compute it per finding, where it is colored by level at a terminal; for every other check it is a property of the check itself, shown by checks info.

Findings for account-005: No Subscription Interest (4 rows)
╭────────┬─────────────────────────────┬────────────────┬─────────────────────────────╮
│ ENTITY │ SUBJECT                     │ SOURCE ACCOUNT │ LOCAL SUBJECT               │
├────────┼─────────────────────────────┼────────────────┼─────────────────────────────┤
│ $G     │ $SYS.REQ.ACCOUNT.$G.CONNZ   │ SYS            │ $SYS.REQ.ACCOUNT.PING.CONNZ │
│ $G     │ $SYS.REQ.USER.$G.INFO       │ SYS            │ $SYS.REQ.USER.INFO          │
│ APP1   │ $JS.API.>                   │ SYS            │ $JS.API.>                   │
│ APP1   │ $SYS.REQ.ACCOUNT.APP1.STATZ │ SYS            │ $SYS.REQ.ACCOUNT.PING.STATZ │
╰────────┴─────────────────────────────┴────────────────┴─────────────────────────────╯

A check the system has disabled is marked [disabled] after its name in the title. Its rows are real findings, but the check is not graded and nothing it finds is delivered. The JSON output carries the same fact as disabled: true; the CSV output has no marker.

Page through findings as JSON:

insights checks findings stream-002 --format json --page 2

insights ops backup

ops backup has two subcommands: save builds a backup and view serves one in the web UI. save is the default, so insights ops backup [flags] and insights ops backup save [flags] are the same command.

ops backup save builds a local copy of a system's Insights database. The client streams each table over $INS.db.backup.stream and inserts it into a DuckDB file it creates. The server writes nothing, so the disk space the backup needs is on the host running the command, not on the server. The result is a complete database (schema, views, macros, and comments come from the insights binary running the command), so it can be restored as a --data-dir, or browsed in place with insights ops backup view. Use the viewer to look at a backup: a --data-dir is opened read-write, and the server migrates and prunes what it finds there.

The output must be a real file: the copy writes into a seekable DuckDB database, so a backup cannot be piped to another process until it is complete.

insights ops backup save [flags]

ops backup save always prints a plan summary (time range, epoch coverage, estimated file size) to stderr. When stdin is a terminal, it prompts for confirmation before running. Pass --force / -f to skip the prompt. --force is required when running non-interactively (for example, cron or CI).

Each table's row count is printed as it completes. On a terminal, the table being copied also reports its progress against the plan's estimate on a line rewritten in place; --no-progress turns that off.

The latest epoch is fixed once, before the first table, and every table is copied up to it, so tables copied minutes apart stay consistent even while scrapes continue. A retention sweep during the run is the one thing that cannot be pinned: it deletes the oldest epochs, so tables copied first can keep rows that tables copied last have lost. The command warns, names the epoch below which the tables disagree, and keeps the file. For an exact range, pass a --start newer than the retention horizon. A window that changes in a way retention cannot explain fails the backup.

Metadata reads retry transient NATS timeouts and unavailable connections, up to three attempts. --timeout bounds each attempt and each message of a table copy. If the final check of the window cannot be read, the command warns that it could not verify the window and keeps the completed backup.

--start bounds history, not identity: a windowed copy keeps the identity and configuration rows its stats refer to, however old, and fails rather than write a file whose stats name entities it cannot resolve. An identity row the server itself no longer holds is kept as a gap and reported as a warning.

Run it with the same Insights version as the server. A column the server has that this binary does not, or a column whose type differs, is refused up front and named. A column this binary has that the server lacks is a warning instead: it is filled with its declared default, and the warning names the value.

Flags

FlagEnvDefaultDescription
--output / -oINSIGHTS_OUTPUTinsights-backup-<system>-<timestamp>.dbLocal output path. The file is built alongside it as <output>.part and renamed on success.
--startINSIGHTS_START(no lower bound)Start time: absolute (YYYY-MM-DD, RFC 3339, or YYYY-MM-DDTHH:MM:SS UTC) or relative duration (7d, 1h, 30m).
--endINSIGHTS_END(latest epoch)End time: same formats as --start.
--force / -fINSIGHTS_FORCEfalseSkip the confirmation prompt. Required for non-interactive use.
--no-progressINSIGHTS_NO_PROGRESSfalseDon't report per-table progress while copying.
--timeoutINSIGHTS_TIMEOUT10mHow long to wait for each metadata request attempt or message of a table's backup stream.
--systemINSIGHTS_SYSTEM(discovered)Target system on a multi-system deployment. See Selecting a system or node.

Examples

Back up the last seven days, prompting for confirmation:

insights ops backup save --start 7d --output weekly-backup.db

Non-interactive full backup to a dated filename:

insights ops backup save --force --output "insights-$(date +%Y%m%d).db"

insights ops backup view

Serve the web UI against a backup file. The file is opened in DuckDB's read-only access mode and nothing that writes is started: no scraper, no indexer, no webhook delivery, no retention sweep, no updater, and no connection to your NATS system. Browsing a backup cannot change it.

insights ops backup view <file> [flags]

A NATS server does start, embedded and bound to loopback on an ephemeral port, because the web tier reads its database through the query API. Its JetStream store is a temporary directory removed on exit.

The pages mount under /systems/backup, and insights logs the URL to open once it is serving. A backup carries no system identity of its own, so the system id is always backup.

Views that describe the serving process rather than the backed-up system, such as the Operations page's NATS panel and the node inventory, report this process. The Operations page offers no scraper controls and lists the scraper as None. The time controls read OFFLINE and pin to the backup's last epoch; their presets and the charts' resolution follow the scrape interval inferred from the spacing of the file's epochs.

Open the file with the same Insights version that wrote it. The command compares the file's recorded migrations with its own and refuses to open on any difference, naming what differs.

Flags

FlagEnvDefaultDescription
--hostnameINSIGHTS_HOSTNAME127.0.0.1Web server bind hostname.
--portINSIGHTS_PORT8080Web server bind port.

Example

insights ops backup view ./insights-backup-prod-20260816-101500.db --port 8123

insights ops sizing

Recommend a DuckDB memory_limit and threads configuration for the running instance, grounded in DuckDB's per-thread guidance and the instance's real host capacity (cores and RAM) plus its observed memory use. Because it runs against the live database on the actual host the limits apply to, it is most useful run against an instance that is under load, for example when investigating a performance issue. You can then decide whether to retune the limits or add host memory.

When the memory profiler is enabled (the default), the estimates are grounded in the peak memory measured under real load rather than conservative constants; the report flags when it had no samples to work from. The report also projects steady-state disk size at full retention and flags when that exceeds host RAM. The largest single write is usually a retention prune, which runs far less often than the profiler's rolling window. Use --observe <duration> to watch the live profile long enough to span one so that peak is captured.

insights ops sizing [flags]

Flags

FlagEnvDefaultDescription
--per-threadINSIGHTS_PER_THREAD2GiB per thread used in the query budget (DuckDB recommends 1–4).
--threadsINSIGHTS_THREADS0Threads to size for (0 = recommend min(cores, 4)).
--observe / -oINSIGHTS_OBSERVE0Observe the live memory profile for this long and size from the peaks seen (0 = one-shot).
--timeoutINSIGHTS_TIMEOUT30sNATS request timeout.
--systemINSIGHTS_SYSTEM(discovered)Target system on a multi-system deployment. See Selecting a system or node.

Examples

# Size the running instance against its real host:
insights ops sizing

# Size for a specific thread count and per-thread budget:
insights ops sizing --threads 8 --per-thread 4

# Observe live load for an hour (ideally spanning a retention prune):
insights ops sizing --observe 1h

insights ops memory

Show the running instance's live DuckDB memory profile: current and peak resident bytes per component over a rolling window. This is the ground truth behind ops sizing's measured estimates, and a quick way to watch what the database is actually doing under load.

A background sampler in the server records the memory profile every --db.memory-profile.interval (default 2s) into a --db.memory-profile.window (default 15m) rolling window; ops memory reads that retained snapshot over NATS rather than sampling on demand. The same readings are also exported as the insights_duckdb_memory_bytes Prometheus gauge. Output is a summary table (current / peak / peak time per component) followed by a small self-scaled trend chart per component, in a fixed order so --live refreshes do not reorder them.

insights ops memory [flags]

Flags

FlagEnvDefaultDescription
--live / -lINSIGHTS_LIVEfalseContinuously re-render the profile as the server samples it (Ctrl-C to stop).
--timeoutINSIGHTS_TIMEOUT30sNATS request timeout.
--systemINSIGHTS_SYSTEM(discovered)Target system on a multi-system deployment. See Selecting a system or node.

Example

insights ops memory --live

If the profiler is disabled (--db.memory-profile.enabled=false), the command reports that no samples are available.

insights ops pprof

Capture a runtime profile from the running instance for diagnostics. The most useful is a goroutine dump to see where the process is parked when something has stalled (for example, a scrape or index loop that stopped advancing). The server does not expose HTTP profiling handlers, so this subcommand is the way to reach the standard profiles. It addresses a node rather than a system, and is also available in the collector build.

insights ops pprof [flags]

Flags

FlagEnvDefaultDescription
--typeINSIGHTS_TYPEgoroutineProfile to capture: goroutine, heap, allocs, threadcreate, block, mutex.
--debugINSIGHTS_DEBUG0Debug level: 0 = binary protobuf for go tool pprof; 1 = text with aggregated counts; 2 = full per-goroutine stacks.
--output / -oINSIGHTS_OUTPUT(stdout)Write the profile to this file instead of stdout.
--timeoutINSIGHTS_TIMEOUT30sNATS request timeout.
--nodeINSIGHTS_NODE(discovered)Target Insights node on a multi-node deployment. See Selecting a system or node.

A binary profile (--debug 0) is refused when stdout is a terminal. Pass --output to write it to a file, or --debug 2 for a readable text dump.

Examples

# Read a goroutine dump directly:
insights ops pprof --type goroutine --debug 2

# Capture a heap profile and open it in pprof:
insights ops pprof --type heap --output heap.pprof
go tool pprof heap.pprof

block and mutex return an empty profile unless their sampling was enabled at server startup. The whole profile is returned in a single NATS reply, so a profile larger than the server's max payload fails rather than being truncated.

insights system list

Discover the systems the deployment holds: broadcast one $INS.ping round and print the replies as a table. This is the same data --system discovery and its ambiguity error are built from.

insights system list [flags]

The table has one row per logical system, not per reply. Every Insights process answers ping, collectors included, so a system whose scrape and catalog sides live on different nodes still appears once, listed against the nodes holding its active data source. ROLES names that source: scrape whenever any node is scraping the system, catalog for a retained catalog whose scraper is gone. node list shows where every participation physically runs.

$ insights system list

╭───────────┬────────┬────────────────┬─────────╮
│ SYSTEM    │ ROLES  │ NODE           │ VERSION │
├───────────┼────────┼────────────────┼─────────┤
│ core-prod │ scrape │ hub-us-east    │ 1.2.0   │
│ edge-west │ scrape │ edge-collector │ 1.2.0   │
╰───────────┴────────┴────────────────┴─────────╯

2 systems on 2 nodes.

When any system carries metadata labels (system.metadata), a METADATA column lists them as key=value pairs. A node that holds a system's catalog but cannot open it is named below the table, one line per node, as ! <system>: catalog unavailable on node <node> — <reason>.

Nothing answering prints no insights node responded and exits zero: an empty deployment is a valid answer, not an error.

Flags

FlagEnvDefaultDescription
--format / -fINSIGHTS_FORMATtextOutput format: text or json. JSON emits the raw ping replies for scripting.
--timeoutINSIGHTS_TIMEOUT2sHow long to collect ping replies.

insights node list

The node view of the same discovery round: one row per responding Insights process (full nodes and collectors alike) with the systems each participates in. A participation without a catalog is annotated with its role, since it cannot answer queries:

$ insights node list

╭────────────────┬─────────┬──────────────────────╮
│ NODE           │ VERSION │ SYSTEMS              │
├────────────────┼─────────┼──────────────────────┤
│ edge-collector │ 1.2.0   │ edge-west (scrape)   │
│ hub-us-east    │ 1.2.0   │ core-prod, edge-west │
╰────────────────┴─────────┴──────────────────────╯

2 nodes.

A node carrying metadata labels (node.metadata) adds a METADATA column. Use the listing to find the id for ops pprof --node. Flags are the same as system list.

--version

Print the binary version and exit. Recognized only as the sole argument:

insights --version
1.2.0

Official release binaries and container images print the product version bare, as above. Unstamped binaries print dev.