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

@rallycry/conveyor-incidents

v0.3.8

Published

Typed TypeScript client for Conveyor's public incidents REST API

Readme

@rallycry/conveyor-incidents

Typed TypeScript client for reporting incidents to Conveyor via the public REST API.

Install

npm install @rallycry/conveyor-incidents
# or
bun add @rallycry/conveyor-incidents

Usage

import { ConveyorIncidents } from "@rallycry/conveyor-incidents";

const incidents = new ConveyorIncidents({
  apiKey: process.env.CONVEYOR_INCIDENT_TOKEN!, // cvyr_... project token with "incident" scope
  projectId: process.env.CONVEYOR_PROJECT_ID!,
});

const { incidentId, attachmentResults } = await incidents.report({
  title: "Checkout service returned 500",
  description: "Happened on release v1.4.2 under load.",
  severity: "high",
  source: "prod-web", // stable values let the server dedup repeats
  fingerprintKey: "checkout-500-v1.4.2",
  attachments: [
    {
      fileName: "stack.txt",
      mimeType: "text/plain",
      content: stackTrace, // string or Uint8Array
    },
  ],
});

Card types

report() accepts an optional type — "incident" (default), "suggestion", or "task":

  • "incident" — fingerprint dedup (see below); repeat reports collapse into one incident.
  • "suggestion" — AI duplicate detection; a repeat report upvotes the existing suggestion and returns its id instead of creating a new card. fingerprintKey and severity are ignored (suggestions are ranked by votes).
  • "task" — creates a plain task card. No dedup; fingerprintKey is ignored. severity maps to the card's priority.
// Platform-user suggestion, routed to the team for review
await incidents.report({
  type: "suggestion",
  title: "Add dark mode toggle",
  assigned: ["[email protected]"],
  review: ["[email protected]", "[email protected]"],
});

Assigning members by email

assigned and review are optional arrays of email addresses, matched against the target project's members:

  • assigned — the first email that resolves to a project member becomes the card's assignee; remaining entries are ignored.
  • review — every email that resolves to a project member is added as a reviewer.
  • Emails that don't match a project member are silently skipped — safe to pass end-user emails; only teammates who are members of the Conveyor project resolve.
  • Applied only when a new card is created. Dedup hits (incident fingerprint match, suggestion AI-merge) leave the existing card untouched.
// Incident auto-assigned when the reporter is on your own email domain
await incidents.report({
  title: "Checkout service returned 500",
  severity: "high",
  fingerprintKey: "checkout-500-v1.4.2",
  assigned: ["[email protected]"],
});

Creator attribution (createdBy)

createdBy is an optional single email, matched against the target project's members the same way as assigned/review:

  • A resolvable project-member email becomes the card's creator.
  • When omitted — or when the email doesn't resolve to a member — the card is attributed to the "Conveyor Incidents" system user, not a human.
  • Applied only when a new card is created; dedup hits keep the existing creator.

Behavior change in 0.3.0: previously, cards reported through this endpoint were attributed to the token owner (suggestions/tasks) or the project owner (incidents). Pass createdBy explicitly if you want a specific member credited.

await incidents.report({
  type: "task",
  title: "Wire up the new webhook",
  createdBy: "[email protected]",
});

Reporter metadata (reporter)

For any user-initiated report, send who filed it and what page they were on:

await incidents.report({
  title: "Riot login fails",
  description: userMessage,
  reporter: {
    name: currentUser.displayName,
    email: currentUser.email,
    url: window.location.href,
  },
});

Conveyor renders these as Reported by: / URL: lines in the card description's --- metadata footer, and surfaces them on the card's Slack message. Unlike createdBy, no project membership is required — this is descriptive metadata, so an end-user report keeps an identity instead of reading as an anonymous automated alert. Lines your own footer already writes (e.g. your own URL:) are not duplicated, and url must be an absolute http(s) URL.

Conveyor also stores reporter.email on the card as a structured field. When a project has reporter emails enabled, that address is the one Conveyor drafts a thank-you reply to — so send it for user-initiated reports even when the reporter has no Conveyor account. A malformed address is dropped; the report still lands.

