Skip to main content
Glama
bayuh85
by bayuh85

skimsearch

Coding agents burn tokens when they grep a monorepo and paste the dump back into the prompt. A wall of unrelated files crowds out the few lines that matter. skimsearch indexes the tree once, then answers with a short deck of ranked snippets that fit a token budget.

What skimsearch does

  1. Index. skimsearch index folds code and docs into skim cards. A card is one function, class, heading, or window, with its path and line range. Cards live in a compact SQLite file at .skimsearch/index.sqlite. SQLite FTS5 (BM25) ranks them offline.

  2. Search. skimsearch search returns the best cards, trimmed so the whole deck stays inside a token budget. The default mode is lexical and does not call the network.

  3. Ask. --mode semantic asks an OpenAI-compatible model to turn a question into a few lexical probes, then searches locally. --mode ask does that and returns a short answer that cites only the cards it used. Optional --embed stores vectors when the provider implements POST /embeddings.

Agents spend tokens on the deck, not on a full-tree dump.

Related MCP server: code-memory-rs

Quickstart

Requires Python 3.10+.

python -m venv .venv
source .venv/bin/activate
pip install -e .

skimsearch index examples/acme-billing
skimsearch search "where is the late fee calculated" --path examples/acme-billing

A lexical search stays on disk. On the sample project the deck looks like this:

skimsearch  mode=lexical  cards=4  tokens=322/700

1. src/billing/invoices.py:17-28  function apply_late_fee  (python, score 1.71)
    def apply_late_fee(invoice: dict, as_of: date) -> dict:
        """The late fee is calculated from the subtotal after the grace window."""
        if invoice.get("settled"):
            return invoice
        overdue = (as_of - invoice["due"]).days
        if overdue <= GRACE_DAYS:
            return invoice
        fee = round(invoice["subtotal"] * LATE_FEE_RATE, 2)
        updated = dict(invoice)
        updated["late_fee"] = fee
        updated["total"] = round(invoice["subtotal"] + fee, 2)
        return updated

2. docs/billing.md:3-5  heading Late fees  (markdown, score 1.50)
    ## Late fees

    A fee of 1.5 percent is added after a three-day grace period. The late fee is calculated from the invoice subtotal.

Each card is a rank, a path:start-end locator, the symbol, and a trimmed body. The whole deck stops at the token budget.

LLM-assisted search uses the configured provider:

export ATMOROUTER_API_KEY=ar-your-key-here
skimsearch search "where is the late fee calculated" --path examples/acme-billing --mode ask

MCP, for Cursor or Claude Code:

skimsearch serve /absolute/path/to/repo

serve builds an index first when the project does not have one yet.

Smoke tests run offline:

pip install -e ".[dev]"
pytest -q

Default LLM: AtmoRouter

Semantic and ask modes call an OpenAI-compatible chat API. The default base URL is AtmoRouter, a cheap AI API gateway and an OpenRouter alternative. One ar- key reaches the catalogue. Point SKIMSEARCH_BASE_URL at any other OpenAI-compatible endpoint when you want a different provider. Powered by AtmoRouter.

AtmoRouter also accepts the Anthropic Messages API. Set SKIMSEARCH_API_STYLE=anthropic and skimsearch posts to {SKIMSEARCH_BASE_URL}/messages (the default base already includes /v1). Chat completions stay on {SKIMSEARCH_BASE_URL}/chat/completions.

curl "$SKIMSEARCH_BASE_URL/models" -H "Authorization: Bearer $ATMOROUTER_API_KEY"

The default model is atmo/deepseek-v4.1-flash. Set SKIMSEARCH_MODEL to any id your key can call.

Configuration

Copy .env.example to .env in the project you index, or export the variables in the shell. Values already set in the environment win over .env.

Variable

Default

Role

ATMOROUTER_API_KEY

unset

Preferred API key. AtmoRouter keys look like ar-….

SKIMSEARCH_API_KEY

unset

Alias used when ATMOROUTER_API_KEY is unset.

OPENAI_API_KEY

unset

Alias used when the two keys above are unset.

SKIMSEARCH_BASE_URL

https://atmorouter.dev/v1

OpenAI-compatible base URL, including /v1.

OPENAI_BASE_URL

unset

Used when SKIMSEARCH_BASE_URL is unset.

SKIMSEARCH_MODEL

atmo/deepseek-v4.1-flash

Chat model id.

SKIMSEARCH_API_STYLE

openai

openai or anthropic.

SKIMSEARCH_BUDGET

700

Token budget for one deck.

SKIMSEARCH_CARD_TOKENS

140

Token cap for a single card before the budget cuts it.

SKIMSEARCH_TIMEOUT

60

HTTP timeout in seconds.

SKIMSEARCH_EMBED_MODEL

unset

Embedding model for index --embed.

index skips dependency folders, build output, lockfiles, and secret files (.env, keys, .pem). It also reads .gitignore. A .skimignore file uses the same pattern syntax and is applied after .gitignore, so a negation such as !dist/ puts a path back into the index. The sample project ignores vendor/ with its own .gitignore and ships a fake .env that the built-in rules leave out of the index.

skimsearch index .                 # create or update .skimsearch/index.sqlite
skimsearch index . --force         # rebuild
skimsearch index . --embed         # also store /embeddings vectors
skimsearch search "session ttl" -C . --budget 500
skimsearch search "session ttl" --mode semantic
skimsearch search "session ttl" --mode ask --format json
skimsearch status .
skimsearch serve .

search from a subdirectory finds an index in a parent directory. index always writes the index into the directory you name.

Cursor and Claude Code

Index the repo, then register the stdio server. Both clients use the same command.

Cursor (.cursor/mcp.json):

{
  "mcpServers": {
    "skimsearch": {
      "command": "skimsearch",
      "args": ["serve", "/absolute/path/to/repo"],
      "env": {
        "ATMOROUTER_API_KEY": "ar-your-key-here",
        "SKIMSEARCH_BASE_URL": "https://atmorouter.dev/v1"
      }
    }
  }
}

Claude Code (.mcp.json in the project, or the user MCP config):

{
  "mcpServers": {
    "skimsearch": {
      "command": "skimsearch",
      "args": ["serve", "/absolute/path/to/repo"],
      "env": {
        "ATMOROUTER_API_KEY": "ar-your-key-here"
      }
    }
  }
}

Tools:

  • skim_search — query, optional mode (lexical, semantic, ask), budget, limit, format

  • skim_status — index stats and the configured provider

lexical is the right default for a tight loop. Use ask when the question is phrased in prose and you want a short grounded answer plus the cards that support it.

License

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables local semantic code search across repositories using natural language, with AST-aware chunking and hybrid vector/FTS5 retrieval.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to search and ask questions about a local codebase with citations, using BM25 and optional embeddings, all offline.
    1 npm
    1
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables semantic and keyword search over a local codebase by chunking source files along function and class boundaries, embedding them offline, and fusing vector and BM25 results. Runs entirely on your machine in a single SQLite file, so AI coding agents can find relevant code with no API keys, telemetry, or cloud calls.
    7
    13 npm
    MIT