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

@tatlacas/brevwick-sdk

v1.0.1

Published

Brevwick core SDK — submit issues from any browser app. AI-formatted into clean GitHub-ready issues.

Readme

@tatlacas/brevwick-sdk

npm License: MIT

Framework-agnostic core SDK for Brevwick. Submit issues from any browser app — screenshot, redact, send. AI-formatted into clean, triage-ready GitHub issues.

For React apps, use @tatlacas/brevwick-react (provider, hook, floating-action-button widget). This package is the underlying primitive and ships the entire wire protocol.

Install

npm install @tatlacas/brevwick-sdk@beta

Or with pnpm / yarn / bun — same name. Pre-1.0 releases track the beta dist-tag.

Quick start

Drop into any HTML page — no build step

The fastest possible install. Paste this anywhere a <script type="module"> tag works (static sites, Webflow, WordPress, classic templating layers).

The SDK core ships no built-in floating-action button — the FAB lives in @tatlacas/brevwick-react. For a no-build install, you wire any HTML button (or your own UI) to bw.submit() directly, as below. If you need the FAB UX without React, install the React package and ship a tiny wrapper.

<button id="feedback-btn" type="button">Report a bug</button>

<script type="module">
  import { createBrevwick } from 'https://esm.sh/@tatlacas/[email protected]';

  const bw = createBrevwick({ projectKey: 'pk_live_...' });
  bw.install();

  document
    .getElementById('feedback-btn')
    .addEventListener('click', async () => {
      const result = await bw.submit({
        description: prompt('What went wrong?') ?? '',
        attachments: [await bw.captureScreenshot()],
      });
      alert(
        result.ok
          ? `Filed issue ${result.issue_id}`
          : `Failed: ${result.error.message}`,
      );
    });
</script>

Either CDN works — pick whichever your CSP allows:

| CDN | URL | | -------- | ----------------------------------------------------------------------- | | esm.sh | https://esm.sh/@tatlacas/[email protected] | | jsdelivr | https://cdn.jsdelivr.net/npm/@tatlacas/[email protected]/+esm |

The CDN URLs pin a specific pre-1.0 version so no-build users never silently shift under you. Bump the pin when you upgrade — the latest published beta is the version published to npm under @latest.

End-to-end runnable example (no build tool, serve over python -m http.server): examples/vanilla/static.

With a bundler (Vite / Webpack / Rollup / Next / etc.)

import { createBrevwick } from '@tatlacas/brevwick-sdk';

const bw = createBrevwick({
  projectKey: 'pk_live_...',
  buildSha: process.env.BUILD_SHA,
});

// Start capturing console + network + route rings. Safe to call multiple times.
bw.install();

const result = await bw.submit({
  description: 'Checkout hangs after pressing Pay the second time',
  expected: 'Order completes and confirmation page loads',
  actual: 'Button stays spinning for 30s, then nothing',
  attachments: [await bw.captureScreenshot()],
});

if (result.ok) {
  console.log('Issue filed:', result.issue_id);
} else {
  console.error(result.error.code, result.error.message);
}

submit() never throws for normal failures — callers discriminate on result.ok.

Button-driven submit (no FAB)

For sites without a floating-action-button widget — wire any DOM event to bw.submit(). The example below assumes you already have a form / dialog / chat composer in your own UI:

import { createBrevwick } from '@tatlacas/brevwick-sdk';

const bw = createBrevwick({ projectKey: 'pk_live_...' });
bw.install();

document.querySelector('#report-bug')?.addEventListener('click', async () => {
  const description =
    document.querySelector<HTMLTextAreaElement>('#bug-body')?.value ?? '';
  const screenshot = await bw.captureScreenshot();

  const result = await bw.submit({ description, attachments: [screenshot] });
  if (!result.ok) {
    console.error('[brevwick]', result.error.code, result.error.message);
  }
});

Configuration

createBrevwick(config: BrevwickConfig): Brevwick

BrevwickConfig

