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

peyeeye

v1.1.2

Published

Official TypeScript SDK for the peyeeye PII redaction API — detect, redact, and rehydrate personal data in LLM pipelines with deterministic tokens, zero retention, and stateless sealed sessions.

Readme

peyeeye

npm version npm downloads bundle size license types: TypeScript provenance CI homepage

Official TypeScript SDK for peyeeye.ai — redact PII on the way into your LLM prompts and rehydrate it on the way out. One round-trip, deterministic tokens, zero data retention by default.

  • Works on Node 18+, Bun, Deno, Cloudflare Workers, and Vercel Edge.
  • Zero runtime dependencies. Uses the platform fetch.
  • Dual ESM + CJS build with typed .d.ts / .d.cts.
  • Streaming (SSE) redact, stateless sealed mode, custom detectors — full parity with the HTTP API documented at https://peyeeye.ai/docs.
npm install peyeeye

Get an API key

  1. Sign up at https://peyeeye.ai/signup (free plan, no card required — 1 M characters / month, all 30+ built-in detectors).
  2. Head to https://peyeeye.ai/dashboard/keysNew key.
  3. Copy the full pk_live_… (or pk_test_…) token shown once — only a hash is stored after you close the dialog. Drop it into your env:
export PEYEEYE_KEY=pk_live_...

Test keys bypass billing and are rate-limited for development; live keys count against your plan. Paid tiers (Build / Pro / Scale) unlock streaming, custom detectors, and higher throughput — see https://peyeeye.ai/pricing.

Never ship an API key to the browser — call /redact and /rehydrate from your server or edge runtime.

Quickstart

import { Peyeeye } from "peyeeye";
import Anthropic from "@anthropic-ai/sdk";

const peyeeye = new Peyeeye({ apiKey: process.env.PEYEEYE_KEY! });
const claude  = new Anthropic();

const shield = await peyeeye.shield();
const safe   = await shield.redact("Hi, I'm Ada, [email protected]");

const reply = await claude.messages.create({
  model: "claude-sonnet-*",
  max_tokens: 256,
  messages: [{ role: "user", content: safe }],
});

console.log(await shield.rehydrate(reply.content[0].text));
// "Hi Ada, thanks — we've emailed [email protected]."

shield() opens a session on first redact(), keeps using it across calls, and swaps tokens back on rehydrate(). The same real value always yields the same token inside one shield, and tokens never leak across shields.

Configuration

new Peyeeye({
  apiKey: "pk_live_…",
  baseUrl: "https://api.peyeeye.ai", // optional
  maxRetries: 3,                     // default; 429 + 5xx back off exponentially
  timeoutMs: 30_000,                 // default per-request timeout
  defaultHeaders: { "X-App": "my-app" },
  fetch: globalThis.fetch,           // override e.g. for Cloudflare Workers
});

All requests send Authorization: Bearer <apiKey>. Never ship the key to a browser — proxy the redact + rehydrate calls from your backend.

Low-level calls

const r = await peyeeye.redact("Card: 4242 4242 4242 4242");
// r.redacted → "Card: [CARD_1]"
// r.session  → "ses_…"
// r.entities → [{ token: "[CARD_1]", type: "CARD", span: [6, 25], confidence: 0.99 }]

const clean = await peyeeye.rehydrate("Confirmation for [CARD_1].", r.session);
// clean.text → "Confirmation for 4242 4242 4242 4242."

Array input is processed in one session and mirrored on output:

const r = await peyeeye.redact(["Hi Ada", "email [email protected]"]);
// r.redacted[0] → "Hi [PERSON_1]"
// r.redacted[1] → "email [EMAIL_1]"

Idempotency

await peyeeye.redact(text, { idempotencyKey: "req_a1b2c3" });

Mismatched bodies with the same key raise idempotency_conflict (409). Same body is served from the cache instantly.

Stateless sealed mode

Skip server-side storage entirely. The response includes a rehydration_key (skey_…) — an AES-256-GCM-sealed blob of the token→value mapping. Store it yourself, hand it back to rehydrate as the session:

const r = await peyeeye.redact("Email [email protected]", { session: "stateless" });
// r.rehydration_key → "skey_…"

const clean = await peyeeye.rehydrate("[EMAIL_1] received.", r.rehydration_key!);

Or via a shield:

const shield = await peyeeye.shield({ stateless: true });
await shield.redact("Email [email protected]");
// shield.rehydrationKey holds the skey_… blob if you need to persist it
await shield.rehydrate("[EMAIL_1] received.");

Streaming

redactStream() (SSE — Build plan and higher)

for await (const ev of peyeeye.redactStream(["Hi Ada", " card 4242 4242 4242 4242"])) {
  if (ev.event === "session")  console.log("session:", ev.data.session);
  if (ev.event === "redacted") process.stdout.write(ev.data.text);
  if (ev.event === "done")     console.log("\ntotal chars:", ev.data.chars);
}

Rehydrate an LLM token stream safely

Naive rehydration breaks when a chunk ends mid-token ("Hi [PERS"). The shield buffers the partial token until the next chunk closes it:

const shield = await peyeeye.shield();
const safe   = await shield.redact(userInput);

const upstream = await claude.messages.stream({
  model: "claude-sonnet-*",
  messages: [{ role: "user", content: safe }],
});

for await (const chunk of upstream) {
  process.stdout.write(await shield.rehydrateChunk(chunk));
}
process.stdout.write(await shield.flush()); // emit any buffered tail

Never call flush() while upstream is still delivering chunks — you can emit a half-formed token.

Custom detectors