About source and fingerprintKey

Conveyor deduplicates reports by a fingerprint derived from (projectId, source, fingerprintKey) — or by (projectId, source, title) if fingerprintKey is omitted. Use stable values for source (e.g. "prod-web", not "node-pid-1234") so that repeat reports collapse into a single incident instead of creating noise.

Attachment limits

  • 5 MB per file (MAX_ATTACHMENT_BYTES, exported). Oversized files are rejected client-side with a descriptive attachmentResults error — no HTTP call is made.
  • 10 attachments per card (server-enforced).

Browser network evidence

For browser-based incident reporters, opt into the bounded fetch recorder early in your app shell and attach its log when the user reports a bug:

import {
  buildNetworkLogAttachment,
  installNetworkCapture,
} from "@rallycry/conveyor-incidents";

installNetworkCapture({ maxEntries: 120, maxBodyChars: 6000 });

await incidents.report({
  title: "Competition page showed 403",
  attachments: [
    buildNetworkLogAttachment(),
    // ...context.json, screenshot.jpg, console.log, etc.
  ].filter((it): it is NonNullable<typeof it> => it !== undefined),
});

network.log is newline-delimited JSON. Each entry includes method, URL with sensitive query parameters redacted, status, duration, traceparent headers when available, tRPC procedure names for /trpc/a,b,c batches, and capped response snippets for failed responses and tRPC batches. This is intentionally diagnostic, not a full traffic archive.

Error handling

  • The main report() call throws ConveyorIncidentsError on non-2xx responses; it exposes .status and .body.
  • Attachment upload failures are captured per-file in attachmentResults and never throw — inspect ok and error.

Node <18

Pass a fetch polyfill via opts.fetch if your runtime lacks a global fetch.

DEP0169 url.parse() warning

The @rallycry/conveyor-incidents SDK does not call url.parse() — it uses the WHATWG URL constructor and the global fetch API. If your process emits this warning while using the SDK, the call is coming from another dependency in your project. Run node --trace-deprecation your-script.js to locate the source.

Troubleshooting 403 from /api/incidents/report

The endpoint requires a project token with scope: "incident". After the 2026-04-22 scope migration, older project tokens default to scope: "claude_code" and are rejected. The error body now includes a reason field — "scope_mismatch" indicates the token exists but is scoped for another surface; generate a new token from Project Settings → Incidents in the Conveyor UI.

API

new ConveyorIncidents({
  apiKey: string;
  projectId: string;
  baseUrl?: string; // default: "https://api.conveyor.rallycryapp.com"
  fetch?: typeof fetch;
});

client.report({
  title: string;
  description?: string;
  severity?: "critical" | "high" | "medium" | "low"; // default: "medium"
  source?: string;                                    // default: "client"
  fingerprintKey?: string;
  plan?: string;
  type?: "incident" | "suggestion" | "task";          // default: "incident"
  assigned?: string[]; // project-member emails; first match becomes assignee
  review?: string[];   // project-member emails; every match becomes a reviewer
  createdBy?: string;  // project-member email; no match → "Conveyor Incidents" system user
  reporter?: {         // who filed it + where; rendered into the card's metadata footer
    name?: string;
    email?: string;    // also stored on the card; used for reporter thank-you emails
    url?: string;      // absolute http(s) URL of the page at report time
  };
  attachments?: Array<{
    fileName: string;
    mimeType: string;
    content: Uint8Array | string; // strings are utf-8 encoded
  }>;
}): Promise<{
  incidentId: string;
  cardId: string; // alias of incidentId — prefer for suggestion/task types
  attachmentResults: Array<{
    fileName: string;
    ok: boolean;
    fileId?: string;
    error?: string;
  }>;
}>;

installNetworkCapture({
  maxEntries?: number;          // default: 80
  maxBodyChars?: number;        // default: 4000
  captureTrpcResponses?: boolean; // default: true
}): void;

getNetworkLogEntries(): NetworkLogEntry[];
clearNetworkLogEntries(): void;
uninstallNetworkCapture(): void;
buildNetworkLogAttachment(fileName?: string): IncidentAttachment | undefined;

License

MIT