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

hemisphere

v1.2.1

Published

Lightweight memory MCP server with hybrid FTS + vector search

Readme

Hemisphere

License: GPL v3 Node

Author: Hector Jarquin

A persistent, searchable memory store for AI agents. Gives LLMs long-term recall across sessions using hybrid FTS + vector search, structured metadata, full CRUD, progressive summaries, and a live dashboard — no model downloads, no API keys, no Python.

Quick Start

npm install -g hemisphere

Once installed, run the dashboard:

hemisphere
# → http://localhost:3456

CLI commands

hemisphere              # Start the dashboard
hemisphere stop         # Stop a running instance
hemisphere restart      # Stop then restart

MCP server

Add to opencode.json:

{
  "mcp": {
    "hemisphere": {
      "type": "local",
      "command": ["node", "/home/user/.nvm/versions/node/v22.22.0/lib/node_modules/hemisphere/index.js"],
      "enabled": true
    }
  }
}

Find your exact path with npm root -g — append /hemisphere/index.js (e.g. /usr/lib/node_modules/hemisphere/index.js).

Now your agent can store and retrieve memories:

Store: "the database uses pg-bouncer for pooling"
Search: "what's our connection pooling setup?"

Dashboard

Browse, search and manage memories visually at http://localhost:3456.

Port configurable via ~/.hemisphere/config.json or HEMISPHERE_PORT.

Features: dark/light theme toggle with SVG icon, WCAG 2.1 AA accessibility (keyboard navigation, ARIA labels, focus-visible outlines), real-time SSE updates (no polling), toast notifications, skeleton loading, search with debounce and prefix matching, project and kind filters, View dropdown (Active / Deleted), expandable detail rows, delete with native <dialog> confirmation, restore and permanent purge of soft-deleted memories, on-demand backups.

Installation

Prerequisites

  • Node.js 18+
  • C++ build tools (build-essential on Debian/Ubuntu, Xcode CLI tools on macOS) — required to compile better-sqlite3

From npm (recommended)

npm install -g hemisphere

The hemisphere CLI is now available globally. The MCP server runs via your AI client — see Configuration.

From git

git clone https://github.com/hectorjarquin/hemisphere.git ~/hemisphere
cd ~/hemisphere
npm install
npm link  # creates global `hemisphere` command

Configuration

{
  "mcp": {
    "hemisphere": {
      "type": "local",
      "command": ["node", "/usr/lib/node_modules/hemisphere/index.js"],
      "enabled": true
    }
  }
}

Find your exact path with npm root -g — append /hemisphere/index.js. For nvm users the path is typically ~/.nvm/versions/node/vX/lib/node_modules/hemisphere/index.js.

Memories are scoped by project — searching under my-plugin won't return memories from my-theme.

Updating

npm

npm install -g hemisphere@latest

Then restart your MCP client (or OpenCode) to pick up the new tools, and run hemisphere restart to refresh the dashboard.

Database migrations run automatically on first launch — no manual steps required.

From git

cd ~/hemisphere
git pull
npm install
npm link
hemisphere restart

MCP Tools (Agent-Facing)

memory_store

Store a memory observation with hybrid FTS+vector indexing.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace | | content | string | yes | — | Memory text | | kind | string | no | note | Category label (fact, decision, bug, note, plan) | | status | string | no | — | Lifecycle status (e.g. pending, completed, approved) | | related_ids | number[] | no | — | IDs of related memories for relationship tracking | | metadata | object | no | {} | Kind-based schema — see Metadata below |

memory_search

Hybrid FTS + vector search with weighted scoring.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | no | — | Project to search within. Omit for cross-project search. | | query | string | yes | — | Search text | | limit | number | no | 10 | Max results | | alpha | number | no | 0.3 | Vector weight. 0 = FTS-only, 1 = vector-only | | archived | boolean | no | false | If true, search archived memories instead of active ones |

Returns memories sorted by relevance (score 0–1).

memory_context

Same as memory_search but returns plain text formatted for prompt injection.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | no | — | Project to search within. Omit for cross-project search. | | query | string | yes | — | Search text | | limit | number | no | 10 | Max results | | archived | boolean | no | false | If true, search archived memories instead of active ones |

Output:

[1] (fact) The application uses Node.js with Express framework
[2] (decision) Use pg-bouncer for connection pooling

memory_list

List recent memories, optionally filtered by kind.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace | | kind | string | no | — | Optional kind filter | | trash | boolean | no | false | If true, list soft-deleted memories | | archived | boolean | no | false | If true, list archived memories | | limit | number | no | 20 | Max results |