await peyeeye.createEntity({
  id: "ORDER_ID",
  kind: "regex",
  pattern: "#A-\\d{6,}",
  examples: ["#A-884217", "#A-007431"],
  confidence_floor: 0.9,
});

// dry-run a pattern before saving
await peyeeye.testPattern({ pattern: "#A-\\d+", text: "#A-884217 and #A-1" });
// → { matches: [{ value: "#A-884217", start: 0, end: 9 }], count: 1 }

// list / update / retire
await peyeeye.listEntities();
await peyeeye.updateEntity("ORDER_ID", { enabled: false });
await peyeeye.deleteEntity("ORDER_ID");

// starter templates (Stripe keys, Twilio SIDs, JWTs, Slack tokens, …)
for (const t of await peyeeye.entityTemplates()) {
  console.log(t.id, t.pattern);
}

Plan gates: Free 0, Build 3, Pro 10, Scale unlimited. Over-cap returns 403 forbidden.

Sessions

await peyeeye.getSession("ses_…");    // → SessionInfo
await peyeeye.deleteSession("ses_…"); // drop immediately, don't wait for TTL

Framework integrations

Vercel AI SDK

Drop-in middleware for wrapLanguageModel — redacts prompt text before the model sees it, rehydrates tokens in the response, and buffers partial placeholders across streamed chunks. One fresh session per model call, so tokens never leak between requests.

import { wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import { Peyeeye } from "peyeeye";
import { peyeeyeMiddleware } from "peyeeye/vercel-ai";

const peyeeye = new Peyeeye({ apiKey: process.env.PEYEEYE_KEY! });

const model = wrapLanguageModel({
  model: openai("gpt-4o-mini"),
  middleware: peyeeyeMiddleware({ peyeeye }),
});

// Use `model` anywhere the SDK expects a LanguageModel — generateText,
// streamText, generateObject — prompts are redacted in, responses rehydrated
// out, with zero extra app code.

Opt into stateless sealed mode (no server-side mapping) with peyeeyeMiddleware({ peyeeye, stateless: true }).

LangChain.js

Wrap any LangChain.js Runnable (chat model, LLM, or chain). Redacts the prompt before .invoke(), rehydrates tokens in the response, and opens a fresh session per call.

import { ChatOpenAI } from "@langchain/openai";
import { Peyeeye } from "peyeeye";
import { withPeyeeye } from "peyeeye/langchain";

const peyeeye = new Peyeeye({ apiKey: process.env.PEYEEYE_KEY! });
const model   = withPeyeeye(new ChatOpenAI({ model: "gpt-4o-mini" }), { peyeeye });

const reply = await model.invoke("Hi, I'm Ada — [email protected]");

Accepted inputs: plain strings, BaseMessage arrays, tuple shorthand (["human", "text"]), dict messages ({ role, content }), and multimodal content arrays — image parts pass through untouched. Exposes .invoke() and .batch(). For LCEL composition (.pipe(...)) wrap with RunnableLambda.from((x) => wrapped.invoke(x)).

Opt into stateless sealed mode with withPeyeeye(model, { peyeeye, stateless: true }).

Errors

Every non-2xx raises PeyeeyeError:

import { PeyeeyeError } from "peyeeye";

try {
  await peyeeye.redact(input);
} catch (e) {
  if (e instanceof PeyeeyeError) {
    console.error(e.code, e.status, e.message, e.requestId, e.rateLimit);
    if (e.retryable) { /* 429 / 5xx — SDK already retried up to maxRetries */ }
  }
}

Codes the backend uses: invalid_request, unknown_token, unauthorized, forbidden, not_found, session_not_found, idempotency_conflict, payload_too_large, rate_limited, internal_error.

Rate limits

Parsed from response headers and surfaced on PeyeeyeError.rateLimit:

{ limit: 500, remaining: 487, retryAfter: null }

429s carry retryAfter in seconds — the SDK honours it automatically via exponential backoff, capped at maxRetries.

Environment variables

The SDK itself reads no env vars. Typical usage:

PEYEEYE_KEY=pk_live_…
new Peyeeye({ apiKey: process.env.PEYEEYE_KEY! });

TypeScript types

Everything public is re-exported:

import type {
  PeyeeyeOptions, RedactOptions, RedactResponse,
  RehydrateOptions, RehydrateResponse,
  DetectedEntity, SessionInfo, RateLimit,
  EntitiesList, CustomDetector, EntityTemplate, StreamEvent,
} from "peyeeye";

Using this SDK from an AI coding assistant

Copy-paste snippets — no fluff.

Install: npm install peyeeye

One round-trip through an LLM:

import { Peyeeye } from "peyeeye";
const peyeeye = new Peyeeye({ apiKey: process.env.PEYEEYE_KEY! });
const shield  = await peyeeye.shield();
const safe    = await shield.redact(userInput);
const reply   = await callYourLLM(safe);           // your own code
const out     = await shield.rehydrate(reply);     // tokens → real values

Stateless (no server-side storage):

const shield = await peyeeye.shield({ stateless: true });
const safe   = await shield.redact(userInput);
// shield.rehydrationKey is the skey_… blob — persist it if you need to
const out    = await shield.rehydrate(reply);

Stream an LLM response back safely:

for await (const chunk of llmStream) {
  process.stdout.write(await shield.rehydrateChunk(chunk));
}
process.stdout.write(await shield.flush());

Register a custom detector:

await peyeeye.createEntity({
  id: "ORDER_ID",
  pattern: "#A-\\d{6,}",
  examples: ["#A-884217"],
});

Links

License

MIT.