lightjj
lightjj is a browser-based UI for Jujutsu (jj)
version control. The browser is for the user — do NOT open URLs or screenshot
it. Use lightjj api to read and write through the same HTTP API the browser
uses. It auto-discovers the running instance, sets headers correctly, and works
in harnesses that denylist curl.
If no lightjj is running, ask the user to start it (lightjj in a jj repo) —
do not start it yourself.
Bootstrap
1. lightjj sessions # confirm a session exists, find its repo
2. lightjj api GET /tab/0/api/agent # full API contract (markdown) — read once
3. lightjj api GET /tab/0/api/capabilities # probe feature availability
GET /api/agent is the source of truth for endpoints, request/response schemas,
and the comment/suggestion model. Read it before guessing routes — it describes
the doc-comment store, the navigate endpoint, and the review-comment store.
Synopsis
lightjj api [flags] METHOD PATH [BODY]
METHOD GET | POST | PUT | DELETE | PATCH
PATH verbatim — most routes are tab-scoped: /tab/{N}/api/...
Root-only: /tabs, /api/config. Tab 0 is the launch repo.
BODY literal JSON | @file | "-" for stdin
Full reference (flags, exit codes): lightjj api --help / GET /api/agent.
Quote query strings — bare & backgrounds the shell:
lightjj api GET '/tab/0/api/file-show?revision=@&path=docs/DESIGN.md'
Common operations
# What is the user looking at right now? Read this FIRST — it's the difference
# between narrating a review and spraying comments past the user's cursor.
# Returns {change_id, commit_id, active_view, doc_file_path, updated_at}.
# Stale if updated_at is >60s old (browser closed or not focused) — the
# frontend heartbeats every 20s while the tab is visible.
lightjj api GET /tab/0/api/focus
# Read the current revision graph (commit metadata, descriptions, bookmarks)
lightjj api GET /tab/0/api/log
# Read a file at a revision
lightjj api GET '/tab/0/api/file-show?revision=@&path=src/main.go'
# Read existing doc-mode comments on a markdown file
lightjj api GET '/tab/0/api/doc-comments?path=docs/DESIGN.md'
# Post a doc-mode comment (range-anchored on rendered text — see /api/agent
# for the anchor schema). Set "author" so the UI marks it as agent-posted.
lightjj api POST /tab/0/api/doc-comments @comment.json
# Steer the user's view to a file/line, or to a comment by id
lightjj api POST /tab/0/api/navigate '{"file_path":"src/main.go","line":42}'
lightjj api POST /tab/0/api/navigate '{"change_id":"xyzabc","comment_id":"a1b2c3"}'
# Read inline review comments (annotations) on a change. Note: camelCase param.
lightjj api GET '/tab/0/api/annotations?changeId=xyzabc'
# Post a diff-line review comment. Same store the user's Alt+click writes to.
# severity: must-fix | suggestion | question | nitpick | reviewed
lightjj api POST /tab/0/api/annotations '{"id":"a1","changeId":"xyzabc",
"filePath":"src/main.go","lineNum":42,"lineContent":"func main() {",
"comment":"missing error check","severity":"suggestion","author":"agent-name"}'
Review loop
A review is multi-turn — the user reads your comments, accepts some, marks others won't-fix, and may post their own. Be a good participant:
Set
authoron everything you post. Both/api/annotationsand/api/doc-commentstake anauthorfield. Use a stable name (your harness or model name). The UI renders agent comments with a ⟐ prefix and lets the user hide-by-author. Without it, your comments look like the user's own and you can't tell yours apart on re-read.Read before writing. GET the existing comments for the file/change before posting. The store upserts by
id— a re-POST with a fresh UUID is a duplicate, not an update. To update, re-POST with the sameid.Re-read after the user reviews. Poll
/api/annotations?changeId=...or/api/doc-comments?path=...and checkresolutionon the comments you posted."addressed"= accepted,"wontfix"= rejected, absent = still open. There is no "review finished" signal — poll until your ids resolve, or agree a convention with the user.Respect won't-fix. Don't re-post a finding the user marked
resolution: "wontfix". They saw it and decided.Don't write
/api/focus. It's the frontend's report of what the user is looking at — POSTing to it forges that report and lies to yourself on the next read. Use/api/navigateto steer the user;/api/focusto read where they are.
Multiple sessions / repos
Discovery matches the agent's cwd against each session's repo dir. If you're
inside the same repo lightjj was launched in, lightjj api ... just works. If
not:
lightjj sessions # see what's running
lightjj api --repo /path/to/other GET /tab/0/api/log
lightjj api --addr 127.0.0.1:54321 GET /tab/0/api/log
A session run with lightjj --remote user@host:/repo is listed by sessions
but won't auto-match — its repo dir is a remote path. Use --addr.
Don't
- Don't
curl— the entire reasonlightjj apiexists is that harnesses denycurl. It also won't auto-discover the port or setContent-Type. - Don't run
jjcommands directly when the user is reviewing in lightjj — the snapshot loop will pick up your changes and the user's view will jump. If you need to mutate, tell the user what you'd do and let them decide. - Don't open the browser URL or screenshot the UI.
- Don't guess endpoint shapes —
lightjj api GET /tab/0/api/agentdocuments all of them with example payloads.
Common errors
no running lightjj session matches <path>— lightjj isn't running in this repo. Ask the user to start it, or pass--repo/--addr.address ... is not loopback—--addronly accepts127.0.0.1,::1, orlocalhost. SSH tunnels: forward to a local port, then--addr 127.0.0.1:N.multiple lightjj sessions match— two instances on the same repo.lightjj sessions, then pick one with--addr.- HTTP 200 but the body is HTML — you forgot the
/tab/N/prefix; the unprefixed path falls through to the SPA. Use/tab/0/api/.... - HTTP 400
Content-Type must be application/json— only happens withcurl;lightjj apisets it automatically when a body is present. - HTTP 400
changeId required—/api/annotationsuses camelCase query params (changeId,id);/api/navigateuses snake_case body fields (change_id,file_path). They predate each other — check/api/agent.
Source: chronologos/lightjj — distributed by TomeVault.