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

learn-secrets-sdk

v1.11.18

Published

Secrets management SDK for static sites with Learn platform

Readme

learn-secrets-sdk

secure api proxy for static sites. call external apis (openai, anthropic, stripe, etc.) without exposing api keys.

zero-config: no tokens or credentials in your code. authentication via origin header.

quick start (cli)

1. install cli

npm install -g learn-secrets-sdk

2. authenticate

learn-secrets login

3. initialize project

learn-secrets init --project YOUR_PROJECT_ID --origins yourdomain.com,localhost --env .env

auto-detects api keys from .env and uploads them. configures origins for your site.

get project id: visit ctklearn.carsontkempf.workers.dev → copy from sidebar

4. install sdk in project

npm install learn-secrets-sdk

5. use in code

import { SecretsSDK } from 'learn-secrets-sdk';

// zero-config - no credentials needed
const sdk = new SecretsSDK();

// call openai securely
const response = await sdk.post('openai', '/v1/chat/completions', {
  model: 'gpt-4',
  messages: [{ role: 'user', content: 'hello' }]
});

how it works:

  • browser sends Origin header automatically
  • proxy validates origin matches configured domains
  • api key injected server-side
  • no tokens or credentials in client code

features

  • zero-config - no tokens, appid, or credentials in code
  • origin-based auth - automatic browser security via origin header
  • api keys secured - stored server-side, never exposed to clients
  • auto-detection - cli detects openai, anthropic, stripe, github, google
  • works anywhere - cloudflare pages, netlify, vercel, any static host
  • rate limiting - 100 req/min per domain built-in
  • typescript - full type definitions included

cli commands

login

learn-secrets login

authenticate via browser oauth. credentials saved to ~/.learn/credentials.json.

init

learn-secrets init \
  --project abc123xyz \           # your project id
  --origins example.com,localhost \ # allowed domains
  --env .env                      # path to .env file

reads .env, detects api keys, uploads to project, configures origins.

auto-detects:

  • OPENAI_API_KEY → openai provider
  • ANTHROPIC_API_KEY → anthropic provider
  • STRIPE_SECRET_KEY → stripe provider
  • GITHUB_TOKEN → github provider
  • *_API_KEY → custom provider

full cli docs: cli reference

api methods

const sdk = new SecretsSDK();

// http methods
await sdk.get(keyName, endpoint, headers?)
await sdk.post(keyName, endpoint, body, headers?)
await sdk.put(keyName, endpoint, body, headers?)
await sdk.delete(keyName, endpoint, headers?)
await sdk.patch(keyName, endpoint, body, headers?)

// custom request
await sdk.call(keyName, endpoint, {
  method: 'POST',
  body: { },
  headers: { }
})

constructor options

zero-config (recommended):

const sdk = new SecretsSDK();

uses origin header for authentication. no credentials needed.

advanced options:

const sdk = new SecretsSDK({
  baseUrl?: string,     // custom proxy url (default: production)
  timeout?: number,     // request timeout ms (default: 30000)
  retryOn429?: boolean  // auto-retry on rate limit (default: true)
});

error handling

import { SecretsSDK, SecretsSDKError, OriginMismatchError, RateLimitError } from 'learn-secrets-sdk';

const sdk = new SecretsSDK();

try {
  const response = await sdk.post('openai', '/v1/chat/completions', data);
} catch (error) {
  if (error instanceof OriginMismatchError) {
    // domain not configured in allowed origins
    console.error('add domain to origins in dashboard');
  } else if (error instanceof RateLimitError) {
    // exceeded 100 req/min
    console.log('retry after:', error.retryAfter, 'seconds');
  } else if (error instanceof SecretsSDKError) {
    // other api errors
    console.log('status:', error.status);
    console.log('message:', error.message);
  }
}

usage tracking

const sdk = new SecretsSDK();

await sdk.post('openai', '/v1/chat/completions', data);

// check rate limit status
const usage = sdk.getUsage();
console.log('remaining:', usage.remaining);  // requests left this minute
console.log('limit:', usage.limit);          // 100
console.log('resets at:', usage.reset);      // unix timestamp

examples

vanilla javascript

<!DOCTYPE html>
<html>
<body>
  <button onclick="callAPI()">call ai</button>
  <div id="response"></div>

  <script type="module">
    import { SecretsSDK } from './node_modules/learn-secrets-sdk/dist/index.js';

    // zero-config
    const sdk = new SecretsSDK();

    window.callAPI = async function() {
      const result = await sdk.post('openai', '/v1/chat/completions', {
        model: 'gpt-4',
        messages: [{ role: 'user', content: 'hello' }]
      });

      document.getElementById('response').textContent =
        result.choices[0].message.content;
    };
  </script>
</body>
</html>

react

import { useMemo } from 'react';
import { SecretsSDK } from 'learn-secrets-sdk';

function App() {
  // zero-config sdk
  const sdk = useMemo(() => new SecretsSDK(), []);

  const handleClick = async () => {
    const result = await sdk.post('openai', '/v1/chat/completions', {
      model: 'gpt-4',
      messages: [{ role: 'user', content: 'hello' }]
    });
    console.log(result.choices[0].message.content);
  };

  return <button onClick={handleClick}>call ai</button>;
}

