ChatCrystal collects conversations from AI coding tools (Claude Code, Cursor, Codex CLI, Trae, GitHub Copilot), uses LLM to distill them into searchable structured notes, and builds your personal knowledge base — all running locally.
After 200+ AI conversations, finding that one solution you discussed last week becomes impossible. ChatCrystal watches your conversation files, auto-generates structured notes, and lets you search across everything with natural language — no cloud, no subscription, runs entirely on your machine.
npm install -g chatcrystal
crystal serve -d
crystal importThen open http://localhost:3721 in your browser.
- Multi-source ingestion — Auto-imports conversations from Claude Code, Codex CLI, Cursor, Trae, and GitHub Copilot with real-time file watching
- Structured LLM summarization — Generates notes via
generateObject+ Zod schema (guaranteed valid output, auto-retry on schema violation). Turn-based transcript preprocessing selects the most valuable conversation segments within a configurable token budget. - Semantic search — Embedding-powered vector search (vectra) with text preview snippets and relation-aware result expansion. Embedding content includes title, summary, conclusions, tags, and code snippet descriptions.
- Knowledge graph — Structured relation discovery via
generateObjectwith typed schemas. 8 relation types with confidence scoring and force-directed visualization. - Conversation viewer — Markdown rendering, code highlighting, collapsible tool calls, noise filtering
- Multi-provider support — Ollama, OpenAI, Anthropic, Google AI, Azure OpenAI, or any OpenAI-compatible API, switchable at runtime
- Task queue — Batch summarization/embedding via p-queue with real-time progress tracking and cancellation
- Desktop app — Electron with system tray, minimize-to-tray on close
ChatCrystal uses a multi-stage pipeline to turn raw conversations into searchable knowledge:
AI coding conversations have a natural turn structure — a user gives an instruction, the assistant responds (potentially with many tool calls), and the cycle repeats. Long conversations (100+ messages with heavy MCP tool usage) can't fit in a single LLM context window.
Instead of naive head+tail truncation, ChatCrystal uses a turn-based selection algorithm:
- Split — Messages are grouped into turns at user→assistant boundaries. Consecutive user messages (e.g., pasting logs + follow-up) stay in the same turn.
- Filter — Within each turn, only the user instruction and the first/last substantial assistant reply are kept. Tool call chains in between are discarded.
- Score — Each turn is scored:
user_text_length × (1 + assistant_reply_count). Longer instructions with more assistant engagement = higher importance. - Select — The first turn (requirements) and last two turns (conclusions) are always included. Remaining budget goes to the highest-scored middle turns.
- Summarize skipped turns — Skipped turns are compressed into one-line previews (
[skipped] User: fix the CSS issue with login page...) so the LLM still sees the conversation's causal chain.
The character budget defaults to 32,000 (~8K tokens) and is configurable via LLM_MAX_INPUT_CHARS for users with larger-context models.
Summarization uses Vercel AI SDK's generateObject() with a Zod schema instead of prompt-engineered JSON. This guarantees valid output structure with automatic retry (up to 3 attempts) when schema validation fails — eliminating the truncation and parse failures common with generateText() + manual JSON extraction.
ChatCrystal also provides a CLI tool and MCP Server, published as an npm package.
npm install -g chatcrystalcrystal status # Server status and DB stats
crystal import [--source claude-code] # Scan and import conversations
crystal search "query" [--limit 10] # Semantic search
crystal notes list [--tag X] # Browse notes
crystal notes get <id> # View note detail
crystal tags # List tags with counts
crystal summarize --all # Batch summarize
crystal config get # View config
crystal serve -d # Start server in background
crystal serve stop # Stop background serverAuto-start: commands that need the server will auto-launch it in background if not running. Output is TTY-aware — colored tables in terminal, JSON when piped.
Integrate with AI coding tools (Claude Code, Cursor, etc.) so they can retrieve knowledge from your conversation history during coding.
crystal mcp # Start MCP stdio serverClaude Code configuration (settings.json):
{
"mcpServers": {
"chatcrystal": {
"command": "crystal",
"args": ["mcp"]
}
}
}ChatCrystal MCP uses stdio transport. Configure it with command and args, not as an HTTP/SSE MCP URL. The local web/API server runs on http://localhost:3721; do not use a bare http://127.0.0.1 URL in tools that require an HTTP endpoint, because HTTP defaults to port 80.
MCP exposes 6 tools: read-only knowledge tools search_knowledge, get_note, list_notes, get_relations, plus memory-loop tools recall_for_task and write_task_memory.
Formal portable ChatCrystal skills live under skills/ and are documented in docs/agent-skills.md.
ChatCrystal's agent memory loop is split into three layers:
- ChatCrystal Core — Local knowledge storage, retrieval, merge, and writeback
- MCP Layer — Stable tools for knowledge lookup plus task recall and task writeback
- Skill Layer — Portable skills that trigger recall before substantial work and writeback after meaningful work
When Core is unavailable, the skills degrade safely: they continue helping with the task, but do not pretend memory was recalled or persisted.
See docs/agent-skills.md for installation, full mode, degraded mode, and publishing guidance.
| Layer | Technology |
|---|---|
| Backend | Node.js + Fastify v5 + TypeScript |
| Frontend | Vite v8 + React 19 + Tailwind CSS v4 + TanStack React Query v5 |
| Desktop | Electron + electron-builder (NSIS installer) |
| Database | sql.js (WASM SQLite) |
| LLM | Vercel AI SDK v6 — Ollama / OpenAI / Anthropic / Google / Azure / Custom |
| Embedding | vectra vector index, multi-provider |
| File watching | chokidar |
- Node.js >= 20
- An LLM service (pick one):
- Ollama (local inference, free)
- OpenAI / Anthropic / Google AI API key
- Any OpenAI-compatible service (OpenRouter, Poe, etc.)
If using Ollama, pull the required models:
ollama pull qwen2.5:7b # LLM summarization
ollama pull nomic-embed-text # Embeddinggit clone https://github.com/ZengLiangYi/ChatCrystal.git
cd ChatCrystal
npm installConfiguration is persisted in ~/.chatcrystal/data/config.json (or an explicit DATA_DIR) after first launch. .env is optional and only needed if you want local development overrides.
npm run dev:electron # Dev mode (Electron + Vite HMR)
npm run build:electron # Build NSIS installer → release/The installer is in the release/ directory. Data is stored in ~/.chatcrystal/data/ by default, matching the CLI and MCP server.
npm run dev # Starts backend (3721) + frontend (13721)Visit http://localhost:13721
npm run build # Build backend + frontend
npm start # Start server (frontend served statically)Visit http://localhost:3721
- Click "Import" in the sidebar to scan Claude Code / Codex CLI / Cursor / Trae / GitHub Copilot conversations
- Browse imported conversations on the Conversations page
- Click "Summarize" or use "Batch Summarize" to distill conversations into notes
- Search your knowledge on the Search page; enable "Expand related notes" to follow relation edges
- Explore note relationships on the Graph page (force-directed, draggable, zoomable)
- Filter and browse all notes by tag on the Notes page
- Switch LLM/Embedding providers and models on the Settings page
ChatCrystal now treats config.json in the active data directory as the primary runtime config. The Settings page and crystal config commands update that file directly.
Default locations:
- CLI / MCP / npm package / repo checkout / Electron:
~/.chatcrystal/data/config.json - Custom
DATA_DIR:<DATA_DIR>/config.json
.env is optional. Keep it only if you want local overrides such as a custom PORT, source directory overrides, or pre-seeded API keys during development.
Typical keys stored in config.json:
{
"llm": {
"provider": "ollama",
"baseURL": "http://localhost:11434",
"model": "qwen2.5:7b",
"apiKey": ""
},
"embedding": {
"provider": "ollama",
"baseURL": "http://localhost:11434",
"model": "nomic-embed-text",
"apiKey": ""
},
"enabledSources": ["claude-code", "codex", "cursor", "trae", "copilot"]
}Optional .env overrides:
# Server port
PORT=3721
# Optional runtime data override
# DATA_DIR=C:\path\to\chatcrystal-data
# Optional source overrides
# CLAUDE_PROJECTS_DIR=~/.claude/projects
# CODEX_SESSIONS_DIR=~/.codex/sessions
# Optional provider defaults
# LLM_PROVIDER=ollama
# LLM_BASE_URL=http://localhost:11434
# LLM_MODEL=qwen2.5:7b
# EMBEDDING_PROVIDER=ollama
# EMBEDDING_BASE_URL=http://localhost:11434
# EMBEDDING_MODEL=nomic-embed-text
# LLM_API_KEY=
# EMBEDDING_API_KEY=Note: LLM and Embedding must be configured separately. Semantic search requires a dedicated embedding model that supports the
/v1/embeddingsendpoint. Large language models (Claude, GPT-4, Qwen, etc.) cannot be used as embedding models. Common embedding models:
Provider Models Ollama (local) nomic-embed-text,mxbai-embed-largeOpenAI text-embedding-3-small,text-embedding-3-largetext-embedding-004
You can set these through the Settings page / config.json, or keep them in .env as local overrides.
# OpenAI
LLM_PROVIDER=openai
LLM_API_KEY=sk-...
LLM_MODEL=gpt-4o
# Anthropic
LLM_PROVIDER=anthropic
LLM_API_KEY=sk-ant-...
LLM_MODEL=claude-sonnet-4-20250514
# Google AI
LLM_PROVIDER=google
LLM_API_KEY=AIza...
LLM_MODEL=gemini-2.0-flash
# OpenAI-compatible service (Poe / OpenRouter / etc.)
LLM_PROVIDER=custom
LLM_BASE_URL=https://openrouter.ai/api/v1
LLM_API_KEY=your-key
LLM_MODEL=anthropic/claude-sonnet-4ChatCrystal/
├── electron/ # Electron main process (window, tray, lifecycle)
├── shared/types/ # Shared TypeScript types
├── server/src/
│ ├── db/ # SQLite schema + utilities
│ ├── parser/ # Plugin-based conversation parsers (Claude Code / Codex / Cursor / Trae / Copilot)
│ ├── services/ # Import, summarization, LLM, embedding, relations, providers
│ ├── routes/ # Fastify API routes
│ ├── watcher/ # chokidar file watching
│ └── queue/ # p-queue task queue + TaskTracker
├── client/src/
│ ├── pages/ # Page components (Dashboard, Conversations, Notes, Search, Graph, Settings)
│ ├── components/ # Shared components (StatusBar, ActivityPanel, etc.)
│ ├── hooks/ # React Query hooks
│ ├── themes/ # Theme definitions
│ └── providers/ # ThemeProvider
├── scripts/ # Release and utility scripts
├── electron-builder.yml # Electron packaging config
└── data/ # Optional DATA_DIR override location (gitignored)
Implement the SourceAdapter interface to add a new AI tool:
interface SourceAdapter {
name: string;
displayName: string;
detect(): Promise<SourceInfo | null>;
scan(): Promise<ConversationMeta[]>;
parse(meta: ConversationMeta): Promise<ParsedConversation>;
}Built-in adapters:
| Adapter | Data Source | Format |
|---|---|---|
claude-code |
~/.claude/projects/**/*.jsonl |
JSONL conversation log |
codex |
~/.codex/sessions/**/rollout-*.jsonl |
JSONL event stream |
cursor |
Cursor workspaceStorage/state.vscdb |
SQLite KV store |
trae |
Trae workspaceStorage/state.vscdb |
SQLite KV store |
copilot |
VS Code workspaceStorage/chatSessions/*.jsonl |
JSONL session snapshots |
Create a new adapter file in server/src/parser/adapters/ and register it in parser/index.ts.
| Method | Path | Description |
|---|---|---|
| GET | /api/status |
Server status + statistics |
| GET | /api/config |
Current configuration (secrets redacted) |
| POST | /api/config |
Update provider configuration |
| POST | /api/config/test |
Test LLM connection |
| GET | /api/providers |
Available provider list |
| POST | /api/import/scan |
Trigger full scan import |
| GET | /api/conversations |
Conversation list (filterable, paginated) |
| GET | /api/conversations/:id |
Conversation detail + messages |
| POST | /api/conversations/:id/summarize |
Generate summary for one conversation |
| POST | /api/summarize/batch |
Batch summarization |
| POST | /api/summarize/reset-errors |
Reset error status |
| GET | /api/notes |
Note list |
| GET | /api/notes/:id |
Note detail |
| POST | /api/notes/:id/embed |
Generate embedding |
| POST | /api/embeddings/batch |
Batch embedding generation |
| GET | /api/search?q=...&expand=true |
Semantic search (expand follows relation edges) |
| GET | /api/notes/:id/relations |
Note relations list |
| POST | /api/notes/:id/relations |
Create relation manually |
| DELETE | /api/relations/:id |
Delete relation |
| POST | /api/notes/:id/discover-relations |
LLM auto-discover relations |
| POST | /api/relations/batch-discover |
Batch relation discovery |
| GET | /api/relations/graph |
Knowledge graph data (nodes + edges) |
| GET | /api/tags |
Tag list |
| GET | /api/queue/status |
Queue status |
| POST | /api/queue/cancel |
Cancel queued tasks |
After generating note summaries, the LLM automatically analyzes relationships between notes. Supported relation types:
| Relation | Meaning | Example |
|---|---|---|
CAUSED_BY |
Causation | Login failure ← Token expiration logic bug |
LEADS_TO |
Leads to | Route refactor → page flicker bug |
RESOLVED_BY |
Resolved by | Memory leak → added cleanup function |
SIMILAR_TO |
Similar topic | Two conversations both discussing deployment |
CONTRADICTS |
Contradiction | Use Redux vs Context is enough |
DEPENDS_ON |
Dependency | New feature depends on auth middleware refactor |
EXTENDS |
Extension | Added eviction policy on top of caching solution |
REFERENCES |
Reference | Conversation mentions a previous architecture decision |
View related notes at the bottom of the note detail page. Supports AI discovery, manual addition, and search-to-link. Browse the entire knowledge network via the force-directed graph on the Graph page.
Semantic search returns 500 "Not Found"
Embedding model misconfigured. Make sure EMBEDDING_MODEL is a dedicated embedding model (e.g., nomic-embed-text), not a large language model (e.g., claude-haiku, qwen2.5). LLMs do not support the /v1/embeddings endpoint.
Can't connect to Ollama on startup
Make sure Ollama is running and the models are pulled:
ollama pull qwen2.5:7b
ollama pull nomic-embed-textNo conversations after import
Check your configured source paths in the Settings page or config.json, and make sure the target directory contains .jsonl files. If you use .env overrides, verify those paths there instead.
Knowledge graph is empty
You need to generate notes first, then click "Discover" on a note detail page, or use POST /api/relations/batch-discover for batch discovery.




