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