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/sdkThe 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 worksDrift, as a value
const report = await shpbl.compatibility();
if (!report.ok) console.warn(report.summary); // log on boot, or assert in CIThe 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
| Method | Tool | Lane |
|---|---|---|
| welcome() | welcome | free |
| methodProtocol() | method_protocol | free |
| listRepos() | list_repos | free |
| evaluateRepo() | evaluate_repo | free |
| fixRepo() | fix_repo | free |
| harvestRepo() | harvest_repo | free |
| buildIntent() | build_intent | free |
| libraryIndex() | library_index | free |
| selfcheck() | selfcheck_mcp | free |
| subscriptionStatus() | subscription_status | free |
| 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.