Skip to content

Fourth door · @shpbl/sdk v0.2.1

The TypeScript SDK

A typed client for the same method. The Evaluation edition runs it offline from a zip, the MCP server runs it inside the chatbot you already use, the HTTP API runs it from any language — and this is that API with the types, the tiers and the run's bookkeeping already in the package.

It is not a second product and not a second procedure. The client calls the identical fourteen tool handlers the MCP server advertises, against the same gate, the same key and the same meter. No model runs in the package, and none runs on the server behind it. The method itself lives in one place — the user's manual, which is the single source of truth for the tools, the lanes and the meter. This page only documents the envelope.

Node 20+, or any runtime with `fetch` — browsers, Workers, Deno, Bun. Source lives in packages/sdk of SweetKenneth/shpbl-master. Current server contract: v1.35.8.

Install and call

Install

npm install @shpbl/sdk

The first call

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

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

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 write-back — the same three-lane arrangement described on pricing.

What the package holds for you

Typed, and the types are pinned
Every tool has a method, every argument has a type, and the tier of every tool travels in the package — so a client answers “may I call this?” with no request and no round trip.
The paywall is an object
`ask(tool)` returns why a call needs something you have not given, what it opens, and what still works without a key. Every auth error carries the same ask, and `explain()` renders it as plain text for a log, an issue or a modal.
The gauntlet's bookkeeping
`gauntlet()` holds the fold token, the server-dictated step order and the human checkpoint. The ledger stays yours: your model writes it, and the session refuses to send a step past the first without one.
Drift you can read
`compatibility()` diffs this release against the live descriptor: new tools, missing tools, tier changes, argument drift. Unknown arguments are forwarded untouched, so calls keep working while you upgrade.
No model, no hidden state
The package never calls an LLM, and neither does the server behind it. There is no session on the server: the run's proof travels with your calls.
Zero runtime dependencies
Dual ESM and CJS, one file each, nothing installed underneath it. The wrapper is MIT; the service, the libraries and the reports stay proprietary under their own terms.

Know before you call

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 tools open to you right now

const ask = free.ask("run_gauntlet"); // why, what it opens, what still works

Drift, as a value

const report = await shpbl.compatibility();
if (!report.ok) console.warn(report.summary);   // log on boot, or assert in CI

The conducted run, without the bookkeeping

The server holds no session. Every step past the first carries 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 exactly that, and nothing else. Approval is never implied: acknowledge() is the only way a continuation is sent.

A run, end to end

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) session.acknowledge();    // only if a person said yes
  step = await session.next({ ledgerDigest: ledger });
}

Every tool has a method

MethodToolLane
welcome()welcomefree
methodProtocol()method_protocolfree
listRepos()list_reposfree
evaluateRepo()evaluate_repofree
fixRepo()fix_repofree
harvestRepo()harvest_repofree
buildIntent()build_intentfree
libraryIndex()library_indexfree
selfcheck()selfcheck_mcpfree
subscriptionStatus()subscription_statusfree
runGauntlet()run_gauntlet$39/mo
librarySearch()library_search$39/mo
libraryDocument()library_document$39/mo
writeToRepo()write_to_repo$39/mo

invoke(name, args) calls anything by name, and tools() / descriptor() read the live contract, so a new server argument never needs an SDK release. What each tool actually does is written once, in the manual.

Errors you can branch on

ShpblValidationError

400 — arguments rejected; `.issues` lists them

ShpblAuthError

401 / 402 / 403 — missing key, wrong tier; carries the ask

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 — the instrument is built to say “change nothing” when that is the truth. Pass { refusalsAsResult: true } to receive one as a result with ok: false instead of a throw.

Something wrong, or something missing? Write to dev@cmpsbl.com, or open an issue on SweetKenneth/shpbl-master. A real person reads both.