The zot command line
cli.RmdThe zot command line operates this package without
writing R. It parses arguments, calls one exported function and maps the
result and the package’s condition classes to standard output and an
exit code. Every rule it applies belongs to the package, so the same
operation is available from R.
The command is a separate component: it lives in the product
repository under cli/ and is installed from an authorized
checkout together with this package. Installing the package from an
archive does not install it, and this article describes the interface
rather than shipping it.
The three lanes
| Lane | Verbs | Effect on a library |
|---|---|---|
| Read |
doctor, libraries, census,
collections, items, get,
local items\|query\|records\|files, semantic,
identity twins\|match
|
None |
| Build |
clean name\|title\|payload,
payload template\|check\|crossref,
plan retitle\|refile\|move-create\|move-trash,
preview
|
None; plan and preview verbs write local batch files |
| Mutation |
approve, execute, verify
|
Only execute writes, and only under an approved
preview |
zot --help lists them with one line each, and
zot VERB --help prints that verb’s flags. Use the full
two-word name for a subverb, for example
zot plan retitle --help. --json prints an
operation result as one document on standard output; diagnostics go to
standard error. Help and version output remain text.
Local paths and result files
The CLI has no --root flag and does not discover a
project directory. Relative input and output file paths resolve from the
working directory of the command, including
--sql-file, --records,
--candidates, --items, --payload,
--plan, --preview, --approval,
--ledger and --out. Selecting or moving a JSON
file does not change that base. Absolute paths keep their meaning.
--library selects a Zotero library, not a local
directory.
For local verbs, --db selects a SQLite
file; the default is ~/Zotero/zotero.sqlite. For
semantic, --db selects a Chroma directory; the
default is ~/.config/zotero-mcp/chroma_db. Explicit
relative database paths also use the working directory.
semantic --python selects the companion interpreter; use
its absolute path to avoid executable-search ambiguity. Its default is
~/.local/share/uv/tools/zotero-mcp-server/bin/python3.
doctor probes these defaults and has no --db
or --python override.
Only the four plan verbs, preview and
approve accept --out. They require a new
destination in an existing directory, then write one JSON batch document
there. The writer uses <destination>.tmp beside the
destination and renames it into place. That companion path is part of
the write; use a separate destination for each concurrent batch
writer.
execute --ledger is different: it creates or updates the
ledger at the exact path supplied, with a sibling .tmp
during each replacement. The parent directory must already exist. An
existing ledger is reusable for the same preview digest; a different
digest is refused. plan move-trash and verify
only read their --ledger file. No command recursively
clears a project output directory.
All verbs also report a result on stdout. With --json,
that result is an envelope containing ok,
verb, data and meta, or an
error on an exception. It is not a batch
file and is not automatically accepted as an input record
array. --items for plan retitle and
--records for identity twins and the move
plans take JSON arrays of records with the fields described below;
identity match takes arrays of candidate and reference
records. Prepare those arrays from the result data before passing them
back. Payload inputs take the payload object itself. Shell redirection
such as > result.json is controlled by the shell and has
none of the CLI’s new---out checks.
Inputs, results and effects by verb
The table covers the 27 verbs in the CLI’s own verb table. Optional selectors and limits are listed by each verb’s help. Except for the batch and ledger destinations shown here, results go to stdout; the command does not save a second report automatically.
| Verb | Input or source | Result and destination |
|---|---|---|
doctor |
Installed package, environment, one Web API read when credentials are available, local database and semantic paths. | Availability table. Opens SQLite read-only; checks semantic paths without launching Python. |
libraries |
Configured user’s accessible libraries via the Web API. | Personal/group identifiers, names and library versions. |
census |
--library and optional repeated
--collection NAME=KEY; API reads. |
Top-level, Trash and named-collection counts. |
collections |
--library; API reads. |
Collection identifiers, hierarchy paths, item counts and versions. |
items |
--library, repeated --key or listing
selectors/filters. No selector lists the library. |
Items or versions, with listing metadata or missing keys. A listing
returns one page by default; --all requires
--max. |
get |
Positional /SUBPATH, --library, optional
repeated --query NAME=VALUE. |
GET status, headers and body under the library’s API prefix. This path is not a filesystem path. |
local items, local records
|
SQLite --db, optionally --library; without
a library selector these read all local libraries. |
Non-trashed items or bibliographic identity records. No database copy or update. |
local query |
Exactly one of --sql or --sql-file,
optional positional --param values, SQLite
--db. |
Query rows through a read-only connection; supply a read-only SQL statement. |
local files |
SQLite --db, optional repeated --key and
--md5. |
Attachment metadata and existing local paths. Imported files resolve
under storage/<key>/ beside the selected database;
supported absolute linked paths remain as stored. It does not download,
move or copy attachments. |
semantic |
Positional query, existing Chroma --db, companion
--python, optional --limit. |
Item keys, group IDs, titles and distances. Launches Python to query
zotero_library; does not build or refresh the index. There
is no library selector. |
identity twins |
--records: array with item keys, titles and identity
evidence. |
Duplicate-candidate pairs and their identity verdicts. |
identity match |
--candidates and --records: arrays of API
records or identity objects. |
Candidate/reference pairs and evidence verdicts. |
clean name, clean title
|
Positional text. | Cleaned text. A filename argument is treated as text; no file is renamed. |
clean payload |
--payload: JSON payload object. |
Cleaned payload on stdout; the input file is preserved. |
payload template |
--type; Zotero template API reads. |
Allowed fields, creator types and empty template. |
payload check |
--payload: JSON object; template API reads. |
Validation result or a refusal with the problems found; no item is created. |
payload crossref |
Positional DOI; Crossref lookup, without Zotero credentials. | Sanitized create payload on stdout; no item is created. |
plan retitle |
--items: array with itemKey,
library, title. |
Title patches using the fixed title cleaner, saved as a plan at new
--out; also reports the plan on stdout. |
plan refile |
--library, repeated --key,
--add-to; reads current API memberships. |
Collection-membership patches at new --out, skipping
absent items and memberships already present. |
plan move-create |
--records: array with itemKey,
hasFile, payload; --to-library
and --collection. |
Create plan at new --out. Records carrying files are
refused; this does not transfer attachments. |
plan move-trash |
Original --records in the same order as the create
plan, its --ledger, and --from-library. |
Trash plan at new --out for originals whose
corresponding create-<row> ledger entry is
done. No remote Trash action occurs here. |
preview |
--plan batch file and --describe. |
Validated preview with digest, counts and rows at new
--out; stdout reports its summary. No API request. |
approve |
--preview, the displayed --digest, and
--notes. |
Approval record at new --out; stdout reports digest,
notes and time. It records authorization supplied by the caller; it does
not authenticate the approver or execute the batch. |
execute |
--preview, matching --approval, and
--ledger; configured API access. |
Applies approved patch/create/Trash rows remotely, updating the local ledger before and after each attempt. Stdout reports counts, ledger path and unfinished rows. |
verify |
--preview, --ledger, optional
--full; API reads. |
Counts and read-back verdicts on stdout. Does not rewrite the ledger
or modify Zotero. Default verification samples up to ten completed patch
rows; --full checks every completed row, including create
and Trash. |
The plan, preview and approval files use the package’s
kind-tagged JSON batch format. A ledger contains
digest and items; it is maintained by
execution. The separate --out files let the owner inspect
each stage before authorizing execution. Changing an output path and
rerunning a plan or preview rebuilds it from its current inputs.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success. Zero results, or a 404 from get, is a verdict
rather than an error |
| 1 | Operational failure: an exhausted transport, an unavailable plane |
| 2 | Usage: an unknown verb or flag, a missing value, an input file that is not there |
| 3 | Refusal-class condition: configuration, plan, approval, bound or unresolved-ledger error; see the effect limits below |
| 4 | Incomplete: a batch whose rows are not all done, or a verification that did not confirm |
An exit code classifies the result; it does not by itself establish
whether remote effects occurred. Validation of credentials, plan and
approval before execution can refuse without a write. However, a failure
to rename the ledger after an API write is also a plan-class error
mapped to code 3. The phrase “refused without effects” in older help is
therefore not a blanket guarantee. Current help states that codes do not
establish write effects. A previous attempt can also mean
an unresolved remote outcome. Inspect the reported cause, ledger and
current library state before resuming.
Code 4 means incomplete execution or verification, not proof of a
successful remote write: verify performs reads only, and an
execution can record failed rows. A JSON envelope with
ok: true means the runner returned a result;
doctor, execute and verify can
return that envelope with a nonzero exit code. Check the exit code and
the result’s statuses together.
From a plan to a verified batch
The mutation lane is the guarded flow of
vignette("guarded-workflow") with each step in its own
process, so the owner can read and approve the exact rows between
them:
zot plan retitle --items items.json --out plan.json
zot preview --plan plan.json --describe "Normalize reviewed titles" --out preview.json
# show preview.json to the owner, then retype the digest it reports
zot approve --preview preview.json --digest <sha256> \
--notes "owner approved the displayed batch" --out approval.json
zot execute --preview preview.json --approval approval.json --ledger ledger.json
zot verify --preview preview.json --ledger ledger.json --fullapprove reads the preview under the digest given on the
command line: a preview whose rows changed since it was displayed cannot
be approved by mistake. execute recalculates that digest
again before its first request, resumes an interrupted run through the
same ledger path, and stops on a row whose outcome is unknown.
verify re-reads what the ledger recorded.
The batch is not a transaction. Execution records
attempt before each row, then its result; completed rows
are skipped on resume. A retained attempt stops execution
for reconciliation, while other unfinished statuses are attempted again.
A failed read-back can follow a successful remote write, so an
unfinished row is not evidence that nothing happened. Earlier rows are
not rolled back when a later row fails. Verification does not repair the
ledger or authorize a retry.
The files are the package’s own batch documents, so R can read and write the same ones:
library(data.table)
#>
#> Attaching package: 'data.table'
#> The following object is masked from 'package:base':
#>
#> %notin%
library(zot)
Items <- data.table(
itemKey = c("A", "B"),
library = "user",
title = c("Reviewed book (1).pdf", "Existing title")
)
Plan <- ztRetitle(Items, transform = ztCleanTitle)
PlanFile <- tempfile(fileext = ".json")
zotWriteBatch(Plan, PlanFile)
Preview <- zotPreview(zotReadBatch(PlanFile), describe = "Normalize reviewed titles")
Preview$digest
#> [1] "1737f2bbf3c2706d92827a746c5299a38e518e066d343dd596632c04ae8abb9e"A preview read back from disk recalculates its digest, so an edited file is refused instead of executed under the earlier approval:
PreviewFile <- tempfile(fileext = ".json")
zotWriteBatch(Preview, PreviewFile)
Document <- jsonlite::read_json(PreviewFile, simplifyVector = FALSE)
Document$rows[[1]]$payload$title <- "Something else"
jsonlite::write_json(Document, PreviewFile, auto_unbox = TRUE, null = "null")
zotReadBatch(PreviewFile)
#> Error:
#> ! preview rows no longer match their stored digest: /tmp/RtmpJ0EYsX/file42fb378104bc.jsonCredentials and planes
The command reads ZOTERO_LIBRARY_ID and
ZOTERO_API_KEY from its process environment, which R fills
from ~/.Renviron; there is no flag for the key, because
command lines are visible to other processes. zot doctor
reports the package, the credentials, the Web API, the local database
and the optional semantic sidecar, naming the missing variable or path
instead of guessing. Local and semantic reads are advisory planes: the
Web API remains the truth a write is verified against.