npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@agntn/forges

v0.4.0

Published

Unified Git Provider - single TypeScript API for GitHub, GitLab, Gitea, and GitBucket

Readme

@agntn/forges

npm version npm downloads license Ask DeepWiki

⚒️ Four forges, ten resources, 50 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. Same repos.get, same Issue, same PullRequest.
  • 🔑 It finds the token. Explicit value, then env, then gh or glab, 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. Leave token out and you get AuthenticationError, not a quiet guest session.
  • 🤖 50 tools, three surfaces. MCP, Pi and OMP share the executors. Twelve tools write to the host.
  • 🚫 Missing is 501. Code search on Gitea is not an empty page. You get a ForgesError with status 501.
  • 🧭 GitBucket is GitHub plus baseURL. Forgejo and Codeberg are Gitea plus baseURL. Same class, different host.

📦 Install

pnpm add @agntn/forges

Node.js 26 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.

Local Git

@agntn/forges/local checks a fetched checkout without changing it. Git with --no-lazy-fetch support must be on PATH; inspection also needs ls-files --deduplicate.

import { inspectLocal, verifyLocalMerge } from "@agntn/forges/local";

const inspection = await inspectLocal({
  cwd: "/path/to/checkout",
  paths: ["*AGENTS.md"],
  historyLimit: 3,
});

const evidence = await verifyLocalMerge({
  cwd: "/path/to/checkout",
  head: "topic",
  mergeCommit: "66c39f4bccd275e930420f408b7c311b9c494af8",
  target: "origin/main",
  paths: ["package.json", "README.md"],
});
console.log(evidence.mergeReachable, evidence.pathsMatch);

Status rows and tracked files are paged separately. Continue with statusOffset: inspection.nextStatusOffset or filesOffset: inspection.nextFilesOffset until it's null, keeping paths unchanged. The agent guide covers limits and concurrent edits.

Replace the sample mergeCommit with the forge's actual merge or squash SHA for that PR, not the current target tip. The two booleans answer different questions: is that commit in the target's local history, and do the selected paths match the PR head? Neither authorizes deleting a branch. Details and limits: Agents.

🗺️ 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 50 tools. Twelve 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

Hosted 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        # vp test in watch mode
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

📄 License

MIT