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

@hardlydifficult/github

v1.0.12

Published

Typed GitHub API client wrapping Octokit with a chainable API.

Readme

@hardlydifficult/github

Typed GitHub API client wrapping Octokit with a chainable API.

Install

npm install @hardlydifficult/github

Usage

import { GitHubClient } from "@hardlydifficult/github";

// Create client — token defaults to GH_PAT env var
const github = await GitHubClient.create();
const github = await GitHubClient.create("ghp_...");

// Repo-level
const repo = github.repo("owner", "repo");
const prs = await repo.getOpenPRs();
const repoInfo = await repo.get();

// PR-level (chainable from repo)
const pr = repo.pr(42);
const data = await pr.get();
const diff = await pr.getDiff();
const files = await pr.getFiles();
const commits = await pr.getCommits();
const reviews = await pr.getReviews();
const comments = await pr.getComments();
const checkRuns = await pr.getCheckRuns();
const timeline = await pr.getTimeline(); // merged comments + reviews + commits
await pr.postComment("LGTM!");
await pr.merge("feat: my feature (#42)");

// Owner-level
const repos = await github.getOwnerRepos("owner");

// User-level (uses auto-resolved username)
const contributed = await github.getContributedRepos(30);
const myPRs = await github.getMyOpenPRs();

Watching for PR activity

Poll repos and your open PRs for real-time updates — no webhooks required.

const watcher = github.watch({
  repos: ["owner/repo1", "owner/repo2"],
  myPRs: true,
  intervalMs: 30_000, // default
});

watcher.onNewPR((event) => {
  console.log(`New PR: ${event.pr.title} in ${event.repo.owner}/${event.repo.name}`);
});

watcher.onComment((event) => {
  console.log(`${event.comment.user.login} commented on #${event.pr.number}`);
});

watcher.onReview((event) => {
  console.log(`${event.review.user.login} ${event.review.state} #${event.pr.number}`);
});

watcher.onCheckRun((event) => {
  console.log(`${event.checkRun.name}: ${event.checkRun.status} (${event.checkRun.conclusion})`);
});

watcher.onPRUpdated((event) => {
  if (event.changes.draft) {
    console.log(`PR #${event.pr.number} ${event.changes.draft.to ? "converted to draft" : "marked ready"}`);
  }
});

watcher.onMerged((event) => {
  console.log(`PR #${event.pr.number} was merged`);
});

watcher.onClosed((event) => {
  console.log(`PR #${event.pr.number} was closed`);
});

watcher.onPollComplete((event) => {
  console.log(`Poll complete — tracking ${event.prs.length} PRs`);
});

watcher.onError((error) => {
  console.error("Watcher error:", error);
});

await watcher.start(); // initial poll + begins interval
watcher.stop();        // stop polling

The first poll fires onNewPR for all existing open PRs (discovery). Subsequent polls fire granular events for new comments, reviews, check run changes, metadata updates (draft, labels, mergeable state), and state transitions.

API

GitHubClient

| Method | Description | |--------|-------------| | static create(token?) | Create client (token defaults to GH_PAT env var) | | repo(owner, name) | Get a RepoClient scoped to owner/repo | | watch(options) | Create a PRWatcher for polling PR activity | | getOwnerRepos(owner) | List repos for a user or org | | getContributedRepos(days) | Find repos the user contributed to recently | | getMyOpenPRs() | Find open PRs by the authenticated user |

RepoClient

| Method | Description | |--------|-------------| | pr(number) | Get a PRClient scoped to a pull request | | getOpenPRs() | List open pull requests | | get() | Get repository info |

PRClient

| Method | Description | |--------|-------------| | get() | Get pull request details | | getDiff() | Get PR diff as string | | getFiles() | List files changed in the PR | | getCommits() | List commits in the PR | | getReviews() | List reviews on the PR | | getComments() | List comments on the PR | | getTimeline() | Merged timeline of comments, reviews, and commits (sorted) | | getCheckRuns() | List check runs (auto-resolves head SHA) | | postComment(body) | Post a comment on the PR | | merge(title) | Squash-merge the PR |

PRWatcher

Created via github.watch(options). All on* methods return an unsubscribe function.

| Method | Description | |--------|-------------| | onNewPR(callback) | New PR appeared in a watched repo or user's PRs | | onComment(callback) | New comment posted on a tracked PR | | onReview(callback) | New review submitted on a tracked PR | | onCheckRun(callback) | Check run created or status changed on a tracked PR | | onPRUpdated(callback) | PR metadata changed (draft, labels, mergeable state) | | onMerged(callback) | Tracked PR was merged | | onClosed(callback) | Tracked PR was closed without merge | | onPollComplete(callback) | Poll cycle finished — receives snapshot of all tracked PRs | | onError(callback) | Polling or callback error | | start() | Begin polling (initial poll + interval) | | stop() | Stop polling | | getWatchedPRs() | Returns current snapshot of all tracked PRs | | addRepo(repo) | Start watching a new repo ("owner/repo" format) | | removeRepo(repo) | Stop watching a repo |

WatchOptions

| Field | Type | Default | Description | |-------|------|---------|-------------| | repos | string[] | [] | Repos to watch ("owner/repo" format) | | myPRs | boolean | false | Also watch all open PRs by the authenticated user | | intervalMs | number | 30000 | Polling interval in milliseconds |

Types

PullRequest, Repository, User, CheckRun, PullRequestReview, PullRequestComment, PullRequestFile, PullRequestCommit, Label, ContributionRepo, MergeableState, WatchOptions, PREvent, CommentEvent, ReviewEvent, CheckRunEvent, PRUpdatedEvent, PollCompleteEvent, TimelineEntry, TimelineEntryKind

Timeline Utilities

| Function | Description | |----------|-------------| | buildTimeline(comments, reviews, commits) | Merge PR data into a sorted TimelineEntry[] | | formatTimeline(entries) | Render timeline as readable markdown text |

These are also available standalone (no PRClient needed) for custom pipelines.