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

zephr

v1.3.1

Published

Zero-knowledge secret transport for AI agents — CLI and JavaScript SDK

Readme

zephr

How agents hand off credentials, securely. CLI and JavaScript SDK for Zephr.

Create a one-time secret link: encrypted on your device and self-destructing after a single retrieval. Pass it between agents, services, and pipelines with no shared infrastructure and no plaintext on the server.

Designed for zero-knowledge secret handoff between independent systems: AI agents, CI/CD pipelines, GitHub Actions, and human operators.

New in Sprint 2: Webhook callbacks (--callback-url) notify your pipeline when a secret is consumed. Idempotency keys are auto-generated on every create for safe retries. See Webhook callback and Idempotency below.

How it works

  1. A 256-bit key is generated locally. It never reaches Zephr's servers.
  2. Your secret is encrypted with AES-GCM on your device
  3. Only the ciphertext is uploaded to Zephr
  4. The link embeds the key in the URL fragment, which browsers never transmit to servers
  5. First retrieval atomically consumes the record. A second request returns 410.

Features

  • No shared infrastructure: a link is the entire transport mechanism between independent processes
  • Zero-knowledge: the server never receives your plaintext or encryption keys
  • Local encryption: AES-GCM-256 on your device before any network call
  • One-time access: record marked consumed atomically on first retrieval
  • Zero external dependencies: built on Node.js built-ins and Web Crypto only
  • Pipe support: reads from stdin
  • API key support for higher limits and longer expiry
  • Webhook callbacks: get notified when a secret is consumed (callbackUrl + callbackSecret)
  • Idempotency: auto-generated Idempotency-Key on every create for safe retries

Installation

# No installation required — one-off use, CI, agent environments
npx zephr "my-secret"

# Global install
npm install -g zephr

# SDK for Node.js 22+ and browser bundles
npm install zephr

CLI

zephr <secret> [options]              # Create a one-time secret
echo "secret" | zephr [options]       # Create from stdin
zephr retrieve <link> [options]       # Retrieve and decrypt

Create options:
  -e, --expiry <minutes> Expiration in minutes: 5, 15, 30, 60, 1440, 10080, or 43200 (default: 60; sub-hour values (5, 15, 30) require Dev/Pro; all other values require a free account or higher)
  -s, --split            Return URL and key separately
  -H, --hint <label>     Plaintext label for routing and audit logs (1-128 printable ASCII, not encrypted)
  --callback-url <url>   HTTPS webhook URL — receive a signed event on consumption
  --callback-secret <s>  HMAC-SHA256 signing secret for the webhook (required with --callback-url)
  -k, --api-key <key>    API key; takes precedence over ZEPHR_API_KEY env var

Retrieve options:
      --url <url>        Secret URL (split mode)
      --key <key>        Encryption key (split mode)
  -k, --api-key <key>    API key

General:
  -v, --version          Show version
  -h, --help             Show help

Examples

# Pass a credential to a downstream process
echo "$DB_PASSWORD" | zephr --expiry 60

# Split mode: URL and key through separate channels
zephr "$API_KEY" --split

# Attach a plaintext label for routing and audit logs
zephr "$API_KEY" --hint STRIPE_KEY_PROD

# From password manager
pass show production/db | zephr

# From file
cat ~/.ssh/id_rsa.pub | zephr

# Authenticated: higher limits and longer expiry
zephr "$API_KEY" --expiry 10080 --api-key zeph_...
ZEPHR_API_KEY=zeph_... zephr "$API_KEY" --expiry 10080

# Dev/Pro: 30-day expiry
zephr "$API_KEY" --expiry 43200 --api-key zeph_...

# Webhook callback — get notified when the secret is consumed
zephr "$API_KEY" --callback-url https://my-server.example.com/zephr-events \
  --callback-secret my-hmac-secret --api-key zeph_...

Idempotency

The CLI auto-generates an Idempotency-Key header on every create. If a request times out at the infrastructure level and is replayed, the server returns the cached response without creating a duplicate secret. Cache TTL: 24 hours.

Retrieve

# Standard mode — pass the full link
zephr retrieve "https://zephr.io/secret/abc123#v1.key..."

# Split mode — URL and key separately
zephr retrieve --url "https://zephr.io/secret/abc123" --key "v1.key..."

