Multi-forge MR/CI management TUI for GitHub and GitLab
Project description
One TUI. Every forge. Full review.
A terminal-native code review inbox for developers who work across GitHub and GitLab.
Your team uses GitHub. Another uses GitLab. You live in the terminal. tongs gives you a single review inbox across forges, with syntax-highlighted diffs and inline comments, right where you already work.
lazygit is great for git operations. tongs picks up where it stops, at code review.
Documentation | PyPI | Issues
Install
# Recommended: install with pipx or uvx for isolation
pipx install tongs
# or
uvx install tongs
# Or plain pip
pip install tongs
Requires Python 3.12+. No other system dependencies.
From source
git clone https://github.com/andre-motta/tongs.git
cd tongs
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
Quick start
# Run tongs -- it scans ~/git by default
tongs
# Override the scan root for a single run
tongs --scan-root ~/projects
To change the default permanently, set scan_root in ~/.config/tongs/config.toml:
[general]
scan_root = "~/projects"
tongs discovers repos under your scan root, reads their git remotes, and populates the inbox. Auth tokens come from your existing gh and glab CLI logins automatically.
First-time auth setup
# GitHub
gh auth login
# GitLab (gitlab.com)
glab auth login
# GitLab (self-hosted)
glab auth login --hostname gitlab.example.com
tongs never stores your tokens. It delegates to gh auth token / glab auth token at runtime, falling back to ~/.netrc and then the system keyring (if the optional keyring package is installed).
Features
Inbox
- Multi-forge inbox -- GitHub PRs and GitLab MRs in one view, auto-detected from your git remotes
- Three tabs -- My Reviews, My MRs, All Open with lazy loading and parallel fetching
- Per-repo scope -- click any repo in the repo list to filter the inbox to just that project
- Sortable -- press
sto cycle sort order (updated, title, CI status, author) - Repo list -- searchable, filterable by forge type (GH/GL/All), sortable by name/forge/host
Diff review
- Split-pane viewer -- file tree on the left, diff content on the right
- Syntax highlighting -- 500+ languages via Pygments, bulk-highlighted per file for performance
- Word-level diffs -- changed words highlighted bold+underline within modified lines
- Context folding -- long unchanged sections collapse to "... N unchanged lines ..." markers
- Truncated diff handling -- files too large for the API appear in the file tree with +/- stats and a "view in browser" prompt
- Markdown preview -- toggle rendered preview for
.mdfiles withm - File navigation -- jump between files with
n/Shift+N
Commenting
- Inline comments -- press
con any diff line to open the bottom-docked comment editor - Discussion threads -- existing inline comments appear as gutter markers in the diff; press
dto expand/collapse threads with full Markdown rendering, replies, and resolution status - Discussion tab -- card-based view of all MR discussions with diff snippets, Rich Markdown threads, reply/resolve actions, and cross-tab jump-to-diff navigation
- Comment navigation -- jump between comments with
]/[, wrapping around - Reply to threads -- press
rto reply to a discussion from the diff or discussion tab - Resolve threads -- press
Rto resolve/unresolve a discussion (GitHub and GitLab; double-press to confirm) - Visual line selection -- select multiple lines with
Shift+J/Shift+KorCtrl+Clickfor multi-line comments - Suggested changes -- press
F3to open your$EDITORwith the selected code; edit it, and tongs posts a suggestion block (GitHubsuggestion/ GitLabsuggestion:-0+Nsyntax) - External editor -- press
F2inside the comment editor to switch to your preferred editor - General comments -- press
cfrom the overview tab to post a top-level MR comment
Pipeline / CI
- Three-level drill-down -- browse pipelines, drill into jobs grouped by stage, drill into full job logs
- ANSI log rendering -- CI color output rendered natively via
Text.from_ansi()with line numbers - Cancel / Retry -- cancel running pipelines or individual jobs (
C), retry failed pipelines or jobs (R), with double-press confirmation - Log search -- press
/in the log view to search for text across the full job output - Open in editor -- press
F2to open the job log in your$EDITORfor deeper analysis - Open in browser -- press
oto jump to the pipeline or job in the web UI - MR-scoped -- Pipeline tab shows only pipelines associated with the current MR/PR
- Lazy loading -- pipeline data is fetched only when the Pipeline tab is first opened
Caching
- SQLite-backed cache -- API responses are cached locally in SQLite (via aiosqlite) for snappy navigation
- Transparent caching layer --
CachedForgeClientwraps forge clients, caching reads and invalidating on mutations - Per-key TTL -- MR lists (60s) and diffs (300s) have configurable time-to-live; expired entries are pruned automatically
- LRU eviction -- size-capped at 100 MB by default; recently accessed entries are protected from eviction
- WAL mode -- concurrent reads never block the UI event loop
- Clear Cache -- available from the command palette (
Ctrl+P) to force a fresh fetch
Actions
- Approve (
A) -- approve the MR/PR with double-press confirmation - Unapprove (
U) -- revoke your approval (GitLab) - Merge (
M) -- merge with double-press confirmation - Close (
X) -- close with double-press confirmation - Merge readiness -- visual indicator showing blockers (draft, conflicts, CI failing, merge status)
Navigation
- Command palette (
Ctrl+P) -- context-aware fuzzy search across all available actions - Commits tab -- browse the full commit history for any MR/PR
- Open in browser (
o) -- jump to the MR/PR in your default browser - Copy URL (
Ctrl+Y) -- yank the MR URL to clipboard - Refresh (
Ctrl+R) -- reload the current view
Keybindings
Global
| Key | Action |
|---|---|
Ctrl+P |
Command palette |
? |
Help |
q |
Quit / Back |
Ctrl+R |
Refresh current view |
o |
Open in browser |
Inbox
| Key | Action |
|---|---|
1 |
My Reviews tab |
2 |
My MRs tab |
3 |
All Open tab |
s |
Cycle sort order |
r |
Repo list / Back |
Enter |
Open MR detail |
Repo list
| Key | Action |
|---|---|
/ |
Filter repos |
f |
Cycle forge filter (All/GH/GL) |
s |
Cycle sort order (name/forge/host) |
Enter |
Open scoped inbox for repo |
MR detail
| Key | Action |
|---|---|
1-5 |
Switch tabs (Overview/Diff/Commits/Discussion/Pipeline) |
c |
Add comment |
A |
Approve (press twice) |
U |
Unapprove (press twice) |
M |
Merge (press twice) |
X |
Close (press twice) |
Ctrl+Y |
Copy MR URL |
Diff viewer
| Key | Action |
|---|---|
j / k |
Move cursor down / up |
J / K |
Extend selection down / up |
Ctrl+Click |
Extend selection to clicked line |
] / [ |
Jump to next / previous comment |
d |
Expand / collapse discussion thread |
r |
Reply to discussion on current line |
R |
Resolve / unresolve discussion (press twice) |
c |
Comment on current line or selection |
F3 |
Suggest changes (opens $EDITOR) |
n / Shift+N |
Next / previous file |
m |
Toggle Markdown preview |
Escape |
Clear selection |
Comment editor
| Key | Action |
|---|---|
Ctrl+S |
Submit comment |
Escape |
Cancel (press twice if text entered) |
F2 |
Open in external editor |
Discussion tab
| Key | Action |
|---|---|
j / k |
Move between discussion cards |
Enter |
Jump to diff location (inline discussions) |
r |
Reply to focused discussion |
R |
Resolve / unresolve (press twice) |
f |
Cycle filter (All / Unresolved / Resolved) |
] / [ |
Jump to next / previous unresolved |
Pipeline tab
| Key | Action |
|---|---|
j / k |
Move between pipeline / job cards |
Enter |
Drill into jobs (from pipeline) or log (from job) |
Escape |
Drill out one level |
C |
Cancel pipeline or job (press twice) |
R |
Retry pipeline or job (press twice) |
o |
Open pipeline / job in browser |
F2 |
Open job log in external editor |
/ |
Search job log text |
Configuration
tongs uses ~/.config/tongs/config.toml (or the platform-appropriate config directory). All settings have sensible defaults.
[general]
scan_root = "~/git"
scan_depth = 5
[ui]
theme = "monokai"
diff_style = "unified"
ascii_mode = false # Set true for minimal terminals
[cache]
mr_list_ttl = 60
diff_ttl = 300
max_size_mb = 100
[concurrency]
max_parallel = 8
request_timeout = 30
# Self-hosted forges
[hosts.my-gitlab]
hostname = "gitlab.example.com"
forge_type = "gitlab"
[hosts.my-ghes]
hostname = "github.corp.com"
forge_type = "github"
Plugin system
tongs uses a plugin architecture based on Python entry points. The MCP server, for example, is itself a plugin. You can extend tongs with new commands, screens, and lifecycle hooks by writing your own.
Writing a plugin
- Subclass
TongsPlugin:
from tongs.plugins.base import TongsPlugin
class MyPlugin(TongsPlugin):
@property
def name(self) -> str:
return "my-plugin"
@property
def version(self) -> str:
return "0.1.0"
async def on_app_ready(self, app) -> None:
"""Called after the TUI app is mounted."""
async def on_app_shutdown(self, app) -> None:
"""Called before app exit."""
def get_commands(self) -> list[tuple[str, str, object]]:
"""Return (display, help_text, callback) tuples for the command palette."""
return [("My Action", "Does something useful", self._do_it)]
def get_screens(self) -> dict[str, type]:
"""Return screen_name -> Screen class mappings."""
return {}
- Register it as an entry point in your package's
pyproject.toml:
[project.entry-points."tongs.plugins"]
my-plugin = "my_package.plugin:MyPlugin"
- Install your package (or
pip install -e .for development) and tongs discovers it automatically on startup.
Plugin configuration
Plugins are enabled by default. To disable a plugin, add a [plugins.<name>] section to your config:
# ~/.config/tongs/config.toml
[plugins.mcp]
enabled = false
Plugin hooks
| Hook | When it fires |
|---|---|
on_app_ready(app) |
After the TUI mounts |
on_app_shutdown(app) |
Before app exit |
get_commands() |
Command palette collects entries |
get_screens() |
Screen registry collects routes |
MCP server (plugin)
tongs ships an optional Model Context Protocol server, packaged as a built-in plugin, that lets AI assistants interact with your merge requests programmatically.
Install
pip install tongs[mcp]
Start
tongs-mcp
The server communicates over stdio, so you can wire it into any MCP-compatible client (Claude Code, Claude Desktop, etc.). It reuses the same forge configuration and auth tokens as the TUI, so no extra setup is needed. The MCP plugin also adds a "Start MCP Server" command to the command palette.
Tools
| Tool | Description |
|---|---|
list_mrs |
List open/closed/merged MRs for a repository |
get_mr |
Get detailed MR info (description, approvals, labels, conflicts) |
get_mr_diff |
Get the unified diff for an MR |
post_comment |
Post a general comment on an MR |
approve_mr |
Approve an MR |
list_pipelines |
List CI pipelines for an MR |
All tools accept a repo_path in hostname/owner/repo format (e.g. github.com/acme/app). Destructive actions (merge, close, cancel) are intentionally excluded as a security boundary.
tongs is for you if
- You review code across both GitHub and GitLab
- You want diffs, inline comments, and approvals in your terminal
- You manage many repos and want a single inbox
- You prefer keyboard-driven workflows over browser tabs
| tongs | gh-dash | GitHub/GitLab web | |
|---|---|---|---|
| GitHub + GitLab | Yes | GitHub only | One at a time |
| Terminal-native diffs | Yes | No | No |
| Inline comments | Yes | No | Yes |
| Discussion threads + tab | Yes | No | Yes |
| Suggested changes | Yes | No | Yes |
| Pipeline / CI drill-down | Yes | No | Yes |
| Approve / Merge | Yes | No | Yes |
| Command palette | Yes | No | N/A |
| MCP server (AI integration) | Yes | No | No |
| Plugin system | Yes | No | No |
| SQLite offline cache | Yes | No | N/A |
| Rate limit auto-retry | Yes | No | N/A |
| Zero config auth | Yes | Yes | N/A |
| Self-hosted forges | Yes | Yes | N/A |
Roadmap
| Phase | What | Status |
|---|---|---|
| 1 | Scanner, forge abstraction, GitLab client, TUI shell, inbox, repo list | Done |
| 2 | MR detail view, diff viewer, syntax highlighting, GitHub client, commits tab | Done |
| 3 | MR actions (approve, merge, close), inline comments, suggested changes | Done |
| 4 | Discussion threads (inline + card-based tab with diff snippets), command palette, comment navigation, cross-tab jump-to-diff | Done |
| 5 | Pipeline/CI management (three-level drill-down, job logs, retry, cancel, log search) | Done |
| 6 | Plugin system (entry-point-based, MCP refactored as plugin) | Done |
| 7 | SQLite caching, MCP server, GitHub thread resolution, MkDocs site | Done |
| 8 | CachedForgeClient, GitHub CI/approvals, rate limit retry, keyring auth, sort cycling, bulk highlighting, truncated diffs | Done |
Contributing
See CONTRIBUTING.md for development setup, code style, and how to add forge backends. 618 tests and growing.
tongs is early enough that contributions shape the architecture. The plugin system makes it easy to add new commands, screens, and lifecycle hooks without touching core code. Check the issues for good starting points, or open one to discuss what you'd like to build.
Why "tongs"?
In metalworking, tongs grip hot work across the forge. This tool grips your merge requests across forges.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tongs-0.4.0.tar.gz.
File metadata
- Download URL: tongs-0.4.0.tar.gz
- Upload date:
- Size: 9.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce84410907c9909c6d62359cb91d0516bc75e3433ea55a79d51e7342ea12ab44
|
|
| MD5 |
88fe1a808cf0384bcca8b7a0ba412b7b
|
|
| BLAKE2b-256 |
f2c50ffb32762c4bab1beabaafebb155df5e21e0f65949f6158881ac6ad6ceeb
|
Provenance
The following attestation bundles were made for tongs-0.4.0.tar.gz:
Publisher:
publish.yml on andre-motta/tongs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tongs-0.4.0.tar.gz -
Subject digest:
ce84410907c9909c6d62359cb91d0516bc75e3433ea55a79d51e7342ea12ab44 - Sigstore transparency entry: 2209549415
- Sigstore integration time:
-
Permalink:
andre-motta/tongs@0e9717d44d26eb0249a25e135d06cc2edc69ebf4 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/andre-motta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0e9717d44d26eb0249a25e135d06cc2edc69ebf4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tongs-0.4.0-py3-none-any.whl.
File metadata
- Download URL: tongs-0.4.0-py3-none-any.whl
- Upload date:
- Size: 82.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf51720e82ef2a3e9439df7b7907ffd9cc24d1b4f81514a213556cb031ad3b8e
|
|
| MD5 |
61314ed9cccaee38ece913d7cccba91b
|
|
| BLAKE2b-256 |
ee6d67771defa8fa2c3c124d9b1c49678206c87e7417bea31bc401b971e6c267
|
Provenance
The following attestation bundles were made for tongs-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on andre-motta/tongs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tongs-0.4.0-py3-none-any.whl -
Subject digest:
bf51720e82ef2a3e9439df7b7907ffd9cc24d1b4f81514a213556cb031ad3b8e - Sigstore transparency entry: 2209549469
- Sigstore integration time:
-
Permalink:
andre-motta/tongs@0e9717d44d26eb0249a25e135d06cc2edc69ebf4 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/andre-motta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0e9717d44d26eb0249a25e135d06cc2edc69ebf4 -
Trigger Event:
push
-
Statement type: