New here? tutorial.md walks through a server, two clients, and a round trip with real output.
scribe [global options] <command> [options] [paths]Every command finds its workspace by walking up from the current directory. Paths may be relative to the current directory or absolute; they must lie inside the workspace.
Global options
| option | meaning |
|---|---|
--server URL | tcp://host:port or tls://host:port. Precedence: this flag, SCRIBE_SERVER, the workspace's recorded server, tcp://127.0.0.1:7447 |
--token-file FILE | personal access token for a tls:// server (or SCRIBE_TOKEN_FILE); never stored in the workspace |
--trust-fingerprint BLAKE3:… | approve a not-yet-trusted server only if its CA fingerprint matches; required for first use in scripts |
--tls-ca FILE | verify the server against this CA instead of remembered trust (or SCRIBE_TLS_CA) |
--config-dir DIR | per-user configuration root holding remembered trust (or SCRIBE_CONFIG_DIR) |
--json | one JSON object on stdout; diagnostics stay on stderr |
--global-ignore FILE | lowest-precedence ignore rules |
--log-level LEVEL, --log-filter FILTER, --log-format text|json, -v, -vv, -q | diagnostics on stderr; default warn; SCRIBE_LOG holds a filter |
Progress is shown only when stderr is a terminal and neither --json nor
-q is given. Colors follow the terminal; NO_COLOR=1 or TERM=dumb
disables them, and redirected output is always plain.
Exit codes
| code | meaning |
|---|---|
| 0 | success |
| 1 | user or semantic error: usage, not found, conflict, rejected submit, permission denied |
| 2 | refused to destroy local data |
| 3 | submit outcome unknown; the candidate is kept, run scribe submit again |
| 4 | integrity failure (a digest did not match) |
| 5 | transport or storage failure |
Connecting to a TLS server
scribe --server tls://vcs.example:7447 --token-file ~/.scribe-token repo listThe first connection to a server shows the fingerprint of its certificate
authority and asks for yes. Compare it with the value your administrator
gave you; the token is sent only after that trust is saved. Later
connections verify against the saved CA and need no prompt. A server whose
CA changed is refused until the old trust is forgotten explicitly.
scribe --server tls://HOST:PORT trust add [--trust-fingerprint BLAKE3:…] # approve without sending a token
scribe --server tls://HOST:PORT trust show # offline
scribe --server tls://HOST:PORT trust forget --trust-fingerprint BLAKE3:… # must match the saved valueRemembered trust lives outside every workspace: ~/Library/Application Support/Scribe/trust on macOS, %LOCALAPPDATA%\Scribe\trust on Windows,
$XDG_CONFIG_HOME/scribe/trust (or ~/.config/scribe/trust) on Linux.
Scripts and --json never prompt: pass --trust-fingerprint or approve
first with trust add. A tcp:// server accepts no token and no trust
options. Tokens, roles, and certificates are explained in auth.md.
Repositories
scribe repo create <name> # letters, digits, '-', '_', '.'; not starting with '.'; ≤ 128 bytes
scribe repo list # only repositories you may read on an authenticated server
scribe repo info <name|id>Creating a repository on an authenticated server requires namespace administration.
Workspaces
scribe workspace create <repo> <directory> [--view PREFIX]... [--author NAME] [--readonly]
scribe workspace infoThe directory must not already contain a workspace. --view fixes inclusion
prefixes (for example --view Assets/Characters --view Source); paths outside
the view are neither synced nor addable, and changing a view means a new
workspace. --author defaults to SCRIBE_AUTHOR, then the OS user name; on
an authenticated server the token's user is the recorded author regardless.
--readonly marks unopened tracked files read-only: edit clears the
attribute; revert, sync, and submit restore it.
Opening files
scribe add [--force] <path|dir>... # new files; directories recurse with ignore rules
scribe edit <path>... # tracked files you will modify
scribe delete [--force] <path>... # tracked files; removes the local file
scribe revert <path>... # restore the recorded base; reverting an add keeps the fileadd --force overrides ignore rules only: it never adds links, names
Windows cannot represent, anything under .scribe/, or files outside the
workspace. delete refuses a locally modified file unless --force, which
moves the content to .scribe/recovery/ first. revert of a modified edit
keeps the modified content there too.
Status and scanning
scribe status # opened files only (fast: size + mtime fingerprints)
scribe status --scan # also tracked-but-unopened modifications and deletions, and untracked files
scribe status --verify # hash every tracked file instead of trusting fingerprints
scribe scan [--verify] # same as status --scan
scribe ignore explain <path> # the deciding rule file, line, and pattern, or the excluded parentFingerprints are caches; --verify is the proof. Ignore rules never hide
tracked changes. status also lists unresolved merge or stale-file conflicts.
Submitting
scribe submit -m "message"A submit is the whole of "commit and push": it chunks and hashes every opened add and edit, persists the candidate with its operation id, uploads only chunks the server lacks together with manifests and the change set, and finalizes local baselines after the server's durable acknowledgement. A file that changes while it is read or uploaded is refused.
- accepted: prints the change number, file count, chunks sent, bytes on the wire versus logical bytes, and server flush count;
- rejected (
base_mismatch,locked_by_other,path_collision, …): nothing changes locally. For stale opened files runscribe resolve --update, choose or edit each resolution, and submit again; - unknown (connection lost): exit 3. The candidate is kept; the next
scribe submitfirst asks the server what happened and never creates a duplicate.
Syncing and exporting
scribe sync # to head
scribe sync --revision <change> # to a pinned change; an older target is a full reconcile
scribe export --revision <change> --to <empty dir> [--repo NAME] [--view PREFIX]...Sync writes a journal before touching files, applies it in batches, reuses
unchanged chunks from the local copy and the cache, verifies every
reconstructed file's content id, and advances the watermark only when
everything is applied. Every local file about to be replaced is moved to
.scribe/recovery/ first, modified or not, and the report names each
location; that directory is yours to empty. Opened paths are skipped and
listed as deferred; they update on revert or on the next sync after they
are closed. Files another program holds open (Windows sharing violations)
are reported and retried on the next run, which resumes from the journal.
export writes a pinned tree into an empty directory with no workspace
state.
Branches
scribe branch list
scribe branch create <name> [--from BRANCH] [--revision CHANGE] # default: the workspace branch's head
scribe branch rename <old> <new> # id, history, bindings, and locks unchanged
scribe branch retry # finish a branch operation whose reply was lost
scribe switch <branch>Branches have stable ids, so renaming changes nothing but the name. main
cannot be renamed and no branch can be deleted. Creation copies metadata
only and shares every asset object. switch requires no opened files, no
pending submit or resolution, and no held locks; it persists its intent
first and resumes after an interruption. Change numbers are one sequence per
repository across all branches.
Merging and resolving
scribe merge <source> [--revision CHANGE] [--base CHANGE] # prepare a three-way merge into the workspace branch
scribe status # unresolved paths
scribe resolve # list them
scribe resolve --export <empty dir> # base/, ours/, theirs/ for an external tool
scribe resolve --ours|--theirs <path>... | --all # pick a side
scribe resolve --working <path>... # accept the edited working file
scribe submit -m "Merge source" # the merge becomes history
scribe merge --continue # resume after an interruption
scribe merge --abort # give up; displaced files go to .scribe/recovery
scribe resolve --update # refresh stale opened files against head, then resubmitNon-overlapping text edits merge automatically (UTF-8, no NUL, at most
4 MiB per input). Binary, add/add, delete/edit, mode, and path-shape
conflicts need a decision. ours is the workspace branch, theirs the
source branch or, for stale files, the server. Merge needs a full-view
workspace with no opened files. If the target branch moves before you
submit, run scribe resolve --update. Limits and measured costs are in
branches.md.
History, info, locks
scribe log [path] [--limit N] [--before CHANGE]
scribe info <path> # base, local state, head revision, lock holder
scribe lock <path>... # exclusive, repository-wide across branches, held until unlock
scribe unlock [--force] <path>...
scribe locks [--prefix DIR]A submit touching a path locked by another workspace is rejected. unlock
releases only this workspace's acquisition; --force releases someone
else's, and on an authenticated server needs the maintainer role.
Ignore files
.scribeignore files are versioned and nest; .scribe/ignore is local; a
--global-ignore file has the lowest precedence. Git-style syntax; deeper
files win; the last match in a file wins; an excluded directory is not
entered. A malformed rule stops discovery rather than being skipped. Details
in ignore-and-paths.md.
Typical day
scribe workspace create game ~/game --view Assets --view Source --author ann
cd ~/game && scribe sync
scribe edit Assets/Hero.uasset && scribe lock Assets/Hero.uasset
# ... work ...
scribe status
scribe submit -m "hero rig update"
scribe unlock Assets/Hero.uasset
scribe syncCompression
Repository storage and connection compression are independent. Both default
to none. Choose the permanent repository storage policy at creation:
scribe repo create game --storage-compression lz4
scribe --transfer-compression lz4 workspace create game ./gameThe workspace creation command saves that transfer preference. Change it later from inside the workspace:
scribe workspace config --compression none
scribe workspace config --compression lz4scribe workspace info reports the saved preference. A global
--transfer-compression none|lz4 overrides it for one command, for example
scribe --transfer-compression none sync; it does not change the saved value.
scribe repo info game reports the repository's immutable storage policy.
There is no API or CLI to change that policy after creation.
LZ4 only compresses object data, leaving control messages unchanged. Objects that do not shrink remain raw on disk; transfers similarly retain raw blocks when compression does not help. Compression does not change chunk IDs, manifest IDs, deduplication, or accepted history. Working-copy files remain ordinary uncompressed files. The workspace object cache uses raw storage.
Submit and sync JSON include protocol_bytes_sent and protocol_bytes_received
for that connection (handshake, metadata frames, and encoded object data; TLS
and TCP overhead excluded). Existing bytes_sent/bytes_fetched workload
counters count original object bytes and should not be used to measure wire
compression. node tools/scenario.js compares all four storage/transfer settings
on identical repetitive and random inputs, reporting submit/sync wall times,
logical throughput, protocol byte counts, and physical object-file sizes.
Interactive login
scribe --server tls://storage:7447 login alice --auth tls://auth:7443The client displays Password: on stderr and hides typed input. Enter submits
it; Ctrl+C cancels and restores terminal settings. The resulting session is
saved and reused by later commands. Passwords are never saved in session state.
Use --password-file FILE for automation; without it, redirected stdin is
refused. Initial TLS trust still requires approval (or a supplied --auth-ca).
An optional scribe-gateway can provide one client endpoint for several existing storage instances. Standalone operation remains supported.