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

keychains

v0.0.19

Published

CLI for Keychains.dev — register machines, manage permissions, and make proxied API calls

Readme

keychains

CLI for Keychains.dev — secure credential delegation for AI agents.

Install

npm install -g keychains

Quick Start

# Just run it — no setup needed
keychains curl https://api.lifx.com/v1/lights/all \
  -X GET \
  -H "Authorization: Bearer {{LIFX_PERSONAL_ACCESS_TOKEN}}"

That's it. On first run, Keychains automatically:

  1. Registers your machine (using your hostname)
  2. Creates a default wildcard permission
  3. Returns an authorization URL

Click the URL, sign in, connect your credentials, and re-run the command. After that, every call just works.

You never put real tokens in the command. Use template variables like {{OAUTH2_ACCESS_TOKEN}} or {{LIFX_PERSONAL_ACCESS_TOKEN}} and Keychains swaps them for real credentials at proxy time.

How it works

keychains curl <url>
   │
   ├── Machine not registered? → auto-register using hostname
   ├── No permission? → auto-create wildcard (active immediately)
   ├── Make proxied API call
   │
   └── 403: credentials missing
       └── "Authorize at: https://keychains.dev/add-permissions?machineId=...&scopes=..."
           └── User clicks → signs in → machine linked → credentials connected
               └── Re-run → works!

Output Formats

Every command supports three output formats:

| Flag | Format | Use case | |------|--------|----------| | --human | Colored table (default) | Reading in terminal | | --json | Raw JSON | Piping to jq, scripts, CI | | --env | KEY=VALUE | Appending to .env files |

# Create a permission and append directly to .env
keychains permissions create --name "Dev" --env >> .env

# List permissions as JSON
keychains permissions ls --json

# Get your identity as env vars
keychains whoami --env >> .env

Input Variables (Piped JSON)

Use {{$varname}} patterns in your URL, headers, or body to inject values from piped JSON input. This is different from Keychains credential templates ({{OAUTH2_ACCESS_TOKEN}}) which are resolved server-side.

# Pipe JSON into curl — variables get substituted before the request
echo '{"user": "alice", "email": "[email protected]"}' \
  | keychains curl https://api.com/users/{{$user}} \
    -X POST \
    -d '{"user": "{{$user}}", "email": "{{$email}}"}'

Supported accessor syntax

| Syntax | Resolves to | |--------|-------------| | {{$user}} | params.user | | {{$user.name}} | params.user.name | | {{$items[0].id}} | params.items[0].id | | {{$items[0]['key']}} | params.items[0]["key"] | | {{$items[0]["key"]}} | params.items[0]["key"] | | {{$a.b[2].c['d']}} | params.a.b[2].c["d"] |

How it works

  1. Keychains detects {{$...}} patterns in the URL, headers, and body
  2. If variables are found, it reads JSON from stdin (Unix pipe)
  3. Each {{$path}} is replaced with the resolved value from the piped JSON
  4. The original command (with {{$...}} patterns) is saved to history
  5. When exported, the generated code is a function accepting params
# Complex nested data
echo '{"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}' \
  | keychains curl https://api.com/users/{{$users[0].id}} \
    -X PATCH \
    -d '{"name": "{{$users[0].name}}"}'

# Credential templates (server-side) can mix with input variables (client-side)
echo '{"repo": "my-org/my-repo"}' \
  | keychains curl https://api.github.com/repos/{{$repo}}/issues \
    -X POST \
    -H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}" \
    -d '{"title": "Bug in {{$repo}}"}'

Commands

keychains machine — Machine Identity

Manage your machine's cryptographic identity (Ed25519 SSH keypair).

Most users don't need to register manually — machines are auto-registered on first use. Use these commands for explicit registration or management.

keychains machine register --name "my-laptop"   # Register with a custom name
keychains machine register --name "ci" --server https://custom.keychains.dev
keychains machine status                         # Show machine status & permissions
keychains machine ls                             # List all machines on your account
keychains machine update --name "new-name"       # Rename this machine
keychains machine rotate-keys                    # Rotate SSH keypair
keychains machine rm <machineId>                 # Remove a machine from your account
keychains machine rm <machineId> -f              # Remove without confirmation

keychains permissions — Permission Requests

Create, list, revoke, and suspend permissions. Use -a to see all permissions across your account (like git branch -a).

# Create a wildcard permission (default)
keychains permissions create --name "Dev machine"
keychains permissions create --name "Dev" --env >> .env   # Append to .env

# Create a scoped permission
keychains permissions create --name "GitHub read-only" \
  --scopes "github::repo:read,github::read:org"

# List permissions
keychains permissions ls                    # This machine only
keychains permissions ls -a                 # All permissions on your account
keychains permissions ls --json             # JSON output
keychains permissions ls --status active    # Filter by status