| Field | Type | Default | Description | | ------------------- | -------------------------------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | projectKey | string | required | Public ingest key, e.g. pk_live_xxx or pk_test_xxx. Safe to ship in client bundles. | | endpoint | string | https://api.brevwick.com | Override the ingest endpoint. Useful for self-hosted or staging. | | environment | 'dev' \| 'stg' \| 'prod' | unset | Tag issues with the environment they came from. | | enabled | boolean | true | Set false to make every method a no-op. Useful in tests or during incidents. | | buildSha | string | unset | Build SHA included on every issue. Typically process.env.BUILD_SHA or your CI commit. | | release | string | unset | Released app version, e.g. 1.4.2. | | userContext | () => Record<string, unknown> | unset | Resolved at submit time and merged into user_context. Use a function so changing values (route, feature flags, auth state) are captured at the moment of submission, not at SDK init. | | user | { id: string; [k: string]: unknown } | unset | Opaque user identity attached to issues. id is required; any extra fields ride along. | | rings | { console?, network?, route? } | all true | Per-ring toggles. Each accepts the legacy boolean shorthand or the object form below for finer-grained control. | | redact | { disable?, custom? } | unset | Tune the on-device redactor — selectively disable built-in patterns and/or extend with project-specific regexes. | | fingerprintOptOut | boolean | false | Send X-Brevwick-Fingerprint-Optout: 1 to skip the server-side salted fingerprint. |

rings.console

Pass true (or omit) for the default — capture all five console levels (log / info / warn / error / debug) into a 50-entry FIFO. Pass false to disable. Pass an object to narrow:

rings: {
  console: {
    levels: ['warn', 'error'], // default: all five
    max: 100,                  // default: 50, hard ceiling 200
  },
}

To reproduce the legacy errors-only behaviour:

rings: {
  console: {
    levels: ['error'],
  },
}

rings.network

Pass true (or omit) for the default — capture every completed fetch + XHR (success and failure) into a 20-entry FIFO. Pass false to disable. Pass an object to narrow:

rings: {
  network: {
    captureSuccess: false, // default: true — opt out for failures-only mode
    max: 50,               // default: 20, hard ceiling 100
  },
}

Wire contract — behaviour change. The submitted issue payload renames the network ring's wire field from network_errorsnetwork_calls. The server-side ingest (sanitiser + schema shape-lock fixtures) mirrors the rename in lockstep. Consumers that previously read network_errors off captured payloads must follow the rename — this is a breaking wire change in the pre-1.0 series.

redact

The redactor runs on every string field that leaves the device. Built-in patterns scrub Authorization / Cookie headers, Bearer … tokens, JWTs, emails, credit-card numbers (Luhn-gated), IPv4 / IPv6 literals, US SSN / UK NI numbers, E.164 phone numbers, AWS access keys, GitHub tokens, and long base64 blobs.

redact: {
  // Selectively turn off built-ins. Names accepted:
  //   'auth', 'cookie', 'bearer', 'jwt', 'email',
  //   'card', 'ip', 'ssn', 'phone', 'aws', 'github', 'base64'
  disable: ['phone'], // common case: free-text fields with phone-like order numbers
  // Add project-specific patterns. A bare RegExp is replaced with [redacted];
  // pass an object to control the replacement string.
  custom: [/secret-\w+/g, { pattern: /widget-\d+/g, replacement: '[w]' }],
}

The phone-number matcher is the most false-positive-prone pattern (any 8–15 digit run with separators looks like a phone number). Concretely, the following inputs will be masked by the phone matcher today — flip it off via disable: ['phone'] if any of these hurt you:

  • ISO-8601 timestamps: 2026-05-01T10:30:45[phone]T10:30:45
  • SHA hashes whose leading 8+ characters happen to be all digits
  • Order numbers, tracking IDs, or national IDs of 8–15 digits (e.g. SA-ID 9001015800087[phone])

It is on by default because real phone leaks in free-text fields are higher-impact than the false positives above; the trade-off is exposed via disable rather than tightened in the regex so consumers stay in control.

Example with everything set

const bw = createBrevwick({
  projectKey: 'pk_live_abc123',
  environment: 'prod',
  buildSha: process.env.NEXT_PUBLIC_BUILD_SHA,
  release: process.env.NEXT_PUBLIC_APP_VERSION,
  user: { id: currentUser.id, plan: currentUser.plan },
  userContext: () => ({
    route: window.location.pathname,
    locale: document.documentElement.lang,
  }),
  rings: { console: true, network: true, route: true },
});

