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

tether-name

v2.0.4

Published

Official Node.js SDK for tether.name - AI agent identity verification

Readme

tether-name

Official Node.js SDK for tether.name — cryptographic identity verification for AI agents. Tether lets AI agents prove their identity using RSA-2048 signatures, enabling trusted agent-to-agent communication.

Installation

npm install tether-name

Requires Node.js 20+ (uses native fetch and crypto modules).

Quick Start

import { TetherClient } from 'tether-name';

const client = new TetherClient({
  agentId: 'your-agent-id',
  privateKeyPath: '/path/to/your/private-key.pem'
});

// One-call verification
const result = await client.verify();
console.log(result.verified);    // true
console.log(result.agentName);   // "Jawnnybot"
console.log(result.verifyUrl);   // "https://tether.name/check?challenge=..."

Step-by-Step Usage

For more control over the verification process:

import { TetherClient } from 'tether-name';

const client = new TetherClient({
  agentId: 'your-agent-id',
  privateKeyPem: `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----`
});

try {
  // 1. Request a challenge from Tether
  const challenge = await client.requestChallenge();
  
  // 2. Sign the challenge with your private key
  const proof = client.sign(challenge);
  
  // 3. Submit the proof for verification
  const result = await client.submitProof(challenge, proof);
  
  if (result.verified) {
    console.log(`✅ Verified as ${result.agentName}`);
    console.log(`📝 Public verification: ${result.verifyUrl}`);
  } else {
    console.log(`❌ Verification failed: ${result.error}`);
  }
} catch (error) {
  console.error('Verification error:', error.message);
}

Configuration Options

Constructor Options

interface TetherClientConfig {
  // Management bearer token (API key or JWT)
  apiKey?: string;                 // Or use TETHER_API_KEY env var

  // Agent ID (required for verify/sign, optional with apiKey)
  agentId?: string;           // Or use TETHER_AGENT_ID env var

  // Private key (required for verify/sign, choose one)
  privateKeyPath?: string;         // Path to DER or PEM file
  privateKeyPem?: string;          // PEM string directly
  privateKeyBuffer?: Buffer;       // DER buffer directly

  // Optional
}

Authentication Modes

Bearer token only — manage agents without a private key:

const client = new TetherClient({
  apiKey: 'sk-tether-name-...'
});

const agent = await client.createAgent('my-bot');

Bearer token + agent + private key — full access (management and verification):

const client = new TetherClient({
  apiKey: 'sk-tether-name-...',
  agentId: 'your-agent-id',
  privateKeyPath: '/path/to/key.pem'
});

Agent + private key only — verification without agent management (original behavior):

const client = new TetherClient({
  agentId: 'your-agent-id',
  privateKeyPath: '/path/to/key.pem'
});

Key Format Support

The SDK supports both PEM and DER private key formats:

// From file path (auto-detects format)
const client1 = new TetherClient({
  agentId: 'your-id',
  privateKeyPath: '/path/to/key.pem'    // or .der
});

// From PEM string
const client2 = new TetherClient({
  agentId: 'your-id',
  privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\n...'
});

// From DER buffer
const derBuffer = fs.readFileSync('/path/to/key.der');
const client3 = new TetherClient({
  agentId: 'your-id',
  privateKeyBuffer: derBuffer
});

Agent Management

Create and manage agents programmatically with a bearer token:

const client = new TetherClient({ apiKey: 'sk-tether-name-...' });

// Create an agent
const agent = await client.createAgent('my-bot', 'Does helpful things', 'verified-domain-id');
console.log(agent.id);              // "abc123"
console.log(agent.agentName);       // "my-bot"
console.log(agent.registrationToken); // Use to register the agent

// List all agents
const agents = await client.listAgents();

// List registered domains
const domains = await client.listDomains();

// Update which identity shows on verification
// Pass a verified domain ID to show that domain:
await client.updateAgentDomain(agent.id, 'verified-domain-id');
// Or pass empty string to show account email:
await client.updateAgentDomain(agent.id, '');

// Delete an agent
await client.deleteAgent(agent.id);

// List key lifecycle entries for an agent
const keys = await client.listAgentKeys(agent.id);

// Rotate key (requires step-up via email code OR challenge+proof)
const rotated = await client.rotateAgentKey(agent.id, {
  publicKey: 'BASE64_SPKI_PUBLIC_KEY',
  gracePeriodHours: 24,
  reason: 'routine_rotation',
  stepUpCode: '123456',
});

// Revoke a key
const revoked = await client.revokeAgentKey(agent.id, rotated.newKeyId, {
  reason: 'compromised',
  stepUpCode: '654321',
});

Environment Variables

Set these environment variables to avoid hardcoding secrets:

export TETHER_API_KEY="sk-tether-name-..."                        # Management bearer token (API key or JWT)
export TETHER_AGENT_ID="your-agent-id"
export TETHER_PRIVATE_KEY_PATH="/path/to/your/private-key.pem"

