tobi/qmdPublic

mini cli search engine for your docs, knowledge bases, meeting notes, whatever. Tracking current sota approaches while being all local

AI summary: A local-first, minimal CLI search engine for querying documentation and personal knowledge bases.

Stars
30.2K
+53 today
Forks
1.9K
Watchers
105
Open issues
99
Open PRs
86
Contributors
~92
Commits
692
Branches
43

TypeScriptMITCreated Dec 8, 2025Last push 2d agoLatest release v2.8.3+158 stars this week+687 this month

Quick answers

What is qmd?
A local-first, minimal CLI search engine for querying documentation and personal knowledge bases.
What does qmd do?
qmd is a lightweight command-line tool that indexes and searches local text files, documentation, and meeting notes without relying on cloud services. It brings state-of-the-art embedding and retrieval techniques directly to the developer's terminal, prioritizing speed and privacy. The technical approach involves running embedding models locally to generate vector representations of documents, allowing for semantic search rather than just keyword matching. What's distinctive is its uncompromising focus on being entirely local and minimal, rejecting the bloat of traditional enterprise search tools. It provides a fast, privacy-respecting way to query sprawling personal knowledge bases directly from the command line.
Who is qmd for?
This tool is built for developers, researchers, and power users who maintain large local repositories of text and prefer terminal-based workflows. It requires basic familiarity with command-line interfaces and local environment setup.
How do I get started with qmd?
npm install -g qmd-cli && qmd index ./docs
How popular is qmd on GitHub?
tobi/qmd has 30,168 stars and 1,883 forks on GitHub, and gained 158 stars in the last 7 days.
What license does qmd use?
tobi/qmd is released under the MIT license.

Star history

since Jul 29, 2026
010K20K30KJul 2026Aug 2026Sep 2026Oct 2026
30.2K stars as of Oct 3, 2026. Measured daily since Jul 29, 2026; GitHub no longer exposes earlier star timestamps.

Contribution activity

commits per day, last 52 weeks
OctNovDecJanFebMarAprMayJunJulAugSepMonWedFri2025-10-04: 0 commits2025-10-05: 0 commits2025-10-06: 0 commits2025-10-07: 0 commits2025-10-08: 0 commits2025-10-09: 0 commits2025-10-10: 0 commits2025-10-11: 0 commits2025-10-12: 0 commits2025-10-13: 0 commits2025-10-14: 0 commits2025-10-15: 0 commits2025-10-16: 0 commits2025-10-17: 0 commits2025-10-18: 0 commits2025-10-19: 0 commits2025-10-20: 0 commits2025-10-21: 0 commits2025-10-22: 0 commits2025-10-23: 0 commits2025-10-24: 0 commits2025-10-25: 0 commits2025-10-26: 0 commits2025-10-27: 0 commits2025-10-28: 0 commits2025-10-29: 0 commits2025-10-30: 0 commits2025-10-31: 0 commits2025-11-01: 0 commits2025-11-02: 0 commits2025-11-03: 0 commits2025-11-04: 0 commits2025-11-05: 0 commits2025-11-06: 0 commits2025-11-07: 0 commits2025-11-09: 0 commits2025-11-10: 0 commits2025-11-11: 0 commits2025-11-12: 0 commits2025-11-13: 0 commits2025-11-14: 0 commits2025-11-15: 0 commits2025-11-16: 0 commits2025-11-17: 0 commits2025-11-18: 0 commits2025-11-19: 0 commits2025-11-20: 0 commits2025-11-21: 0 commits2025-11-22: 0 commits2025-11-23: 0 commits2025-11-24: 0 commits2025-11-25: 0 commits2025-11-26: 0 commits2025-11-27: 0 commits2025-11-28: 0 commits2025-11-29: 0 commits2025-11-30: 0 commits2025-12-01: 0 commits2025-12-02: 0 commits2025-12-03: 0 commits2025-12-04: 0 commits2025-12-05: 0 commits2025-12-06: 0 commits2025-12-07: 3 commits2025-12-08: 8 commits2025-12-09: 2 commits2025-12-10: 1 commit2025-12-11: 0 commits2025-12-12: 26 commits2025-12-13: 33 commits2025-12-14: 1 commit2025-12-15: 0 commits2025-12-16: 3 commits2025-12-17: 0 commits2025-12-18: 0 commits2025-12-19: 0 commits2025-12-20: 5 commits2025-12-21: 15 commits2025-12-22: 0 commits2025-12-23: 0 commits2025-12-24: 0 commits2025-12-25: 0 commits2025-12-26: 0 commits2025-12-27: 0 commits2025-12-28: 0 commits2025-12-29: 0 commits2025-12-30: 0 commits2025-12-31: 1 commit2026-01-01: 0 commits2026-01-02: 0 commits2026-01-03: 1 commit2026-01-04: 2 commits2026-01-05: 0 commits2026-01-06: 0 commits2026-01-07: 0 commits2026-01-08: 2 commits2026-01-09: 2 commits2026-01-10: 0 commits2026-01-11: 3 commits2026-01-12: 0 commits2026-01-13: 1 commit2026-01-14: 0 commits2026-01-15: 0 commits2026-01-16: 0 commits2026-01-17: 0 commits2026-01-18: 0 commits2026-01-19: 1 commit2026-01-20: 3 commits2026-01-21: 0 commits2026-01-22: 0 commits2026-01-23: 3 commits2026-01-24: 4 commits2026-01-25: 15 commits2026-01-26: 2 commits2026-01-27: 1 commit2026-01-28: 8 commits2026-01-29: 0 commits2026-01-30: 2 commits2026-01-31: 17 commits2026-02-01: 4 commits2026-02-02: 1 commit2026-02-03: 0 commits2026-02-04: 0 commits2026-02-05: 0 commits2026-02-06: 0 commits2026-02-07: 0 commits2026-02-08: 0 commits2026-02-09: 0 commits2026-02-10: 1 commit2026-02-11: 0 commits2026-02-12: 0 commits2026-02-13: 7 commits2026-02-14: 1 commit2026-02-15: 25 commits2026-02-16: 13 commits2026-02-17: 5 commits2026-02-18: 15 commits2026-02-19: 12 commits2026-02-20: 6 commits2026-02-21: 1 commit2026-02-22: 8 commits2026-02-23: 1 commit2026-02-24: 2 commits2026-02-25: 0 commits2026-02-26: 0 commits2026-02-27: 0 commits2026-02-28: 0 commits2026-03-01: 1 commit2026-03-02: 0 commits2026-03-03: 1 commit2026-03-04: 0 commits2026-03-05: 1 commit2026-03-06: 8 commits2026-03-07: 9 commits2026-03-08: 1 commit2026-03-09: 2 commits2026-03-10: 13 commits2026-03-11: 4 commits2026-03-12: 3 commits2026-03-13: 7 commits2026-03-14: 0 commits2026-03-15: 0 commits2026-03-16: 0 commits2026-03-17: 0 commits2026-03-18: 0 commits2026-03-19: 0 commits2026-03-20: 0 commits2026-03-21: 1 commit2026-03-22: 1 commit2026-03-23: 2 commits2026-03-24: 5 commits2026-03-25: 0 commits2026-03-26: 3 commits2026-03-27: 2 commits2026-03-28: 1 commit2026-03-29: 1 commit2026-03-30: 0 commits2026-03-31: 0 commits2026-04-01: 0 commits2026-04-02: 0 commits2026-04-03: 6 commits2026-04-04: 0 commits2026-04-05: 30 commits2026-04-06: 1 commit2026-04-07: 4 commits2026-04-08: 1 commit2026-04-09: 4 commits2026-04-10: 1 commit2026-04-11: 3 commits2026-04-12: 0 commits2026-04-13: 0 commits2026-04-14: 1 commit2026-04-15: 0 commits2026-04-16: 0 commits2026-04-17: 0 commits2026-04-18: 0 commits2026-04-19: 0 commits2026-04-20: 0 commits2026-04-21: 1 commit2026-04-22: 0 commits2026-04-23: 2 commits2026-04-24: 0 commits2026-04-25: 0 commits2026-04-26: 0 commits2026-04-27: 0 commits2026-04-28: 0 commits2026-04-29: 0 commits2026-04-30: 0 commits2026-05-01: 0 commits2026-05-02: 0 commits2026-05-03: 0 commits2026-05-04: 0 commits2026-05-05: 0 commits2026-05-06: 0 commits2026-05-07: 0 commits2026-05-08: 0 commits2026-05-09: 18 commits2026-05-10: 0 commits2026-05-11: 1 commit2026-05-12: 0 commits2026-05-13: 2 commits2026-05-14: 0 commits2026-05-15: 1 commit2026-05-16: 13 commits2026-05-17: 0 commits2026-05-18: 3 commits2026-05-19: 10 commits2026-05-20: 5 commits2026-05-21: 0 commits2026-05-22: 5 commits2026-05-23: 0 commits2026-05-24: 1 commit2026-05-25: 1 commit2026-05-26: 0 commits2026-05-27: 0 commits2026-05-28: 8 commits2026-05-29: 0 commits2026-05-30: 0 commits2026-05-31: 1 commit2026-06-01: 2 commits2026-06-02: 0 commits2026-06-03: 0 commits2026-06-04: 0 commits2026-06-05: 1 commit2026-06-06: 0 commits2026-06-07: 1 commit2026-06-08: 1 commit2026-06-09: 1 commit2026-06-10: 0 commits2026-06-11: 1 commit2026-06-12: 2 commits2026-06-13: 1 commit2026-06-14: 2 commits2026-06-15: 0 commits2026-06-16: 0 commits2026-06-17: 1 commit2026-06-18: 0 commits2026-06-19: 0 commits2026-06-20: 1 commit2026-06-21: 0 commits2026-06-22: 1 commit2026-06-23: 0 commits2026-06-24: 2 commits2026-06-25: 0 commits2026-06-26: 0 commits2026-06-27: 0 commits2026-06-28: 0 commits2026-06-29: 0 commits2026-06-30: 0 commits2026-07-01: 0 commits2026-07-02: 1 commit2026-07-03: 0 commits2026-07-04: 0 commits2026-07-05: 0 commits2026-07-06: 0 commits2026-07-07: 0 commits2026-07-08: 0 commits2026-07-09: 0 commits2026-07-10: 0 commits2026-07-11: 0 commits2026-07-12: 0 commits2026-07-13: 2 commits2026-07-14: 0 commits2026-07-15: 0 commits2026-07-16: 0 commits2026-07-17: 0 commits2026-07-18: 0 commits2026-07-19: 0 commits2026-07-20: 1 commit2026-07-21: 0 commits2026-07-22: 0 commits2026-07-23: 0 commits2026-07-24: 4 commits2026-07-25: 0 commits2026-07-26: 2 commits2026-07-27: 0 commits2026-07-28: 0 commits2026-07-29: 0 commits2026-07-30: 0 commits2026-07-31: 0 commits2026-08-01: 0 commits2026-08-02: 0 commits2026-08-03: 0 commits2026-08-04: 0 commits2026-08-05: 1 commit2026-08-06: 1 commit2026-08-07: 0 commits2026-08-08: 0 commits2026-08-09: 0 commits2026-08-10: 0 commits2026-08-11: 1 commit2026-08-12: 23 commits2026-08-13: 27 commits2026-08-14: 1 commit2026-08-15: 5 commits2026-08-16: 2 commits2026-08-17: 2 commits2026-08-18: 1 commit2026-08-19: 0 commits2026-08-20: 0 commits2026-08-21: 0 commits2026-08-22: 1 commit2026-08-23: 0 commits2026-08-24: 0 commits2026-08-25: 0 commits2026-08-26: 0 commits2026-08-27: 2 commits2026-08-28: 0 commits2026-08-29: 0 commits2026-08-30: 1 commit2026-08-31: 0 commits2026-09-01: 0 commits2026-09-02: 0 commits2026-09-03: 0 commits2026-09-04: 1 commit2026-09-05: 0 commits2026-09-06: 0 commits2026-09-07: 0 commits2026-09-08: 0 commits2026-09-09: 3 commits2026-09-10: 0 commits2026-09-11: 0 commits2026-09-12: 0 commits2026-09-13: 0 commits2026-09-14: 0 commits2026-09-15: 0 commits2026-09-16: 0 commits2026-09-17: 0 commits2026-09-18: 0 commits2026-09-19: 0 commits2026-09-20: 0 commits2026-09-21: 0 commits2026-09-22: 0 commits2026-09-23: 0 commits2026-09-24: 0 commits2026-09-25: 0 commits2026-09-26: 0 commits2026-09-27: 0 commits2026-09-28: 0 commits2026-09-29: 0 commits2026-09-30: 0 commits2026-10-01: 0 commits2026-10-02: 0 commits2026-10-03: 0 commits
555 commits in the last yearLessMore

