Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions .ralph-tui/iterations/iteration-001-US-001.log

Large diffs are not rendered by default.

153 changes: 153 additions & 0 deletions .ralph-tui/iterations/iteration-002-US-002.log

Large diffs are not rendered by default.

120 changes: 120 additions & 0 deletions .ralph-tui/iterations/iteration-003-US-004.log

Large diffs are not rendered by default.

246 changes: 246 additions & 0 deletions .ralph-tui/iterations/iteration-004-US-003.log

Large diffs are not rendered by default.

282 changes: 282 additions & 0 deletions .ralph-tui/iterations/iteration-005-US-005.log

Large diffs are not rendered by default.

174 changes: 174 additions & 0 deletions .ralph-tui/iterations/iteration-006-US-006.log

Large diffs are not rendered by default.

178 changes: 178 additions & 0 deletions .ralph-tui/iterations/iteration-007-US-007.log

Large diffs are not rendered by default.

241 changes: 241 additions & 0 deletions .ralph-tui/iterations/iteration-008-US-008.log

Large diffs are not rendered by default.

281 changes: 281 additions & 0 deletions .ralph-tui/iterations/iteration-009-US-009.log

Large diffs are not rendered by default.

220 changes: 220 additions & 0 deletions .ralph-tui/iterations/iteration-010-US-013.log

Large diffs are not rendered by default.

132 changes: 132 additions & 0 deletions .ralph-tui/iterations/iteration-011-US-010.log

Large diffs are not rendered by default.

158 changes: 158 additions & 0 deletions .ralph-tui/iterations/iteration-012-US-011.log

Large diffs are not rendered by default.

214 changes: 214 additions & 0 deletions .ralph-tui/iterations/iteration-013-US-012.log

Large diffs are not rendered by default.

229 changes: 229 additions & 0 deletions .ralph-tui/iterations/iteration-014-US-014.log

Large diffs are not rendered by default.

673 changes: 672 additions & 1 deletion .ralph-tui/progress.md

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions .ralph-tui/session-meta.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"id": "6700ace9-747a-479d-87d7-2d919e1e4bff",
"id": "c7536d28-63ec-4b84-bac6-3daae3e551e2",
"status": "completed",
"startedAt": "2026-01-19T18:22:32.322Z",
"updatedAt": "2026-01-19T18:22:38.081Z",
"startedAt": "2026-01-20T08:35:53.199Z",
"updatedAt": "2026-01-20T19:21:37.603Z",
"agentPlugin": "claude",
"trackerPlugin": "json",
"prdPath": "./tasks/prd.json",
"currentIteration": 0,
"maxIterations": 10,
"currentIteration": 14,
"maxIterations": 50,
"totalTasks": 0,
"tasksCompleted": 0,
"tasksCompleted": 14,
"cwd": "/Users/shepbook/git/github-issues-tui",
"endedAt": "2026-01-19T18:22:38.081Z"
"endedAt": "2026-01-20T19:21:37.603Z"
}
Binary file added GitHub Issues TUI - MiniMax M2.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
162 changes: 162 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# ghissues - GitHub Issues TUI

A terminal-based user interface for reviewing GitHub issues offline. Fetch issues from GitHub, store them locally in a SQLite database, and browse them with an interactive, keyboard-driven UI.

![TUI Screenshot Placeholder]

## Features

- **Offline Access**: Issues are stored locally after syncing
- **Keyboard Navigation**: Fully keyboard-driven interface
- **Markdown Rendering**: Issues and comments rendered with GitHub-flavored markdown
- **Multiple Repositories**: Configure and switch between multiple repos
- **Color Themes**: 6 built-in themes (default, dracula, gruvbox, nord, solarized-dark, solarized-light)
- **Incremental Sync**: Only fetches updated issues on refresh

## Installation

### Prerequisites

- Go 1.25.5 or later
- GitHub Personal Access Token (with `repo` scope for private repositories)

### Building

```bash
git clone https://github.com/yourusername/github-issues-tui.git
cd github-issues-tui
go build -o ghissues ./cmd/ghissues
```

## Configuration

### First-Time Setup

Run the interactive configuration wizard:

```bash
./ghissues config
```

This will prompt you for:
- GitHub Personal Access Token (or use `GITHUB_TOKEN` environment variable)
- Repository owner/name (e.g., `owner/repo`)
- Display preferences (theme, columns, sorting)

### Manual Configuration

The config file is stored at `~/.config/ghissues/config.toml`:

```toml
repositories = ["owner/repo1", "owner/repo2"]
default_repository = "owner/repo1"

[auth]
token = "ghp_xxxxxxxxxxxx" # Optional; also reads GITHUB_TOKEN env

[display]
theme = "dracula"
columns = ["number", "title", "author", "date", "comments"]
sort = "updated"
sort_order = "desc"

[database]
path = ".ghissues.db"
```

### Authentication

Authentication is checked in this order:
1. `GITHUB_TOKEN` environment variable
2. `token` in config file
3. GitHub CLI token (`gh auth token`)

