Lang on Rock
Guides

CLI reference

Every command, every query verb, and every flag.

langonrock --help
langonrock --version

Running with no arguments prints the usage and exits 1. --help prints the same thing and exits 0. Results go to stdout and everything else, statistics, diagnostics and status lines, goes to stderr, so a result can always be piped. The database commands print one line of JSON.

Commands

CommandWhat it does
compile <dir>Compile one bundle to a manifest on stdout, storing nothing
import <dir>Import a folder of bundles into a tenant, with conflict detection
sync <dir>The same command as import
put <dir>Import one directory as one bundle
watch <dir>Keep a tenant following a folder
export <dir>Write the current revision's exact Markdown to a new directory
migrate <dir>Move a legacy tenant into the engine, from its original sources
manifestPrint the stored manifest
get <id...>Fetch concepts by id
transactCommit an atomic batch of document changes
historyList retained revisions, newest first
restore <rev>Publish an older revision as a new commit
verify [rev]Check stored data, or one candidate revision
repair <rev>Make a verified revision the tenant's only retained root
gcRemove what retention no longer needs
serveRun the daemon
tokenMint a token and record its grant
query <dsn> …Any read, source or database verb over any connection string
mcp <dsn>Serve MCP over stdio

import is accepted although --help lists only its older name, sync.

compile

langonrock compile sources/acme/sales --bundle sales
# bundle: sales
id	kind	status	grain	summary	links
customers	bigquery_table	deprecated	customer_id	Registered customers, including churned.	-
orders	bigquery_table	-	order_id	One row per completed customer order.	customers

Writes the manifest of one bundle to stdout and nothing to the store, so it is the fastest way to see what the compiler makes of a folder. Diagnostics and the statistics line go to stderr. Without --bundle, the directory name is used.

import, sync and put

langonrock import sources/acme --data ./data --tenant acme
langonrock put sources/acme/sales --data ./data --tenant acme --bundle sales

import, and sync under its older name, treats every immediate subdirectory as a bundle. put imports one directory as one bundle, named by --bundle or after the directory. Each reports the resulting snapshot on stderr:

snapshot cd9078b9af0b (new), 2 bundles [ops sales], 3 concepts, 1236 bytes on disk

(reused) means the documents compiled to the snapshot that is already current; importing an unchanged folder again commits nothing. The byte count is the size of the snapshot file.

An import remembers what it last read from each folder. A file that has not changed there since never overwrites a database edit, and a document changed on both sides fails the whole import, with nothing published:

folder and database both changed sales/tables/customers.md; reconcile the source hashes before importing

--strict exits 1 when any diagnostic is reported, after the import has been committed. It changes the exit code, not what is stored. A legacy tenant is refused until it is migrated:

legacy tenant: migrate with original sources before using database transactions

See Import, watch and export.

watch

langonrock watch sources/acme --data ./data --tenant acme --debounce 500

Runs until interrupted. It prints watching sources/acme for tenant acme, then a snapshot line for each import it runs, and reports a failed import, a conflict included, as an error: line without stopping. Writes inside dot directories are ignored, because .git and .obsidian change constantly and importing on them would never stop. A full rescan every 30 seconds backs up the filesystem events. A legacy tenant is still compiled from its folder, as before.

export

langonrock export ./exported-acme --data ./data --tenant acme
{"revision":"496744db3e3c76ce67cb8ba028b15fc6558ed3f3a5f3cd1b7827c628df0aab57","destination":"/home/me/exported-acme","files":3,"bytes":1115}

Writes the current revision's documents to a new directory, exactly as stored, and refuses one that already exists:

export destination already exists; choose a new directory to preserve existing files

To export an older state, restore it first.

migrate

langonrock migrate sources/acme --data ./data --tenant acme --dry-run
langonrock migrate sources/acme --data ./data --tenant acme
{"alreadyMigrated":false,"dryRun":true,"snapshot":"cd9078b9af0b1d912a9e6e0c8c12d409be3aac3394085d56dc618458b5e23ad8","concepts":3}
{"alreadyMigrated":false,"dryRun":false,"snapshot":"cd9078b9af0b1d912a9e6e0c8c12d409be3aac3394085d56dc618458b5e23ad8","concepts":3,"revision":"bb87d48285cf586b1a4c9eee72e92e84a7427af22a001ec1406a1a810cfeb1df"}

Moves a tenant created before the engine existed into it, using the original Markdown folder. The dry run checks that the migrated tenant would compile to exactly the read model the legacy one serves, without publishing anything. Running it again after a migration reports "alreadyMigrated":true. Pass --bundle for a single-bundle source directory, and the --summary-width the legacy tenant was compiled with. See Migrating a legacy tenant.

manifest and get

langonrock manifest --data ./data --tenant acme
langonrock get orders customers --section schema --data ./data --tenant acme