Signals and awards

derived from tracked data
  • Widely adopted

    30,168 stars

  • Very active

    555 commits in 52 weeks

  • Permissive license

    MIT

  • Continuous integration

    Automated checks passing

  • Repeat trending

    5 trending appearances

What qmd does

qmd is a lightweight command-line tool that indexes and searches local text files, documentation, and meeting notes without relying on cloud services. It brings state-of-the-art embedding and retrieval techniques directly to the developer's terminal, prioritizing speed and privacy. The technical approach involves running embedding models locally to generate vector representations of documents, allowing for semantic search rather than just keyword matching. What's distinctive is its uncompromising focus on being entirely local and minimal, rejecting the bloat of traditional enterprise search tools. It provides a fast, privacy-respecting way to query sprawling personal knowledge bases directly from the command line.

This tool is built for developers, researchers, and power users who maintain large local repositories of text and prefer terminal-based workflows. It requires basic familiarity with command-line interfaces and local environment setup.

  • Local Semantic Search: utilizes local embedding models to understand the context of queries rather than just matching exact keywords.
  • Zero Cloud Dependency: ensures complete privacy by performing all indexing and searching directly on the user's hardware.
  • Minimalist CLI Interface: integrates seamlessly into existing terminal workflows without requiring a heavy graphical interface.
  • State-of-the-Art Retrieval: implements modern vector search techniques to surface highly relevant documents quickly.
  • Broad Format Support: parses and indexes various text formats including Markdown, meeting notes, and raw code files.

Where teams use it

Personal Knowledge Management

Developers use it to instantly locate specific code snippets or architectural decisions hidden deep within their local notes.

Offline Documentation Search

Engineers working in air-gapped environments or while traveling use it to query large documentation sets without internet access.

Meeting Note Retrieval

Project managers quickly surface historical context by semantically searching years of plain-text meeting minutes.

Terminal-Integrated Workflows

Power users integrate the tool into shell scripts or aliases to automate information retrieval during active development.

Getting started: npm install -g qmd-cli && qmd index ./docs

README

main branch

QMD - Query Markup Documents

An on-device search engine for everything you need to remember. Index your markdown notes, meeting transcripts, documentation, and knowledge bases. Search with keywords or natural language. Ideal for your agentic flows.