# Revoke / suspend
keychains permissions rm <id>               # Revoke a permission
keychains permissions rm <id> -f            # Skip confirmation
keychains permissions suspend <id>          # Suspend (requires re-auth)

Aliases: keychains perm create, keychains perm ls, etc.


keychains delegates — Delegate Identities

Give remote systems (sandboxed agents, CI runners) limited access without sharing your machine keys.

# Create a wildcard delegate from a permission
keychains delegates add <permissionId> --wildcard --ttl 3600

# Create a scoped delegate
keychains delegates add <permissionId> --scopes "github::repo"

# List delegate-enabled permissions
keychains delegates ls
keychains delegates ls -a                   # All on account
keychains delegates ls <permissionId>       # For a specific permission

# Revoke a delegate
keychains delegates rm <delegateId>
keychains delegates rm <delegateId> -f      # Skip confirmation

# Get delegate credentials as env vars
keychains delegates add <permissionId> --wildcard --env >> .env

Aliases: keychains delegate add, keychains delegate ls, etc.


keychains connections — OAuth & API Key Connections

List and manage your connected services (OAuth providers and API keys). Interactive and non-interactive modes.

# List connections
keychains connections ls                    # Active connections
keychains connections ls --status all       # All connections
keychains connections ls --provider github  # Filter by provider
keychains connections ls --json             # JSON output
keychains connections ls -i                 # Interactive mode

# Remove a connection
keychains connections rm github             # By provider name
keychains connections rm <connectionId>     # By connection ID
keychains connections rm -i                 # Interactive mode (select from list)
keychains connections rm github -f          # Skip confirmation

Aliases: keychains conn ls, keychains conn rm, etc.


keychains curl — Proxied API Calls

Make API calls through the Keychains proxy. Credentials are injected automatically.

# Basic GET request
keychains curl https://api.github.com/user \
  -H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}"

# POST with body
keychains curl https://slack.com/api/chat.postMessage \
  -X POST \
  -H "Authorization: Bearer {{SLACK_BOT_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{"channel": "#general", "text": "Hello from keychains!"}'

# Use a specific permission
keychains curl https://api.stripe.com/v1/customers?limit=5 \
  --permission pr_abc123 \
  -H "Authorization: Bearer {{STRIPE_SECRET_KEY}}"

Permission resolution order:

  1. --permission <id> flag
  2. KEYCHAINS_PERMISSION_ID in .env (cwd)
  3. KEYCHAINS_PERMISSION_ID environment variable
  4. First active wildcard permission on this machine
  5. Auto-create a default wildcard permission

Every curl command is automatically saved to local history.


keychains whoami — Identity Check

keychains whoami                  # Human-readable
keychains whoami --json           # JSON output
keychains whoami --env >> .env    # Append to .env

keychains history — Curl Command History

Browse, search, and manage your past keychains curl commands. History is stored locally in ~/.keychains/history.json (default: 100 entries).

# List recent history
keychains history

# Search history
keychains history "github"
keychains history "stripe" --limit 50

# Interactive browse (select entries to export/re-run)
keychains history -i
keychains history -i "gmail"

# Save a history entry as a template
keychains history save hist_abc123 --name "List GitHub repos"

# Clear history
keychains history clear
keychains history clear -f          # Skip confirmation

keychains templates — Request Templates

Save frequently-used requests as named templates for quick re-use and code export.

# List templates
keychains templates ls
keychains templates ls "github"     # Search templates
keychains templates ls --json       # JSON output

# Show template details
keychains templates show "my-template"
keychains templates show tpl_abc123

# Delete a template
keychains templates rm tpl_abc123
keychains templates rm "my-template" -f   # By name, skip confirmation

Aliases: keychains tpl ls, keychains tpl show, etc.


keychains export — Code Generation

Export any history entry or template as runnable code in your language of choice.

When the command contains input variables ({{$...}}), the generated code is a function that accepts a params dict. Use --name to set the function name.

# Export a standalone script (no input variables)
keychains export hist_abc123 --nodejs > index.js
keychains export hist_abc123 --ts > index.ts
keychains export hist_abc123 --python > script.py
keychains export hist_abc123 --go > main.go
keychains export hist_abc123 --curl > request.sh

# Export a function (when input variables are present)
keychains export hist_abc123 --ts --name fetchUser > fetchUser.ts
keychains export tpl_abc123 --python --name fetch_user > fetch_user.py
keychains export hist_abc123 --go --name FetchUser > fetch_user.go

# Export from template (by ID or name)
keychains export tpl_abc123 --nodejs > index.js
keychains export "my-template" --python > script.py

