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). When rw serve is running, add, reply, and resolve send it a best-effort notification so any browser viewing the affected page refreshes its comments automatically. If no server is running, the comment is still written and the notify is skipped.
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:ai", name: "AI" }— therw commentCLI's primary user is an LLM agent, so an unattributed comment is stamped as AI (a sparkles avatar in the browser) rather than as a human. (Browser-authored comments viarw servestill default to{ id: "local:human", name: "You" }.)
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