CLI

Every subcommand and alias, how a notebook path resolves, help and exit behaviour, and the exact grammar of the success and error reports, their trailers, and what a terminal adds.

slivingdoc is a subcommand CLI. The subcommand comes first, before any flag.

Subcommands

Command Alias Effect
serve s Serve the notebook over MCP stdio. This is the server.
pull p Write the current notebook into a directory and exit.
commit c Publish the changes at a directory (-m <message>) and exit.
login Log in to hosted storage through the browser.
logout Revoke and remove the stored login.
space List the login’s spaces, create one, or set the default.
version v Print slivingdoc <semver> and exit, touching nothing else.

help is not a command with an effect of its own: slivingdoc help prints the usage, and slivingdoc help <command> prints that command’s help. slivingdoc -h and --help print the usage too. A missing command shows the usage, or on a terminal a home screen, and exits 1. A command line of flags only (slivingdoc --foo) first prints the line error: give a command before --foo on stderr, then does the same; an unknown command prints its usage as well and exits 1. No server starts in either case.

serve, pull, and commit read the same flags and environment variables. -h on any of them prints that command’s own help, followed by the shared flag and environment reference. commit adds the required -m/--message flag. See Configuration for the full flag table.

slivingdoc version writes slivingdoc <semver> and one LF to stdout and exits zero without resolving configuration or loading the native and S3 dependencies. -h on any command follows the same rule.

Account commands

login, logout, and space manage the stored login that hosted storage can use instead of SLIVINGDOC_TOKEN. They need slivingdoc 0.2.4 or newer. How the storage is then chosen is in Configuration.

  • slivingdoc login prints an approval page and a code, opens the page in a browser, and waits while you sign in and approve. It stores the account’s key in credentials.json and prints one line on stdout, Logged in as <account> (<access>) until <date>. The line names the endpoint after the account when it is not the default, says with no expiry when the key has none, and ends with the default space, if the login has one. Before it stores anything it shows who approved, the storage endpoint, the site, and the spaces the login reaches; on a terminal it asks Store this login? [y/N] and revokes the key unless you answer y. Flags: --space (or --bucket) makes a space the default, --read-only asks for a read-only login, --site (or SLIVINGDOC_SITE) names the site, --no-browser prints the page without opening one, and --force replaces another account’s login when there is no terminal to ask.

  • slivingdoc space lists the spaces the login reaches on stdout, one per line as <name> (<access>), owned by <owner> (a team when no person owns it), with * marking the default. slivingdoc space <name> checks that the login reaches the space and stores it as the default, which serve, pull, and commit use when neither --space nor SLIVINGDOC_SPACE is given. --endpoint (or SLIVINGDOC_ENDPOINT) picks the login when several are stored. slivingdoc space new <name> creates one space for the login’s account and prints it on two lines:

    Created hosted space research (write, ada@example.com) at https://api.slivingdoc.dev.
    It is not the default; run 'slivingdoc space research' to make it the default.

    The name is 3 to 63 lowercase letters, digits, and inner hyphens, with a letter or digit at each end, and must not be a name S3 reserves; anything else is refused before the site is called, with nothing created. A read-only login creates nothing, and the create needs a free space in the plan. See Use hosted storage.

  • slivingdoc logout revokes each stored login key at its site, which also revokes every token minted from it, and removes it and its default space. --site (or SLIVINGDOC_SITE) limits it to logins that site issued. A key whose revocation fails stays stored, and the command exits 1, so it can be repeated.

Arguments and path resolution

pull and commit take at most one positional notebook path, which may precede or follow the flags. Resolution is:

  1. An omitted path uses the workspace root, which is the working directory unless --workspace-root says otherwise.
  2. A path beginning with ~/ resolves against the current user’s home directory.
  3. Any other relative path resolves against the working directory.
  4. The resolved path must stay at or below the workspace root.

-- ends flag parsing, as it does for any Go flag set. A second positional argument is an error: <command>: at most one notebook path argument is accepted, got <n>.

pull and commit never take a session directory. They address a directory you can still open after the process exits. serve with neither root configured does take one — see Share a directory with humans.

Exit behaviour

The exit code is 0 on success and for help, and exactly 1 for everything else.

Outcome Stream Exit
Success, and version stdout 0
-h on a command, help, help <command>, -h or --help with no command stdout (the help or the usage) 0
Domain error from pull or commit (the report below) the report on stdout, error: <CODE> on stderr 1
Refusal at startup: a missing -m, more than one path, a configuration, probe, or hosted check one error: <message> line on stderr, stdout empty 1
Flag parse error error: <command>: <flag error>; run 'slivingdoc <command> -h' for its flags on stderr 1
Missing command the usage on stdout (the home screen on a terminal) 1
Flags but no command error: give a command before <flag> on stderr, then as for a missing command 1
Unknown command error: "<name>" is not a slivingdoc command on stderr, the usage on stdout 1
serve shutdown deadline expired error: app: shutdown deadline expired on stderr 1
serve client disconnect, or a clean signal shutdown none 0

A refusal is always exactly one line, so scripts can read it whole. On a terminal the line starts with a red cross instead of error: . A commit without -m says commit: a message is required: -m <message>. Refusals that name a setting are the ones listed under startup refusals.

pull and commit run the same startup sequence as serve: the pinned engine check and the store check. In S3 mode that is the compatibility probe, unless a probe recorded in the last 24 hours stands in for it. In hosted mode it is an access check instead, with the token mint before it for a stored login. Then they perform one operation and exit. Logs always go to stderr; stdout carries only protocol messages and command output.

