Documentation
¶
Index ¶
- Constants
- Variables
- func CheckGHAuth() error
- func CheckoutBranch(repoPath, branchName string) error
- func CleanupClone(owner, repo string) error
- func CloneRepository(owner, repo, targetPath string) error
- func ClosePR(owner, repo string, prNumber int) error
- func DeleteKeychainToken() error
- func DeleteKeychainTokenForAccount(username string) error
- func EnsureCloneDirectory() error
- func FetchBranch(repoPath, branchName string) error
- func FindExistingClone(owner, repo string) (string, bool)
- func GeneratePRPrompt(pr *PRInfo, includeDescription bool) string
- func GetClonePath(owner, repo string) string
- func GetCurrentUserLogin(ctx context.Context) (string, error)
- func GetCurrentUserLoginWithToken(ctx context.Context, token string) (string, error)
- func GetKeychainToken() string
- func GetKeychainTokenForAccount(username string) string
- func GetPRDiff(owner, repo string, prNumber int) (string, error)
- func GetRemoteURL(repoPath string) (string, error)
- func IsForkRepo(ctx context.Context, owner, repo string) (bool, error)
- func IsGitHubRef(input string) bool
- func IsTerminal(priority PRPriority) bool
- func ListClonedRepos() ([]string, error)
- func ListKeychainAccounts() []string
- func MergePR(owner, repo string, prNumber int, method string) error
- func PollDeviceAuth(ctx context.Context, deviceCode string) (string, error)
- func PostPRComment(owner, repo string, prNumber int, body string) error
- func SetKeychainToken(token string) error
- func SetKeychainTokenForAccount(username, token string) error
- func StoreTokenForDiscoveredUser(ctx context.Context, token string) error
- func WaitForDeviceAuth(ctx context.Context, da *DeviceAuthStart) (string, error)
- type AccountToken
- type CloneOptions
- type CloneResult
- type DeviceAuthStart
- type ETagCache
- type IssueResult
- type PRAnnotationSession
- type PRAnnotationWorktree
- type PRComment
- type PRInfo
- func GetPRForBranch(ctx context.Context, owner, repo, branch string) (*PRInfo, error)
- func GetPRInfo(owner, repo string, prNumber int) (*PRInfo, error)
- func GetPRInfoConditional(ctx context.Context, owner, repo string, prNumber int, cache *ETagCache) (*PRInfo, bool, error)
- func GetPRInfoCtx(ctx context.Context, owner, repo string, prNumber int) (*PRInfo, error)
- type PRPriority
- type ParsedGitHubRef
- type RefType
- type RepoRef
- type RepoResult
- type UserPR
- type UserPRCache
- func (c *UserPRCache) Annotate(sessions []PRAnnotationSession, worktrees []PRAnnotationWorktree)
- func (c *UserPRCache) GetAll() []UserPR
- func (c *UserPRCache) GetCachedLogin() string
- func (c *UserPRCache) GetCachedLogins() []string
- func (c *UserPRCache) InvalidateLoginCache()
- func (c *UserPRCache) Refresh(ctx context.Context) error
- func (c *UserPRCache) SetOnUpdated(fn func(prs []UserPR))
- func (c *UserPRCache) Start(ctx context.Context)
- func (c *UserPRCache) Stop()
- func (c *UserPRCache) Subscribe(id string, ch chan []UserPR)
- func (c *UserPRCache) Unsubscribe(id string)
- type UserPRCacheConfig
Constants ¶
const (
// DefaultCloneBase is the default base directory for cloned repositories
DefaultCloneBase = "~/.stapler-squad/repos"
)
Variables ¶
var ErrAuthorizationPending = errors.New("authorization_pending")
ErrAuthorizationPending is returned by PollDeviceAuth while waiting for the user to complete the browser authorization step.
var ErrDeviceFlowExpired = errors.New("expired_token")
ErrDeviceFlowExpired is returned when the device code has expired before the user authorized. The caller should restart the flow.
var ErrNoPR = errors.New("no pull request found for branch")
ErrNoPR is returned by GetPRForBranch when no pull request exists for the branch.
var ErrNotAuthenticated = errors.New("github token not configured")
ErrNotAuthenticated is returned by SearchUserRepos and ListRepoIssues when no GitHub token is configured (GITHUB_TOKEN, GH_TOKEN, or OS keychain).
var GhBaseURL = "https://api.github.com/"
GhBaseURL is the GitHub REST API base URL. Tests override this to point at an httptest.Server so requests never reach the real API.
Functions ¶
func CheckGHAuth ¶
func CheckGHAuth() error
CheckGHAuth verifies GitHub authentication via GET /user using the native HTTP client. No subprocess is invoked — avoids forkExec lock contention. Results are cached for 5 minutes. Concurrent callers share a single inflight call via singleflight.
func CheckoutBranch ¶
CheckoutBranch checks out a branch in an existing repository
func CleanupClone ¶
CleanupClone removes a cloned repository Use with caution - this deletes the entire repository directory
func CloneRepository ¶
CloneRepository clones a GitHub repository
func DeleteKeychainToken ¶ added in v1.35.0
func DeleteKeychainToken() error
DeleteKeychainToken removes the legacy single-account token.
func DeleteKeychainTokenForAccount ¶ added in v1.35.0
DeleteKeychainTokenForAccount removes the token for username and removes it from the accounts list.
func EnsureCloneDirectory ¶
func EnsureCloneDirectory() error
EnsureCloneDirectory ensures the base clone directory exists
func FetchBranch ¶
FetchBranch fetches a specific branch in an existing repository
func FindExistingClone ¶
FindExistingClone checks if a repository is already cloned Returns the path and true if found, empty string and false otherwise
func GeneratePRPrompt ¶
GeneratePRPrompt generates a context prompt from PR information This can be used to initialize a Claude Code session with PR context
func GetClonePath ¶
GetClonePath returns the path where a repository would be cloned Format: ~/.stapler-squad/repos/{owner}/{repo}
func GetCurrentUserLogin ¶ added in v1.35.0
GetCurrentUserLogin returns the GitHub login of the authenticated user via GET /user. Returns an empty string (not an error) when unauthenticated so callers can degrade gracefully.
func GetCurrentUserLoginWithToken ¶ added in v1.35.0
GetCurrentUserLoginWithToken fetches the GitHub login for an explicit token. Returns ("", nil) when the token is invalid or unauthenticated.
func GetKeychainToken ¶ added in v1.35.0
func GetKeychainToken() string
GetKeychainToken returns any stored GitHub token (first account, or the legacy single-account slot). Kept for backward-compatibility with the single-token auth flow.
func GetKeychainTokenForAccount ¶ added in v1.35.0
GetKeychainTokenForAccount returns the stored token for the given username, or "".
func GetRemoteURL ¶
GetRemoteURL returns the remote URL of a repository (used to determine owner/repo)
func IsForkRepo ¶ added in v1.12.0
IsForkRepo reports whether the given repo is a fork of another repository.
func IsGitHubRef ¶
IsGitHubRef checks if the input string looks like a GitHub URL or reference This is a quick check that doesn't validate the full format
func IsTerminal ¶ added in v1.12.0
func IsTerminal(priority PRPriority) bool
IsTerminal returns true if the priority indicates a terminal PR state. Terminal sessions should not be polled at normal frequency.
func ListClonedRepos ¶
ListClonedRepos returns a list of all cloned repositories
func ListKeychainAccounts ¶ added in v1.35.0
func ListKeychainAccounts() []string
ListKeychainAccounts returns the ordered list of connected GitHub usernames.
func PollDeviceAuth ¶ added in v1.35.0
PollDeviceAuth polls GitHub's token endpoint once.
- Returns (token, nil) on success — the caller should store the token.
- Returns ("", ErrAuthorizationPending) if the user hasn't approved yet.
- Returns ("", ErrDeviceFlowExpired) if the device code has expired.
- Returns ("", err) for any other error.
func PostPRComment ¶
PostPRComment posts a comment on a pull request
func SetKeychainToken ¶ added in v1.35.0
SetKeychainToken stores a token under the legacy single-account slot. Prefer SetKeychainTokenForAccount when the username is known.
func SetKeychainTokenForAccount ¶ added in v1.35.0
SetKeychainTokenForAccount stores a token under a per-username key and adds the username to the accounts list if not already present.
func StoreTokenForDiscoveredUser ¶ added in v1.35.0
StoreTokenForDiscoveredUser fetches the GitHub login for token and stores it under the per-username keychain slot. Falls back to the legacy slot if the login cannot be determined.
func WaitForDeviceAuth ¶ added in v1.35.0
func WaitForDeviceAuth(ctx context.Context, da *DeviceAuthStart) (string, error)
WaitForDeviceAuth polls GitHub repeatedly until the user completes authorization, the code expires, or ctx is cancelled. On success it stores the token in the OS keychain and returns it.
Types ¶
type AccountToken ¶ added in v1.35.0
type AccountToken struct {
Username string // empty for the legacy single-account slot
Token string
}
AccountToken pairs a GitHub username with its token.
func GetAllKeychainTokens ¶ added in v1.35.0
func GetAllKeychainTokens() []AccountToken
GetAllKeychainTokens returns all stored tokens across all named accounts plus the legacy single-account slot. Each entry is a (username, token) pair.
type CloneOptions ¶
type CloneOptions struct {
Owner string
Repo string
Branch string // Optional: specific branch to checkout after cloning
Shallow bool // Use shallow clone (--depth=1) for faster cloning
}
CloneOptions specifies options for cloning or accessing a repository
type CloneResult ¶
type CloneResult struct {
Path string // Full path to the repository
WasCloned bool // True if we just cloned it, false if it already existed
Branch string // Current branch name
}
CloneResult contains information about a cloned or existing repository
func GetOrCloneRepository ¶
func GetOrCloneRepository(opts CloneOptions) (*CloneResult, error)
GetOrCloneRepository ensures a repository is available locally It will clone it if not already present, or return the existing clone
type DeviceAuthStart ¶ added in v1.35.0
type DeviceAuthStart struct {
DeviceCode string
UserCode string
VerificationURI string
// ExpiresIn is how long the code is valid (seconds).
ExpiresIn int
// Interval is the minimum poll interval suggested by GitHub (seconds).
Interval int
}
DeviceAuthStart holds the values returned by the first step of the Device Flow: the user-visible code and the URL where they enter it.
func StartDeviceAuth ¶ added in v1.35.0
func StartDeviceAuth(ctx context.Context) (*DeviceAuthStart, error)
StartDeviceAuth initiates the GitHub Device Flow and returns the codes the user must enter at verification_uri. The returned DeviceAuthStart.DeviceCode must be passed to PollDeviceAuth.
type ETagCache ¶ added in v1.12.0
type ETagCache struct {
// contains filtered or unexported fields
}
ETagCache stores ETags and cached PRInfo responses per (owner, repo, prNumber). Using conditional requests (If-None-Match) allows GitHub to return 304 Not Modified responses that cost zero rate-limit quota when the PR has not changed. sync.Map gives lock-free reads in the steady state — entries are written once on first PR discovery and then read on every subsequent poll tick.
func NewETagCache ¶ added in v1.12.0
func NewETagCache() *ETagCache
NewETagCache creates a new empty ETagCache.
type IssueResult ¶ added in v1.35.0
IssueResult is the domain return type for ListRepoIssues.
func ListRepoIssues ¶ added in v1.35.0
func ListRepoIssues(ctx context.Context, owner, repo, state, search string, limit int) ([]IssueResult, error)
ListRepoIssues fetches issues for a specific repo. When search is empty it uses GET /repos/{owner}/{repo}/issues. When search is non-empty it uses GET /search/issues. Returns ErrNotAuthenticated when no token is configured.
type PRAnnotationSession ¶ added in v1.35.0
type PRAnnotationSession struct {
ID string
Branch string
Repo RepoRef
PRNumber int // fallback: match by PR number when branch name doesn't match headRef
}
PRAnnotationSession carries session data needed to annotate UserPR entries. Defined here (not in the session package) to avoid an import cycle: session imports github, so github cannot import session.
Repo is a typed value object: holding a valid RepoRef proves owner and repo are non-empty. Sessions without a resolvable GitHub repo are skipped.
type PRAnnotationWorktree ¶ added in v1.35.0
PRAnnotationWorktree carries worktree data for annotation.
type PRComment ¶
type PRComment struct {
ID int `json:"id"`
Author string `json:"author"`
Body string `json:"body"`
CreatedAt time.Time `json:"createdAt"`
Path string `json:"path,omitempty"` // For review comments
Line int `json:"line,omitempty"` // For review comments
IsReview bool `json:"isReview,omitempty"` // True if this is a review comment
}
PRComment represents a comment on a PR (either issue comment or review comment)
type PRInfo ¶
type PRInfo struct {
Number int `json:"number"`
Title string `json:"title"`
Body string `json:"body"`
HeadRef string `json:"headRefName"`
BaseRef string `json:"baseRefName"`
State string `json:"state"`
Author string `json:"author"`
Labels []string `json:"labels"`
HTMLURL string `json:"url"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
IsDraft bool `json:"isDraft"`
Mergeable string `json:"mergeable"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
ChangedFiles int `json:"changedFiles"`
// Review and CI status fields (populated by GetPRInfo with extended fields)
ReviewDecision string // "approved" / "changes_requested" / "review_required" / ""
ApprovedCount int // Count of current non-dismissed APPROVED reviews
ChangesRequestedCount int // Count of current non-dismissed CHANGES_REQUESTED reviews
CheckConclusion string // "success" / "failure" / "pending" / "action_required" / "neutral" / ""
CheckStatus string // "completed" / "in_progress" / ""
}
PRInfo contains metadata about a GitHub pull request
func GetPRForBranch ¶ added in v1.12.0
GetPRForBranch finds the GitHub PR associated with a branch. Uses the GitHub REST API directly (no gh subprocess) to avoid forkExec lock contention. Returns ErrNoPR when no pull request exists for the branch.
func GetPRInfoConditional ¶ added in v1.12.0
func GetPRInfoConditional(ctx context.Context, owner, repo string, prNumber int, cache *ETagCache) (*PRInfo, bool, error)
GetPRInfoConditional fetches PR info using ETag conditional requests. Uses native net/http instead of a gh subprocess to avoid forkExec lock contention. Returns (info, changed, error).
- changed=false means 304 Not Modified; info contains the cached value.
- changed=true means 200 OK; info contains freshly fetched data.
- Both info and changed may be zero values when an error is returned.
type PRPriority ¶ added in v1.12.0
type PRPriority string
PRPriority is a derived single-enum priority computed from compound PR state.
const ( PRPriorityBlocking PRPriority = "blocking" // changes requested or CI failing PRPriorityReady PRPriority = "ready" // approved + CI passing PRPriorityPending PRPriority = "pending" // awaiting review or checks running PRPriorityDraft PRPriority = "draft" // PR is a draft PRPriorityComplete PRPriority = "complete" // PR is merged or closed PRPriorityNoPR PRPriority = "no_pr" // no PR found for branch PRPriorityAuthError PRPriority = "auth_error" // gh CLI not authenticated PRPriorityError PRPriority = "error" // transient error fetching status )
func DerivePRPriority ¶ added in v1.12.0
func DerivePRPriority(info *PRInfo) PRPriority
DerivePRPriority computes a single priority enum from compound PR state. Returns PRPriorityNoPR if info is nil.
type ParsedGitHubRef ¶
type ParsedGitHubRef struct {
Type RefType
Owner string
Repo string
PRNumber int // Only populated for RefTypePR
IssueNumber int // Only populated for RefTypeIssue
Branch string // Populated for RefTypeBranch, or PR head branch after fetching
CommitSHA string // Only populated for RefTypeCommit
FilePath string // Only populated for RefTypeFile (path within repo)
LineStart int // Only populated for RefTypeFile (optional line number)
LineEnd int // Only populated for RefTypeFile (optional end line for range)
BaseBranch string // Only populated for RefTypeCompare (the base of comparison)
HeadBranch string // Only populated for RefTypeCompare (the head being compared)
Tag string // Only populated for RefTypeRelease
OriginalURL string // The original input string
}
ParsedGitHubRef represents a parsed GitHub URL or reference
func ParseGitHubRef ¶
func ParseGitHubRef(input string) (*ParsedGitHubRef, error)
ParseGitHubRef parses a GitHub URL or shorthand reference into a ParsedGitHubRef Supported formats:
- https://github.com/owner/repo/pull/123
- https://github.com/owner/repo/tree/branch-name
- https://github.com/owner/repo/blob/branch/path/to/file.go
- https://github.com/owner/repo/blob/branch/file.go#L10 (with line number)
- https://github.com/owner/repo/blob/branch/file.go#L10-L20 (with line range)
- https://github.com/owner/repo/commit/abc123
- https://github.com/owner/repo/issues/42
- https://github.com/owner/repo/compare/main...feature
- https://github.com/owner/repo/releases/tag/v1.0.0
- https://github.com/owner/repo
- github.com/owner/repo
- git@github.com:owner/repo.git (SSH)
- ssh://git@github.com/owner/repo (SSH protocol)
- owner/repo:branch-name
- owner/repo
func (*ParsedGitHubRef) CloneURL ¶
func (p *ParsedGitHubRef) CloneURL() string
CloneURL returns the HTTPS clone URL for the repository
func (*ParsedGitHubRef) DisplayName ¶
func (p *ParsedGitHubRef) DisplayName() string
DisplayName returns a human-readable name for the reference
func (*ParsedGitHubRef) HTMLURL ¶
func (p *ParsedGitHubRef) HTMLURL() string
HTMLURL returns the human-readable GitHub URL
func (*ParsedGitHubRef) RepoFullName ¶
func (p *ParsedGitHubRef) RepoFullName() string
RepoFullName returns "owner/repo" format
func (*ParsedGitHubRef) SuggestedSessionName ¶
func (p *ParsedGitHubRef) SuggestedSessionName() string
SuggestedSessionName returns a suggested session name based on the reference
type RefType ¶
type RefType int
RefType represents the type of GitHub reference parsed from a URL or shorthand
type RepoRef ¶ added in v1.35.0
type RepoRef struct {
// contains filtered or unexported fields
}
RepoRef is a value object that bundles a GitHub owner and repository name. Both fields are non-empty by construction — holding a RepoRef proves the invariant holds without any further nil/empty checks at use sites.
func GetOwnerRepoFromRemote ¶ added in v1.35.0
GetOwnerRepoFromRemote returns a RepoRef for a local git repository by reading the origin remote URL and parsing it. Returns an invalid zero-value RepoRef (not an error) when the remote is not a GitHub URL.
func NewRepoRef ¶ added in v1.35.0
NewRepoRef constructs a RepoRef, returning an error if either field is empty.
func (RepoRef) BranchKey ¶ added in v1.35.0
BranchKey returns the map key used to match sessions by branch: "owner/branch".
func (RepoRef) IsValid ¶ added in v1.35.0
IsValid reports whether the ref was constructed with non-empty owner and repo. The zero value (RepoRef{}) is not valid.
type RepoResult ¶ added in v1.35.0
RepoResult is the domain return type for SearchUserRepos.
func SearchUserRepos ¶ added in v1.35.0
SearchUserRepos fetches repos accessible to the authenticated user. When query is empty it uses GET /user/repos (all accessible repos sorted by push time). When non-empty it uses GET /search/repositories. Returns ErrNotAuthenticated when no token is configured.
type UserPR ¶ added in v1.35.0
type UserPR struct {
Owner string
Repo string
Number int
Title string
URL string
HeadRef string
BaseRef string
State string
IsDraft bool
UpdatedAt time.Time
ClosedAt time.Time
MergedAt time.Time
ApprovedCount int
ChangesReqCount int
CheckConclusion string // "success" / "failure" / "pending" / ""
SessionIDs []string
LocalWorktreePath string
}
UserPR is an open GitHub pull request authored by the authenticated user, optionally annotated with local session IDs and worktree paths.
type UserPRCache ¶ added in v1.35.0
type UserPRCache struct {
// contains filtered or unexported fields
}
func NewUserPRCache ¶ added in v1.35.0
func NewUserPRCache() *UserPRCache
NewUserPRCache creates a cache with default configuration.
func NewUserPRCacheWithConfig ¶ added in v1.35.0
func NewUserPRCacheWithConfig(cfg UserPRCacheConfig) *UserPRCache
NewUserPRCacheWithConfig creates a cache with custom configuration.
func (*UserPRCache) Annotate ¶ added in v1.35.0
func (c *UserPRCache) Annotate(sessions []PRAnnotationSession, worktrees []PRAnnotationWorktree)
Annotate enriches the current snapshot with session IDs and worktree paths. It performs a COW update: load → copy → mutate → store. No-op if the snapshot hasn't been populated yet.
func (*UserPRCache) GetAll ¶ added in v1.35.0
func (c *UserPRCache) GetAll() []UserPR
GetAll returns a copy of the current PR snapshot. Returns nil before the first successful fetch.
func (*UserPRCache) GetCachedLogin ¶ added in v1.35.0
func (c *UserPRCache) GetCachedLogin() string
GetCachedLogin returns the first connected GitHub login, or "" if none yet.
func (*UserPRCache) GetCachedLogins ¶ added in v1.35.0
func (c *UserPRCache) GetCachedLogins() []string
GetCachedLogins returns all connected GitHub logins.
func (*UserPRCache) InvalidateLoginCache ¶ added in v1.35.0
func (c *UserPRCache) InvalidateLoginCache()
InvalidateLoginCache clears the cached login state so the next Refresh call re-fetches the authenticated user from the GitHub API. Call this after storing a new token (e.g. after a successful Device Flow auth) so the cache picks up the new credentials immediately.
func (*UserPRCache) Refresh ¶ added in v1.35.0
func (c *UserPRCache) Refresh(ctx context.Context) error
Refresh triggers an immediate fetch from GitHub, coalescing concurrent calls.
func (*UserPRCache) SetOnUpdated ¶ added in v1.35.0
func (c *UserPRCache) SetOnUpdated(fn func(prs []UserPR))
SetOnUpdated atomically registers a callback invoked after every successful refresh. Pass nil to clear. The callback receives the current PR slice. Safe to call at any time, including after Start.
func (*UserPRCache) Start ¶ added in v1.35.0
func (c *UserPRCache) Start(ctx context.Context)
Start launches the background polling goroutine. Safe to call multiple times; only the first call starts the goroutine.
func (*UserPRCache) Stop ¶ added in v1.35.0
func (c *UserPRCache) Stop()
Stop halts background polling.
func (*UserPRCache) Subscribe ¶ added in v1.35.0
func (c *UserPRCache) Subscribe(id string, ch chan []UserPR)
Subscribe registers a channel to receive PR snapshot updates. The channel must be buffered. The caller is responsible for calling Unsubscribe.
func (*UserPRCache) Unsubscribe ¶ added in v1.35.0
func (c *UserPRCache) Unsubscribe(id string)
Unsubscribe removes a previously registered subscriber channel.
type UserPRCacheConfig ¶ added in v1.35.0
type UserPRCacheConfig struct {
// PollInterval controls how often the cache refreshes from GitHub.
PollInterval time.Duration
// LoginCacheTTL controls how long the authenticated login is cached.
LoginCacheTTL time.Duration
}
UserPRCacheConfig controls polling behaviour.
func DefaultUserPRCacheConfig ¶ added in v1.35.0
func DefaultUserPRCacheConfig() UserPRCacheConfig
DefaultUserPRCacheConfig returns sensible defaults.