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

@mcdmag/licensing-saas-sdk

v0.1.2

Published

SDK for integrating license validation, plugin encryption, and distribution with the licensing-saas platform

Downloads

393

Readme

@mcdmag/licensing-saas-sdk

npm version license

SDK for plugin authors to integrate license validation, encrypt plugin artifacts, and distribute plugins to end users via the licensing-saas platform.

What This SDK Does

This SDK provides two things:

  1. A library — validate license keys in your plugin at runtime (online, offline, or cached)
  2. A CLI — package, encrypt, obfuscate, upload, and generate installers for your plugin

Prerequisites

  • Node.js >= 18
  • npm (or any compatible package manager)

Install

npm install @mcdmag/licensing-saas-sdk

Quick Start (3 commands)

The fastest way to go from plugin code to a distributable installer:

# 1. Register as a tenant to get your API key (one-time)
curl -X POST https://licensing-saas.fly.dev/api/tenants \
  -H "Content-Type: application/json" \
  -d '{"name": "Your Company", "email": "[email protected]"}'
# → { "tenantId": "...", "apiKey": "sk-..." }

# 2. Initialize your plugin (one-time): generates a secret, registers it, saves config
npx licensing-saas-sdk init --name my-plugin --api-key sk-YOUR_API_KEY

# 3. Release (every time you ship): obfuscate + encrypt + upload + generate install.sh
npx licensing-saas-sdk release ./dist

That's it. Step 1 gives you an API key. The init command uses it to register your plugin and saves everything to .licensingrc.json. The release command reads that config and does the rest in one step.

Important: Add .licensingrc.json to your .gitignore — it contains your plugin secret and API key.

What happens under the hood

  1. init generates a cryptographic plugin secret, registers the plugin with the platform, and saves all credentials to .licensingrc.json
  2. release obfuscates .js files, tars + encrypts with AES-256-GCM, uploads the artifact, and generates install.sh

End users install with their license key

LICENSE_KEY=their-license-key bash install.sh

The installer validates the license, downloads the encrypted artifact, decrypts it locally, and extracts the plugin to ~/.claude/plugins/<plugin-name>. If the plugin contains an mcp-server.js entry point, the installer also registers it as a Claude Code MCP server so it's available in Claude Code automatically.


How Licensing Works

sequenceDiagram
    participant A as Plugin Author
    participant S as SaaS Platform
    participant U as End User

    A->>A: 1. Build plugin
    A->>A: 2. package (CLI)<br/>obfuscate + tar + encrypt
    A->>S: 3. upload (CLI) → .enc blob
    A-->>U: 4. Share install.sh

    U->>S: 5. Run install.sh
    S->>S: Validate license + derive key
    S->>U: 6. .enc + decryption key
    U->>U: 7. Decrypt + extract

    U->>S: 8. Plugin validates license at runtime

How Encryption Works

Plugin code is never stored in plaintext on the platform:

  1. Obfuscate — The CLI copies your plugin to a temp directory and obfuscates all .js files (control flow flattening, string encoding, identifier mangling). This is on by default; skip with --no-obfuscate
  2. Package — The obfuscated copy is tarred and encrypted with AES-256-GCM
  3. Key derivation — The encryption key is derived from your pluginSecret using HKDF-SHA256 with a random salt per artifact
  4. Upload — The encrypted .enc blob + encryption metadata (salt, IV, authTag) are uploaded to the platform
  5. Install — When a licensed user runs install.sh, the platform validates their license, derives the decryption key from the stored pluginSecret, and returns it alongside the encrypted download
  6. Decrypt — The installer decrypts locally using AES-256-GCM and extracts the plugin (code is still obfuscated)

HKDF parameters:

  • IKM: pluginSecret (32-byte hex, generated once per plugin)
  • Salt: random 32 bytes (unique per artifact)
  • Info: "plugin-encryption" (fixed context string)
  • Output: 32-byte AES-256 key

How License Validation Works

License keys use the format <base64-payload>.<base64-signature>:

  • Payload — JSON with: licenseId, pluginId, tenantId, email, plan, expiresAt, features
  • Signature — Ed25519 signature over the payload bytes
  • Validation is three-tier with automatic fallback:

| Tier | Method | When | |------|--------|------| | 1 | Online | POST to license server with key + machine fingerprint | | 2 | Offline | Ed25519 signature verification (no network needed) | | 3 | Cached | Read from local cache file (7-day TTL) |

Online is attempted first. If it fails (network down, server error), offline Ed25519 verification runs. If that fails too (e.g., expired key), the SDK checks for a cached validation from a previous successful online check.

Machine Fingerprinting

getMachineFingerprint() creates a SHA-256 hash from:

hostname | username | platform | arch | cpuModel

This is sent during online validation for seat enforcement. No PII is stored or transmitted — only the hash.

Step-by-Step Guide

If you prefer more control, or want to understand each step, here's the full walkthrough.

1. Register as a Tenant

Create a tenant account on the platform to get your API key:

