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

@mnemahq/sdk

v0.5.0

Published

Typed client for the Mnema API — docs, tasks, projects, sessions, repos, search.

Readme

@mnemahq/sdk

Typed client for the Mnema API — docs, tasks, projects, sessions, repos, search, the knowledge graph and the findings engine.

npm install @mnemahq/sdk

Quickstart

import { Mnema } from '@mnemahq/sdk';

const mnema = new Mnema({ apiKey: process.env.MNEMA_API_KEY });

const me = await mnema.me();
console.log(`workspace ${me.workspaceName} — ${me.credential} credential, scopes: ${me.scopes}`);

for await (const task of mnema.tasks.list({ status: 'in_progress' })) {
  console.log(task.publicId, task.title);
}

Get a key from Settings → Access in the app, or run mnema login if you have the CLI — the SDK accepts either credential.

All calls go to the versioned public API (/api/public/v1), which is scope-enforced and rate-limited at 60 req/min per credential.

Every ts block in this file is extracted and type-checked against the built package in CI, and the quickstart is executed end to end against a mock server. If an example here does not work, that is a bug in the SDK, not in the README. See scripts/check-readme.mjs.

Ask before you call

me() answers three separate questions, and merging them is how integrations go wrong:

const me = await mnema.me();

me.scopes;   // what this CREDENTIAL may do    — narrow the key, this shrinks
me.features; // what this PLAN includes         — upgrade, this grows
me.edition;  // what this BUILD contains        — neither; it is the binary you run

A refused call on a self-hosted core build is not a billing problem and cannot be fixed by upgrading. A refused call on cloud free is not a scope problem and cannot be fixed by minting a broader key. Checking here is cheaper and more honest than calling and interpreting the failure.

Pagination is an async iterator

list() returns something you can iterate directly — it fetches pages as you consume them and stops the moment you stop.

for await (const doc of mnema.docs.list()) { void doc; }   // every doc, page by page
const first10 = await mnema.tasks.list({ limit: 10 }).all(); // or collect
for await (const page of mnema.sessions.list().pages()) { void page; } // or batch at a time

Breaking out of the loop stops fetching. No cursor bookkeeping.

The knowledge graph

Gated: needs the graph feature and a build that contains it.

const answer = await mnema.graph.ask('why did we move off the flat exports map?');

if (answer.confidence < 0.4) {
  console.log(`low confidence (${answer.confidenceReason}) — treat as a lead, not a fact`);
}
console.log(answer.answer, answer.sources);

Read confidence before you render answer. The interpreter hedges rather than asserts below its threshold; a UI that prints the sentence and drops the number turns a hedge into a claim. confidenceReason says which kind of thin it is: no_data, stale, thin or contested.

const path = await mnema.graph.traverse('Billing', 'Onboarding');
console.log(path.connected ? `${path.hopCount} hops` : 'no route between them');

const critical = await mnema.graph.godNodes({ limit: 10 }).all();

connected: false with an empty path is a real answer, not an error. Traversal ignores edge direction — "how are these connected" is a reachability question.

Findings

Ungated: the findings engine ships in every build. What varies is how much it can see.

const briefing = await mnema.findings.briefing();

if (briefing.coverage.degraded) {
  console.log(briefing.coverage.notice); // a short list is not necessarily good news
}
if (briefing.neverComputed) {
  console.log('the engine has never run here');
}

A short list has three quite different causes and only one is good news:

| signal | meaning | | --- | --- | | coverage.degraded | this build cannot produce most finding types — six of seven need the knowledge graph | | neverComputed | the engine has never run for this workspace | | neither | genuinely quiet |

Repeated findings arrive pre-collapsed, so branch on grouped:

import { isFindingGroup } from '@mnemahq/sdk';

for (const item of (await mnema.findings.briefing()).findings) {
  if (isFindingGroup(item)) console.log(`${item.count}x ${item.headline}`);
  else console.log(item.headline);
}

Errors tell you which wall you hit

Every failure is typed, so you never have to parse a message to find out whether you have a bug, a quota, a paywall, or a build that simply lacks the feature.

import { PlanRequiredError, FeatureUnavailableError, InsufficientCreditError, RateLimitError, AuthError } from '@mnemahq/sdk';

try {
  await mnema.graph.ask('what changed in billing last week?');
} catch (err) {
  if (err instanceof FeatureUnavailableError) {
    console.log(`${err.feature} is not in this ${err.edition} build — nothing to buy here`);
  } else if (err instanceof PlanRequiredError) {
    console.log(`${err.feature} needs the ${err.required} plan — ${err.upgradeUrl}`);
  } else if (err instanceof InsufficientCreditError) {
    console.log(`top up ${err.topUpNeededCents}c — balance ${err.balanceCents}c`);
  } else if (err instanceof RateLimitError) {
    console.log(`slow down for ${err.retryAfterMs}ms`);
  } else if (err instanceof AuthError) {
    console.log(err.fix); // "Run `mnema login` to authenticate."
  }
}

| error | when | | --- | --- | | AuthError | no credentials, expired, or revoked — carries .fix | | PlanRequiredError | your plan does not include it — .feature .plan .required .upgradeUrl | | FeatureUnavailableError | this build does not contain it — .feature .edition | | InsufficientCreditError | you have the feature, the prepaid balance ran out — .balanceCents .topUpNeededCents .topUpPacks | | RateLimitError | 429 — .retryAfterMs .limit .remaining .resetAt | | ValidationError | 400/422 — .fields | | ApiError | any other non-2xx — .status | | NetworkError | never reached the server |

PlanRequiredError and FeatureUnavailableError are deliberately different. You cannot buy your way out of the second: a self-hosted core build has no graph engine, and telling that user to upgrade points them at a plan that does not exist on their own server. Likewise InsufficientCreditError is not a plan problem — the caller is already paying and needs to top up, and "upgrade your plan" would send them somewhere that cannot help.

A gated call always throws. It never returns an empty array — you should never be able to build a dashboard on a paywall and watch it render zeros.

And so does a shape mismatch. If the API returns something this client does not recognise, you get an ApiError naming the keys it actually found — not an empty list. An earlier build read the wrong envelope key and returned [] against 593 real tasks, silently. That cannot happen now.

Rate limits, before you hit them

Limits are read from every response, not only rejections:

await mnema.tasks.list().all();
console.log(mnema.rateLimit); // { limit: 60, remaining: 41, resetAt: Date }

const paced = new Mnema({
  apiKey: process.env.MNEMA_API_KEY,
  onRateLimit: ({ remaining }) => { if ((remaining ?? 60) < 10) console.warn('slowing down'); },
});
void paced;

The numbers describe the 60/min per-credential limit that governs /api/public/v1. 429s are retried automatically, honouring Retry-After.

Retries

Automatic on 5xx, 429 and network failures, with full-jitter exponential backoff.

Only for requests that are safe to repeat — GET and HEAD. A failed POST is never retried, because a duplicate task is worse than a visible error. The exception is 429: the request was refused, not attempted, so repeating it cannot duplicate anything.

Runtimes

Node 20+, Bun, Deno, and edge runtimes. ESM and CJS. No heavy dependencies, and fetch is pluggable:

const instrumented = new Mnema({ fetch: globalThis.fetch, timeoutMs: 10_000 });
void instrumented;

License

Apache-2.0