This document explains how gh-activity is structured and how data flows through the CLI, so you can extend or maintain it confidently.
- Language: Go
- Entry point:
cmd/main.go - Internal modules under
internal/config/: Token storage and config-file helpersgithub/: HTTP client for GitHub Events API + validation helpershandlers/: CLI-facing formatting and orchestration per commandservices/: Business logic and aggregation per event typemodels/: Typed DTOs for JSON decoding and shared typescustom-error/: Lightweight error wrapper for consistent messages
The CLI fetches recent GitHub user events, transforms them into summaries per command, and prints readable, script-friendly output.
cmd/main.goparses args:<scope> <username> <command> [flags].- Validates scope/command via
internal/github/valid_scope.goand rules frominternal/models/rules.go. - Builds user events URL:
https://api.github.com/users/<username>/events?per_page=100. - Calls
internal/github/events.go::FetchGitHubApiData()to retrieve and decode events:- Adds
Authorization: Bearer <token>header if a token is available. - Handles errors and specific GitHub responses (e.g., bad credentials) gracefully.
- Adds
- Dispatches to a
handlerbased on command:pushes→internal/handlers/push_event.gopulls→internal/handlers/pull_event.goissues→internal/handlers/issue_event.gowatches→internal/handlers/watch_event.gosummary→internal/handlers/summary_all.go
- Handlers delegate business logic to
services/*and print formatted output.
gh-token.go: Stores and loads a personal access token in a user config file (JSON). Implements age warnings (older than 90 days).config-path.go: Resolves platform-specific config file path and RW helpers.
events.go: HTTP GET to GitHub Events API, auth header injection, error parsing and retry logic (removes invalid token and retries once unauthenticated).valid_scope.go: Simple validators usingslices.Contains.
event_model.go: Structs for decoding GitHub Events API JSON (GitResponseObject,ActorModel,RepoModel, etc.).rules.go: Contains CLI rules mapping commands to valid flags.
- Encapsulate event filtering and aggregation logic.
push_event.go,pull_event.go,issue_event.go,watch_event.go: Provide methods likeGetPushEventsRepoWise(limit)etc.summary_all.go: Builds a map of event-type → repo → count. Note on--limitsemantics below.
- Responsible for user-facing output formatting.
- Each handler prints a small overview and then a table-like output aligned with spaces (
strings.Repeatand formatting verbs). The summary handler uses Go’stabwriterfor clean column alignment.
- Entry:
internal/handlers/summary_all.go::GetAllSummary(limit, jsonData) - Service:
internal/services/summary_all.go::GetAllSummary(limit)→ returnsmap[string]map[string]int64keyed by event type (pushEvents,pullEvents,issueEvents,watchEvents). - Handler computes totals by summing counts per repo for each event type, prints:
- A metrics table (Metric/Count) using
tabwriter - An aggregated "Top Repositories (by total activity)" list combining all event types, sorted by total desc
- A metrics table (Metric/Count) using
summary_all.goservice usesIsGreaterThanLimit(len(typeMap), limit)to cap the number of distinct repositories per event type.- This means
--limit Nrestricts how many unique repos (not total events) are included for each event type. - If you want
--limitto mean “process only the last N events overall”: change the service loop to stop on a global counter (e.g.,cnt >= limit) instead of checkinglen(map)per type.
- Define the command in
internal/models/rules.go(valid flags and scope mapping). - Add handler in
internal/handlers/<your_command>.gowith an interface and constructor. - Implement service functions in
internal/services/<your_command>.gothat take[]models.GitResponseObjectand return structured results. - Wire it in
cmd/main.go:- Validate flags
- Instantiate handler and call methods
- Print output aligned consistently (reuse the style from other handlers; prefer
tabwriterfor multi-column tables).
- Use
customerror.Wrap(context, err)to wrap messages consistently. - Token management:
- Set:
gh-activity set token <token>→ writes JSON with timestamp. - Get:
gh-activity get token→ prints token and warns if missing/old.
- Set:
events.goretries unauthenticated if token is bad (and deletes stored token to avoid repeated failures).
- Local run (requires Go):
# Windows
go run .\cmd\main.go user <username> summary --limit 10- Typical commands:
# Pushes
go run .\cmd\main.go user <username> pushes --limit 10
# Pull requests (state required)
go run .\cmd\main.go user <username> pulls --state open --limit 5
# Issues (state required)
go run .\cmd\main.go user <username> issues --state closed --limit 5
# Watches
go run .\cmd\main.go user <username> watches --limit 5- Keep handlers focused on formatting and user messages.
- Keep services pure and testable: input events → output aggregates.
- Use explicit types (
int64for counters from services) to avoid mismatches. - Prefer sorted output for deterministic results.
- Avoid changing public interfaces unless necessary; keep edits minimal and focused.
- Unit tests for services (aggregation correctness, limit semantics).
- Handlers: table formatting snapshots (or simple alignment checks).
- HTTP: mock
FetchGitHubApiDatafor predictable inputs.
- Ship platform-specific binaries in a
bin/folder within release zips. - Add a Makefile or GoReleaser config for reproducible builds.
- Global
--limitsemantics option (last N events overall). - Additional commands (e.g., comments, reviews, stars by repo).
- Configurable output styles (JSON/plain/table).
- Caching recent events to reduce API calls.