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

@agentex/github

v0.0.2

Published

Thin typed wrapper over the gh CLI for PR / issue / status operations

Readme

@agentex/github

A thin, typed wrapper over the gh CLI for PRs, issues, and status checks. Pairs with @agentex/workspace — workspace owns git plumbing, this package owns the GitHub host API.

This is a wrapper, not an abstraction. We don't model GitHub semantics beyond what gh exposes. If gh doesn't have a flag for it, this package doesn't have a method for it.

Install

pnpm add @agentex/github

gh is consumer-installed (brew install gh, etc.) — never bundled. Calling any operation when gh is missing throws NotInstalledError.

Quick start

import { github } from "@agentex/github";

// Preflight (recommended on app start)
const installed = await github.checkInstalled();
const authed    = await github.checkAuthenticated();
if (!installed.installed || !authed.authenticated) {
  // Show a non-blocking banner; everything else still works.
}

// Repo-scoped operations (cwd-bound)
const repo = github.repo("/abs/path/to/repo");

const pr = await repo.createPR({
  base: "main",
  head: "agent/task-42",
  title: "Implement foo",
  body: "<long markdown body>",
  draft: true,
});

const checks = await repo.listChecks(pr.number);

Typically used right after @agentex/workspace's ws.git.push():

await ws.git.push();
const pr = await github.repo(ws.path).createPR({
  base: ws.git.base,
  head: ws.git.branch,
  title: task.title,
  body: task.summary,
});
db.tasks.update(task.id, { prNumber: pr.number, prUrl: pr.url });

See it work

The companion demo at demo/workspace-demo/run.ts exercises this package against your real gh install:

# Always-on: preflight against your local gh
npx tsx demo/workspace-demo/run.ts

# + read-only ops against a sandbox repo
GH_DEMO_REPO=your-org/your-sandbox-repo npx tsx demo/workspace-demo/run.ts

# + actually create + clean up a draft PR (with a long body that exercises stdin)
GH_DEMO_REPO=your-org/your-sandbox-repo \
GH_DEMO_CREATE_PR=1 \
  npx tsx demo/workspace-demo/run.ts

API surface

Top-level (stateless preflight)

import { github } from "@agentex/github";

await github.checkInstalled()
  // → { installed: boolean, version?: string, path?: string }

await github.checkAuthenticated()
  // → { authenticated: boolean, user?: string, host?: string }

Repo-scoped (github.repo(path))

The instance carries cwd, so you don't pass a repo path to every call.

Every PR/issue id parameter accepts a PRId = number | string (or IssueId) — a number, a string number ("42"), or a full URL ("https://github.com/owner/repo/pull/42"). gh itself accepts all three; we just thread them through.

const repo = github.repo("/abs/path/to/repo");

// Pull requests
await repo.createPR({ base, head, title, body, draft?, reviewers?, labels? })
  // → PRSummary (re-fetched via `gh pr view --json` for a fully-typed return)

await repo.listPRs({ state?, head?, base?, author? })
  // → PRSummary[]
  // head/base/author all wire through to `gh pr list --head/--base/--author`

await repo.getPR(id)                        // → PRDetail (body + reviews + comments + statusCheckRollup)

await repo.commentOnPR(id, body)
await repo.requestReviewers(id, ["alice", "bob"])
await repo.merge(id, { method?: "merge" | "squash" | "rebase", deleteBranch?: boolean })
await repo.openInBrowser(id)                // gh pr view --web

// Status checks
await repo.listChecks(id)                   // → CheckRun[] (name, conclusion, status, url)

// Issues
await repo.listIssues({ state?, labels?, assignee? })   // → IssueSummary[]
await repo.getIssue(id)                                  // → IssueDetail
await repo.createIssue({ title, body, labels?, assignees? })
                                                         // → IssueSummary
await repo.commentOnIssue(id, body)

// Escape hatch — any gh subcommand
await repo.raw(args, { input? })
  // → { stdout, stderr, exitCode }

Escape hatch: repo.raw

The typed methods cover routine ops. For anything we haven't typed (gh api, gh release, custom flags) — or when an agent should drive gh directly — use raw:

// gh api passthrough
const { stdout } = await repo.raw(["api", "user", "--jq", ".login"]);

// long-body op via stdin (same E2BIG-safe pattern as createPR)
await repo.raw(
  ["pr", "edit", "42", "--body-file", "-"],
  { input: agentWrittenLongBody },
);

raw returns { stdout, stderr, exitCode } so the caller decides how to parse and how to react to non-zero exits. It still throws NotInstalledError if gh is missing, but it does not map other failures to typed errors — that's the typed methods' job.

Common patterns

Find the open PR for a branch:

const [openPR] = await repo.listPRs({ state: "open", head: ws.git.branch });

Connect work to a PR (consumer-side):

const pr = await repo.createPR({...});
db.tasks.update(taskId, { prNumber: pr.number, prUrl: pr.url });

// Later, refresh:
const detail = await repo.getPR(taskRow.prNumber);
const checks = await repo.listChecks(taskRow.prNumber);

Pull issues into your task tracker:

const issues = await repo.listIssues({ state: "open", labels: ["bug"] });
for (const issue of issues) {
  const detail = await repo.getIssue(issue.number);
  await db.tasks.create({
    title: issue.title,
    description: detail.body,
    sourceIssueUrl: issue.url,
  });
}

Typed errors

| Error | Thrown when | |---|---| | NotInstalledError | gh is not on $PATH (any operation) | | NotAuthenticatedError | gh auth status reports no credentials, or stderr matches "not authenticated" patterns | | RateLimitedError | stderr contains "rate limit" / "API rate limit exceeded" | | RepoNotFoundError | stderr contains "could not resolve" / "repository not found" | | BranchNotFoundError | stderr indicates a missing branch (e.g. "must first push the current branch") | | GhCommandError | fallback for any other non-zero gh exit (carries args, exitCode, stdout, stderr) |

Each typed error exposes the raw gh stderr both as .stderr and via the standard Error#cause slot, so generic log-aggregation tools that follow cause pick it up.

Platform

macOS and Linux. The package declares "os": ["darwin", "linux"] for parity with @agentex/workspace (which depends on POSIX process-group semantics).

Notes

Long bodies are piped via stdin

createPR, createIssue, commentOnPR, and commentOnIssue all pass their body to gh as --body-file - with the body written to stdin, rather than --body <text>. This avoids the OS arg-length limit (E2BIG — ~128KB on Linux, ~256KB on macOS), so agent-written PR descriptions and design-doc-sized issue bodies don't truncate.

createPR returns a fully-typed PRSummary

gh pr create only prints the new PR's URL. We follow up with gh pr view --json <fields> to return a typed PRSummary (number, title, state, url, isDraft, headRefName, baseRefName, author, createdAt, updatedAt). Costs one extra round-trip; saves the consumer from re-fetching themselves.

Test-only executor injection

The package exports _setGhExecutor(fn) / _resetGhExecutor() (underscore-prefixed) so tests can stub gh without spawning real subprocesses. Don't use these in app code — they're an internal seam for unit tests.

Spec

See internal-docs/prd-github.md for the full design rationale and the multi-forge / GitLab future-work notes.

License

MIT.