get prints each concept prefixed by @@ <id>. It warns on stderr for any id it could not find, warn: no such concept or section: <id>, and exits 1 when at least one is missing.

A partial read, from --offset, --limit or a --find window, frames itself instead:

langonrock get orders --find "customer_id" --limit 120 --data ./data --tenant acme
@@ orders [158..278 of 476] 2 matches at 212 462

--find is a literal, case-insensitive phrase. The output is a window around the first occurrence and the offset of every one, which is how you jump to the others with --offset.

Both read the store directly. Use query to go through a daemon or a server.

transact, history and restore

langonrock transact --data ./data --tenant acme --from batch.json
langonrock history --data ./data --tenant acme --limit 20
langonrock restore "$REVISION" --expected-revision "$CURRENT" --data ./data --tenant acme

transact reads a JSON batch from --from, or from stdin without it, and prints the commit:

{"revision":"984e1dcb03ea2403acd84528949187909ef0e327504f03d92e00b199ba9dc9c9","snapshot":"0203add32834701b39b60d1b330ad88349a42c93e52bf29d0a660961372098ff","concepts":4,"bundles":["ops","sales"],"diagnostics":[]}

A batch whose preconditions fail publishes nothing and exits 1 with the reason, such as concept already exists: re-read the concept and retry with its new hash.

history prints a page of revisions, newest first: 20 by default, --limit up to 100. When older revisions remain, the page carries a next cursor; pass it back with --before to continue.

restore needs the revision to restore and --expected-revision, the revision you believe is current. If anything was committed since, it is refused with database changed: re-read the revision and document hashes. See Transactions and History and restore.

verify and repair

langonrock verify --data ./data --tenant acme
{"ok":true,"headHash":"6e8800ac883efc977ac044808be4286b9e45f060cb24b54c1f1d776e3ce69f4b","verifiedRevisions":["496744db3e3c76ce67cb8ba028b15fc6558ed3f3a5f3cd1b7827c628df0aab57","984e1dcb03ea2403acd84528949187909ef0e327504f03d92e00b199ba9dc9c9","7a4b86c1a08808561bab370254e5ce68157797da2e56326a3406a4b6b586075c"],"issues":[]}

verify reads the stored data without the ordinary caches and exits 1 when ok is false. Given a revision, it checks that one candidate instead.

langonrock repair "$REVISION" --expected-head "$HEAD_HASH" --data ./data --tenant acme

repair makes a verified revision the tenant's only retained root. --expected-head is the headHash that verify reported, or missing when there is no HEAD; the old HEAD is kept under recovery/. It never runs on its own. See Durability and failures.

gc

langonrock gc --data ./data --tenant acme --keep 2 --dry-run
acme: would remove 1 snapshot and 0 partials, kept 2, freed 421 bytes

Keeps the newest ten committed revisions by default, and removes every file nothing retained refers to: snapshots, source archives, revision records and import maps, all counted as snapshots in that line, plus abandoned staging files, the partials. Without --tenant it collects every tenant. On a legacy tenant it counts snapshots, honours --grace, and exits 1 when current points at a snapshot that is missing or truncated. See Collection and backups.

serve

langonrock serve --data ./data --socket /tmp/okf.sock
langonrock serve --data ./data --host 127.0.0.1 --port 7777
langonrock serving ./data on /tmp/okf.sock (0 tokens, 0 writable tenants)

Without --socket, --host or --port, the daemon listens on <data>/langonrock.sock. The last count in the banner is the number of folders it watches from sources.json, not the number of tenants it will write. The daemon commits writes to any database tenant a request is allowed to write: by its token's grant, or, with no tokens at all, by anyone who can reach the socket. See Running a server.

token

langonrock token --data ./data --tenant acme            # read-only
langonrock token --data ./data --tenant acme --write    # may write

The token goes to stdout so you can capture it into a variable. Everything else goes to stderr. A running server reads tokens.json only at startup, so restart it before a new token works. See Tokens and TLS.

mcp

langonrock mcp "okf:///abs/path/to/data?tenant=acme"
langonrock mcp "okf:///abs/path/to/data?tenant=acme" --database-tools

Speaks MCP over stdio against any connection string, with six tools by default and transact, history and restore added by --database-tools. The transport owns stdout, so diagnostics go to stderr only. See MCP server.

query

langonrock query <dsn> <verb> [args]

One command against any connection mode, so a local store and a remote server are driven the same way.

Read verbs

VerbOutput
manifestThe manifest, or one bundle with --bundle
snapshotThe current snapshot digest
search <words...>Ranked manifest rows plus a pos column; --k and --bundle apply
get <id...>Concepts; --section, --offset, --limit and --find apply

Each direct search hit ends in pos, the offset of the passage densest in the query's words. Follow it with get <id> --offset <pos> --limit 2000 to read the passage instead of the document. Linked rows, and hits whose words appear only in the manifest row, show -.

