An annotation tool for human-in-the-loop AI workflows.
Platform: macOS (Apple Silicon), Linux (NixOS/Wayland tested), and Windows (build from source).
AI agents work fast, but "make it better" is a lossy feedback channel. When an agent drafts a plan, proposes a refactor, or generates code — you need a way to point at specific lines and say what you actually mean, before any of it becomes real.
annot is that moment of review. It opens a window, you shape the content with located, structured feedback, then it closes and gets out of the way.
macOS:
brew install denolehov/tap/annotBuild from source (macOS)
git clone https://github.com/denolehov/annot.git && cd annot
pnpm install
pnpm tauri buildBuild from source (macOS — with Nix)
git clone https://github.com/denolehov/annot.git && cd annot
nix develop # enter dev shell with all build dependencies
pnpm install
pnpm tauri build # .app bundle at src-tauri/target/release/bundle/macos/Build from source (Ubuntu)
Install system dependencies:
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
build-essential \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
libgstreamer-plugins-bad1.0-devInstall Rust, Node.js 24, and pnpm, then build:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
git clone https://github.com/denolehov/annot.git && cd annot
pnpm install
pnpm tauri build # binary at src-tauri/target/release/annotBuild from source (NixOS)
git clone https://github.com/denolehov/annot.git && cd annot
nix develop # enter dev shell with all build dependencies
pnpm install
pnpm tauri build # binary at src-tauri/target/release/annotOr install into your Nix profile (wraps the binary with required env vars):
nix profile add path:.Wayland note: The installed binary automatically sets
GDK_BACKEND=x11andWEBKIT_DISABLE_DMABUF_RENDERER=1via the Nix wrapper, which prevents broken text rendering in WebKit2GTK under Wayland.
Build from source (Windows)
Install prerequisites:
- Rust — install via rustup
- Node.js 24 — install via nodejs.org
- pnpm —
npm install -g pnpm - WebView2 Runtime — preinstalled on Windows 11; Windows 10 users can download the Evergreen Bootstrapper from Microsoft.
Clone and build:
git clone https://github.com/denolehov/annot.git
cd annot
pnpm install
pnpm tauri buildThe binary is at src-tauri\target\release\annot.exe. There is no installer bundle yet —
add the src-tauri\target\release\ directory to your PATH, or reference the .exe directly.
Same command on macOS, Linux, and Windows:
claude mcp add --scope user annot annot mcpClaude now has review tools (review_file, review_diff, review_content). Ask it to review something and a window opens for your feedback.
Windows note:
annot.exemust be on yourPATH(see build instructions above), or pass the full path:claude mcp add --scope user annot "C:\path\to\annot.exe" mcp.
annot file.rs # Open a file for annotation
cat file.go | annot -l main.go # Pipe from stdin (label sets highlighting)
annot --json file.rs # Output as JSON (for agent consumption)
annot diff # Review uncommitted changes
annot diff 4f7d4491 # Review one revision against its parent
annot diff main..HEAD # Review a revision range (main...HEAD for merge base)
annot diff --staged # Review staged changes (git only — jj has no index)
annot diff -- src/ '*.rs' # Limit the diff (pathspecs; jj filesets in a jj repo)- A window opens with your content (code, diff, or markdown)
- Click line numbers to select ranges, then type your annotation
- Weave tags into prose for structured feedback:
[# VERIFY] this claim with a test - Select an exit mode (Tab) to signal intent — "Apply", "Reject", "Needs changes"
- Close the window — structured annotations return to the caller
No data leaves your machine. No accounts. No cloud.
| Feature | What it does |
|---|---|
| File review | Any source file, syntax highlighting for 50+ languages |
| Diff review | git & jj: working copy, revision, range, staged — or raw diffs via stdin/MCP |
| Content review | Agent-generated markdown — plans, drafts, analysis |
| Tags | Semantic mini-prompts ([# VERIFY]) woven into annotations |
| Exit modes | Signal intent on close — "Apply", "Reject", agent-defined per session |
| Session context | Review-wide framing comment (Shift+C) |
| Replace blocks | Propose inline code changes (/replace) |
| Excalidraw | Sketch diagrams inside annotations (/excalidraw); convert Mermaid to sketch |
| Mermaid | Diagrams rendered inline from markdown code blocks |
| Portal links | Embed live code from other files into markdown under review |
| Images | Paste screenshots directly into annotations |
| References | @-link annotations, sections, and project files |
| Diff navigation | File tree sidebar (Cmd+B), per-file collapse, unified/split view, unfold context |
| Command palette | : — tags, exit modes, file jump, copy, save, theme |
| Export | Save to markdown file, send to Obsidian |
| Structured output | LLM-ready text or --json with embedded images |
Full detail in docs/features.md. Three highlights:
Composable mini-prompts you build over time. Type # in the annotation editor to insert one:
[# VERIFY] this with a dedicated test
Make this configurable. Add a new section in @config.rs.
[# ELABORATE] on the error handling here
Tags carry semantic meaning that LLMs interpret. They appear in a LEGEND block in the output. Create your own via the command palette (:).
Signal intent when closing a review. Instead of just closing, indicate what should happen next.
User-defined modes persist across sessions. Agent-defined modes are ephemeral and passed via MCP:
{
"exit_modes": [
{"name": "Apply", "instruction": "Apply all changes exactly as annotated", "color": "green"},
{"name": "Reject", "instruction": "Reject and explain reasoning", "color": "red"}
]
}Press Shift+C to add comments that apply to the entire review — framing context like "focus on error handling, ignore style."
| Parameter | Type | Required | Description |
|---|---|---|---|
file_path |
string | yes | Absolute or relative path to the file |
exit_modes |
array | no | Ephemeral exit modes for this session |
| Parameter | Type | Required | Description |
|---|---|---|---|
working_dir |
string | no | The repository to review — any directory inside it works. Defaults to annot's own process directory (see below) |
target |
object | no* | What to diff: {"kind": "working_copy"} (default), {"kind": "revision", "rev": "@-"}, {"kind": "range", "from": "main", "to": "HEAD", "merge_base": true}, or {"kind": "staged"} (git only) |
pathspecs |
array | no | Pathspecs limiting the diff (e.g., ["src/", "*.rs"]); jj filesets in a jj repo |
diff_content |
string | no* | Raw unified diff content (mutually exclusive with target/pathspecs) |
label |
string | no | Display name (default: "diff") |
exit_modes |
array | no | Ephemeral exit modes for this session |
*target defaults to working_copy — in git, worktree vs HEAD (staged + unstaged, untracked included); in jj, @ vs its parents after snapshotting the working copy. range with merge_base: true diffs from merge_base(from, to) to to, like from...to.
Agents working across repositories should pass working_dir. annot's MCP server runs as a sidecar of your agent and inherits the directory the agent was launched from — which is frequently not the repository being edited. Left to default, it finds whatever repo encloses that launch directory and reviews it: a real diff, confidently rendered, from somewhere else entirely. working_dir names the repo explicitly; it also roots the file picker and :save for the session. It accepts any directory inside the repo (annot searches upward for the root), and errors if the path doesn't exist.
Works with git and jj alike, colocated or not. rev strings are the repository's own dialect — git revspecs, or jj revsets (@-, trunk()) in a jj repo. In a jj repo annot snapshots the working copy the way every jj command does (one snapshot working copy op — annot's only write), and conflicted files render as jj's marker text instead of erroring.
| Parameter | Type | Required | Description |
|---|---|---|---|
content |
string | yes | Markdown-formatted text content |
label |
string | yes | Display name with .md extension |
exit_modes |
array | no | Ephemeral exit modes for this session |
| Shortcut | Function |
|---|---|
| Click line numbers | Select/deselect lines |
| Shift+Drag | Select range |
| c | Comment hovered line |
| Shift+C | Session context (global comment) |
| Tab / Shift+Tab | Cycle exit modes |
| Alt+Tab | Exit mode picker |
| : | Command palette |
| Cmd+F / Ctrl+F | Search |
| Cmd+B / Ctrl+B | Toggle file tree (diffs) |
| Cmd+= / Cmd+- / Cmd+0 | Zoom in / out / reset |
| Cmd+S / Ctrl+S | Save to file |
| Cmd+W / Ctrl+W | Save and close |
| ? | Help overlay |
In annotation editor:
| Shortcut | Function |
|---|---|
| # | Insert tag |
| @ | Reference (annotations, sections, files) |
| / | Slash commands (/replace, /excalidraw) |
