Read and write comments in a project's .rw/comments/sqlite.db from scripts and LLM agents (Claude Code, Cursor, etc.). The CLI opens the SQLite store directly — rw serve can be running or not; both processes can safely share the database (SQLite WAL).
List comment threads. Defaults to open only; client-side filter keeps top-level threads only (no replies) unless you pass --parent.
rw comment list [--document DOC] [--status open|resolved|all] [--parent ID|all]
Example:
rw comment list --document guide --status open
Print a single thread — parent plus all replies — in text or JSON.
rw comment show <id>
Create a new top-level comment. Page-level by default; pass --quote "passage text" to anchor it inline to a passage in the rendered page (see Anchoring below).
rw comment add --document DOC --body BODY [--quote "anchor text"]
Example:
rw comment add --document guide --quote "The quick brown fox" \
--body "Can we add an example here?"
Reply to an existing thread. You only need the parent id — the CLI looks up the thread's document.
rw comment reply <parent-id> --body BODY
Mark a thread resolved.
rw comment resolve <id>
New comments carry an author stamp. Resolution order:
--author-idand--author-nameflags on the subcommand.$RW_COMMENT_AUTHOR_IDand$RW_COMMENT_AUTHOR_NAMEenv vars.- If both are unset, the CLI writes the comment with the default identity
{ id: "local:human", name: "You" }. (This is the same defaultrw serveuses when the browser writes a comment — viewer and CLI stay aligned.)
Set both or neither — a partial identity (e.g. id without name) is rejected before the request leaves the CLI.
Recommended for LLM agents: export the env vars once (in .claude/settings.json or your shell profile) and let every rw comment invocation pick them up:
export RW_COMMENT_AUTHOR_ID=local:claude-code
export RW_COMMENT_AUTHOR_NAME="Claude Code"
When you pass --quote "..." to rw comment add, the CLI:
- Renders the referenced page.
- Flattens the rendered HTML to a
textContent-equivalent string (matches what the browser anchoring sees). - Searches for the quote as an exact substring.
- Builds a
TextQuoteSelector(with 32 chars of prefix/suffix context) plus aTextPositionSelector.
Zero matches → exit code 3, error "quote not found in document 'X'". The quote must appear as rendered text — **bold** is not in the rendered output, bold is.
Multiple matches → exit code 3, error "quote matches N times in document 'X' — add more surrounding context to disambiguate". Quote more surrounding text to pin the occurrence you want.
--quote and explicit selectors are mutually exclusive.
--format text (default) is human-readable. --format json emits the raw API shape — for show, { "comment": Comment, "replies": [Comment, …] }.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Internal error (store I/O, render failure, unexpected) |
| 2 | Comment not found (show, resolve, reply <missing-parent>) |
| 3 | Validation error (quote not found or ambiguous, document not found, invalid --parent uuid, mutually exclusive flags, partial identity) |
Shell example:
if ! rw comment add --document guide --quote "foo" --body "…"; then
case $? in
3) echo "bad quote, missing document, or invalid flags — see stderr" ;;
*) echo "something went wrong — see stderr" ;;
esac
fi