memory_trash

Soft-delete a memory by ID (scoped to project). Sets deleted_at; recoverable via memory_restore.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace | | id | number | yes | — | Memory ID to soft-delete |

memory_delete (deprecated)

DEPRECATED: Use memory_trash instead. Will be removed in v3.0.0.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace | | id | number | yes | — | Memory ID to delete |

memory_update

Update an existing memory by ID (scoped to project). Pass only the fields to change. Content updates automatically re-index FTS + vector.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | id | number | yes | — | Memory ID to update | | project | string | yes | — | Project namespace for scoping | | kind | string | no | — | New category label | | content | string | no | — | New content text | | related_ids | number[] | no | — | New set of related memory IDs | | status | string | no | — | New lifecycle status | | metadata | object | no | — | Fields to merge (existing metadata merged, re-normalized to kind schema, updated_at auto-bumped) |

Returns { updated: true/false }.

memory_purge

Permanently delete a memory by ID. By default requires the memory to be in trash first.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace | | id | number | yes | — | Memory ID to permanently delete | | force | boolean | no | false | Bypass trash requirement |

memory_restore

Restore a soft-deleted memory from trash.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace | | id | number | yes | — | Memory ID to restore |

memory_archive

Archive a memory by ID. Sets archived_at; archived memories are excluded from default list/search/context. Restorable via memory_unarchive.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace | | id | number | yes | — | Memory ID to archive |

memory_unarchive

Restore an archived memory back to active.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace | | id | number | yes | — | Memory ID to unarchive |

memory_reassign

Move memories from one project to another.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | from_project | string | yes | — | Source project namespace | | to_project | string | yes | — | Destination project namespace | | ids | number[] | no | — | Specific IDs to move. Omit to move all. |

project_list

List all project namespaces with stored memories. No parameters.

project_count

Count non-trashed memories in a project, grouped by kind.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace |

Returns { total: N, kind1: N1, kind2: N2, ... }.

project_trash

Soft-delete all non-trashed memories in a project (recoverable). Refuses if the project already has no non-trashed memories.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace to trash |

project_purge

Permanently delete a project and all its memories.

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace to purge | | force | boolean | no | false | Bypass trash requirement |

memory_progressive_summary

Returns structured memory data for progressive summarization. Uses a dual threshold trigger: returns fresh data when context pressure ≤ 20% OR ≥ 10 turns have passed since the last summary. Returns the last summary when still current. The agent must synthesize a concise summary (200-500 words, organized as State / Recent Decisions / Pending / Next) from the structured data, inject the synthesized summary into its prompt, and persist it via memory_store with kind: "progressive_summary".

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | project | string | yes | — | Project namespace | | turns_since_last | number | no | 0 | Conversation turns since last summary (agent tracks) | | context_remaining_pct | number | no | 100 | Percentage of context window free (agent tracks, e.g. 40 = 40% free) |

Returns either:

  • Fresh data{ needs_store: true, memories: [{id, kind, status, title}], synthesis_template: "...", trigger: "turn_interval"|"context_pressure" } → agent synthesizes a summary from the structured data and stores it
  • Current summary{ up_to_date: true, content: "...", summary_id: N } → use the existing summary

HTTP API (Dashboard-Facing)

The dashboard exposes REST endpoints:

| Method | Path | Description | |--------|------|-------------| | GET | /api/projects | List distinct project namespaces | | GET | /api/stats | Get counts grouped by kind and project | | GET | /api/memories?project=&kind=&limit=&offset=&trash= | Paginated memory list. trash=1 shows soft-deleted. search= triggers FTS+vector. | | POST | /api/notify | SSE event relay from MCP server (internal — called by notifyDash()) | | DELETE | /api/memories/:id?project= | Soft-delete a memory (sets deleted_at) | | POST | /api/memories/:id/restore?project= | Restore a soft-deleted memory (clears deleted_at) | | DELETE | /api/memories/:id/purge?project=&force= | Permanently delete a memory | | POST | /api/memories/:id/archive?project= | Archive a memory | | POST | /api/memories/:id/unarchive?project= | Unarchive a memory | | POST | /api/project/trash?project= | Trash all memories in a project | | DELETE | /api/project/purge?project=&force= | Permanently delete a project | | POST | /api/reassign?from=&to=&ids= | Move memories between projects | | POST | /api/purge?days= | Permanently delete memories soft-deleted longer than days ago (default from config or 30) | | GET | /api/backups | List backup .db files | | POST | /api/backups | Trigger an on-demand backup | | GET | /api/events | SSE (Server-Sent Events) stream for real-time dashboard updates |