Then initialize without parameters:

const client = new TetherClient({});  // Uses env vars

API Reference

TetherClient

constructor(config: TetherClientConfig)

Creates a new Tether client instance.

async verify(): Promise<VerificationResult>

Performs complete verification in one call. Requests challenge, signs it, and submits proof.

Throws: TetherVerificationError if verification fails.

async requestChallenge(): Promise<string>

Requests a new challenge from the Tether API.

Returns: Challenge string to be signed.

sign(challenge: string): string

Signs a challenge using the configured private key.

Returns: URL-safe base64 signature (no padding).

async submitProof(challenge: string, proof: string): Promise<VerificationResult>

Submits signed proof to verify the challenge.

async createAgent(agentName: string, description?: string, domainId?: string): Promise<Agent>

Creates a new agent. Requires bearer auth (Authorization: Bearer ..., JWT or API key). domainId is optional and links this agent to a verified domain.

async listAgents(): Promise<Agent[]>

Lists all agents for the authenticated account. Requires bearer auth (JWT or API key).

async listDomains(): Promise<Domain[]>

Lists all registered domains for the authenticated account. Requires bearer auth (JWT or API key).

async updateAgentDomain(agentId: string, domainId?: string): Promise<UpdateAgentResponse>

Updates which identity is shown when an agent is verified. Pass a verified domainId to show that domain, or pass "" (empty string) to show the account email. Requires bearer auth (JWT or API key).

async deleteAgent(agentId: string): Promise<boolean>

Deletes an agent by ID. Requires bearer auth (JWT or API key). Returns true on success.

async listAgentKeys(agentId: string): Promise<AgentKey[]>

Lists key lifecycle entries (active, grace, revoked) for an agent. Requires bearer auth (JWT or API key).

async rotateAgentKey(agentId: string, request: RotateAgentKeyRequest): Promise<RotateAgentKeyResponse>

Rotates an agent key. Requires bearer auth (JWT or API key) plus step-up verification via either stepUpCode or challenge + proof.

async revokeAgentKey(agentId: string, keyId: string, request?: RevokeAgentKeyRequest): Promise<RevokeAgentKeyResponse>

Revokes an agent key. Requires bearer auth (JWT or API key) plus step-up verification via either stepUpCode or challenge + proof.

Types

interface VerificationResult {
  verified: boolean;           // Whether verification succeeded
  agentName?: string;          // Registered agent name
  verifyUrl?: string;          // Public verification URL
  email?: string;              // Registered email
  domain?: string;             // Verified domain (if assigned)
  registeredSince?: string;    // ISO date of registration
  error?: string;              // Error message if failed
  challenge?: string;          // The verified challenge
}
interface Agent {
  id: string;                  // Unique agent ID
  agentName: string;           // Agent display name
  description: string;         // Agent description
  domainId?: string;           // Optional assigned domain ID
  domain?: string | null;      // Resolved assigned domain name
  createdAt: number;           // Creation time (epoch ms)
  registrationToken?: string;  // Token for key registration (returned on create)
  lastVerifiedAt?: number;     // Last verification time (epoch ms)
}
interface Domain {
  id: string;
  domain: string;
  verified: boolean;
  verifiedAt: number;
  lastCheckedAt: number;
  createdAt: number;
}
interface AgentKey {
  id: string;
  status: 'active' | 'grace' | 'revoked';
  createdAt: number;
  activatedAt: number;
  graceUntil: number;
  revokedAt: number;
  revokedReason: string;
}

interface RotateAgentKeyRequest {
  publicKey: string;
  gracePeriodHours?: number;
  reason?: string;
  stepUpCode?: string;
  challenge?: string;
  proof?: string;
}

interface RevokeAgentKeyRequest {
  reason?: string;
  stepUpCode?: string;
  challenge?: string;
  proof?: string;
}

Errors

  • TetherError - Base error class
  • TetherVerificationError - Verification failed
  • TetherAPIError - API request failed

Getting Your Agent Identity

  1. Visit tether.name
  2. Register your agent and get an agent ID
  3. Generate an RSA-2048 private key:
# Generate private key (PEM format)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private-key.pem

Requirements

  • Node.js 20+ (uses native fetch)
  • RSA-2048 private key
  • Zero runtime dependencies (uses only Node.js built-ins)

Security Notes

  • Keep your private key secure and never commit it to version control
  • Use environment variables or secure key management
  • The SDK uses SHA256withRSA signatures with URL-safe base64 encoding
  • All verification happens server-side at tether.name

Publishing

Published to npm automatically via GitHub Actions when a release is created.

Version checklist

Update the version in:

  1. package.json"version"

Steps

  1. Update version numbers above
  2. Commit and push to main
  3. Create a GitHub release with a matching tag (e.g. v1.0.0)
  4. CI builds and publishes to npm automatically

Manual publish (if needed)

npm run build
npm publish --access public

License

MIT License - see LICENSE file for details.

Links