Thank you for your interest in contributing to greg! This guide will help you get started with development and making contributions.
- Quick Start
- Prerequisites
- Development Setup
- Development Workflow
- Project Structure
- Making Changes
- Testing
- Code Style
- Submitting Changes
- Common Tasks
- Getting Help
# Clone and setup
git clone https://github.com/justchokingaround/greg.git
cd greg
just deps # Download dependencies
just tools # Install dev tools
just doctor # Verify environment
just build # Build binary
just test # Run tests- Go 1.21+ - Download Go
- just - Command runner (install just)
- Git - Version control
- mpv - Video player (install mpv)
- golangci-lint - Linter (installed via
just tools) - goimports - Import organizer (installed via
just tools) - air - Hot reload for development (installed via
just tools) - ffmpeg - For download functionality (optional)
just doctorThis will verify:
- Go version
- Required dependencies (mpv, ffmpeg)
- Development tools (golangci-lint, air)
- Project status (go.mod, binary)
# Fork the repository on GitHub, then:
git clone https://github.com/justchokingaround/greg.git
cd greg
# Add upstream remote
git remote add upstream https://github.com/justchokingaround/greg.git
# Verify remotes
git remote -v# Download Go dependencies
just deps
# Install development tools
just tools# Build development binary
just build
# Run the binary
just run
# Or build and run with arguments
just run-args --helpFor rapid development with automatic rebuilds:
# Start hot reload (requires air)
just devThis will automatically rebuild and restart when you save changes.
# Start hot reload for rapid iteration
just dev
# Or manually build and run
just build && just run
# Watch tests in another terminal
just watch-test# Run all pre-commit checks (formats, lints, tests)
just pre-commitThis runs:
just fmt- Format code with gofmt and goimportsjust lint- Run go vet and golangci-lintjust test- Run all unit tests
If this passes, you’re good to commit!
git fetch upstream
git checkout main
git merge upstream/main
git push origin maingreg/ ├── cmd/ │ └── greg/ # Main entry point ├── internal/ # Internal packages (not importable) │ ├── config/ # Configuration + logging │ ├── database/ # SQLite database + migrations │ ├── downloader/ # Download manager (native Go + fallback tools) │ ├── history/ # Watch history management │ ├── player/ # mpv player control (gopv IPC) │ ├── providers/ # Streaming providers │ │ ├── api/ # Shared API client │ │ ├── hianime/ # HiAnime provider (API-only) │ │ ├── sflix/ # SFlix provider (dual implementation) │ │ ├── movies/ # Internal movie scrapers │ │ │ └── sflix/ # SFlix embedded scraper │ │ └── anime/ # Internal anime scrapers │ ├── registry/ # Provider registry │ ├── scraper/ # HTML scraping utilities │ ├── tracker/ # AniList integration (OAuth2 + GraphQL) │ ├── transport/ # HTTP transport layer │ ├── tui/ # Bubble Tea UI components │ │ ├── components/ # Reusable TUI components │ │ ├── pages/ # Full-screen pages │ │ └── styles/ # Theme and styling │ └── watchparty/ # WatchParty functionality ├── pkg/ # Public packages (importable) │ ├── extractors/ # URL extractors │ ├── interfaces/ # Public interfaces │ ├── types/ # Public types │ └── utils/ # Public utilities ├── docs/ # Documentation │ ├── COMMANDS.org # Just commands reference │ ├── CONFIG.org # Configuration guide │ ├── PROVIDERS.org # Provider development guide │ └── dev/ │ ├── ARCHITECTURE.org # System architecture │ └── CONTRIBUTING.org # This file ├── justfile # Build commands and tasks └── AGENTS.md # AI agent documentation
See ARCHITECTURE.org for detailed architecture documentation.
Create descriptive branches:
# Feature branches
git checkout -b feature/add-crunchyroll-provider
git checkout -b feature/download-queue-ui
# Bug fix branches
git checkout -b fix/player-crash-on-exit
git checkout -b fix/search-special-characters
# Documentation branches
git checkout -b docs/update-provider-guideFollow Conventional Commits format:
type(scope): subject body (optional) footer (optional)
Types:
feat- New featurefix- Bug fixdocs- Documentation onlystyle- Code style (formatting, no logic change)refactor- Code refactoringperf- Performance improvementtest- Adding or updating testschore- Maintenance (dependencies, build, etc.)
Examples:
feat(providers): add Crunchyroll provider Implement new anime provider with: - Search functionality - Episode listing - Stream URL extraction - Subtitle support Closes #123
fix(player): prevent crash when mpv exits unexpectedly Add proper cleanup and error handling when mpv process terminates. Check if IPC socket exists before attempting communication. Fixes #456
# Run all unit tests
just test
# Run with verbose output
go test -v ./...
# Run specific package tests
just test-pkg providers
# Run tests with coverage
just test-coverage
# Check for race conditions
just test-race
# Run benchmarks
just benchIntegration tests require network access and are tagged separately:
# Run integration tests
just test-integration
# Run only integration tests
go test -tags=integration -run Integration ./...func TestProviderSearch(t *testing.T) {
// Arrange
provider := NewTestProvider()
ctx := context.Background()
// Act
results, err := provider.Search(ctx, "test query")
// Assert
assert.NoError(t, err)
assert.NotEmpty(t, results)
assert.Equal(t, "Expected Title", results[0].Title)
}func TestParseQuality(t *testing.T) {
tests := []struct {
name string
input string
expected Quality
wantErr bool
}{
{"1080p", "1080p", Quality1080p, false},
{"720p", "720p", Quality720p, false},
{"HD", "hd", Quality720p, false},
{"invalid", "999p", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseQuality(tt.input)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
})
}
}func TestHTTPProvider(t *testing.T) {
// Create test server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"results": []map[string]string{
{"id": "1", "title": "Test Media"},
},
})
}))
defer server.Close()
// Test provider with mock server
provider := NewProvider(server.URL, 30*time.Second)
results, err := provider.Search(context.Background(), "test")
assert.NoError(t, err)
assert.Len(t, results, 1)
assert.Equal(t, "Test Media", results[0].Title)
}To prevent UI regressions, use snapshot testing for Bubble Tea components:
// in internal/tui/components/mycomponent/mycomponent_test.go
package mycomponent
import (
"flag"
"os"
"testing"
"github.com/justchokingaround/greg/internal/tui/tuitest"
)
func TestMain(m *testing.M) {
flag.Parse()
os.Exit(m.Run())
}
func TestMyComponentView(t *testing.T) {
model := New() // Initialize component
view := model.View()
tuitest.AssertSnapshot(t, view)
}Generate/update snapshots:
go test -v ./... -updateThe snapshot (.snap file) will be created in testdata/ directory.
Aim for 80%+ coverage for new code:
# Generate coverage report
just test-coverage
# View in browser
go tool cover -html=coverage.out# Format all code (gofmt + goimports)
just fmtRun this before every commit. The pre-commit check will fail if code is not formatted.
# Run all linters
just lint
# Auto-fix some issues
golangci-lint run --fixFollow Go Code Review Comments and Google’s Go Style Guide.
Key principles:
- Error Handling - Always wrap errors with context
// Good if err != nil { return fmt.Errorf("failed to fetch media %s: %w", id, err) } // Bad if err != nil { return err // No context }
- Context - Always pass
context.Contextas first parameter// Good func Search(ctx context.Context, query string) ([]Media, error) // Bad func Search(query string) []Media // Missing context and error
- Don’t Panic - Return errors instead of panicking
// Good func ParseURL(urlStr string) (*url.URL, error) { u, err := url.Parse(urlStr) if err != nil { return nil, fmt.Errorf("invalid URL: %w", err) } return u, nil } // Bad func ParseURL(urlStr string) *url.URL { u, err := url.Parse(urlStr) if err != nil { panic(err) // Don't panic in library code! } return u }
- Names - Use clear, descriptive names
- Packages: short, lowercase, single-word (
providers,player,config) - Functions: camelCase with verb (
GetMedia,ParseURL,ValidateConfig) - Interfaces: noun or agent noun (
Provider,Player,Downloader) - Variables: short for small scopes (
i,err,ctx), descriptive for large scopes
- Packages: short, lowercase, single-word (
- Comments - Document all exported items
// Search queries the provider for media matching the given query string. // It returns a list of matching media items or an error if the search fails. // // The search is case-insensitive and supports partial matching. func Search(ctx context.Context, query string) ([]Media, error) { // ... }
Provider Implementation:
- Always implement full
Providerinterface - Cache API responses to reduce redundant calls
- Handle all errors gracefully, never panic
- See PROVIDERS.org for detailed guide
TUI Components:
- Follow Bubble Tea patterns (Model/Init/Update/View)
- Handle all keyboard inputs
- Test with different terminal sizes
- Use theme colors from
internal/tui/styles
HTTP Clients:
- Use
internal/providers/httpclient with retry logic - Always set timeouts
- Add proper User-Agent headers
- Handle rate limiting
Before creating a PR:
- [ ] Run
just pre-commitand ensure it passes - [ ] Update documentation if needed
- [ ] Add tests for new functionality
- [ ] Update CHANGELOG if significant change
- [ ] Commit messages follow conventional format
- [ ] Branch is up to date with upstream/main
# Update your branch
git fetch upstream
git rebase upstream/main
# Run pre-commit checks
just pre-commit
# Push to your fork
git push origin feature/your-feature-nameThen:
- Go to GitHub repository
- Click “New Pull Request”
- Select your fork and branch
- Fill out the PR template
# 1. Create provider package
mkdir -p internal/providers/myprovider
# 2. Implement provider (see PROVIDERS.org for full guide)
# - Implement Provider interface
# - Add unit tests
# - Add integration tests
# - Register in init.go
# 3. Test your provider
just test-pkg providers/myprovider
# 4. Update documentation
# Edit docs/PROVIDERS.org to add your providerSee PROVIDERS.org for comprehensive provider development guide.
# 1. Create component directory
mkdir -p internal/tui/components/mycomponent
# 2. Implement Bubble Tea Model interface
# - Init() (tea.Model, tea.Cmd)
# - Update(tea.Msg) (tea.Model, tea.Cmd)
# - View() string
# 3. Add keyboard shortcuts
# - Use consistent key bindings
# - Document in help screen
# 4. Add snapshot tests
# - Create mycomponent_test.go
# - Use tuitest.AssertSnapshot()
# 5. Integrate with main UI
# - Wire up in internal/tui/app.go# Test specific package
just test-pkg providers
# Test specific file
go test -v ./internal/providers/sflix/sflix_test.go
# Test specific function
go test -v -run TestSearch ./internal/providers/...
# Test with verbose output
go test -v ./...# Build with debug symbols
go build -gcflags="all=-N -l" -o greg cmd/greg/main.go
# Run with delve debugger
dlv debug cmd/greg/main.go
# Add debug logging
# Use logger from internal/config
logger.Debug("Debug message", "key", value)# Reset database (WARNING: deletes all data)
just db-reset
# Database is located at:
# ~/.local/share/greg/greg.db
# View database with sqlite3
sqlite3 ~/.local/share/greg/greg.db# Build optimized binary
just build-release
# Build for Windows
just build-windows
# Build for all platforms
just build-all
# Create release archives
just release- Documentation
- COMMANDS.org - Just commands reference
- PROVIDERS.org - Provider development guide
- CONFIG.org - Configuration options
- ARCHITECTURE.org - System architecture
- AGENTS.md - AI agent documentation
- Community
- GitHub Issues - Bug reports and feature requests
- GitHub Discussions - Questions and general discussion
When reporting bugs, include:
- Description - Clear description of the bug
- Steps to Reproduce - Exact steps to reproduce
- Expected Behavior - What should happen
- Actual Behavior - What actually happens
- Environment
- OS and version
- greg version (
greg version) - Go version (
go version) - mpv version (
mpv --version)
- Logs - Relevant error messages or logs
- Screenshots - If applicable
When requesting features, include:
- Use Case - What problem does this solve?
- Proposed Solution - How should it work?
- Alternatives - Other solutions you’ve considered
- Additional Context - Screenshots, examples, references
# Most used commands
just build # Build binary
just run # Build and run
just dev # Hot reload
just test # Run tests
just pre-commit # Pre-commit checks
just fmt # Format code
just lint # Run linters
# See all commands
just --list- Use
just benchto benchmark code changes - Run
just test-raceto detect race conditions - Profile with
go tool pproffor performance bottlenecks - Use
just test-coverageto identify untested code paths
By contributing, you agree that your contributions will be licensed under the MIT License.
—
Thank you for contributing to greg! 🎉
Your contributions help make greg better for everyone.