# From stdin (e.g. piped from another command)
echo "https://zephr.io/secret/abc123#v1.key..." | zephr retrieve

The decrypted secret is written to stdout (pipeable). If the secret has a hint, it is written to stderr:

Hint: STRIPE_KEY_PROD          ← stderr (metadata)
sk-live-abc123                 ← stdout (data)

This means zephr retrieve <link> | pbcopy copies only the plaintext.

Output

Standard mode:

[OK] Secret encrypted on your device
[OK] Uploaded to Zephr

Link: https://zephr.io/secret/abc123#v1.key...

Expires in 1 hour. One-time access only.

Split mode:

[OK] Secret encrypted on your device
[OK] Uploaded to Zephr

Split mode - share these separately:

URL: https://zephr.io/secret/abc123
Key: v1.key...

Expires in 1 hour. One-time access only.

Authentication

The CLI and SDK work without an account. No setup required. Free, Dev, and Pro tier features require an API key. Pass it via --api-key or the ZEPHR_API_KEY environment variable. Anonymous requests are capped at 3/day per IP with a 1 h max expiry.

| Tier | Create limit | Expiry options | Max size | Authentication | |------|-------------|----------------|----------|----------------| | Anonymous | 3/day | 1h | 6 KB | None | | Free | 50/month | 1h, 24h, 7d, 30d | 20 KB | --api-key zeph_... | | Dev ($15/mo) | 2,000/month | 5m, 15m, 30m, 1h, 24h, 7d, 30d | 200 KB | --api-key zeph_... | | Pro ($39/mo) | 50,000/month | 5m, 15m, 30m, 1h, 24h, 7d, 30d | 1 MB | --api-key zeph_... |

Getting an API key: Log in at zephr.io/account, open the API Keys tab, and create a key. The raw key is shown exactly once. Copy it immediately.

Passing the key:

# Flag takes precedence over the environment variable
zephr "secret" --api-key zeph_...

# Environment variable: preferred for CI and scripts
export ZEPHR_API_KEY=zeph_...
zephr "secret"

# Inline for one-off use
ZEPHR_API_KEY=zeph_... zephr "secret"

GitHub Actions: Add ZEPHR_API_KEY as a repository secret, then reference it in your workflow. All zephr steps are automatically authenticated:

env:
  ZEPHR_API_KEY: ${{ secrets.ZEPHR_API_KEY }}

steps:
  - run: echo "$SECRET" | zephr --expiry 60
    env:
      SECRET: ${{ secrets.MY_SECRET }}

The key is sent as Authorization: Bearer zeph_... on each request. An invalid or revoked key returns HTTP 401.

JavaScript / TypeScript SDK

Isomorphic: works in Node.js 22+ and any browser bundle. TypeScript declarations included. Zero external dependencies.

import { createSecret, retrieveSecret } from 'zephr';

// Named expiry constants for readability (raw integers also accepted)
import { EXPIRY } from 'zephr/limits.js';
// EXPIRY.MINUTES_5, EXPIRY.MINUTES_15, EXPIRY.MINUTES_30,
// EXPIRY.HOURS_1, EXPIRY.HOURS_24, EXPIRY.DAYS_7, EXPIRY.DAYS_30

Agent A encrypts and hands off the link. Agent B retrieves it exactly once:

// Agent A: encrypt and dispatch
const { fullLink } = await createSecret('sk-live-abc123', { expiry: 60 });
agentB.dispatch({ credential: fullLink });

// Agent B: consumed atomically on first read
const { plaintext } = await retrieveSecret(fullLink);

Split mode: URL and key through separate channels:

const { url, key } = await createSecret('db-password', { split: true, expiry: 60 });
agentB.dispatch({ credentialUrl: url });
sideChannel.send(key); // key never shares a channel with the URL

const { plaintext } = await retrieveSecret({ url, key });

Return value

createSecret() resolves to an object. Standard mode (split: false):

{
  mode:      'standard',
  fullLink:  'https://zephr.io/secret/Ht7kR2...#v1.key...',
  expiresAt: '2026-03-08T12:00:00.000Z',  // ISO 8601
  secretId:  'Ht7kR2...',                  // 22-char base64url ID
}

Split mode (split: true):

{
  mode:      'split',
  url:       'https://zephr.io/secret/Ht7kR2...',
  key:       'v1.key...',
  expiresAt: '2026-03-08T12:00:00.000Z',
  secretId:  'Ht7kR2...',
}

