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
FLAGMEANING
[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-configWrite the fully documented default config to ~/.config/drift/config.toml and exit.
lang <command>Manage language plugins: install, build, list, remove.
-V, --versionPrint 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

KEYACTION
h / lFocus the file tree / the code pane (arrows work too; clicking a pane focuses it).
j / kMove the cursor in the focused pane.
d / uJump 15 lines (also PageDown / PageUp).
g / GJump to top / bottom; 10G goes to line 10.
H / LPrevious / next file, skipping reviewed ones.
oOpen / close a folder (also Enter, Space).
tShow / 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

KEYACTION
[ / ]Widen / narrow the tree-sitter block scope around a change.
zCollapse / expand unchanged lines.
CFold / unfold comment blocks.
x / XCheck a file off as reviewed / uncheck the last one.
vVisual mode: select lines.
yCopy the line or selection.
sSend the line or selection to an AI agent pane.
cCopy the file path.
eOpen the file in your editor at the cursor's line.
bChoose the base branch, then the review scope (everything, untracked only, one commit).
rRefresh.
qQuit (Esc only cancels — it never quits).

Pull requests

KEYACTION
pList the repo's open PRs / MRs; inside a PR, return to the list.
a / AComment on the line or reply to the thread / write a PR-level comment.
TFold / unfold the review thread under the cursor.
rOn a thread: toggle resolved. Elsewhere: refetch the PR.
dOn 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:

ENTRYMEANING
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/.

FILEMEANING
language.tomlThe manifest: name, file extensions, grammar repo pinned to a commit, and the block kinds.
highlights.scmThe 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 build rebuilds 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.