serve stops on SIGINT and SIGTERM: it accepts no new requests, cancels in-flight request contexts, waits at most 30 seconds, closes native and lock resources, and exits 1 if that deadline expires.

The success report

A successful subcommand writes the unified result report to stdout and exits zero. Its shape is a status line, one indented line per changed file, a totals trailer, and the path-set trailers:

OK  generation 18  /srv/notes
  archive/old.md  -3
  notes/a.md  +1 -1
  notes/c.md  +2
3 files changed, 3 insertions(+), 4 deletions(-)
Part Grammar
Status line The OK token, two spaces, generation <n> (the accepted remote generation after the operation), two spaces, the resolved notebook directory.
File line Two spaces, the path, two spaces, then +<insertions> and -<deletions>, separated by one space. A zero-count side is omitted entirely.
Totals trailer <n> files changed, <n> insertions(+), <n> deletions(-).
Path trailers The trailers below, when configured.

The diffstat answers “what is new to check out”. pull reports the delta between the visible directory before the pull and the materialised result, so a file restored under a read-only path shows up there like any other change. commit reports the increment the publication added over the remote state it observed. A no-op synchronisation reports an empty stat: no file lines, and zeros in the totals.

A diffstat line is an LF-terminated run of bytes, with one trailing CR stripped for comparison and counting. A final run without a trailing LF still counts as one line; content ending in LF has no phantom empty final line; empty content has zero lines. A file present only after the change counts every line as an insertion, a file present only before it counts every line as a deletion, and a modified file uses a deterministic line diff.

The error report

A domain error prints the same status/detail/trailer skeleton to stdout and exits nonzero:

CONTENT_CONFLICT · MERGE_CONFLICT
Resolve the conflict blocks before notes_commit.
  notes/today.md  conflict  lines 12-18, 40-42
next: edit the files, then commit
retryable: false
Part Grammar
Status line The error code, a space, a middle dot, a space, then the reason token.
Message One line. Message text can change between releases; the tokens cannot.
File line Two spaces, the path padded to the report’s longest path plus two spaces, the file reason as lower-case words, then lines <start>-<end> pairs joined by , when the file has marker ranges. Ranges are one-based and inclusive.
next: The caller’s next step, worded from the action token.
retryable: true or false.
recovery: stage=<stage> remoteAccepted=<yes|no|unknown> resynchronized=<bool>, only for RECOVERY_FAILURE.
Path trailers The trailers below, when configured.

File reasons are rendered as words:

Token Printed as
TEXT_CONFLICT conflict
PATH_CONFLICT path conflict
UNRESOLVED_MARKERS unresolved markers
READ_ONLY read-only
INVALID_CONTENT invalid content
NOT_IN_NOTEBOOK not in the notebook
DIFFERS_FROM_NOTEBOOK differs from the notebook

And so is the action token, on the next: line:

Token Printed as
FIX_INPUT correct the request, then call again
EDIT_FILES edit the files, then commit
PULL pull, then continue
RETRY retry the same call
OPERATOR operator attention needed

One reason overrides its action’s wording. A refused first pull (DIRECTORY_NOT_EMPTY) needs the directory changed, not the request, so its next: line reads pull into an empty directory, or move those files away, then pull again.

An unknown token of either kind is printed verbatim rather than dropped. Errors lists every code, reason, and action.

Path-set trailers

Both reports end with the same trailers, each printed only when that set is configured:

writable: agents/scout
read-only: docs, faq.md
path-rule: longest match decides

The writable trailer precedes the read-only one, because the writable set is the frame and the read-only entries are the exceptions inside it. The path-rule: line appears only when both sets are non-empty, since the two can then name the same region at different depths. Entries are normalised, sorted, and joined with , . See Restrict agents with path policies.

A read-only refusal therefore reads:

INVALID_REQUEST · READ_ONLY_PATH
docs is read-only in this server. Your changes there were discarded and the files reset. Write outside the read-only paths, then commit again.
  docs/faq.md  read-only
next: edit the files, then commit
retryable: false
read-only: docs

On a terminal

The plain report above is the script form: no escape codes, no marks. It is what a pipe, a redirect, or a non-terminal gets, and what any non-empty NO_COLOR forces even on a terminal. When stdout is a real terminal the report adds to it and removes nothing:

  • A mark leads the status line: ✓ before OK, ✗ before the error code. → takes the place of next:.
  • Colour: the OK token and the per-file insertion counts are green, deletion counts red, the generation summary brand blue. On an error the code is red, the reason token and the file reason word dim, a file path amber. The totals, retryable:, recovery:, and path-set trailers are dimmed.

So OK generation 18 /srv/notes becomes ✓ OK generation 18 /srv/notes on a terminal, and a script that matches on the leading OK should read a pipe, not a terminal.

While pull or commit runs, a terminal’s stderr also shows one progress line naming what runs, where, and for how long:

Pulling /home/me/notes · space notes · 1.2s

It says Publishing for a commit, and bucket <name> instead of space <name> in S3 mode. The line is cleared when the operation ends, log records never share it, and stdout keeps only the report. serve on a terminal prints one header line on stderr naming the store, the workspace root, and that it is waiting for an MCP client on stdio; an MCP host’s stderr is not a terminal and gets nothing. Bare slivingdoc, login, space, and logout on a terminal show more styling, and can open a numbered picker to choose a space or a login.

NO_COLOR turns all of that plain, except that pickers still appear, because they depend on a person being able to answer and not on colour. See Logging and profiling for NO_COLOR’s effect on log records. slivingdoc space’s picker lists the targets, with access, owner, and directories where they apply, marks the default, and offers a row that creates a new target, which it then makes the default, so a create need not be typed at all.

Next

Last updated October 21, 2026

Type to search the documentation.