# Use --lang flag
keychains export hist_abc123 --lang typescript > index.ts

Generated function example (TypeScript):

// keychains export hist_xxx --ts --name fetchUser > fetchUser.ts

interface InputParams {
  user: any;
  email: any;
}

export async function fetchUser(params: InputParams): Promise<{ status: number; data: any }> {
  const url = `https://api.com/users/${params.user}`;
  const headers: Record<string, string> = {};
  headers["Content-Type"] = "application/json";
  const body = `{"user": "${params.user}", "email": "${params.email}"}`;

  const response = await fetch(url, { method: "POST", headers, body });
  const text = await response.text();
  let data: any;
  try { data = JSON.parse(text); } catch { data = text; }
  return { status: response.status, data };
}

Supported languages:

| Flag | Language | File | |------|----------|------| | --nodejs / --node / --js / --javascript | Node.js (CommonJS) | .js | | --typescript / --ts | TypeScript (fetch) | .ts | | --python / --py | Python (requests) | .py | | --go / --golang | Go (net/http) | .go | | --curl | curl / bash function | .sh |


Credential Templates

You don't pass real secrets. Use {{VARIABLE_NAME}} placeholders in your headers. Keychains resolves them at the proxy level — the actual token never touches your machine.

OAuth Tokens

| Template | Resolves to | |----------|-------------| | {{OAUTH2_ACCESS_TOKEN}} | OAuth 2.0 access token (auto-refreshed) | | {{OAUTH2_REFRESH_TOKEN}} | OAuth 2.0 refresh token | | {{OAUTH1_TOKEN}} | OAuth 1.0 token | | {{OAUTH1_TOKEN_SECRET}} | OAuth 1.0 token secret |

API Keys

Named API keys stored in the Keychains dashboard:

| Template | Example service | |----------|-----------------| | {{STRIPE_SECRET_KEY}} | Stripe | | {{OPENAI_API_KEY}} | OpenAI | | {{SENDGRID_API_KEY}} | SendGrid | | {{LIFX_PERSONAL_ACCESS_TOKEN}} | LIFX |


Multi-Account

When you have multiple accounts for the same provider (e.g. a personal and a work Google account), Keychains lets you target a specific account by label.

keychains curlX-Proxy-Account header

Pass the X-Proxy-Account header to route the request through a specific account:

# Use the "work" Google account
keychains curl https://gmail.googleapis.com/gmail/v1/users/me/messages \
  -H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}" \
  -H "X-Proxy-Account: work"

# Use the "personal" Google account
keychains curl https://gmail.googleapis.com/gmail/v1/users/me/messages \
  -H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}" \
  -H "X-Proxy-Account: personal"

The X-Proxy-Account header is consumed by the proxy and never forwarded to the upstream API.

keychains connections--account option

Filter connections by account label:

# List only "work" connections
keychains connections ls --account work

# Remove a specific account's connection
keychains connections rm google --account personal

When multiple accounts exist for the same provider, connections ls shows the account label alongside the account identifier. If no --account filter is provided, all matching connections are shown and the rm command will prompt you to pick one.


Examples

# Toggle LIFX lights
keychains curl https://api.lifx.com/v1/lights/all/toggle \
  -X POST \
  -H "Authorization: Bearer {{LIFX_PERSONAL_ACCESS_TOKEN}}"

# List Stripe customers
keychains curl https://api.stripe.com/v1/customers?limit=5 \
  -H "Authorization: Bearer {{STRIPE_SECRET_KEY}}"

# Send a Slack message
keychains curl https://slack.com/api/chat.postMessage \
  -X POST \
  -H "Authorization: Bearer {{SLACK_BOT_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{"channel": "#general", "text": "Hello from keychains!"}'

# Create a GitHub issue
keychains curl https://api.github.com/repos/octocat/hello-world/issues \
  -X POST \
  -H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{"title": "Found a bug", "body": "Something is broken"}'

Workflow Example

# 1. Register machine
keychains machine register --name "dev-laptop"

# 2. Create a scoped permission for a project
keychains permissions create --name "My Project" \
  --scopes "github::repo,slack::chat:write" \
  --env >> .env

# 3. Make API calls (permission auto-detected from .env)
keychains curl https://api.github.com/user \
  -H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}"

# 4. Use input variables with piped JSON
echo '{"owner":"octocat","repo":"hello-world"}' \
  | keychains curl https://api.github.com/repos/{{$owner}}/{{$repo}}/issues \
    -X POST \
    -H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}" \
    -d '{"title":"Bug in {{$repo}}"}'

# 5. Export the command as a TypeScript function
keychains export hist_abc123 --ts --name createIssue > createIssue.ts

# 6. Give a CI runner limited access
keychains delegates add pr_abc123 --wildcard --ttl 3600 --env >> ci.env

