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

@shpbl/sdk

v0.3.1

Published

Typed client for the SHPBL API — the same fourteen tools the MCP server runs, over ordinary HTTP. No model, no magic, no hidden state.

Readme

@shpbl/sdk

A typed client for the SHPBL HTTP API. Same fourteen tools the MCP server runs, same gate, same meter, same ledger — only the envelope differs.

No model runs in this package. The SDK does not call an LLM, and SHPBL's server does not either. The reasoning belongs to your agent; the runtime is deterministic.

The manual is the source of truth. The method, every tool, the lanes and the meter are documented once, at https://shpbl.com/manual. This page documents the envelope; the client reference lives at https://shpbl.com/sdk, and the source is packages/sdk of https://github.com/SweetKenneth/shpbl-master.

Install

npm install @shpbl/sdk

Node 20+, or any runtime with fetch (browsers, Workers, Deno, Bun).

Use

import { ShpblClient, text } from "@shpbl/sdk";

const shpbl = new ShpblClient({ key: process.env.SHPBL_KEY }); // key optional

const welcome = await shpbl.welcome({ name: "Kenneth" });
console.log(text(welcome));

const audit = await shpbl.evaluateRepo({ repo: "owner/repo", brief: true });
console.log(text(audit));

Without a key the free lane runs: the repository evaluation is complete and yours, and the run stops at the composition boundary. A Practitioner key opens run_gauntlet, library search, library_document and writes.

Know before you call

Tiers are pinned in the package, so the SDK answers "can I do this?" with no request and no round trip.

const free = new ShpblClient();               // no key

free.canCall("evaluate_repo");                // true
free.canCall("library_search");               // false
free.tierOf("write_to_repo");                 // "practitioner"
free.available();                             // the ten tools open to you now

The ask

Hitting the key wall is a normal branch in a good integration, not an exception you mop up. So it is an object, not a stack trace.

const ask = free.ask("run_gauntlet");
{
  tool: "run_gauntlet",
  tier: "practitioner",
  hasKey: false,
  reason: "no_key",              // "no_key" | "tier" | "unknown"
  headline: "`run_gauntlet` needs a Practitioner key. You do not have one set.",
  unlocks: "the full conducted run: survey, evaluation, repair, batched harvest and report",
  stillWorks: [ ...what the free lane does right now ],
  url: "https://shpbl.com/mcp-access",
  docsUrl: "https://shpbl.com/manual",
}

Every ShpblAuthError carries one, with the server's own remedy line folded in as serverNextStep. explain() renders it as plain text with no ANSI, equally at home in a log, a GitHub issue or a modal.

try {
  await shpbl.librarySearch({ query: "retry backoff" });
} catch (error) {
  if (error instanceof ShpblAuthError) console.log(error.explain());
}
`library_search` needs a Practitioner key. You do not have one set.

What it opens:  search across the Collective Master Library, the vault and past composites
Take a key:     https://shpbl.com/mcp-access
Manual:         https://shpbl.com/manual
Server says:    Take or check a key at https://shpbl.com/mcp-access, then run the same call again.

Working right now, with no key:
  · evaluate_repo: the complete diagnosis of your repository, at full depth
  · fix_repo: verbatim source and the ordered remediation protocol
  · harvest_repo: what the repository can already do, and how much owned capability is offerable against it
  · list_repos, method_protocol, library_index, build_intent, selfcheck_mcp

Register onAsk once and every paywall in your integration routes to your own banner, prompt or metric. It notifies; it does not swallow the throw.

const shpbl = new ShpblClient({
  key: process.env.SHPBL_KEY,
  onAsk: (ask) => ui.showUpgrade(ask),
});

Drift

A typed client is a promise about a contract, and contracts move. One request tells you whether this pinned release still matches the server.

const report = await shpbl.compatibility();

if (!report.ok) console.warn(report.summary);
// @shpbl/sdk 0.2.0 against server 1.36.0: the server has 1 tool(s) this SDK has
// no method for (compose_repo). 1 tool(s) accept arguments this SDK does not
// name: evaluate_repo (depth). Unknown arguments are forwarded untouched, so
// calls keep working; upgrade the SDK to get types for them.

report gives you newTools, missingTools, tierChanges and per-tool arguments drift. Log it on boot, or assert ok in CI and find out about a server change from your own pipeline rather than a bug report. Nothing here changes behaviour: invoke() forwards unknown arguments regardless.

