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

xjdp

v0.0.1

Published

Secure DevTools protocol for your remote JS servers.

Readme

xjdp

Secure DevTools protocol for your remote JS servers.

xjdp gives you eval, exec, and filesystem access to any running JavaScript server through a lightweight debugging protocol. Think of it as attaching a DevTools session to a production Node.js process, except it works over HTTP with ECDSA public-key authentication, scoped permissions, and path-jailed filesystem access — instead of an open WebSocket on localhost.

Built for AI agents. Connect an agent to a remote server and let it inspect state, run commands, read logs, and edit files — turning any deployment into an interactive sandbox. Treat long-running servers like short-lived containers: attach, debug, fix, detach.

  • Zero production dependencies — Web Crypto API only
  • Isomorphic client (browser + Node.js + AI agents)
  • SSE streaming with HTTP polling fallback
  • Path-jailed filesystem, env-filtered exec, scoped permissions

Quick Start

1. Set up a server

The server exports a standard { fetch } handler that works with any runtime (Node.js via srvx, Bun, Deno, etc).

import { serve } from "srvx";
import { createServer, generateKeyPair, fingerprint } from "xjdp";

const serverKeyPair = await generateKeyPair();

// Print server fingerprint — clients use this to verify they're connecting to the right server
console.log("Server fingerprint:", await fingerprint(serverKeyPair.publicKey));

const server = createServer({
  serverKeyPair,
  acl: {
    // Grant any client read-only access
    "*": ["fs:read"],
  },
});

serve({ fetch: server.fetch, port: 3000 });

2. Generate a client key

npx xjdp keygen

This outputs everything you need:

Generated ECDSA P-384 key pair

Fingerprint:
9b4270452ef7f75efc0...

Private key (pass via -k flag):
eyJrdHkiOiJFQyIs...

ACL entry (paste into server config):
"9b4270452ef7...": ["eval","exec","fs:read","fs:write"]

Connect:
npx xjdp -u http://localhost:3000 -k eyJrdHkiOiJFQyIs...

Copy the ACL entry into your server's acl config:

acl: {
  "*": ["fs:read"],
  "9b4270452ef7...": ["eval", "exec", "fs:read", "fs:write"],
},

3. Connect

npx xjdp -u http://localhost:3000 -k eyJrdHkiOiJFQyIs...

Programmatic API

import { RJDPClient, generateKeyPair, parseKey } from "xjdp";

// Fresh key pair (ephemeral — works with "*" wildcard ACL)
const { privateKey, publicKey } = await generateKeyPair();

// Or import a pre-shared key
// const { privateKey, publicKey } = await parseKey("eyJrdHkiOiJFQyIs...");

const client = await RJDPClient.connect("http://localhost:3000", {
  privateKey,
  publicKey,
  serverFingerprint: "a3f9", // optional — prefix match
});

// Eval
const { result } = await client.eval("process.version");

// Eval with context
const { result: sum } = await client.eval("a + b", { context: { a: 1, b: 2 } });

// Exec with streaming output
const proc = client.exec("ls", ["-la"]);
for await (const chunk of proc.stdout) {
  process.stdout.write(chunk);
}
const exit = await proc.wait();

// Filesystem
const content = await client.fs.read("/src/index.ts");
await client.fs.write("/tmp/out.txt", "hello");
const entries = await client.fs.list("/src");
await client.fs.mkdir("/src/new-dir");
await client.fs.rename("/old.txt", "/new.txt");
await client.fs.delete("/tmp/out.txt");

client.close();

Key Utilities

| Function | Description | | ---------------------------------------- | --------------------------------------------------------------- | | generateKeyPair() | Generate ECDSA P-384 key pair (non-extractable by default) | | generateKeyPair({ extractable: true }) | Generate extractable key pair (for serialization) | | serializeKey(key) | Serialize a CryptoKey to a compact base64 string | | parseKey(str) | Parse back to CryptoKeyPair (private) or CryptoKey (public) | | fingerprint(publicKey) | SHA-256 hex fingerprint of a public key |

CLI Commands

