Skip to content

Repository files navigation

orbit (Offline Review Board In Terminal)

orbit

A terminal-based code review tool. Browse git diffs, leave comments on specific lines, then export everything as a prompt you can paste into Claude Code or any other AI tool.

Demo

Why

Inspired by difit, a great browser-based local diff viewer. Commenting on lines works in the browser, but not yet in the TUI. I wanted a fully terminal-native diff reviewer with line-level comments, a customizable syntax highlighting theme, split view, and a prompt export workflow for AI coding assistants -- so I built orbit.

The key trick is the prompt export. Your review comments become a structured prompt that an AI coding assistant can act on directly. Review a diff, jot down what needs fixing, copy the prompt, paste it, done.

Screenshots

Home screen -- file tree with diff preview

Home screen

Unified view -- unified diff with syntax highlighting, fold/unfold

Unified view

Split view -- side-by-side comparison

Split view

Comment -- add review comments on any line

Comment

Prompt preview -- export comments as a structured prompt

Prompt preview

Install

Requires Bun v1.2+.

git clone https://github.com/Hoshock/orbit.git
cd orbit
bun install
bun run register # symlinks bin/orbit to ~/.local/bin/orbit

Make sure ~/.local/bin is in your PATH.

Usage

orbit # unstaged changes (git diff)
orbit .# same as above
orbit --staged # staged changes (git diff --staged)
orbit HEAD # last commit (HEAD~1..HEAD)
orbit HEAD~3..HEAD # commit range
orbit feature main # branch comparison
orbit -- file.ts # single file diff
orbit HEAD -- file.ts # single file from commit range
orbit --include-untracked -- file.ts # include selected untracked file(s)
orbit --include-untracked .# include all untracked files
orbit --root SHA~1..SHA # diff against empty tree if base is unresolvable

Lazygit integration

Add the following to your lazygit config (~/.config/lazygit/config.yml) to launch orbit directly from lazygit with a single key:

customCommands:
- key: "o"description: "Review working tree diff"command: "orbit ."context: "files"output: terminal
- key: "O"description: "Review working tree diff (include untracked)"command: "orbit --include-untracked ."context: "files"output: terminal
- key: "o"description: "Review branch diff"command: "orbit {{.SelectedLocalBranch.Name}} {{.CheckedOutBranch.Name}}"context: "localBranches"output: terminal
- key: "o"description: "Review commit(s)"command: "orbit --root {{.SelectedCommitRange.From}}~1..{{.SelectedCommitRange.To}}"context: "commits"output: terminal

Press o / O in these panels to open orbit:

PanelWhat it reviews
Fileso: current diff, O: current diff + untracked
BranchesDiff between selected branch and current branch
CommitsSelected commit, or range if multiple are selected

In files context, orbit is launched without file selection templating, so nil pointer issues from missing SelectedFile/SelectedPath do not occur. For commit ranges, select multiple commits with v (visual mode) or shift+up/down in lazygit, then press o. The ~1 ensures the first selected commit is included in the diff. The --root flag handles the case where the selection includes the initial commit by diffing against an empty tree.

Keybindings

The following are default keybindings. You can override them in config.toml.

File list

KeyAction
Esc/qQuit
Up/DownMove cursor
Left/RightCollapse/expand directory
[/]Shrink/grow tree panel (10%-50%)
EnterOpen diff or toggle directory
cComment list
pPrompt preview
tToggle split/unified
vToggle viewed

Diff view

KeyAction
Esc/qBack to file list
Up/DownMove by line
Left/RightSwitch side (split mode)
Shift+Up/DownSelect range
cComment on current line or selection
dDelete comment at cursor
eEdit comment at cursor
fFile-level comment
tToggle split/unified
vToggle viewed
zFold/unfold (incremental, incremental-fold-lines at a time)
ZFold/unfold all in the current file

Comment input

KeyAction
EscCancel
Ctrl+EnterSubmit

Comment list

KeyAction
Esc/qBack to file list
Up/DownMove cursor
EnterJump to comment
dDelete comment
eEdit comment

Prompt preview

KeyAction
Esc/qBack
yCopy prompt to clipboard

How the prompt works