bw.install();

The Brevwick instance

createBrevwick(config) returns a Brevwick object with the following methods.

install(): void

Starts the enabled rings (console / network / route). Safe to call more than once — subsequent calls while already installed are no-ops. Full no-op in non-browser contexts (SSR, workers).

uninstall(): void

Restores every patched global and drains internal buffers. A second call is a no-op. After uninstall(), calling install() again on the same instance is not supported and will throw — create a new instance if you need to restart.

submit(input: FeedbackInput): Promise<SubmitResult>

Send a feedback issue. Resolves to a tagged union — does not throw for ingest errors.

FeedbackInput:

| Field | Type | Description | | ------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | description | string | Required. The body of the issue — what happened, from the user's perspective. | | title | string | Optional issue title. Defaults to the first line of description, truncated to 120 chars. | | expected | string | What the user expected to see. | | actual | string | What actually happened. | | attachments | Array<Blob \| FeedbackAttachment> | Up to 5 files, each ≤ 10 MB, MIME in image/png, image/jpeg, image/webp, video/webm. | | use_ai | boolean | Per-issue AI formatting opt-in/out. Only honoured when the project enables submitter choice; omit otherwise and the server applies the project default. |

FeedbackAttachment:

interface FeedbackAttachment {
  blob: Blob;
  filename?: string;
}

Plain Blobs also work — Brevwick will derive a filename from the MIME type.

SubmitResult:

type SubmitResult =
  | { ok: true; issue_id: string }
  | { ok: false; error: { code: SubmitErrorCode; message: string } };

SubmitErrorCode:

| Code | When it fires | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ATTACHMENT_UPLOAD_FAILED | Client-side validation rejected an attachment (count > 5, size > 10 MB, disallowed MIME), or the presign / R2 PUT failed before the issue POST was reached. | | INGEST_REJECTED | Ingest endpoint returned a 4xx (e.g. quota exceeded, payload too large). Not retried — the same payload would be rejected again. The server-echoed message is appended (capped at 256 chars, already redacted). | | INGEST_RETRY_EXHAUSTED | Ingest POST hit the max retry count (one initial + two backoffs) on 5xx / thrown fetch and never succeeded. | | INGEST_TIMEOUT | The 30 s total-budget AbortController fired before the pipeline completed. | | INGEST_INVALID_RESPONSE | Ingest returned 2xx with a body that didn't parse as JSON or didn't include a string issue_id. |

The one case where submit() rejects instead of resolving is an environmental failure before the pipeline runs — the lazy submit chunk fails to load (offline, CDN outage, deploy mismatch). A rejection is the honest signal that the request never reached the ingest. Wrap in .catch if your app runs in hostile environments.

captureScreenshot(opts?): Promise<Blob>

Capture a WebP screenshot of the current page (or a sub-tree). Never throws — on failure, returns a 1×1 transparent WebP placeholder so callers that always attach the result still get a valid image/webp blob.

const blob = await bw.captureScreenshot({
  element: document.getElementById('app') ?? undefined,
  quality: 0.9,
});

Options:

| Field | Type | Default | Description | | --------- | -------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------- | | element | HTMLElement | document.body | Sub-tree to capture. Only descendants with [data-brevwick-skip] are scrubbed — a skip marker on the root itself is ignored. | | quality | number (0–1) | 0.85 | WebP encoder quality, forwarded to modern-screenshot's domToBlob. |

About the document.body default: prior to this version the default was document.documentElement. We changed it because the documentElement default reproduced as a ~2 KiB blank image in at least one consumer integration and modern-screenshot's own README captures against <body>. The exact failure mode of the documentElement path is not yet root-caused — treat the new default as a behaviour-improving change rather than a verified upstream fix. Two behaviour changes worth knowing:

  • CSS custom properties declared on :root (html) or <body> are still inherited by the cloned <body> subtree because modern-screenshot inlines computed styles before reparenting under <foreignObject>, and getComputedStyle(body) already resolves ancestor-declared tokens. No action needed for typical design-system setups (Tailwind, CSS variables on :root, theme classes on body).
  • Portals into <html> (browser extensions, atypical portal libraries) are no longer inside the capture tree. Most React/Solid/Vue portals land in document.body and continue to be captured. To preserve the previous behaviour, pass opts.element: document.documentElement explicitly.