vue

import { ref } from 'vue';
import { SecretsSDK } from 'learn-secrets-sdk';

// zero-config sdk
const sdk = new SecretsSDK();
const response = ref('');

async function callAPI() {
  const result = await sdk.post('openai', '/v1/chat/completions', {
    model: 'gpt-4',
    messages: [{ role: 'user', content: 'hello' }]
  });
  response.value = result.choices[0].message.content;
}

next.js

'use client';

import { useCallback, useMemo } from 'react';
import { SecretsSDK } from 'learn-secrets-sdk';

export default function ChatPage() {
  // zero-config sdk
  const sdk = useMemo(() => new SecretsSDK(), []);

  const chat = useCallback(async (message: string) => {
    const result = await sdk.post('openai', '/v1/chat/completions', {
      model: 'gpt-4',
      messages: [{ role: 'user', content: message }]
    });
    return result.choices[0].message.content;
  }, [sdk]);

  // ... rest of component
}

supported providers

auto-configured base urls when using cli:

  • openai (OPENAI_API_KEY)
  • anthropic (ANTHROPIC_API_KEY)
  • stripe (STRIPE_SECRET_KEY)
  • github (GITHUB_TOKEN)
  • google (GOOGLE_API_KEY)

custom providers supported - configure base url in dashboard.

how it works

1. cli uploads api keys to proxy server
2. cli configures allowed origins (yourdomain.com, localhost, etc.)
3. your static site imports sdk: new SecretsSDK()
4. browser automatically sends Origin header
5. proxy validates origin matches configured list
6. proxy injects api key server-side
7. external api called with key
8. response returned to browser

security:

  • api keys never sent to browser
  • origin header enforced by browser (cannot be spoofed)
  • stolen code useless from unauthorized domains

security

security model

the sdk uses origin-based authentication specifically designed for static sites. here's how it protects your api keys:

zero credentials in code:

  • no api keys, tokens, or appid in your javascript
  • anyone can view your source code - nothing sensitive to steal
  • works because authentication happens via browser origin header

browser origin header:

  • automatically sent by browser on every request
  • contains the domain making the request (e.g., https://yourdomain.com)
  • cannot be spoofed - browsers prevent javascript from modifying it
  • enforced by browser security model (same-origin policy)

server-side validation:

1. browser sends request with Origin: https://yourdomain.com
2. proxy checks if yourdomain.com is in allowed origins list
3. if match: proxy injects api key server-side and calls external api
4. if no match: request rejected with 403 error
5. api keys never sent to browser

what if someone steals your code?

  • they deploy it on their domain (e.g., https://attacker.com)
  • browser sends Origin: https://attacker.com
  • proxy rejects - their domain not in your origins list
  • api calls fail - they can't use your api keys

comparison to alternatives:

traditional approach (insecure):

// api key exposed in code
const apiKey = 'sk-proj-abc123...';
fetch('https://api.openai.com/v1/chat/completions', {
  headers: { 'Authorization': `Bearer ${apiKey}` }
});

problem: anyone viewing source code gets your api key

environment variables (doesn't work for static sites):

// process.env doesn't exist in browser
const apiKey = process.env.OPENAI_API_KEY; // undefined

problem: static sites have no server to hold env vars

backend proxy (requires server):

// requires deploying and maintaining your own backend
fetch('https://yourbackend.com/api/openai', { ... });

problem: need to deploy and maintain server infrastructure

learn-secrets approach (secure for static sites):

// zero credentials - authentication via origin header
const sdk = new SecretsSDK();
await sdk.post('openai', '/v1/chat/completions', { ... });

solution: browser origin header provides authentication

security guarantees

  • api keys never exposed - stored server-side, injected in proxy layer
  • origin header enforced - browsers prevent spoofing, validated server-side
  • no credentials to leak - zero-config means no tokens in your code
  • rate limiting - 100 req/min per domain prevents abuse
  • instant revocation - remove origins in dashboard to block access immediately
  • encrypted storage - api keys encrypted at rest in database
  • https only - sdk enforces https in production

best practices

allowed origins:

  • only add domains you control
  • use specific domains instead of wildcards
  • localhost allowed for development only
  • remove test domains before going live

deployment:

  • deploy to production domain first
  • then add that domain to allowed origins
  • test that api calls work from production
  • never commit credentials to version control

monitoring:

  • check usage dashboard regularly
  • watch for unexpected rate limit hits
  • review origins list periodically
  • remove unused origins

incident response:

  • if domain compromised: remove from origins list immediately
  • if api key leaked: regenerate in provider dashboard
  • if suspecting abuse: check usage logs in dashboard

documentation

troubleshooting

403 origin not allowed:

  • run learn-secrets init with correct --origins
  • or update origins in dashboard: settings → sdk tokens

404 api key not found:

  • verify key name matches .env variable (e.g., OPENAI_API_KEYopenai)
  • re-run learn-secrets init to upload keys

429 rate limit:

  • 100 requests per minute per domain
  • implement throttling or caching in your app

license

mit