01 · REFERENCE

CLI reference

Bare xerj starts the server, and everything the engine itself does is controlled by the TOML config and driven through the HTTP API. The subcommands — autoindex, index, brain, mcp, and the Corpus Hub commands ask, hub and code --def — are clients of a running node, not other ways to run one. (xerj ask starts a node of its own when none answers; see below.)

USAGE:
  xerj [OPTIONS]
  xerj <SUBCOMMAND> [ARGS]

OPTIONS:
  -c, --config <PATH>     Path to the TOML config file
                          [default: ./xerj.toml]
  -d, --data-dir <PATH>   Override [server] data_dir
  -b, --bind <ADDR>       Override [server] bind_address
                          [default: 127.0.0.1 — loopback only]
  -k, --insecure          Disable TLS and API-key auth (dev only)
  -h, --help              Print help
  -V, --version           Print version and build commit

SUBCOMMANDS:
  index      <opts>            direct NDJSON → engine ingest
  autoindex  <folder> [opts]   zero-config folder discovery + indexing
  brain      <folder>          index a folder into a browsable second brain
  mcp        [opts]            Model Context Protocol stdio server
  ask        "<question>"      answer from a Corpus Hub pack (pulled on first use)
  hub        <command>         pull, sync, status, stop, config; build-pack and
                               build-catalogue for CI
  code       <corpus> "<q>"   retrieval over a corpus; --def <SYMBOL> for one definition
  corpus     add|index|list    clone + index your own reference corpora

Abridged. xerj --help prints every subcommand; each prints its own options with --help.

--config / -c

Path to the TOML config. Defaults to ./xerj.toml. The XERJ_CONFIG environment variable is consulted only if this flag is absent.

--data-dir / -d

Convenience override for [server] data_dir. Useful for ephemeral dev runs and for pointing XERJ at a tmpfs when you just want to burn something down after.

--bind / -b

Interface to bind every listener to; overrides [server] bind_address. Env: XERJ_BIND_ADDRESS. The default is 127.0.0.1 — loopback only. Pass 0.0.0.0 (or a specific address) to expose the node; with TLS off that also needs server.allow_insecure_network_bind = true (env XERJ_ALLOW_INSECURE_NETWORK_BIND), because every request, API key included, would cross the network in cleartext. The value must be an IPv4 or IPv6 literal; host names are not resolved, and one is refused at startup before the data directory is created.

--insecure / -k

Disables TLS and API-key auth. Dev only. It does not unlock a network bind: clearing tls.enabled is exactly what makes a non-loopback bind_address refuse to start, so --insecure on 0.0.0.0 exits non-zero unless the exposure is declared.

--help / -h

Prints the synopsis above and exits.

--version / -V

Prints the version, git SHA, and build date. Useful in oncall pages: xerj -V | head -1.

xerj autoindex <folder>

Point it at a folder and it discovers, infers and indexes the contents — no mapping to write, no pipeline to configure. It is a client of a node that is already running, and it never reads the TOML config, so the endpoint and the credential are its own flags.

USAGE:
  xerj autoindex <folder> [OPTIONS]   discover + index a folder
  xerj autoindex map [OPTIONS]        print the discovered data map
  xerj autoindex status [OPTIONS]     resume-journal + index progress view