Metadata

Metadata is auto-populated with created_at and updated_at Unix timestamps on every store and update. Each kind has a schema with defaults filled automatically:

| Kind | Required fields | Defaults | |------|----------------|----------| | fact | — | — | | decision | status | files: [], rationale: "" | | bug | status | severity: "minor", files: [] | | plan | status | files: [], steps: [] | | note | — | tags: [], cwd: "" | | progressive_summary | — | — |

Status values:

  • decision: proposedapprovedrejectedimplementedsuperseded
  • bug: openin_progressfixedwont_fixcant_repro
  • plan: pendingin_progresscompletedcancelled

Invalid status values reset to the kind default. Extraneous keys are preserved.

Configuration

Config File (~/.hemisphere/config.json)

Create an optional JSON config file to customize operational settings. All keys are optional — missing keys use the code defaults.

Priority chain (highest wins): code defaults < config file < environment variables.

{
  "port": 3456,
  "dbPath": "~/.hemisphere/memories.db",
  "backup": {
    "dir": "./backups",
    "intervalWrites": 50,
    "retentionCount": 10
  },
  "retention": {
    "days": {
      "note": 30,
      "plan": 180,
      "decision": 0,
      "fact": 0,
      "bug": 180,
      "progressive_summary": 90
    },
    "trashPurgeDays": 30
  },
  "search": {
    "limit": 10,
    "alpha": 0.3
  },
  "list": {
    "limit": 20
  },
  "summary": {
    "turnThreshold": 10,
    "contextThreshold": 20,
    "recentLimit": 50
  },
  "dashboard": {
    "paginationLimit": 50,
    "maxLimit": 200
  }
}

| Key | Type | Default | Description | |-----|------|---------|-------------| | port | number | 3456 | Dashboard HTTP server port | | dbPath | string | ~/.hemisphere/memories.db | SQLite database file path (supports ~) | | backup.dir | string | ./backups | Directory for automatic backup .db files | | backup.intervalWrites | number | 50 | Write operations before auto-backup triggers | | backup.retentionCount | number | 10 | Maximum backup files kept on disk | | retention.days.* | number | varies by kind | Days before automatic expiry. 0 = forever. | | retention.trashPurgeDays | number | 30 | Days before soft-deleted memories are permanently purged | | search.limit | number | 10 | Default search result count | | search.alpha | number | 0.3 | Vector weight in hybrid search (0–1) | | list.limit | number | 20 | Default list result count | | summary.turnThreshold | number | 10 | Turns since last summary before new one triggered | | summary.contextThreshold | number | 20 | Context free % at or below which summary triggered | | summary.recentLimit | number | 50 | Max memories retrieved for summary context | | dashboard.paginationLimit | number | 50 | Default page size for dashboard API | | dashboard.maxLimit | number | 200 | Hard cap on API page size |

Environment Variables

Environment variables override the config file and code defaults at the highest priority. Set any of these to skip the config file entirely:

| Variable | Equivalent Config Key | Default | |----------|----------------------|---------| | HEMISPHERE_PORT | port | 3456 | | HEMISPHERE_DB_PATH | dbPath | ~/.hemisphere/memories.db | | BACKUP_DIR | backup.dir | ./backups | | BACKUP_INTERVAL_WRITES | backup.intervalWrites | 50 | | BACKUP_RETENTION_COUNT | backup.retentionCount | 10 | | RETENTION_POLICY | retention.days | {"note":30,...} |

RETENTION_POLICY accepts a JSON object like {"note": 15, "bug": 365}. Values can be "forever", 0, or "0" for indefinite retention. Missing kinds inherit from defaults.

Architecture

Embedding

MurmurHash3 (32-bit x86) applied to word unigrams and bigrams, accumulated into a 256-bin Float32Array, then L2-normalized. Runs in ~2µs per text with no external dependencies. Collisions are inherent to feature hashing and don't materially affect retrieval quality at this dimension count.

Hybrid Search

Three indexing layers work together:

  1. FTS5 — SQLite full-text search with BM25 ranking (unicode61 tokenizer)
  2. Vectorsqlite-vec virtual table with 256-dim float vectors, cosine distance
  3. Merge — Weighted combination: score = alpha * vec + (1 - alpha) * fts. Both sides are normalized to 0–1 before merging.