# 7. Check your connections
keychains connections ls
keychains connections ls --json | jq '.[] | .provider'

Using .env for Projects

Drop a .env in your project root:

KEYCHAINS_PERMISSION_ID=pr_abc123

Every keychains curl in that directory will use it automatically.

Or create a permission directly into .env:

keychains permissions create --name "My Project" --env >> .env

Local Storage

All local data is stored in ~/.keychains/:

| File | Contents | |------|----------| | credentials.json | Machine ID, server URL, key paths, proxy URL | | history.json | Curl command history (default limit: 100) | | templates.json | Saved request templates | | config.json | CLI configuration (history limit, etc.) | | id_ed25519 | Ed25519 private key | | id_ed25519.pub | Ed25519 public key |


Satellite Proxy

Deploy your own credential proxy so API secrets never leave your infrastructure. The proxy is open source (MIT) and deploys to Vercel in seconds.

# Deploy the proxy
git clone https://github.com/interagentic/keychains.dev_proxy && cd keychains.dev_proxy
npx vercel --prod
# Connect to your account (runs a full end-to-end test first)
keychains proxy set https://your-proxy-abc.vercel.app

The proxy set command runs a 9-field end-to-end test before saving — it sends {{...}} placeholders through your proxy in headers, query params, and body, then verifies every value was resolved and forwarded correctly. If the test fails, the proxy URL is not saved.

  Checking proxy server health and version...
  200 keychains-proxy v0.1.0

  Running end-to-end proxy test...

  1. Initializing test on keychains.dev... OK (test a1b2c3d4...)
  2. Sending proxied request through your-proxy-abc.vercel.app... OK
  3. Verifying values on keychains.dev... OK

    ✓ header_oauth   a1b2c3d4...    ✓ query_oauth    m3n4o5p6...
    ✓ header_apikey  e5f6g7h8...    ✓ query_apikey   q7r8s9t0...
    ✓ header_custom  i9j0k1l2...    ✓ query_custom   u1v2w3x4...
    ✓ body_oauth     y5z6a7b8...    ✓ body_apikey    c9d0e1f2...
    ✓ body_custom    g3h4i5j6...

  ✓ All 9 test values passed through the proxy correctly.
  ✓ User account <[email protected]> now uses remote proxy for all API calls.

After that, your existing code works unchanged — all SDKs and CLI calls automatically route through your proxy.

# Other proxy commands
keychains proxy get                # Show current proxy URL
keychains proxy test               # Re-run the test on configured proxy
keychains proxy test <url>         # Test a proxy without saving it
keychains proxy clear              # Remove proxy (use keychains.dev direct)
keychains proxy set <url> --skip-test  # Skip the e2e test

You can also manage the proxy URL from the keychains.dev dashboard.

How it works: Your proxy sends only the target URL, HTTP method, and {{...}} placeholder names to keychains.dev. Credentials are resolved server-side and returned — keychains.dev never sees your API traffic. Refresh tokens are never returned to the proxy. Read more.


All Commands Reference

| Command | Description | |---------|-------------| | Machine | | | machine register --name <n> | Register this machine | | machine status | Show machine status | | machine ls | List machines on account | | machine update --name <n> | Update machine name | | machine rotate-keys | Rotate SSH keypair | | machine rm <id> | Remove a machine | | Permissions | | | permissions create --name <n> | Create permission (wildcard default) | | permissions ls | List machine permissions | | permissions ls -a | List all account permissions | | permissions rm <id> | Revoke a permission | | permissions suspend <id> | Suspend a permission | | Delegates | | | delegates add <permId> | Create a delegate identity | | delegates ls | List delegate-enabled permissions | | delegates rm <id> | Revoke a delegate | | Connections | | | connections ls | List connections | | connections ls --account <label> | Filter by account label | | connections rm <provider> | Remove a connection | | connections rm <provider> --account <label> | Remove by provider + account | | Curl & History | | | curl <url> | Proxied API call | | history [query] | Browse curl history | | history save <id> --name <n> | Save history as template | | history clear | Clear history | | Templates | | | templates ls [query] | List templates | | templates show <id> | Show template details | | templates rm <id> | Delete a template | | Export | | | export <id> --nodejs | Export as Node.js | | export <id> --ts | Export as TypeScript | | export <id> --python | Export as Python | | export <id> --go | Export as Go | | export <id> --curl | Export as curl | | Proxy | | | proxy set <url> | Set satellite proxy (runs e2e test) | | proxy get | Show current proxy URL | | proxy test [url] | End-to-end proxy test | | proxy clear | Remove satellite proxy | | Identity | | | whoami | Show current identity |


Build from Source

npm run build
node dist/index.js --help