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

ghostbug

v0.2.0

Published

Zero-config automatic bug context collector SDK for the browser

Readme


Why ghostbug?

Testers spend 90% of their time manually collecting bug context — error messages, console logs, network failures, steps to reproduce. ghostbug eliminates that by silently capturing everything in the background.

| | Sentry / LogRocket | ghostbug | |---|---|---| | Setup | Account, API keys, config | ghostbug.init() | | Server needed | Yes (paid SaaS) | No — 100% client-side | | Bundle size | 50–200KB+ | ~7KB gzipped | | Dependencies | Multiple | Zero | | Data privacy | Sent to their servers | Stays in the browser | | Output | Dashboard (another tab) | JSON / Markdown for GitHub Issues | | Pricing | Free tier / Paid | Free forever |


Install

npm install ghostbug
# or
yarn add ghostbug
# or
pnpm add ghostbug

Quick Start

import ghostbug from "ghostbug";

ghostbug.init();

That's it. ghostbug now silently auto-captures:

| Collector | What it catches | |---|---| | Errors | window.onerror + unhandled promise rejections with stack traces | | Console | console.error() and console.warn() with full arguments | | Network | Failed fetch and XMLHttpRequest (4xx/5xx) with timing | | Clicks | Last 20 user clicks with element selectors and positions | | Interactions | Form input, scroll, and resize events | | Performance | Long Tasks, FCP, LCP, layout shifts via PerformanceObserver | | Memory | Heap usage sampling, high usage (>90%) and rapid growth (>50%) alerts | | Context | URL, browser, viewport, device pixel ratio, referrer |


API

ghostbug.init(options?)

Initialize ghostbug. Call once when your app loads.

ghostbug.init({
  // Show floating widget UI (default: false)
  widget: true,

  // Or with widget options
  widget: {
    position: "bottom-right", // 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'
    collapsed: true,
    zIndex: 2147483647,
  },

  // Toggle individual collectors (all true by default)
  collectors: {
    errors: true,
    console: true,
    network: true,
    clicks: true,
    interactions: true,
    performance: true,
    memory: true,
  },

  maxReports: 50,      // Max reports stored (oldest dropped when full)
  maxBreadcrumbs: 20,  // Max breadcrumb trail per report
  maxClicks: 20,       // Max click trail entries

  // Rate limiting (prevent flood from error loops)
  rateLimit: { maxEvents: 10, windowMs: 1000 },

  // Filter/transform reports before storage
  beforeReport: (report) => {
    // Return false/null to discard
    // Return modified report to transform
    return report;
  },

  debug: false, // Enable SDK debug logging
});

ghostbug.getReports()

Returns all captured bug reports as an array.

const reports = ghostbug.getReports();

ghostbug.toMarkdown()

Returns a GitHub/Jira-ready markdown string of all captured bugs.

const md = ghostbug.toMarkdown();
// Paste into GitHub Issues, Jira, Slack, etc.
## 1. TypeError: Cannot read property 'id' of undefined

- **Type:** error
- **Time:** 2026-02-23T10:21:33.000Z
- **Occurrences:** 3
- **URL:** https://myapp.com/dashboard
- **Browser:** Chrome 122

### Stack Trace
TypeError: Cannot read property 'id' of undefined
    at UserProfile (app.js:42:12)

### Breadcrumbs
| Time | Category | Event |
|------|----------|-------|
| 10:21:30 | click | Clicked button "Save" |
| 10:21:31 | network | POST /api/user -> 500 |
| 10:21:33 | error | TypeError: Cannot read... |

ghostbug.download(filename?)

Downloads all reports as a JSON file.

ghostbug.download();              // ghostbug-report.json
ghostbug.download("my-bugs.json"); // custom filename

ghostbug.onBug(callback)

Subscribe to bugs in real-time. Returns an unsubscribe function.

const unsubscribe = ghostbug.onBug((report) => {
  // Send to Slack, your API, anywhere
  fetch("/api/slack-webhook", {
    method: "POST",
    body: JSON.stringify({ text: report.payload.message }),
  });
});