retrieveSecret() resolves to a RetrievalResult object with properties plaintext (string), hint (string or undefined), and purgeAt (string or undefined).

Authenticated use: pass your API key for higher limits and longer expiry:

// Free: 50/mo, 30d max; Dev: 2,000/mo, 30d max; Pro: 50,000/mo, 30d max
const { fullLink } = await createSecret('sk-live-abc123', {
  expiry: 10080,                          // up to 43200 on Dev/Pro
  apiKey: process.env.ZEPHR_API_KEY,      // 'zeph_...'
});

// apiKey is optional on retrieve; include it to count against your authenticated quota
const { plaintext } = await retrieveSecret(fullLink, {
  apiKey: process.env.ZEPHR_API_KEY,
});

In GitHub Actions, expose the repository secret as an environment variable and process.env.ZEPHR_API_KEY is populated automatically. No code changes needed:

env:
  ZEPHR_API_KEY: ${{ secrets.ZEPHR_API_KEY }}

Webhook callback

Get notified when a secret is consumed or expires — no polling needed:

const { fullLink } = await createSecret('db-password', {
  expiry: 60,
  hint: 'DB_PASSWORD_PROD',
  callbackUrl: 'https://my-orchestrator.example.com/zephr-events',
  callbackSecret: 'my-hmac-signing-secret',
  apiKey: process.env.ZEPHR_API_KEY,
});

When the secret is retrieved, Zephr POSTs a signed event:

{
  "event":       "secret.consumed",
  "event_id":    "550e8400-e29b-41d4-a716-446655440000",
  "secret_id":   "Ht7kR2mNqP3wXvYz8aB4cD",
  "occurred_at": "2026-03-22T14:32:00.000Z",
  "hint":        "DB_PASSWORD_PROD"
}

Verify the X-Zephr-Signature header (HMAC-SHA256 hex digest of the body, signed with your callbackSecret). See examples/webhook-receiver for runnable Node.js and Python receivers.

Fire-and-forget in v1 — no retries. 5-second timeout. Redirects blocked.

Idempotency

The SDK auto-generates an Idempotency-Key on every create — retries are safe by default. If a request times out and the caller retries, the server returns the cached response without creating a duplicate.

Full SDK reference at zephr.io/docs.

Error handling

import { createSecret, retrieveSecret, ValidationError, EncryptionError, ApiError, NetworkError } from 'zephr';

try {
  const { fullLink } = await createSecret('my secret');
} catch (err) {
  if (err instanceof ValidationError) {
    // Invalid input: empty or whitespace-only string, exceeds 2,048 bytes, unsupported expiry
  } else if (err instanceof EncryptionError) {
    // AES-GCM key generation or encryption/decryption failed
  } else if (err instanceof ApiError) {
    console.error(err.statusCode);  // e.g. 429, 403, 401, 410
    console.error(err.code);        // e.g. 'MONTHLY_LIMIT_EXCEEDED'
  } else if (err instanceof NetworkError) {
    // Connection failed or timed out
  }
}

Common ApiError codes:

| Code | Status | Meaning | |------|--------|---------| | INVALID_API_KEY | 401 | Key not found or revoked | | UPGRADE_REQUIRED | 403 | Feature requires a higher tier (e.g. expiry > 60 min without an account, or sub-hour expiry (5, 15, 30 min) without Dev/Pro) | | ANON_RATE_LIMIT_EXCEEDED | 429 | Anonymous daily limit reached (3/day per IP) | | MONTHLY_LIMIT_EXCEEDED | 429 | Monthly create limit reached for this API key | | PAYLOAD_TOO_LARGE | 413 | Encrypted blob exceeds the tier blob ceiling | | SECRET_NOT_FOUND | 404 | Secret ID does not exist or has expired | | SECRET_ALREADY_CONSUMED | 410 | Secret was already retrieved | | SECRET_EXPIRED | 410 | Secret has passed its expiry time |

Security

  • Encrypts on your device before any network call
  • AES-GCM-256 with authenticated encryption and built-in tamper detection
  • Keys never reach the server. They travel in the URL fragment (RFC 3986 §3.5), which browsers strip before sending requests.
  • No plaintext logging. No analytics in the CLI or SDK.

Requirements

Node.js 22.0.0 or higher

License

MIT