Files
diple/README.md

663 lines
22 KiB
Markdown

> [!IMPORTANT]
> **This entire repository is AI-generated.** The source code, tests, and
> documentation were produced through AI-assisted development.
>
> **Repository owner's two cents:**
> Like the previous text says this Repo is entirely slop coded.
> I guided the ai (gpt5.6-sol) as best as I could and got out the program I desired.
> Use this at your own risk, especially the AI integration.
> _No AI Agents were harmed during creation of this Program_
# diple
`diple` is a keyboard-first terminal interface for reading and responding to
GitHub pull request reviews. It is designed primarily for the person receiving
a review: it keeps the PR description, status, changed code, review threads,
and the actions needed to address feedback in one terminal application.
The project is under active development. GitHub write actions are guarded by
the permissions reported for the current user and ask for confirmation where
the result is consequential. The optional AI review feature is experimental,
disabled by default, and local-only.
## What diple does
### Pull request picker
- Loads open PRs assigned to the authenticated user across repositories.
- Groups the picker by repository.
- Can be restricted to one `owner/repository`.
- Can show every open PR in a selected repository.
- Uses a disk cache to display a recent snapshot immediately while live data
loads.
### Dashboard
- Shows the title, Markdown description, branches, author, assignees,
reviewers, labels, milestone, merge state, review decision, checks, change
statistics, submitted reviews, timeline activity, and PR conversation.
- Reports whether the PR has conflicts.
- Attempts to identify conflicting files with a read-only temporary Git
analysis. This does not inspect or modify the current Git or Jujutsu
checkout.
- Shows check-run annotations independently so a failure in one subsection
does not blank the rest of the dashboard.
- Provides a Health popup containing API, cache, persistence, conflict-scan,
rate-limit, write-capability, and AI-provider diagnostics.
### Review threads
- Displays review comments beside the exact review-time diff hunk when GitHub
provides it, even when the file has since changed.
- Syntax-highlights code using the file path to choose a lexer.
- Highlights the reviewed line range and exact changed spans.
- Wraps long source lines as continuation rows without inventing line numbers.
- Renders GitHub Flavored Markdown, including quoted replies, inline and
fenced code, lists, task lists, tables, links, emphasis, strikethrough,
emoji, and GitHub alerts.
- Renders GitHub suggestion blocks as syntax-highlighted removal/addition
previews.
- Shows deterministic per-author colors and read-only reaction counts.
- Folds resolved threads by default and distinguishes unread or updated local
state.
- After resolving the selected thread, keeps the cursor nearby by selecting
the next thread, or the previous thread when resolving the last one.
- Supports fuzzy path search; whitespace-separated terms may match separate
portions of the same path.
- Supports configurable status and within-status ordering.
### GitHub write actions
When GitHub reports that the authenticated user has permission, diple can:
- reply to review threads;
- resolve and unresolve review threads;
- edit the PR title, Markdown description, target branch, requested reviewers,
and assignees;
- enable or disable auto-merge; and
- merge immediately when the PR is eligible.
The UI explains unavailable actions through its write-capability gate.
Metadata and reply drafts are persisted locally so cancellation or a restart
does not silently discard work. Replies, thread resolution changes, and PR
metadata or people edits are also placed in a durable FIFO queue before they
are sent. If the most recent cached snapshot granted the action, it may be
queued while offline and is replayed automatically after connectivity returns.
Merge and auto-merge actions remain online-only.
Reactions are currently read-only. Assigning labels or milestones is not
implemented yet.
## Requirements
- Go 1.24 or newer to build from source.
- An authenticated [GitHub CLI](https://cli.github.com/) installation, or a
supported GitHub token environment variable.
- Git 2.38 or newer for conflicting-file discovery. The rest of the PR remains
usable if that optional scan cannot run.
- A terminal with reasonable Unicode support.
- Optional: an authenticated Codex CLI for experimental local AI review.
## Install and run
From a source checkout:
```sh
go install .
gh auth login
diple
```
Run without installing:
```sh
go run .
```
Use `go run .`, not `go run main.go`. The latter omits the other Go files in
the package.
By default, diple finds open PRs assigned to the authenticated user across all
repositories:
```sh
diple
```
Limit the picker to one repository:
```sh
diple --repo owner/repository
```
Include every open PR in that repository:
```sh
diple --repo owner/repository --all
```
Adjust polling or inspect all command-line options:
```sh
diple --poll 15s
diple --version
diple --help
```
Command-line options override configuration values. `GH_REPO` supplies the
default repository only when `--repo` is absent.
## Authentication
Credential lookup uses the first available value in this order:
1. `GH_TOKEN`
2. `GITHUB_TOKEN`
3. `GH_ENTERPRISE_TOKEN`
4. `GITHUB_ENTERPRISE_TOKEN`
5. the token returned by `gh auth token` for the endpoint host
For normal interactive use:
```sh
gh auth login
diple
```
For GitHub Enterprise Server, authenticate the host and provide its GraphQL
endpoint:
```sh
gh auth login --hostname github.example.com
diple \
--repo owner/repository \
--endpoint https://github.example.com/api/graphql
```
The token must have sufficient access to read the selected repositories.
Write actions additionally depend on the permissions GitHub reports for the
particular PR or thread.
## Navigation
The defaults are Vim-like and every binding is configurable.
- `j` / `k`: move down / up
- `h` / `l`: switch panes or move left / right in the active context
- `enter`: open or toggle the selected item
- `b`: go back outside text editing
- `d`: open the dashboard
- `tab`: hide or show the thread list
- `/`: fuzzy-search thread file paths
- `n` / `N`: next / previous unread thread
- `c`: reply to the selected thread
- `R`: resolve or unresolve the selected thread
- `r`: refresh
- `H`: open Health
- `A`: open the experimental local AI menu
- `?`: show all bindings for the current screen
- `q`: quit
Compact footers show only the first configured key for each action. The
contextual help popup shows all alternatives and is the authoritative in-app
reference.
Set `mouse = true` to enable mouse-wheel scrolling. Each wheel event moves the
focused pane by three items or rendered lines. Mouse reporting remains disabled
by default so normal terminal text selection is unchanged; with mouse reporting
enabled, terminals commonly require holding Shift while selecting text.
Editable text fields default to Vim-style modal editing, including PR metadata,
reply, and local-AI discussion fields. They support Normal, Insert, and Visual
modes, word/find motions, deletion, system clipboard yank/paste, and
soft-wrap-aware movement. The active mode and input are shown in a Neovim-style
footer bar. Set `editing.mode = "standard"` for non-modal inputs with arrow-key
cursor movement, including movement across wrapped lines. Search remains a
dedicated insert-only filter. Target-branch, reviewer, and assignee completion use
`ctrl+n` and `ctrl+p`; `enter` accepts the selected completion, while `tab`
moves to the next metadata field. Reviewer and assignee fields accept
comma-separated GitHub usernames. Pending individual review requests and assignees are
prefilled and marked in completion results. Reviewers who already submitted a
review, and requested teams, appear first as protected subdued tokens in the
reviewer field. Their handles retain a darker version of their deterministic
user color, while their brackets and review state use the theme's dim color.
GitHub only permits changing pending review requests.
Protected reviewers cannot receive cursor focus or be deleted, and are excluded
from reviewer completion. Newly entered names gain a visual `@` prefix and
their deterministic user color as soon as they exactly match an eligible
reviewer. At that point the suggestions reset to the remaining eligible users;
pressing Space commits the current reviewer and starts the next entry. Reviewers
already present in the field are excluded from those suggestions. Reviewer
suggestions prioritize recent contributors using the latest 100 commits on the
repository's default branch; this bounded window is also shown in the editor.
Every change is shown in the existing confirmation screen before GitHub is
updated.
## Configuration
Configuration is optional TOML. diple checks:
1. `--config FILE`;
2. `DIPLE_CONFIG`;
3. `$XDG_CONFIG_HOME/diple/config.toml`; and
4. the operating-system configuration directory.
Common default paths:
- Linux: `~/.config/diple/config.toml`
- macOS: `~/Library/Application Support/diple/config.toml`
- macOS fallback: `~/.config/diple/config.toml`
Unknown settings and invalid values are rejected at startup instead of being
silently ignored.
### Example configuration
All settings below show their normal defaults unless noted otherwise:
```toml
theme = "dark"
refresh_interval = "10s" # minimum 2s
repository = "" # optional "owner/repository"
show_all = false # requires repository
limit = 50 # 1-1000
endpoint = "https://api.github.com/graphql"
mouse = false # opt in to accelerated mouse-wheel scrolling
mascot = false # show the optional Difflet terminal mascot
mascot_expressive = false # allow emotional Difflet expressions
mascot_animated = false # allow brief state-driven motion
[display]
fold_resolved = true
thread_list_width_percent = 33 # 20-60
dashboard_mode = "hotkey" # "hotkey" or "intermediate"
compact_reviews = true
viewer_label = "login" # "login" or "you"
[paths]
scroll = false
scroll_interval = "350ms" # minimum 50ms
[threads]
# Each category must occur exactly once. Resolved wins over outdated.
status_order = ["unresolved", "outdated", "resolved"]
within_status = "file" # "file" or "timestamp"
[cache]
enabled = true
max_age = "168h" # 7 days; 0 disables offline expiry
directory = "" # empty uses the OS cache directory
max_entries = 200 # 10-10000
[editing]
mode = "vim" # "vim" or "standard"
[ai]
enabled = false
provider = "codex-cli" # currently the only implemented provider
model = "" # empty selects the provider default
command = "codex"
timeout = "3m"
max_calls = 8
max_request_bytes = 180000
max_run_bytes = 900000
max_file_bytes = 150000
max_context_rounds = 2 # automatic file-request rounds for a thread
max_context_files = 8 # additional files per thread discussion
store_directory = ""
exclude = [
"*.lock", "go.sum", "package-lock.json", "vendor/", "node_modules/",
"dist/", "build/", "generated/", "coverage/", "*.generated.*",
"*_generated.*", "*.min.js", "*.map",
]
sensitive_paths = [
".env", ".env.*", "*.pem", "*.key", "*.p12", "*.pfx",
"*credentials*",
]
```
As with other TOML arrays, setting `exclude` or `sensitive_paths` replaces its
default list. Copy the defaults you still want before adding project-specific
patterns.
`dashboard_mode = "hotkey"` opens threads directly from the picker and leaves
the dashboard on `d`. `"intermediate"` places the dashboard between the picker
and thread viewer.
`compact_reviews = true` summarizes the submitted-review history instead of
showing every repeated `COMMENTED` event.
`viewer_label = "login"` shows your GitHub username like every other author.
Set it to `"you"` to replace your username with `@you` throughout the UI.
Difflet is disabled by default. Set `mascot = true` to show it on the pull
request picker, dashboard, and thread screens. On the dashboard it is centered
beside the first metadata rows so it does not add whitespace below the pull
request title. Editor and popup views hide it to preserve their full usable
height. On other supported screens Difflet sits to the right of the naturally
sized header. Header information wraps when the combined header and mascot do
not fit.
`mascot_animated` controls brief loading, blink, success, and error motion
independently from `mascot_expressive`, which permits stronger emotional
faces. Disabling animation leaves the appropriate final state visible.
Disabling the mascot preserves the normal header layout.
Thread categories are:
- `unresolved`: current unresolved threads;
- `outdated`: unresolved threads attached to outdated code; and
- `resolved`: all resolved threads, including resolved-and-outdated threads.
New threads retain a `NEW THREAD` marker. When an existing thread receives new
comments, diple places a `NEW MESSAGES` divider before the first unread comment
and emphasizes the unread comment rail. Moving the thread-list cursor does not
clear this state. It is cleared when the thread detail pane receives focus, when
the thread is resolved, after the last unread comment becomes visible while
scrolling the focused detail pane, or manually with
`keybindings.threads.mark_read` (`m` by default).
Within a category, `"file"` keeps paths together and `"timestamp"` sorts by
the time the thread was opened.
### Themes
Built-in themes:
- `dark`
- `light`
- `catppuccin` / `catppuccin-mocha`
- `catppuccin-latte`
- `gruvbox` / `gruvbox-dark`
- `gruvbox-light`
- `one-dark-pro`
- `github` / `github-dark`
- `github-light`
- `high-contrast`
- `no-color`
The selected palette also controls Markdown and source-code syntax
highlighting.
For a custom theme, set `theme = "custom"` and override any subset of a
built-in base:
```toml
theme = "custom"
[custom_theme]
base = "catppuccin"
mode = "dark"
title = "#f5c2e7"
dim = "#7f849c"
text = "#cdd6f4"
active_foreground = "#11111b"
active_background = "#89b4fa"
success = "#a6e3a1"
warning = "#f9e2af"
error = "#f38ba8"
editor_foreground = "#cdd6f4"
editor_background = "#313244"
pane_inactive = "#585b70"
pane_active = "#89b4fa"
quote = "#94e2d5"
selection_background = "#45475a"
suggestion_remove_background = "#3b1f2b"
suggestion_add_background = "#193b2a"
changed_remove_background = "#4b1f2b"
changed_add_background = "#1d4b32"
author_palette = ["#89b4fa", "#cba6f7", "#94e2d5", "#f9e2af"]
syntax_theme = "catppuccin-mocha"
```
Colors must use `#RRGGBB`. `mode` is `dark` or `light`; `syntax_theme` must be
an installed Chroma style. Omitted custom values inherit from `base`.
### Keybindings
Each action accepts one or more Bubble Tea key names. Defining an action
replaces its default list; omitted actions retain their defaults. Configuration
validation rejects conflicting assignments within the same active context.
```toml
[keybindings.general]
quit = ["q", "ctrl+c"]
help = ["?", "f1"]
refresh = ["r"]
back = ["b", "esc"]
confirm = ["y"]
reject = ["n", "esc"]
[keybindings.navigation]
down = ["j", "down"]
up = ["k", "up"]
left = ["h", "left"]
right = ["l", "right"]
first = ["g"]
last = ["G"]
page_down = ["ctrl+d", "pgdown"]
page_up = ["ctrl+u", "pgup"]
[keybindings.views]
open = ["enter", "l"]
dashboard = ["d"]
health = ["H"]
edit = ["e"]
auto_merge = ["a"]
merge_now = ["M"]
toggle_list = ["tab"]
ai = ["A"]
[keybindings.threads]
search = ["/"]
clear_filter = ["F"]
next_unread = ["n"]
previous_unread = ["N"]
mark_read = ["m"]
reply = ["c"]
resolve = ["R"]
toggle = ["enter"]
fold_prefix = ["z"]
fold_toggle = ["a"]
[keybindings.input]
cancel = ["esc"]
submit = ["ctrl+s"]
newline = ["enter"]
delete_backward = ["backspace"]
delete_forward = ["delete"]
clear = ["ctrl+u"]
next_field = ["tab"]
previous_field = ["shift+tab"]
next_completion = ["ctrl+n"]
previous_completion = ["ctrl+p"]
line_start = ["home", "ctrl+a"]
line_end = ["end", "ctrl+e"]
[keybindings.vim]
insert = ["i"]
append = ["a"]
insert_line_start = ["I"]
append_line_end = ["A"]
open_below = ["o"]
open_above = ["O"]
replace_character = ["s"]
visual = ["v"]
visual_line = ["V"]
selection_other_end = ["o"]
yank = ["y"]
delete = ["d", "x", "delete"]
delete_before = ["X", "backspace"]
paste = ["p"]
line_start = ["0", "home"]
first_non_blank = ["^"]
line_end = ["$", "end"]
word_forward = ["w"]
big_word_forward = ["W"]
word_backward = ["b"]
big_word_backward = ["B"]
word_end = ["e"]
big_word_end = ["E"]
go_prefix = ["g"]
find_forward = ["f"]
find_backward = ["F"]
till_forward = ["t"]
till_backward = ["T"]
repeat_find = [";"]
repeat_find_reverse = [","]
```
Printable bindings do not steal ordinary text while an input field, search, or
Insert mode owns that key.
## Cache and local data
The read cache is designed for fast startup and offline fallback:
- core picker and PR snapshots are stored separately;
- unchanged content is not rewritten on every refresh;
- changed files are replaced atomically;
- old entries are pruned at `cache.max_entries`; and
- live data automatically replaces the visible cached snapshot.
Cached data is labelled when first shown. A normal refresh does not repeatedly
reintroduce the cached header.
Read state and recoverable drafts live beside the configuration file as
`state.json` and `drafts.json`. Reversible GitHub writes are kept in
`mutation-queue.json` until a live refresh verifies them. Experimental AI data defaults
to the `ai` directory beside the configuration. These files are versioned and
written atomically; sensitive user-authored state uses restrictive permissions.
Cached permission gates are treated as the last known truth while offline:
actions granted by the snapshot can be queued, while actions denied by it stay
disabled. Queued replies and projected PR or thread changes are displayed
optimistically as successful without being written into the read cache. A
pending marker appears only after a live refresh cannot verify the change or a
definite rejection needs attention. Retryable transport failures remain
optimistic and are recorded in Health. Replay preserves global enqueue order,
including across repositories.
If GitHub permissions or the target changed, replay pauses before the first
unsafe operation and presents choices to keep it queued, discard that item and
continue, or discard the remaining queued changes for that PR. A lost reply
response is checked against fresh GitHub thread data first; only when delivery
cannot be determined does diple ask whether to retry or treat it as applied.
## Experimental local AI review
Enable the feature explicitly:
```toml
[ai]
enabled = true
provider = "codex-cli"
command = "codex"
```
Authenticate Codex separately before opening diple:
```sh
codex login
```
The `A` menu can:
- review the current PR and create local-only review threads;
- discuss an existing local AI thread with the same selected model;
- add local-only context to existing unresolved GitHub threads;
- let a focused thread discussion request bounded, exact-head repository files;
- produce small GitHub-style suggestion blocks for contained changes;
- refresh provider status without making an inference call; and
- run one explicitly confirmed, minimal provider test that consumes quota but
sends no PR contents.
Before a review, diple shows the exact head commit, selected model, initial
included and excluded files, byte count, maximum model-call count, and
redaction count. Every run requires confirmation. A focused thread confirmation
also shows its repository-tree summary and the configured automatic
file-request limits.
Full reviews use the authenticated GitHub PR diff. Focused discussions instead
send only the selected thread, its hunk, the complete target file when allowed,
minimal PR identifiers, and a bounded tree for the exact PR head. The model can
request additional paths from that tree, but diple validates and retrieves
their committed blobs through GitHub; the provider never receives local
checkout access.
`sensitive_paths` are absent from the model-visible tree and can never be
requested. `exclude` paths may appear as unavailable tree entries but their
contents are not sent. Binary, submodule, oversized, generated, vendored, and
lock-file content remains unavailable. All supplied content is bounded,
control-sanitized, and checked for secret-like values. Full-review findings
remain restricted to visibly changed lines in the prepared head.
The Codex process runs ephemerally in an empty temporary directory with:
- repository instructions ignored;
- a read-only sandbox;
- approvals disabled;
- a restricted environment;
- tools, commands, browser, network, plugins, memories, and multi-agent
features disabled; and
- a strict structured-output schema.
Attempted tool or file-change events fail the run. Provider output is bounded
and sanitized. Progress may display a provider-exposed reasoning summary, but
diple neither requests nor displays hidden chain-of-thought. Progress and
Health report GitHub, filtering, and provider timing without storing prompts or
repository contents.
AI findings are stored locally, deduplicated deterministically, and marked
outdated when the PR head changes. Resolving a local AI thread remains local.
diple never publishes an AI finding or discussion to GitHub automatically.
Only the Codex CLI provider is currently implemented. The interface permits
future providers, but their privacy and retention behavior must be defined
before they are added.
## Shell completion
Generate completion without contacting GitHub or loading configuration:
```sh
# Bash, current session
source <(diple completion bash)
# Zsh, current session
source <(diple completion zsh)
# Fish, persistent user installation
diple completion fish > ~/.config/fish/completions/diple.fish
```
For persistent Zsh completion, save the output as `_diple` in a directory on
`$fpath` and ensure `compinit` runs. The generated Zsh script also initializes
completion when sourced directly:
```sh
mkdir -p ~/.zfunc
diple completion zsh > ~/.zfunc/_diple
fpath=(~/.zfunc $fpath)
autoload -Uz compinit
compinit
```
Run `diple completion --help` for the supported shells.
## Development
The module path is `git.pablu.de/Pablu/diple`. The repository is a single Go
`package main` built around Bubble Tea, Lip Gloss, Chroma, and Glamour.
```sh
gofmt -w path/to/changed.go
go test ./...
go test -race ./...
go vet ./...
go build ./...
```
Repository architecture, safety invariants, and contributor guidance are
documented in [AGENTS.md](AGENTS.md). Planned work is tracked in
[TODO.md](TODO.md).