QMD combines BM25 full-text search, vector semantic search, and LLM re-ranking—all running locally via node-llama-cpp with GGUF models.

flowchart LR
  Q[User Query] --> X[Query Expansion]
  Q --> FTS[BM25 Search]
  Q --> VS[Vector Search]
  X --> HYDE[HyDE]
  X --> VEC[Vec dense sentences]
  X --> LEX[Lex BM25 keywords]
  HYDE --> VS
  VEC --> VS
  LEX --> FTS
  VS --> RRF[Reciprocal Rank Fusion]
  FTS --> RRF
  RRF --> RR[LLM Reranker]
  RR --> OUT[Final ranked results]
Loading

Typed expansions are routed exclusively: lex → BM25/FTS, vec and hyde → vector search. The original query is sent to both backends, then fused with RRF and reranked.

You can read more about QMD's progress in the CHANGELOG.

Quick Start

# Install globally (Node or Bun)
npm install -g @tobilu/qmd
# or
bun install -g @tobilu/qmd

# Or run directly
npx @tobilu/qmd ...
bunx @tobilu/qmd ...

# Create collections for your notes, docs, and meeting transcripts
qmd collection add ~/notes --name notes
qmd collection add ~/Documents/meetings --name meetings
qmd collection add ~/work/docs --name docs

# Add context to help with search results, each piece of context will be returned when matching sub documents are returned. This works as a tree. This is the key feature of QMD as it allows LLMs to make much better contextual choices when selecting documents. Don't sleep on it!
qmd context add qmd://notes "Personal notes and ideas"
qmd context add qmd://meetings "Meeting transcripts and notes"
qmd context add qmd://docs "Work documentation"

# Generate embeddings for semantic search
qmd embed

# Search across everything
qmd search "project timeline"           # Fast keyword search
qmd vsearch "how to deploy"             # Semantic search
qmd query "quarterly planning process"  # Hybrid + reranking (best quality)

# Get a specific document
qmd get "meetings/2024-01-15.md"

# Get a document by docid (shown in search results)
qmd get "#abc123"

# Get multiple documents by glob pattern
qmd multi-get "journals/2025-05*.md"

# Search within a specific collection
qmd search "API" -c notes

# Export all matches for an agent
qmd search "API" --all --files --min-score 0.3

Using with AI Agents

QMD's --json and --files output formats are designed for agentic workflows:

# Get structured results for an LLM
qmd search "authentication" --json -n 10

# List all relevant files above a threshold
qmd query "error handling" --all --files --min-score 0.4

# Retrieve full document content
qmd get "docs/api-reference.md" --full

MCP Server

Although the tool works perfectly fine when you just tell your agent to use it on the command line, it also exposes an MCP (Model Context Protocol) server for tighter integration.

Tools exposed:

  • query — Search with typed sub-queries (lex/vec/hyde), combined via RRF + reranking
  • get — Retrieve a document by path or docid (with fuzzy matching suggestions)
  • multi_get — Batch retrieve by glob pattern, comma-separated list, or docids
  • status — Index health and collection info

Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "qmd": {
      "command": "qmd",
      "args": ["mcp"]
    }
  }
}

Claude Code — Install the plugin (recommended):

claude plugin marketplace add tobi/qmd
claude plugin install qmd@qmd

Or configure MCP manually in ~/.claude/settings.json:

{
  "mcpServers": {
    "qmd": {
      "command": "qmd",
      "args": ["mcp"]
    }
  }
}
HTTP Transport

By default, QMD's MCP server uses stdio (launched as a subprocess by each client). For a shared, long-lived server that avoids repeated model loading, use the HTTP transport:

# Foreground (Ctrl-C to stop)
qmd mcp --http                    # localhost:8181
qmd mcp --http --port 8080        # custom port
qmd mcp --http --host 0.0.0.0     # bind all interfaces (e.g. container probes)

# Background daemon
qmd mcp --http --daemon           # start, writes PID to ~/.cache/qmd/mcp.pid
qmd mcp stop                      # stop via PID file
qmd status                        # shows "MCP: running (PID ...)" when active

The server binds to localhost by default. Pass --host (or set the QMD_HOST environment variable) to override — --host 0.0.0.0 is useful when the server runs in a container and a liveness probe connects from a non-loopback address.

The HTTP server exposes two endpoints:

  • POST /mcp — MCP Streamable HTTP (JSON responses, stateless)
  • POST /query (alias /search) — structured search without the MCP protocol. Accepts the same optional filter object as the query tool (invalid filters return 400); see Metadata Filtering
  • GET /health — liveness check with uptime
Origin and Host validation

Every request is screened before routing: a request carrying an Origin header that does not name a loopback address is rejected with 403, as is a Host header naming something other than the address the server is bound to. This is what stops a web page you visit from reading your index through DNS rebinding — loopback binding alone does not, since the browser makes the request from your own machine.

Requests without an Origin header — curl, MCP clients, editors — are unaffected, which covers every normal local client.

Variable Effect
QMD_ALLOWED_ORIGINS Comma-separated origins to accept in addition to loopback, e.g. https://notes.internal. Set to * to disable the check entirely.
QMD_ALLOWED_HOSTS Comma-separated Host values to accept in addition to loopback and the bind address.

--host 0.0.0.0 cannot know which Host values are legitimate, so it skips the host check and warns at startup. Set QMD_ALLOWED_HOSTS to re-enable it, and remember the endpoints are unauthenticated — put your own auth in front of a server that is reachable off-host.

LLM models stay loaded in VRAM across requests. Embedding/reranking contexts are disposed after 5 min idle and transparently recreated on the next request (~1s penalty, models remain loaded).

Point any MCP client at http://localhost:8181/mcp to connect.