Tools

| Method | Tool | | --- | --- | | welcome() | welcome — first call of a conversation | | methodProtocol() | method_protocol | | listRepos() | list_repos | | evaluateRepo() | evaluate_repo | | fixRepo() | fix_repo | | harvestRepo() | harvest_repo | | runGauntlet() | run_gauntlet (Practitioner) | | librarySearch() | library_search | | libraryIndex() | library_index | | libraryDocument() | library_document | | buildIntent() | build_intent | | writeToRepo() | write_to_repo | | subscriptionStatus() | subscription_status | | selfcheck() | selfcheck_mcp |

invoke(name, args) calls anything by name, and tools() / descriptor() read the live contract so a new server argument never needs an SDK release.

The gauntlet, without the bookkeeping

run_gauntlet is a chain, and the server holds no session. Every step past the first must carry the ledger_digest your model folded and the signed fold_token the previous step handed back, the run pauses for a person every few harvest steps, and the step after the last batch is the closing step rather than the next number. gauntlet() holds that bookkeeping — the token, the order the server dictates, and the pause — and nothing else.

The ledger is yours. Your model reads each batch and writes one line per capability; the SDK will not invent it, and it refuses to send a step past the first without one instead of letting the server refuse the call for you.

import { ShpblClient, gauntlet } from "@shpbl/sdk";

const shpbl = new ShpblClient({ key: process.env.SHPBL_KEY });
const session = gauntlet(shpbl, { repo: "owner/repo" });

let ledger = "";
let step = await session.next(); // step 1

while (!step.done) {
  console.log(step.text); // your model reads this batch
  ledger += `\n${yourModelsLinesFor(step)}`; // one line per capability

  if (step.checkpoint) {
    // Report to your person. Only if they said yes:
    session.acknowledge();
  }
  step = await session.next({ ledgerDigest: ledger });
}

Approval is never implied. acknowledge() is the only way continue_ack is sent, and snapshot() gives you exactly what to persist to resume in another process. A pause is the server's own gauntlet-checkpoint stage, never wording in the returned text — the harvest protocol prose contains the word "checkpoint". steps() walks for you and hands control back at a checkpoint or when the next call needs a ledger. startWithPlan opens with the run card, which names the batch count and the closing step before anything is read. maxSteps (default 200) is a hard ceiling on calls, so a run that never closes stops instead of looping.

Writing back

files carry the complete new contents under text, never a diff.

await shpbl.writeToRepo({
  repo: "owner/repo",
  title: "Land the harvest",
  summary: "Run seal and coverage.",
  kind: "harvest",
  files: [{ path: ".shpbl/README.md", text: "# Capability library\n" }],
});

Errors

One class per situation, so you branch instead of parsing strings.

| Class | When | | --- | --- | | ShpblValidationError | 400 — arguments rejected; .issues lists them | | ShpblAuthError | 401 / 402 / 403 — missing key, wrong tier | | ShpblNotFoundError | 404 — no such tool | | ShpblRefusalError | 422 — the tool declined; .nextStep says what to do | | ShpblRateLimitError | 429 — .retryAfterSeconds | | ShpblServerError | 5xx | | ShpblNetworkError | never reached a response | | ShpblTimeoutError | timed out or cancelled |

A refusal is a legitimate answer, not a bug. Pass { refusalsAsResult: true } to get it back as a result with ok: false instead of a throw.

Options

new ShpblClient({
  key: "shpbl_mcp_…",
  baseUrl: "https://shpbl.com",
  timeoutMs: 120_000,   // runs are not fast
  maxRetries: 2,        // 429 / 5xx / network only, honours retry-after
  headers: {},
  fetch: customFetch,
  userAgent: "my-app/1.0",
  onRequest: (e) => {},
  onResponse: (e) => {},
});

Per-call overrides: { signal, timeoutMs, maxRetries, headers, refusalsAsResult }.

Headers you supply win. Setting authorization yourself replaces the bearer token the client would otherwise send.

withKey(key) returns an independent client, useful per tenant.

License

The package is MIT. The service it talks to is not: the server, the libraries, the catalogs and the reports stay proprietary under their own terms. The wrapper is free so nobody needs a legal conversation to build against SHPBL. See NOTICE for the exact split.

Development

npm install
npm run typecheck
npm test
npm run build