1 Forge-Client
Virgil edited this page 2026-02-19 17:00:16 +00:00

Forge Client

Package: forge.lthn.ai/core/go-scm/forge
Wraps: codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2

Thin wrapper around the Forgejo Go SDK providing config-based authentication, paginated listing helpers, and convenience methods for managing repositories, issues, pull requests, labels, webhooks, and organisations on a Forgejo instance.

Authentication

Credentials are resolved from three sources, each overriding the previous:

  1. Config file -- ~/.core/config.yaml keys forge.token and forge.url
  2. Environment variables -- FORGE_TOKEN and FORGE_URL
  3. Flag overrides -- passed directly to NewFromConfig(flagURL, flagToken)

If no URL is configured, it defaults to http://localhost:4000.

// Explicit credentials
client, err := forge.New("https://forge.lthn.ai", "my-token")

// Auto-resolve from config/env/flags
client, err := forge.NewFromConfig("", "")

// Resolve without creating a client (inspect values)
url, token, err := forge.ResolveConfig("", "")

Persisting Configuration

// Save URL and token to ~/.core/config.yaml
err := forge.SaveConfig("https://forge.lthn.ai", "my-token")

Client Methods

Core

Method Signature Description
API() *forgejo.Client Expose the underlying SDK client for direct access
URL() string Return the Forgejo instance URL
Token() string Return the API token
GetCurrentUser() (*forgejo.User, error) Get the authenticated user

Repositories

Method Signature Description
GetRepo(owner, name) (*forgejo.Repository, error) Get a single repository
ListOrgRepos(org) ([]*forgejo.Repository, error) List all repos in an organisation (paginated)
ListUserRepos() ([]*forgejo.Repository, error) List all repos for the authenticated user (paginated)
CreateOrgRepo(org, opts) (*forgejo.Repository, error) Create a new repo under an organisation
ForkRepo(owner, repo, org) (*forgejo.Repository, error) Fork a repository, optionally into an organisation
DeleteRepo(owner, name) error Delete a repository
MigrateRepo(opts) (*forgejo.Repository, error) Migrate a repository from an external service

Issues

Method Signature Description
ListIssues(owner, repo, opts) ([]*forgejo.Issue, error) List issues with state/label/pagination filters
GetIssue(owner, repo, number) (*forgejo.Issue, error) Get a single issue
CreateIssue(owner, repo, opts) (*forgejo.Issue, error) Create a new issue
EditIssue(owner, repo, number, opts) (*forgejo.Issue, error) Edit an existing issue
AssignIssue(owner, repo, number, assignees) error Assign users to an issue
CloseIssue(owner, repo, number) error Close an issue
CreateIssueComment(owner, repo, issue, body) error Post a comment on an issue or PR
ListIssueComments(owner, repo, number) ([]*forgejo.Comment, error) List all comments (paginated)

ListIssuesOpts

type ListIssuesOpts struct {
    State  string   // "open", "closed", "all" (default: "open")
    Labels []string // Filter by label names
    Page   int      // Page number (default: 1)
    Limit  int      // Items per page (default: 50)
}

Pull Requests

Method Signature Description
ListPullRequests(owner, repo, state) ([]*forgejo.PullRequest, error) List PRs by state (paginated)
GetPullRequest(owner, repo, number) (*forgejo.PullRequest, error) Get a single PR
CreatePullRequest(owner, repo, opts) (*forgejo.PullRequest, error) Create a new pull request
MergePullRequest(owner, repo, index, method) error Merge a PR ("squash", "rebase", or "merge")
SetPRDraft(owner, repo, index, draft) error Set/clear draft status via raw HTTP PATCH
ListPRReviews(owner, repo, index) ([]*forgejo.PullReview, error) List all reviews (paginated)
DismissReview(owner, repo, index, reviewID, message) error Dismiss a review
GetCombinedStatus(owner, repo, ref) (*forgejo.CombinedStatus, error) Get combined commit status for a ref

PR Metadata (Pipeline Support)

The PRMeta struct extracts structural signals from a pull request for use in AI-driven pipeline workflows:

type PRMeta struct {
    Number       int64
    Title        string
    State        string
    Author       string
    Branch       string
    BaseBranch   string
    Labels       []string
    Assignees    []string
    IsMerged     bool
    CreatedAt    time.Time
    UpdatedAt    time.Time
    CommentCount int
}
meta, err := client.GetPRMeta("core", "go-scm", 42)
fmt.Printf("PR #%d by %s, %d comments\n", meta.Number, meta.Author, meta.CommentCount)

Additional content methods:

Method Signature Description
GetPRMeta(owner, repo, pr) (*PRMeta, error) Get structural signals for a PR
GetCommentBodies(owner, repo, pr) ([]Comment, error) Get all comment bodies with metadata
GetIssueBody(owner, repo, issue) (string, error) Get the body text of an issue

Labels

Method Signature Description
ListRepoLabels(owner, repo) ([]*forgejo.Label, error) List all repo labels (paginated)
ListOrgLabels(org) ([]*forgejo.Label, error) List labels via first org repo
CreateRepoLabel(owner, repo, opts) (*forgejo.Label, error) Create a label
GetLabelByName(owner, repo, name) (*forgejo.Label, error) Find a label by name (case-insensitive)
EnsureLabel(owner, repo, name, colour) (*forgejo.Label, error) Get or create a label
AddIssueLabels(owner, repo, number, labelIDs) error Add labels to an issue
RemoveIssueLabel(owner, repo, number, labelID) error Remove a label from an issue

Organisations

Method Signature Description
ListMyOrgs() ([]*forgejo.Organization, error) List all orgs for the authenticated user
GetOrg(name) (*forgejo.Organization, error) Get a single organisation
CreateOrg(opts) (*forgejo.Organization, error) Create a new organisation

Webhooks

Method Signature Description
CreateRepoWebhook(owner, repo, opts) (*forgejo.Hook, error) Create a webhook on a repository
ListRepoWebhooks(owner, repo) ([]*forgejo.Hook, error) List all webhooks (paginated)

Error Handling

All methods use the log.E(scope, message, err) pattern from forge.lthn.ai/core/go/pkg/log, which wraps errors with contextual scope information for structured logging:

issues, err := client.ListIssues("core", "go-scm", forge.ListIssuesOpts{})
if err != nil {
    // Error message: "forge.ListIssues: failed to list issues: <underlying error>"
    log.Error("failed to fetch issues", "err", err)
}

Example: Create an Issue and Add a Label

client, err := forge.NewFromConfig("", "")
if err != nil {
    log.Fatal(err)
}

// Create the issue
issue, err := client.CreateIssue("core", "go-scm", forgejo.CreateIssueOption{
    Title: "Add webhook retry logic",
    Body:  "Implement exponential backoff for failed webhook deliveries.",
})

// Ensure the label exists and add it
label, err := client.EnsureLabel("core", "go-scm", "enhancement", "#0075ca")
err = client.AddIssueLabels("core", "go-scm", issue.Index, []int64{label.ID})

See Also