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

lichen-core

v0.1.1

Published

A local fact-mesh brain over your own notes: honest recall, reinforce/decay memory, zero dependencies. Grown, not trained.

Readme

lichen-core

A local fact-mesh brain over your own notes. You teach it facts — by pointing it at a folder of markdown — and it answers from those facts only, telling you honestly when it doesn't know. No account, no cloud, no API key, no cost. Everything lives in a JSON file on your disk that you can open and read.

Published by Zero under the MIT license. Zero npm dependencies — pure Node.js (CommonJS), Node 18 or newer.

Why honest recall matters

Every LLM chatbot answers every question — including the ones it knows nothing about. It fills gaps with confident invention, and you can't tell which is which. lichen-core takes the opposite deal: it retrieves from facts you taught it, scores how well your notes actually cover the question (coverage: high / partial / low, plus the exact words no fact mentions), and when coverage is low its standing instruction is to say "I don't have that in my notes yet — tell me and I'll remember it." A memory that admits its gaps is one you can trust with the rest.

And the memory itself is alive: facts you actually use get stronger and decay slower; facts you never touch fade and are pruned. Teach it a correction and the old fact is superseded, not silently contradicted. Tell it whether an answer helped (/outcome) and usefulness — not just recency — shapes what survives. Every mutation is written to a hash-chained audit journal you can replay.

Quickstart

# 1. Install Node.js 18+ (https://nodejs.org). That's the only requirement.
git clone https://github.com/thefinalmilkman/lichen-core && cd lichen-core     # or unzip it anywhere

# 2. (Optional but recommended) Install Ollama (https://ollama.com) for real
#    semantic embeddings + grounded answers:
ollama pull nomic-embed-text phi4-mini

# 3. Start the brain
node server.js          # http://127.0.0.1:4174 — open it for the chat page

# 4. Teach it your notes (in a second terminal)
node grow.js ./my-notes # .md/.txt, walked recursively; --dry-run to preview first

Then ask it things:

# recall raw facts (no LLM call at all — works with Ollama down)
curl -s http://127.0.0.1:4174/recall -d '{"query":"oil change interval"}'

# ask (grounded answer via Ollama; without Ollama you get the facts + an honest "unavailable" note)
curl -s http://127.0.0.1:4174/ask -d '{"prompt":"what oil does my truck take?"}'

# teach a single fact directly
curl -s http://127.0.0.1:4174/learn -d '{"text":"The truck takes 5W-30 full synthetic, 6 quarts."}'

# tell it whether a fact helped (useful facts decay slower; harmful ones faster)
curl -s http://127.0.0.1:4174/outcome -d '{"factId":1,"good":true}'

# health + the audit trail of every memory mutation
curl -s http://127.0.0.1:4174/health
curl -s "http://127.0.0.1:4174/journal?tail=20"

Windows note: curl.exe works the same; -d sends JSON with the default Content-Type the server expects.

The HTTP API

| Route | Method | What it does | |---|---|---| | /health | GET | version, fact count, edge count, unread attention flags | | /learn | POST | {facts:[{text,meta}]} or {text,meta}; replaceSrc re-teaches a note | | /recall | POST | {query, k?} → top-k facts + coverage. No model call. | | /ask | POST | {prompt} or OpenAI-style {messages} → grounded answer + sources + coverage | | /outcome | POST | {factId|text, good:true|false} → reward/punish a fact | | /journal | GET | ?tail=N → recent memory mutations (hash-chained audit log) | | /attention | GET | flags raised via /notify (a small inbox for "worth a look" items) | | /notify | POST | {title, detail?, level?, source?} → raise an attention flag | | / | GET | a minimal chat page (works offline; optional local speech) |

The server binds 127.0.0.1 only — your notes never leave the machine.

Configuration (all env vars)

| Var | Default | Purpose | |---|---|---| | LICHEN_PORT | 4174 | server port | | LICHEN_DATA | ./data | where mesh.json, graph.json, journal.jsonl, attention.jsonl live | | OLLAMA_URL | http://127.0.0.1:11434 | Ollama base URL | | LICHEN_MODEL | phi4-mini | procedure model used by /ask |