MCP Tool Parameters
Tool Parameter Type Notes
query searches array Typed sub-queries (lex/vec/hyde), 1–10. Required. First gets 2x weight.
query collections string[] Filter by collection names (OR). Array only — singular collection is silently ignored.
query filter object Metadata filter (recursive operator-discriminated JSON AST; see Metadata Filtering)
query intent string Disambiguation context (does not search on its own)
query limit number Max results (default 10)
query minScore number Minimum relevance 0–1 (default 0)
query candidateLimit number Max candidates to rerank (default 40)
query rerank boolean Run LLM reranking (default true); set false for RRF-only
get file string Path, docid (#abc123), or path:from:count (e.g. #abc123:120:40)
get fromLine number Start line (1-indexed); overrides the :from suffix
get maxLines number Limit returned lines
get lineNumbers boolean Prefix lines with numbers (default true)
multi_get pattern string Glob pattern or comma-separated list
multi_get maxBytes number Skip files larger than N (default 10240)
multi_get maxLines number Limit lines per file
multi_get lineNumbers boolean Prefix lines with numbers (default true)

Unknown parameters are silently ignored (not rejected) — double-check names if results seem unscoped. The HTTP /query and /search endpoints return qmd://collection/path URIs in the file field, matching the CLI and MCP output.

SDK / Library Usage

Use QMD as a library in your own Node.js or Bun applications.

Installation
npm install @tobilu/qmd
Quick Start
import { createStore } from '@tobilu/qmd'

const store = await createStore({
  dbPath: './my-index.sqlite',
  config: {
    collections: {
      docs: { path: '/path/to/docs', pattern: '**/*.md' },
    },
  },
})

const results = await store.search({ query: "authentication flow" })
console.log(results.map(r => `${r.title} (${Math.round(r.score * 100)}%)`))

await store.close()
Store Creation

createStore() accepts three modes:

import { createStore } from '@tobilu/qmd'

// 1. Inline config — no files needed besides the DB
const store = await createStore({
  dbPath: './index.sqlite',
  config: {
    collections: {
      docs: { path: '/path/to/docs', pattern: '**/*.md' },
      notes: { path: '/path/to/notes' },
    },
  },
})

// 2. YAML config file — collections defined in a file
const store2 = await createStore({
  dbPath: './index.sqlite',
  configPath: './qmd.yml',
})

// 3. DB-only — reopen a previously configured store
const store3 = await createStore({ dbPath: './index.sqlite' })
Search

The unified search() method handles both simple queries and pre-expanded structured queries:

// Simple query — auto-expanded via LLM, then BM25 + vector + reranking
const results = await store.search({ query: "authentication flow" })

// With options
const results2 = await store.search({
  query: "rate limiting",
  intent: "API throttling and abuse prevention",
  collection: "docs",
  limit: 5,
  minScore: 0.3,
  explain: true,
})

// Pre-expanded queries — skip auto-expansion, control each sub-query
const results3 = await store.search({
  queries: [
    { type: 'lex', query: '"connection pool" timeout -redis' },
    { type: 'vec', query: 'why do database connections time out under load' },
  ],
  collections: ["docs", "notes"],
})

// Skip reranking for faster results
const fast = await store.search({ query: "auth", rerank: false })

// Metadata filter — every returned result satisfies it (also available on
// searchLex() and searchVector()); results expose indexed metadata via
// r.metadata. See "Metadata Filtering" for the full grammar.
const published = await store.search({
  query: "authentication flow",
  filter: {
    operator: "and",
    operands: [
      { key: "topics", operator: "all", value: ["typescript"] },
      { key: "status", operator: "ne", value: "draft" },
    ],
  },
})

For direct backend access:

// BM25 keyword search (fast, no LLM)
const lexResults = await store.searchLex("auth middleware", { limit: 10 })

// Vector similarity search (embedding model, no reranking)
const vecResults = await store.searchVector("how users log in", { limit: 10 })

// Manual query expansion for full control
const expanded = await store.expandQuery("auth flow", { intent: "user login" })
const results4 = await store.search({ queries: expanded })
Retrieval
// Get a document by path or docid
const doc = await store.get("docs/readme.md")
const byId = await store.get("#abc123")

if (!("error" in doc)) {
  console.log(doc.title, doc.displayPath, doc.context)
}

// Get document body with line range
const body = await store.getDocumentBody("docs/readme.md", {
  fromLine: 50,
  maxLines: 100,
})

// Batch retrieve by glob or comma-separated list
const { docs, errors } = await store.multiGet("docs/**/*.md", {
  maxBytes: 20480,
})
Collections
// Add a collection
await store.addCollection("myapp", {
  path: "/src/myapp",
  pattern: "**/*.ts",
  ignore: ["node_modules/**", "*.test.ts"],
})

// List collections with document stats
const collections = await store.listCollections()
// => [{ name, pwd, glob_pattern, doc_count, active_count, last_modified, includeByDefault }]

// Get names of collections included in queries by default
const defaults = await store.getDefaultCollectionNames()

// Remove / rename
await store.removeCollection("myapp")
await store.renameCollection("old-name", "new-name")
Context

Context adds descriptive metadata that improves search relevance and is returned alongside results:

// Add context for a path within a collection
await store.addContext("docs", "/api", "REST API reference documentation")

// Set global context (applies to all collections)
await store.setGlobalContext("Internal engineering documentation")

// List all contexts
const contexts = await store.listContexts()
// => [{ collection, path, context }]

// Remove context
await store.removeContext("docs", "/api")
await store.setGlobalContext(undefined)  // clear global
Indexing
// Re-index collections by scanning the filesystem
const result = await store.update({
  collections: ["docs"],  // optional — defaults to all
  onProgress: ({ collection, file, current, total }) => {
    console.log(`[${collection}] ${current}/${total} ${file}`)
  },
})
// => { collections, indexed, updated, unchanged, removed, needsEmbedding }

// Generate vector embeddings
const embedResult = await store.embed({
  force: false,           // true to re-embed everything
  chunkStrategy: "auto",  // "regex" (default) or "auto" (AST for code files)
  onProgress: ({ current, total, collection }) => {
    console.log(`Embedding ${current}/${total}`)
  },
})
Types

Key types exported for SDK consumers:

import type {
  QMDStore,            // The store interface
  SearchOptions,       // Options for search()
  LexSearchOptions,    // Options for searchLex()
  VectorSearchOptions, // Options for searchVector()
  HybridQueryResult,   // Search result with score, snippet, context
  SearchResult,        // Result from searchLex/searchVector
  ExpandedQuery,       // Typed sub-query { type: 'lex'|'vec'|'hyde', query }
  DocumentResult,      // Document metadata + body
  DocumentNotFound,    // Error with similarFiles suggestions
  MultiGetResult,      // Batch retrieval result
  UpdateProgress,      // Progress callback info for update()
  UpdateResult,        // Aggregated update result
  EmbedProgress,       // Progress callback info for embed()
  EmbedResult,         // Embedding result
  StoreOptions,        // createStore() options
  CollectionConfig,    // Inline config shape
  IndexStatus,         // From getStatus()
  IndexHealthInfo,     // From getIndexHealth()
} from '@tobilu/qmd'

Utility exports:

import {
  extractSnippet,              // Extract a relevant snippet from text
  addLineNumbers,              // Add line numbers to text
  DEFAULT_MULTI_GET_MAX_BYTES, // Default max file size for multiGet (64KB)
  Maintenance,                 // Database maintenance operations
} from '@tobilu/qmd'
Lifecycle
// Close the store — disposes LLM models and DB connection
await store.close()

The SDK requires explicit dbPath — no defaults are assumed. This makes it safe to embed in any application without side effects.

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                         QMD Hybrid Search Pipeline                          │
└─────────────────────────────────────────────────────────────────────────────┘

                              ┌─────────────────┐
                              │   User Query    │
                              └────────┬────────┘
                                       │
                        ┌──────────────┴──────────────┐
                        ▼                             ▼
               ┌────────────────┐            ┌────────────────┐
               │ Query Expansion│            │  Original Query│
               │  (fine-tuned)  │            │   (×2 weight)  │
               └───────┬────────┘            └───────┬────────┘
                       │                             │
                       │ 2 alternative queries       │
                       └──────────────┬──────────────┘
                                      │
              ┌───────────────────────┼───────────────────────┐
              ▼                       ▼                       ▼
     ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
     │ Original Query  │     │ Expanded Query 1│     │ Expanded Query 2│
     └────────┬────────┘     └────────┬────────┘     └────────┬────────┘
              │                       │                       │
      ┌───────┴───────┐       ┌───────┴───────┐       ┌───────┴───────┐
      ▼               ▼       ▼               ▼       ▼               ▼
  ┌───────┐       ┌───────┐ ┌───────┐     ┌───────┐ ┌───────┐     ┌───────┐
  │ BM25  │       │Vector │ │ BM25  │     │Vector │ │ BM25  │     │Vector │
  │(FTS5) │       │Search │ │(FTS5) │     │Search │ │(FTS5) │     │Search │
  └───┬───┘       └───┬───┘ └───┬───┘     └───┬───┘ └───┬───┘     └───┬───┘
      │               │         │             │         │             │
      └───────┬───────┘         └──────┬──────┘         └──────┬──────┘
              │                        │                       │
              └────────────────────────┼───────────────────────┘
                                       │
                                       ▼
                          ┌───────────────────────┐
                          │   RRF Fusion + Bonus  │
                          │  Original query: ×2   │
                          │  Top-rank bonus: +0.05│
                          │     Top 30 Kept       │
                          └───────────┬───────────┘
                                      │
                                      ▼
                          ┌───────────────────────┐
                          │    LLM Re-ranking     │
                          │  (qwen3-reranker)     │
                          │  Yes/No + logprobs    │
                          └───────────┬───────────┘
                                      │
                                      ▼
                          ┌───────────────────────┐
                          │  Position-Aware Blend │
                          │  Top 1-3:  75% RRF    │
                          │  Top 4-10: 60% RRF    │
                          │  Top 11+:  40% RRF    │
                          └───────────────────────┘

Score Normalization & Fusion

Search Backends

Backend Raw Score Conversion Range
FTS (BM25) SQLite FTS5 BM25 Math.abs(score) 0 to ~25+
Vector Cosine distance 1 / (1 + distance) 0.0 to 1.0
Reranker LLM 0-10 rating score / 10 0.0 to 1.0

Fusion Strategy

The query command uses Reciprocal Rank Fusion (RRF) with position-aware blending:

  1. Query Expansion: Original query (×2 for weighting) + 1 LLM variation
  2. Parallel Retrieval: Each query searches both FTS and vector indexes
  3. RRF Fusion: Combine all result lists using score = Σ(1/(k+rank+1)) where k=60
  4. Top-Rank Bonus: Documents ranking #1 in any list get +0.05, #2-3 get +0.02
  5. Top-K Selection: Take top 30 candidates for reranking
  6. Re-ranking: LLM scores each document (yes/no with logprobs confidence)
  7. Position-Aware Blending:
    • RRF rank 1-3: 75% retrieval, 25% reranker (preserves exact matches)
    • RRF rank 4-10: 60% retrieval, 40% reranker
    • RRF rank 11+: 40% retrieval, 60% reranker (trust reranker more)

Why this approach: Pure RRF can dilute exact matches when expanded queries don't match. The top-rank bonus preserves documents that score #1 for the original query. Position-aware blending prevents the reranker from destroying high-confidence retrieval results.

Score Interpretation

Score Meaning
0.8 - 1.0 Highly relevant
0.5 - 0.8 Moderately relevant
0.2 - 0.5 Somewhat relevant
0.0 - 0.2 Low relevance

Requirements

System Requirements

  • Node.js >= 22
  • Bun >= 1.0.0
  • macOS: Homebrew SQLite (for extension support)
    brew install sqlite

GGUF Models (via node-llama-cpp)

QMD uses three local GGUF models (auto-downloaded on first use):

Model Purpose Size
embeddinggemma-300M-Q8_0 Vector embeddings (default) ~300MB
qwen3-reranker-0.6b-q8_0 Re-ranking ~640MB
qmd-query-expansion-1.7B-q4_k_m Query expansion (fine-tuned) ~1.1GB

Models are downloaded from HuggingFace and cached in ~/.cache/qmd/models/.

Custom Embedding Model

Override the default embedding model via the QMD_EMBED_MODEL environment variable. This is useful for multilingual corpora (e.g. Chinese, Japanese, Korean) where embeddinggemma-300M has limited coverage.

# Use Qwen3-Embedding-0.6B for better multilingual (CJK) support
export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"

# After changing the model, re-embed all collections:
qmd embed -f

Supported model families:

  • embeddinggemma (default) — English-optimized, small footprint
  • Qwen3-Embedding — Multilingual (119 languages including CJK), MTEB top-ranked

Note: When switching embedding models, you must re-index with qmd embed -f since vectors are not cross-compatible between models. The prompt format is automatically adjusted for each model family.

Installation

npm install -g @tobilu/qmd
# or
bun install -g @tobilu/qmd

Development

git clone https://github.com/tobi/qmd
cd qmd
npm install
npm link

Usage

Collection Management

# Create a collection from current directory
qmd collection add . --name myproject

# Create a collection with explicit path and custom glob mask
qmd collection add ~/Documents/notes --name notes --mask "**/*.md"

# Comma-separated masks are a union (brace form `{a,b}` also works)
qmd collection add ~/notes --name notes --mask "sources/**/*.md,CO - *.md"

# List all collections
qmd collection list

# Remove a collection
qmd collection remove myproject

# Rename a collection
qmd collection rename myproject my-project

# List files in a collection
qmd ls notes
qmd ls notes/subfolder

# Show collection details (path, glob mask, include status, context count)
qmd collection show notes

# Include or exclude a collection from default (unscoped) queries
qmd collection include notes
qmd collection exclude notes

# Run a command before every `qmd update` (e.g. git pull); empty arg clears it
qmd collection update-cmd notes 'git pull --rebase'
qmd collection update-cmd notes

Generate Vector Embeddings

# Embed all indexed documents (900 tokens/chunk, 15% overlap)
qmd embed

# Force re-embed everything
qmd embed -f

# Enable AST-aware chunking for code files (TS, JS, Python, Go, Rust)
qmd embed --chunk-strategy auto

# Also works with query for consistent chunk selection
qmd query "auth flow" --chunk-strategy auto

# Memory control for large corpora / constrained systems
qmd embed --max-docs-per-batch 50   # cap docs per embedding batch
qmd embed --max-batch-mb 64         # cap batch size in MB

AST-aware chunking (--chunk-strategy auto) uses tree-sitter to chunk code files at function, class, and import boundaries instead of arbitrary text positions. This produces higher-quality chunks and better search results for codebases. Markdown and other file types always use regex-based chunking regardless of strategy.

The default is regex (existing behavior). Use --chunk-strategy auto to opt in. Run qmd status to verify which grammars are available.

Note: Tree-sitter grammars are optional dependencies. If they are not installed, --chunk-strategy auto falls back to regex-only chunking automatically. Tested on both Node.js and Bun.

Context Management

Context adds descriptive metadata to collections and paths, helping search understand your content.

# Add context to a collection (using qmd:// virtual paths)
qmd context add qmd://notes "Personal notes and ideas"
qmd context add qmd://docs/api "API documentation"

# Add context from within a collection directory
cd ~/notes && qmd context add "Personal notes and ideas"
cd ~/notes/work && qmd context add "Work-related notes"

# Add global context (applies to all collections)
qmd context add / "Knowledge base for my projects"

# List all contexts
qmd context list

# Remove context
qmd context rm qmd://notes/old

Configuring index.yml

The collection and context commands above all read and write a single YAML config file — you can also edit it directly. Everything QMD knows about your collections (paths, masks, exclusions, per-collection update hooks, contexts, and optional model overrides) lives here. A fully-commented starter template ships as example-index.yml in this repo.

Location: ~/.config/qmd/index.yml by default. The directory honors XDG_CONFIG_HOME (→ $XDG_CONFIG_HOME/qmd/index.yml) and QMD_CONFIG_DIR. A named index uses {name}.yml — qmd --index work … reads/writes work.yml. A project-local index created with qmd init lives at .qmd/index.yml (.qmd/index.yaml is also accepted) alongside a project-local index.sqlite, so config and index stay inside the project instead of ~/.config / ~/.cache.

# ~/.config/qmd/index.yml

# Context applied to every collection (system-message style). Optional.
global_context: "Knowledge base for my projects"

# Terminal hyperlink template for search results. Optional.
# Overridden by the QMD_EDITOR_URI env var. See "Editor Links" below.
editor_uri: "vscode://file{path}:{line}:{col}"

# Override the default GGUF models per role. Optional — omit to use the
# built-in defaults. `qmd init` writes this block pre-filled with the
# resolved defaults. See "Model Configuration" for the default URIs.
models:
  embed: "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf"
  rerank: "hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf"
  generate: "hf:tobil/qmd-query-expansion-1.7B-gguf/qmd-query-expansion-1.7B-q4_k_m.gguf"

# One entry per collection. The key is the collection name.
collections:
  notes:
    path: /Users/me/notes        # absolute path to index (required)
    pattern: "**/*.md"           # glob mask (default: **/*.md)
    ignore:                      # glob patterns to exclude from indexing
      - "Archive/**"
      - "**/drafts/**"
    update: "git pull --rebase"  # bash command run before each `qmd update`
    includeByDefault: true       # include in unscoped queries (default: true)
    context:                     # path prefix → description; longest match wins
      "/": "Personal notes and ideas"
      "/work": "Work-related notes"
Key Scope Purpose
global_context top-level Context prepended for every collection. Set via qmd context add /.
editor_uri (alias editor_uri_template) top-level Hyperlink template for clickable result paths; QMD_EDITOR_URI overrides.
models.embed / .rerank / .generate top-level HuggingFace GGUF URIs (hf:<user>/<repo>/<file>) overriding the built-in defaults per role.
collections.<name>.path per-collection Absolute directory to index.
collections.<name>.pattern per-collection Glob mask. Set via qmd collection add --mask. Default **/*.md. Comma-separated lists and brace groups ({a,b}) are a union of patterns.
collections.<name>.ignore per-collection Glob patterns excluded from indexing — useful to stop nested collections double-indexing. YAML-only — no CLI command sets this. Additive with QMD's built-in exclusions (node_modules, .git, .cache, vendor, dist, build), which you cannot un-ignore.
collections.<name>.update per-collection Bash command run before qmd update re-indexes this collection. Set via qmd collection update-cmd.
collections.<name>.includeByDefault per-collection Whether unscoped queries search it. Toggle with qmd collection include/exclude. Default true.
collections.<name>.context per-collection Path-prefix → description map; the most specific (longest) matching prefix wins. Set via qmd context add.

Note: Editing index.yml changes which directories and models QMD uses, but does not re-index on its own. Run qmd update after changing path, pattern, or ignore, and qmd embed after changing models.embed.

Automatic update commands

A collection's update field is QMD's built-in refresh hook: when you run qmd update, each collection's update command runs first, then the collection is re-indexed. This keeps a collection in sync with an upstream source (a git remote, a sync script) without wrapping qmd yourself.

collections:
  wiki:
    path: ~/reference/wiki
    update: "git pull --ff-only"
$ qmd update
[1/3] wiki (**/*.md)
    Running update command: git pull --ff-only
    Already up to date.