// Later: stop listening
unsubscribe();

ghostbug.setUser(user)

Attach user context to every report.

ghostbug.setUser({ id: "user-42", plan: "pro", email: "[email protected]" });

ghostbug.setTags(tags)

Add custom tags to every report. Merges with existing tags.

ghostbug.setTags({ environment: "staging", version: "2.1.0" });

ghostbug.destroy()

Teardown everything — removes all listeners, restores patched APIs, unmounts widget.

ghostbug.destroy();

Framework Integration

React / Next.js

"use client"; // Next.js App Router

import { useEffect } from "react";
import ghostbug from "ghostbug";

export default function GhostbugProvider({ children }) {
  useEffect(() => {
    ghostbug.init({ widget: true });
    return () => ghostbug.destroy();
  }, []);

  return <>{children}</>;
}
// layout.tsx
import GhostbugProvider from "./components/GhostbugProvider";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <GhostbugProvider>{children}</GhostbugProvider>
      </body>
    </html>
  );
}

Vue

// main.ts
import ghostbug from "ghostbug";

ghostbug.init({ widget: true });

Svelte

<!-- +layout.svelte -->
<script>
  import { onMount, onDestroy } from "svelte";
  import ghostbug from "ghostbug";

  onMount(() => ghostbug.init({ widget: true }));
  onDestroy(() => ghostbug.destroy());
</script>

<slot />

Plain HTML (CDN)

<script src="https://unpkg.com/ghostbug/dist/index.iife.js"></script>
<script>
  ghostbug.default.init({ widget: true });
</script>

Widget

Enable the floating widget for testers:

ghostbug.init({ widget: true });

The widget:

  • Shows a live bug count badge
  • Click to expand and see all captured bugs
  • Copy MD — copies markdown to clipboard
  • Export — downloads JSON file
  • Uses Shadow DOM — styles never leak into your app
  • Fully isolated — your CSS won't break it

Bug Report Structure

Each captured bug contains:

{
  id: string;              // Unique report ID
  fingerprint: string;     // Hash for deduplication
  type: "error" | "unhandled-rejection" | "console" | "network" | "performance" | "memory";
  timestamp: string;       // ISO 8601
  count: number;           // Occurrences (deduped)
  payload: { ... };        // Error/network/console/perf/memory details
  breadcrumbs: [           // Events leading up to the bug
    { timestamp, category, message, data }
  ];
  context: {
    url: string;
    referrer: string;
    userAgent: string;
    language: string;
    viewport: { width, height };
    screen: { width, height };
    devicePixelRatio: number;
    memory?: { usedJSHeapSize, totalJSHeapSize };
    user?: { ... };        // From setUser()
    tags?: { ... };        // From setTags()
  };
}

How It Works

| Mechanism | Details | |---|---| | Error capture | Patches window.onerror and listens for unhandledrejection | | Console capture | Monkey-patches console.error / console.warn (always calls originals) | | Network capture | Patches fetch and XMLHttpRequest (only captures 4xx/5xx, never alters responses) | | Click tracking | Uses capture-phase click listener (catches clicks even with stopPropagation) | | Interactions | Listens for input, scroll, resize events as breadcrumbs | | Performance | Uses PerformanceObserver for long-task, paint, and layout-shift entries | | Memory | Samples performance.memory every 10s, flags high usage and rapid growth | | Deduplication | Identical errors increment a counter instead of creating duplicate reports | | Rate limiting | Token-bucket algorithm prevents floods from error loops | | Ring buffer | Fixed-capacity storage prevents memory leaks in long-running apps | | Safe teardown | destroy() restores all original APIs cleanly |


Development

npm install          # Install dependencies
npm run build        # Build (ESM + CJS + IIFE)
npm test             # Run tests
npm run test:watch   # Watch mode
npm run typecheck    # TypeScript strict check
npm run lint         # ESLint

License

MIT — do whatever you want.