LICHEN_DATA means you can run several independent brains side by side:

LICHEN_PORT=4201 LICHEN_DATA=./brains/work node server.js
LICHEN_PORT=4202 LICHEN_DATA=./brains/home node server.js

When Ollama is down

Nothing breaks and nothing is faked. At startup the server probes Ollama and prints a loud warning if it's unreachable. Embeddings then degrade to a built-in lexical embedder (hashed bag-of-words): retrieval still works, but "similar" means shared words, not shared meaning. /ask still responds — it returns the retrieved facts with an explicit [procedure model ... unavailable] note instead of a synthesized answer. /recall, /learn, /outcome, /journal are fully functional offline. Install Ollama and pull nomic-embed-text + any small chat model to get the real thing.

How it works (short version)

  • Facts live in a reinforce/decay mesh (mesh.js): recall strengthens, disuse decays, a hybrid exponential→power-law forgetting curve decides what fades, and a floor prunes the dead.
  • Retrieval is hybrid (retrieval.js): dense vector similarity and from-scratch BM25, fused by reciprocal rank fusion — so exact tokens (error codes, part numbers) are never smeared by vectors.
  • Ingest triage dedups by exact-text hash, reinforces restatements, and supersedes old facts only when a cheap contradiction signal fires — near-duplicates that aren't contradictions stay both live, journaled as related.
  • Associations (graph.js): facts co-recalled together grow Hebbian edges; frequently-used edges potentiate and decay at half rate.
  • Audit (journal.js): every mutation is one JSONL line in a hash-chained log. Verify with node journal.js --verify data/journal.jsonl.

Honest limits

  • Brute-force retrieval. Ranking is an O(N) scan over all facts per query. That's instant up to tens of thousands of facts and fine past 100k on a desktop, but there is no ANN index — the ceiling is real.
  • Whole-file JSON storage. Save/load is one big mesh.json. Known behavior at scale: a brain grown to ~120,000 facts is a ~124 MB JSON file; loading takes seconds and saves block briefly. Fine for a personal notes brain; not a multi-tenant store.
  • Single-user by design. One mesh, no auth, localhost-only. It is your brain, not a service.
  • A small procedure model is still a small model. With Ollama, /ask synthesis can ramble on abstract questions and may occasionally leak a parametric fact despite instructions. The retrieved facts (from /recall) are the ground truth; the prose is a convenience.
  • Retrieval quality is capped by the embedder. Lexical fallback = keyword overlap. Nomic via Ollama = real semantics. Same code, honest difference.

Tests

npm test        # = node test/run.js — fully offline, no Ollama, no services

Three suites: test/verify.js (mesh behavior: grow/reinforce/decay/supersede/outcomes/journal — deterministic, lexical embedder), test/verify-graph.js (associative edges, consolidation, journal hash chain), and test/smoke.js (boots a real server on a scratch port with a temp data dir, grows a fixture folder through grow.js, recalls, checks outcomes + journal, shuts down).

Files

server.js      HTTP brain (routes above), binds 127.0.0.1
grow.js        folder-of-notes ingestion -> POST /learn (the way your notes get in)
lichen.js      the three-substrate model: procedure + voice + facts
mesh.js        the living fact store (reinforce/decay, supersede, outcomes, pruning)
retrieval.js   BM25 + reciprocal rank fusion
embed.js       Ollama embeddings with a zero-dep lexical fallback
graph.js       Hebbian association edges, spreading activation, sleep consolidation
journal.js     hash-chained append-only audit log
notify.js      attention inbox (a tiny flag queue for "worth a look")
distill.js     optional sleep pass: turn unread attention flags into mesh facts
test/          offline test suites (npm test)

Everything in data/ is yours. Delete it and the brain is gone; copy it and the brain moves. Nothing else anywhere knows it exists.

Ecosystem

  • openclaw-factmesh — makes this brain the memory backend for OpenClaw agents.
  • The Local AI Field Manual — the field guide this project was built alongside: hardware tiers, Ollama tuning, honest retrieval. Pay-what-you-want; lichen-core stays free.

If lichen-core earns it, the storefront above is where a tip lives — USDT or ETH on Base, no accounts.