Collection: ~/reference/wiki (**/*.md)
Indexed: 0 new, 2 updated, 340 unchanged, 0 removed

The command runs via bash -c in the collection's own directory (its path), not your current working directory. If it exits non-zero, qmd update prints the failure and aborts the entire run — collections after the failing one are not re-indexed. Set or clear it from the CLI instead of editing YAML by hand:

View on GitHub

Recent activity

commits and pull requests

Releases and announcements

14 total
  1. v2.8.3v2.8.3Aug 16, 2026

    ## [2.8.3] - 2026-08-16 ### Security - `qmd update` no longer runs a project-local `.qmd/index.yml`'s `update:` commands without approval (#886). That file arrives with a `git clone` and is adopted automatically for any command run inside the tree, so cloning a repository and running `qmd update` executed shell commands chosen by whoever wrote it. On a terminal QMD now lists the commands and asks; with nobody to ask it skips them and keeps indexing. Approvals are recorded per config file and per command set in `<config dir>/trusted.json`, so editing a command — or a `git pull` that rewrites one — asks again. New `qmd trust`, `qmd trust list` and `qmd trust revoke` manage approvals, and `QMD_TRUST_UPDATE_HOOKS=1` opts unattended runs back in. Commands in your own `~/.config/qmd/*.yml`, including anything `qmd collection update-cmd` writes, are unaffected. - The same project-local trust gate now covers collection `path` values that resolve outside the project and non-default `models.embed` / `models.rerank` / `models.generate` URIs (#889). In-project paths still index unattended; out-of-project directories are skipped until `qmd trust`, and custom model

  2. v2.5.3v2.5.3May 29, 2026

    ## [2.5.3] - 2026-05-28 ### Features - `qmd get` now accepts a `:from:count` suffix on a path or docid (e.g. `qmd get "#abc123:120:40"` reads 40 lines starting at line 120). Explicit `--from`/`-l` flags still override the suffix. The MCP `get` tool accepts the same suffix. - `qmd get` and `qmd multi-get` are now **line-numbered by default** and print the document's `#docid` and `qmd://` path in the output header. Disable line numbers with `--no-line-numbers`. The MCP `get`/`multi_get` tools default `lineNumbers` to `true` to match. - `qmd multi-get` now includes the `#docid` in every output format (`--md`, `--json`, `--csv`, `--xml`, `--files`, and the default CLI view), consistent with `qmd search`. - `qmd get` and `qmd multi-get` accept `--full-path`, which replaces the `qmd://` path + `#docid` with the document's on-disk filesystem path (handy for piping into `Read`/`Edit`/an editor). Falls back to the canonical `qmd://` + docid header when the file no longer exists on disk. - `qmd search` / `qmd query` now show a clearer hit identifier: the default CLI view (and the new `**file:**` line in `--md` output) always prints the full `qmd://collection/path`

  3. v2.5.2v2.5.2May 22, 2026

    ## [2.5.2] - 2026-05-22 ### Fixes - Launcher: Rewrite `bin/qmd` as a Node-based shebang polyglot to fix global npm installation execution failures on Windows (#668 / #452), while supporting seamless fallback to Bun in Node-less environments. ## [2.5.1] - 2026-05-20 ### Changes - Release: publish from GitHub Actions via npm Trusted Publishing/OIDC instead of a long-lived `NPM_TOKEN` secret. ## [2.5.0] - 2026-05-19 ### Changes - Dependencies: update core SQLite/config/chunking packages (`better-sqlite3`, `yaml`, `web-tree-sitter`, `tree-sitter-go`, and `tree-sitter-python`) while keeping incompatible `zod`, `tsx`, and `vitest` majors pinned. - Agent skills: add `qmd skills list|get|path` to serve version-matched runtime skill instructions from the installed CLI, and make `qmd skill install` write a stable discovery stub so installed agent skills do not go stale after QMD upgrades. - CLI: add `qmd doctor` for index/runtime diagnostics, including SQLite/sqlite-vec versions, embedding fingerprint freshness, mixed-fingerprint detection, safe legacy fingerprint adoption, and content-hash sampling. ### Fixes - Launcher: prefer runnable TypeScript source in git checkouts even whe

  4. v2.5.1v2.5.1May 20, 2026

    ## [2.5.1] - 2026-05-20 ### Changes - Release: publish from GitHub Actions via npm Trusted Publishing/OIDC instead of a long-lived `NPM_TOKEN` secret. ## [2.5.0] - 2026-05-19 ### Changes - Dependencies: update core SQLite/config/chunking packages (`better-sqlite3`, `yaml`, `web-tree-sitter`, `tree-sitter-go`, and `tree-sitter-python`) while keeping incompatible `zod`, `tsx`, and `vitest` majors pinned. - Agent skills: add `qmd skills list|get|path` to serve version-matched runtime skill instructions from the installed CLI, and make `qmd skill install` write a stable discovery stub so installed agent skills do not go stale after QMD upgrades. - CLI: add `qmd doctor` for index/runtime diagnostics, including SQLite/sqlite-vec versions, embedding fingerprint freshness, mixed-fingerprint detection, safe legacy fingerprint adoption, and content-hash sampling. ### Fixes - Launcher: prefer runnable TypeScript source in git checkouts even when ignored `dist/` artifacts exist, while packaged installs continue to run `dist/`. - GPU: keep node-llama-cpp's documented `gpu: "auto"` initialization as the primary path, then perform no-build packaged CUDA/Vulkan/Metal probes only if auto fal

  5. v2.1.0v2.1.0Apr 5, 2026

    ## [2.1.0] - 2026-04-05 Code files now chunk at function and class boundaries via tree-sitter, clickable editor links land you at the right line from search results, and per-collection model configuration means you can point different collections at different embedding models. 25+ community PRs fix embedding stability, BM25 accuracy, and cross-platform launcher issues. ### Changes - AST-aware chunking for code files via `web-tree-sitter`. Supported languages: TypeScript/JavaScript, Python, Go, and Rust. Code files are chunked at function, class, and import boundaries instead of arbitrary text positions. Markdown and unknown file types are unchanged. `--chunk-strategy <auto|regex>` flag on `qmd embed` and `qmd query` (default `regex`). SDK: `chunkStrategy` option on `embed()` and `search()`. `qmd status` shows grammar availability. - `qmd bench <fixture.json>` command for search quality benchmarks. Measures precision@k, recall, MRR, and F1 across BM25, vector, hybrid, and full pipeline backends. Ships with an example fixture against the eval-docs test collection. #470 (thanks @jmilinovich) - `models:` section in `index.yml` lets you configure `embed`, `rerank`,

Code frequency

additions and deletions
+45.8K-45.8KWeek of 2025-12-07: +27,368 linesWeek of 2025-12-07: -14,559 linesWeek of 2025-12-14: +2,979 linesWeek of 2025-12-14: -1,961 linesWeek of 2025-12-21: +2,078 linesWeek of 2025-12-21: -1,110 linesWeek of 2025-12-28: +452 linesWeek of 2025-12-28: -393 linesWeek of 2026-01-04: +285 linesWeek of 2026-01-04: -232 linesWeek of 2026-01-11: +110 linesWeek of 2026-01-11: -52 linesWeek of 2026-01-18: +13,710 linesWeek of 2026-01-18: -1,810 linesWeek of 2026-01-25: +45,813 linesWeek of 2026-01-25: -32,872 linesWeek of 2026-02-01: +277 linesWeek of 2026-02-01: -177 linesWeek of 2026-02-08: +1,863 linesWeek of 2026-02-08: -711 linesWeek of 2026-02-15: +26,410 linesWeek of 2026-02-15: -16,513 linesWeek of 2026-02-22: +6,144 linesWeek of 2026-02-22: -15,930 linesWeek of 2026-03-01: +1,459 linesWeek of 2026-03-01: -335 linesWeek of 2026-03-08: +9,429 linesWeek of 2026-03-08: -5,185 linesWeek of 2026-03-15: +14 linesWeek of 2026-03-15: -5 linesWeek of 2026-03-22: +2,754 linesWeek of 2026-03-22: -104 linesWeek of 2026-03-29: +295 linesWeek of 2026-03-29: -839 linesWeek of 2026-04-05: +6,966 linesWeek of 2026-04-05: -2,845 linesWeek of 2026-04-12: +1 linesWeek of 2026-04-12: -0 linesWeek of 2026-04-19: +61 linesWeek of 2026-04-19: -31 linesWeek of 2026-04-26: +0 linesWeek of 2026-04-26: -0 linesWeek of 2026-05-03: +1,239 linesWeek of 2026-05-03: -140 linesWeek of 2026-05-10: +2,063 linesWeek of 2026-05-10: -313 linesWeek of 2026-05-17: +3,065 linesWeek of 2026-05-17: -998 linesWeek of 2026-05-24: +1,386 linesWeek of 2026-05-24: -285 linesWeek of 2026-05-31: +641 linesWeek of 2026-05-31: -8 linesWeek of 2026-06-07: +673 linesWeek of 2026-06-07: -73 linesWeek of 2026-06-14: +654 linesWeek of 2026-06-14: -78 linesWeek of 2026-06-21: +382 linesWeek of 2026-06-21: -146 linesWeek of 2026-06-28: +138 linesWeek of 2026-06-28: -15 linesWeek of 2026-07-05: +0 linesWeek of 2026-07-05: -0 linesWeek of 2026-07-12: +1,207 linesWeek of 2026-07-12: -40 linesWeek of 2026-07-19: +1,349 linesWeek of 2026-07-19: -106 linesWeek of 2026-07-26: +32 linesWeek of 2026-07-26: -5 linesWeek of 2026-08-02: +56 linesWeek of 2026-08-02: -1 linesWeek of 2026-08-09: +11,405 linesWeek of 2026-08-09: -1,465 linesWeek of 2026-08-16: +3,354 linesWeek of 2026-08-16: -55 linesWeek of 2026-08-23: +47 linesWeek of 2026-08-23: -10 linesWeek of 2026-08-30: +278 linesWeek of 2026-08-30: -76 linesWeek of 2026-09-06: +96 linesWeek of 2026-09-06: -29 linesWeek of 2026-09-13: +0 linesWeek of 2026-09-13: -0 linesWeek of 2026-09-20: +0 linesWeek of 2026-09-20: -0 linesWeek of 2026-09-27: +0 linesWeek of 2026-09-27: -0 linesDec 7, 2025Sep 27, 2026
+176.5K lines added, -99.5K removed over the last year.

Commits per week

last 52 weeks
770Week of 2025-10-04: 0 commitsWeek of 2025-10-11: 0 commitsWeek of 2025-10-18: 0 commitsWeek of 2025-10-25: 0 commitsWeek of 2025-11-01: 0 commitsWeek of 2025-11-09: 0 commitsWeek of 2025-11-16: 0 commitsWeek of 2025-11-23: 0 commitsWeek of 2025-11-30: 0 commitsWeek of 2025-12-07: 73 commitsWeek of 2025-12-14: 9 commitsWeek of 2025-12-21: 15 commitsWeek of 2025-12-28: 2 commitsWeek of 2026-01-04: 6 commitsWeek of 2026-01-11: 4 commitsWeek of 2026-01-18: 11 commitsWeek of 2026-01-25: 45 commitsWeek of 2026-02-01: 5 commitsWeek of 2026-02-08: 9 commitsWeek of 2026-02-15: 77 commitsWeek of 2026-02-22: 11 commitsWeek of 2026-03-01: 20 commitsWeek of 2026-03-08: 30 commitsWeek of 2026-03-15: 1 commitsWeek of 2026-03-22: 14 commitsWeek of 2026-03-29: 7 commitsWeek of 2026-04-05: 44 commitsWeek of 2026-04-12: 1 commitsWeek of 2026-04-19: 3 commitsWeek of 2026-04-26: 0 commitsWeek of 2026-05-03: 18 commitsWeek of 2026-05-10: 17 commitsWeek of 2026-05-17: 23 commitsWeek of 2026-05-24: 10 commitsWeek of 2026-05-31: 4 commitsWeek of 2026-06-07: 7 commitsWeek of 2026-06-14: 4 commitsWeek of 2026-06-21: 3 commitsWeek of 2026-06-28: 1 commitsWeek of 2026-07-05: 0 commitsWeek of 2026-07-12: 2 commitsWeek of 2026-07-19: 5 commitsWeek of 2026-07-26: 2 commitsWeek of 2026-08-02: 2 commitsWeek of 2026-08-09: 57 commitsWeek of 2026-08-16: 6 commitsWeek of 2026-08-23: 2 commitsWeek of 2026-08-30: 2 commitsWeek of 2026-09-06: 3 commitsWeek of 2026-09-13: 0 commitsWeek of 2026-09-20: 0 commitsWeek of 2026-09-27: 0 commitsOct 4, 2025Sep 27, 2026
555 commits in the last 52 weeks.

When work happens

weekday and hour
SunMonTueWedThuFriSat036912151821Sun 0:00 — 7 commitsSun 1:00 — 2 commitsSun 2:00 — 0 commitsSun 3:00 — 1 commitsSun 4:00 — 1 commitsSun 5:00 — 0 commitsSun 6:00 — 0 commitsSun 7:00 — 0 commitsSun 8:00 — 1 commitsSun 9:00 — 2 commitsSun 10:00 — 11 commitsSun 11:00 — 6 commitsSun 12:00 — 5 commitsSun 13:00 — 20 commitsSun 14:00 — 5 commitsSun 15:00 — 7 commitsSun 16:00 — 7 commitsSun 17:00 — 9 commitsSun 18:00 — 14 commitsSun 19:00 — 5 commitsSun 20:00 — 0 commitsSun 21:00 — 5 commitsSun 22:00 — 11 commitsSun 23:00 — 2 commitsMon 0:00 — 0 commitsMon 1:00 — 1 commitsMon 2:00 — 2 commitsMon 3:00 — 0 commitsMon 4:00 — 1 commitsMon 5:00 — 0 commitsMon 6:00 — 0 commitsMon 7:00 — 1 commitsMon 8:00 — 8 commitsMon 9:00 — 5 commitsMon 10:00 — 1 commitsMon 11:00 — 4 commitsMon 12:00 — 2 commitsMon 13:00 — 0 commitsMon 14:00 — 5 commitsMon 15:00 — 0 commitsMon 16:00 — 1 commitsMon 17:00 — 4 commitsMon 18:00 — 0 commitsMon 19:00 — 0 commitsMon 20:00 — 8 commitsMon 21:00 — 1 commitsMon 22:00 — 0 commitsMon 23:00 — 1 commitsTue 0:00 — 1 commitsTue 1:00 — 0 commitsTue 2:00 — 0 commitsTue 3:00 — 0 commitsTue 4:00 — 1 commitsTue 5:00 — 0 commitsTue 6:00 — 3 commitsTue 7:00 — 0 commitsTue 8:00 — 2 commitsTue 9:00 — 2 commitsTue 10:00 — 3 commitsTue 11:00 — 8 commitsTue 12:00 — 2 commitsTue 13:00 — 3 commitsTue 14:00 — 6 commitsTue 15:00 — 2 commitsTue 16:00 — 1 commitsTue 17:00 — 1 commitsTue 18:00 — 2 commitsTue 19:00 — 1 commitsTue 20:00 — 4 commitsTue 21:00 — 3 commitsTue 22:00 — 3 commitsTue 23:00 — 8 commitsWed 0:00 — 4 commitsWed 1:00 — 1 commitsWed 2:00 — 0 commitsWed 3:00 — 1 commitsWed 4:00 — 1 commitsWed 5:00 — 1 commitsWed 6:00 — 0 commitsWed 7:00 — 2 commitsWed 8:00 — 1 commitsWed 9:00 — 1 commitsWed 10:00 — 1 commitsWed 11:00 — 1 commitsWed 12:00 — 3 commitsWed 13:00 — 1 commitsWed 14:00 — 1 commitsWed 15:00 — 4 commitsWed 16:00 — 1 commitsWed 17:00 — 0 commitsWed 18:00 — 0 commitsWed 19:00 — 4 commitsWed 20:00 — 7 commitsWed 21:00 — 8 commitsWed 22:00 — 11 commitsWed 23:00 — 13 commitsThu 0:00 — 9 commitsThu 1:00 — 7 commitsThu 2:00 — 5 commitsThu 3:00 — 4 commitsThu 4:00 — 0 commitsThu 5:00 — 5 commitsThu 6:00 — 5 commitsThu 7:00 — 1 commitsThu 8:00 — 1 commitsThu 9:00 — 1 commitsThu 10:00 — 6 commitsThu 11:00 — 3 commitsThu 12:00 — 1 commitsThu 13:00 — 3 commitsThu 14:00 — 2 commitsThu 15:00 — 4 commitsThu 16:00 — 0 commitsThu 17:00 — 2 commitsThu 18:00 — 0 commitsThu 19:00 — 2 commitsThu 20:00 — 0 commitsThu 21:00 — 0 commitsThu 22:00 — 3 commitsThu 23:00 — 3 commitsFri 0:00 — 5 commitsFri 1:00 — 0 commitsFri 2:00 — 0 commitsFri 3:00 — 1 commitsFri 4:00 — 1 commitsFri 5:00 — 0 commitsFri 6:00 — 0 commitsFri 7:00 — 2 commitsFri 8:00 — 0 commitsFri 9:00 — 1 commitsFri 10:00 — 0 commitsFri 11:00 — 1 commitsFri 12:00 — 5 commitsFri 13:00 — 3 commitsFri 14:00 — 4 commitsFri 15:00 — 5 commitsFri 16:00 — 18 commitsFri 17:00 — 14 commitsFri 18:00 — 0 commitsFri 19:00 — 6 commitsFri 20:00 — 5 commitsFri 21:00 — 2 commitsFri 22:00 — 2 commitsFri 23:00 — 10 commitsSat 0:00 — 2 commitsSat 1:00 — 3 commitsSat 2:00 — 0 commitsSat 3:00 — 0 commitsSat 4:00 — 0 commitsSat 5:00 — 0 commitsSat 6:00 — 0 commitsSat 7:00 — 1 commitsSat 8:00 — 0 commitsSat 9:00 — 7 commitsSat 10:00 — 5 commitsSat 11:00 — 7 commitsSat 12:00 — 8 commitsSat 13:00 — 10 commitsSat 14:00 — 7 commitsSat 15:00 — 9 commitsSat 16:00 — 4 commitsSat 17:00 — 14 commitsSat 18:00 — 17 commitsSat 19:00 — 6 commitsSat 20:00 — 9 commitsSat 21:00 — 2 commitsSat 22:00 — 2 commitsSat 23:00 — 2 commits
Commit volume by weekday and hour (UTC). Larger dots mean more commits.

Who is committing

last 52 weeks
Maintainer commits524 (76%)
Community commits168 (24%)

692 commits in total over the last year.

DateListRankStars gained
Feb 2, 2026daily#18+137
Feb 1, 2026daily#15+155
Jan 31, 2026daily#21+136
Jan 27, 2026daily#11+227
Jan 11, 2026daily#18+202
  • freeCodeCamp/freeCodeCamp

    freeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.

    456.7K stars · TypeScript

  • openclaw/openclaw

    The AI that really does things. Any OS. Any Platform. The lobster way. 🦞

    391.3K stars · TypeScript

  • anomalyco/opencode

    The open source coding agent.

    211.7K stars · TypeScript

  • n8n-io/n8n

    Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.

    206.7K stars · TypeScript

  • microsoft/vscode

    Visual Studio Code

    193.5K stars · TypeScript

  • firecrawl/firecrawl

    Supercharge your AI agents with data from the web and beyond. Building the library for superintelligence. 🔥

    188.6K stars · TypeScript