Repository navigation
CLI JSON
--json anywhere on the barocss-hub command line switches the output to machine-readable JSON. It is the interface for tools, starting with the VS Code extension (#38). Code: crates/hub-cli/src/json.rs. Golden documents: crates/hub-cli/tests/golden/json/ (fixed replica id …abc); contract tests: crates/hub-cli/tests/json.rs, editor.rs.
Status: [decision-proposal] (#37). Human output is unchanged by this page.
-
stdout: exactly one JSON document, one line, ending in
\n(watch --json: one event per line, JSON Lines). Nothing else is ever written to stdout under--json. -
Document shape: an object whose first keys are
"schema": 1and"command": "<verb>", and whose last key is"warnings": an array of strings (the text of eachwarning:line the command produced, e.g. torn-tail recovery, skipped symlinks, an ignored bad snapshot). Key order is fixed (golden tests compare bytes). -
Errors: stdout is empty; stderr holds one document and the exit status is 1:
{"schema":1,"program":"barocss-hub","error":{"code":"<code>","message":"<human text>"},"warnings":[...]}. An error the remote end of a sync reported in a wireErrorframe adds"remote": trueaftermessage(#43, Remote-Sync §5.2.6).codeis chosen where the error is raised (hub_cli::Code, #37 repair), never derived from the message;messageis human text and may change. One golden per code:crates/hub-cli/tests/golden/json/code-<code>.json.code meaning / what a tool does usagebad arguments, unknown command not-a-repositoryno .hub/here or above (also a remote path that is not a repository)already-existsinitin a repository;mkdir/touch/mvonto an existing pathinvalid-path.., empty or invalid name, moving/removing the rootno-such-pathno node at the path; missing directory argument not-a-file/not-a-directorywrong node kind bad-versionversion id not 4-64 hex digits not-foundwell-formed version id, checkpoint or peer that matches nothing ambiguousa prefix or checkpoint message matching several no-such-blob/bad-blob-idblob not stored / not 64 hex lockedanother watcher (or lock holder) is running behindthe working directory is behind head: run checkoutpinnedthe working directory is pinned to an old version: checkoutto return (see the pinned way out)interrupteda checkout was interrupted: run checkoutto finish itblockedpaths collide with unmaterialized case-clashing paths (D-019) refusedthe working-directory update would clobber unrecorded or ignored data duplicate-replicasync aborted: a copied .hub/(D-022): runreidcorrupta damaged .hub/file, blob or unsupported format; also a.hub/remoteswhose name, url or exec fails the #45 safety rules (messagerefusing …: remote X is unsafe: …; nothing was run)ioi/o failure (also: the current directory cannot be read, exit 2; a transport failure: ssh exited, connection closed, with the ssh stderr tail) protocolwire protocol violation: no BHUBWIREpreamble (not barocss-hub, or a login banner), malformed or unexpected frame (#43)version-mismatchno common wire protocol version with the remote (both ranges in the message) limita wire limit was exceeded (frame, chunk, list, blob size, or an existing record larger than the remote accepts; the message names the op); also (#54) an op this replica authors whose encoded record exceeds TEXT_OP_MAX(192 KiB): refused before anything is written (Text-CRDT 7.4.1 chained saves)timeoutthe remote sent nothing within the step timeout (60 s, Hello15 s) or the server session wall time (1 h) ran outunsupporteda scheme or feature that is not implemented or not negotiated ( https://remotes,RefMoves)forbiddenrefused by the server: path confinement ( --root,..,~user, symlink escape), a bad forced command, or a write to aserve --read-onlyserverbarerecord,checkout,mkdir,touch,mv,rm,watchin a bare repository (D-042)untrusted-remotefirst contact with a remote (ssh or file://) from.hub/remotesthat this user never added or trusted from this repository (the key includes the local repository; migrated peers start untrusted), with no terminal to ask (or--json) and no--yes(#45, Remote-Sync §5.9.7); fix: check it, thenremote trust <name>orsync --yes <name>upgrade-required(#54, D-053 E-T6) sync stopped: the peer lacks wire feature textand the ops it is missing (pull) or would receive (push, mixed remotes) include text edits; fatal for the session, nothing persisted; fix: upgrade barocss-hub on the side that lacks it (Remote-Sync 5.2.3, Text-CRDT 7.9)erroranything else (internal) -
Exit status (human and JSON):
0success;1any command error;2the current directory cannot be read;3syncfinished but the remote rejected some of this repository's ops that weren't reported before (#50) (D-045, #45): stdout carries the normal document (withrejectednon-empty) and stderr no error document.watchnever exits 3: it emitsrejectedevents. A closed stdout (EPIPE) exits 0 quietly. -
Human mode errors are
barocss-hub: <message>on stderr (washub:). -
Encoding: RFC 8259. Strings escape
"\and every control character (U+0000–U+001F, U+007F) plus U+2028/U+2029; non-ASCII is raw UTF-8. Paths are UTF-8 (the scanner skips non-UTF-8 names with a warning). No dependency (hand-rolled writer, tested for control characters and non-ASCII). -
Stability: schema 1 may gain keys (and error codes); existing keys keep their names and meaning. Consumers must ignore unknown keys. A breaking change bumps
schema. -
Caveat: the flag is the exact argument
--json, removed wherever it appears; a commit message that is literally--jsoncannot be given.
| value | form |
|---|---|
| node id / op id |
"<counter>.<32 hex replica>", e.g. "2.00000000000000000000000000000abc" (root: "0.000…0") |
| version |
{"id": "<64 hex>", "short": "<shortest unique prefix, ≥ 8>"}; short is what show, cat, checkout accept |
| blob id |
"<64 hex>" (SHA-256 of the bytes; the empty file's blob is e3b0…b855) |
| replica / peer / author | "<32 hex>" |
| path | root-relative, /-separated, root = ""
|
-
entry:
{"path", "kind": "file"|"dir", "node", "blob": <blob id>|null}(blobnull for dirs). -
change:
{"kind": "added"|"modified"|"deleted"|"renamed", "path", "from": <old path>|null, "node": <id>|null, "dir": bool}. Forrenamed,pathis the new path andfromthe old one.nodeis null only for an added path not yet recorded (status). -
checkpoint:
{"version", "message", "published": bool, "author"}. -
conflict (one of, by
kind;pathis null when the node is trashed):-
name-collision:node,path(projected, suffixed),name(original),winner(node that kept the name); -
content:node,path,winner,loser(op ids),winner_blob,loser_blob(fetch both withcat-blob); -
cycle,same-node-move:node,path,winner,loser; -
revived:node,path,delete,move; -
edit-revived:node,path,edit,delete; -
invalid-op:op.
-
-
sync warning:
{"reason": "equivocation"|"lamport-violation"|"corrupt-blob"|"duplicate-replica", "peer", "author"|null, "op"|null, "detail", "kept"|null, "received"|null, "action", "stuck": [op ids], "first_seen", "text"}(text= the human line). #45:textis a sentence saying who (author replica, peer), which op, what differs and what to do, never RustDebugtext;kept/receivedare human descriptions of the two payloads of an equivocation (e.g.create file "a" in 0.000…), null for other reasons and for warnings stored before #45;actionis what to do;detailis the stored detail (#45 equivocations:kept: …\nreceived: …; Lamport:dep <id> is not older than the op <id> (Lamport rule)). -
rejected op (#45):
{"op", "reason": "equivocation"|"lamport-violation", "message"}(message= what it means and what to do).
| command | keys after schema, command (then warnings) |
|---|---|
status [--ignored] |
root; head {version, ops, checkpoints, published}; workdir {state, version, behind_ops, pinned, interrupted_checkout, clean}; watch {running, pid}; conflicts; sync_warnings; pending_ops; remotes ([{name, url, default}], #43; replaced peers, D-041); changes; blocked ([{path, with: [{path, node}]}], D-019); unmaterialized (paths); tracked_but_ignored (paths); ignored ([{path, dir, default}], top-most of each ignored subtree, default = hidden by a built-in default; always present, --ignored changes nothing); hidden_by_defaults (count) |
status in a bare repository |
root; bare: true; head; conflicts; sync_warnings; pending_ops; remotes (no workdir, watch, changes or ignore keys) |
sync [--yes] [<remote|url>] |
remote (the path or URL synced with), received {ops, blobs, checkpoints}, sent {…}, read_only (bool: the server announced read-only, pulled only), head (version), pending_ops, rejected ([rejected op]: our pushed ops the remote rejected, #45), rejected_reported (bool: the remote speaks rejected-ops; false for a pre-#45 server, which cannot report), output (the human lines) — #43. Under --json an untrusted remote (ssh or file://, D-047) is never asked about: untrusted-remote unless --yes. Each rejected entry also carries already_reported (bool, #50/D-050). Exit 3 when rejected has an entry with already_reported: false (D-045). If all of them are already reported, it exits 0. |
ls [-l] [path] |
path (the listed dir), entries (every entry below it, recursively, full paths, sorted) |
show [-l] <version> |
version, messages (checkpoint messages on it), entries
|
log |
checkpoints (newest first; local then published-only, as the human log) |
log --path <path> |
path, node, kind, blob (head content), checkpoints (each + path and blob of that node in that version; only versions where the node is live), ops (every op on the node, newest first: `{op, kind: "create" |
warnings |
sync_warnings, pending ([{op, peer}]), reported_rejections ([{op, reason, payload_sha256, remote, exec}]: rejections already reported once, not warned again, #50) |
warnings --clear-reported [<remote>] |
cleared_reported (count forgotten; they're reported again on the next sync, #50) |
cat <version> <path> |
version, path, node, blob, size, encoding: "base64", content
|
cat-blob <blob id> |
blob, size, encoding, content
|
record [-m <msg>] |
recorded (bool), changes (with the new nodes of added paths), head, checkpoint (null without -m) |
checkout [--force] [<version>] |
version, written, removed, at_head
|
commit -m <msg> |
checkpoint |
reid --rescue [--dry-run] [--offline] |
dry_run (bool), old_replica, new_replica (null in a dry run); a no-op is {dry_run, noop: true, rescue: [], next: null}, rescue ([op]: the removed ops), files ([{path, change: "modified"|"added"|"deleted"}], the working-tree files that differ from the server-side version), backup (path of .hub/rescue/<ts>/files; in a dry run of a fresh plan the parent .hub/rescue, since the directory does not exist yet; null if nothing is backed up), recorded (changes as record; absent in a dry run), checkpoints ([{message, author, published}]: checkpoint records naming the removed ops, re-created privately, #49 R1), resumed (bool), noop (bool), sync (null when not run; else {ok, exit, rejected, message}), checkout (null when no sync ran; else {done, reason} or {done: true, version, written, removed}: the automatic checkout after an online rescue, only if the working tree was unchanged), restored_checkpoints ([{message, short, was_published}]), next (a human hint, e.g. "run barocss-hub sync") (#49) |
help / --version
|
text / version (command is "help" / "version") |
any other (init, mkdir, touch, mv, rm, publish, remote, plain reid) |
output: the human lines, as an array (content not stable) |
workdir.state: "at-head" (the working directory is head), "behind" (head has behind_ops ops the working directory lacks; after a sync; checkout fixes it), "pinned" (checkout <old id>; pinned holds that version; the watcher pauses), "unknown" (the index base does not resolve). interrupted_checkout true means run checkout.
Without --json, cat and cat-blob write the raw bytes to stdout (the easiest way for a tool to read a file version); errors are then human text.
One object per line: {"schema":1,"event":"<name>",...}. stderr stays empty unless the watcher fails to start (then the error document, exit 1).
| event | keys |
|---|---|
started |
root, pid, remotes (count; was peers before #43, D-041), sync (bool; false under watch --no-sync, #45) |
recorded |
changes, head
|
checked-out |
version, written, removed, at_head, reason: "incoming" (ops from a peer applied) or "resume-interrupted"
|
synced |
peer (the remote's name), received {ops, blobs, checkpoints}, sent {…}, read_only (only syncs that moved something) |
deferred |
paths: tracked files missing while their editor sidecar exists (save in progress, Repository-Semantics §1.6.1) |
paused |
pinned (version) — once per pause |
resumed |
— |
warning |
message |
untrusted-remote |
peer, url, message — an ssh remote this user never trusted is skipped (not contacted); once per remote per run; not an error, no backoff (#45) |
rejected |
peer, ops ([rejected op]) — the remote rejected pushed ops (#45); only rejections not reported before (#50) |
error |
stage (auto-record, apply, sync), message, peer and code (sync; the remote's name and the error code) — the watcher keeps running; a failing remote backs off (up to 32 sync intervals) |
stopped |
— (SIGINT/SIGTERM, exit 0) |
- Poll
status --jsonfor the status bar:workdir.state/behind_ops,watch.running/watch.pid,conflicts(withwinner_blob/loser_blobfor a diff),blocked,unmaterialized,warnings. - Timeline for a file:
log --json --path <p>(checkpoints + everyset-contentop with its blob), thencat-blob <blob>(raw) for a version's bytes, orcat <version> <p>. - Run the watcher as a child with
watch --jsonand read events. - Every error is
error.codeon stderr with exit 1.