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

@bastra-recall/daemon

v1.1.0

Published

MCP server over a markdown vault: recall, load_memory, save_memory, edit_memory, archive_memory and document tools — plus the REST gateway, the quiet hook lanes and the bastra CLI.

Readme

@bastra-recall/daemon

The MCP server + HTTP gateway behind bastra-recall. Watches a markdown vault, indexes it with BM25 + optional embeddings, and exposes a stable tool surface over both stdio MCP and HTTP REST.

For project-level docs (vision, install, REST API, roadmap), see the top-level README and PLAN.md.

What it does

  • Watches a directory of .md files with YAML frontmatter (schema in ../../docs/memory-schema.md).
  • Indexes via minisearch BM25; recall_when is the highest-weighted field because it's authored for triggering.
  • Hybrid recall: optional embeddings (Ollama or OpenAI) via Reciprocal Rank Fusion on top of BM25 — see BASTRA_EMBEDDING_PROVIDER.
  • Save path: writes .md files, validates frontmatter (zod), force-reindexes so save+recall in the same turn are consistent.
  • Auto-related enricher: for each new save, fills related_via with cosine ≥0.7 neighbors.
  • Memory graph: multi-hop recall (expand_hops: 1) returns 1-hop neighbors via related_via.
  • Sensitivity filter: private memories aren't visible to external MCP/REST callers — and since #464 not writable by them either. The permission is bound to the transport, not passed as a tool argument, and a refused read and a refused write answer alike, so neither is an existence oracle.
  • Staleness re-ranking: memories with valid_until / expires_after_days / last_reviewed_at get demoted (or excluded if expired) at recall time.

Surfaces

| Surface | Entry point | Use case | |---|---|---| | MCP (stdio, standalone) | dist/index.js | Single-client setup; each session spawns its own embedded daemon | | MCP forwarder (stdio → loopback HTTP) | dist/mcp-forwarder.js | Default for multi-client setups. Starts a missing daemon on demand, first giving an installed managed macOS service up to ten seconds. All sessions share one vault state, one index, one telemetry stream | | HTTP REST | http://127.0.0.1:6723/api/v1/{tool} | Non-MCP clients (ChatGPT Custom GPT Actions, web apps, scripts). Bearer auth + CORS supported | | Hooks | dist/hook.js (PreToolUse), dist/session-hook.js (SessionStart) | Both POST to the daemon's /hook/recall | | CLI | dist/cli.js (bastra bin) | Install / uninstall / doctor across every supported AI client |

Tools exposed (MCP + REST symmetric)