OPTIONS (abridged — `xerj autoindex --help` prints all of them):
  --url <U>           ES-compat endpoint
                      [default: http://localhost:9200]
  --api-key <K>       API key for that endpoint (or env XERJ_API_KEY)
  --workers <N>       workers for both phases [default: every core]
  --prefix <P>        index prefix [default: ax]
  --no-semantic       skip semantic_text on body fields (pure BM25+keyword)
  --dry-run           walk+sniff+infer, print the plan, index nothing
  --json              machine-readable RESULT on stdout
  --progress <MODE>   liveness on stderr: auto|plain|json|none

autoindex --api-key

The key the target node expects, or env XERJ_API_KEY (the flag wins). autoindex never takes it from xerj.toml — the config file belongs to the server, not to its clients. A node running with [auth] enabled = true (the default, and what happens on any start without --insecure) refuses an unauthenticated run with HTTP 401 Unauthorized. That node wrote its key to <data_dir>/admin.key on first boot:

$ export XERJ_API_KEY="$(cat ./data/admin.key)"
$ xerj autoindex ~/my-project

# or per-run:
$ xerj autoindex ~/my-project --api-key "$(cat ./data/admin.key)"

Against a node started with --insecure no key is needed, because that flag clears auth.enabled too.

Source · engine/crates/xerj-server/src/main.rs · engine/crates/xerj-autoindex/src/cli.rs

xerj ask "<question>"

Answers a question from a Corpus Hub pack: a pinned, signed corpus that CI has already indexed. The first question to a corpus downloads its pack, checks the sha256 and the ed25519 signature, and restores it into a node; later questions answer from that pack. The answer always names the corpus, the pin and the licence. xerj "<question>" is the same command when the question contains a space. The help text, as the binary prints it:

usage: xerj ask "<question>" [options]
       xerj "<question>"            (the same, when the question contains a space)

Answers from a hub corpus - pulling its pre-indexed pack the first time - and
always names the corpus, the pinned revision and the licence.

options:
  --corpus <slug>        skip routing and ask this corpus
  --def <SYMBOL>         exact definition lookup (file:line, declaration, value)
  --how                  a how-does-it-work question (ranked passages)
  --files                list the relevant files with their licences
  --offline              never touch the network (cached catalogue and packs only)
  --no-fetch             do not download a pack for next time
  --hosted on|off|auto   may the question be sent to the hosted hub? (default
                         auto: off inside a git repository, on elsewhere)
  --max-tokens N         cap the answer at about N tokens
  --json                 print the answer as JSON
  --url <node>           the node that holds the packs (default: XERJ_URL, then
                         localhost:9200, else a local node is started)

exit codes: 0 answered; 1 no match or nothing to answer from; 2 usage error or
unknown corpus; 3 a pack failed verification; 4 more than one corpus matches
(candidates are printed; re-run with --corpus <slug>); 5 this build has no hub
public key, so no pack can be verified (nothing was downloaded)

Example, with the pack already on disk:

$ xerj ask --def LZ4_VERSION_RELEASE
lz4  pin 2026-10-02 (15 days old)  mixed: BSD-2 (lib) / GPL-2.0+ (rest)  lz4/lib/lz4.h:133
#define LZ4_VERSION_RELEASE  0    /* for tweaks, bug-fixes, or development */
value: 0
  approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy
answered from: local pack
warning: approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy

Verify: answered from: local pack in the output and exit 0. Three things the help does not say. A question does not auto-download a pack over 50 MiB (AUTO_PULL_MAX_BYTES); xerj hub pull <slug> fetches it explicitly, up to the 4 GiB disk budget (DISK_BUDGET_BYTES). This build has no hosted-answer client: whatever --hosted says, the question is answered on this machine and is never sent to the hub. And this build writes no xerj.lock and has no --locked flag. The walkthrough, the routing rules and every message are on the Corpus Hub page.

xerj hub <command>

Manages the pack cache, the catalogue and the node xerj ask starts. xerj hub --help prints this, and so does --help after any command other than sync and build-catalogue, which have their own:

usage: xerj hub <command>

  build-pack <slug> --registry <dir> --out <dir> --url <node> --sign-key <seed-file>
        Build a signed, pre-indexed pack from a registry manifest (CI).
  build-catalogue --registry <dir> --packs <dir> --out <file> --sign-key <seed-file>
        Build and sign the catalogue from verified packs (CI; see -h).
  sync [--url <node>] | sync --install-timer
        Refresh the catalogue and bring every cached pack up to its pin;
        --install-timer schedules that every 6 hours (see `xerj hub sync -h`).
  pull <slug> [--quiet] [--url <node>]
        Download, verify and restore a pack, then `xerj code <slug>` works.
        Exits 2 for an unknown corpus, 3 when the pack fails verification, 5 when
        this build has no hub public key to verify with (nothing is downloaded),
        1 for anything else (an excluded licence and a pack over the size limit
        are refused here too).
  status
        Cached packs, the automatic node, the hosted setting, catalogue age.
  stop
        Stop the automatic node `xerj ask` started (only that process).
  config hosted on|off|auto
        Whether questions may be sent to the hosted hub (default auto: off
        inside a git repository, on elsewhere).

Ask a question with `xerj ask "<question>"` (see `xerj ask --help`).

Packs are pulled on demand and never cloned or indexed on the user's machine
(docs/CORPUS_HUB_V2_IMPLEMENTATION.md).

There is no xerj hub find (it exits 2 with unknown command 'find'); to see which corpora could answer a question, use the MCP tool xerj_hub_find or run xerj ask, which prints the candidates when more than one fits. update and rm do not exist either.

hub pull

$ xerj hub pull snappy
pulled snappy 9c28114a 15034490 → 3 indices, 382 definitions
$ xerj hub pull snappy
snappy: already present

Verify: exit 0, and xerj hub status lists a pack: snappy line. --quiet drops the progress notices. --url picks the node to restore into (default XERJ_URL, then localhost:9200, else the automatic node). A node you started yourself must allow the pack directory as a snapshot location; the fix is on the Corpus Hub page.

hub sync

usage: xerj hub sync [--url <node>]
       xerj hub sync --install-timer

Refresh the catalogue, then bring every cached pack up to the catalogue's pin:
the new pin is pulled by `xerj hub pull` (sha256 and signature are verified
before anything is extracted), the old pin's directory is removed only after the
new one is restored, and the cache is pruned to the 4.0 GB disk budget, least
recently used first. `~/.xerj/hub/last_sync` is written when nothing failed.

  --url <node>      the node the packs are restored into (passed to `hub pull`)
  --install-timer   write a systemd user timer (Linux) or a launchd agent
                    (macOS) that runs `xerj hub sync` every 6 hours, and print
                    its path. Nothing is enabled, loaded or started: the
                    comment at the top of the written file says how.

exit codes: 0 synced (or nothing to do); 1 the catalogue could not be refreshed
or a pack could not be updated (its old pin is kept); 2 usage error; 3 a pack
failed verification (its old pin is kept)

Verify: the last line reads hub sync: <n> updated, <n> current, <n> kept, 0 failed, <n> pruned and the exit code is 0. Run it once before using the MCP hub tools: they read the cached catalogue and never fetch one.

hub status, hub stop, hub config

$ xerj hub status
pack: lz4 0774d055  15 days old  8.1 MB  last used today
node: running  pid 2789576  port 9300
hosted: auto
last sync: today
catalogue: 0 days old
$ xerj hub stop
stopped the hub node (pid 2789576)
$ xerj hub config hosted off
hosted = off

Verify: after stop, xerj hub status shows node: not running, and a second stop prints no hub node was started by xerj; nothing to stop. stop signals only the PID xerj ask recorded; it never touches a node you started.

hub build-pack, hub build-catalogue (CI)

These two build what users download. They need the hub signing seed, so in practice they run in .github/workflows/hub-packs.yml; they are documented here so the pipeline can be reproduced with a throwaway key. build-pack clones the corpus at its manifest's pinned commit, indexes it into the node at --url (which must allow --out in [limits] snapshot_repo_allowlist), snapshots it and signs the archive. Its last stdout line is pack <slug> <pin8> <bytes> <indices> <defs>, for example pack lz4 0774d055 5810327 2 1702.

usage: xerj hub build-catalogue --registry <dir> --packs <dir> --out <file> --sign-key <seed-file>
                                  [--backlog <file>] [--release-base <url>] [--tags <tsv>]

Builds and signs the hub catalogue (writes <out> and <out>.sig) from the packs in
--packs (`<slug>-<pin8>.hubpack.tar.zst` + `.sig`, as `hub build-pack --out` writes
them). One entry per live lane-A corpus in the backlog (default
<registry>/backlog/backlog-100.json) whose current pin has a pack there.

  --release-base <url>  pack_url prefix (default
                        https://github.com/xerj-org/xerj/releases/download)
  --tags <tsv>          `slug<TAB>pin8<TAB>tag` lines naming each pack's release
                        tag (a -rN retag); default tag pack-<slug>-<pin8>

Every pack is verified (sha256, signature against the hub key this build embeds,
format, slug and pin) before it is listed; any failure aborts.

Verify: it prints catalogue <n> corpora, <bytes> bytes (…) and writes both files. The per-pack consumer check CI runs before it publishes a pack is tools/hub-packs/consumer_e2e.sh.

xerj code <corpus> --def <SYMBOL>

xerj code searches a corpus held by a node: one you built with xerj corpus, or a hub pack after xerj hub pull. With --def it looks up one definition and prints it in the same format as xerj ask --def:

usage: xerj code <corpus> --def <SYMBOL> [-k N] [--json] [--url URL]

$ xerj code lz4 --def LZ4_MAX_INPUT_SIZE --url http://127.0.0.1:9300
lz4  pin 2026-10-02 (15 days old)  mixed: BSD-2 (lib) / GPL-2.0+ (rest)  lz4/lib/lz4.h:214
#define LZ4_MAX_INPUT_SIZE        0x7E000000   /* 2 113 929 216 bytes */
value: 0x7E000000
  approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy
answered from: local pack

Verify: exit 0 and a value: line. Unlike xerj ask, xerj code never starts a node: it queries --url, else XERJ_URL, else localhost:9200. To read a pack restored into the node xerj ask started, pass that node's port from xerj hub status, as above. Pointed at a node that does not hold the pack, it prints defs query failed: … and no match in lz4, and exits 1. xerj code --help prints the options for the passage search (xerj code <corpus> "<what you need>"); xerj code --def X --help prints the usage line above.

xerj corpus add|index|list

Clones and indexes reference corpora on your machine for xerj code: xerj corpus add <name> <git-url>… (or --from <manifest file>), then xerj corpus index <name>. Use it for corpora you define yourself. For a corpus the hub already publishes, xerj ask or xerj hub pull downloads it pre-indexed instead. xerj corpus --help prints the options.

Source · engine/crates/xerj-hub/src/cli.rs · engine/crates/xerj-hub/src/sync.rs · engine/crates/xerj-hub/src/build_catalogue.rs · engine/crates/xerj-autoindex/src/xc.rs

xerj mcp

Starts a Model Context Protocol server on stdio — newline-delimited JSON-RPC 2.0 on stdin/stdout — so any MCP client (desktop assistants, IDE agents, function-calling hosts) can query XERJ without a shell. It is part of the single installed binary: if you ran the installer, you already have it.

It does not start a node. Start one first; xerj mcp proxies every tool call to the URL in XERJ_URL.

xerj --insecure --data-dir ~/xerj-data &   # 1. the node
xerj mcp                               # 2. the MCP server (your client runs this)

OPTIONS:
  --url <URL>      Base URL of the ES-compatible listener. Overrides XERJ_URL.
                   [default: http://localhost:9200]
  --auth <VALUE>   Authorization header sent verbatim on every proxied request,
                   e.g. 'ApiKey <token>'. Overrides XERJ_AUTH.
  -h, --help       Print help (includes the client config block below)
  -V, --version    Print version and exit

Client configuration — the standard mcpServers shape:

{
  "mcpServers": {
    "xerj": {
      "command": "/home/you/.local/bin/xerj",
      "args": ["mcp"],
      "env": { "XERJ_URL": "http://localhost:9200" }
    }
  }
}

Use an absolute path — the installer puts xerj in ~/.local/bin on Linux and macOS (command -v xerj confirms) and in %LOCALAPPDATA%\Programs\xerj on Windows, and an MCP host launched from a desktop icon does not inherit your shell's PATH. When the node is not running --insecure, add "XERJ_AUTH": "ApiKey <key>" next to XERJ_URL; the key lives in <data-dir>/admin.key. For a loopback node that manual step is optional: with no XERJ_AUTH given, the server reads <data-dir>/admin.key itself (the same locations xerj autoindex checks) and announces the file it used on stderr — it never does this for a non-loopback URL.

Supported agents

Twenty-six clients can register xerj mcp as a local stdio server. The entry is the absolute path to the binary plus mcp; only the file it lives in and the key wrapping it differ. One row has been run end to end (OpenCode 2.1.2 on Windows against xerj v1.0.0-rc.97, which answered initialize and tools/list with its full tool list); the rest are transcribed from each client's own documentation and are not marked as tested.

OpenCode                 | opencode.json (project) or ~/.config/opencode/opencode.json | mcp.servers       [tested]
Claude Code              | .mcp.json (project) or ~/.claude.json (user)                | mcpServers
Cursor                   | .cursor/mcp.json (project) or ~/.cursor/mcp.json            | mcpServers
Codex                    | ~/.codex/config.toml or .codex/config.toml (trusted)        | [mcp_servers.xerj]
Gemini CLI               | .gemini/settings.json or ~/.gemini/settings.json            | mcpServers
GitHub Copilot (VS Code) | .vscode/mcp.json                                            | servers
Windsurf                 | %APPDATA%\devin\mcp_config.json or ~/.config/devin/mcp_config.json | mcpServers
Claude Desktop           | %APPDATA%\Claude\claude_desktop_config.json, ~/Library/Application Support/Claude/..., ~/.config/Claude/... | mcpServers
Cline                    | ~/.cline/data/settings/cline_mcp_settings.json (CLI)        | mcpServers
Roo Code                 | .roo/mcp.json or <VS Code User>/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json | mcpServers
Kilo Code                | kilo.jsonc (project) or ~/.config/kilo/kilo.jsonc           | mcp
Continue                 | ~/.continue/config.yaml or .continue/mcpServers/*.yaml      | mcpServers
Zed                      | ~/.config/zed/settings.json                                 | context_servers
JetBrains AI Assistant   | Settings > Tools > AI Assistant > Model Context Protocol   | mcpServers
Junie                    | .junie/mcp/mcp.json or ~/.junie/mcp/mcp.json                | mcpServers
Amp                      | ~/.config/amp/settings.json or .amp/settings.json           | amp.mcpServers
Goose                    | ~/.config/goose/config.yaml or %APPDATA%\Block\goose\config\config.yaml | extensions
Kiro                     | .kiro/settings/mcp.json or ~/.kiro/settings/mcp.json        | mcpServers
Antigravity              | ~/.gemini/config/mcp_config.json or .agents/mcp_config.json | mcpServers
OpenClaw                 | ~/.openclaw/openclaw.json                                   | mcp.servers
DeepSeek Harness (dsh)   | $DSH_HOME/cordis.patch.yml or a profile patch               | insert: dsh-mcp-client
Warp                     | Settings > Agents > MCP servers, or ~/.warp/.mcp.json and .warp/.mcp.json | mcpServers
Augment Code             | ~/.augment/settings.json (Auggie CLI)                       | mcpServers
Qwen Code                | ~/.qwen/settings.json or .qwen/settings.json                | mcpServers
Kimi CLI                 | ~/.kimi-code/mcp.json or .kimi-code/mcp.json                | mcpServers
Grok CLI                 | ~/.grok/config.toml or .grok/config.toml                    | [mcp_servers.xerj]

Codex, the Grok CLI, Kilo Code, Continue, Goose, Amp, VS Code and Copilot, Zed, OpenClaw and DeepSeek Harness do not use the JSON shape above; the README carries the exact snippet for each. Aider is absent because it has no MCP client support, T3 Code hosts an MCP endpoint rather than consuming one, and Grok Bot has no documented local stdio registration.

Twenty tools are served (seventeen in a build with no hub key, which omits the three hub tools). Eleven are thin proxies over an endpoint the engine already exposes — nothing is added, and engine errors come back verbatim: xerj_search, xerj_semantic_search, xerj_vector_search, xerj_hybrid_search, xerj_memory_store, xerj_memory_recall, xerj_brain_overview, xerj_brain_ego, xerj_brain_link, xerj_brain_unlink, xerj_plan (POST /_ask). Two are not plain proxies: xerj_map returns the autoindex catalog's per-index field map (trimmed to a byte cap), and xerj_code_search runs the xerj code pipeline and returns its rendered text. xerj_field_report files an agent field report (a GitHub PR first, the /api/field-report endpoint when GitHub is unavailable; it sends by default). xerj_index_budget counts a folder and estimates its index against the node's disk budget before xerj autoindex hashes anything (it does not index), xerj_autoindex_remove drops one autoindexed folder after listing it and deletes nothing unless confirm is true, and xerj_corpus_feedback rates a hub corpus. The three Corpus Hub tools, xerj_hub_ask, xerj_hub_def and xerj_hub_find, run the xerj ask flow, but never download a pack and never fetch the catalogue: run xerj hub sync and xerj hub pull <slug> in a shell first (details on the Corpus Hub page). Two caveats the tool descriptions carry themselves: xerj_semantic_search embeds server-side with the lexical feature-hashing embedder unless the node runs --embed-mode neural, and the xerj_brain_* tools work a deterministic link index, not a graph database.

Machine-readable schemas for all twenty: /docs/agents/schemas/mcp-tools.json, generated from a live tools/list and gated against the binary in CI.

Source · engine/crates/xerj-server/src/main.rs · engine/crates/xerj-mcp/src/lib.rs