drift docs
Everything to install, drive, and configure drift. The landing page shows what it looks like; this page is the reference.
Install
macOS & Linux — prebuilt binaries (x86_64/aarch64) land
in ~/.local/bin; override the directory with
DRIFT_INSTALL_DIR:
$ curl -fsSL https://tothalex.github.io/drift/install.sh | sh
Windows (experimental) — download
drift-windows-x86_64.zip from the
latest release,
unzip, move drift.exe somewhere permanent, and add that
folder to your user PATH. Windows Terminal is
recommended.
Or build from source on any platform.
Usage
$ drift # review the current repo $ drift --base dev # compare against a different base $ drift ~/some/repo # review another repository $ drift --pr 123 # open pull request #123 right away
| FLAG | MEANING |
|---|---|
| [path] | Repository to review; defaults to the current directory. |
| -b, --base <branch> | Base branch to compare against. Auto-detected when absent: origin/HEAD, then main, then master. |
| --pr <number> | Open this pull request / merge request immediately (needs an authenticated gh or glab). |
| --init-config | Write the fully documented default config to ~/.config/drift/config.toml and exit. |
| lang <command> | Manage language plugins: install, build, list, remove. |
| -V, --version | Print the version. |
The comparison point is git merge-base <base> HEAD,
diffed against the working tree — committed work, uncommitted edits,
and untracked files all show up together, live as you edit.
Repositories are read natively (gitoxide); the git
binary is not required.
Keybindings
Press ? inside the app for this list; every binding is rebindable. Motions take vim-style counts (10j).
Navigate
| KEY | ACTION |
|---|---|
| h / l | Focus the file tree / the code pane (arrows work too; clicking a pane focuses it). |
| j / k | Move the cursor in the focused pane. |
| d / u | Jump 15 lines (also PageDown / PageUp). |
| g / G | Jump to top / bottom; 10G goes to line 10. |
| H / L | Previous / next file, skipping reviewed ones. |
| o | Open / close a folder (also Enter, Space). |
| t | Show / hide the file tree. |
| > / < | Resize the panes (or drag the divider). |
| / | Search the focused pane; n / N cycle matches — with no search active they hop between changes. |
Review
| KEY | ACTION |
|---|---|
| [ / ] | Widen / narrow the tree-sitter block scope around a change. |
| z | Collapse / expand unchanged lines. |
| C | Fold / unfold comment blocks. |
| x / X | Check a file off as reviewed / uncheck the last one. |
| v | Visual mode: select lines. |
| y | Copy the line or selection. |
| s | Send the line or selection to an AI agent pane. |
| c | Copy the file path. |
| e | Open the file in your editor at the cursor's line. |
| b | Choose the base branch, then the review scope (everything, untracked only, one commit). |
| r | Refresh. |
| q | Quit (Esc only cancels — it never quits). |
Pull requests
| KEY | ACTION |
|---|---|
| p | List the repo's open PRs / MRs; inside a PR, return to the list. |
| a / A | Comment on the line or reply to the thread / write a PR-level comment. |
| T | Fold / unfold the review thread under the cursor. |
| r | On a thread: toggle resolved. Elsewhere: refetch the PR. |
| d | On your comment, pressed twice: delete it. |
Configuration
Everything lives in ~/.config/drift/config.toml
(respects $XDG_CONFIG_HOME). Generate the fully
documented default file to edit from:
$ drift --init-config
Top-level entries:
| ENTRY | MEANING |
|---|---|
| base = "main" | Default comparison branch; --base overrides, auto-detected if unset. |
| colorscheme = "onedark" | Base palette: a built-in name or a file in themes/ next to the config. |
| editor = "nvim +{line}" | Editor command for e; {file} and {line} are substituted, and the path is appended when {file} is absent. E.g. "code -g {file}:{line}". |
[keys] — rebind anything
Keys take single characters ("g", "G",
"<"), named keys (enter, space,
tab, arrows, pageup/pagedown,
home/end), optionally prefixed
ctrl-. Digits and esc are reserved.
Listing an action replaces all of its default keys:
[keys] next_file = ["tab"] yank = ["y", "ctrl-c"]
[theme] — every color yours
Colors take ANSI names ("darkgray"), 256-color
indexes ("114"), or hex ("#87d787") —
UI accents and the full syntax palette alike. Colors layer in
three steps: the built-in onedark palette, then a
named colorscheme file, then [theme] overrides on
top.
# ~/.config/drift/config.toml colorscheme = "mytheme" # ~/.config/drift/themes/mytheme.toml — same keys as [theme]; # missing keys keep the built-in defaults keyword = "#fb4934" string = "#b8bb26" [rust] bracket = "#fe8019"
A [theme.<lang>] section (naming any
installed or curated language — rust,
typescript, go, …) overrides any syntax color for that language
only — the way onedark itself paints brackets orange in
TypeScript but purple in Go.
[forge] — pull-request integration
drift talks to GitHub/GitLab through the official
gh / glab CLIs and detects the forge
from the origin remote. If your self-hosted
hostname names neither "github" nor "gitlab", set it
explicitly:
[forge] kind = "gitlab" # or "github" # gh = "/path/to/gh" # binary overrides, if not on PATH # glab = "/path/to/glab"
[agent] — send code to an AI session
[agent] # backend = "auto" # auto | herdr | tmux | cmux | off — naming one # # forces it from outside that session too # target = "claude" # pin an agent name or pane id; empty = auto-pick # submit = true # false: stage the prompt without pressing enter # # (cmux has no staging — it always submits) # template = "{input}\n\n{file}:{lines}\n```\n{code}\n```"
Template placeholders: {input} is the typed
instruction; {file} (absolute path),
{relfile} (repo-relative), {lines}
("12" or "12-18"), {start}, {end}, and
{code} describe the selection.
Language plugins
Every language is a plugin: a tree-sitter grammar fetched and compiled on your machine, plus a highlight query — no grammar is compiled into drift itself. The first time drift shows a file whose language is available but not installed, it offers it right there: press y and keep reviewing while the grammar builds in the background — highlighting and block scoping appear in place when it's ready; n declines for the session. Files without an installed language review as plain hunks.
The same works from the command line. Installing needs
git and a C compiler — the only place drift uses
either.
$ drift lang install rust # curated: bash, c, cpp, csharp, css, dockerfile, # go, html, java, javascript, json, lua, markdown, # php, python, ruby, rust, toml, tsx, typescript, # yaml $ drift lang install https://github.com/tree-sitter-grammars/tree-sitter-zig $ drift lang list # installed and installable $ drift lang build # rebuild grammars (after a drift upgrade) $ drift lang remove rust
For the languages drift curates deeply (rust, typescript, tsx,
javascript, python, go) the installed highlights.scm
is drift's full query stack — the grammar's bundled queries
plus hand-tuned supplements that close the gaps against
editor-grade highlighting — and it's yours to edit. Other
languages get the grammar repo's bundled query.
A plugin is a directory under
~/.config/drift/languages/<name>/; compiled
grammars are cached in ~/.cache/drift/grammars/.
| FILE | MEANING |
|---|---|
| language.toml | The manifest: name, file extensions, grammar repo pinned to a commit, and the block kinds. |
| highlights.scm | The tree-sitter highlight query: drift's curated stack, or the grammar repo's bundled query — yours to edit either way. |
# ~/.config/drift/languages/zig/language.toml name = "zig" extensions = ["zig", "zon"] [grammar] repo = "https://github.com/tree-sitter-grammars/tree-sitter-zig" rev = "…" # commit to build; default branch if unset # path = "zig" # subdirectory, for repos hosting several grammars # symbol = "tree_sitter_zig" # exported fn; tree_sitter_<name> if unset block_kinds = ["function_declaration", "if_statement"]
block_kinds lists the grammar's node kinds that
count as reviewable blocks — what changes expand to and what
[/] widen through: functions, classes, loops.
drift lang build warns about node kinds the grammar
doesn't actually have, and validates the highlight query against
the freshly built grammar.
- Curated languages are complete manifests (and query stacks) in the repo's languages/ directory, embedded into the binary — adding one upstream is a one-file PR. URL installs scaffold the manifest and point out what to fill in.
- Every installed language's name works in [theme.<lang>] sections.
- A broken plugin never stops drift — it degrades to a startup
warning and plain hunk view for its files. After a drift upgrade
changes the tree-sitter ABI,
drift lang buildrebuilds everything.
Grammars are native code compiled into a library that drift loads — install only grammars you trust.
Pull requests
Press p (or run drift --pr 123) to browse
the repo's open pull requests (GitHub) or merge requests (GitLab) —
the same file tree, block-scoped diffs, review progress, and search
as the primary view, with the whole conversation in place: inline
threads under the exact lines they were written on, a pinned
# conversation entry for the description and PR-level
discussion, replying, resolving, and deleting without leaving the
terminal.
Setup is the forge's own CLI — install one and authenticate once:
$ gh auth login # GitHub — https://cli.github.com $ glab auth login # GitLab — https://gitlab.com/gitlab-org/cli
That is also what makes GitHub Enterprise and self-managed GitLab
work: authenticate the CLI against your host
(gh auth login --hostname git.corp.example) and drift
picks the forge from the repo's origin remote — see
[forge] for hosts naming neither.
When the PR's commits exist locally (after any
git fetch), diffs get full syntax highlighting and
block scoping; otherwise drift falls back to plain hunks — no fetch
is ever run for you.
AI agents
Select lines (or sit on one), press s, type an
instruction — the prompt lands in the Claude Code (or Codex, or any
agent) pane beside drift, with the file location and the code
attached. A selection that covers a change is sent unified-diff
style — - old lines, + new lines.
This works through herdr, a
terminal workspace manager for AI coding agents — or through plain
tmux, or cmux. In herdr, drift finds
every agent pane automatically, with its live idle/working state;
in tmux and cmux, agent panes are recognized by their running
process (claude, codex, aider, …) with the same closest-first
targeting, minus the idle/working state. Auto-detection takes the
innermost session drift runs inside — herdr or tmux ahead of the
cmux surface holding them — and naming a backend
(backend = "tmux") forces it from any terminal, except
cmux, whose socket answers only its own surfaces unless you widen
its socketControlMode setting.
- An agent split in drift's own tab is picked automatically — the drift-beside-claude layout never shows a picker.
- With several sessions, a picker asks (closest first, each row naming where its agent is); ↑↓ re-aim the prompt while composing, and the title follows.
- The choice sticks: later sends aim at the same session until its pane goes away. Pin one for good with agent.target.
Build from source
$ cargo build --release # binary at target/release/drift $ cargo test
Git repositories are read natively (via gitoxide) — the
git binary is not required to review. Only
drift lang install/build shell out to
git and a C compiler, to fetch and compile grammar
plugins.