Files
diple/main.go
2026-07-28 11:46:07 +02:00

162 lines
5.4 KiB
Go

package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
tea "github.com/charmbracelet/bubbletea"
)
func main() {
defaults := defaultConfig()
defaultConfigPath, err := configPath()
if err != nil {
exitf("configuration: %v", err)
}
var (
configFile = flag.String("config", defaultConfigPath, "TOML configuration file")
repo = flag.String("repo", "", "optional GitHub repository filter as owner/name (or GH_REPO)")
poll = flag.Duration("poll", defaults.RefreshInterval.Duration, "refresh interval")
showAll = flag.Bool("all", defaults.ShowAll, "show all open PRs in --repo, not only PRs assigned to you")
limit = flag.Int("limit", defaults.Limit, "maximum open PRs to load (1-1000)")
endpoint = flag.String("endpoint", defaults.Endpoint, "GitHub GraphQL endpoint")
theme = flag.String("theme", defaults.Theme, "color theme: dark, light, high-contrast, or no-color")
foldResolved = flag.Bool("fold-resolved", defaults.Display.FoldResolved, "start resolved threads folded")
listWidth = flag.Int("thread-list-width", defaults.Display.ThreadListWidthPercent, "thread list width as terminal percentage (20-60)")
dashboardMode = flag.String("dashboard-mode", defaults.Display.DashboardMode, "dashboard navigation: intermediate or hotkey")
compactReviews = flag.Bool("compact-reviews", defaults.Display.CompactReviews, "aggregate submitted review history")
pathScroll = flag.Bool("path-scroll", defaults.Paths.Scroll, "scroll truncated paths")
pathScrollRate = flag.Duration("path-scroll-interval", defaults.Paths.ScrollInterval.Duration, "path scrolling interval")
cacheEnabled = flag.Bool("cache", defaults.Cache.Enabled, "enable local read cache fallback")
cacheMaxAge = flag.Duration("cache-max-age", defaults.Cache.MaxAge.Duration, "maximum offline cache age (0 disables expiry)")
cacheDir = flag.String("cache-dir", defaults.Cache.Directory, "local read cache directory")
)
flag.Parse()
visited := map[string]bool{}
flag.Visit(func(item *flag.Flag) { visited[item.Name] = true })
config, err := loadConfig(*configFile, visited["config"] || os.Getenv("GH_THREADS_CONFIG") != "")
if err != nil {
exitf("configuration: %v", err)
}
if visited["repo"] {
config.Repository = *repo
} else if envRepo := os.Getenv("GH_REPO"); envRepo != "" {
config.Repository = envRepo
}
if visited["poll"] {
config.RefreshInterval.Duration = *poll
}
if visited["all"] {
config.ShowAll = *showAll
}
if visited["limit"] {
config.Limit = *limit
}
if visited["endpoint"] {
config.Endpoint = *endpoint
}
if visited["theme"] {
config.Theme = *theme
}
if visited["fold-resolved"] {
config.Display.FoldResolved = *foldResolved
}
if visited["thread-list-width"] {
config.Display.ThreadListWidthPercent = *listWidth
}
if visited["dashboard-mode"] {
config.Display.DashboardMode = *dashboardMode
}
if visited["compact-reviews"] {
config.Display.CompactReviews = *compactReviews
}
if visited["path-scroll"] {
config.Paths.Scroll = *pathScroll
}
if visited["path-scroll-interval"] {
config.Paths.ScrollInterval.Duration = *pathScrollRate
}
if visited["cache"] {
config.Cache.Enabled = *cacheEnabled
}
if visited["cache-max-age"] {
config.Cache.MaxAge.Duration = *cacheMaxAge
}
if visited["cache-dir"] {
config.Cache.Directory = *cacheDir
}
if err := validateConfig(config); err != nil {
exitf("configuration: %v", err)
}
var owner, name string
if config.Repository != "" {
parts := strings.Split(config.Repository, "/")
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
exitf("--repo must be owner/name")
}
owner, name = parts[0], parts[1]
}
if err := applyTheme(config.Theme); err != nil {
exitf("configuration: %v", err)
}
token, err := resolveToken(config.Endpoint)
if err != nil {
exitf("authenticate: %v", err)
}
client := NewGitHubClient(config.Endpoint, token)
var service GitHubService = client
if config.Cache.Enabled {
cacheDir := config.Cache.Directory
if cacheDir == "" {
cacheDir, err = defaultCacheDir()
if err != nil {
exitf("configuration: %v", err)
}
}
service = NewCachedGitHubService(client, cacheDir, config.Cache.MaxAge.Duration)
}
statePath := filepath.Join(filepath.Dir(*configFile), "state.json")
app := NewAppWithSettings(
service, owner, name, config.ShowAll, config.Limit, config.RefreshInterval.Duration,
AppSettings{
FoldResolved: config.Display.FoldResolved,
ThreadListWidthPercent: config.Display.ThreadListWidthPercent,
DashboardMode: config.Display.DashboardMode,
CompactReviews: config.Display.CompactReviews,
ReadState: loadReadState(statePath),
PathScroll: config.Paths.Scroll,
PathScrollInterval: config.Paths.ScrollInterval.Duration,
ThreadStatusOrder: config.Threads.StatusOrder,
ThreadWithinStatus: config.Threads.WithinStatus,
},
)
if _, err := tea.NewProgram(app, tea.WithAltScreen()).Run(); err != nil {
exitf("run TUI: %v", err)
}
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if value != "" {
return value
}
}
return ""
}
func exitf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "gh-threads: "+format+"\n", args...)
os.Exit(1)
}
// Keep interface drift visible at compile time.
var _ GitHubService = (*GitHubClient)(nil)
var _ GitHubWriteService = (*GitHubClient)(nil)
var _ GitHubWriteService = (*CachedGitHubService)(nil)