langonrock query "okf+unix:///tmp/okf.sock?tenant=acme" search order grain --k 5
langonrock query "okf+http://127.0.0.1:7777?token=$TOKEN" manifest --bundle sales

Source verbs

VerbWhat it does
sourceList documents as hash, bytes and bundle/path
read <bundle> <path>Print one document, its hash on stderr
write <bundle> <path>Commit one document, from stdin or --from
delete <bundle> <path>Commit the removal of one document
delete-bundle <bundle>Commit the removal of a whole bundle
syncReport the current commit; recompile a legacy tenant's sources
langonrock query "$DSN" source
langonrock query "$DSN" read sales tables/orders.md
langonrock query "$DSN" write sales metrics/new.md --create < new.md
langonrock query "$DSN" write sales tables/orders.md --replaces "$HASH" < edited.md
langonrock query "$DSN" delete sales tables/old.md --force
langonrock query "$DSN" sync

source prints one line per document, the hash first:

b8475eb207b4239c0565fe75e0e95cd3d79732569a3c4ee4804e00452cd57f97	212	ops/runbooks/deploy.md
dce62d2e17f8bbf981c4e8bb419ab98d6db77fe322cf9b9ecc6dae2d90c41632	317	sales/tables/customers.md
3b3807a007b08a92991718ba790e3ff312793710a8d9ea141c7f19633deb5750	586	sales/tables/orders.md

Each write, delete and delete-bundle is its own commit, visible to the next read. write confirms on stderr with the new hash:

wrote sales/metrics/new.md b8475eb207b4239c0565fe75e0e95cd3d79732569a3c4ee4804e00452cd57f97

sync on a database tenant only reports the current state, without creating a revision:

snapshot f55e4feb8df8, 2 bundles, 4 concepts

On a legacy tenant whose folder is registered in sources.json, the source verbs edit that folder instead, and sync recompiles it.

A change that names no version is refused before anything is read, and one that names a stale version is refused by the database:

a write needs --replaces <hash> to update, --create for a new concept, or --force to overwrite whatever is there
concept changed since it was read: re-read the concept and retry with its new hash

A write must name the version it replaces

--create, --replaces <hash> and --force are the command-line spelling of the same precondition, and there is no default. A write or delete with none of them is refused, and so is a stale hash, each with a message that says what to do next. --force reads the current hash and writes against it, which is the honest name for taking whatever is there right now. See Editing over the network.

Database verbs

VerbWhat it does
transactCommit a JSON batch from stdin or --from
historyA page of revisions; --limit and --before apply
restore <rev>Restore with --expected-revision

They print the same JSON as the local commands, through any connection, so a remote tenant is committed to, paged and restored exactly like a local one.

Flags

Store and tenant

FlagDefault
--data <dir>$LANGONROCK_DATA, else the platform data directory
--tenant <id>required by most commands, up to 64 characters

Reading

FlagDefaultApplies to
--section <name>noneget
--offset <n>0get
--limit <n>none for get, 20 for historyget, history
--find <text>noneget
--k <n>8search
--bundle <name>the directory name for compile and put, no filter elsewherecompile, put, migrate, and query's manifest and search
--out <file>stdoutanything that prints a result

Compiling

FlagDefaultEffect
--summary-width <n>120Maximum characters per summary cell
--strictoffExit non-zero when any diagnostic is reported

Database

FlagEffect
--from <file>The batch for transact, or the content for write
--before <cursor>Continue a history page from its next cursor
--expected-revision <revision>restore only if this revision is still current
--expected-head <hash|missing>repair only if HEAD still has the hash verify reported
--dry-runValidate a migrate, or preview a gc, without publishing

Serving

FlagDefault
--socket <path><data>/langonrock.sock
--host <name>127.0.0.1 when TCP
--port <n>7777
--tls-cert <file>none, required off loopback
--tls-key <file>none, required with --tls-cert
--watch <dir>none, also import this folder into --tenant
--debounce <ms>200
--rescan <ms>30000

--tls-cert and --tls-key must be given together, and neither works with a unix socket.

Writing source

FlagEffect
--from <file>Read the content from a file instead of stdin
--replaces <hash>The hash this change is based on
--createThe document must not exist yet
--forceUse whatever hash is there right now

Tokens and MCP

FlagEffect
--writeThe token may write
--database-toolsmcp also exposes transact, history, restore

Collection

FlagDefaultEffect
--keep <n>10Revisions to retain per tenant; snapshots on a legacy one
--grace <ms>3600000Legacy tenants only: never collect anything newer
--dry-runoffReport what would be removed

Exit codes

CodeMeaning
0Success
1An error, with its message on stderr; a missing concept in get; ok: false from verify; a corrupt legacy current in gc; any diagnostic under --strict

On this page