curl -X POST https://licensing-saas.fly.dev/api/tenants \
  -H "Content-Type: application/json" \
  -d '{"name": "Your Company", "email": "[email protected]"}'
# → { "tenantId": "...", "apiKey": "..." }

2. Generate a Plugin Secret

The SDK exports a generatePluginSecret() helper that creates a cryptographically random 32-byte hex string:

import { generatePluginSecret } from "@mcdmag/licensing-saas-sdk"

const secret = generatePluginSecret()
console.log(secret)
// → e.g. "a3f1...c9d2" (64 hex characters)

Save this secret securely — you'll use it for every package command. It cannot be recovered from the platform.

3. Create a Plugin

Register your plugin on the platform using the secret you generated:

curl -X POST https://licensing-saas.fly.dev/api/plugins \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-plugin", "pluginSecret": "YOUR_GENERATED_SECRET_HEX"}'
# → { "pluginId": "...", "publicKeyBase64": "..." }

Save the returned pluginId and publicKeyBase64 — you'll need both.

4. Issue a License Key

License keys are created through the platform API (not this SDK). Create one for a user:

curl -X POST https://licensing-saas.fly.dev/api/licenses \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pluginId": "YOUR_PLUGIN_ID",
    "email": "[email protected]",
    "plan": "pro",
    "features": ["feature-a", "feature-b"]
  }'
# → { "licenseKey": "eyJsa....<base64-payload>.<base64-signature>" }

Distribute this key to your customer.

5. Add License Validation to Your Plugin

Use validateLicense in your plugin code to gate features behind a valid license:

import { validateLicense } from "@mcdmag/licensing-saas-sdk"

try {
  const result = await validateLicense(licenseKey, {
    publicKeyBase64: "YOUR_PUBLIC_KEY_BASE64",
    apiUrl: "https://licensing-saas.fly.dev",
    pluginId: "your-plugin-id",
  })

  if (result.valid) {
    console.log(`Plan: ${result.payload.plan}`)
    console.log(`Source: ${result.source}`) // 'online' | 'offline' | 'cached'
    console.log(`Features: ${result.payload.features.join(", ")}`)
  }
} catch (err) {
  // All three validation tiers failed (online, offline, cached)
  console.error("License validation failed:", err.message)
}

6. Package & Upload

# Package: obfuscate + tar + encrypt (obfuscation is on by default)
npx licensing-saas-sdk package ./my-plugin --plugin-secret YOUR_SECRET_HEX
# → my-plugin.tar.gz.enc + my-plugin.tar.gz.enc.meta.json

# Upload to platform
npx licensing-saas-sdk upload ./my-plugin.tar.gz.enc \
  --api-key YOUR_API_KEY \
  --plugin-id YOUR_PLUGIN_ID

7. Generate Installer for End Users

npx licensing-saas-sdk generate-installer \
  --plugin-id YOUR_PLUGIN_ID \
  --plugin-name my-plugin \
  --public-key YOUR_PUBLIC_KEY_BASE64
# → install.sh

API Reference

validateLicense(key, options): Promise<LicenseResult>

Validate a license key using three-tier fallback: online → offline → cached.

const result = await validateLicense(key, {
  publicKeyBase64: "...",   // Required — Ed25519 public key (DER, base64)
  apiUrl: "...",             // License server URL for online validation
  pluginId: "...",           // Plugin ID (used for caching)
  machineId: "...",          // Override machine fingerprint
  hostname: "...",           // Override hostname
  timeout: 5000,             // Online request timeout in ms (default: 5000)
  cacheDir: "...",           // Cache directory (default: ~/.claude)
})

Returns:

{
  valid: boolean
  payload: LicensePayload
  source: "online" | "offline" | "cached"
}

Throws if all three tiers fail, with a combined error message.

validateOffline(key, publicKeyBase64): LicenseResult

Verify a license key offline using Ed25519 signature verification. No network call.

Throws:

  • "Malformed license key" — no . separator
  • "Invalid license signature" — Ed25519 verification failed
  • "License payload is not valid JSON" — corrupted payload
  • "License has expired"expiresAt is in the past

getMachineFingerprint(): string

Returns a 64-character hex SHA-256 hash of hostname|username|platform|arch|cpuModel.

Types

interface LicensePayload {
  licenseId: string
  pluginId: string
  tenantId: string
  email: string
  plan: string
  purchasedAt?: string    // ISO 8601
  issuedAt?: string       // ISO 8601
  expiresAt: string | null // null = permanent license
  features: string[]
}

interface LicenseResult {
  valid: boolean
  payload: LicensePayload
  source: "online" | "offline" | "cached"
}

interface ValidateOptions {
  publicKeyBase64: string
  apiUrl?: string
  pluginId?: string
  machineId?: string
  hostname?: string
  timeout?: number
  cacheDir?: string
}

CLI Reference

init

One-time setup: generates a plugin secret, registers the plugin with the platform, and saves all credentials to .licensingrc.json.