| Command | Description | | --------------------- | --------------------------------------------- | | <js code> | Evaluate JavaScript (or fall through to exec) | | eval <code> | Explicitly evaluate JavaScript | | repl | Enter JS REPL mode (all input is eval'd) | | exec <file> [args] | Execute a file with streaming output | | cd [path] | Change remote working directory | | ls [path] | List directory contents | | cat <path> | Read file contents | | write <path> <text> | Write text to a file | | stat <path> | File/directory info | | mkdir <path> | Create directory | | rm <path> | Delete file or directory | | mv <from> <to> | Rename/move | | help | Show all commands | | exit | Quit |

Unrecognized commands are passed to exec automatically, so you can type node -v or git status directly.

CLI Flags

| Flag | Env Var | Description | | ------------------- | ------------------ | --------------------------------------------- | | -u, --url | XJDP_URL | Server URL (default: http://localhost:3000) | | -f, --fingerprint | XJDP_FINGERPRINT | Expected server fingerprint (prefix match) | | -k, --key | XJDP_KEY | Pre-shared private key (base64 JWK) |

Subcommands

| Command | Description | | -------- | ------------------------------------------------------------- | | keygen | Generate a key pair and print fingerprint, key, and ACL entry |

Examples

# Pin server fingerprint (prefix match — even 4 chars works)
npx xjdp -u http://localhost:3000 -f a3f9

# Combine fingerprint pinning + pre-shared key
npx xjdp -u http://localhost:3000 -f a3f9 -k eyJrdHkiOiJFQyIs...

# Via environment variables
XJDP_URL=http://localhost:3000 XJDP_FINGERPRINT=a3f9 XJDP_KEY=eyJrdHkiOiJFQyIs... npx xjdp

Server Configuration

createServer({
  serverKeyPair: CryptoKeyPair, // Required — server ECDSA P-384 key pair
  acl: Record<string, Scope[]>, // Required — fingerprint → scopes mapping
  fsRoot: "/workspace", // Path jail root (default: /workspace)
  transports: ["sse", "http"], // Enabled transports (default: both)
  capabilities: ["eval", "exec", "fs"], // Enabled capabilities (default: all)
  maxConcurrentExec: 3, // Per-session exec limit
  sessionTtl: 3600000, // Session TTL in ms (default: 1h)
  evalTimeout: 5000, // Eval timeout in ms (default: 5s)
  maxReadSize: 10485760, // Max file read size (default: 10MB)
  envDenylist: [/AWS_.*/, /.*TOKEN.*/], // Exec env filter patterns
});

Scopes

| Scope | Description | | ---------- | -------------------------------------- | | eval | Execute JavaScript via AsyncFunction | | exec | Spawn child processes | | fs:read | Read files, list directories, stat | | fs:write | Write files, mkdir, rename, delete |

Endpoints

| Endpoint | Method | Auth | Description | | ----------------- | ------ | ---- | --------------------------------------------- | | /.jdp/info | GET | No | Capabilities, transports & server fingerprint | | /.jdp/challenge | GET | No | Request auth nonce | | /.jdp/auth | POST | No | Authenticate with signed nonce | | /.jdp/stream | GET | Yes | Open SSE stream | | /.jdp/send | POST | Yes | Send frame via SSE transport | | /.jdp/invoke | POST | Yes | Send frame via HTTP transport | | /.jdp/poll | GET | Yes | Poll exec output (HTTP fallback) |

Protocol

Client                                          Server
  │                                                │
  │─── GET /.jdp/info ──────────────────────────►  │
  │◄── { transports, capabilities, fingerprint }   │
  │                                                │
  │  ┌─────────────────────────────┐               │
  │  │ verify server fingerprint   │  (optional)   │
  │  └─────────────────────────────┘               │
  │                                                │
  │─── GET /.jdp/challenge ─────────────────────►  │
  │◄── { nonce, serverPubKey, ttl } ─────────────  │
  │                                                │
  │  ┌─────────────────────────────┐               │
  │  │ sign(nonce, clientPrivKey)  │               │
  │  └─────────────────────────────┘               │
  │                                                │
  │─── POST /.jdp/auth { sig, pubKey, nonce } ──►  │
  │◄── { sessionId, scopes, expiresAt } ─────────  │
  │                                                │
  │  ┌─────────────────────────────┐               │
  │  │ pick best transport         │               │
  │  └─────────────────────────────┘               │
  │                                                │
  ╔════════════════════════════════════════════════╗
  ║  SSE Transport                                 ║
  ║                                                ║
  ║  │─── GET /.jdp/stream ──────────────────►  │  ║
  ║  │◄── text/event-stream ─────────────────   │  ║
  ║  │                                          │  ║
  ║  │─── POST /.jdp/send { frame } ────────►  │  ║
  ║  │◄── SSE: event: eval.res ──────────────   │  ║
  ║  │◄── SSE: event: exec.stdout ───────────   │  ║
  ║  │◄── SSE: event: exec.exit ─────────────   │  ║
  ╚════════════════════════════════════════════════╝
  ╔════════════════════════════════════════════════╗
  ║  HTTP Fallback Transport                       ║
  ║                                                ║
  ║  │─── POST /.jdp/invoke { frame } ──────►  │  ║
  ║  │◄── { response frame } ────────────────   │  ║
  ║  │                                          │  ║
  ║  │─── GET /.jdp/poll?id=…&cursor=… ─────►  │  ║
  ║  │◄── { chunks, next, done } ────────────   │  ║
  ╚════════════════════════════════════════════════╝

Frames: { id, type, ts, payload }

  eval.req ──► eval.res
  exec.req ──► exec.stdout* ──► exec.stderr* ──► exec.exit
  exec.kill
  fs.req   ──► fs.res
  ping     ──► pong

Authentication

All auth uses ECDSA P-384 via the Web Crypto API. No OpenSSL or third-party crypto required.

  1. Client fetches /.jdp/info — optionally verifies server fingerprint (prefix match)
  2. Client requests a challenge nonce from the server
  3. Client signs the nonce with its private key
  4. Server verifies the signature against its ACL (keyed by public key fingerprint)
  5. Server issues a session token with scoped permissions

Nonces are single-use with a 30-second TTL, stored in an LRU cache for replay prevention.

Security

  • Path jail — All filesystem operations are confined to a root directory. Symlink escapes are caught via realpathSync re-check.
  • Env denylist — Exec filters out environment variables matching AWS_*, *TOKEN*, *SECRET*, *PASSWORD*, *PRIVATE*.
  • Scoped permissions — Each client key is mapped to specific capabilities (eval, exec, fs:read, fs:write).
  • Nonce replay prevention — LRU cache of used nonces with TTL expiry.
  • Session expiry — Configurable TTL (default 1 hour).
  • Frame size limits — Configurable max frame and file read sizes.
  • Server fingerprint pinning — Clients can verify the server's identity via fingerprint prefix matching.

Development

pnpm dev           # playground server + REPL
pnpm test          # lint + typecheck
pnpm fmt           # auto-fix lint + format

Sponsors

License

MIT