Database

  • WAL journal mode for concurrent reads, busy_timeout=5000ms for write-contention safety across processes
  • memories table (project, kind, content, metadata, related_ids, status, created_at, updated_at, deleted_at, archived_at)
  • memories_fts — FTS5 external content table
  • memories_vecvec0 table with float[256]

Memory Lifecycle

Memories move through four tiers:

| Tier | Column | Filter | Tools | |------|--------|--------|-------| | Active | deleted_at IS NULL AND archived_at IS NULL | Default | memory_store, memory_search, memory_list, memory_update | | Archived | archived_at IS NOT NULL AND deleted_at IS NULL | archived=true | memory_archive, memory_unarchive, search/list/context with archived=true | | Trashed | deleted_at IS NOT NULL | trash=true | memory_trash, memory_restore, list with trash=true | | Purged | Row deleted | — | memory_purge |

Protection layers:

  1. Archive (archived_at) — memory_archive preserves memories with historical value outside the active set. Not subject to retention auto-purge. Restorable to active via memory_unarchive.
  2. Soft-Delete (deleted_at) — memory_trash sets a timestamp instead of removing the row. Memories are hidden from normal queries but recoverable via restore.
  3. Write-Count Snapshots — Every N writes (configurable via backup.intervalWrites), VACUUM INTO creates a timestamped .db backup.
  4. Per-Kind RetentionenforceLiveRetention() purges active memories older than their kind's configured days. At least one progressive_summary is always preserved.

Soft-deleted memories are permanently purged after retention.trashPurgeDays days (default 30) via /api/purge or the dashboard's hourly auto-purge. Archived memories are not subject to retention auto-purge.

SSE Real-Time Updates

The dashboard uses Server-Sent Events for live updates — no polling. Events flow through a three-hop chain:

MCP server (index.js) → notifyDash() → dashboard /api/notifybroadcast() → SSE clients → app.js listeners

| Event | Source | Frontend Behavior | |---|---|---| | memory_new | memory_store | Toast + reload list | | memory_update | memory_update | Reload if row visible | | memory_trash | memory_trash | Animate row removal | | memory_purge | memory_purge | Animate row removal | | memory_restore | memory_restore | Remove from trash / reload active | | memory_archive | memory_archive | Remove from active / reload archived | | memory_unarchive | memory_unarchive | Remove from archived / reload active | | memory_reassign | memory_reassign | Reload list + projects if affected | | project_new | memory_store, memory_reassign | Refresh project dropdown | | project_deleted | project_purge, memory_purge, memory_reassign | Clear current project + refresh dropdown | | project_trash | project_trash | Reload list if affected | | project_purge | project_purge | Clear current project + refresh dropdown |

If the SSE connection drops, a 30-second fallback poll resumes.

Project Structure

hemisphere/
├── index.js                MCP stdio server (9 tool handlers)
├── dashboard.js            HTTP dashboard + SSE broadcast server
├── dashboard/
│   ├── api-handler.js      Dashboard REST API routes
│   └── public/
│       ├── index.html      Dashboard HTML + ARIA structure
│       ├── style.css       Full theme (dark/light), toast, dialog, skeleton
│       └── app.js          Frontend SSE client, keyboard nav, WCAG 2.1 AA
├── db.js                   SQLite init, CRUD, hybrid FTS+vec search, lifecycle
├── embedding.js            MurmurHash3 → 256-dim float vector
├── config.js               Config loader (defaults ← config.json ← env vars)
├── package.json
└── README.md

Development

Scripts

For local development (after git clone):

npm start           # Start dashboard server (same as `hemisphere`)
npm run stop        # Stop running instance (same as `hemisphere stop`)
npm run restart     # Stop then start

For installed users, the global hemisphere CLI handles these — see Quick Start.

Testing the MCP server

Test with the MCP Inspector:

npx @modelcontextprotocol/inspector node index.js

Roadmap

  • Auth / access control — API keys for multi-client deployments
  • PostgreSQL backend — configurable backend for serverless and production
  • Connection pooling — WAL-mode write queue for concurrent agent access
  • Rate limiting / resource governance — per-project limits and content caps
  • Multi-agent collaboration — shared memory spaces with access controls
  • Import / export — portable memory archives across Hemisphere instances

Contributing

Open an issue or PR at github.com/hectorjarquin/hemisphere.

License

GNU General Public License v3.0 or later — see LICENSE for details.