licensing-saas-sdk init --name <plugin-name> --api-key <key> [--api-url <url>] [--output <path>]

| Flag | Required | Description | | ---- | -------- | ----------- | | --name | Yes | Plugin name | | --api-key | Yes | Tenant API key (from tenant registration) | | --api-url | No | Platform URL (default: https://licensing-saas.fly.dev) | | --output | No | Config file path (default: ./.licensingrc.json) |

release

Package, upload, and generate installer in one command. Reads credentials from .licensingrc.json (created by init).

licensing-saas-sdk release <directory> [--config <path>] [--no-obfuscate] [--skip-installer] [--installer-output <path>]

| Flag | Required | Description | | ---- | -------- | ----------- | | --config | No | Config file path (default: ./.licensingrc.json) | | --no-obfuscate | No | Skip obfuscation (not recommended) | | --skip-installer | No | Skip generating install.sh | | --installer-output | No | Installer output path (default: ./install.sh) | | --obfuscate-seed | No | Deterministic seed for reproducible obfuscation | | --obfuscate-exclude | No | File path patterns to skip (repeatable) |

package

Obfuscate, package, and encrypt a plugin directory into an .enc tarball. Obfuscation is on by default — source code is never readable inside the artifact.

licensing-saas-sdk package <directory> --plugin-secret <hex> [--output <path>] [--no-obfuscate] [--obfuscate-seed <n>] [--obfuscate-exclude <pattern>...]

| Flag | Required | Description | |------|----------|-------------| | --plugin-secret | Yes | 32-byte hex string | | --output | No | Output path for .enc file | | --no-obfuscate | No | Skip obfuscation (not recommended) | | --obfuscate-seed | No | Deterministic seed for reproducible obfuscation | | --obfuscate-exclude | No | File path patterns to skip (repeatable) |

Output: <dir>.tar.gz.enc + <dir>.tar.gz.enc.meta.json (contains salt, sha256, iv, authTag)

upload

Upload an encrypted artifact to the platform.

licensing-saas-sdk upload <file> --api-key <key> --plugin-id <id> [--api-url <url>]

| Flag | Required | Description | |------|----------|-------------| | --api-key | Yes | Tenant API key | | --plugin-id | Yes | Target plugin ID | | --api-url | No | Platform URL (default: https://licensing-saas.fly.dev) |

generate-installer

Generate a bash install.sh script for end users.

licensing-saas-sdk generate-installer \
  --plugin-id <id> \
  --plugin-name <name> \
  [--api-url <url>] \
  [--public-key <base64>] \
  [--output <path>]

| Flag | Required | Description | |------|----------|-------------| | --plugin-id | Yes | Plugin ID | | --plugin-name | Yes | Human-readable plugin name | | --api-url | No | Platform URL (default: https://licensing-saas.fly.dev) | | --public-key | No | Ed25519 public key for offline validation | | --output | No | Output path (default: ./install.sh) |

obfuscate

Obfuscate JavaScript files in a directory using control flow flattening and string encoding. Protects plugin source code from casual reverse-engineering.

When to use this vs package: The package command already runs obfuscation by default before encrypting. Use the standalone obfuscate command only when you want to obfuscate code without packaging it — for example, to inspect the obfuscated output, or to obfuscate before running integration tests against obfuscated code.

licensing-saas-sdk obfuscate <directory> [--output <path>] [--seed <n>] [--exclude <pattern>...]

| Flag | Required | Description | |------|----------|-------------| | --output | No | Output directory (default: overwrites in-place via atomic swap) | | --seed | No | Deterministic seed for reproducible obfuscation (default: 0) | | --exclude | No | File path patterns to skip (repeatable) |

What it does:

  • Recursively finds all .js files in the directory
  • Applies javascript-obfuscator with:
    • Control flow flattening (50% threshold)
    • String array encoding (base64)
    • Hexadecimal identifier names
    • Node.js target
  • Writes obfuscated files to --output or overwrites originals

Example — obfuscate without packaging (e.g., for inspection):

# Build your plugin
npm run build

# Obfuscate the dist/, excluding test files
licensing-saas-sdk obfuscate ./dist --exclude __tests__ --exclude .test.

# Inspect the output, then package + encrypt as usual
licensing-saas-sdk package ./dist --plugin-secret YOUR_SECRET_HEX --no-obfuscate

Note the --no-obfuscate flag on package — since you already obfuscated manually, this avoids double-obfuscating.

Security Model

| Aspect | Detail | |--------|--------| | Encryption | AES-256-GCM (authenticated encryption) | | Key derivation | HKDF-SHA256 from pluginSecret + random salt | | License signing | Ed25519 (small keys, fast verification, deterministic) | | Machine binding | SHA-256 fingerprint (no PII transmitted) | | Cache TTL | 7 days for offline fallback | | Trust model | The SaaS provider stores encrypted secrets and can derive decryption keys. Plugin code is stored encrypted at rest. |

For full details, see docs/security-model.md.

License

MIT