Create a token at: https://github.com/settings/tokens (needs `repo` scope)

## Usage

### Commands

| Command | Description |
|---------|-------------|
| `./ghissues config` | Run interactive configuration setup |
| `./ghissues sync` | Manually sync issues from GitHub |
| `./ghissues tui` | Launch the TUI interface |
| `./ghissues themes` | Preview available color themes |
| `./ghissues repos` | List configured repositories |
| `./ghissues` | Run with defaults (TUI + auto-refresh) |

### Flags

| Flag | Description | Default |
|------|-------------|---------|
| `--db PATH` | Database file path | `.ghissues.db` |
| `--repo OWNER/REPO` | Repository to use | From config |

Example:
```bash
./ghissues --db /path/to/db.sqlite --repo owner/repo
```

## TUI Keybindings

| Key | Action |
|-----|--------|
| `j` / `Down` | Move down in issue list |
| `k` / `Up` | Move up in issue list |
| `Enter` | Open issue comments |
| `r` | Refresh issues from GitHub |
| `m` | Toggle markdown rendering |
| `?` | Toggle help overlay |
| `q` | Go back / Quit |
| `Ctrl+C` | Force quit |

### Sorting and Filtering

Press `s` to cycle through sorting options:
- By updated date
- By created date
- By issue number
- By comment count

Toggle sort order with `o`.

## Project Structure

```
├── cmd/ghissues/ # CLI commands and TUI
│ ├── main.go # Entry point
│ ├── config.go # Setup wizard
│ ├── sync.go # GitHub sync logic
│ ├── tui.go # Main TUI application
│ └── themes.go # Theme preview
├── internal/
│ ├── config/ # Configuration management
│ ├── auth/ # GitHub authentication
│ ├── github/ # GitHub API client
│ ├── db/ # SQLite database layer
│ ├── themes/ # Color theme definitions
│ └── errors/ # Error handling
```

## Development

### Running Tests

```bash
go test ./...
```

### Adding a New Theme

Themes are defined in `internal/themes/themes.go`:

```go
var themes = []Theme{
{Name: "mytheme", ...},
}
```

## License

MIT
158 changes: 158 additions & 0 deletions cmd/ghissues/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package main

import (
"context"
"flag"
"fmt"
"os"

"github.com/shepbook/ghissues/internal/auth"
"github.com/shepbook/ghissues/internal/config"
"github.com/shepbook/ghissues/internal/db"
"github.com/shepbook/ghissues/internal/github"
)

var dbFlag string
var repoFlag string

func init() {
flag.StringVar(&dbFlag, "db", "", "Path to the database file (default: .ghissues.db in current directory)")
flag.StringVar(&repoFlag, "repo", "", "Repository to use (owner/repo format)")
}

func main() {
flag.Parse()

// Check for 'config' subcommand
if len(os.Args) > 1 && os.Args[1] == "config" {
if err := runSetup(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
return
}

// Check for 'sync' subcommand
if len(os.Args) > 1 && os.Args[1] == "sync" {
if err := runSync(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
return
}

// Check for 'themes' subcommand
if len(os.Args) > 1 && os.Args[1] == "themes" {
if err := runThemes(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
return
}

// Check for 'repos' subcommand
if len(os.Args) > 1 && os.Args[1] == "repos" {
if err := runRepos(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
return
}

// Check for 'tui' subcommand
if len(os.Args) > 1 && os.Args[1] == "tui" {
if err := runTUI(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
return
}

// Check if config exists, if not run setup
if !config.Exists() {
fmt.Println("Welcome to ghissues! Let's set up your configuration.")
fmt.Println()

if err := runSetup(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

// Load configuration
cfg, err := config.Load()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
os.Exit(1)
}

// Determine database path
dbPath, err := db.GetPath(dbFlag, cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}

// Verify database path is writable
if err := db.IsWritable(dbPath); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}

fmt.Printf("Database path: %s\n", dbPath)

// Validate authentication
if err := validateAuth(); err != nil {
fmt.Fprintf(os.Stderr, "Authentication error: %v\n", err)
os.Exit(1)
}

// Run TUI with auto-refresh
fmt.Println("Starting TUI with auto-refresh...")
if err := RunTUIWithRefresh(dbPath, cfg); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

// validateAuth attempts to get and validate a GitHub token
func validateAuth() error {
token, source, err := auth.GetToken()
if err != nil {
return err
}

fmt.Printf("Using GitHub token from %s...\n", source)

// Validate token by making an API call
client := github.NewClient(token)
ctx := context.Background()
if err := client.ValidateToken(ctx); err != nil {
return err
}

return nil
}

// runTUI runs the TUI application
func runTUI() error {
// Check if config exists
if !config.Exists() {
return fmt.Errorf("configuration not found. Run 'ghissues config' first")
}

// Load configuration
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}

// Determine database path
dbPath, err := db.GetPath(dbFlag, cfg)
if err != nil {
return err
}

// Run the TUI
return RunTUI(dbPath, cfg)
}
Loading