Screenshot privacy: any element marked data-brevwick-skip is hidden before capture and restored afterwards, even on failure. Use it on password fields, PII, card numbers, anything that should never land in a bug report:

<input data-brevwick-skip type="password" />
<div data-brevwick-skip>{customerEmail}</div>

A tree-shakable top-level captureScreenshot is also exported for standalone use (no Brevwick instance required). It's dynamically imported on first call so modern-screenshot stays out of your initial bundle:

import { captureScreenshot } from '@tatlacas/brevwick-sdk';
const blob = await captureScreenshot({ quality: 0.9 });

getConfig(): Promise<ProjectConfig | null>

Fetches project-level AI config from GET /v1/ingest/config. Used by the React widget to decide whether to render the per-issue "Format with AI" toggle. Returns null on non-2xx, malformed JSON, or thrown fetch — treat null as "no submitter choice, use server default". Cached per instance for the session.

interface ProjectConfig {
  ai_enabled: boolean;
  ai_submitter_choice_allowed: boolean;
}

Redaction

Every payload runs through the redactor before it leaves the browser:

  • Console ringlog / info / warn / error / debug messages, deduped across identical repeats within a 500 ms window. All five levels by default; narrow via rings.console.levels.
  • Network ring — every completed fetch + XHR (success and failure) by default. Request body capped at 2 kB, response body at 4 kB, both redacted. Headers are allow-listed. Opt into failures-only mode with rings.network: { captureSuccess: false }.
  • Route ringpushState / popstate / hashchange route transitions.

Built-in redact patterns cover Authorization / Cookie / Bearer headers, JWTs, emails, credit-card numbers (Luhn-gated to skip false positives), IPv4 / IPv6 literals, US SSN + UK NI numbers, E.164 phone numbers, AWS access keys, GitHub tokens, and long base64 blobs. Tune via BrevwickConfig.redact: { disable, custom } (see the redact section above).

Server-side sanitisation runs as defence-in-depth, but the client redactor is the primary guarantee — nothing leaves the device unredacted.

Bundle size

Enforced in CI via size-limit and asserted in tests:

  • Eager core (createBrevwick + console ring + network ring + ring config validation): ≤ 8 kB gzip. The console + network rings ride the eager path on purpose — they have to be live before the first user error or fetch fires, otherwise the issue you're trying to file arrives missing the very evidence you opened the widget to report.
  • On first captureScreenshot() call: modern-screenshot dynamic-imports and adds up to ≤ 25 kB gzip.
  • On first submit() call: the submit pipeline (presign / upload / ingest / retry) dynamic-imports.

The screenshot encoder, the submit pipeline, and the project-config fetch are all dynamic-imported — importing this package does not pull them in until you actually call them.

sideEffects: false

The package is marked "sideEffects": false. Bundlers will tree-shake away everything you don't import.

TypeScript

First-class TS — the package ships .d.ts for both ESM and CJS. Key types re-exported:

import type {
  Brevwick,
  BrevwickConfig,
  BrevwickRedactConfig,
  BrevwickRingsConfig,
  CaptureScreenshotOpts,
  ConsoleLevel,
  ConsoleRingConfig,
  Environment,
  FeedbackAttachment,
  FeedbackInput,
  NetworkRingConfig,
  ProjectConfig,
  RedactCustomPattern,
  RedactPatternName,
  SubmitError,
  SubmitErrorCode,
  SubmitResult,
} from '@tatlacas/brevwick-sdk';

Browser support

ES2020 targets — modern evergreen browsers (Chrome/Edge 90+, Firefox 90+, Safari 15+). No IE, no transpile-down. Runs fine inside SSR / workers as a no-op (methods defer to real work on first client mount).

Links

License

MIT