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:
- Config file --
~/.core/config.yaml keys forge.token and forge.url
- Environment variables --
FORGE_TOKEN and FORGE_URL
- 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