Skip to main content

CLI reference

gnarl <command> [subcommand] [flags]

The command is gnarl. The things with a user interface — the desktop app, the web Console, and Gnarly Edge on phones — are Gnarly. Same node either way: the executable's name decides which it presents as.

Global flags​

FlagDefaultDescription
--output <fmt>texttext, json, or ndjson. Global on purpose: an agent driving this CLI asks for JSON once and every command answers the same way.
-V, --versionPrint the version. Each binary names itself.
-h, --helpHelp for any command or subcommand.

Commands that talk to a running node also take --port <PORT> (default 8080) and --no-tls. There is no config file — everything else is a flag on start, listed in the configuration reference.

Running a node​

gnarl start [--port 8080] [--data-dir <path>] [--desktop] [--headless]
gnarl status # node ID, peers, claims
gnarl explain # ownership, readiness, routing policy
gnarl peers # known peers with health and RTT
gnarl stats # indexing, storage, proof, query counters
gnarl contribution # claims, bytes, queries served
gnarl inspect # explain + stats + peers in one view
gnarl reset # wipe the data directory and regenerate identity

start runs in the foreground; Ctrl+C stops it.

reset regenerates your identity

A node's identity is its keypair. Resetting means peers that trusted the old node ID do not follow it to the new one.

Joining a mesh​

A fresh node is private and announces nothing. Joining is deliberate, and it is how two machines share indexes and agent memory.

gnarl mesh setup --mesh-name my-mesh --with-invite
gnarl mesh invite --mesh-name my-mesh --admit-peer <their-node-id>
gnarl mesh join --invite 'lucenia1.…'

setup creates the profile and prints the gnarl start … line to run. invite emits a token pinned to one node ID. join decodes a token and prints the exact start command. Confirm with gnarl status — you want peers > 0.

A mesh name is a partition, not a fortress

--mesh-name separates traffic; it does not authenticate anyone. Pin node IDs with --admit-peer.

Agent memory​

gnarl memory remember "<fact>"
gnarl memory recall "<query>"
gnarl memory setup | invite | join # aliases of `gnarl mesh …`

The same tools are exposed over MCP at http://localhost:8080/mcp — see Agent memory.

MCP clients​

gnarl mcp install --client claude # or cursor, or codex
gnarl mcp install --client cursor --print # show the block, change nothing

Writes the MCP server entry into the client's configuration, backing the file up first and preserving every other key in it. Codex is always printed rather than written, because merging into a TOML file we do not own would reformat it.

FlagMeaning
--clientclaude, cursor, or codex
--profiletool surface to expose (default gnarl-memory)
--portnode port (default 8080)
--no-tlsthe node is serving plain HTTP
--printprint the block instead of editing the file

The endpoint is always 127.0.0.1. /mcp answers the local machine only.

Indexes​

gnarl index create <name> --field body:text --field tag:keyword
gnarl index list
gnarl index show <name> # declared fields
gnarl index count <name> # document count
gnarl index policy <name> # where this index's data may go
gnarl index delete <name>

--field is repeatable and takes NAME:TYPE. The CLI builds the request body for you; a malformed pair is refused locally rather than by the node.

Placement​

gnarl index policy notes # show
gnarl index policy notes --placement local # never leaves this machine
gnarl index policy crawl --placement public --replicas 6
PlacementWho may hold, read, or learn of it
localnobody but this node
meshpeers of your own mesh — the default
publicany peer, including strangers

Only the node that created an index may change its placement; any other answers 403. Narrowing takes effect on the next anti-entropy cycle and drops replicas already held. Omitted flags are left alone, so a partial change cannot widen anything by accident.

See Data placement for what mesh means on a public node — it is the setting that lets one node serve the commons while keeping its memory private.

Documents​

Documents are written over HTTP — there is no gnarl verb for a single document yet:

curl -X POST http://localhost:8080/v1/indexes/notes/_doc \
-H 'content-type: application/json' \
-d '{"body": "decentralized search across distance"}'

Searching​

gnarl search <query> [flags]
FlagDefaultDescription
--index <name>allRestrict to one index
--limit <n>10Hits to return
--explainoffPrint the plan and timings
--require-completeoffFail rather than return a partial answer
gnarl search "glacier retreat" --index imagery --limit 25 --explain

--require-complete matters on a mesh: an index is spread across claims, and a claim whose holder is asleep simply does not answer. Without it you get what was available, with coverage reported.

Manifests, peers, identity​

gnarl manifest list
gnarl manifest show <index>
gnarl peer list
gnarl peer show <node-id>
gnarl identity show

Backup and restore​

gnarl repo add <name> --path <dir>
gnarl repo add <name> --endpoint <url> --bucket <b> --region <r> \
--access-key-id <id> --secret-access-key <secret> [--prefix <p>] [--virtual-host]
gnarl repo list
gnarl repo remove <name>

gnarl snapshot create <repo> <snapshot> [--index <name>] [--namespace <ns>]
gnarl snapshot list <repo>
gnarl snapshot show <repo> <snapshot> # includes whether the signature verifies
gnarl snapshot restore <repo> <snapshot>
gnarl snapshot delete <repo> <snapshot> # descriptor only; cleanup reclaims bytes
gnarl snapshot cleanup <repo> [--grace-seconds <n>]
gnarl snapshot jobs [--id <job-id>]

Long operations wait by default. --no-wait returns the job id instead.

Four flags are omitted above because they need their own explanation rather than a line in a list — --encryption-key (unrecoverable if lost), --allow-overwrite-live-index (destroys everything written since the snapshot), --allow-unverified-signer (accepts a backup this node cannot attribute to your fleet) and --allow-shared-pool (captures every tenant in a pool). See Backup and restore.

Access control​

gnarl rbac issue --user <name> [--role reader|writer|admin] [--ttl <seconds>]
FlagDefaultDescription
--user <name>requiredSubject the token is minted for
--role <role>readerreader, writer, or admin
--ttl <seconds>86400Token lifetime
--data-dir <path>~/.luceniaWhere the issuer key lives
--issuer-key <path><data-dir>/rbac/issuer_ed25519.binIssuer key to sign with
RBAC is private-mesh only. A private mesh enables it by default; a public mesh
stays open by design.

Demo​

gnarl demo # golden scenario, end to end

Exit codes​

CodeMeaning
0Success
1Failure
2Bad usage — an unknown flag or malformed argument
3Coverage was incomplete and --require-complete was set

Codes 4 (verification failed) and 5 (refused by policy) are reserved and not emitted yet: both conditions exit 1 today.