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

@holonograph/client

v0.14.0

Published

TypeScript HTTP client for a Holonograph lens. Send messages through the lens and report scored outcomes back to it.

Downloads

1,440

Readme

@holonograph/client

The TypeScript HTTP client for a Holonograph lens.

A Holonograph lens sits in front of your LLM calls and turns every call into a longitudinal, gradeable record. This package is the client you integrate into your application: it sends messages through the lens over HTTP and reports the scored outcome of each call back to it. Nothing else — no engine, no model keys, no server. The lens does the work; this is the wire to reach it.

Install

npm install @holonograph/client

The package is a single self-contained ES module with no runtime dependencies. It targets Node 18+ and any runtime with a global fetch.

Quickstart

import { HolonographClient } from '@holonograph/client';

const client = new HolonographClient({
  // Where your running lens is reachable (the binary's local default port).
  endpoint: 'http://127.0.0.1:8787',
  // Optional bearer token, if the lens requires one.
  token: process.env.HOLONOGRAPH_TOKEN,
  // Optional run mode, forwarded on every request.
  runMode: 'production',
  // Pinned into every evaluation event this client produces.
  lensVersion: 'lv_2026_07_01',
  substrate: {
    lensVersion: 'lv_2026_07_01',
    lightSourceIdentifier: 'anthropic/claude/4',
    runMode: 'production',
    provenance: 'production',
    operatorColumns: {},
  },
});

// 1. Send a message through the lens.
const handle = await client.messages.create({
  surfaceId: 'support.triage',
  messages: [{ role: 'user', content: 'My order never arrived.' }],
});

// Read the model's output off the returned handle.
const result = handle.result;

// 2. Score the call however you like, then report the outcome back to the lens.
await handle.reportOutcome({
  dimensions: [
    {
      dimensionId: 'is_actionable',
      passed: true,
      expected: 'a concrete next step',
      actual: 'offered to open a replacement order',
    },
  ],
});

That is the whole loop: messages.create() → score → reportOutcome(). The lens assembles and stores the evaluation event; over time those events become the longitudinal record you query and visualize.

Verdict reliability — let the lens check your judge

A score is only as trustworthy as the judge that produced it, and an LLM judge can misread its own input: accuse the model of inventing a value that was right there in a tool result, or of skipping a tool it actually called. The lens catches that deterministically — but only if your judge hands it a structured claim to check, instead of burying the accusation in prose. Attach a claim to any judged dimension:

await handle.reportOutcome({
  dimensions: [
    {
      dimensionId: 'grounded',
      passed: false,
      expected: 'only facts present in the tool results',
      actual: 'claimed a refund was issued and cited a total of $84.20',
      // What the judge ASSERTED, as fields the lens can verify — not prose.
      claim: {
        status: 'emitted',
        assertions: [
          // "the model cited a value that is not in what it read"
          { kind: 'datum-absent', datum: '$84.20', reference: 'the lookup total was $48.20' },
          // "the model claimed an action without calling the tool that performs it"
          { kind: 'tool-not-invoked', tool: 'issue_refund' },
        ],
      },
    },
  ],
});

The lens checks each assertion against the captured call — is the accused value actually absent from everything the model read? was that tool really never invoked, anywhere across the whole conversation? — and reports, per assertion, whether the record corroborates or contradicts the judge. A contradiction is the loud case: the judge's verdict rested on something it misread, so that score can't be trusted. Two assertion kinds ship today:

  • datum-absent — a value the judge says was fabricated. It splits the accused datum from the optional reference the judge measured it against, so the lens verifies the accusation, never the yardstick.
  • tool-not-invoked — a tool the judge says was never called. The check reads the whole captured conversation, so a call made in an earlier turn still counts.

A dimension with no claim is simply not claim-instrumented — the lens stays honest-null about it rather than guessing, so you opt in one dimension at a time. If your judge is meant to emit a claim but its output is malformed, report { status: 'malformed', reflectionAttempted: <boolean> } — the lens raises that as a judge-malfunction signal rather than silently trusting a broken instrument. The JudgeClaimReport and JudgeAssertion types are exported so you can build and validate the claim before you send it.

Read the whole layer back over a window from your lens — every claim-instrumented dimension with its per-assertion disposition:

GET /holonograph/verdict-reliability

(There is no dedicated client method for this read yet; use client.callDirectly or a plain fetch against your lens endpoint.)

What it speaks

The client talks to a lens over plain HTTP under the /holonograph/* path space (plus the lens's event and availability endpoints). Requests carry x-holonograph-* headers for correlation and run mode. You never construct these by hand — the client does.

Errors from the lens surface as HolonographHttpError, which carries the HTTP status, an error code, and any details the lens returned.

API surface

  • HolonographClient — construct with an endpoint (plus optional token / runMode) and the lensVersion + substrate to pin into every event.
    • client.messages.create(request) — send a message; returns a handle.
    • handle.reportOutcome(outcome) — commit the scored outcome. Each dimension may carry a claim (a JudgeClaimReport) for the verdict-reliability layer to check (see above). The JudgeClaimReport / JudgeAssertion / JudgeAssertionKind types are exported.
    • handle.gradeObserver(...) — attach grades to a cross-vendor observer call before reporting, when the lens returned observer records.
    • client.contract.register(contract) — publish a surface contract to the lens.
    • client.availability.mark(request) — write availability markers.
    • client.callDirectly(request) — escape hatch for the full request shape.
  • HttpTransport — the underlying transport, exposed for advanced use.
  • HolonographHttpError and the typed error classes for the grading and availability flows.

Everything is fully typed; the package ships its own type declarations.

Watching for changes

Pull-side alerting, transport-agnostic. Poll any windowed lens read on a cadence and act only on the delta — a steady state is silent (the same "no all-quiet pings" discipline the lens applies to its own output). You supply the fetch and where the change goes; the client supplies the cadence and change-detection.

  • watchByKey({ poll, intervalMs, keyOf, onChange }) — poll a collection and fire onChange(diff) only when items are added / removed / changed (matched by keyOf). Returns a handle with .stop(). The first poll silently seeds the baseline, so you only hear about change since the watch started (pass emitInitial: true for a cold-start inventory).
  • diffByKey(prev, next, keyOf) / hasChanges(diff) — the underlying keyed diff, if you want change-detection without the loop.
  • createPoller({ poll, intervalMs, onResult }) — the bare cadence loop (non-overlapping; errors isolated to onError, never kill the watcher).
  • watchReconciliation(...) / watchEmission(...) — typed sugar over watchByKey for the lens's reconciliation + emission reads.
import { watchByKey } from '@holonograph/client';

const handle = watchByKey({
  poll: (signal) => fetchReconciliation({ signal }), // your windowed read
  intervalMs: 30_000,
  keyOf: (row) => row.emissionStreamId,
  onChange: (diff) => notifyOnCall(diff), // added / removed / changed only
});
// later: handle.stop();

Requirements

This client does nothing on its own — it needs a running Holonograph lens to connect to. Point endpoint at your lens and you are set.

License

MIT