@agntn/forges
Unified Git Provider - single TypeScript API for GitHub, GitLab, Gitea, and GitBucket
Package details
Install @agntn/forges from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@agntn/forges- Package
@agntn/forges- Version
0.3.2- Published
- Sep 19, 2026
- Downloads
- 1,011/mo · 395/wk
- Author
- oritwoen
- License
- MIT
- Types
- extension
- Size
- 694.2 KB
- Dependencies
- 5 dependencies · 4 peers
Pi manifest JSON
{
"image": "https://forges.agntn.dev/image.png",
"extensions": [
"./packages/pi/extensions/forges.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@agntn/forges
⚒️ Four forges, ten resources, thirty-five agent tools. You ask for a pull request, you get a pull request.
Why?
Every Git host does the same job and none of them agree on the words. Pull request or merge request, per_page or limit, Link or x-next-page, and GitLab's URL number is the iid not the id. An agent with four clients will pick the wrong number. Talk to one Provider and let it remember which header is which.
Docs, and an explorer that runs the same calls: forges.agntn.dev.
✨ Features
- 🧩 Four forges, one
Provider. GitHub, GitLab, Gitea and GitBucket. Samerepos.get, sameIssue, samePullRequest. - 🔑 It finds the token. Explicit value, then env, then
ghorglab, then the CLI config file. First hit wins. - 📦 Loads one platform.
createProvider("github")is async. It imports GitHub and leaves GitLab on disk. - 🆔 IDs are strings. Even when the API sent a number. A count the forge withholds is missing, not
0. - 🫥 Empty string is guest.
{ token: "" }is anonymous on purpose. Leavetokenout and you getAuthenticationError, not a quiet guest session. - 🤖 Thirty-five tools, three surfaces. MCP, Pi and OMP share the executors. Seven tools write to the host.
- 🚫 Missing is 501. Code search on Gitea is not an empty page. You get a
ForgesErrorwith status 501. - 🧭 GitBucket is GitHub plus
baseURL. Forgejo and Codeberg are Gitea plusbaseURL. Same class, different host.
📦 Install
pnpm add @agntn/forges
Node.js 22 or newer.
🚀 First call
import { createProvider } from "@agntn/forges";
const codeberg = await createProvider("gitea", {
token: "",
baseURL: "https://codeberg.org",
});
const repo = await codeberg.repos.get("forgejo", "forgejo");
console.log(repo.fullName, repo.description, repo.defaultBranch);
forgejo/forgejo Beyond coding. We forge. forgejo
They said it, not me. No key. You still pass the host. Empty string is guest.
Logged into gh? Drop the config object.
const github = await createProvider("github");
const hello = await github.repos.get("octocat", "Hello-World");
console.log(hello.fullName, hello.description, hello.defaultBranch);
octocat/Hello-World My first repository on GitHub! master
GitHub's first hello. Default branch is still master.
Commands
| Command | What it does | Example |
|---|---|---|
mcp |
The MCP server on stdio | forges mcp |
There is no forges repos. MCP is the whole binary. pnpm exec forges mcp after install, or pnpm add -g @agntn/forges once.
🧠 Library
import { createProvider } from "@agntn/forges";
const github = await createProvider("github");
const repo = await github.repos.get("octocat", "Hello-World");
const { items, hasNextPage } = await github.pullRequests.list("octocat", "Hello-World", {
state: "open",
});
const gitlab = await createProvider("gitlab", {
token: "glpat-…",
baseURL: "https://gitlab.example.com",
});
const gitbucket = await createProvider("github", {
token: "…",
baseURL: "https://gitbucket.example.com/api/v3",
});
Ten resources on every provider. repos, issues, pullRequests, threads. Then commits, ciRuns, releases, contributionTemplates, code, users. Lists come back as items plus hasNextPage. totalCount only when the forge counted. Search adds incomplete when the answer is known to be partial. Guides: Authentication, Repositories, Issues, Pull requests, Review threads, Commits, CI and releases, Templates, Code search.
🗺️ Providers
| Platform | Provider | Auth header | Threads | Code search |
|---|---|---|---|---|
| GitHub | github |
Authorization: token |
GraphQL, real flags | global, owner, repository |
| GitLab | gitlab |
Private-Token |
REST discussions | token required, Premium for global |
| Gitea, Forgejo, Codeberg | gitea + baseURL |
Authorization: token |
one thread per review comment | none |
| GitBucket | github + baseURL |
Authorization: token |
none | none |
Code search on Gitea is a 501, not an empty page. Host pages: Platforms.
🤖 Agents
forges mcp
pi install npm:@agntn/forges
omp install @agntn/forges
{
"mcpServers": {
"forges": { "command": "npx", "args": ["-y", "@agntn/forges", "mcp"] }
}
}
MCP, Pi and OMP all hit the same 35 tools. Seven write to the host, so ask forges_users_authenticated who you are before a model does, details in the Agents guide.
🚫 What this does not do
Files, trees, branches, plain tags, release assets, webhooks, org admin. The review loop is the scope: what was proposed, what was said, whether it passed, what shipped.
🧩 Adding a provider
A class extending Provider, the typed mappers, and a 501 for every method you skip. Copy from Custom providers.
🛠️ Development
pnpm install
pnpm test # vitest watch
pnpm test:run # single run, as CI does
pnpm typecheck # tsc, then build, then the extension graph
pnpm lint
pnpm docs # the site, it bundles src/
pnpm run build # obuild
💛 Thanks
I wrote a lot of this with help from Claude for Open Source and Codex for Open Source. Grateful for both <3
