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

@bkbnlab/api

v0.11.0

Published

JavaScript/TypeScript client for the bkbn AI Lab API

Readme

@bkbnlab/api

JavaScript/TypeScript client for the bkbn AI Lab API — AI photo editing for real estate: HDR develop, perspective, signature tone, privacy blur, sky replacement, declutter, staging, magic eraser.

  • One WebSocket per clientorder.wait(), batch.wait() and client.watch() all multiplex a single connection (ticket auth + reconnect hidden). Native WebSocket on browsers and Node ≥ 22; npm i ws on older Node.
  • Zero required dependencies — native fetch for the rest.
  • Typed errors — branch on RateLimitError.retryAfterMs, not on strings.
npm install @bkbnlab/api

Quickstart

import { BkbnClient } from '@bkbnlab/api'

const bkbn = new BkbnClient()            // reads BKBNLAB_API_KEY

const photo = await bkbn.upload('living-room.jpg')   // 3-step upload, hidden

const order = await bkbn.order({
  inputs: [photo],                       // 1-5 brackets of ONE scene
  perspective: true,                     // or { strength: 0.8 }
  tonecraft: true,
  privacy: true,
  sky: 'high_cloud',                     // clear | low_cloud | neutral | high_cloud
})

const result = await order.wait({
  onProgress: (p) => console.log(p.stage, p.percent),
})
await result.save('enhanced.jpg')

Batches — one shoot, one call

Shared defaults, per-scene overrides (undefined inherits), atomic validation server-side: a bad scene refuses the whole batch before anything runs. One SSE connection follows the whole shoot.

const batch = await bkbn.batch({
  name: 'shoot-12-rue-vaugirard',
  perspective: true,
  tonecraft: true,
  scenes: [
    { inputs: [a1, a2, a3] },                          // HDR brackets, inherits
    { inputs: [b1], sky: 'high_cloud' },               // override
    { inputs: [c1], genEdit: 'declutter_local' },
  ],
})

const results = await batch.wait({
  onProgress: (p) => console.log(`${p.completed}/${p.total}`),
  onOrderDone: (o) => console.log('done', o.orderUuid),
})
await results.saveAll('out/')
results.failed                            // a failed scene does NOT throw

Magic eraser

The mask is an asset like any other (white-on-transparent PNG):

const done = await (await bkbn.order({
  inputs: [photo],
  genEdit: 'magic_eraser',
  mask: await bkbn.upload(maskPng),
})).wait()

Exports — other versions of a delivery

Know the format up front? Pass it to order(). Need another version later — a webp for the listing page, a 1024 px thumbnail, a lighter jpeg — export it: no re-run, no re-billing of the pipeline.

const webp = await result.export({ format: 'webp', quality: 80 })

await webp.save('room.webp')      // node
img.src = webp.url                // browser — the link needs no header

An agency's three formats at once, straight off one delivery:

const [print, web, thumb] = await Promise.all([
  result.export({ format: 'jpeg', quality: 95 }),
  result.export({ format: 'webp', quality: 80 }),
  result.export({ format: 'jpeg', longEdge: 1024 }),
])

export() resolves when the file is ready — it listens on the client's one WebSocket, so you never write a polling loop. Asking twice for the same version costs one packaging: the second call returns the stored file immediately.

Any asset you own works, not just the freshest order — use bkbn.export(assetId, options) with an upload id, or with the outputAssetId of an order from three weeks ago.

Watch the account

Everything that happens on your key — progress, completions, intermediate frames (command.resultAssetId), asset events — off the ONE shared WebSocket. Infinite, reconnects by itself. order.wait() and batch.wait() filter this same connection, so a client holds exactly one socket no matter how many orders it follows at once.

for await (const ev of bkbn.watch()) {
  if (ev.type === 'order' && ev.data.command?.resultAssetId) {
    console.log('intermediate frame:', ev.data.command.resultAssetId)
  }
}

The feature catalog

Features are versioned (semver of the delivered recipe):

const features = await bkbn.features()
// [{ key: 'magic_eraser', version: '4.1.0', kind: 'tool', status: 'ga', … }]

Errors

import { RateLimitError, OrderFailedError, IngestError } from '@bkbnlab/api'

Short 429 waits are absorbed automatically (Retry-After honoured); long ones surface as RateLimitError with retryAfterMs. wait() falls back to polling when the WebSocket can't connect (Upgrade-stripping proxies) — it does not break on hostile networks.

Key rotation

const { key } = await bkbn.rotateKey()   // new secret, returned once —
                                         // the old one dies immediately

Configuration

| | | |---|---| | BKBNLAB_API_KEY | your sk-… key (or new BkbnClient({ apiKey })) | | BKBNLAB_API_BASE | HTTP API origin override (default: hosted API) | | BKBNLAB_WS_BASE | WebSocket origin override (default: hosted WS tier) |

API reference

| Method | Returns | Notes | |---|---|---| | new BkbnClient({ apiKey?, base?, fetch? }) | client | env fallbacks: BKBNLAB_API_KEY, BKBNLAB_API_BASE | | bkbn.upload(input, { filename?, contentType? }?) | Promise<assetId> | input: path (node) / Uint8Array / Blob/File; resolves when ingest is ready, throws IngestError | | bkbn.order(options) | Promise<OrderHandle> | submits one scene; format/quality pick the delivery container | | bkbn.orderHandle(uuid) | OrderHandle | reattach by id (no request) | | bkbn.getOrder(uuid) | Promise<OrderSummary> | current state (one poll) | | order.wait({ onProgress?, timeoutMs?, signal? }?) | Promise<OrderResult> | SSE, poll fallback; throws OrderFailedError | | order.events({ signal? }?) | AsyncGenerator<FeedEvent> | raw events for this order, off the shared WS feed | | order.get() | Promise<OrderSummary> | one poll | | result.bytes() / result.save(path) | | save is node-only | | result.export({ format, quality?, longEdge?, timeoutMs?, signal? }) | Promise<ExportResult> | another version of this delivery; resolves when ready (feed, poll fallback) | | bkbn.export(assetId, options) | Promise<ExportResult> | the same, by asset id — an upload, or any past delivery | | export.url / export.bytes() / export.save(path) | | url needs no header; save is node-only | | bkbn.batch(options) | Promise<BatchHandle> | atomic server-side validation | | bkbn.batchHandle(uuid) | BatchHandle | reattach by id | | bkbn.getBatch(uuid) | Promise<BatchState> | current aggregate (one poll) | | batch.wait({ onProgress?, onOrderDone?, timeoutMs?, signal? }?) | Promise<BatchResults> | one SSE for the shoot | | batch.events() / batch.get() | | raw stream / aggregate poll | | results.completed / results.failed / results.saveAll(dir) | | a failed scene never throws | | bkbn.watch({ signal? }?) | AsyncGenerator<FeedEvent> | infinite account feed | | bkbn.listOrders({ status?, limit? }?) | Promise<OrderSummary[]> | snapshot of my queue | | bkbn.features() | Promise<FeatureSpec[]> | versioned catalog | | bkbn.rotateKey() | Promise<{ key, … }> | new secret, returned once |

Runnable examples: examples/ (quickstart.mjs, batch.mjs, watch.mjs).

Smoke — verify a real stack

BKBNLAB_API_KEY=sk-… npm run smoke

Self-contained conformance run against a live stack (sample image included): catalog, upload, order with realtime proof (feed events counted during the run), download byte-check, batch with a per-scene override, typed errors. Exits non-zero on any failure — usable as a deploy gate.