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 loginprints 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 incredentials.jsonand 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, sayswith no expirywhen 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 asksStore this login? [y/N]and revokes the key unless you answery. Flags:--space(or--bucket) makes a space the default,--read-onlyasks for a read-only login,--site(orSLIVINGDOC_SITE) names the site,--no-browserprints the page without opening one, and--forcereplaces another account’s login when there is no terminal to ask. -
slivingdoc spacelists the spaces the login reaches on stdout, one per line as<name> (<access>), owned by <owner>(a teamwhen no person owns it), with*marking the default.slivingdoc space <name>checks that the login reaches the space and stores it as the default, whichserve,pull, andcommituse when neither--spacenorSLIVINGDOC_SPACEis given.--endpoint(orSLIVINGDOC_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 logoutrevokes each stored login key at its site, which also revokes every token minted from it, and removes it and its default space.--site(orSLIVINGDOC_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:
- An omitted path uses the workspace root, which is the working
directory unless
--workspace-rootsays otherwise. - A path beginning with
~/resolves against the current user’s home directory. - Any other relative path resolves against the working directory.
- 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:
✓beforeOK,✗before the error code.→takes the place ofnext:. - Colour: the
OKtoken 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
- Use the CLI without a host — the same commands in practice, including from scripts.
- MCP tools — the same two operations over MCP, with their structured results.
- Configuration — every flag and environment variable.