| Tool | Purpose | |---|---| | recall(query, k?, scope?, type?, expand_hops?) | Search the vault; hybrid BM25 + embeddings when enabled | | load_memory(id) | Fetch full frontmatter + body | | save_memory({title, type, body, …}) | Write a new memory with schema validation + force-reindex | | edit_memory({id, str_replace?, append?, frontmatter?, expected_revision?}) | Patch one existing memory through the same save path (#519) | | find_document(query, k?) | Search documents (PDFs, photos, contracts) | | read_document(id) | Load extracted text + metadata for a document | | open_document(id) | macOS-only: open in the system handler | | find_code(query, mode?, repo?, depth?) | Locate a symbol or file in the code graph; one hop of dependents (#576) | | save_document / recategorize_document / move_document | Document write path (Pro Mac-app uses this; OSS callers may need BASTRA_DOCUMENT_WRITE=1) |

Install + register

See the top-level README. Paths, in order of friction:

  1. curl -fsSL https://bastra.io/install | bash — installs Homebrew + tap + binary + runs the guided setup. The recommended non-developer path.
  2. Install Bastra.command doubleclick — same steps, but a browser download is quarantined and mode 644, so it needs right-click → Open (#320).
  3. bastra install all — single CLI call that registers MCP + Skill + the default quiet Hooks across Claude Code, Claude Desktop, Codex/ChatGPT Desktop, and Cursor. On a first run with no vault configured, an interactive install offers to create ~/BastraVault for you (non-interactive/--yes/--dry-run runs keep the deterministic error).
  4. Fully manual JSON snippets — fallback.

All paths end with the client configs patched. Whether the daemon is already running differs: the interactive Map wizard starts it, and the MCP forwarder starts it on the first tool call — bastra install all only registers, it neither starts nor health-checks the daemon. bastra doctor is what probes the endpoint (#525). Use bastra doctor --fix to repair stale paths, missing required hooks, or a stale Skill copy after an update. The quiet Stop save-eval hook is enabled by default by the CLI. Opt out with bastra install claude-code --no-stop-hook; deliberately disabling this optional hook does not make Doctor fail.

Daemon process check

Don't grep for daemon/dist/index.js — the daemon is often launched with a relative path (node dist/index.js) and won't match. Use the port instead:

lsof -i :6723 -P -n      # who owns the daemon port
curl -sS http://127.0.0.1:6723/health

Exactly one PID should be listed. Two means a stale daemon is running in parallel — the HTTP port goes to whichever bound first, and the loser exits silently (see the EADDRINUSE handler in http-listen.ts).

Daemon startup

The MCP forwarder uses one shared daemon, giving an installed managed macOS service up to ten seconds before fallback spawn. For REST clients that need the daemon before an MCP client connects, start it explicitly:

bastra-recall &

Dev workflow

npm run dev                # ts-watch via tsx (no compile step)
npm run build              # tsc + chmod +x all dist binaries
npm run check:types        # type-check only
npm run smoke              # smoke-test recall against fixtures/sample-vault
npm run smoke:telemetry    # smoke-test telemetry append
npm run backfill:related   # populate related_via on legacy memories

Configuration (environment variables)

Every on/off switch reads 0, false, off or no as off and 1, true, on or yes as on, in any case; the table shows the canonical spelling.

| env var | required | default | meaning | |---|---|---|---| | BASTRA_VAULT_PATH | yes | — | absolute path to the vault root (memories are auto-discovered) | | BASTRA_HTTP_PORT | no | 6723 | loopback HTTP port for REST + hooks; read only when neither URL var below is set | | BASTRA_HTTP_URL | no | derived | full URL override (for non-loopback testing); read only when BASTRA_DAEMON_URL is unset | | BASTRA_API_TOKEN | no | unset | the bearer REST /api/v1/* requires from every caller that is not a direct local one; unset/empty does not open the API — such callers then get a 401 nothing can satisfy until a token is minted (#526) | | BASTRA_AUTH_LOOPBACK_SKIP | no | 1 | the token-free path needs a loopback peer and a present, loopback Host header — a missing header counts as foreign (#526); set to 0 to require the bearer even for direct 127.0.0.1 callers | | BASTRA_CORS_ORIGIN | no | unset (deny all) | comma-separated browser-origin allowlist; unset = no browser origin allowed; * = explicit permissive opt-in (tunnel/dev) | | BASTRA_EMBEDDING_PROVIDER | no | unset | ollama or openai; without it the daemon stays BM25-only | | BASTRA_EMBEDDING_MODEL | no | provider default | e.g. embeddinggemma (ollama) or text-embedding-3-small (openai) | | BASTRA_OLLAMA_URL | no | http://localhost:11434 | ollama provider endpoint | | BASTRA_ALLOW_REMOTE_OLLAMA | no | unset | allow a non-loopback BASTRA_OLLAMA_URL; without it the daemon refuses a remote endpoint, so a mistyped URL never sends memory text off-box (guards both the embedding provider and the reranker) | | BASTRA_OLLAMA_KEEP_ALIVE | no | 10m | per-request keep_alive window — how long Ollama keeps the embedding model in RAM after each embed | | BASTRA_OLLAMA_IDLE_UNLOAD_MS | no | 600000 (10 min) | unload the embedding model from Ollama RAM after this long without an embed (battery saver); 0 disables | | BASTRA_BATTERY_SAVER | no | unset (→ battery.saver, default off) | battery mode on macOS (#632): 1 / 0 override the settings file. On battery, doc2query waits for AC, warm-ups are skipped and the idle unload fires after 60 s — see USAGE | | OPENAI_API_KEY | no | unset | required when BASTRA_EMBEDDING_PROVIDER=openai | | BASTRA_FORWARDER_SPAWN | no | 1 | when 0, the MCP forwarder will not auto-spawn the daemon. This remains a workaround for service-only starts, with BASTRA_DAEMON_URL naming that owner. macOS managed services now get a ten-second chance automatically; a headless service behind a forwarder waits for its idle exit (#758). Linux managed setup follows in #566 | | BASTRA_DAEMON_URL | no | http://127.0.0.1:6723 | which daemon this machine means — highest-precedence input to the single endpoint resolver every surface uses since #531 (CLI, doctor, map, hooks, forwarder, bridge, and the daemon's own bind), and what bastra install writes into a client registration | | BASTRA_HOOK_TIMEOUT_MS | no | per lane | overrides the lane's wall-clock budget before fail-silent — the per-lane defaults are in docs/hooks.md (#305) | | BASTRA_VECTOR_DEADLINE_MS | no | 150 | hook path only: how long a recall waits for the dense arm before serving BM25-only. Bounds that stage, not the call — measured warm the arm costs 87–96ms (total 106–113ms), cold 668ms (total 694ms), so 150ms passes every warm call and caps a cold one near 180ms. The embed is abandoned, not cancelled, so the model still finishes loading and the next call is warm. Degradations are visible as degraded: "vector-arm-timeout"; 0 disables (kill switch) | | BASTRA_HOOK_MAX_SHOW | no | 1 | how often the same memory may appear in <recall-hints> per session (4h window); a load_memory of that id resets the counter | | BASTRA_HOOK_CONTENT_RECALL | no | off | set to 1 to run the opt-in edit-content recall arm (#282) | | BASTRA_DOCUMENT_WRITE | no | unset | set to 1 to expose document write tools | | BASTRA_LOG_PATH | no | ~/.bastra/logs | telemetry JSONL output directory (out-of-vault on purpose) | | BASTRA_TELEMETRY | no | on | set to off to disable telemetry writes entirely |

Telemetry

Every recall, load_memory, save_memory and hook call appends one JSON line to events-YYYY-MM-DD.jsonl in the log dir. Each daemon process gets a fresh session_id; recalls get a recall_id and any load_memory / save_memory within 5 minutes references that id as follows_recall.

That's enough to compute:

  • recalls per session, hit counts, top-score distribution, latency p50/p95
  • recall→load_memory follow-through rate (proxy for "was the hint useful?")
  • saves per session, type/scope distribution, % overwrite vs. new
  • save→follows_recall rate (was a duplicate-check done?)
  • per-hook latency and hit-quality (where do PreToolUse / SessionStart actually help?)

Logs live outside the vault on purpose so the file watcher doesn't index them. Tail one to watch live: tail -f ~/.bastra/logs/events-$(date +%F).jsonl.

Search ranking

Field boosts (in src/search.ts):

recall_when_flat:          5    ← authored for triggering, highest weight
title:                     4
tags_flat:                 3
recall_when_expanded_flat: 2    ← doc2query paraphrases (#117): machine-generated, below the author's words
topic_path_flat:           2
summary:                   2
body:                      1

Fuzzy distance 0.2, prefix matching enabled, combineWith: "OR". Hybrid mode combines BM25 with embedding cosine via Reciprocal Rank Fusion (RRF); the embedding query goes to the provider once per recall and is cached in-memory.

Schema reference

See ../../docs/memory-schema.md for the full memory frontmatter spec. The daemon enforces required fields via zod on load — files that fail validation are skipped with a warning to stderr.

Limitations

  • Shared process. The forwarder starts the daemon on demand. On macOS, bastra autostart on manages an optional LaunchAgent for keeping it running; use bastra autostart off to return to on-demand operation.
  • Cloud-storage mounts (Google Drive, iCloud, Dropbox) need polling-mode in chokidar — enabled by default, but watcher latency can be a few seconds.
  • Cursor rules are per project. bastra install cursor registers MCP. Run bastra rules cursor inside each project to install the shared memory guidance.