Each comment you leave records the file path, line number, which side of the diff (old/new), and the code at that line. When you press p to preview and y to copy, orbit formats all of this into a single text block:

src/app.tsx:L42 (a1b2c3d)
This function should handle the edge case where files is empty.
==========
src/utils/git.ts:L15-L20 (a1b2c3d)
Extract this into a helper, it's duplicated in three places.

Paste that into Claude Code (or any LLM) and it has enough context to act on each comment.

Persistence

Comments, viewed status, and tree panel width are automatically saved and restored across sessions.

DataCache file
Comments, viewed, tree width/tmp/orbit-<repo>-<hash>.json

The hash is derived from the diff range (e.g., HEAD~1..HEAD), so different ranges get separate caches. Cache files live in /tmp and are cleared on OS restart.

Configuration

orbit reads custom settings from ${XDG_CONFIG_HOME:-~/.config}/orbit/config.toml. If the file (or a specific key) is missing, default values are used.

file-tree-initial-width = 0.2initial-view = "unified"# or "split"incremental-fold-lines = 20
[keybindings.file-tree]
quit = "q"tree-shrink = "["tree-grow = "]"comment-list = "c"prompt-preview = "p"toggle-view-mode = "t"toggle-viewed = "v"
[keybindings.diff-view]
quit = "q"comment = "c"delete-comment = "d"edit-comment = "e"file-comment = "f"toggle-view-mode = "t"toggle-viewed = "v"fold = "z"# comment-input has no configurable keybindings (Esc / Ctrl+Enter fixed)
[keybindings.comment-list]
quit = "q"delete-comment = "d"edit-comment = "e"
[keybindings.prompt-preview]
quit = "q"copy-prompt = "y"

Stack

  • Bun - runtime and test runner
  • OpenTUI - terminal UI framework (React-based)
  • React - component model
  • Biome - linter and formatter

Development

bun run start # run from source
bun test# run tests
bun run check # lint + build check
bun run lint # auto-fix lint issues

Syntax highlighting

orbit uses tree-sitter for syntax highlighting. The following languages are currently supported:

LanguageSource
JavaScriptOpenTUI built-in
TypeScriptOpenTUI built-in
MarkdownOpenTUI built-in
ZigOpenTUI built-in
PythonBundled grammar (assets/tree-sitter/python/)
JSONBundled grammar (assets/tree-sitter/json/)
TOMLBundled grammar (assets/tree-sitter/toml/)
YAMLBundled grammar (assets/tree-sitter/yaml/)

Adding a new language

  1. Get the .wasm grammar and highlights.scm

    mkdir -p assets/tree-sitter/<language># Prebuilt wasm from unpkg
    curl -L "https://unpkg.com/tree-sitter-wasms@latest/out/tree-sitter-<language>.wasm" \
    -o assets/tree-sitter/<language>/tree-sitter-<language>.wasm
    # Highlight queries from the official tree-sitter grammar repo
    curl -L "https://raw.githubusercontent.com/tree-sitter/tree-sitter-<language>/master/queries/highlights.scm" \
    -o assets/tree-sitter/<language>/highlights.scm

    Verify the .wasm starts with \0asm (not a 404 page): xxd ... | head -1

  2. Register the parser in src/index.tsx

    importlangWasmfrom"../assets/tree-sitter/<language>/tree-sitter-<language>.wasm"with{type: "file"};importlangHighlightsfrom"../assets/tree-sitter/<language>/highlights.scm"with{type: "file"};

    Then add an entry to the addDefaultParsers() call:

    addDefaultParsers([// ... existing entries{filetype: "<language>",wasm: resolve(__dir,langWasm),queries: {highlights: [resolve(__dir,langHighlights)]},},]);
  3. Ensure filetype.ts has the mappingsrc/utils/filetype.ts maps file extensions to language names. Add an entry if it doesn't exist yet.

  4. Add a LICENSE file — Place a LICENSE in assets/tree-sitter/<language>/ with the MIT notice from the grammar repo. See assets/tree-sitter/python/LICENSE for the format.

  5. Verifybun run check should show the new .wasm and .scm in the build output.

License

MIT

About

A terminal-based code review tool

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages