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

@sharptrick/parley-core

v0.8.0

Published

Parley core: the transport-agnostic seam, normalized Message, cursor/dedup engine, and dual-role MCP channel server. Zero backend dependencies.

Readme

@sharptrick/parley-core

The transport-agnostic seam: normalized Message, cursor/dedup engine, config schema, topic allowlist, and the dual-role MCP server (reactive tools + live claude/channel push). Zero backend dependencies — plugins depend on this package, never the reverse (CLAUDE.md prime directive #1).

This package is a library, not something you run directly. Install a backend plugin (@sharptrick/parley-sqlite, @sharptrick/parley-redis, @sharptrick/parley-matrix, @sharptrick/parley-xmpp, @sharptrick/parley-nats) and build a bridge with the exports below — see the root README for the end-to-end quickstart.

The seam

interface BackendPlugin {
  connect(config: BackendConfig): Promise<void>;
  disconnect(): Promise<void>;
  subscribe(topic: Topic, handler: MessageHandler): Promise<void>;                 // live path (push)
  post(topic: Topic, identity: Handle, content: string, opts?: { inReplyTo?: BackendMsgId }): Promise<BackendMsgId>;
  fetchRecent(args: FetchRecentArgs): Promise<FetchRecentResult>;                  // catch-up
  resolveIdentity(handle: Handle): Promise<BackendIdentity>;
}

A conforming backend guarantees two things (checked by @sharptrick/parley-conformance):

  1. a stable, unique backendMsgId per message — the dedup key;
  2. monotonic, in-order, exclusive-since cursor deliveryfetchRecent returns messages pre-sorted ascending and subscribe's handler fires in ascending order per topic.

cursor and backendMsgId are opaque strings — core never parses or compares them. timestamp is informational only; ordering and dedup never use it.

What this package builds

| Piece | Export | Purpose | |---|---|---| | Normalized message | Message, Topic, Handle, BackendMsgId, Cursor, asTopic/asHandle/asBackendMsgId/asCursor | The one type crossing the seam; branded opaque ids. | | Config | ConfigSchema, parseConfig, loadConfig, instanceIdOf | Validates parley.config.yaml. | | Allowlist | Allowlist, TopicNotAllowedError | config.topics is the allowlist — no wildcard default. | | Engine | SeenSet, ReadStateStore, catchUpTopic / catchUpAll | Dedup set, per-instance read-cursor persistence, catch-up orchestration. | | Reactive tools | registerTools | parley_fetch_recent / parley_post / parley_reply MCP tools. | | Live push | emitChannel, channelMeta, startPushLoop | Emits claude/channel <channel> notifications to already-running Code sessions. | | Local bridge | buildBridge, createStdioBridge | Composes plugin + tools + push loop into one stdio MCP server. | | Remote bridge (v0.2) | buildReactiveServer, createRemoteHttpApp, createOAuthRemoteApp | Streamable-HTTP transport + single-tenant OAuth 2.1 + PKCE front door. | | Owner auth | ParleyOAuthProvider, hashOwnerSecret, makeOwnerVerifier, ownerVerifierFromPassphrase | Owner-secret verification for remote/chat mode. | | External-OIDC auth | createRemoteAuthApp, createOidcRemoteApp, OidcTokenVerifier, fetchOidcDiscovery | Delegated resource-server mode (RFC 9728): an external IdP (e.g. Keycloak) hosts the AS; selected via cfg.auth.mode. |

Config (parley.config.yaml)

backend: local-sqlite          # which plugin to load
instance_id: agent-main        # read-state namespace; DISTINCT per concurrent session sharing a handle
identity: { handle: "agent" }
topics: ["ctx-demo"]            # THE allowlist — no wildcard default
catchup: { on_start: true, limit: 100 }
live_push: { enabled: true, mention_filter: false }
permissions: { skip_permissions: false }   # sandbox-only; default OFF, never flip on as convenience
backend_config:                 # opaque to core; passed verbatim to the plugin's connect()
  db_path: "./parley.db"

backend_config is the only backend-specific part of this file — see the plugin's own README for its shape. Two concurrent sessions must never share an instance_id (or default handle) — each owns its own read-state file, and a clash silently clobbers the other's catch-up position.

MCP tools exposed

| Tool | Role | Effect | |---|---|---| | parley_fetch_recent | catch-up (reactive) | { topic, since?, limit? }{ messages, nextCursor }. Marks returned ids seen so the push loop won't re-deliver them. | | parley_post | write (reactive) | { topic, content, in_reply_to? }{ backendMsgId }. The chat side's only write path. | | parley_reply | write (channel) | Same durable write as parley_post, distinct name so Claude surfaces it as a reply to an inbound <channel> event. |

All three go through the topic Allowlist — any topic outside config.topics throws TopicNotAllowedError.

Local (stdio) bridge

import { createStdioBridge, loadConfig } from '@sharptrick/parley-core';
import { SqlitePlugin } from '@sharptrick/parley-sqlite';

const cfg = loadConfig('parley.config.yaml');
const bridge = await createStdioBridge(new SqlitePlugin(), cfg);
// ... on shutdown: await bridge.shutdown();

This is what each plugin's cli.ts wraps (see @sharptrick/parley-sqlite's parley-sqlite bin). Point a .mcp.json server entry at the built CLI and launch with claude --dangerously-load-development-channels --channels server:parley — see the root README and examples/fakechat-loopback for the full channel walkthrough.

Remote / chat (OAuth) mode

import { createOAuthRemoteApp, ownerVerifierFromPassphrase } from '@sharptrick/parley-core';
// plugin.connect(...) once, then:
const app = createOAuthRemoteApp(plugin, cfg, {
  issuerUrl: new URL('https://parley.example.com'),
  verifyOwner: ownerVerifierFromPassphrase(process.env.PARLEY_OWNER_PASSPHRASE!),
});
await app.listen(3000);

Single-tenant: the instance authenticates exactly one owner; backend credentials never leave the server, and Claude only ever holds a consented, audience-bound token. Full deployment guide (HTTPS, the public-exposure constraint, and Anthropic IP-range allowlisting) is in examples/self-host-remote.

Alternatively, delegate authorization to an external OIDC IdP (e.g. Keycloak) and let Parley act as a pure resource server — set auth: { mode: oidc, oidc: { issuer, audience, ... } } in the config and compose with createRemoteAuthApp(plugin, cfg, { publicUrl }) (which dispatches between the two modes; no owner secret is needed in oidc mode). Realm setup, config reference, and security notes: docs/keycloak-integration.md.

Testing

npx vitest run packages/bridge-core

Covers config parsing, the allowlist, mentions, seen-set/read-state, catch-up, the reactive tools, the channel-emit/push-loop mechanics, and the OAuth provider/remote auth flow. Backend-specific behavior is exercised by each plugin against the shared @sharptrick/parley-conformance suite, not here.