Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fdfdd5d5e | |||
| e872c2da38 | |||
| 0b476e971a | |||
| f7b51a963e | |||
| b68ea38bcc | |||
| 12459eab3b | |||
| 5e6ef4aeb1 | |||
| f46b5b452f | |||
| 2af8db6b82 | |||
| 6a4fa85c10 |
@@ -10,7 +10,7 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "5.1.0",
|
||||
"version": "5.1.4",
|
||||
"source": "./plugin",
|
||||
"description": "Persistent memory system for Claude Code - context compression across sessions"
|
||||
}
|
||||
|
||||
+188
@@ -8,6 +8,194 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [5.1.2] - 2025-11-06
|
||||
|
||||
### Added
|
||||
- **Theme Toggle**: Light/dark mode support in viewer UI
|
||||
- User-selectable theme with persistent settings
|
||||
- Automatic system preference detection
|
||||
- Smooth transitions between themes
|
||||
- Updated viewer UI with theme toggle controls in header
|
||||
|
||||
### Changed
|
||||
- Version bumped from 5.1.1 to 5.1.2 across all metadata files
|
||||
- Rebuilt all plugin scripts with theme functionality
|
||||
|
||||
|
||||
## [5.1.1] - 2025-11-06
|
||||
|
||||
### Fixed
|
||||
- **PM2 ENOENT error on Windows**: Fixed PM2 process spawning by using full path to PM2 binary
|
||||
- Improved cross-platform compatibility for PM2 process management
|
||||
- Updated scripts/smart-install.js to use full PM2 binary path
|
||||
|
||||
|
||||
## [5.1.0] - 2025-11-05
|
||||
|
||||
### Added
|
||||
- **Web-Based Viewer UI**: Production-ready viewer accessible at http://localhost:37777
|
||||
- Real-time visualization via Server-Sent Events (SSE)
|
||||
- Infinite scroll pagination with automatic deduplication
|
||||
- Project filtering to focus on specific codebases
|
||||
- Settings persistence (sidebar state, selected project)
|
||||
- Auto-reconnection with exponential backoff
|
||||
- GPU-accelerated animations for smooth interactions
|
||||
- **New Worker Endpoints** (8 HTTP/SSE routes, +500 lines):
|
||||
- `/api/prompts` - Paginated user prompts with project filtering
|
||||
- `/api/observations` - Paginated observations with project filtering
|
||||
- `/api/summaries` - Paginated session summaries with project filtering
|
||||
- `/api/stats` - Database statistics (total counts by project)
|
||||
- `/api/projects` - List of unique project names
|
||||
- `/stream` - Server-Sent Events for real-time updates
|
||||
- `/` - Serves viewer HTML
|
||||
- **Database Enhancements** (+98 lines in SessionStore):
|
||||
- `getRecentPrompts()` - Paginated prompts with OFFSET/LIMIT
|
||||
- `getRecentObservations()` - Paginated observations with OFFSET/LIMIT
|
||||
- `getRecentSummaries()` - Paginated summaries with OFFSET/LIMIT
|
||||
- `getStats()` - Aggregated statistics by project
|
||||
- `getUniqueProjects()` - Distinct project names
|
||||
- **Complete React UI** (17 new files, 1,500+ lines):
|
||||
- Components: Header, Sidebar, Feed, Cards (Observation, Prompt, Summary, Skeleton)
|
||||
- Hooks: useSSE, usePagination, useSettings, useStats
|
||||
- Utils: Data merging, formatters, constants
|
||||
- Assets: Monaspace Radon font, logos (dark mode + logomark)
|
||||
- Build: esbuild pipeline for self-contained HTML bundle
|
||||
|
||||
|
||||
## [5.0.3] - 2025-11-05
|
||||
|
||||
### Added
|
||||
- **Smart Install Caching**: Eliminated redundant npm install on every SessionStart (2-5s → 10ms)
|
||||
- Caches version state in `.install-version` file
|
||||
- Only runs npm install when actually needed (first time, version change, missing deps)
|
||||
- 200x faster SessionStart for cached installations
|
||||
- Dynamic Python version detection in Windows error messages
|
||||
- Comprehensive Windows troubleshooting guidance
|
||||
|
||||
### Fixed
|
||||
- Fixed Windows installation issues with smart caching installer
|
||||
|
||||
### Changed
|
||||
- Enhanced rsync to respect gitignore rules
|
||||
- Better PM2 worker startup verification
|
||||
- Cross-platform compatible (pure Node.js)
|
||||
|
||||
### Technical Details
|
||||
- New: scripts/smart-install.js (smart caching installer)
|
||||
- Modified: plugin/hooks/hooks.json (use smart-install.js instead of inline npm install)
|
||||
- Modified: package.json (enhanced sync-marketplace script)
|
||||
|
||||
|
||||
## [5.0.2] - 2025-11-05
|
||||
|
||||
### Fixed
|
||||
- **Worker startup reliability**: Fixed async health checks with proper error handling
|
||||
- Added isWorkerHealthy() and waitForWorkerHealth() functions to src/shared/worker-utils.ts
|
||||
- Worker now verifies health before proceeding with hook operations
|
||||
- Improved handling of PM2 failures when not yet installed
|
||||
|
||||
### Changed
|
||||
- Changed ensureWorkerRunning() from synchronous to async with proper await
|
||||
- All hooks now await ensureWorkerRunning for reliable worker communication
|
||||
- Rebuilt all plugin executables with version 5.0.2
|
||||
|
||||
|
||||
## [5.0.1] - 2025-11-05
|
||||
|
||||
### Fixed
|
||||
- Fixed worker service stability issues
|
||||
- Enhanced worker process management and restart reliability
|
||||
- Improved session management and logging across all hooks
|
||||
- Better error handling throughout hook lifecycle
|
||||
|
||||
### Added
|
||||
- GitHub Actions workflows for automated code review
|
||||
|
||||
### Technical Details
|
||||
- Modified: src/services/worker-service.ts (stability improvements)
|
||||
- Modified: src/shared/worker-utils.ts (consistent formatting)
|
||||
- Modified: ecosystem.config.cjs (removed error/output redirection)
|
||||
- Modified: src/hooks/*-hook.ts (ensure worker running)
|
||||
- New: .github/workflows/claude-code-review.yml
|
||||
- New: .github/workflows/claude.yml
|
||||
|
||||
|
||||
## [5.0.0] - 2025-10-27
|
||||
|
||||
### BREAKING CHANGES
|
||||
- **Python dependency for optimal performance**: Semantic search requires Python for ChromaDB
|
||||
- **Search behavior prioritizes semantic relevance**: Chroma semantic search combined with SQLite temporal filtering
|
||||
- **Worker service now initializes ChromaSync on startup**: Automatic vector database synchronization
|
||||
|
||||
### Added
|
||||
- **Hybrid Search Architecture**: Combining ChromaDB semantic search with SQLite FTS5 keyword search
|
||||
- ChromaSync Service for automatic vector database synchronization (738 lines)
|
||||
- Vector embeddings for semantic similarity search
|
||||
- 90-day recency filtering for relevant results
|
||||
- Performance: Semantic search <200ms
|
||||
- **get_context_timeline** MCP tool: Get unified timeline of context around a specific point in time
|
||||
- Anchor by observation ID, session ID, or ISO timestamp
|
||||
- Configurable depth before/after anchor
|
||||
- **get_timeline_by_query** MCP tool: Search for observations and get timeline context around best match
|
||||
- Auto mode: Automatically use top search result as timeline anchor
|
||||
- Interactive mode: Show top N search results for manual anchor selection
|
||||
- **Enhanced MCP tools**: All 9 search tools now support hybrid semantic + keyword search
|
||||
|
||||
### Technical Details
|
||||
- New: src/services/sync/ChromaSync.ts (vector database sync)
|
||||
- Modified: src/servers/search-server.ts (+995 lines for hybrid search)
|
||||
- Modified: src/services/worker-service.ts (+136 lines for ChromaSync integration)
|
||||
- Modified: src/services/sqlite/SessionStore.ts (+276 lines for timeline queries)
|
||||
- Validation: 1,390 observations → 8,279 vector documents
|
||||
- Total MCP tools: 7 → 9 (added timeline tools)
|
||||
|
||||
|
||||
## [4.3.4] - 2025-10-26
|
||||
|
||||
### Fixed
|
||||
- **SessionStart hooks running on session resume**: Added matcher configuration to only run hooks on startup, clear, or compact events
|
||||
- Prevents unnecessary hook execution and improves performance
|
||||
|
||||
### Technical Details
|
||||
- Modified: plugin/hooks/hooks.json (added matcher configuration)
|
||||
|
||||
|
||||
## [4.3.3] - 2025-10-26
|
||||
|
||||
### Added
|
||||
- Made session display count configurable (DISPLAY_SESSION_COUNT = 8)
|
||||
- First-time setup detection with helpful user messaging
|
||||
- Improved UX: First install message clarifies Plugin Hook Error display
|
||||
|
||||
### Technical Details
|
||||
- Updated: src/hooks/context-hook.ts (configurable session count)
|
||||
- Updated: src/hooks/user-message-hook.ts (first-time setup detection)
|
||||
|
||||
|
||||
## [4.3.2] - 2025-10-26
|
||||
|
||||
### Added
|
||||
- **User-facing context display**: Added user-message-hook for displaying context to users via stderr
|
||||
- Hook fires simultaneously with context injection
|
||||
- Error messages don't get added to context, enabling user visibility
|
||||
- Temporary workaround until Claude Code adds ability to share messages with both user and context
|
||||
- **Comprehensive documentation** (4 new files, 2500+ lines total):
|
||||
- docs/architecture-evolution.mdx (801 lines)
|
||||
- docs/context-engineering.mdx (222 lines)
|
||||
- docs/hooks-architecture.mdx (784 lines)
|
||||
- docs/progressive-disclosure.mdx (655 lines)
|
||||
|
||||
### Fixed
|
||||
- Improved cross-platform path handling in context-hook
|
||||
|
||||
### Technical Details
|
||||
- New: src/hooks/user-message-hook.ts (stderr-based display mechanism)
|
||||
- New: plugin/scripts/user-message-hook.js (built executable)
|
||||
- Modified: plugin/hooks/hooks.json (hook configuration)
|
||||
- Modified: src/hooks/context-hook.ts (path handling)
|
||||
- Modified: scripts/build-hooks.js (build support)
|
||||
|
||||
|
||||
## [4.3.1] - 2025-10-26
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -6,7 +6,7 @@ Claude-mem is a Claude Code plugin providing persistent memory across sessions.
|
||||
|
||||
**Your Role**: You are working on the plugin itself. When users interact with Claude Code with this plugin installed, your observations get captured and become their persistent memory.
|
||||
|
||||
**Current Version**: 5.1.0
|
||||
**Current Version**: 5.1.4
|
||||
|
||||
## Critical Architecture Knowledge
|
||||
|
||||
@@ -51,9 +51,15 @@ Claude-mem is a Claude Code plugin providing persistent memory across sessions.
|
||||
- `SessionStore` = CRUD, `SessionSearch` = FTS5 queries
|
||||
|
||||
**MCP Search Server** (`src/servers/search-server.ts`)
|
||||
- Exposes 8 search tools to Claude Code
|
||||
- Exposes 9 search tools to Claude Code
|
||||
- Configured in `plugin/.mcp.json`
|
||||
- Built to `plugin/search-server.js` (ESM format)
|
||||
- Built to `plugin/search-server.mjs` (ESM format)
|
||||
|
||||
**Chroma Vector Database** (`src/services/sync/ChromaSync.ts`)
|
||||
- Hybrid semantic + keyword search architecture
|
||||
- Automatic vector embedding synchronization
|
||||
- 90-day recency filtering for relevant results
|
||||
- Combined with SQLite FTS5 for optimal search performance
|
||||
|
||||
**Viewer UI** (`src/ui/viewer/`)
|
||||
- React + TypeScript web interface accessible at http://localhost:37777
|
||||
@@ -296,8 +302,16 @@ Use this when:
|
||||
- Need complete implementation context
|
||||
- Issue might be a subtle inconsistency between files
|
||||
|
||||
## Recent Changes (v5.1.0)
|
||||
## Recent Changes
|
||||
|
||||
### v5.1.2 - Theme Toggle
|
||||
**Theme Support**: Light/dark mode for viewer UI
|
||||
- User-selectable theme with persistent settings
|
||||
- Automatic system preference detection
|
||||
- Smooth transitions between themes
|
||||
- Settings stored in browser localStorage
|
||||
|
||||
### v5.1.0 - Web-Based Viewer UI
|
||||
**Major Feature**: Web-Based Viewer UI for Real-Time Memory Stream
|
||||
- Production-ready viewer accessible at http://localhost:37777
|
||||
- Real-time visualization via Server-Sent Events (SSE) - see observations, sessions, and prompts as they happen
|
||||
@@ -307,15 +321,29 @@ Use this when:
|
||||
- Auto-reconnection with exponential backoff
|
||||
- GPU-accelerated animations for smooth interactions
|
||||
|
||||
**New Worker Endpoints** (8 HTTP/SSE endpoints, +500 lines):
|
||||
- `/api/prompts` - Paginated user prompts with project filtering
|
||||
- `/api/observations` - Paginated observations with project filtering
|
||||
- `/api/summaries` - Paginated session summaries with project filtering
|
||||
- `/api/stats` - Database statistics (total counts by project)
|
||||
- `/api/projects` - List of unique project names
|
||||
- `/stream` - Server-Sent Events for real-time updates
|
||||
- `/` - Serves viewer HTML
|
||||
- `/health` - Health check endpoint
|
||||
**Worker Service API Endpoints** (14 HTTP/SSE endpoints total):
|
||||
|
||||
*Viewer & Health:*
|
||||
- `GET /` - Serves viewer HTML (self-contained React app)
|
||||
- `GET /health` - Health check endpoint
|
||||
- `GET /stream` - Server-Sent Events for real-time updates
|
||||
|
||||
*Data Retrieval:*
|
||||
- `GET /api/prompts` - Paginated user prompts with project filtering
|
||||
- `GET /api/observations` - Paginated observations with project filtering
|
||||
- `GET /api/summaries` - Paginated session summaries with project filtering
|
||||
- `GET /api/stats` - Database statistics (total counts by project)
|
||||
|
||||
*Settings:*
|
||||
- `GET /api/settings` - Get current viewer settings
|
||||
- `POST /api/settings` - Update viewer settings
|
||||
|
||||
*Session Management:*
|
||||
- `POST /sessions/:sessionDbId/init` - Initialize new session
|
||||
- `POST /sessions/:sessionDbId/observations` - Add observations to session
|
||||
- `POST /sessions/:sessionDbId/summarize` - Generate session summary
|
||||
- `GET /sessions/:sessionDbId/status` - Get session status
|
||||
- `DELETE /sessions/:sessionDbId` - Delete session (graceful cleanup)
|
||||
|
||||
**Database Enhancements** (+98 lines in SessionStore):
|
||||
- `getRecentPrompts()` - Paginated prompts with OFFSET/LIMIT
|
||||
@@ -333,12 +361,21 @@ Use this when:
|
||||
|
||||
**Why This Matters**: Users can now visualize their memory stream in real-time. See exactly what claude-mem is capturing as you work, filter by project, and understand the context being injected into sessions.
|
||||
|
||||
### Previous Release (v5.0.3)
|
||||
|
||||
### v5.0.3 - Smart Install Caching
|
||||
**Smart Caching Installer for Windows Compatibility**:
|
||||
- Eliminated redundant npm install on every SessionStart (2-5s → 10ms)
|
||||
- Caches version in `.install-version` file
|
||||
- Only runs npm install when actually needed (first time, version change, missing deps)
|
||||
- 200x performance improvement for cached installations
|
||||
|
||||
### v5.0.0 - Hybrid Search Architecture
|
||||
**Major Feature**: Chroma Vector Database Integration
|
||||
- Hybrid semantic + keyword search combining ChromaDB with SQLite FTS5
|
||||
- ChromaSync service for automatic vector embedding synchronization (738 lines)
|
||||
- 90-day recency filtering for contextually relevant results
|
||||
- New MCP tools: `get_context_timeline` and `get_timeline_by_query`
|
||||
- Performance: Semantic search <200ms with 8,000+ vector documents
|
||||
- Enhanced all 9 MCP search tools with hybrid search capabilities
|
||||
|
||||
## Configuration Users Can Set
|
||||
|
||||
@@ -388,11 +425,12 @@ Real-time visibility into memory stream helps users understand what's being capt
|
||||
|
||||
## File Locations
|
||||
|
||||
**Source**: `/Users/alexnewman/Scripts/claude-mem/src/`
|
||||
**Built Plugin**: `/Users/alexnewman/Scripts/claude-mem/plugin/`
|
||||
**Installed Plugin**: `~/.claude/plugins/marketplaces/thedotmack/`
|
||||
**Database**: `~/.claude-mem/claude-mem.db`
|
||||
**Usage Logs**: `~/.claude-mem/usage-logs/usage-YYYY-MM-DD.jsonl`
|
||||
**Source**: `<project-root>/src/` - TypeScript source files
|
||||
**Built Plugin**: `<project-root>/plugin/` - Compiled JavaScript outputs
|
||||
**Installed Plugin**: `~/.claude/plugins/marketplaces/thedotmack/` - User's installed plugin location
|
||||
**Database**: `~/.claude-mem/claude-mem.db` - SQLite database with observations, sessions, summaries
|
||||
**Chroma Database**: `~/.claude-mem/chroma/` - Vector embeddings for semantic search
|
||||
**Usage Logs**: `~/.claude-mem/usage-logs/usage-YYYY-MM-DD.jsonl` - Daily API usage tracking
|
||||
|
||||
## Quick Reference
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
|
||||
</a>
|
||||
<a href="package.json">
|
||||
<img src="https://img.shields.io/badge/version-4.3.1-green.svg" alt="Version">
|
||||
<img src="https://img.shields.io/badge/version-5.1.2-green.svg" alt="Version">
|
||||
</a>
|
||||
<a href="package.json">
|
||||
<img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg" alt="Node">
|
||||
@@ -58,7 +58,8 @@ Restart Claude Code. Context from previous sessions will automatically appear in
|
||||
**Key Features:**
|
||||
- 🧠 **Persistent Memory** - Context survives across sessions
|
||||
- 📊 **Progressive Disclosure** - Layered memory retrieval with token cost visibility
|
||||
- 🔍 **7 Search Tools** - Query your project history via MCP
|
||||
- 🔍 **9 Search Tools** - Query your project history via MCP
|
||||
- 🖥️ **Web Viewer UI** - Real-time memory stream at http://localhost:37777
|
||||
- 🤖 **Automatic Operation** - No manual intervention required
|
||||
- 🔗 **Citations** - Reference past decisions with `claude-mem://` URIs
|
||||
|
||||
@@ -85,12 +86,13 @@ npx mintlify dev
|
||||
|
||||
### Architecture
|
||||
- **[Overview](docs/architecture/overview.mdx)** - System components & data flow
|
||||
- **[Architecture Evolution](docs/architecture-evolution.mdx)** - The journey from v3 to v4
|
||||
- **[Architecture Evolution](docs/architecture-evolution.mdx)** - The journey from v3 to v5
|
||||
- **[Hooks Architecture](docs/hooks-architecture.mdx)** - How Claude-Mem uses lifecycle hooks
|
||||
- **[Hooks Reference](docs/architecture/hooks.mdx)** - 5 lifecycle hooks explained
|
||||
- **[Hooks Reference](docs/architecture/hooks.mdx)** - 7 hook scripts explained
|
||||
- **[Worker Service](docs/architecture/worker-service.mdx)** - HTTP API & PM2 management
|
||||
- **[Database](docs/architecture/database.mdx)** - SQLite schema & FTS5 search
|
||||
- **[MCP Search](docs/architecture/mcp-search.mdx)** - 7 search tools & examples
|
||||
- **[MCP Search](docs/architecture/mcp-search.mdx)** - 9 search tools & examples
|
||||
- **[Viewer UI](docs/VIEWER.md)** - Web-based memory stream visualization
|
||||
|
||||
### Configuration & Development
|
||||
- **[Configuration](docs/configuration.mdx)** - Environment variables & settings
|
||||
@@ -103,7 +105,7 @@ npx mintlify dev
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Session Start → Inject context from last 10 sessions │
|
||||
│ Session Start → Inject recent observations as context │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
@@ -124,10 +126,11 @@ npx mintlify dev
|
||||
```
|
||||
|
||||
**Core Components:**
|
||||
1. **5 Lifecycle Hooks** - SessionStart, UserPromptSubmit, PostToolUse, Stop, SessionEnd
|
||||
2. **Worker Service** - HTTP API on port 37777 managed by PM2
|
||||
3. **SQLite Database** - Stores sessions, observations, summaries with FTS5 search
|
||||
4. **7 MCP Search Tools** - Query historical context with citations
|
||||
1. **7 Lifecycle Hook Scripts** - smart-install, context-hook, user-message-hook, new-hook, save-hook, summary-hook, cleanup-hook
|
||||
2. **Worker Service** - HTTP API on port 37777 with web viewer UI, managed by PM2
|
||||
3. **SQLite Database** - Stores sessions, observations, summaries with FTS5 full-text search
|
||||
4. **9 MCP Search Tools** - Query historical context with citations and timeline analysis
|
||||
5. **Chroma Vector Database** - Hybrid semantic + keyword search for intelligent context retrieval
|
||||
|
||||
See [Architecture Overview](docs/architecture/overview.mdx) for details.
|
||||
|
||||
@@ -135,15 +138,17 @@ See [Architecture Overview](docs/architecture/overview.mdx) for details.
|
||||
|
||||
## MCP Search Tools
|
||||
|
||||
Claude-Mem provides 7 specialized search tools:
|
||||
Claude-Mem provides 9 specialized search tools:
|
||||
|
||||
1. **search_observations** - Full-text search across observations
|
||||
2. **search_sessions** - Full-text search across session summaries
|
||||
3. **search_user_prompts** - Search raw user requests
|
||||
4. **find_by_concept** - Find by concept tags
|
||||
5. **find_by_file** - Find by file references
|
||||
6. **find_by_type** - Find by type (decision, bugfix, feature, etc.)
|
||||
7. **get_recent_context** - Get recent session context
|
||||
4. **find_by_concept** - Find by concept tags (discovery, problem-solution, pattern, etc.)
|
||||
5. **find_by_file** - Find observations referencing specific files
|
||||
6. **find_by_type** - Find by type (decision, bugfix, feature, refactor, discovery, change)
|
||||
7. **get_recent_context** - Get recent session context for a project
|
||||
8. **get_context_timeline** - Get unified timeline of context around a specific point in time
|
||||
9. **get_timeline_by_query** - Search for observations and get timeline context around best match
|
||||
|
||||
**Example Queries:**
|
||||
```
|
||||
@@ -151,24 +156,39 @@ search_observations with query="authentication" and type="decision"
|
||||
find_by_file with filePath="worker-service.ts"
|
||||
search_user_prompts with query="add dark mode"
|
||||
get_recent_context with limit=5
|
||||
get_context_timeline with anchor="S890" depth_before=10 depth_after=10
|
||||
get_timeline_by_query with query="viewer UI implementation" mode="auto"
|
||||
```
|
||||
|
||||
See [MCP Search Tools Guide](docs/usage/search-tools.mdx) for detailed examples.
|
||||
|
||||
---
|
||||
|
||||
## What's New in v4.3.1
|
||||
## What's New in v5.1.2
|
||||
|
||||
**Critical Fix:**
|
||||
- **SessionStart hook context injection**: Fixed context not being injected into new sessions
|
||||
- npm install output was polluting hook JSON responses
|
||||
- Changed npm loglevel to `--loglevel=silent` for clean output
|
||||
- Context injection now works reliably across all sessions
|
||||
**🎨 Theme Toggle (v5.1.2):**
|
||||
- Light/dark mode support in viewer UI
|
||||
- System preference detection
|
||||
- Persistent theme settings across sessions
|
||||
- Smooth transitions between themes
|
||||
|
||||
**Code Quality:**
|
||||
- Consolidated hooks architecture by removing wrapper layer
|
||||
- Fixed double shebang issues in hook executables
|
||||
- Simplified codebase maintenance
|
||||
**🖥️ Web-Based Viewer UI (v5.1.0):**
|
||||
- Real-time memory stream visualization at http://localhost:37777
|
||||
- Server-Sent Events (SSE) for instant updates
|
||||
- Infinite scroll pagination with automatic deduplication
|
||||
- Project filtering to focus on specific codebases
|
||||
- Settings persistence (sidebar state, selected project)
|
||||
- Auto-reconnection with exponential backoff
|
||||
|
||||
**⚡ Smart Install Caching (v5.0.3):**
|
||||
- Eliminated redundant npm installs on every session (2-5s → 10ms)
|
||||
- Caches version in `.install-version` file
|
||||
- Only runs npm install when needed (first time, version change, missing deps)
|
||||
|
||||
**🔍 Hybrid Search Architecture (v5.0.0):**
|
||||
- Chroma vector database for semantic search
|
||||
- Combined with FTS5 keyword search
|
||||
- Intelligent context retrieval with 90-day recency filtering
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for complete version history.
|
||||
|
||||
|
||||
+542
@@ -0,0 +1,542 @@
|
||||
# Chroma Vector Database - Hybrid Semantic Search
|
||||
|
||||
## Overview
|
||||
|
||||
Claude-Mem v5.0.0 introduced **Chroma**, a vector database that enables semantic search across your memory stream. Combined with SQLite's FTS5 keyword search, this creates a powerful **hybrid search architecture** that finds contextually relevant observations using both meaning and keywords.
|
||||
|
||||
**Key Benefits:**
|
||||
- 🧠 **Semantic Search** - Find observations by meaning, not just keywords
|
||||
- 🔍 **Hybrid Architecture** - Combines semantic similarity with keyword matching
|
||||
- ⏱️ **Recency Filtering** - Focus on recent 90 days for relevant context
|
||||
- ⚡ **Fast Performance** - Semantic search under 200ms with 8,000+ documents
|
||||
- 🔄 **Auto-Sync** - ChromaSync service keeps vectors updated automatically
|
||||
|
||||
## What is Chroma?
|
||||
|
||||
[ChromaDB](https://www.trychroma.com/) is an open-source vector database designed for AI applications. It stores text as **vector embeddings** - mathematical representations that capture semantic meaning.
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Query: "authentication bug"
|
||||
Keyword Match: Must contain both "authentication" AND "bug"
|
||||
Semantic Match: Also finds "login error", "auth failure", "sign-in issue"
|
||||
```
|
||||
|
||||
Semantic search understands that "authentication bug" is conceptually similar to "login error" even though they share no keywords.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Hybrid Search Flow
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ User Query: "How does authentication work?" │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────┴─────────────────┐
|
||||
↓ ↓
|
||||
┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ Chroma Semantic │ │ SQLite FTS5 │
|
||||
│ Vector Similarity │ │ Keyword Search │
|
||||
│ │ │ │
|
||||
│ Finds conceptually │ │ Finds exact/fuzzy │
|
||||
│ similar observations │ │ keyword matches │
|
||||
└──────────────────────┘ └──────────────────────┘
|
||||
↓ ↓
|
||||
└─────────────────┬─────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────┐
|
||||
│ Merge Results │
|
||||
│ - Deduplicate by ID │
|
||||
│ - Sort by relevance + recency │
|
||||
│ - Filter by 90-day window │
|
||||
└─────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────┐
|
||||
│ Return Top Matches │
|
||||
│ Semantic + Keyword combined │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
### ChromaSync Service
|
||||
|
||||
The **ChromaSync** service (`src/services/sync/ChromaSync.ts`) automatically synchronizes observations to Chroma:
|
||||
|
||||
**When Observations Are Synced:**
|
||||
1. **Session Summary** - After each session completes, all new observations synced
|
||||
2. **Worker Startup** - On initialization, checks for unsynced observations
|
||||
3. **Manual Trigger** - Can force sync via internal API (development only)
|
||||
|
||||
**What Gets Embedded:**
|
||||
- Observation ID (unique identifier)
|
||||
- Title (compressed learning statement)
|
||||
- Narrative (detailed explanation)
|
||||
- Project path (for project-specific filtering)
|
||||
- Timestamp (for recency filtering)
|
||||
- Concepts (semantic tags)
|
||||
- File references (associated code files)
|
||||
|
||||
**Embedding Model:**
|
||||
- Currently using Chroma's default embedding function
|
||||
- Future: Configurable embedding models (e.g., OpenAI, sentence-transformers)
|
||||
|
||||
### Data Structure
|
||||
|
||||
**SQLite (Source of Truth):**
|
||||
```sql
|
||||
CREATE TABLE observations (
|
||||
id INTEGER PRIMARY KEY,
|
||||
title TEXT,
|
||||
narrative TEXT,
|
||||
facts TEXT,
|
||||
concepts TEXT,
|
||||
files TEXT,
|
||||
type TEXT,
|
||||
projectPath TEXT,
|
||||
createdAt INTEGER
|
||||
);
|
||||
```
|
||||
|
||||
**Chroma (Vector Embeddings):**
|
||||
```typescript
|
||||
{
|
||||
ids: ["obs_12345"],
|
||||
embeddings: [[0.123, -0.456, ...]], // 384-dimensional vector
|
||||
documents: ["Title: Authentication flow\nNarrative: Implemented..."],
|
||||
metadatas: [{
|
||||
type: "feature",
|
||||
project: "claude-mem",
|
||||
timestamp: 1698765432000,
|
||||
concepts: "pattern,architecture"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## How Semantic Search Works
|
||||
|
||||
### Vector Embeddings
|
||||
|
||||
Text converted to high-dimensional vectors that capture meaning:
|
||||
|
||||
```
|
||||
"user authentication" → [0.12, -0.34, 0.56, ..., 0.78]
|
||||
"login system" → [0.15, -0.32, 0.54, ..., 0.81]
|
||||
"database schema" → [-0.45, 0.67, -0.23, ..., 0.12]
|
||||
```
|
||||
|
||||
Notice: "user authentication" and "login system" have similar vectors (close in vector space), while "database schema" is distant.
|
||||
|
||||
### Similarity Search
|
||||
|
||||
Chroma uses **cosine similarity** to find nearest neighbors:
|
||||
|
||||
```typescript
|
||||
// Query embedding
|
||||
query: "authentication bug"
|
||||
query_vector: [0.14, -0.33, 0.55, ..., 0.79]
|
||||
|
||||
// Find observations with similar vectors
|
||||
results = chroma.query(
|
||||
query_vector,
|
||||
n_results: 10,
|
||||
where: { timestamp: { $gte: now - 90_days } }
|
||||
)
|
||||
```
|
||||
|
||||
**Result Ranking:**
|
||||
- Higher cosine similarity = more semantically similar
|
||||
- Filtered by 90-day recency window
|
||||
- Combined with keyword matches from FTS5
|
||||
|
||||
## 90-Day Recency Filtering
|
||||
|
||||
Why 90 days?
|
||||
|
||||
**Rationale:**
|
||||
- Recent context more likely relevant to current work
|
||||
- Prevents very old observations from diluting results
|
||||
- Balances completeness with relevance
|
||||
- Reduces vector search space for faster queries
|
||||
|
||||
**Implementation:**
|
||||
```typescript
|
||||
const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);
|
||||
|
||||
// Chroma metadata filter
|
||||
where: {
|
||||
timestamp: { $gte: ninetyDaysAgo }
|
||||
}
|
||||
|
||||
// SQLite WHERE clause
|
||||
WHERE createdAt >= ?
|
||||
```
|
||||
|
||||
**Configurable?**
|
||||
- Not currently user-configurable
|
||||
- Hard-coded in `src/servers/search-server.ts`
|
||||
- Future: Add `CLAUDE_MEM_RECENCY_DAYS` environment variable
|
||||
|
||||
## MCP Tool Integration
|
||||
|
||||
All 9 MCP search tools benefit from hybrid search:
|
||||
|
||||
### search_observations (Hybrid)
|
||||
|
||||
```typescript
|
||||
// Keyword-only (v4.x)
|
||||
search_observations(query: "authentication")
|
||||
// Returns: Observations containing "authentication"
|
||||
|
||||
// Hybrid semantic + keyword (v5.x)
|
||||
search_observations(query: "authentication")
|
||||
// Returns: Observations with "authentication" PLUS semantically similar:
|
||||
// - "login system"
|
||||
// - "user credentials"
|
||||
// - "session management"
|
||||
```
|
||||
|
||||
### get_timeline_by_query (Semantic-First)
|
||||
|
||||
```typescript
|
||||
// Uses Chroma to find best match, then builds timeline
|
||||
get_timeline_by_query(
|
||||
query: "when did we implement the viewer UI?",
|
||||
mode: "auto",
|
||||
depth_before: 10,
|
||||
depth_after: 10
|
||||
)
|
||||
|
||||
// Chroma finds: Observation #4057 "Web-Based Viewer UI for Real-Time Memory Stream"
|
||||
// Returns: Timeline with 10 observations before + anchor + 10 after
|
||||
```
|
||||
|
||||
### Benefits Across All Tools
|
||||
|
||||
- **find_by_concept**: Semantic similarity finds related concepts
|
||||
- **find_by_file**: Finds semantically similar code changes
|
||||
- **find_by_type**: Better relevance ranking within type
|
||||
- **get_recent_context**: Prioritizes semantically relevant recent context
|
||||
|
||||
## Performance
|
||||
|
||||
### Benchmarks (8,279 vector documents)
|
||||
|
||||
| Operation | Time | Notes |
|
||||
|-----------|------|-------|
|
||||
| **Semantic Query** | 150-200ms | 90-day window, top 10 results |
|
||||
| **Keyword Query (FTS5)** | 5-10ms | Full-text search |
|
||||
| **Hybrid Query** | 160-220ms | Combined semantic + keyword |
|
||||
| **Initial Sync** | 2-5 min | First-time embedding of all observations |
|
||||
| **Incremental Sync** | 100-500ms | 1-10 new observations per session |
|
||||
|
||||
### Memory Usage
|
||||
|
||||
- **Chroma DB Size**: ~50MB for 8,000 observations
|
||||
- **Embeddings**: 384 dimensions × 4 bytes = 1.5KB per observation
|
||||
- **Metadata**: ~500 bytes per observation (project, type, timestamp)
|
||||
- **Total**: ~2KB per observation in Chroma
|
||||
|
||||
### Optimization Tips
|
||||
|
||||
1. **Reduce vector dimensions**: Use smaller embedding models (future)
|
||||
2. **Adjust recency window**: Narrow to 30/60 days for faster queries
|
||||
3. **Limit result count**: Request fewer results (n_results=5 vs 10)
|
||||
4. **Project filtering**: Add project filter to metadata query
|
||||
|
||||
## Installation & Dependencies
|
||||
|
||||
### Python Requirement
|
||||
|
||||
Chroma requires Python 3.7+ installed:
|
||||
|
||||
**Check Python:**
|
||||
```bash
|
||||
python3 --version
|
||||
# Should show: Python 3.7.x or higher
|
||||
```
|
||||
|
||||
**Install Python (if needed):**
|
||||
- **macOS**: `brew install python3`
|
||||
- **Windows**: Download from [python.org](https://www.python.org/downloads/)
|
||||
- **Linux**: `apt-get install python3` or `yum install python3`
|
||||
|
||||
### ChromaDB Installation
|
||||
|
||||
Chroma installed automatically as npm dependency:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# Installs: chromadb (Python package via node-gyp bindings)
|
||||
```
|
||||
|
||||
**Manual Installation (if auto-install fails):**
|
||||
```bash
|
||||
pip3 install chromadb
|
||||
```
|
||||
|
||||
### Troubleshooting Installation
|
||||
|
||||
**Error: "Python not found"**
|
||||
```bash
|
||||
# Set Python path explicitly
|
||||
export PYTHON=/usr/local/bin/python3
|
||||
npm install
|
||||
```
|
||||
|
||||
**Error: "chromadb module not found"**
|
||||
```bash
|
||||
# Reinstall chromadb
|
||||
pip3 install --upgrade chromadb
|
||||
|
||||
# Verify installation
|
||||
python3 -c "import chromadb; print(chromadb.__version__)"
|
||||
```
|
||||
|
||||
**Error: "node-gyp build failed"**
|
||||
```bash
|
||||
# Install build tools
|
||||
# macOS: xcode-select --install
|
||||
# Windows: npm install --global windows-build-tools
|
||||
# Linux: apt-get install build-essential
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Currently no user-configurable settings. Future options:
|
||||
|
||||
```json
|
||||
// Proposed for future versions
|
||||
{
|
||||
"env": {
|
||||
"CLAUDE_MEM_CHROMA_ENABLED": "true", // Enable/disable Chroma
|
||||
"CLAUDE_MEM_CHROMA_PATH": "~/.claude-mem/chroma", // DB location
|
||||
"CLAUDE_MEM_EMBEDDING_MODEL": "default", // Embedding model choice
|
||||
"CLAUDE_MEM_RECENCY_DAYS": "90", // Recency window
|
||||
"CLAUDE_MEM_VECTOR_DIM": "384" // Embedding dimensions
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Disabling Chroma (Future)
|
||||
|
||||
To disable semantic search and use keyword-only:
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"CLAUDE_MEM_CHROMA_ENABLED": "false"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Falls back to SQLite FTS5 keyword search only.
|
||||
|
||||
## Database Maintenance
|
||||
|
||||
### Location
|
||||
|
||||
```
|
||||
~/.claude-mem/chroma/
|
||||
├── chroma.sqlite3 # Chroma metadata database
|
||||
└── index/ # Vector index files
|
||||
└── *.bin # Binary vector data
|
||||
```
|
||||
|
||||
### Backup
|
||||
|
||||
```bash
|
||||
# Backup entire Chroma directory
|
||||
cp -r ~/.claude-mem/chroma ~/.claude-mem/chroma.backup
|
||||
|
||||
# Restore from backup
|
||||
rm -rf ~/.claude-mem/chroma
|
||||
cp -r ~/.claude-mem/chroma.backup ~/.claude-mem/chroma
|
||||
```
|
||||
|
||||
### Reset Chroma (Force Resync)
|
||||
|
||||
```bash
|
||||
# Delete Chroma database
|
||||
rm -rf ~/.claude-mem/chroma
|
||||
|
||||
# Restart worker to trigger full resync
|
||||
npm run worker:restart
|
||||
|
||||
# Check logs for sync progress
|
||||
npm run worker:logs
|
||||
```
|
||||
|
||||
**Note**: Resync can take 2-5 minutes for thousands of observations.
|
||||
|
||||
### Disk Space Management
|
||||
|
||||
**Chroma grows with observations:**
|
||||
- 1,000 observations ≈ 5MB
|
||||
- 10,000 observations ≈ 50MB
|
||||
- 100,000 observations ≈ 500MB
|
||||
|
||||
**Cleanup old observations:**
|
||||
```sql
|
||||
-- Delete observations older than 1 year
|
||||
-- This will trigger Chroma resync on next startup
|
||||
sqlite3 ~/.claude-mem/claude-mem.db \
|
||||
"DELETE FROM observations WHERE createdAt < strftime('%s', 'now', '-1 year') * 1000;"
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Direct Chroma Queries (Development)
|
||||
|
||||
For debugging or custom queries:
|
||||
|
||||
```typescript
|
||||
import { ChromaSync } from './services/sync/ChromaSync';
|
||||
|
||||
const sync = new ChromaSync();
|
||||
await sync.initialize();
|
||||
|
||||
// Query Chroma directly
|
||||
const results = await sync.query({
|
||||
queryTexts: ["authentication implementation"],
|
||||
nResults: 10,
|
||||
where: {
|
||||
type: "feature",
|
||||
timestamp: { $gte: Date.now() - 90_days }
|
||||
}
|
||||
});
|
||||
|
||||
console.log(results.ids, results.distances, results.documents);
|
||||
```
|
||||
|
||||
### Custom Embedding Models (Future)
|
||||
|
||||
Chroma supports multiple embedding models:
|
||||
|
||||
```typescript
|
||||
// Future configuration
|
||||
const sync = new ChromaSync({
|
||||
embeddingModel: "sentence-transformers/all-MiniLM-L6-v2", // Smaller, faster
|
||||
// or: "text-embedding-ada-002" (OpenAI, requires API key)
|
||||
// or: "all-mpnet-base-v2" (Higher quality, slower)
|
||||
});
|
||||
```
|
||||
|
||||
### Metadata Filtering
|
||||
|
||||
Chroma supports advanced metadata queries:
|
||||
|
||||
```typescript
|
||||
// Find observations by type and project
|
||||
results = await sync.query({
|
||||
queryTexts: ["API design"],
|
||||
where: {
|
||||
$and: [
|
||||
{ type: { $in: ["decision", "feature"] } },
|
||||
{ project: "claude-mem" }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Find recent observations
|
||||
results = await sync.query({
|
||||
queryTexts: ["database schema"],
|
||||
where: {
|
||||
timestamp: { $gte: Date.now() - 30_days }
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Comparison: Semantic vs Keyword Search
|
||||
|
||||
| Aspect | Semantic (Chroma) | Keyword (FTS5) |
|
||||
|--------|-------------------|----------------|
|
||||
| **Speed** | 150-200ms | 5-10ms |
|
||||
| **Accuracy** | High (meaning-based) | Medium (exact match) |
|
||||
| **Storage** | ~2KB per observation | ~500 bytes per observation |
|
||||
| **Conceptual Matching** | ✅ Yes | ❌ No |
|
||||
| **Exact Match** | ❌ Not guaranteed | ✅ Always |
|
||||
| **Typo Tolerance** | ✅ High | ⚠️ Limited (fuzzy) |
|
||||
| **Dependencies** | Python + chromadb | None (SQLite built-in) |
|
||||
| **Recency Bias** | ✅ Built-in (90 days) | Manual filtering |
|
||||
|
||||
**Best Practice:** Use hybrid search (both) for optimal results.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Chroma not found" Error
|
||||
|
||||
**Symptom:** Worker logs show "Chroma not available, using keyword-only search"
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check Python installation
|
||||
python3 --version
|
||||
|
||||
# Reinstall chromadb
|
||||
pip3 install chromadb
|
||||
|
||||
# Restart worker
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
### Slow Query Performance
|
||||
|
||||
**Symptom:** Searches taking >1 second
|
||||
|
||||
**Solutions:**
|
||||
1. Reduce recency window (edit `src/servers/search-server.ts`)
|
||||
2. Limit result count (`nResults: 5` instead of 10)
|
||||
3. Add project filter to narrow search space
|
||||
4. Check Chroma index size (may need rebuild)
|
||||
|
||||
### Out of Memory Errors
|
||||
|
||||
**Symptom:** Worker crashes with "JavaScript heap out of memory"
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Increase Node.js heap size
|
||||
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
# Restart worker
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
### Sync Taking Too Long
|
||||
|
||||
**Symptom:** Initial Chroma sync takes >10 minutes
|
||||
|
||||
**Possible Causes:**
|
||||
- Large number of observations (>10,000)
|
||||
- Slow embedding model
|
||||
- Limited CPU resources
|
||||
|
||||
**Solutions:**
|
||||
1. Let it complete (one-time cost)
|
||||
2. Delete very old observations to reduce count
|
||||
3. Close resource-intensive apps during sync
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements for future versions:
|
||||
|
||||
- **Configurable Recency**: User-defined recency window (30/60/90/365 days)
|
||||
- **Custom Embeddings**: Choose embedding model (quality vs speed trade-off)
|
||||
- **Incremental Updates**: Update existing vectors instead of full resync
|
||||
- **Semantic Filters**: Search by semantic concept ("all architectural decisions")
|
||||
- **Multi-Language Support**: Embeddings optimized for non-English code/docs
|
||||
- **Clustering**: Auto-cluster related observations for discovery
|
||||
- **Visualization**: 2D/3D visualization of vector space (similar observations near each other)
|
||||
|
||||
## Resources
|
||||
|
||||
- **ChromaDB Documentation**: https://docs.trychroma.com/
|
||||
- **Source Code**: `src/services/sync/ChromaSync.ts`
|
||||
- **Search Server**: `src/servers/search-server.ts`
|
||||
- **Python Package**: https://pypi.org/project/chromadb/
|
||||
|
||||
---
|
||||
|
||||
**Powered by ChromaDB** | **Hybrid Semantic + Keyword Search** | **90-Day Recency Window**
|
||||
+405
@@ -0,0 +1,405 @@
|
||||
# Viewer UI - Web-Based Memory Stream Visualization
|
||||
|
||||
## Overview
|
||||
|
||||
The Claude-Mem Viewer UI is a production-ready web interface that provides real-time visualization of your memory stream. Access it at **http://localhost:37777** while the claude-mem worker is running.
|
||||
|
||||
**Key Features:**
|
||||
- 🔴 **Real-time Updates** - Server-Sent Events (SSE) stream new observations, sessions, and prompts instantly
|
||||
- 📜 **Infinite Scroll** - Load historical data progressively with automatic pagination
|
||||
- 🎯 **Project Filtering** - Focus on specific codebases with smart project selection
|
||||
- 🎨 **Theme Toggle** - Light, dark, or system preference with persistent settings
|
||||
- 💾 **Settings Persistence** - Sidebar state and project filters saved automatically
|
||||
- 🔄 **Auto-Reconnection** - Exponential backoff ensures connection stability
|
||||
- ⚡ **GPU Acceleration** - Smooth animations and transitions
|
||||
|
||||
## Architecture
|
||||
|
||||
### Technology Stack
|
||||
|
||||
| Component | Technology | Purpose |
|
||||
|-----------|-----------|---------|
|
||||
| **Framework** | React + TypeScript | Component-based UI with type safety |
|
||||
| **Build System** | esbuild | Self-contained HTML bundle (no separate assets) |
|
||||
| **Real-time** | Server-Sent Events (SSE) | Push-based updates from worker service |
|
||||
| **State Management** | React hooks | Local state with custom hooks for SSE, pagination, settings |
|
||||
| **Styling** | Inline CSS | No external stylesheets, fully self-contained |
|
||||
| **Typography** | Monaspace Radon | Embedded monospace font for code aesthetics |
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
src/ui/viewer/
|
||||
├── App.tsx # Main application component
|
||||
├── types.ts # TypeScript interfaces
|
||||
├── components/
|
||||
│ ├── Header.tsx # Top navigation with logo and theme toggle
|
||||
│ ├── Sidebar.tsx # Project filter and stats sidebar
|
||||
│ ├── Feed.tsx # Main feed with infinite scroll
|
||||
│ ├── ThemeToggle.tsx # Light/dark/system theme selector
|
||||
│ └── cards/
|
||||
│ ├── ObservationCard.tsx # Displays individual observations
|
||||
│ ├── SummaryCard.tsx # Displays session summaries
|
||||
│ ├── PromptCard.tsx # Displays user prompts
|
||||
│ └── SkeletonCard.tsx # Loading placeholder
|
||||
├── hooks/
|
||||
│ ├── useSSE.ts # Server-Sent Events connection
|
||||
│ ├── usePagination.ts # Infinite scroll logic
|
||||
│ ├── useSettings.ts # Settings persistence
|
||||
│ ├── useStats.ts # Database statistics
|
||||
│ └── useTheme.ts # Theme management
|
||||
└── utils/
|
||||
├── constants.ts # Configuration constants
|
||||
├── data.ts # Data merging and deduplication
|
||||
└── formatters.ts # Date/time formatting helpers
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Worker Service (port 37777) │
|
||||
│ - Express HTTP API │
|
||||
│ - SSE endpoint: /stream │
|
||||
│ - REST endpoints: /api/* │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Viewer UI (React App) │
|
||||
│ - useSSE hook: Real-time stream │
|
||||
│ - usePagination hook: Historical data │
|
||||
│ - useSettings hook: Persistent preferences │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Feed Component │
|
||||
│ - Merges real-time + paginated data │
|
||||
│ - Deduplicates by ID │
|
||||
│ - Filters by selected project │
|
||||
│ - Infinite scroll triggers pagination │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Features In Detail
|
||||
|
||||
### Real-Time Updates (SSE)
|
||||
|
||||
The viewer uses Server-Sent Events to receive updates instantly:
|
||||
|
||||
```typescript
|
||||
// SSE message format
|
||||
{
|
||||
"type": "observation" | "summary" | "prompt" | "projects" | "processing",
|
||||
"data": { /* record data */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Event Types:**
|
||||
- `observation` - New observation created
|
||||
- `summary` - Session summary generated
|
||||
- `prompt` - User prompt captured
|
||||
- `projects` - Project list updated
|
||||
- `processing` - Session processing status changed
|
||||
|
||||
**Connection Management:**
|
||||
- Auto-reconnect on disconnect with exponential backoff
|
||||
- Visual connection status indicator in header
|
||||
- Graceful degradation if SSE unavailable
|
||||
|
||||
### Infinite Scroll Pagination
|
||||
|
||||
The feed loads historical data progressively:
|
||||
|
||||
1. **Initial Load**: First 20 records loaded on mount
|
||||
2. **Scroll Trigger**: When user scrolls to 80% of feed height
|
||||
3. **Batch Load**: Next 20 records fetched via `/api/{type}?offset=X&limit=20`
|
||||
4. **Deduplication**: Merges with real-time data, removes duplicates by ID
|
||||
5. **Loading State**: Skeleton cards show while fetching
|
||||
|
||||
**Performance:**
|
||||
- Requests debounced to prevent spam
|
||||
- Only visible when scrolled near bottom
|
||||
- Continues until no more records available
|
||||
|
||||
### Project Filtering
|
||||
|
||||
Filter memory stream by specific projects:
|
||||
|
||||
1. Projects extracted from observations, summaries, and prompts
|
||||
2. Sidebar shows all unique project names with counts
|
||||
3. Click project name to filter feed
|
||||
4. Click "All Projects" to clear filter
|
||||
5. Filter persisted to localStorage
|
||||
|
||||
**Project Detection:**
|
||||
- Extracted from `projectPath` or `project` field in records
|
||||
- Basename of path used as project name
|
||||
- Empty/null projects shown as "(No Project)"
|
||||
|
||||
### Theme Toggle (v5.1.2)
|
||||
|
||||
Three theme modes available:
|
||||
|
||||
- **Light Mode**: Clean white background, dark text
|
||||
- **Dark Mode**: Dark background, light text (default)
|
||||
- **System**: Matches OS preference automatically
|
||||
|
||||
**Implementation:**
|
||||
```typescript
|
||||
// Theme preference stored in localStorage
|
||||
localStorage.setItem('theme-preference', 'light' | 'dark' | 'system');
|
||||
|
||||
// CSS variables updated dynamically
|
||||
document.documentElement.setAttribute('data-theme', resolvedTheme);
|
||||
```
|
||||
|
||||
**CSS Variables:**
|
||||
```css
|
||||
:root[data-theme="light"] {
|
||||
--bg-primary: #ffffff;
|
||||
--text-primary: #1f2937;
|
||||
/* ... */
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg-primary: #111827;
|
||||
--text-primary: #f9fafb;
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
### Settings Persistence
|
||||
|
||||
Settings automatically saved to worker service:
|
||||
|
||||
**Saved Settings:**
|
||||
- `sidebarOpen` - Sidebar expanded/collapsed state
|
||||
- `selectedProject` - Current project filter
|
||||
- `theme` - Theme preference (light/dark/system)
|
||||
|
||||
**API Endpoints:**
|
||||
- `GET /api/settings` - Retrieve saved settings
|
||||
- `POST /api/settings` - Save settings (debounced 500ms)
|
||||
|
||||
**Local Fallback:**
|
||||
- If API unavailable, settings stored in localStorage
|
||||
- Synced back to API when connection restored
|
||||
|
||||
## Usage Guide
|
||||
|
||||
### Opening the Viewer
|
||||
|
||||
1. Ensure claude-mem worker is running (auto-starts with Claude Code)
|
||||
2. Open browser to http://localhost:37777
|
||||
3. Viewer loads automatically with recent records
|
||||
|
||||
### Navigating the Feed
|
||||
|
||||
**Cards Displayed:**
|
||||
- **Observation Cards** (blue accent) - Tool usage observations with title, narrative, concepts, files
|
||||
- **Summary Cards** (green accent) - Session summaries with request, completion, learnings
|
||||
- **Prompt Cards** (purple accent) - Raw user prompts with timestamp and project
|
||||
|
||||
**Card Features:**
|
||||
- Click to expand/collapse full details
|
||||
- Type indicators (🔴 bugfix, 🟣 feature, 🔄 refactor, etc.)
|
||||
- Concept tags (clickable for future filtering)
|
||||
- File references with paths
|
||||
- Timestamps in relative format ("2 hours ago")
|
||||
|
||||
### Using Project Filters
|
||||
|
||||
1. **Open Sidebar**: Click hamburger menu (☰) in top-left
|
||||
2. **View Stats**: See total observations, sessions, prompts
|
||||
3. **Select Project**: Click project name to filter
|
||||
4. **View Counts**: Numbers show records per project
|
||||
5. **Clear Filter**: Click "All Projects" to reset
|
||||
|
||||
### Changing Theme
|
||||
|
||||
1. **Open Theme Toggle**: Click theme icon in header
|
||||
2. **Select Mode**:
|
||||
- ☀️ Light mode
|
||||
- 🌙 Dark mode
|
||||
- 💻 System (follows OS)
|
||||
3. **Auto-Save**: Preference saved immediately
|
||||
4. **Smooth Transition**: CSS transitions between themes
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Viewer Not Loading:**
|
||||
```bash
|
||||
# Check worker status
|
||||
npm run worker:logs
|
||||
|
||||
# Restart worker
|
||||
npm run worker:restart
|
||||
|
||||
# Check if port 37777 is available
|
||||
lsof -i :37777
|
||||
```
|
||||
|
||||
**SSE Connection Issues:**
|
||||
- Check browser console for connection errors
|
||||
- Verify no proxy/firewall blocking EventSource
|
||||
- Auto-reconnect attempts every 1-5s with exponential backoff
|
||||
|
||||
**Theme Not Persisting:**
|
||||
- Check localStorage: `localStorage.getItem('theme-preference')`
|
||||
- Verify `/api/settings` endpoint responding
|
||||
- Clear browser cache if stale
|
||||
|
||||
**Infinite Scroll Not Triggering:**
|
||||
- Scroll to 80% of feed height
|
||||
- Check browser console for fetch errors
|
||||
- Verify `/api/{type}` endpoints responding with data
|
||||
|
||||
## Development
|
||||
|
||||
### Building the Viewer
|
||||
|
||||
```bash
|
||||
# Build viewer UI
|
||||
npm run build
|
||||
|
||||
# Output: plugin/ui/viewer.html (self-contained)
|
||||
```
|
||||
|
||||
### Adding New Features
|
||||
|
||||
**Example: Add a new card component**
|
||||
|
||||
1. Create component:
|
||||
```typescript
|
||||
// src/ui/viewer/components/cards/MyCard.tsx
|
||||
export function MyCard({ data }: { data: MyData }) {
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">{data.title}</div>
|
||||
<div className="card-body">{data.content}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
2. Add to Feed component:
|
||||
```typescript
|
||||
// src/ui/viewer/components/Feed.tsx
|
||||
import { MyCard } from './cards/MyCard';
|
||||
|
||||
// In render:
|
||||
{myData.map(item => <MyCard key={item.id} data={item} />)}
|
||||
```
|
||||
|
||||
3. Rebuild:
|
||||
```bash
|
||||
npm run build
|
||||
npm run sync-marketplace
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
### Testing Changes
|
||||
|
||||
1. Make changes to `src/ui/viewer/`
|
||||
2. Rebuild: `npm run build`
|
||||
3. Restart worker: `npm run worker:restart`
|
||||
4. Refresh browser (http://localhost:37777)
|
||||
5. Check browser console for errors
|
||||
|
||||
## API Integration
|
||||
|
||||
The viewer consumes these worker service endpoints:
|
||||
|
||||
### Data Retrieval
|
||||
|
||||
```typescript
|
||||
// Get paginated observations
|
||||
GET /api/observations?offset=0&limit=20&project=myproject
|
||||
Response: { observations: Observation[], hasMore: boolean }
|
||||
|
||||
// Get paginated summaries
|
||||
GET /api/summaries?offset=0&limit=20&project=myproject
|
||||
Response: { summaries: Summary[], hasMore: boolean }
|
||||
|
||||
// Get paginated prompts
|
||||
GET /api/prompts?offset=0&limit=20&project=myproject
|
||||
Response: { prompts: UserPrompt[], hasMore: boolean }
|
||||
|
||||
// Get database stats
|
||||
GET /api/stats
|
||||
Response: { totalObservations: number, totalSessions: number, ... }
|
||||
```
|
||||
|
||||
### Real-Time Stream
|
||||
|
||||
```typescript
|
||||
// Server-Sent Events stream
|
||||
GET /stream
|
||||
|
||||
// Message format:
|
||||
event: observation
|
||||
data: {"type":"observation","data":{...}}
|
||||
|
||||
event: summary
|
||||
data: {"type":"summary","data":{...}}
|
||||
```
|
||||
|
||||
### Settings
|
||||
|
||||
```typescript
|
||||
// Get settings
|
||||
GET /api/settings
|
||||
Response: { sidebarOpen: boolean, selectedProject: string, ... }
|
||||
|
||||
// Save settings
|
||||
POST /api/settings
|
||||
Body: { sidebarOpen: boolean, selectedProject: string, ... }
|
||||
Response: { success: boolean }
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Bundle Size
|
||||
- Self-contained HTML: ~150KB (gzipped)
|
||||
- No external dependencies loaded at runtime
|
||||
- Monaspace Radon font embedded (subset)
|
||||
|
||||
### Memory Management
|
||||
- Virtualization: Only renders visible cards
|
||||
- Deduplication: Prevents duplicate records in memory
|
||||
- Cleanup: Old records beyond pagination limit pruned
|
||||
|
||||
### Network Efficiency
|
||||
- SSE: Single long-lived connection for real-time updates
|
||||
- REST: Paginated requests (20 records per batch)
|
||||
- Debouncing: Settings saves debounced 500ms
|
||||
|
||||
### Rendering Performance
|
||||
- React.memo: Cards memoized to prevent unnecessary re-renders
|
||||
- useMemo: Data merging/filtering memoized
|
||||
- CSS transitions: GPU-accelerated for smooth animations
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential features for future versions:
|
||||
|
||||
- **Search**: Full-text search across observations, summaries, prompts
|
||||
- **Export**: Download data as JSON, CSV, or markdown
|
||||
- **Charts**: Visualize observation frequency, types, concepts over time
|
||||
- **Keyboard Shortcuts**: Navigate feed, toggle sidebar, switch themes
|
||||
- **Notifications**: Browser notifications for important observations
|
||||
- **Dark/Light Auto-Schedule**: Auto-switch theme based on time of day
|
||||
- **Custom Themes**: User-defined color schemes
|
||||
- **Multi-Project Views**: Compare multiple projects side-by-side
|
||||
|
||||
## Resources
|
||||
|
||||
- **Source Code**: `src/ui/viewer/`
|
||||
- **Built Output**: `plugin/ui/viewer.html`
|
||||
- **Worker Service**: `src/services/worker-service.ts`
|
||||
- **Build Script**: `scripts/build-viewer.js`
|
||||
- **Documentation**: This file
|
||||
|
||||
---
|
||||
|
||||
**Built with React + TypeScript** | **Powered by Server-Sent Events** | **Self-Contained HTML Bundle**
|
||||
+295
-27
@@ -1,4 +1,4 @@
|
||||
# Architecture Evolution: The Journey from v3 to v4
|
||||
# Architecture Evolution: The Journey from v3 to v5
|
||||
|
||||
## The Problem We Solved
|
||||
|
||||
@@ -10,6 +10,246 @@ This is the story of how claude-mem evolved from a simple idea to a production-r
|
||||
|
||||
---
|
||||
|
||||
## v5.x: Maturity and User Experience
|
||||
|
||||
After establishing the solid v4 architecture, v5.x focused on user experience, visualization, and polish.
|
||||
|
||||
### v5.1.2: Theme Toggle (November 2025)
|
||||
|
||||
**What Changed**: Added light/dark mode theme toggle to viewer UI
|
||||
|
||||
**New Features**:
|
||||
- User-selectable theme preference (light, dark, system)
|
||||
- Persistent theme settings in localStorage
|
||||
- Smooth theme transitions
|
||||
- System preference detection
|
||||
|
||||
**Implementation**:
|
||||
```typescript
|
||||
// Theme context with persistence
|
||||
const ThemeProvider = ({ children }) => {
|
||||
const [theme, setTheme] = useState<'light' | 'dark' | 'system'>(() => {
|
||||
return localStorage.getItem('claude-mem-theme') || 'system';
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('claude-mem-theme', theme);
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**Why It Matters**: Users working in different lighting conditions can now customize the viewer for comfort.
|
||||
|
||||
### v5.1.1: PM2 Windows Fix (November 2025)
|
||||
|
||||
**The Problem**: PM2 startup failed on Windows with ENOENT error
|
||||
|
||||
**Root Cause**:
|
||||
```typescript
|
||||
// ❌ Failed on Windows - PM2 not in PATH
|
||||
execSync('pm2 start ecosystem.config.cjs');
|
||||
```
|
||||
|
||||
**The Fix**:
|
||||
```typescript
|
||||
// ✅ Use full path to PM2 binary
|
||||
const PM2_PATH = join(PLUGIN_ROOT, 'node_modules', '.bin', 'pm2');
|
||||
execSync(`"${PM2_PATH}" start "${ECOSYSTEM_CONFIG}"`);
|
||||
```
|
||||
|
||||
**Impact**: Cross-platform compatibility restored, Windows users can now use claude-mem without issues.
|
||||
|
||||
### v5.1.0: Web-Based Viewer UI (October 2025)
|
||||
|
||||
**The Breakthrough**: Real-time visualization of memory stream
|
||||
|
||||
**What We Built**:
|
||||
- React-based web UI at http://localhost:37777
|
||||
- Server-Sent Events (SSE) for real-time updates
|
||||
- Infinite scroll pagination
|
||||
- Project filtering
|
||||
- Settings persistence (sidebar state, selected project)
|
||||
- Auto-reconnection with exponential backoff
|
||||
- GPU-accelerated animations
|
||||
|
||||
**New Worker Endpoints** (8 additions):
|
||||
```
|
||||
GET / # Serves viewer HTML
|
||||
GET /stream # SSE real-time updates
|
||||
GET /api/prompts # Paginated user prompts
|
||||
GET /api/observations # Paginated observations
|
||||
GET /api/summaries # Paginated session summaries
|
||||
GET /api/stats # Database statistics
|
||||
GET /api/settings # User settings
|
||||
POST /api/settings # Save settings
|
||||
```
|
||||
|
||||
**Database Enhancements**:
|
||||
```typescript
|
||||
// New SessionStore methods for viewer
|
||||
getRecentPrompts(limit, offset, project?)
|
||||
getRecentObservations(limit, offset, project?)
|
||||
getRecentSummaries(limit, offset, project?)
|
||||
getStats()
|
||||
getUniqueProjects()
|
||||
```
|
||||
|
||||
**React Architecture**:
|
||||
```
|
||||
src/ui/viewer/
|
||||
├── components/
|
||||
│ ├── Header.tsx # Navigation + stats
|
||||
│ ├── Sidebar.tsx # Project filter
|
||||
│ ├── Feed.tsx # Infinite scroll
|
||||
│ └── cards/
|
||||
│ ├── ObservationCard.tsx
|
||||
│ ├── PromptCard.tsx
|
||||
│ ├── SummaryCard.tsx
|
||||
│ └── SkeletonCard.tsx
|
||||
├── hooks/
|
||||
│ ├── useSSE.ts # Real-time events
|
||||
│ ├── usePagination.ts # Infinite scroll
|
||||
│ ├── useSettings.ts # Persistence
|
||||
│ └── useStats.ts # Statistics
|
||||
└── utils/
|
||||
├── merge.ts # Data deduplication
|
||||
└── format.ts # Display formatting
|
||||
```
|
||||
|
||||
**Build Process**:
|
||||
```typescript
|
||||
// esbuild bundles everything into single HTML file
|
||||
esbuild.build({
|
||||
entryPoints: ['src/ui/viewer/index.tsx'],
|
||||
bundle: true,
|
||||
outfile: 'plugin/ui/viewer.html',
|
||||
loader: { '.tsx': 'tsx', '.woff2': 'dataurl' },
|
||||
define: { 'process.env.NODE_ENV': '"production"' },
|
||||
});
|
||||
```
|
||||
|
||||
**Why It Matters**: Users can now see exactly what's being captured in real-time, making the memory system transparent and debuggable.
|
||||
|
||||
### v5.0.3: Smart Install Caching (October 2025)
|
||||
|
||||
**The Problem**: `npm install` ran on every SessionStart (2-5 seconds)
|
||||
|
||||
**The Insight**: Dependencies rarely change between sessions
|
||||
|
||||
**The Solution**: Version-based caching
|
||||
```typescript
|
||||
// Check version marker before installing
|
||||
const currentVersion = getPackageVersion();
|
||||
const installedVersion = readFileSync('.install-version', 'utf-8');
|
||||
|
||||
if (currentVersion !== installedVersion) {
|
||||
// Only install if version changed
|
||||
await runNpmInstall();
|
||||
writeFileSync('.install-version', currentVersion);
|
||||
}
|
||||
```
|
||||
|
||||
**Cached Check Logic**:
|
||||
1. Does `node_modules` exist?
|
||||
2. Does `.install-version` match `package.json` version?
|
||||
3. Is `better-sqlite3` present?
|
||||
|
||||
**Impact**:
|
||||
- SessionStart hook: 2-5 seconds → 10ms (99.5% faster)
|
||||
- Only installs on: first run, version change, missing deps
|
||||
- Better Windows error messages with build tool help
|
||||
|
||||
### v5.0.2: Worker Health Checks (October 2025)
|
||||
|
||||
**What Changed**: More robust worker startup and monitoring
|
||||
|
||||
**New Features**:
|
||||
```typescript
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
uptime: process.uptime(),
|
||||
port: WORKER_PORT,
|
||||
memory: process.memoryUsage(),
|
||||
});
|
||||
});
|
||||
|
||||
// Smart worker startup
|
||||
async function ensureWorkerHealthy() {
|
||||
const healthy = await isWorkerHealthy(1000);
|
||||
if (!healthy) {
|
||||
await startWorker();
|
||||
await waitForWorkerHealth(10000);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Graceful degradation when worker is down
|
||||
- Auto-recovery from crashes
|
||||
- Better error messages for debugging
|
||||
|
||||
### v5.0.1: Stability Improvements (October 2025)
|
||||
|
||||
**What Changed**: Various bug fixes and stability enhancements
|
||||
|
||||
**Key Fixes**:
|
||||
- Fixed race conditions in observation queue processing
|
||||
- Improved error handling in SDK worker
|
||||
- Better cleanup of stale PM2 processes
|
||||
- Enhanced logging for debugging
|
||||
|
||||
### v5.0.0: Hybrid Search Architecture (October 2025)
|
||||
|
||||
**The Evolution**: SQLite FTS5 + Chroma vector search
|
||||
|
||||
**What We Added**:
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ HYBRID SEARCH │
|
||||
│ │
|
||||
│ Text Query → SQLite FTS5 (keyword matching) │
|
||||
│ ↓ │
|
||||
│ Chroma Vector Search (semantic) │
|
||||
│ ↓ │
|
||||
│ Merge + Re-rank Results │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**New Dependencies**:
|
||||
- `chromadb` - Vector database for semantic search
|
||||
- Python 3.8+ - Required by chromadb
|
||||
|
||||
**MCP Tools Enhancement**:
|
||||
```typescript
|
||||
// Chroma-backed semantic search
|
||||
search_observations({
|
||||
query: "authentication bug",
|
||||
useSemanticSearch: true // Uses Chroma
|
||||
});
|
||||
|
||||
// Falls back to FTS5 if Chroma unavailable
|
||||
```
|
||||
|
||||
**Why Hybrid**:
|
||||
- FTS5: Fast keyword matching, no dependencies
|
||||
- Chroma: Semantic understanding, finds related concepts
|
||||
- Graceful degradation: Works without Chroma (FTS5 only)
|
||||
|
||||
**Trade-offs**:
|
||||
- Added Python dependency (optional)
|
||||
- Increased installation complexity
|
||||
- Better search relevance
|
||||
|
||||
---
|
||||
|
||||
## v1-v2: The Naive Approach
|
||||
|
||||
### The First Attempt: Dump Everything
|
||||
@@ -698,7 +938,7 @@ createObservation({
|
||||
|
||||
---
|
||||
|
||||
## Migration Guide: v3 → v4
|
||||
## Migration Guide: v3 → v5
|
||||
|
||||
### Step 1: Backup Database
|
||||
|
||||
@@ -713,36 +953,45 @@ cd ~/.claude/plugins/marketplaces/thedotmack
|
||||
git pull
|
||||
```
|
||||
|
||||
### Step 3: Run Migration
|
||||
### Step 3: Update Plugin
|
||||
|
||||
```bash
|
||||
npx tsx src/services/sqlite/migrations/v3-to-v4.ts
|
||||
/plugin update claude-mem
|
||||
```
|
||||
|
||||
**What the migration does:**
|
||||
- Adds new columns to observations table
|
||||
- Creates FTS5 virtual tables
|
||||
- Sets up auto-sync triggers
|
||||
- Migrates existing observations to new schema
|
||||
**What happens automatically:**
|
||||
- Dependencies update (including new ones like chromadb for v5.0.0+)
|
||||
- Database schema migrations run automatically
|
||||
- Worker service restarts with new code
|
||||
- Smart install caching activates (v5.0.3+)
|
||||
|
||||
### Step 4: Restart Worker
|
||||
|
||||
```bash
|
||||
pm2 restart claude-mem-worker
|
||||
pm2 logs claude-mem-worker
|
||||
```
|
||||
|
||||
### Step 5: Test
|
||||
### Step 4: Test
|
||||
|
||||
```bash
|
||||
# Start Claude Code
|
||||
claude
|
||||
|
||||
# Check that context is injected
|
||||
# (Should see progressive disclosure index)
|
||||
# (Should see progressive disclosure index with v5 viewer link)
|
||||
|
||||
# Submit a prompt and check observations
|
||||
pm2 logs claude-mem-worker --nostream
|
||||
# Open viewer UI (v5.1.0+)
|
||||
open http://localhost:37777
|
||||
|
||||
# Submit a prompt and watch real-time updates in viewer
|
||||
```
|
||||
|
||||
### Step 5: Explore New Features
|
||||
|
||||
```bash
|
||||
# View memory stream in browser (v5.1.0+)
|
||||
open http://localhost:37777
|
||||
|
||||
# Toggle theme (v5.1.2+)
|
||||
# Click theme button in viewer header
|
||||
|
||||
# Check worker health
|
||||
npm run worker:status
|
||||
curl http://localhost:37777/health
|
||||
```
|
||||
|
||||
---
|
||||
@@ -767,17 +1016,34 @@ pm2 logs claude-mem-worker --nostream
|
||||
| Hook execution time | ~45ms |
|
||||
| Search latency | ~15ms (FTS5) |
|
||||
|
||||
**Improvements:**
|
||||
### v5 Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Context usage per session | ~1,100 tokens |
|
||||
| Relevant context | ~1,100 tokens (100%) |
|
||||
| Hook execution time | ~10ms (cached install) |
|
||||
| Search latency | ~12ms (FTS5) or ~25ms (hybrid) |
|
||||
| Viewer UI load time | ~50ms (bundled HTML) |
|
||||
| SSE update latency | ~5ms (real-time) |
|
||||
|
||||
**v3 → v4 Improvements:**
|
||||
- 96% reduction in context waste
|
||||
- 12x increase in relevance
|
||||
- 4x faster hooks
|
||||
- 33x faster search
|
||||
|
||||
**v4 → v5 Improvements:**
|
||||
- 78% faster hooks (smart caching)
|
||||
- Real-time visualization (viewer UI)
|
||||
- Better search relevance (hybrid)
|
||||
- Enhanced UX (theme toggle, persistence)
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
The journey from v3 to v4 was about understanding these fundamental truths:
|
||||
The journey from v3 to v5 was about understanding these fundamental truths:
|
||||
|
||||
1. **Context is finite** - Progressive disclosure respects attention budget
|
||||
2. **AI is the compressor** - Semantic understanding beats keyword extraction
|
||||
@@ -787,15 +1053,17 @@ The journey from v3 to v4 was about understanding these fundamental truths:
|
||||
|
||||
The result is a memory system that's both powerful and invisible. Users never notice it working - Claude just gets smarter over time.
|
||||
|
||||
**v5 adds visibility**: Now users CAN see the memory system working if they want (via viewer UI), but it's still non-intrusive.
|
||||
|
||||
---
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Progressive Disclosure](/docs/progressive-disclosure) - The philosophy behind v4
|
||||
- [Hooks Architecture](/docs/hooks-architecture) - How hooks power the system
|
||||
- [Context Engineering](/docs/context-engineering) - Foundational principles
|
||||
- [v4.0.0 Release Notes](/CHANGELOG.md#v400) - Full changelog
|
||||
- [Progressive Disclosure](progressive-disclosure) - The philosophy behind v4
|
||||
- [Hooks Architecture](hooks-architecture) - How hooks power the system
|
||||
- [Context Engineering](context-engineering) - Foundational principles
|
||||
- [Viewer UI](VIEWER) - Real-time visualization (v5.1.0+)
|
||||
|
||||
---
|
||||
|
||||
*This architecture evolution reflects hundreds of hours of experimentation, dozens of dead ends, and the invaluable experience of real-world usage. v4 is the architecture that emerged from understanding what actually works.*
|
||||
*This architecture evolution reflects hundreds of hours of experimentation, dozens of dead ends, and the invaluable experience of real-world usage. v5 is the architecture that emerged from understanding what actually works - and making it visible to users.*
|
||||
|
||||
+80
-13
@@ -1,17 +1,19 @@
|
||||
---
|
||||
title: "Plugin Hooks"
|
||||
description: "5 lifecycle hooks that power Claude-Mem"
|
||||
description: "7 hook scripts that power Claude-Mem"
|
||||
---
|
||||
|
||||
# Plugin Hooks
|
||||
|
||||
Claude-Mem integrates with Claude Code through 5 lifecycle hooks that capture events and inject context.
|
||||
Claude-Mem integrates with Claude Code through 7 hook scripts across 5 lifecycle events that capture events and inject context.
|
||||
|
||||
## Hook Overview
|
||||
|
||||
| Hook Name | Purpose | Timeout | Script |
|
||||
|---------------------|--------------------------------------|---------|-------------------------|
|
||||
| SessionStart | Inject context from previous sessions| 120s | context-hook.js |
|
||||
| SessionStart | Smart dependency installation | 300s | smart-install.js |
|
||||
| SessionStart | Inject context from previous sessions| 300s | context-hook.js |
|
||||
| SessionStart | Display first-time setup message | 10s | user-message-hook.js |
|
||||
| UserPromptSubmit | Create/track new sessions | 120s | new-hook.js |
|
||||
| PostToolUse | Capture tool execution observations | 120s | save-hook.js |
|
||||
| Stop | Generate session summaries | 120s | summary-hook.js |
|
||||
@@ -26,10 +28,15 @@ Hooks are configured in `plugin/hooks/hooks.json`:
|
||||
"description": "Claude-mem memory system hooks",
|
||||
"hooks": {
|
||||
"SessionStart": [{
|
||||
"matcher": "startup|clear|compact",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "cd \"${CLAUDE_PLUGIN_ROOT}/..\" && npm install --prefer-offline --no-audit --no-fund --loglevel=silent && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
|
||||
"timeout": 120
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/../scripts/smart-install.js\" && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
|
||||
"timeout": 300
|
||||
}, {
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js",
|
||||
"timeout": 10
|
||||
}]
|
||||
}],
|
||||
"UserPromptSubmit": [{
|
||||
@@ -65,15 +72,48 @@ Hooks are configured in `plugin/hooks/hooks.json`:
|
||||
}
|
||||
```
|
||||
|
||||
## 1. SessionStart Hook (`context-hook.js`)
|
||||
## 1. SessionStart Hook - Smart Install (`smart-install.js`)
|
||||
|
||||
**Purpose**: Intelligently manage dependencies and ensure worker service is running.
|
||||
|
||||
**Behavior**:
|
||||
- Checks if dependencies need installation using version marker (`.install-version`)
|
||||
- Only runs npm install when:
|
||||
- First-time installation (no node_modules)
|
||||
- Version changed in package.json
|
||||
- Critical dependency missing (e.g., better-sqlite3)
|
||||
- Provides Windows-specific error messages for build tool issues
|
||||
- Auto-starts PM2 worker service after installation
|
||||
- Fast when already installed (~10ms vs 2-5 seconds)
|
||||
|
||||
**Input** (via stdin):
|
||||
```json
|
||||
{
|
||||
"session_id": "claude-session-123",
|
||||
"cwd": "/path/to/project",
|
||||
"source": "startup"
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**: `scripts/smart-install.js`
|
||||
|
||||
**Key Features**:
|
||||
- Version caching prevents redundant installs
|
||||
- Cross-platform compatible (Windows, macOS, Linux)
|
||||
- Helpful error messages with troubleshooting steps
|
||||
- Non-blocking worker startup
|
||||
|
||||
**v5.0.3 Enhancement**: Smart caching eliminates 2-5 second npm install on every SessionStart, reducing to ~10ms for already-installed dependencies.
|
||||
|
||||
## 2. SessionStart Hook - Context Injection (`context-hook.js`)
|
||||
|
||||
**Purpose**: Inject context from previous sessions into Claude's initial context.
|
||||
|
||||
**Behavior**:
|
||||
- Ensures dependencies are installed (runs fast idempotent npm install)
|
||||
- Auto-starts PM2 worker service if not running
|
||||
- Retrieves last 10 session summaries with three-tier verbosity (v4.2.0)
|
||||
- Retrieves last 50 observations (configurable via `CLAUDE_MEM_CONTEXT_OBSERVATIONS`)
|
||||
- Returns context via `hookSpecificOutput` in JSON format (fixed in v4.1.0)
|
||||
- Formats results as progressive disclosure index
|
||||
|
||||
**Input** (via stdin):
|
||||
```json
|
||||
@@ -93,9 +133,36 @@ Hooks are configured in `plugin/hooks/hooks.json`:
|
||||
|
||||
**Implementation**: `src/hooks/context-hook.ts`
|
||||
|
||||
**v4.3.1 Fix**: Changed npm install to use `--loglevel=silent` instead of `--loglevel=error` to prevent output pollution that was breaking JSON context injection.
|
||||
## 3. SessionStart Hook - User Message (`user-message-hook.js`)
|
||||
|
||||
## 2. UserPromptSubmit Hook (`new-hook.js`)
|
||||
**Purpose**: Display helpful user messages during first-time setup or when viewing context.
|
||||
|
||||
**Behavior**:
|
||||
- Shows first-time setup message when node_modules is missing
|
||||
- Displays formatted context information with colors
|
||||
- Provides tips for using claude-mem effectively
|
||||
- Shows link to web viewer UI (http://localhost:37777)
|
||||
- Exits with code 3 (informational, not error)
|
||||
|
||||
**Output Example**:
|
||||
```
|
||||
📝 Claude-Mem Context Loaded
|
||||
ℹ️ Note: This appears as stderr but is informational only
|
||||
|
||||
[Context details...]
|
||||
|
||||
📺 Watch live in browser http://localhost:37777/ (New! v5.1)
|
||||
```
|
||||
|
||||
**Implementation**: `plugin/scripts/user-message-hook.js` (minified)
|
||||
|
||||
**Key Features**:
|
||||
- User-friendly first-time setup experience
|
||||
- Visual context display with colors
|
||||
- Links to new features (viewer UI)
|
||||
- Non-intrusive messaging
|
||||
|
||||
## 4. UserPromptSubmit Hook (`new-hook.js`)
|
||||
|
||||
**Purpose**: Create new session records and initialize session tracking.
|
||||
|
||||
@@ -116,7 +183,7 @@ Hooks are configured in `plugin/hooks/hooks.json`:
|
||||
|
||||
**Implementation**: `src/hooks/new-hook.ts`
|
||||
|
||||
## 3. PostToolUse Hook (`save-hook.js`)
|
||||
## 5. PostToolUse Hook (`save-hook.js`)
|
||||
|
||||
**Purpose**: Capture tool execution observations.
|
||||
|
||||
@@ -140,7 +207,7 @@ Hooks are configured in `plugin/hooks/hooks.json`:
|
||||
|
||||
**Implementation**: `src/hooks/save-hook.ts`
|
||||
|
||||
## 4. Stop Hook (`summary-hook.js`)
|
||||
## 6. Stop Hook (`summary-hook.js`)
|
||||
|
||||
**Purpose**: Generate session summaries when Claude stops.
|
||||
|
||||
@@ -160,7 +227,7 @@ Hooks are configured in `plugin/hooks/hooks.json`:
|
||||
|
||||
**Implementation**: `src/hooks/summary-hook.ts`
|
||||
|
||||
## 5. SessionEnd Hook (`cleanup-hook.js`)
|
||||
## 7. SessionEnd Hook (`cleanup-hook.js`)
|
||||
|
||||
**Purpose**: Mark sessions as completed (graceful cleanup as of v4.1.0).
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
---
|
||||
title: "MCP Search Server"
|
||||
description: "7 search tools with examples and usage patterns"
|
||||
description: "9 search tools with examples and usage patterns"
|
||||
---
|
||||
|
||||
# MCP Search Server
|
||||
|
||||
Claude-Mem includes a Model Context Protocol (MCP) server that exposes 7 specialized search tools for querying stored observations and sessions.
|
||||
Claude-Mem includes a Model Context Protocol (MCP) server that exposes 9 specialized search tools for querying stored observations and sessions.
|
||||
|
||||
## Overview
|
||||
|
||||
- **Location**: `src/servers/search-server.ts`
|
||||
- **Built Output**: `plugin/scripts/search-server.mjs`
|
||||
- **Configuration**: `plugin/.mcp.json`
|
||||
- **Transport**: stdio
|
||||
- **Tools**: 7 specialized search functions
|
||||
- **Tools**: 9 specialized search functions
|
||||
- **Citations**: All results use `claude-mem://` URI scheme
|
||||
|
||||
## Configuration
|
||||
@@ -24,13 +25,13 @@ The MCP server is automatically registered via `plugin/.mcp.json`:
|
||||
"mcpServers": {
|
||||
"claude-mem-search": {
|
||||
"type": "stdio",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/search-server.js"
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/search-server.mjs"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This registers the `claude-mem-search` server with Claude Code, making the 7 search tools available in all sessions. The server is automatically started when Claude Code launches and communicates via stdio transport.
|
||||
This registers the `claude-mem-search` server with Claude Code, making the 9 search tools available in all sessions. The server is automatically started when Claude Code launches and communicates via stdio transport.
|
||||
|
||||
## Search Tools
|
||||
|
||||
@@ -163,6 +164,133 @@ Get recent session context including summaries and observations for a project.
|
||||
get_recent_context with limit=5
|
||||
```
|
||||
|
||||
### 8. get_context_timeline
|
||||
|
||||
Get a unified timeline of context (observations, sessions, and prompts) around a specific point in time. All record types are interleaved chronologically.
|
||||
|
||||
**Parameters**:
|
||||
- `anchor` (required): Anchor point - observation ID, session ID (e.g., "S123"), or ISO timestamp
|
||||
- `depth_before` (default: 10): Number of records to retrieve before anchor (max: 50)
|
||||
- `depth_after` (default: 10): Number of records to retrieve after anchor (max: 50)
|
||||
- `project`: Filter by project name
|
||||
|
||||
**Return Format**:
|
||||
Returns `depth_before` records + anchor + `depth_after` records, all interleaved chronologically. Total records: `depth_before + 1 + depth_after`.
|
||||
|
||||
**Use Case**: Understanding "what was happening when X occurred"
|
||||
|
||||
**Example**:
|
||||
```
|
||||
# Timeline around observation #123
|
||||
get_context_timeline with anchor=123 and depth_before=5 and depth_after=5
|
||||
|
||||
# Timeline around a session
|
||||
get_context_timeline with anchor="S456" and depth_before=10 and depth_after=10
|
||||
|
||||
# Timeline around a timestamp
|
||||
get_context_timeline with anchor="2025-11-06T10:30:00Z" and depth_before=15 and depth_after=5
|
||||
```
|
||||
|
||||
**Response Structure**:
|
||||
```json
|
||||
{
|
||||
"timeline": [
|
||||
{
|
||||
"type": "observation",
|
||||
"id": 120,
|
||||
"title": "Context before",
|
||||
"created_at": "2025-11-06T10:25:00Z"
|
||||
},
|
||||
{
|
||||
"type": "user-prompt",
|
||||
"id": 45,
|
||||
"prompt": "User request",
|
||||
"created_at": "2025-11-06T10:28:00Z"
|
||||
},
|
||||
{
|
||||
"type": "observation",
|
||||
"id": 123,
|
||||
"title": "Anchor observation",
|
||||
"created_at": "2025-11-06T10:30:00Z",
|
||||
"isAnchor": true
|
||||
},
|
||||
{
|
||||
"type": "session",
|
||||
"id": "S456",
|
||||
"request": "Session summary",
|
||||
"created_at": "2025-11-06T10:32:00Z"
|
||||
}
|
||||
],
|
||||
"anchor": {
|
||||
"type": "observation",
|
||||
"id": 123
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9. get_timeline_by_query
|
||||
|
||||
Search for observations using natural language and get timeline context around the best match. Combines search + timeline into a single operation.
|
||||
|
||||
**Parameters**:
|
||||
- `query` (required): Natural language search query to find relevant observations
|
||||
- `mode` (default: "auto"): Operation mode
|
||||
- `"auto"`: Automatically use top search result as timeline anchor
|
||||
- `"interactive"`: Return top N search results for manual anchor selection
|
||||
- `depth_before` (default: 10): Number of timeline records before anchor (max: 50)
|
||||
- `depth_after` (default: 10): Number of timeline records after anchor (max: 50)
|
||||
- `limit` (default: 5): For interactive mode - number of top search results to display (max: 20)
|
||||
- `project`: Filter by project name
|
||||
|
||||
**Use Case**: Faster context discovery - "show me what happened around when we fixed the authentication bug"
|
||||
|
||||
**Example - Auto Mode**:
|
||||
```
|
||||
# Automatically find and show timeline for "authentication bug"
|
||||
get_timeline_by_query with query="authentication bug" and mode="auto" and depth_before=10 and depth_after=10
|
||||
```
|
||||
|
||||
**Example - Interactive Mode**:
|
||||
```
|
||||
# Show top 5 matches, let user choose anchor
|
||||
get_timeline_by_query with query="authentication bug" and mode="interactive" and limit=5
|
||||
```
|
||||
|
||||
**Auto Mode Response**:
|
||||
```json
|
||||
{
|
||||
"search_result": {
|
||||
"id": 123,
|
||||
"title": "Fix authentication bug",
|
||||
"relevance": 0.95
|
||||
},
|
||||
"timeline": [
|
||||
/* timeline records before and after observation 123 */
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Interactive Mode Response**:
|
||||
```json
|
||||
{
|
||||
"top_results": [
|
||||
{
|
||||
"id": 123,
|
||||
"title": "Fix authentication bug",
|
||||
"relevance": 0.95,
|
||||
"created_at": "2025-11-06T10:30:00Z"
|
||||
},
|
||||
{
|
||||
"id": 98,
|
||||
"title": "Authentication refactor",
|
||||
"relevance": 0.82,
|
||||
"created_at": "2025-11-05T14:20:00Z"
|
||||
}
|
||||
],
|
||||
"message": "Select an observation ID to view its timeline context"
|
||||
}
|
||||
```
|
||||
|
||||
## Output Formats
|
||||
|
||||
All search tools support two output formats:
|
||||
@@ -252,6 +380,12 @@ search_user_prompts with query="authentication"
|
||||
|
||||
# Get recent context for debugging
|
||||
get_recent_context with limit=5
|
||||
|
||||
# Timeline around a specific observation
|
||||
get_context_timeline with anchor=123 and depth_before=10 and depth_after=10
|
||||
|
||||
# Quick timeline search for authentication work
|
||||
get_timeline_by_query with query="authentication bug" and mode="auto"
|
||||
```
|
||||
|
||||
## Implementation
|
||||
@@ -277,7 +411,7 @@ If search tools are not available in Claude Code sessions:
|
||||
|
||||
2. Verify search server is built:
|
||||
```bash
|
||||
ls -l plugin/scripts/search-server.js
|
||||
ls -l plugin/scripts/search-server.mjs
|
||||
```
|
||||
|
||||
3. Rebuild if needed:
|
||||
|
||||
@@ -7,12 +7,13 @@ description: "System components and data flow in Claude-Mem"
|
||||
|
||||
## System Components
|
||||
|
||||
Claude-Mem operates as a Claude Code plugin with four core components:
|
||||
Claude-Mem operates as a Claude Code plugin with five core components:
|
||||
|
||||
1. **Plugin Hooks** - Capture lifecycle events
|
||||
1. **Plugin Hooks** - Capture lifecycle events (7 hook files)
|
||||
2. **Worker Service** - Process observations via Claude Agent SDK
|
||||
3. **Database Layer** - Store sessions and observations (SQLite + FTS5)
|
||||
4. **MCP Search Server** - Query historical context
|
||||
4. **MCP Search Server** - Query historical context (9 search tools)
|
||||
5. **Viewer UI** - Web-based real-time memory stream visualization
|
||||
|
||||
## Technology Stack
|
||||
|
||||
@@ -21,7 +22,10 @@ Claude-Mem operates as a Claude Code plugin with four core components:
|
||||
| **Language** | TypeScript (ES2022, ESNext modules) |
|
||||
| **Runtime** | Node.js 18+ |
|
||||
| **Database** | SQLite 3 with better-sqlite3 driver |
|
||||
| **Vector Store** | ChromaDB (optional, for semantic search) |
|
||||
| **HTTP Server** | Express.js 4.18 |
|
||||
| **Real-time** | Server-Sent Events (SSE) |
|
||||
| **UI Framework** | React + TypeScript |
|
||||
| **AI SDK** | @anthropic-ai/claude-agent-sdk |
|
||||
| **Build Tool** | esbuild (bundles TypeScript) |
|
||||
| **Process Manager** | PM2 |
|
||||
@@ -55,18 +59,24 @@ Claude Request → MCP Server → SessionSearch Service → FTS5 Database → Se
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 0. Smart Install Hook Fires │
|
||||
│ Checks dependencies (cached), only runs on version changes │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 1. Session Starts → Context Hook Fires │
|
||||
│ Injects summaries from last 3 sessions into Claude's context │
|
||||
│ Starts PM2 worker if needed, injects context from previous │
|
||||
│ sessions (configurable observation count) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 2. User Types Prompt → UserPromptSubmit Hook Fires │
|
||||
│ Creates SDK session in database, notifies worker service │
|
||||
│ Creates session in database, saves raw user prompt for FTS5 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 3. Claude Uses Tools → PostToolUse Hook Fires (100+ times) │
|
||||
│ Sends observations to worker service for processing │
|
||||
│ Captures tool executions, sends to worker for AI compression │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
@@ -75,13 +85,14 @@ Claude Request → MCP Server → SessionSearch Service → FTS5 Database → Se
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 5. Claude Stops → Stop Hook Fires │
|
||||
│ Generates final summary with request, status, next steps │
|
||||
│ 5. Claude Stops → Summary Hook Fires │
|
||||
│ Generates final summary with request, completions, learnings │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 6. Session Ends → Cleanup Hook Fires │
|
||||
│ Marks session complete, ready for next session context │
|
||||
│ Marks session complete (graceful, not DELETE), ready for │
|
||||
│ next session context. Skips on /clear to preserve ongoing │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -90,15 +101,17 @@ Claude Request → MCP Server → SessionSearch Service → FTS5 Database → Se
|
||||
```
|
||||
claude-mem/
|
||||
├── src/
|
||||
│ ├── hooks/ # Hook implementations (v4.3.1+ consolidated)
|
||||
│ ├── hooks/ # Hook implementations (7 hooks)
|
||||
│ │ ├── smart-install.ts # Dependency check (cached)
|
||||
│ │ ├── context-hook.ts # SessionStart
|
||||
│ │ ├── user-message-hook.ts # UserMessage (for debugging)
|
||||
│ │ ├── new-hook.ts # UserPromptSubmit
|
||||
│ │ ├── save-hook.ts # PostToolUse
|
||||
│ │ ├── summary-hook.ts # Stop
|
||||
│ │ └── cleanup-hook.ts # SessionEnd
|
||||
│ │
|
||||
│ ├── servers/ # MCP servers
|
||||
│ │ └── search-server.ts # MCP search tools server
|
||||
│ │ └── search-server.ts # MCP search tools server (9 tools)
|
||||
│ │
|
||||
│ ├── sdk/ # Claude Agent SDK integration
|
||||
│ │ ├── prompts.ts # XML prompt builders
|
||||
@@ -106,13 +119,20 @@ claude-mem/
|
||||
│ │ └── worker.ts # Main SDK agent loop
|
||||
│ │
|
||||
│ ├── services/
|
||||
│ │ ├── worker-service.ts # Express HTTP service
|
||||
│ │ ├── worker-service.ts # Express HTTP + SSE service
|
||||
│ │ └── sqlite/ # Database layer
|
||||
│ │ ├── SessionStore.ts # CRUD operations
|
||||
│ │ ├── SessionSearch.ts # FTS5 search service
|
||||
│ │ ├── migrations.ts
|
||||
│ │ └── types.ts
|
||||
│ │
|
||||
│ ├── ui/ # Viewer UI
|
||||
│ │ └── viewer/ # React + TypeScript web interface
|
||||
│ │ ├── components/ # UI components
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── utils/ # Utilities
|
||||
│ │ └── assets/ # Fonts, logos
|
||||
│ │
|
||||
│ ├── shared/ # Shared utilities
|
||||
│ │ ├── config.ts
|
||||
│ │ ├── paths.ts
|
||||
@@ -129,14 +149,19 @@ claude-mem/
|
||||
│ ├── .mcp.json # MCP server configuration
|
||||
│ ├── hooks/
|
||||
│ │ └── hooks.json
|
||||
│ └── scripts/ # Built executables
|
||||
│ ├── context-hook.js
|
||||
│ ├── new-hook.js
|
||||
│ ├── save-hook.js
|
||||
│ ├── summary-hook.js
|
||||
│ ├── cleanup-hook.js
|
||||
│ ├── worker-service.cjs # Background worker
|
||||
│ └── search-server.js # MCP search server
|
||||
│ ├── scripts/ # Built executables
|
||||
│ │ ├── smart-install.js
|
||||
│ │ ├── context-hook.js
|
||||
│ │ ├── user-message-hook.js
|
||||
│ │ ├── new-hook.js
|
||||
│ │ ├── save-hook.js
|
||||
│ │ ├── summary-hook.js
|
||||
│ │ ├── cleanup-hook.js
|
||||
│ │ ├── worker-service.cjs # Background worker
|
||||
│ │ └── search-server.mjs # MCP search server
|
||||
│ │
|
||||
│ └── ui/ # Built viewer UI
|
||||
│ └── viewer.html # Self-contained bundle
|
||||
│
|
||||
├── tests/ # Test suite
|
||||
├── docs/ # Documentation
|
||||
@@ -145,14 +170,49 @@ claude-mem/
|
||||
|
||||
## Component Details
|
||||
|
||||
### 1. Plugin Hooks
|
||||
### 1. Plugin Hooks (7 Hooks)
|
||||
- **smart-install.js** - Cached dependency checker (only runs on version changes)
|
||||
- **context-hook.js** - SessionStart: Starts PM2 worker, injects context
|
||||
- **user-message-hook.js** - UserMessage: Debugging hook
|
||||
- **new-hook.js** - UserPromptSubmit: Creates session, saves prompt
|
||||
- **save-hook.js** - PostToolUse: Captures tool executions
|
||||
- **summary-hook.js** - Stop: Generates session summary
|
||||
- **cleanup-hook.js** - SessionEnd: Marks session complete
|
||||
|
||||
See [Plugin Hooks](/architecture/hooks) for detailed hook documentation.
|
||||
|
||||
### 2. Worker Service
|
||||
Express.js HTTP server on port 37777 (configurable) with:
|
||||
- 8 HTTP/SSE endpoints for viewer UI
|
||||
- Async observation processing via Claude Agent SDK
|
||||
- Real-time updates via Server-Sent Events
|
||||
- Auto-managed by PM2 process manager
|
||||
|
||||
See [Worker Service](/architecture/worker-service) for HTTP API and endpoints.
|
||||
|
||||
### 3. Database Layer
|
||||
SQLite3 with better-sqlite3 driver featuring:
|
||||
- FTS5 virtual tables for full-text search
|
||||
- SessionStore for CRUD operations
|
||||
- SessionSearch for FTS5 queries
|
||||
- Location: `~/.claude-mem/claude-mem.db`
|
||||
|
||||
See [Database Architecture](/architecture/database) for schema and FTS5 search.
|
||||
|
||||
### 4. MCP Search Server
|
||||
### 4. MCP Search Server (9 Tools)
|
||||
Provides 9 specialized search tools:
|
||||
- search_observations, search_sessions, search_user_prompts
|
||||
- find_by_concept, find_by_file, find_by_type
|
||||
- get_recent_context, get_context_timeline, get_timeline_by_query
|
||||
|
||||
See [MCP Search Server](/architecture/mcp-search) for search tools and examples.
|
||||
|
||||
### 5. Viewer UI
|
||||
React + TypeScript web interface at http://localhost:37777 featuring:
|
||||
- Real-time memory stream via Server-Sent Events
|
||||
- Infinite scroll pagination with automatic deduplication
|
||||
- Project filtering and settings persistence
|
||||
- GPU-accelerated animations
|
||||
- Self-contained HTML bundle (viewer.html)
|
||||
|
||||
Built with esbuild into a single file deployment.
|
||||
|
||||
@@ -18,13 +18,33 @@ The worker service is a long-running HTTP API built with Express.js and managed
|
||||
|
||||
## REST API Endpoints
|
||||
|
||||
The worker service exposes 6 HTTP endpoints:
|
||||
The worker service exposes 14 HTTP endpoints organized into four categories:
|
||||
|
||||
### 1. Health Check
|
||||
### Viewer & Health Endpoints
|
||||
|
||||
#### 1. Viewer UI
|
||||
```
|
||||
GET /
|
||||
```
|
||||
|
||||
**Purpose**: Serves the web-based viewer UI (v5.1.0+)
|
||||
|
||||
**Response**: HTML page with embedded React application
|
||||
|
||||
**Features**:
|
||||
- Real-time memory stream visualization
|
||||
- Infinite scroll pagination
|
||||
- Project filtering
|
||||
- SSE-based live updates
|
||||
- Theme toggle (light/dark mode) as of v5.1.2
|
||||
|
||||
#### 2. Health Check
|
||||
```
|
||||
GET /health
|
||||
```
|
||||
|
||||
**Purpose**: Worker health status check
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
@@ -34,7 +54,182 @@ GET /health
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Initialize Session
|
||||
#### 3. Server-Sent Events Stream
|
||||
```
|
||||
GET /stream
|
||||
```
|
||||
|
||||
**Purpose**: Real-time updates for viewer UI
|
||||
|
||||
**Response**: SSE stream with events:
|
||||
- `observation-created`: New observation added
|
||||
- `session-summary-created`: New summary generated
|
||||
- `user-prompt-created`: New prompt recorded
|
||||
|
||||
**Event Format**:
|
||||
```
|
||||
event: observation-created
|
||||
data: {"id": 123, "title": "...", ...}
|
||||
```
|
||||
|
||||
### Data Retrieval Endpoints
|
||||
|
||||
#### 4. Get Prompts
|
||||
```
|
||||
GET /api/prompts?project=my-project&limit=20&offset=0
|
||||
```
|
||||
|
||||
**Purpose**: Retrieve paginated user prompts
|
||||
|
||||
**Query Parameters**:
|
||||
- `project` (optional): Filter by project name
|
||||
- `limit` (default: 20): Number of results
|
||||
- `offset` (default: 0): Pagination offset
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"prompts": [{
|
||||
"id": 1,
|
||||
"session_id": "abc123",
|
||||
"prompt": "User's prompt text",
|
||||
"prompt_number": 1,
|
||||
"created_at": "2025-11-06T10:30:00Z"
|
||||
}],
|
||||
"total": 150,
|
||||
"hasMore": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. Get Observations
|
||||
```
|
||||
GET /api/observations?project=my-project&limit=20&offset=0
|
||||
```
|
||||
|
||||
**Purpose**: Retrieve paginated observations
|
||||
|
||||
**Query Parameters**:
|
||||
- `project` (optional): Filter by project name
|
||||
- `limit` (default: 20): Number of results
|
||||
- `offset` (default: 0): Pagination offset
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"observations": [{
|
||||
"id": 123,
|
||||
"title": "Fix authentication bug",
|
||||
"type": "bugfix",
|
||||
"narrative": "...",
|
||||
"created_at": "2025-11-06T10:30:00Z"
|
||||
}],
|
||||
"total": 500,
|
||||
"hasMore": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 6. Get Summaries
|
||||
```
|
||||
GET /api/summaries?project=my-project&limit=20&offset=0
|
||||
```
|
||||
|
||||
**Purpose**: Retrieve paginated session summaries
|
||||
|
||||
**Query Parameters**:
|
||||
- `project` (optional): Filter by project name
|
||||
- `limit` (default: 20): Number of results
|
||||
- `offset` (default: 0): Pagination offset
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"summaries": [{
|
||||
"id": 456,
|
||||
"session_id": "abc123",
|
||||
"request": "User's original request",
|
||||
"completed": "Work finished",
|
||||
"created_at": "2025-11-06T10:30:00Z"
|
||||
}],
|
||||
"total": 100,
|
||||
"hasMore": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. Get Stats
|
||||
```
|
||||
GET /api/stats
|
||||
```
|
||||
|
||||
**Purpose**: Get database statistics by project
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"byProject": {
|
||||
"my-project": {
|
||||
"observations": 245,
|
||||
"summaries": 12,
|
||||
"prompts": 48
|
||||
},
|
||||
"other-project": {
|
||||
"observations": 156,
|
||||
"summaries": 8,
|
||||
"prompts": 32
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"observations": 401,
|
||||
"summaries": 20,
|
||||
"prompts": 80,
|
||||
"sessions": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Settings Endpoints
|
||||
|
||||
#### 8. Get Settings
|
||||
```
|
||||
GET /api/settings
|
||||
```
|
||||
|
||||
**Purpose**: Retrieve user settings
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"sidebarOpen": true,
|
||||
"selectedProject": "my-project",
|
||||
"theme": "dark"
|
||||
}
|
||||
```
|
||||
|
||||
#### 9. Save Settings
|
||||
```
|
||||
POST /api/settings
|
||||
```
|
||||
|
||||
**Purpose**: Persist user settings
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"sidebarOpen": false,
|
||||
"selectedProject": "other-project",
|
||||
"theme": "light"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### Session Management Endpoints
|
||||
|
||||
#### 10. Initialize Session
|
||||
```
|
||||
POST /sessions/:sessionDbId/init
|
||||
```
|
||||
@@ -55,7 +250,7 @@ POST /sessions/:sessionDbId/init
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Add Observation
|
||||
#### 11. Add Observation
|
||||
```
|
||||
POST /sessions/:sessionDbId/observations
|
||||
```
|
||||
@@ -78,7 +273,7 @@ POST /sessions/:sessionDbId/observations
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Generate Summary
|
||||
#### 12. Generate Summary
|
||||
```
|
||||
POST /sessions/:sessionDbId/summarize
|
||||
```
|
||||
@@ -98,7 +293,7 @@ POST /sessions/:sessionDbId/summarize
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Session Status
|
||||
#### 13. Session Status
|
||||
```
|
||||
GET /sessions/:sessionDbId/status
|
||||
```
|
||||
@@ -113,7 +308,7 @@ GET /sessions/:sessionDbId/status
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Delete Session
|
||||
#### 14. Delete Session
|
||||
```
|
||||
DELETE /sessions/:sessionDbId
|
||||
```
|
||||
|
||||
+62
-20
@@ -7,14 +7,16 @@ description: "Environment variables and settings for Claude-Mem"
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|-------------------------|---------------------------------|---------------------------------------|
|
||||
| `CLAUDE_PLUGIN_ROOT` | Set by Claude Code | Plugin installation directory |
|
||||
| `CLAUDE_MEM_DATA_DIR` | `~/.claude-mem/` | Data directory (dev override) |
|
||||
| `CLAUDE_MEM_WORKER_PORT`| `37777` | Worker service port |
|
||||
| `CLAUDE_MEM_MODEL` | `claude-sonnet-4-5` | AI model for processing observations |
|
||||
| `NODE_ENV` | `production` | Environment mode |
|
||||
| `FORCE_COLOR` | `1` | Enable colored logs |
|
||||
| Variable | Default | Description |
|
||||
|-------------------------------|---------------------------------|---------------------------------------|
|
||||
| `CLAUDE_PLUGIN_ROOT` | Set by Claude Code | Plugin installation directory |
|
||||
| `CLAUDE_MEM_DATA_DIR` | `~/.claude-mem/` | Data directory (production default) |
|
||||
| `CLAUDE_CODE_PATH` | Auto-detected | Path to Claude Code CLI (for Windows) |
|
||||
| `CLAUDE_MEM_WORKER_PORT` | `37777` | Worker service port |
|
||||
| `CLAUDE_MEM_MODEL` | `claude-sonnet-4-5` | AI model for processing observations |
|
||||
| `CLAUDE_MEM_CONTEXT_OBSERVATIONS` | `50` | Number of observations to inject |
|
||||
| `NODE_ENV` | `production` | Environment mode |
|
||||
| `FORCE_COLOR` | `1` | Enable colored logs |
|
||||
|
||||
## Model Configuration
|
||||
|
||||
@@ -49,9 +51,14 @@ Edit `~/.claude/settings.json`:
|
||||
|
||||
### Data Directory Structure
|
||||
|
||||
The data directory location depends on the environment:
|
||||
- **Production (installed plugin)**: `~/.claude-mem/` (always, regardless of CLAUDE_PLUGIN_ROOT)
|
||||
- **Development**: Can be overridden with `CLAUDE_MEM_DATA_DIR`
|
||||
|
||||
```
|
||||
~/.claude-mem/
|
||||
├── claude-mem.db # SQLite database
|
||||
├── .install-version # Cached version for smart installer
|
||||
├── worker.port # Current worker port file
|
||||
└── logs/
|
||||
├── worker-out.log # Worker stdout logs
|
||||
@@ -67,14 +74,17 @@ ${CLAUDE_PLUGIN_ROOT}/
|
||||
├── .mcp.json # MCP server configuration
|
||||
├── hooks/
|
||||
│ └── hooks.json # Hook configuration
|
||||
└── scripts/ # Built executables
|
||||
├── context-hook.js
|
||||
├── new-hook.js
|
||||
├── save-hook.js
|
||||
├── summary-hook.js
|
||||
├── cleanup-hook.js
|
||||
├── worker-service.cjs
|
||||
└── search-server.js
|
||||
├── scripts/ # Built executables
|
||||
│ ├── smart-install.js # Smart installer script
|
||||
│ ├── context-hook.js # Context injection hook
|
||||
│ ├── new-hook.js # Session creation hook
|
||||
│ ├── save-hook.js # Observation capture hook
|
||||
│ ├── summary-hook.js # Summary generation hook
|
||||
│ ├── cleanup-hook.js # Session cleanup hook
|
||||
│ ├── worker-service.cjs # Worker service (CJS)
|
||||
│ └── search-server.mjs # MCP search server (ESM)
|
||||
└── ui/
|
||||
└── viewer.html # Web viewer UI bundle
|
||||
```
|
||||
|
||||
## Plugin Configuration
|
||||
@@ -90,7 +100,7 @@ Hooks are configured in `plugin/hooks/hooks.json`:
|
||||
"SessionStart": [{
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "cd \"${CLAUDE_PLUGIN_ROOT}/..\" && npm install --prefer-offline --no-audit --no-fund --loglevel=error && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
|
||||
"timeout": 120
|
||||
}]
|
||||
}],
|
||||
@@ -136,13 +146,13 @@ The MCP search server is configured in `plugin/.mcp.json`:
|
||||
"mcpServers": {
|
||||
"claude-mem-search": {
|
||||
"type": "stdio",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/search-server.js"
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/search-server.mjs"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This registers the `claude-mem-search` server with Claude Code, making the 7 search tools available in all sessions.
|
||||
This registers the `claude-mem-search` server with Claude Code, making the 9 search tools available in all sessions.
|
||||
|
||||
## PM2 Configuration
|
||||
|
||||
@@ -172,6 +182,36 @@ module.exports = {
|
||||
- **watch**: false (no file watching)
|
||||
- **max_memory_restart**: 1G (restart if memory exceeds 1GB)
|
||||
|
||||
## Context Injection Configuration
|
||||
|
||||
### CLAUDE_MEM_CONTEXT_OBSERVATIONS
|
||||
|
||||
Controls how many observations are injected into each new session for context continuity.
|
||||
|
||||
**Default**: 50 observations
|
||||
|
||||
**What it does**:
|
||||
- Fetches the most recent N observations from the database
|
||||
- Injects them as context at SessionStart
|
||||
- Allows Claude to maintain awareness of recent work across sessions
|
||||
|
||||
**Configuration** in `~/.claude/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "100"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Considerations**:
|
||||
- **Higher values** = More context but slower SessionStart and more tokens used
|
||||
- **Lower values** = Faster SessionStart but less historical awareness
|
||||
- Default of 50 balances context richness with performance
|
||||
|
||||
**Note**: This injects individual observations, not entire sessions. Each observation represents a single tool execution (Read, Write, Edit, etc.) that was compressed into a semantic learning.
|
||||
|
||||
## Customization
|
||||
|
||||
### Custom Data Directory
|
||||
@@ -213,12 +253,14 @@ Modify timeouts in `plugin/hooks/hooks.json`:
|
||||
```
|
||||
|
||||
Recommended values:
|
||||
- SessionStart: 120s (needs time for npm install and context retrieval)
|
||||
- SessionStart: 120s (needs time for smart install check and context retrieval)
|
||||
- UserPromptSubmit: 60s
|
||||
- PostToolUse: 120s (can process many observations)
|
||||
- Stop: 60s
|
||||
- SessionEnd: 60s
|
||||
|
||||
**Note**: With smart install caching (v5.0.3+), SessionStart is typically very fast (10ms) unless dependencies need installation.
|
||||
|
||||
### Worker Memory Limit
|
||||
|
||||
Modify PM2 memory limit in `ecosystem.config.cjs`:
|
||||
|
||||
+112
-2
@@ -33,13 +33,16 @@ The build process uses esbuild to compile TypeScript:
|
||||
|
||||
1. Compiles TypeScript to JavaScript
|
||||
2. Creates standalone executables for each hook in `plugin/scripts/`
|
||||
3. Bundles MCP search server to `plugin/scripts/search-server.js`
|
||||
3. Bundles MCP search server to `plugin/scripts/search-server.mjs`
|
||||
4. Bundles worker service to `plugin/scripts/worker-service.cjs`
|
||||
5. Bundles web viewer UI to `plugin/ui/viewer.html`
|
||||
|
||||
**Build Output**:
|
||||
- Hook executables: `*-hook.js` (ESM format)
|
||||
- Smart installer: `smart-install.js` (ESM format)
|
||||
- Worker service: `worker-service.cjs` (CJS format)
|
||||
- Search server: `search-server.js` (ESM format)
|
||||
- Search server: `search-server.mjs` (ESM format)
|
||||
- Viewer UI: `viewer.html` (self-contained HTML bundle)
|
||||
|
||||
### Build Scripts
|
||||
|
||||
@@ -66,6 +69,8 @@ src/
|
||||
├── servers/ # MCP search server
|
||||
├── sdk/ # Claude Agent SDK integration
|
||||
├── shared/ # Shared utilities
|
||||
├── ui/
|
||||
│ └── viewer/ # React web viewer UI components
|
||||
└── utils/ # General utilities
|
||||
```
|
||||
|
||||
@@ -111,6 +116,111 @@ echo '{"session_id":"test-123","cwd":"'$(pwd)'","source":"startup"}' | node plug
|
||||
|
||||
Repeat steps 1-4 until your changes work as expected.
|
||||
|
||||
## Viewer UI Development
|
||||
|
||||
### Working with the React Viewer
|
||||
|
||||
The web viewer UI is a React application built into a self-contained HTML bundle.
|
||||
|
||||
**Location**: `src/ui/viewer/`
|
||||
|
||||
**Structure**:
|
||||
```
|
||||
src/ui/viewer/
|
||||
├── index.tsx # Entry point
|
||||
├── App.tsx # Main application component
|
||||
├── components/ # React components
|
||||
│ ├── Header.tsx # Header with logo and actions
|
||||
│ ├── Sidebar.tsx # Project filter sidebar
|
||||
│ ├── Feed.tsx # Main feed with infinite scroll
|
||||
│ ├── cards/ # Card components
|
||||
│ │ ├── ObservationCard.tsx
|
||||
│ │ ├── PromptCard.tsx
|
||||
│ │ ├── SummaryCard.tsx
|
||||
│ │ └── SkeletonCard.tsx
|
||||
├── hooks/ # Custom React hooks
|
||||
│ ├── useSSE.ts # Server-Sent Events connection
|
||||
│ ├── usePagination.ts # Infinite scroll pagination
|
||||
│ ├── useSettings.ts # Settings persistence
|
||||
│ └── useStats.ts # Database statistics
|
||||
├── utils/ # Utilities
|
||||
│ ├── constants.ts # Constants (API URLs, etc.)
|
||||
│ ├── formatters.ts # Date/time formatting
|
||||
│ └── merge.ts # Data merging and deduplication
|
||||
└── assets/ # Static assets (fonts, logos)
|
||||
```
|
||||
|
||||
### Building Viewer UI
|
||||
|
||||
```bash
|
||||
# Build everything including viewer
|
||||
npm run build
|
||||
|
||||
# The viewer is built to plugin/ui/viewer.html
|
||||
# It's a self-contained HTML file with inlined JS and CSS
|
||||
```
|
||||
|
||||
### Testing Viewer Changes
|
||||
|
||||
1. Make changes to React components in `src/ui/viewer/`
|
||||
2. Build: `npm run build`
|
||||
3. Sync to installed plugin: `npm run sync-marketplace`
|
||||
4. Restart worker: `npm run worker:restart`
|
||||
5. Refresh browser at http://localhost:37777
|
||||
|
||||
**Hot Reload**: Not currently supported. Full rebuild + restart required for changes.
|
||||
|
||||
### Adding New Viewer Features
|
||||
|
||||
**Example: Adding a new card type**
|
||||
|
||||
1. Create component in `src/ui/viewer/components/cards/YourCard.tsx`:
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
|
||||
export interface YourCardProps {
|
||||
// Your data structure
|
||||
}
|
||||
|
||||
export const YourCard: React.FC<YourCardProps> = ({ ... }) => {
|
||||
return (
|
||||
<div className="card">
|
||||
{/* Your UI */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
2. Import and use in `Feed.tsx`:
|
||||
|
||||
```tsx
|
||||
import { YourCard } from './cards/YourCard';
|
||||
|
||||
// In render logic:
|
||||
{item.type === 'your_type' && <YourCard {...item} />}
|
||||
```
|
||||
|
||||
3. Update types if needed in `src/ui/viewer/types.ts`
|
||||
|
||||
4. Rebuild and test
|
||||
|
||||
### Viewer UI Architecture
|
||||
|
||||
**Data Flow**:
|
||||
1. Worker service exposes HTTP + SSE endpoints
|
||||
2. React app fetches initial data via HTTP (paginated)
|
||||
3. SSE connection provides real-time updates
|
||||
4. Custom hooks handle state management and data merging
|
||||
5. Components render cards based on item type
|
||||
|
||||
**Key Patterns**:
|
||||
- **Infinite Scroll**: `usePagination` hook with Intersection Observer
|
||||
- **Real-Time Updates**: `useSSE` hook with auto-reconnection
|
||||
- **Deduplication**: `merge.ts` utilities prevent duplicate items
|
||||
- **Settings Persistence**: `useSettings` hook with localStorage
|
||||
- **Theme Support**: CSS variables with light/dark/system themes
|
||||
|
||||
## Adding New Features
|
||||
|
||||
### Adding a New Hook
|
||||
|
||||
+124
-41
@@ -15,15 +15,15 @@ Claude-Mem is fundamentally a **hook-driven system**. Every piece of functionali
|
||||
│ (Main session - user interacting with Claude) │
|
||||
│ │
|
||||
│ SessionStart → UserPromptSubmit → Tool Use → Stop │
|
||||
│ ↓ ↓ ↓ ↓ │
|
||||
│ [Hook] [Hook] [Hook] [Hook] │
|
||||
│ ↓ ↓ ↓ ↓ ↓ ↓ │
|
||||
│ [3 Hooks] [Hook] [Hook] [Hook] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓ ↓ ↓ ↓
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ CLAUDE-MEM SYSTEM │
|
||||
│ │
|
||||
│ Context New Session Observation Summary │
|
||||
│ Injection Tracking Capture Generation │
|
||||
│ Smart Context User New Obs │
|
||||
│ Install Inject Message Session Capture │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -68,42 +68,71 @@ Claude Code's hook system provides exactly what we need:
|
||||
|
||||
---
|
||||
|
||||
## The Five Hooks
|
||||
## The Seven Hook Scripts
|
||||
|
||||
### Hook 1: SessionStart (Context Hook)
|
||||
Claude-Mem uses 7 hook scripts across 5 lifecycle events. SessionStart runs 3 hooks in sequence.
|
||||
|
||||
**Purpose:** Inject relevant context from previous sessions
|
||||
### Hook 1: SessionStart - Smart Install
|
||||
|
||||
**When:** Claude Code starts or resumes
|
||||
**Purpose:** Intelligently manage dependencies and start worker service
|
||||
|
||||
**When:** Claude Code starts (startup, clear, or compact)
|
||||
|
||||
**What it does:**
|
||||
1. Extracts project name from current working directory
|
||||
2. Queries SQLite for recent session summaries (last 10)
|
||||
3. Queries SQLite for recent observations (last 50)
|
||||
4. Formats as progressive disclosure index
|
||||
5. Outputs to stdout (automatically injected into context)
|
||||
1. Checks if dependencies need installation (version marker)
|
||||
2. Only runs `npm install` when necessary:
|
||||
- First-time installation
|
||||
- Version changed in package.json
|
||||
- Critical dependency missing (better-sqlite3)
|
||||
3. Provides Windows-specific error messages
|
||||
4. Starts PM2 worker service
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [{
|
||||
"matcher": "startup",
|
||||
"matcher": "startup|clear|compact",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
|
||||
"timeout": 120
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/../scripts/smart-install.js\" && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
|
||||
"timeout": 300
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- ✅ Version caching (`.install-version` file)
|
||||
- ✅ Fast when already installed (~10ms vs 2-5 seconds)
|
||||
- ✅ Cross-platform compatible
|
||||
- ✅ Helpful Windows error messages for build tools
|
||||
|
||||
**v5.0.3 Enhancement:** Smart caching eliminates redundant installs
|
||||
|
||||
**Source:** `scripts/smart-install.js`
|
||||
|
||||
---
|
||||
|
||||
### Hook 2: SessionStart - Context Injection
|
||||
|
||||
**Purpose:** Inject relevant context from previous sessions
|
||||
|
||||
**When:** Claude Code starts (runs after smart-install)
|
||||
|
||||
**What it does:**
|
||||
1. Extracts project name from current working directory
|
||||
2. Queries SQLite for recent session summaries (last 10)
|
||||
3. Queries SQLite for recent observations (configurable, default 50)
|
||||
4. Formats as progressive disclosure index
|
||||
5. Outputs to stdout (automatically injected into context)
|
||||
|
||||
**Key decisions:**
|
||||
- ✅ Only runs on "startup" (not "clear" or "compact")
|
||||
- ✅ 120-second timeout for npm install (v4.3.1 fix)
|
||||
- ✅ Uses `--loglevel=silent` for clean JSON output
|
||||
- ✅ Runs on startup, clear, and compact
|
||||
- ✅ 300-second timeout (allows for npm install if needed)
|
||||
- ✅ Progressive disclosure format (index, not full details)
|
||||
- ✅ Configurable observation count via `CLAUDE_MEM_CONTEXT_OBSERVATIONS`
|
||||
|
||||
**Output format:**
|
||||
```markdown
|
||||
@@ -125,7 +154,56 @@ Claude Code's hook system provides exactly what we need:
|
||||
|
||||
---
|
||||
|
||||
### Hook 2: UserPromptSubmit (New Session Hook)
|
||||
### Hook 3: SessionStart - User Message
|
||||
|
||||
**Purpose:** Display helpful user messages during first-time setup
|
||||
|
||||
**When:** Claude Code starts (runs after context-hook)
|
||||
|
||||
**What it does:**
|
||||
1. Checks if dependencies are installed
|
||||
2. Shows first-time setup message if needed
|
||||
3. Displays formatted context information with colors
|
||||
4. Shows link to viewer UI (http://localhost:37777)
|
||||
5. Exits with code 3 (informational, not error)
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [{
|
||||
"matcher": "startup|clear|compact",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js",
|
||||
"timeout": 10
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Output Example:**
|
||||
```
|
||||
📝 Claude-Mem Context Loaded
|
||||
ℹ️ Note: This appears as stderr but is informational only
|
||||
|
||||
[Context details with colors...]
|
||||
|
||||
📺 Watch live in browser http://localhost:37777/ (New! v5.1)
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- ✅ User-friendly first-time experience
|
||||
- ✅ Visual context display
|
||||
- ✅ Links to viewer UI
|
||||
- ✅ Non-intrusive (exit code 3)
|
||||
|
||||
**Source:** `plugin/scripts/user-message-hook.js` (minified)
|
||||
|
||||
---
|
||||
|
||||
### Hook 4: UserPromptSubmit (New Session Hook)
|
||||
|
||||
**Purpose:** Initialize session tracking when user submits a prompt
|
||||
|
||||
@@ -172,7 +250,7 @@ VALUES (?, ?, ?, ...)
|
||||
|
||||
---
|
||||
|
||||
### Hook 3: PostToolUse (Save Observation Hook)
|
||||
### Hook 5: PostToolUse (Save Observation Hook)
|
||||
|
||||
**Purpose:** Capture tool execution observations for later processing
|
||||
|
||||
@@ -233,7 +311,7 @@ VALUES (?, ?, ?, ?, ...)
|
||||
|
||||
---
|
||||
|
||||
### Hook 4: Summary Hook (Mid-Session Checkpoint)
|
||||
### Hook 6: Summary Hook (Mid-Session Checkpoint)
|
||||
|
||||
**Purpose:** Generate AI-powered session summaries during the session
|
||||
|
||||
@@ -288,7 +366,7 @@ VALUES (?, ?, ?, ?, ...)
|
||||
|
||||
---
|
||||
|
||||
### Hook 5: SessionEnd (Cleanup Hook)
|
||||
### Hook 7: SessionEnd (Cleanup Hook)
|
||||
|
||||
**Purpose:** Mark sessions as completed when they end
|
||||
|
||||
@@ -395,11 +473,13 @@ sequenceDiagram
|
||||
|
||||
| Event | Timing | Blocking | Timeout | Output Handling |
|
||||
|-------|--------|----------|---------|-----------------|
|
||||
| **SessionStart** | Before session | No | 120s | stdout → context |
|
||||
| **UserPromptSubmit** | Before processing | No | 60s | stdout → context |
|
||||
| **PostToolUse** | After tool | No | 60s | Transcript only |
|
||||
| **Summary** | Worker triggered | No | 300s | Database |
|
||||
| **SessionEnd** | On exit | No | 60s | Log only |
|
||||
| **SessionStart (smart-install)** | Before session | No | 300s | stderr (info) |
|
||||
| **SessionStart (context)** | Before session | No | 300s | stdout → context |
|
||||
| **SessionStart (user-message)** | Before session | No | 10s | stderr (info) |
|
||||
| **UserPromptSubmit** | Before processing | No | 120s | stdout → context |
|
||||
| **PostToolUse** | After tool | No | 120s | Transcript only |
|
||||
| **Summary** | Worker triggered | No | 120s | Database |
|
||||
| **SessionEnd** | On exit | No | 120s | Log only |
|
||||
|
||||
---
|
||||
|
||||
@@ -668,20 +748,23 @@ claude --debug
|
||||
|
||||
| Hook | Average | p95 | p99 |
|
||||
|------|---------|-----|-----|
|
||||
| SessionStart | 45ms | 120ms | 250ms |
|
||||
| SessionStart (smart-install, cached) | 10ms | 20ms | 40ms |
|
||||
| SessionStart (smart-install, first run) | 2500ms | 5000ms | 8000ms |
|
||||
| SessionStart (context) | 45ms | 120ms | 250ms |
|
||||
| SessionStart (user-message) | 5ms | 10ms | 15ms |
|
||||
| UserPromptSubmit | 12ms | 25ms | 50ms |
|
||||
| PostToolUse | 8ms | 15ms | 30ms |
|
||||
| SessionEnd | 5ms | 10ms | 20ms |
|
||||
|
||||
**Why SessionStart is slower:**
|
||||
- npm install check (idempotent but runs every time)
|
||||
- Database query for 10 sessions + 50 observations
|
||||
- Formatting progressive disclosure index
|
||||
**Why smart-install is sometimes slow:**
|
||||
- First-time: Full npm install (2-5 seconds)
|
||||
- Cached: Version check only (~10ms)
|
||||
- Version change: Full npm install + PM2 restart
|
||||
|
||||
**Optimization (v4.3.1):**
|
||||
- Use `--loglevel=silent` for npm install
|
||||
- Cache package.json hash to skip unnecessary installs
|
||||
- Use prepared statements for database queries
|
||||
**Optimization (v5.0.3):**
|
||||
- Version caching with `.install-version` marker
|
||||
- Only install on version change or missing deps
|
||||
- Windows-specific error messages with build tool help
|
||||
|
||||
### Database Performance
|
||||
|
||||
@@ -775,9 +858,9 @@ LIMIT 20
|
||||
## Further Reading
|
||||
|
||||
- [Claude Code Hooks Reference](https://docs.claude.com/claude-code/hooks) - Official documentation
|
||||
- [Progressive Disclosure](/docs/progressive-disclosure) - Context priming philosophy
|
||||
- [Architecture Evolution](/docs/architecture-evolution) - v3 to v4 journey
|
||||
- [Worker Service Design](/docs/worker-service) - Background processing details
|
||||
- [Progressive Disclosure](progressive-disclosure) - Context priming philosophy
|
||||
- [Architecture Evolution](architecture-evolution) - v3 to v4 journey
|
||||
- [Worker Service Design](architecture/worker-service) - Background processing details
|
||||
|
||||
---
|
||||
|
||||
|
||||
+17
-13
@@ -23,7 +23,9 @@ Restart Claude Code. Context from previous sessions will automatically appear in
|
||||
## Key Features
|
||||
|
||||
- 🧠 **Persistent Memory** - Context survives across sessions
|
||||
- 🔍 **7 Search Tools** - Query your project history via MCP
|
||||
- 🔍 **9 Search Tools** - Query your project history via MCP
|
||||
- 🌐 **Web Viewer UI** - Real-time memory stream visualization at http://localhost:37777
|
||||
- 🎨 **Theme Toggle** - Light, dark, and system preference themes
|
||||
- 🤖 **Automatic Operation** - No manual intervention required
|
||||
- 📊 **FTS5 Search** - Fast full-text search across observations
|
||||
- 🔗 **Citations** - Reference past decisions with `claude-mem://` URIs
|
||||
@@ -56,7 +58,8 @@ Restart Claude Code. Context from previous sessions will automatically appear in
|
||||
1. **5 Lifecycle Hooks** - SessionStart, UserPromptSubmit, PostToolUse, Stop, SessionEnd
|
||||
2. **Worker Service** - HTTP API on port 37777 managed by PM2
|
||||
3. **SQLite Database** - Stores sessions, observations, summaries with FTS5 search
|
||||
4. **7 MCP Search Tools** - Query historical context with citations
|
||||
4. **9 MCP Search Tools** - Query historical context with citations
|
||||
5. **Web Viewer UI** - Real-time visualization with SSE and infinite scroll
|
||||
|
||||
See [Architecture Overview](architecture/overview) for details.
|
||||
|
||||
@@ -67,21 +70,22 @@ See [Architecture Overview](architecture/overview) for details.
|
||||
- **PM2**: Process manager (bundled - no global install required)
|
||||
- **SQLite 3**: For persistent storage (bundled)
|
||||
|
||||
## What's New in v4.3.1
|
||||
## What's New in v5.1.2
|
||||
|
||||
**Critical Fix:**
|
||||
- Fixed SessionStart hook context injection (v4.3.1)
|
||||
- Context wasn't being injected due to npm output pollution
|
||||
- Changed npm loglevel to `--loglevel=silent` for clean JSON output
|
||||
**Latest Updates (v5.1.2):**
|
||||
- Theme toggle for light, dark, and system preferences in viewer UI
|
||||
- Improved visual design with theme-aware components
|
||||
|
||||
**Code Quality:**
|
||||
- Consolidated hooks architecture (removed bin/hooks wrapper layer)
|
||||
- Fixed double shebang issues in hook executables
|
||||
**Recent Updates (v5.1.0):**
|
||||
- Web-based viewer UI for real-time memory stream visualization
|
||||
- Server-Sent Events (SSE) for instant updates
|
||||
- Infinite scroll pagination with project filtering
|
||||
- 8 new HTTP/SSE endpoints in worker service
|
||||
|
||||
**Recent Updates (v4.3.0):**
|
||||
**Previous Updates (v4.3.1):**
|
||||
- Fixed SessionStart hook context injection
|
||||
- Smart install caching for Windows compatibility
|
||||
- Progressive disclosure context with observation timelines
|
||||
- Enhanced session summaries with token cost visibility
|
||||
- Cross-platform path detection improvements
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
@@ -644,8 +644,8 @@ Progressive disclosure respects the agent's intelligence and autonomy. We provid
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Context Engineering for AI Agents](/docs/context-engineering) - Foundational principles
|
||||
- [Claude-Mem Architecture](/docs/architecture) - How it all fits together
|
||||
- [Context Engineering for AI Agents](context-engineering) - Foundational principles
|
||||
- [Claude-Mem Architecture](architecture/overview) - How it all fits together
|
||||
- Cognitive Load Theory (Sweller, 1988)
|
||||
- Information Foraging Theory (Pirolli & Card, 1999)
|
||||
- Progressive Disclosure (Nielsen Norman Group)
|
||||
|
||||
@@ -5,6 +5,177 @@ description: "Common issues and solutions for Claude-Mem"
|
||||
|
||||
# Troubleshooting Guide
|
||||
|
||||
## v5.x Specific Issues
|
||||
|
||||
### Viewer UI Not Loading
|
||||
|
||||
**Symptoms**: Cannot access http://localhost:37777, page doesn't load, or shows connection error.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Check if worker is running on port 37777:
|
||||
```bash
|
||||
lsof -i :37777
|
||||
# or
|
||||
npm run worker:status
|
||||
```
|
||||
|
||||
2. Verify worker is healthy:
|
||||
```bash
|
||||
curl http://localhost:37777/health
|
||||
```
|
||||
|
||||
3. Check worker logs for errors:
|
||||
```bash
|
||||
npm run worker:logs
|
||||
```
|
||||
|
||||
4. Restart worker service:
|
||||
```bash
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
5. Check for port conflicts:
|
||||
```bash
|
||||
# If port 37777 is in use by another service
|
||||
export CLAUDE_MEM_WORKER_PORT=38000
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
### Theme Toggle Not Persisting
|
||||
|
||||
**Symptoms**: Theme preference (light/dark mode) resets after browser refresh.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Check browser localStorage is enabled:
|
||||
```javascript
|
||||
// In browser console
|
||||
localStorage.getItem('claude-mem-settings')
|
||||
```
|
||||
|
||||
2. Verify settings endpoint is working:
|
||||
```bash
|
||||
curl http://localhost:37777/api/settings
|
||||
```
|
||||
|
||||
3. Clear localStorage and try again:
|
||||
```javascript
|
||||
// In browser console
|
||||
localStorage.removeItem('claude-mem-settings')
|
||||
```
|
||||
|
||||
4. Check for browser privacy mode (blocks localStorage)
|
||||
|
||||
### SSE Connection Issues
|
||||
|
||||
**Symptoms**: Viewer shows "Disconnected" status, updates not appearing in real-time.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Check SSE endpoint is accessible:
|
||||
```bash
|
||||
curl -N http://localhost:37777/stream
|
||||
```
|
||||
|
||||
2. Check browser console for errors:
|
||||
- Open DevTools (F12)
|
||||
- Look for EventSource errors
|
||||
- Check Network tab for failed /stream requests
|
||||
|
||||
3. Verify worker is running:
|
||||
```bash
|
||||
npm run worker:status
|
||||
```
|
||||
|
||||
4. Check for network/proxy issues blocking SSE
|
||||
- Corporate firewalls may block SSE
|
||||
- Try disabling VPN temporarily
|
||||
|
||||
5. Restart worker and refresh browser:
|
||||
```bash
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
### Chroma/Python Dependency Issues (v5.0.0+)
|
||||
|
||||
**Symptoms**: Installation fails with chromadb or Python-related errors.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Verify Python 3.8+ is installed:
|
||||
```bash
|
||||
python --version
|
||||
# or
|
||||
python3 --version
|
||||
```
|
||||
|
||||
2. Install chromadb manually:
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack
|
||||
npm install chromadb
|
||||
```
|
||||
|
||||
3. Check chromadb health:
|
||||
```bash
|
||||
npm run chroma:health
|
||||
```
|
||||
|
||||
4. Windows-specific: Ensure Python is in PATH:
|
||||
```bash
|
||||
where python
|
||||
# Should show Python installation path
|
||||
```
|
||||
|
||||
5. If Chroma continues to fail, hybrid search will gracefully degrade to SQLite FTS5 only
|
||||
|
||||
### Smart Install Caching Issues (v5.0.3+)
|
||||
|
||||
**Symptoms**: Dependencies not updating after plugin update, stale version marker.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Clear install cache:
|
||||
```bash
|
||||
rm ~/.claude/plugins/marketplaces/thedotmack/.install-version
|
||||
```
|
||||
|
||||
2. Force reinstall:
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack
|
||||
npm install --force
|
||||
```
|
||||
|
||||
3. Check version marker:
|
||||
```bash
|
||||
cat ~/.claude/plugins/marketplaces/thedotmack/.install-version
|
||||
cat ~/.claude/plugins/marketplaces/thedotmack/package.json | grep version
|
||||
```
|
||||
|
||||
4. Restart Claude Code after manual install
|
||||
|
||||
### PM2 ENOENT Error on Windows (v5.1.1 Fix)
|
||||
|
||||
**Symptoms**: Worker fails to start with "ENOENT" error on Windows.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. This was fixed in v5.1.1 - update to latest version:
|
||||
```bash
|
||||
/plugin update claude-mem
|
||||
```
|
||||
|
||||
2. If still experiencing issues, verify PM2 path:
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack
|
||||
dir node_modules\.bin\pm2.cmd
|
||||
```
|
||||
|
||||
3. Manual PM2 install if needed:
|
||||
```bash
|
||||
npm install pm2@latest
|
||||
```
|
||||
|
||||
## Worker Service Issues
|
||||
|
||||
### Worker Service Not Starting
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
title: "MCP Search Tools"
|
||||
description: "Query your project history with 7 specialized search tools"
|
||||
description: "Query your project history with 9 specialized search tools"
|
||||
---
|
||||
|
||||
# MCP Search Tools Usage
|
||||
|
||||
Once claude-mem is installed as a plugin, 7 search tools become available in your Claude Code sessions for querying project history.
|
||||
Once claude-mem is installed as a plugin, 9 search tools become available in your Claude Code sessions for querying project history.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
@@ -18,6 +18,8 @@ Once claude-mem is installed as a plugin, 7 search tools become available in you
|
||||
| find_by_file | Find observations referencing files |
|
||||
| find_by_type | Find observations by type |
|
||||
| get_recent_context | Get recent session context |
|
||||
| get_context_timeline | Get unified timeline around a specific point |
|
||||
| get_timeline_by_query | Search and get timeline context in one step |
|
||||
|
||||
## Example Queries
|
||||
|
||||
@@ -115,6 +117,70 @@ Get recent context for debugging:
|
||||
Use get_recent_context to show me what we've been working on
|
||||
```
|
||||
|
||||
### get_context_timeline
|
||||
|
||||
Get a unified timeline of context around a specific point in time. This tool interleaves observations, sessions, and user prompts chronologically to show what was happening before and after a specific moment.
|
||||
|
||||
**Anchor by observation ID:**
|
||||
```
|
||||
get_context_timeline with anchor=12345 and depth_before=10 and depth_after=10
|
||||
```
|
||||
|
||||
**Anchor by session ID:**
|
||||
```
|
||||
get_context_timeline with anchor="S123" and depth_before=5 and depth_after=5
|
||||
```
|
||||
|
||||
**Anchor by ISO timestamp:**
|
||||
```
|
||||
get_context_timeline with anchor="2025-10-21T14:30:00Z" and depth_before=15 and depth_after=15
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Understand what was happening when a specific observation occurred
|
||||
- See the full context around a bug fix or decision
|
||||
- Trace the events leading up to and following a specific change
|
||||
- View chronological sequence of related work
|
||||
|
||||
**Benefits:**
|
||||
- All record types (observations, sessions, prompts) in one chronological view
|
||||
- Configurable depth before/after anchor point
|
||||
- Flexible anchoring by ID or timestamp
|
||||
- See the complete narrative arc around key events
|
||||
|
||||
### get_timeline_by_query
|
||||
|
||||
Search for observations using natural language and get timeline context around the best match. This combines search + timeline into a single operation for faster context discovery.
|
||||
|
||||
**Auto mode (default):**
|
||||
```
|
||||
get_timeline_by_query with query="authentication implementation"
|
||||
```
|
||||
Automatically uses the top search result as timeline anchor and returns surrounding context.
|
||||
|
||||
**Interactive mode:**
|
||||
```
|
||||
get_timeline_by_query with query="authentication" and mode="interactive" and limit=5
|
||||
```
|
||||
Shows top 5 search results for you to manually choose which to use as timeline anchor.
|
||||
|
||||
**Customize timeline depth:**
|
||||
```
|
||||
get_timeline_by_query with query="bug fix" and depth_before=20 and depth_after=10
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Quick context discovery: "What was happening when we implemented X?"
|
||||
- Investigate issues: Find a bug fix and see what led to it
|
||||
- Decision archaeology: Search for a decision and understand the context
|
||||
- Feature timeline: See the complete story of a feature implementation
|
||||
|
||||
**Benefits:**
|
||||
- Single-step operation (no need to search, then timeline separately)
|
||||
- Auto mode provides instant context
|
||||
- Interactive mode gives you control over anchor selection
|
||||
- Natural language search makes it easy to find relevant moments
|
||||
|
||||
## Search Strategy
|
||||
|
||||
### 1. Start with Index Format
|
||||
|
||||
Generated
+2
-6
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "5.0.3",
|
||||
"version": "5.1.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-mem",
|
||||
"version": "5.0.3",
|
||||
"version": "5.1.3",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.27",
|
||||
@@ -1473,7 +1473,6 @@
|
||||
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -2327,7 +2326,6 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
@@ -3840,7 +3838,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -4842,7 +4839,6 @@
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "5.1.0",
|
||||
"version": "5.1.4",
|
||||
"description": "Memory compression system for Claude Code - persist context across sessions",
|
||||
"keywords": [
|
||||
"claude",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "5.1.0",
|
||||
"version": "5.1.4",
|
||||
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
|
||||
"author": {
|
||||
"name": "Alex Newman"
|
||||
|
||||
@@ -400,4 +400,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
|
||||
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${o.replace("project","s.project")}
|
||||
ORDER BY up.created_at_epoch ASC
|
||||
`;try{let T=this.db.prepare(c).all(a,_,...i),g=this.db.prepare(m).all(a,_,...i),u=this.db.prepare(S).all(a,_,...i);return{observations:T,sessions:g.map(d=>({id:d.id,sdk_session_id:d.sdk_session_id,project:d.project,request:d.request,completed:d.completed,next_steps:d.next_steps,created_at:d.created_at,created_at_epoch:d.created_at_epoch})),prompts:u.map(d=>({id:d.id,claude_session_id:d.claude_session_id,project:d.project,prompt:d.prompt_text,created_at:d.created_at,created_at_epoch:d.created_at_epoch}))}}catch(T){return console.error("[SessionStore] Error querying timeline records:",T.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(p,e,s){return p==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:p==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:p==="UserPromptSubmit"||p==="PostToolUse"?{continue:!0,suppressOutput:!0}:p==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function f(p,e,s={}){let t=$(p,e,s);return JSON.stringify(t)}import y from"path";import{spawn as D}from"child_process";var W=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10);async function k(p=100){try{return(await fetch(`http://127.0.0.1:${W}/health`,{signal:AbortSignal.timeout(p)})).ok}catch{return!1}}async function G(p=1e4){let e=Date.now(),s=100;for(;Date.now()-e<p;){if(await k(1e3))return!0;await new Promise(t=>setTimeout(t,s))}return!1}async function x(){if(await k())return;let p=v(),e=y.join(p,"node_modules",".bin","pm2"),s=y.join(p,"ecosystem.config.cjs"),t=D(e,["list","--no-color"],{cwd:p,stdio:["ignore","pipe","ignore"]}),r="";if(t.stdout?.on("data",i=>{r+=i.toString()}),await new Promise((i,a)=>{t.on("error",_=>a(_)),t.on("close",_=>{i()})}),!(r.includes("claude-mem-worker")&&r.includes("online"))){let i=D(e,["start",s],{cwd:p,stdio:"ignore"});await new Promise((a,_)=>{i.on("error",c=>_(c)),i.on("close",c=>{c!==0&&c!==null?_(new Error(`PM2 start command failed with exit code ${c}`)):a()})})}if(!await G(1e4))throw new Error("Worker failed to become healthy after starting")}var Y=new Set(["ListMcpResourcesTool"]);async function K(p){if(!p)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_output:r}=p;if(Y.has(s)){console.log(f("PostToolUse",!0));return}await x();let n=new R,o=n.createSDKSession(e,"",""),i=n.getPromptCounter(o);n.close();let a=b.formatTool(s,t),_=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10);b.dataIn("HOOK",`PostToolUse: ${a}`,{sessionId:o,workerPort:_});try{let c=await fetch(`http://127.0.0.1:${_}/sessions/${o}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_output:r!==void 0?JSON.stringify(r):"{}",prompt_number:i}),signal:AbortSignal.timeout(2e3)});if(!c.ok){let m=await c.text();throw b.failure("HOOK","Failed to send observation",{sessionId:o,status:c.status},m),new Error(`Failed to send observation to worker: ${c.status} ${m}`)}b.debug("HOOK","Observation sent successfully",{sessionId:o,toolName:s})}catch(c){throw c.cause?.code==="ECONNREFUSED"||c.name==="TimeoutError"||c.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):c}console.log(f("PostToolUse",!0))}var I="";U.on("data",p=>I+=p);U.on("end",async()=>{let p=I?JSON.parse(I):void 0;await K(p)});
|
||||
`;try{let T=this.db.prepare(c).all(a,_,...i),g=this.db.prepare(m).all(a,_,...i),u=this.db.prepare(S).all(a,_,...i);return{observations:T,sessions:g.map(d=>({id:d.id,sdk_session_id:d.sdk_session_id,project:d.project,request:d.request,completed:d.completed,next_steps:d.next_steps,created_at:d.created_at,created_at_epoch:d.created_at_epoch})),prompts:u.map(d=>({id:d.id,claude_session_id:d.claude_session_id,project:d.project,prompt:d.prompt_text,created_at:d.created_at,created_at_epoch:d.created_at_epoch}))}}catch(T){return console.error("[SessionStore] Error querying timeline records:",T.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(p,e,s){return p==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:p==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:p==="UserPromptSubmit"||p==="PostToolUse"?{continue:!0,suppressOutput:!0}:p==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function f(p,e,s={}){let t=$(p,e,s);return JSON.stringify(t)}import y from"path";import{spawn as D}from"child_process";var W=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10);async function k(p=100){try{return(await fetch(`http://127.0.0.1:${W}/health`,{signal:AbortSignal.timeout(p)})).ok}catch{return!1}}async function G(p=1e4){let e=Date.now(),s=100;for(;Date.now()-e<p;){if(await k(1e3))return!0;await new Promise(t=>setTimeout(t,s))}return!1}async function x(){if(await k())return;let p=v(),e=y.join(p,"node_modules",".bin","pm2"),s=y.join(p,"ecosystem.config.cjs"),t=D(e,["list","--no-color"],{cwd:p,stdio:["ignore","pipe","ignore"]}),r="";if(t.stdout?.on("data",i=>{r+=i.toString()}),await new Promise((i,a)=>{t.on("error",_=>a(_)),t.on("close",_=>{i()})}),!(r.includes("claude-mem-worker")&&r.includes("online"))){let i=D(e,["start",s],{cwd:p,stdio:"ignore"});await new Promise((a,_)=>{i.on("error",c=>_(c)),i.on("close",c=>{c!==0&&c!==null?_(new Error(`PM2 start command failed with exit code ${c}`)):a()})})}if(!await G(1e4))throw new Error("Worker failed to become healthy after starting")}var Y=new Set(["ListMcpResourcesTool"]);async function K(p){if(!p)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_response:r}=p;if(Y.has(s)){console.log(f("PostToolUse",!0));return}await x();let n=new R,o=n.createSDKSession(e,"",""),i=n.getPromptCounter(o);n.close();let a=b.formatTool(s,t),_=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10);b.dataIn("HOOK",`PostToolUse: ${a}`,{sessionId:o,workerPort:_});try{let c=await fetch(`http://127.0.0.1:${_}/sessions/${o}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_response:r!==void 0?JSON.stringify(r):"{}",prompt_number:i}),signal:AbortSignal.timeout(2e3)});if(!c.ok){let m=await c.text();throw b.failure("HOOK","Failed to send observation",{sessionId:o,status:c.status},m),new Error(`Failed to send observation to worker: ${c.status} ${m}`)}b.debug("HOOK","Observation sent successfully",{sessionId:o,toolName:s})}catch(c){throw c.cause?.code==="ECONNREFUSED"||c.name==="TimeoutError"||c.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):c}console.log(f("PostToolUse",!0))}var I="";U.on("data",p=>I+=p);U.on("end",async()=>{let p=I?JSON.parse(I):void 0;await K(p)});
|
||||
|
||||
@@ -648,8 +648,8 @@ IMPORTANT: This is not the end of the session. You will receive more requests to
|
||||
WHERE up.claude_session_id = ?
|
||||
ORDER BY up.created_at_epoch DESC
|
||||
LIMIT 1
|
||||
`).get(l);n.close(),u&&this.broadcastSSE({type:"new_prompt",prompt:{id:u.id,claude_session_id:u.claude_session_id,project:u.project,prompt_number:u.prompt_number,prompt_text:u.prompt_text,created_at_epoch:u.created_at_epoch}}),u&&this.chromaSync.syncUserPrompt(u.id,u.sdk_session_id,u.project,u.prompt_text,u.prompt_number,u.created_at_epoch).catch(p=>{Y.failure("WORKER","Failed to sync user_prompt to Chroma - continuing",{promptId:u.id},p)}),c.generatorPromise=this.runSDKAgent(c).catch(p=>{Y.failure("WORKER","SDK agent error",{sessionId:r},p);let f=new pr;f.markSessionFailed(r),f.close(),this.sessions.delete(r)}),Y.success("WORKER","Session initialized",{sessionId:r,port:this.port}),a.json({status:"initialized",sessionDbId:r,port:this.port})}handleObservation(e,a){let r=parseInt(e.params.sessionDbId,10),{tool_name:s,tool_input:i,tool_output:n,prompt_number:o}=e.body,l=this.sessions.get(r);if(!l){let u=new pr,p=u.getSessionById(r);u.close(),l={sessionDbId:r,claudeSessionId:p.claude_session_id,sdkSessionId:null,project:p.project,userPrompt:p.user_prompt,pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:0,startTime:Date.now()},this.sessions.set(r,l),l.generatorPromise=this.runSDKAgent(l).catch(f=>{Y.failure("WORKER","SDK agent error",{sessionId:r},f);let d=new pr;d.markSessionFailed(r),d.close(),this.sessions.delete(r)})}let c=Y.formatTool(s,i);Y.dataIn("WORKER",`Observation queued: ${c}`,{sessionId:r,queue:l.pendingMessages.length+1}),l.pendingMessages.push({type:"observation",tool_name:s,tool_input:i,tool_output:n,prompt_number:o}),a.json({status:"queued",queueLength:l.pendingMessages.length})}handleSummarize(e,a){let r=parseInt(e.params.sessionDbId,10),{prompt_number:s}=e.body,i=this.sessions.get(r);if(!i){let n=new pr,o=n.getSessionById(r);n.close(),i={sessionDbId:r,claudeSessionId:o.claude_session_id,sdkSessionId:null,project:o.project,userPrompt:o.user_prompt,pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:0,startTime:Date.now()},this.sessions.set(r,i),i.generatorPromise=this.runSDKAgent(i).catch(l=>{Y.failure("WORKER","SDK agent error",{sessionId:r},l);let c=new pr;c.markSessionFailed(r),c.close(),this.sessions.delete(r)})}Y.dataIn("WORKER","Summary requested",{sessionId:r,promptNumber:s,queue:i.pendingMessages.length+1}),i.pendingMessages.push({type:"summarize",prompt_number:s}),this.broadcastProcessingStatus(i.claudeSessionId,!0),a.json({status:"queued",queueLength:i.pendingMessages.length})}handleStatus(e,a){let r=parseInt(e.params.sessionDbId,10),s=this.sessions.get(r);if(!s){a.status(404).json({error:"Session not found"});return}a.json({sessionDbId:r,sdkSessionId:s.sdkSessionId,project:s.project,pendingMessages:s.pendingMessages.length})}async handleDelete(e,a){let r=parseInt(e.params.sessionDbId,10),s=this.sessions.get(r);if(!s){a.status(404).json({error:"Session not found"});return}Y.warn("WORKER","Session delete requested",{sessionId:r}),s.abortController.abort(),s.generatorPromise&&await Promise.race([s.generatorPromise,new Promise(n=>setTimeout(n,5e3))]);let i=new pr;i.markSessionFailed(r),i.close(),this.sessions.delete(r),Y.info("WORKER","Session deleted",{sessionId:r}),a.json({status:"deleted"})}async runSDKAgent(e){Y.info("SDK","Agent starting",{sessionId:e.sessionDbId});let a=K5();Y.info("SDK",`Using Claude executable: ${a}`,{sessionId:e.sessionDbId});try{let r=$b({prompt:this.createMessageGenerator(e),options:{model:W5,disallowedTools:Q5,abortController:e.abortController,pathToClaudeCodeExecutable:a}});for await(let n of r){if(n.type==="assistant"){let o=n.message.content,l=Array.isArray(o)?o.filter(u=>u.type==="text").map(u=>u.text).join(`
|
||||
`):typeof o=="string"?o:"",c=l.length;Y.dataOut("SDK",`Response received (${c} chars)`,{sessionId:e.sessionDbId,promptNumber:e.lastPromptNumber}),Y.debug("SDK","Full response",{sessionId:e.sessionDbId},l),this.handleAgentMessage(e,l,e.lastPromptNumber)}n.type==="result"&&n.subtype}let s=Date.now()-e.startTime;Y.success("SDK","Agent completed",{sessionId:e.sessionDbId,duration:`${(s/1e3).toFixed(1)}s`});let i=new pr;i.markSessionCompleted(e.sessionDbId),i.close(),this.sessions.delete(e.sessionDbId)}catch(r){throw r.name==="AbortError"?Y.warn("SDK","Agent aborted",{sessionId:e.sessionDbId}):Y.failure("SDK","Agent error",{sessionId:e.sessionDbId},r),r}}async*createMessageGenerator(e){let a=dE(e.project,e.claudeSessionId,e.userPrompt);for(Y.dataIn("SDK",`Init prompt sent (${a.length} chars)`,{sessionId:e.sessionDbId,claudeSessionId:e.claudeSessionId,project:e.project}),Y.debug("SDK","Full init prompt",{sessionId:e.sessionDbId},a),yield{type:"user",session_id:e.claudeSessionId,parent_tool_use_id:null,message:{role:"user",content:a}};!e.abortController.signal.aborted;){if(e.pendingMessages.length===0){await new Promise(r=>setTimeout(r,100));continue}for(;e.pendingMessages.length>0;){let r=e.pendingMessages.shift();if(r.type==="summarize"){e.lastPromptNumber=r.prompt_number;let s=new pr,i=s.getSessionById(e.sessionDbId);s.close();let n=mE(i);Y.dataIn("SDK",`Summary prompt sent (${n.length} chars)`,{sessionId:e.sessionDbId,promptNumber:r.prompt_number}),Y.debug("SDK","Full summary prompt",{sessionId:e.sessionDbId},n),yield{type:"user",session_id:e.claudeSessionId,parent_tool_use_id:null,message:{role:"user",content:n}}}else if(r.type==="observation"){e.lastPromptNumber=r.prompt_number;let s=fE({id:0,tool_name:r.tool_name,tool_input:r.tool_input,tool_output:r.tool_output,created_at_epoch:Date.now()}),i=Y.formatTool(r.tool_name,r.tool_input);Y.dataIn("SDK",`Observation prompt: ${i}`,{sessionId:e.sessionDbId,promptNumber:r.prompt_number,size:`${s.length} chars`}),Y.debug("SDK","Full observation prompt",{sessionId:e.sessionDbId},s),yield{type:"user",session_id:e.claudeSessionId,parent_tool_use_id:null,message:{role:"user",content:s}}}}}}handleAgentMessage(e,a,r){Y.info("PARSER",`Processing response (${a.length} chars)`,{sessionId:e.sessionDbId,promptNumber:r,preview:a.substring(0,200)});let s=hE(a);s.length>0&&Y.info("PARSER",`Parsed ${s.length} observation(s)`,{sessionId:e.sessionDbId,promptNumber:r,types:s.map(o=>o.type).join(", ")});let i=new pr;for(let o of s){let{id:l,createdAtEpoch:c}=i.storeObservation(e.claudeSessionId,e.project,o,r);Y.success("DB","Observation stored",{sessionId:e.sessionDbId,type:o.type,title:o.title,id:l}),this.broadcastSSE({type:"new_observation",observation:{id:l,session_id:e.claudeSessionId,type:o.type,title:o.title,subtitle:o.subtitle,project:e.project,prompt_number:r,created_at_epoch:c}}),this.chromaSync.syncObservation(l,e.claudeSessionId,e.project,o,r,c).then(()=>{Y.success("WORKER","Observation synced to Chroma",{sessionId:e.sessionDbId,observationId:l})}).catch(u=>{Y.error("WORKER","Observation sync failed - continuing",{sessionId:e.sessionDbId,observationId:l},u)})}Y.info("PARSER","Looking for summary tags...",{sessionId:e.sessionDbId});let n=vE(a,e.sessionDbId);if(n){Y.success("PARSER","Summary parsed successfully!",{sessionId:e.sessionDbId,promptNumber:r,hasRequest:!!n.request,hasInvestigated:!!n.investigated,hasLearned:!!n.learned,hasCompleted:!!n.completed,hasNextSteps:!!n.next_steps});let{id:o,createdAtEpoch:l}=i.storeSummary(e.claudeSessionId,e.project,n,r);Y.success("DB","\u{1F4DD} SUMMARY STORED IN DATABASE",{sessionId:e.sessionDbId,promptNumber:r,id:o}),this.broadcastSSE({type:"new_summary",summary:{id:o,session_id:e.claudeSessionId,request:n.request,investigated:n.investigated,learned:n.learned,completed:n.completed,next_steps:n.next_steps,notes:n.notes,project:e.project,prompt_number:r,created_at_epoch:l}}),this.broadcastProcessingStatus(e.claudeSessionId,!1),this.chromaSync.syncSummary(o,e.claudeSessionId,e.project,n,r,l).then(()=>{Y.success("WORKER","Summary synced to Chroma",{sessionId:e.sessionDbId,summaryId:o})}).catch(c=>{Y.error("WORKER","Summary sync failed - continuing",{sessionId:e.sessionDbId,summaryId:o},c)})}else Y.warn("PARSER","NO SUMMARY TAGS FOUND in response",{sessionId:e.sessionDbId,promptNumber:r,contentSample:a.substring(0,500)}),this.broadcastProcessingStatus(e.claudeSessionId,!1);i.close()}};async function X5(){await new Gc().start(),process.on("SIGINT",()=>{Y.warn("SYSTEM","Shutting down (SIGINT)"),process.exit(0)}),process.on("SIGTERM",()=>{Y.warn("SYSTEM","Shutting down (SIGTERM)"),process.exit(0)})}X5().catch(t=>{Y.failure("SYSTEM","Fatal startup error",{},t),process.exit(1)});0&&(module.exports={WorkerService});
|
||||
`).get(l);n.close(),u&&this.broadcastSSE({type:"new_prompt",prompt:{id:u.id,claude_session_id:u.claude_session_id,project:u.project,prompt_number:u.prompt_number,prompt_text:u.prompt_text,created_at_epoch:u.created_at_epoch}}),u&&this.chromaSync.syncUserPrompt(u.id,u.sdk_session_id,u.project,u.prompt_text,u.prompt_number,u.created_at_epoch).catch(p=>{Y.failure("WORKER","Failed to sync user_prompt to Chroma - continuing",{promptId:u.id},p)}),c.generatorPromise=this.runSDKAgent(c).catch(p=>{Y.failure("WORKER","SDK agent error",{sessionId:r},p);let f=new pr;f.markSessionFailed(r),f.close(),this.sessions.delete(r)}),Y.success("WORKER","Session initialized",{sessionId:r,port:this.port}),a.json({status:"initialized",sessionDbId:r,port:this.port})}handleObservation(e,a){let r=parseInt(e.params.sessionDbId,10),{tool_name:s,tool_input:i,tool_response:n,prompt_number:o}=e.body,l=this.sessions.get(r);if(!l){let u=new pr,p=u.getSessionById(r);u.close(),l={sessionDbId:r,claudeSessionId:p.claude_session_id,sdkSessionId:null,project:p.project,userPrompt:p.user_prompt,pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:0,startTime:Date.now()},this.sessions.set(r,l),l.generatorPromise=this.runSDKAgent(l).catch(f=>{Y.failure("WORKER","SDK agent error",{sessionId:r},f);let d=new pr;d.markSessionFailed(r),d.close(),this.sessions.delete(r)})}let c=Y.formatTool(s,i);Y.dataIn("WORKER",`Observation queued: ${c}`,{sessionId:r,queue:l.pendingMessages.length+1}),l.pendingMessages.push({type:"observation",tool_name:s,tool_input:i,tool_response:n,prompt_number:o}),a.json({status:"queued",queueLength:l.pendingMessages.length})}handleSummarize(e,a){let r=parseInt(e.params.sessionDbId,10),{prompt_number:s}=e.body,i=this.sessions.get(r);if(!i){let n=new pr,o=n.getSessionById(r);n.close(),i={sessionDbId:r,claudeSessionId:o.claude_session_id,sdkSessionId:null,project:o.project,userPrompt:o.user_prompt,pendingMessages:[],abortController:new AbortController,generatorPromise:null,lastPromptNumber:0,startTime:Date.now()},this.sessions.set(r,i),i.generatorPromise=this.runSDKAgent(i).catch(l=>{Y.failure("WORKER","SDK agent error",{sessionId:r},l);let c=new pr;c.markSessionFailed(r),c.close(),this.sessions.delete(r)})}Y.dataIn("WORKER","Summary requested",{sessionId:r,promptNumber:s,queue:i.pendingMessages.length+1}),i.pendingMessages.push({type:"summarize",prompt_number:s}),this.broadcastProcessingStatus(i.claudeSessionId,!0),a.json({status:"queued",queueLength:i.pendingMessages.length})}handleStatus(e,a){let r=parseInt(e.params.sessionDbId,10),s=this.sessions.get(r);if(!s){a.status(404).json({error:"Session not found"});return}a.json({sessionDbId:r,sdkSessionId:s.sdkSessionId,project:s.project,pendingMessages:s.pendingMessages.length})}async handleDelete(e,a){let r=parseInt(e.params.sessionDbId,10),s=this.sessions.get(r);if(!s){a.status(404).json({error:"Session not found"});return}Y.warn("WORKER","Session delete requested",{sessionId:r}),s.abortController.abort(),s.generatorPromise&&await Promise.race([s.generatorPromise,new Promise(n=>setTimeout(n,5e3))]);let i=new pr;i.markSessionFailed(r),i.close(),this.sessions.delete(r),Y.info("WORKER","Session deleted",{sessionId:r}),a.json({status:"deleted"})}async runSDKAgent(e){Y.info("SDK","Agent starting",{sessionId:e.sessionDbId});let a=K5();Y.info("SDK",`Using Claude executable: ${a}`,{sessionId:e.sessionDbId});try{let r=$b({prompt:this.createMessageGenerator(e),options:{model:W5,disallowedTools:Q5,abortController:e.abortController,pathToClaudeCodeExecutable:a}});for await(let n of r){if(n.type==="assistant"){let o=n.message.content,l=Array.isArray(o)?o.filter(u=>u.type==="text").map(u=>u.text).join(`
|
||||
`):typeof o=="string"?o:"",c=l.length;Y.dataOut("SDK",`Response received (${c} chars)`,{sessionId:e.sessionDbId,promptNumber:e.lastPromptNumber}),Y.debug("SDK","Full response",{sessionId:e.sessionDbId},l),this.handleAgentMessage(e,l,e.lastPromptNumber)}n.type==="result"&&n.subtype}let s=Date.now()-e.startTime;Y.success("SDK","Agent completed",{sessionId:e.sessionDbId,duration:`${(s/1e3).toFixed(1)}s`});let i=new pr;i.markSessionCompleted(e.sessionDbId),i.close(),this.sessions.delete(e.sessionDbId)}catch(r){throw r.name==="AbortError"?Y.warn("SDK","Agent aborted",{sessionId:e.sessionDbId}):Y.failure("SDK","Agent error",{sessionId:e.sessionDbId},r),r}}async*createMessageGenerator(e){let a=dE(e.project,e.claudeSessionId,e.userPrompt);for(Y.dataIn("SDK",`Init prompt sent (${a.length} chars)`,{sessionId:e.sessionDbId,claudeSessionId:e.claudeSessionId,project:e.project}),Y.debug("SDK","Full init prompt",{sessionId:e.sessionDbId},a),yield{type:"user",session_id:e.claudeSessionId,parent_tool_use_id:null,message:{role:"user",content:a}};!e.abortController.signal.aborted;){if(e.pendingMessages.length===0){await new Promise(r=>setTimeout(r,100));continue}for(;e.pendingMessages.length>0;){let r=e.pendingMessages.shift();if(r.type==="summarize"){e.lastPromptNumber=r.prompt_number;let s=new pr,i=s.getSessionById(e.sessionDbId);s.close();let n=mE(i);Y.dataIn("SDK",`Summary prompt sent (${n.length} chars)`,{sessionId:e.sessionDbId,promptNumber:r.prompt_number}),Y.debug("SDK","Full summary prompt",{sessionId:e.sessionDbId},n),yield{type:"user",session_id:e.claudeSessionId,parent_tool_use_id:null,message:{role:"user",content:n}}}else if(r.type==="observation"){e.lastPromptNumber=r.prompt_number;let s=fE({id:0,tool_name:r.tool_name,tool_input:r.tool_input,tool_response:r.tool_response,created_at_epoch:Date.now()}),i=Y.formatTool(r.tool_name,r.tool_input);Y.dataIn("SDK",`Observation prompt: ${i}`,{sessionId:e.sessionDbId,promptNumber:r.prompt_number,size:`${s.length} chars`}),Y.debug("SDK","Full observation prompt",{sessionId:e.sessionDbId},s),yield{type:"user",session_id:e.claudeSessionId,parent_tool_use_id:null,message:{role:"user",content:s}}}}}}handleAgentMessage(e,a,r){Y.info("PARSER",`Processing response (${a.length} chars)`,{sessionId:e.sessionDbId,promptNumber:r,preview:a.substring(0,200)});let s=hE(a);s.length>0&&Y.info("PARSER",`Parsed ${s.length} observation(s)`,{sessionId:e.sessionDbId,promptNumber:r,types:s.map(o=>o.type).join(", ")});let i=new pr;for(let o of s){let{id:l,createdAtEpoch:c}=i.storeObservation(e.claudeSessionId,e.project,o,r);Y.success("DB","Observation stored",{sessionId:e.sessionDbId,type:o.type,title:o.title,id:l}),this.broadcastSSE({type:"new_observation",observation:{id:l,session_id:e.claudeSessionId,type:o.type,title:o.title,subtitle:o.subtitle,project:e.project,prompt_number:r,created_at_epoch:c}}),this.chromaSync.syncObservation(l,e.claudeSessionId,e.project,o,r,c).then(()=>{Y.success("WORKER","Observation synced to Chroma",{sessionId:e.sessionDbId,observationId:l})}).catch(u=>{Y.error("WORKER","Observation sync failed - continuing",{sessionId:e.sessionDbId,observationId:l},u)})}Y.info("PARSER","Looking for summary tags...",{sessionId:e.sessionDbId});let n=vE(a,e.sessionDbId);if(n){Y.success("PARSER","Summary parsed successfully!",{sessionId:e.sessionDbId,promptNumber:r,hasRequest:!!n.request,hasInvestigated:!!n.investigated,hasLearned:!!n.learned,hasCompleted:!!n.completed,hasNextSteps:!!n.next_steps});let{id:o,createdAtEpoch:l}=i.storeSummary(e.claudeSessionId,e.project,n,r);Y.success("DB","\u{1F4DD} SUMMARY STORED IN DATABASE",{sessionId:e.sessionDbId,promptNumber:r,id:o}),this.broadcastSSE({type:"new_summary",summary:{id:o,session_id:e.claudeSessionId,request:n.request,investigated:n.investigated,learned:n.learned,completed:n.completed,next_steps:n.next_steps,notes:n.notes,project:e.project,prompt_number:r,created_at_epoch:l}}),this.broadcastProcessingStatus(e.claudeSessionId,!1),this.chromaSync.syncSummary(o,e.claudeSessionId,e.project,n,r,l).then(()=>{Y.success("WORKER","Summary synced to Chroma",{sessionId:e.sessionDbId,summaryId:o})}).catch(c=>{Y.error("WORKER","Summary sync failed - continuing",{sessionId:e.sessionDbId,summaryId:o},c)})}else Y.warn("PARSER","NO SUMMARY TAGS FOUND in response",{sessionId:e.sessionDbId,promptNumber:r,contentSample:a.substring(0,500)}),this.broadcastProcessingStatus(e.claudeSessionId,!1);i.close()}};async function X5(){await new Gc().start(),process.on("SIGINT",()=>{Y.warn("SYSTEM","Shutting down (SIGINT)"),process.exit(0)}),process.on("SIGTERM",()=>{Y.warn("SYSTEM","Shutting down (SIGTERM)"),process.exit(0)})}X5().catch(t=>{Y.failure("SYSTEM","Fatal startup error",{},t),process.exit(1)});0&&(module.exports={WorkerService});
|
||||
/*! Bundled license information:
|
||||
|
||||
depd/index.js:
|
||||
|
||||
File diff suppressed because one or more lines are too long
+313
-70
@@ -15,6 +15,246 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Theme Variables - Light Mode */
|
||||
:root,
|
||||
[data-theme="light"] {
|
||||
--color-bg-primary: #ffffff;
|
||||
--color-bg-secondary: #f6f8fa;
|
||||
--color-bg-tertiary: #f0f0f0;
|
||||
--color-bg-header: #f6f8fa;
|
||||
--color-bg-card: #ffffff;
|
||||
--color-bg-card-hover: #f6f8fa;
|
||||
--color-bg-input: #ffffff;
|
||||
--color-bg-button: #0969da;
|
||||
--color-bg-button-hover: #1177e6;
|
||||
--color-bg-button-active: #0860ca;
|
||||
--color-bg-summary: #fffbf0;
|
||||
--color-bg-prompt: #f6f3fb;
|
||||
--color-bg-stat: #f6f8fa;
|
||||
--color-bg-scrollbar-track: #ffffff;
|
||||
--color-bg-scrollbar-thumb: #d1d5da;
|
||||
--color-bg-scrollbar-thumb-hover: #b1b5ba;
|
||||
|
||||
--color-border-primary: #d0d7de;
|
||||
--color-border-secondary: #d8dee4;
|
||||
--color-border-hover: #0969da;
|
||||
--color-border-focus: #0969da;
|
||||
--color-border-summary: #d4a72c;
|
||||
--color-border-summary-hover: #c29d29;
|
||||
--color-border-prompt: #8250df;
|
||||
--color-border-prompt-hover: #6e40c9;
|
||||
|
||||
--color-text-primary: #24292f;
|
||||
--color-text-secondary: #57606a;
|
||||
--color-text-tertiary: #6e7781;
|
||||
--color-text-muted: #8b949e;
|
||||
--color-text-header: #24292f;
|
||||
--color-text-title: #24292f;
|
||||
--color-text-subtitle: #57606a;
|
||||
--color-text-button: #ffffff;
|
||||
--color-text-summary: #8a6116;
|
||||
--color-text-logo: #24292f;
|
||||
|
||||
--color-accent-primary: #0969da;
|
||||
--color-accent-focus: #0969da;
|
||||
--color-accent-success: #1a7f37;
|
||||
--color-accent-error: #d1242f;
|
||||
--color-accent-summary: #9a6700;
|
||||
--color-accent-prompt: #8250df;
|
||||
|
||||
--color-type-badge-bg: rgba(9, 105, 218, 0.12);
|
||||
--color-type-badge-text: #0969da;
|
||||
--color-summary-badge-bg: rgba(154, 103, 0, 0.12);
|
||||
--color-summary-badge-text: #9a6700;
|
||||
--color-prompt-badge-bg: rgba(130, 80, 223, 0.12);
|
||||
--color-prompt-badge-text: #8250df;
|
||||
|
||||
--color-skeleton-base: #d0d7de;
|
||||
--color-skeleton-highlight: #e8ecef;
|
||||
|
||||
--shadow-focus: 0 0 0 2px rgba(9, 105, 218, 0.3);
|
||||
}
|
||||
|
||||
/* Theme Variables - Dark Mode */
|
||||
[data-theme="dark"] {
|
||||
--color-bg-primary: #1e1e1e;
|
||||
--color-bg-secondary: #2d2d2d;
|
||||
--color-bg-tertiary: #252526;
|
||||
--color-bg-header: #252526;
|
||||
--color-bg-card: #2d2d2d;
|
||||
--color-bg-card-hover: #333333;
|
||||
--color-bg-input: #2d2d2d;
|
||||
--color-bg-button: #0969da;
|
||||
--color-bg-button-hover: #1177e6;
|
||||
--color-bg-button-active: #0860ca;
|
||||
--color-bg-summary: #3d2f00;
|
||||
--color-bg-prompt: #2d1b4e;
|
||||
--color-bg-stat: #2d2d2d;
|
||||
--color-bg-scrollbar-track: #1e1e1e;
|
||||
--color-bg-scrollbar-thumb: #424242;
|
||||
--color-bg-scrollbar-thumb-hover: #4e4e4e;
|
||||
|
||||
--color-border-primary: #404040;
|
||||
--color-border-secondary: #404040;
|
||||
--color-border-hover: #505050;
|
||||
--color-border-focus: #58a6ff;
|
||||
--color-border-summary: #9e6a03;
|
||||
--color-border-summary-hover: #ae7a13;
|
||||
--color-border-prompt: #6e40c9;
|
||||
--color-border-prompt-hover: #8e6cdb;
|
||||
|
||||
--color-text-primary: #cccccc;
|
||||
--color-text-secondary: #a0a0a0;
|
||||
--color-text-tertiary: #6e7681;
|
||||
--color-text-muted: #8b949e;
|
||||
--color-text-header: #e0e0e0;
|
||||
--color-text-title: #e0e0e0;
|
||||
--color-text-subtitle: #a0a0a0;
|
||||
--color-text-button: #ffffff;
|
||||
--color-text-summary: #f2cc60;
|
||||
--color-text-logo: #dadada;
|
||||
|
||||
--color-accent-primary: #58a6ff;
|
||||
--color-accent-focus: #58a6ff;
|
||||
--color-accent-success: #16c60c;
|
||||
--color-accent-error: #e74856;
|
||||
--color-accent-summary: #f2cc60;
|
||||
--color-accent-prompt: #8e6cdb;
|
||||
|
||||
--color-type-badge-bg: rgba(88, 166, 255, 0.125);
|
||||
--color-type-badge-text: #58a6ff;
|
||||
--color-summary-badge-bg: rgba(242, 204, 96, 0.125);
|
||||
--color-summary-badge-text: #f2cc60;
|
||||
--color-prompt-badge-bg: rgba(110, 64, 201, 0.125);
|
||||
--color-prompt-badge-text: #8e6cdb;
|
||||
|
||||
--color-skeleton-base: #404040;
|
||||
--color-skeleton-highlight: #505050;
|
||||
|
||||
--shadow-focus: 0 0 0 2px rgba(88, 166, 255, 0.2);
|
||||
}
|
||||
|
||||
/* System preference default */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not([data-theme]) {
|
||||
--color-bg-primary: #ffffff;
|
||||
--color-bg-secondary: #f6f8fa;
|
||||
--color-bg-tertiary: #f0f0f0;
|
||||
--color-bg-header: #f6f8fa;
|
||||
--color-bg-card: #ffffff;
|
||||
--color-bg-card-hover: #f6f8fa;
|
||||
--color-bg-input: #ffffff;
|
||||
--color-bg-button: #0969da;
|
||||
--color-bg-button-hover: #1177e6;
|
||||
--color-bg-button-active: #0860ca;
|
||||
--color-bg-summary: #fffbf0;
|
||||
--color-bg-prompt: #f6f3fb;
|
||||
--color-bg-stat: #f6f8fa;
|
||||
--color-bg-scrollbar-track: #ffffff;
|
||||
--color-bg-scrollbar-thumb: #d1d5da;
|
||||
--color-bg-scrollbar-thumb-hover: #b1b5ba;
|
||||
|
||||
--color-border-primary: #d0d7de;
|
||||
--color-border-secondary: #d8dee4;
|
||||
--color-border-hover: #0969da;
|
||||
--color-border-focus: #0969da;
|
||||
--color-border-summary: #d4a72c;
|
||||
--color-border-summary-hover: #c29d29;
|
||||
--color-border-prompt: #8250df;
|
||||
--color-border-prompt-hover: #6e40c9;
|
||||
|
||||
--color-text-primary: #24292f;
|
||||
--color-text-secondary: #57606a;
|
||||
--color-text-tertiary: #6e7781;
|
||||
--color-text-muted: #8b949e;
|
||||
--color-text-header: #24292f;
|
||||
--color-text-title: #24292f;
|
||||
--color-text-subtitle: #57606a;
|
||||
--color-text-button: #ffffff;
|
||||
--color-text-summary: #8a6116;
|
||||
--color-text-logo: #24292f;
|
||||
|
||||
--color-accent-primary: #0969da;
|
||||
--color-accent-focus: #0969da;
|
||||
--color-accent-success: #1a7f37;
|
||||
--color-accent-error: #d1242f;
|
||||
--color-accent-summary: #9a6700;
|
||||
--color-accent-prompt: #8250df;
|
||||
|
||||
--color-type-badge-bg: rgba(9, 105, 218, 0.12);
|
||||
--color-type-badge-text: #0969da;
|
||||
--color-summary-badge-bg: rgba(154, 103, 0, 0.12);
|
||||
--color-summary-badge-text: #9a6700;
|
||||
--color-prompt-badge-bg: rgba(130, 80, 223, 0.12);
|
||||
--color-prompt-badge-text: #8250df;
|
||||
|
||||
--color-skeleton-base: #d0d7de;
|
||||
--color-skeleton-highlight: #e8ecef;
|
||||
|
||||
--shadow-focus: 0 0 0 2px rgba(9, 105, 218, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme]) {
|
||||
--color-bg-primary: #1e1e1e;
|
||||
--color-bg-secondary: #2d2d2d;
|
||||
--color-bg-tertiary: #252526;
|
||||
--color-bg-header: #252526;
|
||||
--color-bg-card: #2d2d2d;
|
||||
--color-bg-card-hover: #333333;
|
||||
--color-bg-input: #2d2d2d;
|
||||
--color-bg-button: #0969da;
|
||||
--color-bg-button-hover: #1177e6;
|
||||
--color-bg-button-active: #0860ca;
|
||||
--color-bg-summary: #3d2f00;
|
||||
--color-bg-prompt: #2d1b4e;
|
||||
--color-bg-stat: #2d2d2d;
|
||||
--color-bg-scrollbar-track: #1e1e1e;
|
||||
--color-bg-scrollbar-thumb: #424242;
|
||||
--color-bg-scrollbar-thumb-hover: #4e4e4e;
|
||||
|
||||
--color-border-primary: #404040;
|
||||
--color-border-secondary: #404040;
|
||||
--color-border-hover: #505050;
|
||||
--color-border-focus: #58a6ff;
|
||||
--color-border-summary: #9e6a03;
|
||||
--color-border-summary-hover: #ae7a13;
|
||||
--color-border-prompt: #6e40c9;
|
||||
--color-border-prompt-hover: #8e6cdb;
|
||||
|
||||
--color-text-primary: #cccccc;
|
||||
--color-text-secondary: #a0a0a0;
|
||||
--color-text-tertiary: #6e7681;
|
||||
--color-text-muted: #8b949e;
|
||||
--color-text-header: #e0e0e0;
|
||||
--color-text-title: #e0e0e0;
|
||||
--color-text-subtitle: #a0a0a0;
|
||||
--color-text-button: #ffffff;
|
||||
--color-text-summary: #f2cc60;
|
||||
--color-text-logo: #dadada;
|
||||
|
||||
--color-accent-primary: #58a6ff;
|
||||
--color-accent-focus: #58a6ff;
|
||||
--color-accent-success: #16c60c;
|
||||
--color-accent-error: #e74856;
|
||||
--color-accent-summary: #f2cc60;
|
||||
--color-accent-prompt: #8e6cdb;
|
||||
|
||||
--color-type-badge-bg: rgba(88, 166, 255, 0.125);
|
||||
--color-type-badge-text: #58a6ff;
|
||||
--color-summary-badge-bg: rgba(242, 204, 96, 0.125);
|
||||
--color-summary-badge-text: #f2cc60;
|
||||
--color-prompt-badge-bg: rgba(110, 64, 201, 0.125);
|
||||
--color-prompt-badge-text: #8e6cdb;
|
||||
|
||||
--color-skeleton-base: #404040;
|
||||
--color-skeleton-highlight: #505050;
|
||||
|
||||
--shadow-focus: 0 0 0 2px rgba(88, 166, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -23,8 +263,8 @@
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
|
||||
background: #1e1e1e;
|
||||
color: #cccccc;
|
||||
background: var(--color-bg-primary);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -48,8 +288,8 @@
|
||||
top: 0;
|
||||
width: 400px;
|
||||
height: 100vh;
|
||||
background: #1e1e1e;
|
||||
border-left: 1px solid #404040;
|
||||
background: var(--color-bg-primary);
|
||||
border-left: 1px solid var(--color-border-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translate3d(100%, 0, 0);
|
||||
@@ -64,32 +304,32 @@
|
||||
|
||||
.header {
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid #404040;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #252526;
|
||||
background: var(--color-bg-header);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid #404040;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #252526;
|
||||
background: var(--color-bg-header);
|
||||
}
|
||||
|
||||
.sidebar-header h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #e0e0e0;
|
||||
color: var(--color-text-header);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #e0e0e0;
|
||||
color: var(--color-text-header);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
@@ -113,7 +353,7 @@
|
||||
font-weight: 100;
|
||||
font-size: 20px;
|
||||
letter-spacing: -0.03em;
|
||||
color: #dadada;
|
||||
color: var(--color-text-logo);
|
||||
}
|
||||
|
||||
.status {
|
||||
@@ -123,9 +363,10 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.settings-btn {
|
||||
.settings-btn,
|
||||
.theme-toggle-btn {
|
||||
background: transparent;
|
||||
border: 1px solid #404040;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
padding: 8px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
@@ -133,22 +374,24 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #cccccc;
|
||||
color: var(--color-text-primary);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.settings-btn:hover {
|
||||
background: #2d2d2d;
|
||||
border-color: #58a6ff;
|
||||
.settings-btn:hover,
|
||||
.theme-toggle-btn:hover {
|
||||
background: var(--color-bg-secondary);
|
||||
border-color: var(--color-border-focus);
|
||||
}
|
||||
|
||||
.settings-btn.active {
|
||||
background: #0969da;
|
||||
border-color: #0969da;
|
||||
color: white;
|
||||
background: var(--color-bg-button);
|
||||
border-color: var(--color-bg-button);
|
||||
color: var(--color-text-button);
|
||||
}
|
||||
|
||||
.settings-icon {
|
||||
.settings-icon,
|
||||
.theme-toggle-btn svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
@@ -157,12 +400,12 @@
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #e74856;
|
||||
background: var(--color-accent-error);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.status-dot.connected {
|
||||
background: #16c60c;
|
||||
background: var(--color-accent-success);
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@@ -181,9 +424,9 @@
|
||||
select,
|
||||
input,
|
||||
button {
|
||||
background: #2d2d2d;
|
||||
color: #cccccc;
|
||||
border: 1px solid #404040;
|
||||
background: var(--color-bg-input);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
padding: 6px 12px;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
@@ -193,30 +436,30 @@
|
||||
|
||||
select:hover,
|
||||
input:hover {
|
||||
border-color: #58a6ff;
|
||||
border-color: var(--color-border-focus);
|
||||
}
|
||||
|
||||
select:focus,
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: #58a6ff;
|
||||
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);
|
||||
border-color: var(--color-border-focus);
|
||||
box-shadow: var(--shadow-focus);
|
||||
}
|
||||
|
||||
button {
|
||||
background: #0969da;
|
||||
color: #ffffff;
|
||||
background: var(--color-bg-button);
|
||||
color: var(--color-text-button);
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background: #1177e6;
|
||||
background: var(--color-bg-button-hover);
|
||||
}
|
||||
|
||||
button:active:not(:disabled) {
|
||||
background: #0860ca;
|
||||
background: var(--color-bg-button-active);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
@@ -240,8 +483,8 @@
|
||||
.card {
|
||||
margin-bottom: 24px;
|
||||
padding: 20px 24px;
|
||||
background: #2d2d2d;
|
||||
border: 1px solid #404040;
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 8px;
|
||||
transition: all 0.15s ease;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
@@ -261,7 +504,7 @@
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: #505050;
|
||||
border-color: var(--color-border-hover);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -270,14 +513,14 @@
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
color: var(--color-text-muted);
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
.card-type {
|
||||
padding: 2px 8px;
|
||||
background: #58a6ff20;
|
||||
color: #58a6ff;
|
||||
background: var(--color-type-badge-bg);
|
||||
color: var(--color-type-badge-text);
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
@@ -288,7 +531,7 @@
|
||||
.card-title {
|
||||
font-size: 17px;
|
||||
margin-bottom: 8px;
|
||||
color: #e0e0e0;
|
||||
color: var(--color-text-title);
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
letter-spacing: -0.01em;
|
||||
@@ -296,46 +539,46 @@
|
||||
|
||||
.card-subtitle {
|
||||
font-size: 14px;
|
||||
color: #a0a0a0;
|
||||
color: var(--color-text-subtitle);
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
font-size: 12px;
|
||||
color: #6e7681;
|
||||
color: var(--color-text-tertiary);
|
||||
margin-top: 8px;
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
border-color: #9e6a03;
|
||||
background: #3d2f00;
|
||||
border-color: var(--color-border-summary);
|
||||
background: var(--color-bg-summary);
|
||||
}
|
||||
|
||||
.summary-card:hover {
|
||||
border-color: #ae7a13;
|
||||
border-color: var(--color-border-summary-hover);
|
||||
}
|
||||
|
||||
.summary-card .card-type {
|
||||
background: #f2cc6020;
|
||||
color: #f2cc60;
|
||||
background: var(--color-summary-badge-bg);
|
||||
color: var(--color-summary-badge-text);
|
||||
}
|
||||
|
||||
.summary-card .card-title {
|
||||
color: #f2cc60;
|
||||
color: var(--color-text-summary);
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
padding: 18px;
|
||||
border-bottom: 1px solid #404040;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.settings-section h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 14px;
|
||||
color: #e0e0e0;
|
||||
color: var(--color-text-header);
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
@@ -347,13 +590,13 @@
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
color: var(--color-text-muted);
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
.setting-description {
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -366,13 +609,13 @@
|
||||
|
||||
.stat {
|
||||
padding: 10px 12px;
|
||||
background: #2d2d2d;
|
||||
border: 1px solid #404040;
|
||||
background: var(--color-bg-stat);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #8b949e;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 4px;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
@@ -381,7 +624,7 @@
|
||||
|
||||
.stat-value {
|
||||
font-size: 18px;
|
||||
color: #e0e0e0;
|
||||
color: var(--color-text-header);
|
||||
font-weight: 600;
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
}
|
||||
@@ -396,42 +639,42 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1e1e1e;
|
||||
background: var(--color-bg-scrollbar-track);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #424242;
|
||||
background: var(--color-bg-scrollbar-thumb);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #4e4e4e;
|
||||
background: var(--color-bg-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
.save-status {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.prompt-card {
|
||||
border-color: #6e40c9;
|
||||
background: #2d1b4e;
|
||||
border-color: var(--color-border-prompt);
|
||||
background: var(--color-bg-prompt);
|
||||
}
|
||||
|
||||
.prompt-card:hover {
|
||||
border-color: #8e6cdb;
|
||||
border-color: var(--color-border-prompt-hover);
|
||||
}
|
||||
|
||||
.prompt-card .card-type {
|
||||
background: #6e40c920;
|
||||
color: #8e6cdb;
|
||||
background: var(--color-prompt-badge-bg);
|
||||
color: var(--color-prompt-badge-text);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
margin-top: 12px;
|
||||
line-height: 1.6;
|
||||
color: #cccccc;
|
||||
color: var(--color-text-primary);
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
@@ -440,7 +683,7 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #58a6ff;
|
||||
color: var(--color-accent-focus);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
margin-left: auto;
|
||||
@@ -449,8 +692,8 @@
|
||||
.spinner {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 2px solid #404040;
|
||||
border-top-color: #58a6ff;
|
||||
border: 2px solid var(--color-border-primary);
|
||||
border-top-color: var(--color-accent-focus);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
@@ -471,7 +714,7 @@
|
||||
|
||||
.skeleton-line {
|
||||
height: 16px;
|
||||
background: linear-gradient(90deg, #404040 25%, #505050 50%, #404040 75%);
|
||||
background: linear-gradient(90deg, var(--color-skeleton-base) 25%, var(--color-skeleton-highlight) 50%, var(--color-skeleton-base) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
border-radius: 4px;
|
||||
|
||||
+94
-48
@@ -164,60 +164,97 @@ function runNpmInstall() {
|
||||
log('🔨 Installing dependencies...', colors.bright);
|
||||
log('', colors.reset);
|
||||
|
||||
try {
|
||||
// Run npm install with error output visible
|
||||
execSync('npm install --prefer-offline --no-audit --no-fund', {
|
||||
cwd: PLUGIN_ROOT,
|
||||
stdio: 'inherit', // Show all output including errors
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
// Try normal install first, then retry with force if it fails
|
||||
const strategies = [
|
||||
{ command: 'npm install', label: 'normal' },
|
||||
{ command: 'npm install --force', label: 'with force flag' },
|
||||
];
|
||||
|
||||
// Verify better-sqlite3 was installed
|
||||
if (!existsSync(BETTER_SQLITE3_PATH)) {
|
||||
throw new Error('better-sqlite3 installation verification failed');
|
||||
let lastError = null;
|
||||
|
||||
for (const { command, label } of strategies) {
|
||||
try {
|
||||
log(`Attempting install ${label}...`, colors.dim);
|
||||
|
||||
// Run npm install silently
|
||||
execSync(command, {
|
||||
cwd: PLUGIN_ROOT,
|
||||
stdio: 'pipe', // Silent output unless error
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
// Verify better-sqlite3 was installed
|
||||
if (!existsSync(BETTER_SQLITE3_PATH)) {
|
||||
throw new Error('better-sqlite3 installation verification failed');
|
||||
}
|
||||
|
||||
const version = getPackageVersion();
|
||||
setInstalledVersion(version);
|
||||
|
||||
log('', colors.green);
|
||||
log('✅ Dependencies installed successfully!', colors.bright);
|
||||
log(` Version: ${version}`, colors.dim);
|
||||
log('', colors.reset);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
// Continue to next strategy
|
||||
}
|
||||
|
||||
const version = getPackageVersion();
|
||||
setInstalledVersion(version);
|
||||
|
||||
log('', colors.green);
|
||||
log('✅ Dependencies installed successfully!', colors.bright);
|
||||
log(` Version: ${version}`, colors.dim);
|
||||
log('', colors.reset);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
log('', colors.red);
|
||||
log('❌ Installation failed!', colors.bright);
|
||||
log('', colors.reset);
|
||||
|
||||
// Provide Windows-specific help
|
||||
if (isWindows && error.message && error.message.includes('better-sqlite3')) {
|
||||
log(getWindowsErrorHelp(error.message), colors.yellow);
|
||||
}
|
||||
|
||||
// Show generic error info
|
||||
if (error.stderr) {
|
||||
log('Error output:', colors.dim);
|
||||
log(error.stderr.toString(), colors.red);
|
||||
} else if (error.message) {
|
||||
log(error.message, colors.red);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// All strategies failed - show error
|
||||
log('', colors.red);
|
||||
log('❌ Installation failed after retrying!', colors.bright);
|
||||
log('', colors.reset);
|
||||
|
||||
// Provide Windows-specific help
|
||||
if (isWindows && lastError && lastError.message && lastError.message.includes('better-sqlite3')) {
|
||||
log(getWindowsErrorHelp(lastError.message), colors.yellow);
|
||||
}
|
||||
|
||||
// Show generic error info with troubleshooting steps
|
||||
if (lastError) {
|
||||
if (lastError.stderr) {
|
||||
log('Error output:', colors.dim);
|
||||
log(lastError.stderr.toString(), colors.red);
|
||||
} else if (lastError.message) {
|
||||
log(lastError.message, colors.red);
|
||||
}
|
||||
|
||||
log('', colors.yellow);
|
||||
log('📋 Troubleshooting Steps:', colors.bright);
|
||||
log('', colors.reset);
|
||||
log('1. Check your internet connection', colors.yellow);
|
||||
log('2. Try running: npm cache clean --force', colors.yellow);
|
||||
log('3. Try running: npm install (in plugin directory)', colors.yellow);
|
||||
log('4. Check npm version: npm --version (requires npm 7+)', colors.yellow);
|
||||
log('5. Try updating npm: npm install -g npm@latest', colors.yellow);
|
||||
log('', colors.reset);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we should fail when worker startup fails
|
||||
* Returns true if worker failed AND dependencies are missing
|
||||
*/
|
||||
function shouldFailOnWorkerStartup(workerStarted) {
|
||||
return !workerStarted && !existsSync(NODE_MODULES_PATH);
|
||||
}
|
||||
|
||||
function startWorker() {
|
||||
const ECOSYSTEM_CONFIG = join(PLUGIN_ROOT, 'ecosystem.config.cjs');
|
||||
const PM2_PATH = join(PLUGIN_ROOT, 'node_modules', '.bin', 'pm2');
|
||||
|
||||
log('🚀 Starting worker service...', colors.dim);
|
||||
|
||||
try {
|
||||
// Use pm2 start which works whether worker is running or not
|
||||
// Use the full path to PM2 to avoid PATH issues on Windows
|
||||
// PM2 will either start it or report it's already running (both are success cases)
|
||||
execSync(`pm2 start "${ECOSYSTEM_CONFIG}"`, {
|
||||
execSync(`"${PM2_PATH}" start "${ECOSYSTEM_CONFIG}"`, {
|
||||
cwd: PLUGIN_ROOT,
|
||||
stdio: 'pipe', // Capture output to avoid clutter
|
||||
encoding: 'utf-8',
|
||||
@@ -252,21 +289,30 @@ async function main() {
|
||||
|
||||
if (!installSuccess) {
|
||||
log('', colors.red);
|
||||
log('⚠️ Installation failed - worker startup may fail', colors.yellow);
|
||||
log('❌ Installation failed - cannot start worker without dependencies', colors.bright);
|
||||
log('', colors.reset);
|
||||
// Don't exit - still try to start worker with existing deps
|
||||
log('Please resolve the installation issues above and try again.', colors.yellow);
|
||||
log('', colors.reset);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Always start/ensure worker is running
|
||||
// This runs whether we installed deps or not
|
||||
startWorker();
|
||||
// Start/ensure worker is running (only after successful install or if deps already exist)
|
||||
const workerStarted = startWorker();
|
||||
|
||||
// Success - dependencies installed (if needed) and worker running
|
||||
if (shouldFailOnWorkerStartup(workerStarted)) {
|
||||
log('', colors.red);
|
||||
log('❌ Worker failed to start and dependencies are missing', colors.bright);
|
||||
log('', colors.reset);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Success - dependencies installed (if needed) and worker running (or already running)
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
log(`❌ Unexpected error: ${error.message}`, colors.red);
|
||||
log('', colors.reset);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface PostToolUseInput {
|
||||
cwd: string;
|
||||
tool_name: string;
|
||||
tool_input: any;
|
||||
tool_output: any;
|
||||
tool_response: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
throw new Error('saveHook requires input');
|
||||
}
|
||||
|
||||
const { session_id, tool_name, tool_input, tool_output } = input;
|
||||
const { session_id, tool_name, tool_input, tool_response } = input;
|
||||
|
||||
if (SKIP_TOOLS.has(tool_name)) {
|
||||
console.log(createHookResponse('PostToolUse', true));
|
||||
@@ -65,7 +65,7 @@ async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
body: JSON.stringify({
|
||||
tool_name,
|
||||
tool_input: tool_input !== undefined ? JSON.stringify(tool_input) : '{}',
|
||||
tool_output: tool_output !== undefined ? JSON.stringify(tool_output) : '{}',
|
||||
tool_response: tool_response !== undefined ? JSON.stringify(tool_response) : '{}',
|
||||
prompt_number: promptNumber
|
||||
}),
|
||||
signal: AbortSignal.timeout(2000)
|
||||
|
||||
@@ -68,7 +68,7 @@ interface ObservationMessage {
|
||||
type: 'observation';
|
||||
tool_name: string;
|
||||
tool_input: string;
|
||||
tool_output: string;
|
||||
tool_response: string;
|
||||
prompt_number: number;
|
||||
}
|
||||
|
||||
@@ -701,11 +701,11 @@ class WorkerService {
|
||||
|
||||
/**
|
||||
* POST /sessions/:sessionDbId/observations
|
||||
* Body: { tool_name, tool_input, tool_output, prompt_number }
|
||||
* Body: { tool_name, tool_input, tool_response, prompt_number }
|
||||
*/
|
||||
private handleObservation(req: Request, res: Response): void {
|
||||
const sessionDbId = parseInt(req.params.sessionDbId, 10);
|
||||
const { tool_name, tool_input, tool_output, prompt_number } = req.body;
|
||||
const { tool_name, tool_input, tool_response, prompt_number } = req.body;
|
||||
|
||||
let session = this.sessions.get(sessionDbId);
|
||||
if (!session) {
|
||||
@@ -751,7 +751,7 @@ class WorkerService {
|
||||
type: 'observation',
|
||||
tool_name,
|
||||
tool_input,
|
||||
tool_output,
|
||||
tool_response,
|
||||
prompt_number
|
||||
});
|
||||
|
||||
@@ -1015,7 +1015,7 @@ class WorkerService {
|
||||
id: 0,
|
||||
tool_name: message.tool_name,
|
||||
tool_input: message.tool_input,
|
||||
tool_output: message.tool_output,
|
||||
tool_response: message.tool_response,
|
||||
created_at_epoch: Date.now()
|
||||
});
|
||||
|
||||
|
||||
+313
-70
@@ -15,6 +15,246 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Theme Variables - Light Mode */
|
||||
:root,
|
||||
[data-theme="light"] {
|
||||
--color-bg-primary: #ffffff;
|
||||
--color-bg-secondary: #f6f8fa;
|
||||
--color-bg-tertiary: #f0f0f0;
|
||||
--color-bg-header: #f6f8fa;
|
||||
--color-bg-card: #ffffff;
|
||||
--color-bg-card-hover: #f6f8fa;
|
||||
--color-bg-input: #ffffff;
|
||||
--color-bg-button: #0969da;
|
||||
--color-bg-button-hover: #1177e6;
|
||||
--color-bg-button-active: #0860ca;
|
||||
--color-bg-summary: #fffbf0;
|
||||
--color-bg-prompt: #f6f3fb;
|
||||
--color-bg-stat: #f6f8fa;
|
||||
--color-bg-scrollbar-track: #ffffff;
|
||||
--color-bg-scrollbar-thumb: #d1d5da;
|
||||
--color-bg-scrollbar-thumb-hover: #b1b5ba;
|
||||
|
||||
--color-border-primary: #d0d7de;
|
||||
--color-border-secondary: #d8dee4;
|
||||
--color-border-hover: #0969da;
|
||||
--color-border-focus: #0969da;
|
||||
--color-border-summary: #d4a72c;
|
||||
--color-border-summary-hover: #c29d29;
|
||||
--color-border-prompt: #8250df;
|
||||
--color-border-prompt-hover: #6e40c9;
|
||||
|
||||
--color-text-primary: #24292f;
|
||||
--color-text-secondary: #57606a;
|
||||
--color-text-tertiary: #6e7781;
|
||||
--color-text-muted: #8b949e;
|
||||
--color-text-header: #24292f;
|
||||
--color-text-title: #24292f;
|
||||
--color-text-subtitle: #57606a;
|
||||
--color-text-button: #ffffff;
|
||||
--color-text-summary: #8a6116;
|
||||
--color-text-logo: #24292f;
|
||||
|
||||
--color-accent-primary: #0969da;
|
||||
--color-accent-focus: #0969da;
|
||||
--color-accent-success: #1a7f37;
|
||||
--color-accent-error: #d1242f;
|
||||
--color-accent-summary: #9a6700;
|
||||
--color-accent-prompt: #8250df;
|
||||
|
||||
--color-type-badge-bg: rgba(9, 105, 218, 0.12);
|
||||
--color-type-badge-text: #0969da;
|
||||
--color-summary-badge-bg: rgba(154, 103, 0, 0.12);
|
||||
--color-summary-badge-text: #9a6700;
|
||||
--color-prompt-badge-bg: rgba(130, 80, 223, 0.12);
|
||||
--color-prompt-badge-text: #8250df;
|
||||
|
||||
--color-skeleton-base: #d0d7de;
|
||||
--color-skeleton-highlight: #e8ecef;
|
||||
|
||||
--shadow-focus: 0 0 0 2px rgba(9, 105, 218, 0.3);
|
||||
}
|
||||
|
||||
/* Theme Variables - Dark Mode */
|
||||
[data-theme="dark"] {
|
||||
--color-bg-primary: #1e1e1e;
|
||||
--color-bg-secondary: #2d2d2d;
|
||||
--color-bg-tertiary: #252526;
|
||||
--color-bg-header: #252526;
|
||||
--color-bg-card: #2d2d2d;
|
||||
--color-bg-card-hover: #333333;
|
||||
--color-bg-input: #2d2d2d;
|
||||
--color-bg-button: #0969da;
|
||||
--color-bg-button-hover: #1177e6;
|
||||
--color-bg-button-active: #0860ca;
|
||||
--color-bg-summary: #3d2f00;
|
||||
--color-bg-prompt: #2d1b4e;
|
||||
--color-bg-stat: #2d2d2d;
|
||||
--color-bg-scrollbar-track: #1e1e1e;
|
||||
--color-bg-scrollbar-thumb: #424242;
|
||||
--color-bg-scrollbar-thumb-hover: #4e4e4e;
|
||||
|
||||
--color-border-primary: #404040;
|
||||
--color-border-secondary: #404040;
|
||||
--color-border-hover: #505050;
|
||||
--color-border-focus: #58a6ff;
|
||||
--color-border-summary: #9e6a03;
|
||||
--color-border-summary-hover: #ae7a13;
|
||||
--color-border-prompt: #6e40c9;
|
||||
--color-border-prompt-hover: #8e6cdb;
|
||||
|
||||
--color-text-primary: #cccccc;
|
||||
--color-text-secondary: #a0a0a0;
|
||||
--color-text-tertiary: #6e7681;
|
||||
--color-text-muted: #8b949e;
|
||||
--color-text-header: #e0e0e0;
|
||||
--color-text-title: #e0e0e0;
|
||||
--color-text-subtitle: #a0a0a0;
|
||||
--color-text-button: #ffffff;
|
||||
--color-text-summary: #f2cc60;
|
||||
--color-text-logo: #dadada;
|
||||
|
||||
--color-accent-primary: #58a6ff;
|
||||
--color-accent-focus: #58a6ff;
|
||||
--color-accent-success: #16c60c;
|
||||
--color-accent-error: #e74856;
|
||||
--color-accent-summary: #f2cc60;
|
||||
--color-accent-prompt: #8e6cdb;
|
||||
|
||||
--color-type-badge-bg: rgba(88, 166, 255, 0.125);
|
||||
--color-type-badge-text: #58a6ff;
|
||||
--color-summary-badge-bg: rgba(242, 204, 96, 0.125);
|
||||
--color-summary-badge-text: #f2cc60;
|
||||
--color-prompt-badge-bg: rgba(110, 64, 201, 0.125);
|
||||
--color-prompt-badge-text: #8e6cdb;
|
||||
|
||||
--color-skeleton-base: #404040;
|
||||
--color-skeleton-highlight: #505050;
|
||||
|
||||
--shadow-focus: 0 0 0 2px rgba(88, 166, 255, 0.2);
|
||||
}
|
||||
|
||||
/* System preference default */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not([data-theme]) {
|
||||
--color-bg-primary: #ffffff;
|
||||
--color-bg-secondary: #f6f8fa;
|
||||
--color-bg-tertiary: #f0f0f0;
|
||||
--color-bg-header: #f6f8fa;
|
||||
--color-bg-card: #ffffff;
|
||||
--color-bg-card-hover: #f6f8fa;
|
||||
--color-bg-input: #ffffff;
|
||||
--color-bg-button: #0969da;
|
||||
--color-bg-button-hover: #1177e6;
|
||||
--color-bg-button-active: #0860ca;
|
||||
--color-bg-summary: #fffbf0;
|
||||
--color-bg-prompt: #f6f3fb;
|
||||
--color-bg-stat: #f6f8fa;
|
||||
--color-bg-scrollbar-track: #ffffff;
|
||||
--color-bg-scrollbar-thumb: #d1d5da;
|
||||
--color-bg-scrollbar-thumb-hover: #b1b5ba;
|
||||
|
||||
--color-border-primary: #d0d7de;
|
||||
--color-border-secondary: #d8dee4;
|
||||
--color-border-hover: #0969da;
|
||||
--color-border-focus: #0969da;
|
||||
--color-border-summary: #d4a72c;
|
||||
--color-border-summary-hover: #c29d29;
|
||||
--color-border-prompt: #8250df;
|
||||
--color-border-prompt-hover: #6e40c9;
|
||||
|
||||
--color-text-primary: #24292f;
|
||||
--color-text-secondary: #57606a;
|
||||
--color-text-tertiary: #6e7781;
|
||||
--color-text-muted: #8b949e;
|
||||
--color-text-header: #24292f;
|
||||
--color-text-title: #24292f;
|
||||
--color-text-subtitle: #57606a;
|
||||
--color-text-button: #ffffff;
|
||||
--color-text-summary: #8a6116;
|
||||
--color-text-logo: #24292f;
|
||||
|
||||
--color-accent-primary: #0969da;
|
||||
--color-accent-focus: #0969da;
|
||||
--color-accent-success: #1a7f37;
|
||||
--color-accent-error: #d1242f;
|
||||
--color-accent-summary: #9a6700;
|
||||
--color-accent-prompt: #8250df;
|
||||
|
||||
--color-type-badge-bg: rgba(9, 105, 218, 0.12);
|
||||
--color-type-badge-text: #0969da;
|
||||
--color-summary-badge-bg: rgba(154, 103, 0, 0.12);
|
||||
--color-summary-badge-text: #9a6700;
|
||||
--color-prompt-badge-bg: rgba(130, 80, 223, 0.12);
|
||||
--color-prompt-badge-text: #8250df;
|
||||
|
||||
--color-skeleton-base: #d0d7de;
|
||||
--color-skeleton-highlight: #e8ecef;
|
||||
|
||||
--shadow-focus: 0 0 0 2px rgba(9, 105, 218, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme]) {
|
||||
--color-bg-primary: #1e1e1e;
|
||||
--color-bg-secondary: #2d2d2d;
|
||||
--color-bg-tertiary: #252526;
|
||||
--color-bg-header: #252526;
|
||||
--color-bg-card: #2d2d2d;
|
||||
--color-bg-card-hover: #333333;
|
||||
--color-bg-input: #2d2d2d;
|
||||
--color-bg-button: #0969da;
|
||||
--color-bg-button-hover: #1177e6;
|
||||
--color-bg-button-active: #0860ca;
|
||||
--color-bg-summary: #3d2f00;
|
||||
--color-bg-prompt: #2d1b4e;
|
||||
--color-bg-stat: #2d2d2d;
|
||||
--color-bg-scrollbar-track: #1e1e1e;
|
||||
--color-bg-scrollbar-thumb: #424242;
|
||||
--color-bg-scrollbar-thumb-hover: #4e4e4e;
|
||||
|
||||
--color-border-primary: #404040;
|
||||
--color-border-secondary: #404040;
|
||||
--color-border-hover: #505050;
|
||||
--color-border-focus: #58a6ff;
|
||||
--color-border-summary: #9e6a03;
|
||||
--color-border-summary-hover: #ae7a13;
|
||||
--color-border-prompt: #6e40c9;
|
||||
--color-border-prompt-hover: #8e6cdb;
|
||||
|
||||
--color-text-primary: #cccccc;
|
||||
--color-text-secondary: #a0a0a0;
|
||||
--color-text-tertiary: #6e7681;
|
||||
--color-text-muted: #8b949e;
|
||||
--color-text-header: #e0e0e0;
|
||||
--color-text-title: #e0e0e0;
|
||||
--color-text-subtitle: #a0a0a0;
|
||||
--color-text-button: #ffffff;
|
||||
--color-text-summary: #f2cc60;
|
||||
--color-text-logo: #dadada;
|
||||
|
||||
--color-accent-primary: #58a6ff;
|
||||
--color-accent-focus: #58a6ff;
|
||||
--color-accent-success: #16c60c;
|
||||
--color-accent-error: #e74856;
|
||||
--color-accent-summary: #f2cc60;
|
||||
--color-accent-prompt: #8e6cdb;
|
||||
|
||||
--color-type-badge-bg: rgba(88, 166, 255, 0.125);
|
||||
--color-type-badge-text: #58a6ff;
|
||||
--color-summary-badge-bg: rgba(242, 204, 96, 0.125);
|
||||
--color-summary-badge-text: #f2cc60;
|
||||
--color-prompt-badge-bg: rgba(110, 64, 201, 0.125);
|
||||
--color-prompt-badge-text: #8e6cdb;
|
||||
|
||||
--color-skeleton-base: #404040;
|
||||
--color-skeleton-highlight: #505050;
|
||||
|
||||
--shadow-focus: 0 0 0 2px rgba(88, 166, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -23,8 +263,8 @@
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
|
||||
background: #1e1e1e;
|
||||
color: #cccccc;
|
||||
background: var(--color-bg-primary);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -48,8 +288,8 @@
|
||||
top: 0;
|
||||
width: 400px;
|
||||
height: 100vh;
|
||||
background: #1e1e1e;
|
||||
border-left: 1px solid #404040;
|
||||
background: var(--color-bg-primary);
|
||||
border-left: 1px solid var(--color-border-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translate3d(100%, 0, 0);
|
||||
@@ -64,32 +304,32 @@
|
||||
|
||||
.header {
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid #404040;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #252526;
|
||||
background: var(--color-bg-header);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid #404040;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #252526;
|
||||
background: var(--color-bg-header);
|
||||
}
|
||||
|
||||
.sidebar-header h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #e0e0e0;
|
||||
color: var(--color-text-header);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #e0e0e0;
|
||||
color: var(--color-text-header);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
@@ -113,7 +353,7 @@
|
||||
font-weight: 100;
|
||||
font-size: 20px;
|
||||
letter-spacing: -0.03em;
|
||||
color: #dadada;
|
||||
color: var(--color-text-logo);
|
||||
}
|
||||
|
||||
.status {
|
||||
@@ -123,9 +363,10 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.settings-btn {
|
||||
.settings-btn,
|
||||
.theme-toggle-btn {
|
||||
background: transparent;
|
||||
border: 1px solid #404040;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
padding: 8px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
@@ -133,22 +374,24 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #cccccc;
|
||||
color: var(--color-text-primary);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.settings-btn:hover {
|
||||
background: #2d2d2d;
|
||||
border-color: #58a6ff;
|
||||
.settings-btn:hover,
|
||||
.theme-toggle-btn:hover {
|
||||
background: var(--color-bg-secondary);
|
||||
border-color: var(--color-border-focus);
|
||||
}
|
||||
|
||||
.settings-btn.active {
|
||||
background: #0969da;
|
||||
border-color: #0969da;
|
||||
color: white;
|
||||
background: var(--color-bg-button);
|
||||
border-color: var(--color-bg-button);
|
||||
color: var(--color-text-button);
|
||||
}
|
||||
|
||||
.settings-icon {
|
||||
.settings-icon,
|
||||
.theme-toggle-btn svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
@@ -157,12 +400,12 @@
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #e74856;
|
||||
background: var(--color-accent-error);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.status-dot.connected {
|
||||
background: #16c60c;
|
||||
background: var(--color-accent-success);
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@@ -181,9 +424,9 @@
|
||||
select,
|
||||
input,
|
||||
button {
|
||||
background: #2d2d2d;
|
||||
color: #cccccc;
|
||||
border: 1px solid #404040;
|
||||
background: var(--color-bg-input);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
padding: 6px 12px;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
@@ -193,30 +436,30 @@
|
||||
|
||||
select:hover,
|
||||
input:hover {
|
||||
border-color: #58a6ff;
|
||||
border-color: var(--color-border-focus);
|
||||
}
|
||||
|
||||
select:focus,
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: #58a6ff;
|
||||
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);
|
||||
border-color: var(--color-border-focus);
|
||||
box-shadow: var(--shadow-focus);
|
||||
}
|
||||
|
||||
button {
|
||||
background: #0969da;
|
||||
color: #ffffff;
|
||||
background: var(--color-bg-button);
|
||||
color: var(--color-text-button);
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background: #1177e6;
|
||||
background: var(--color-bg-button-hover);
|
||||
}
|
||||
|
||||
button:active:not(:disabled) {
|
||||
background: #0860ca;
|
||||
background: var(--color-bg-button-active);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
@@ -240,8 +483,8 @@
|
||||
.card {
|
||||
margin-bottom: 24px;
|
||||
padding: 20px 24px;
|
||||
background: #2d2d2d;
|
||||
border: 1px solid #404040;
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 8px;
|
||||
transition: all 0.15s ease;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
@@ -261,7 +504,7 @@
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: #505050;
|
||||
border-color: var(--color-border-hover);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -270,14 +513,14 @@
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
color: var(--color-text-muted);
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
.card-type {
|
||||
padding: 2px 8px;
|
||||
background: #58a6ff20;
|
||||
color: #58a6ff;
|
||||
background: var(--color-type-badge-bg);
|
||||
color: var(--color-type-badge-text);
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
@@ -288,7 +531,7 @@
|
||||
.card-title {
|
||||
font-size: 17px;
|
||||
margin-bottom: 8px;
|
||||
color: #e0e0e0;
|
||||
color: var(--color-text-title);
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
letter-spacing: -0.01em;
|
||||
@@ -296,46 +539,46 @@
|
||||
|
||||
.card-subtitle {
|
||||
font-size: 14px;
|
||||
color: #a0a0a0;
|
||||
color: var(--color-text-subtitle);
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
font-size: 12px;
|
||||
color: #6e7681;
|
||||
color: var(--color-text-tertiary);
|
||||
margin-top: 8px;
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
border-color: #9e6a03;
|
||||
background: #3d2f00;
|
||||
border-color: var(--color-border-summary);
|
||||
background: var(--color-bg-summary);
|
||||
}
|
||||
|
||||
.summary-card:hover {
|
||||
border-color: #ae7a13;
|
||||
border-color: var(--color-border-summary-hover);
|
||||
}
|
||||
|
||||
.summary-card .card-type {
|
||||
background: #f2cc6020;
|
||||
color: #f2cc60;
|
||||
background: var(--color-summary-badge-bg);
|
||||
color: var(--color-summary-badge-text);
|
||||
}
|
||||
|
||||
.summary-card .card-title {
|
||||
color: #f2cc60;
|
||||
color: var(--color-text-summary);
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
padding: 18px;
|
||||
border-bottom: 1px solid #404040;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.settings-section h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 14px;
|
||||
color: #e0e0e0;
|
||||
color: var(--color-text-header);
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
@@ -347,13 +590,13 @@
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
color: var(--color-text-muted);
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
.setting-description {
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -366,13 +609,13 @@
|
||||
|
||||
.stat {
|
||||
padding: 10px 12px;
|
||||
background: #2d2d2d;
|
||||
border: 1px solid #404040;
|
||||
background: var(--color-bg-stat);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #8b949e;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 4px;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
@@ -381,7 +624,7 @@
|
||||
|
||||
.stat-value {
|
||||
font-size: 18px;
|
||||
color: #e0e0e0;
|
||||
color: var(--color-text-header);
|
||||
font-weight: 600;
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
}
|
||||
@@ -396,42 +639,42 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1e1e1e;
|
||||
background: var(--color-bg-scrollbar-track);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #424242;
|
||||
background: var(--color-bg-scrollbar-thumb);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #4e4e4e;
|
||||
background: var(--color-bg-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
.save-status {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.prompt-card {
|
||||
border-color: #6e40c9;
|
||||
background: #2d1b4e;
|
||||
border-color: var(--color-border-prompt);
|
||||
background: var(--color-bg-prompt);
|
||||
}
|
||||
|
||||
.prompt-card:hover {
|
||||
border-color: #8e6cdb;
|
||||
border-color: var(--color-border-prompt-hover);
|
||||
}
|
||||
|
||||
.prompt-card .card-type {
|
||||
background: #6e40c920;
|
||||
color: #8e6cdb;
|
||||
background: var(--color-prompt-badge-bg);
|
||||
color: var(--color-prompt-badge-text);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
margin-top: 12px;
|
||||
line-height: 1.6;
|
||||
color: #cccccc;
|
||||
color: var(--color-text-primary);
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
@@ -440,7 +683,7 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #58a6ff;
|
||||
color: var(--color-accent-focus);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
margin-left: auto;
|
||||
@@ -449,8 +692,8 @@
|
||||
.spinner {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 2px solid #404040;
|
||||
border-top-color: #58a6ff;
|
||||
border: 2px solid var(--color-border-primary);
|
||||
border-top-color: var(--color-accent-focus);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
@@ -471,7 +714,7 @@
|
||||
|
||||
.skeleton-line {
|
||||
height: 16px;
|
||||
background: linear-gradient(90deg, #404040 25%, #505050 50%, #404040 75%);
|
||||
background: linear-gradient(90deg, var(--color-skeleton-base) 25%, var(--color-skeleton-highlight) 50%, var(--color-skeleton-base) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useSSE } from './hooks/useSSE';
|
||||
import { useSettings } from './hooks/useSettings';
|
||||
import { useStats } from './hooks/useStats';
|
||||
import { usePagination } from './hooks/usePagination';
|
||||
import { useTheme } from './hooks/useTheme';
|
||||
import { Observation, Summary, UserPrompt } from './types';
|
||||
import { mergeAndDeduplicateByProject } from './utils/data';
|
||||
|
||||
@@ -19,6 +20,7 @@ export function App() {
|
||||
const { observations, summaries, prompts, projects, processingSessions, isConnected } = useSSE();
|
||||
const { settings, saveSettings, isSaving, saveStatus } = useSettings();
|
||||
const { stats } = useStats();
|
||||
const { preference, resolvedTheme, setThemePreference } = useTheme();
|
||||
const pagination = usePagination(currentFilter);
|
||||
|
||||
// Reset paginated data when filter changes
|
||||
@@ -88,6 +90,8 @@ export function App() {
|
||||
onSettingsToggle={toggleSidebar}
|
||||
sidebarOpen={sidebarOpen}
|
||||
isProcessing={processingSessions.size > 0}
|
||||
themePreference={preference}
|
||||
onThemeChange={setThemePreference}
|
||||
/>
|
||||
<Feed
|
||||
observations={allObservations}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
import { ThemePreference } from '../hooks/useTheme';
|
||||
|
||||
interface HeaderProps {
|
||||
isConnected: boolean;
|
||||
@@ -8,6 +10,8 @@ interface HeaderProps {
|
||||
onSettingsToggle: () => void;
|
||||
sidebarOpen: boolean;
|
||||
isProcessing: boolean;
|
||||
themePreference: ThemePreference;
|
||||
onThemeChange: (theme: ThemePreference) => void;
|
||||
}
|
||||
|
||||
export function Header({
|
||||
@@ -17,7 +21,9 @@ export function Header({
|
||||
onFilterChange,
|
||||
onSettingsToggle,
|
||||
sidebarOpen,
|
||||
isProcessing
|
||||
isProcessing,
|
||||
themePreference,
|
||||
onThemeChange
|
||||
}: HeaderProps) {
|
||||
return (
|
||||
<div className="header">
|
||||
@@ -73,6 +79,10 @@ export function Header({
|
||||
<option key={project} value={project}>{project}</option>
|
||||
))}
|
||||
</select>
|
||||
<ThemeToggle
|
||||
preference={themePreference}
|
||||
onThemeChange={onThemeChange}
|
||||
/>
|
||||
<button
|
||||
className={`settings-btn ${sidebarOpen ? 'active' : ''}`}
|
||||
onClick={onSettingsToggle}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import { ThemePreference } from '../hooks/useTheme';
|
||||
|
||||
interface ThemeToggleProps {
|
||||
preference: ThemePreference;
|
||||
onThemeChange: (theme: ThemePreference) => void;
|
||||
}
|
||||
|
||||
export function ThemeToggle({ preference, onThemeChange }: ThemeToggleProps) {
|
||||
const cycleTheme = () => {
|
||||
const cycle: ThemePreference[] = ['system', 'light', 'dark'];
|
||||
const currentIndex = cycle.indexOf(preference);
|
||||
const nextIndex = (currentIndex + 1) % cycle.length;
|
||||
onThemeChange(cycle[nextIndex]);
|
||||
};
|
||||
|
||||
const getIcon = () => {
|
||||
switch (preference) {
|
||||
case 'light':
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="5"></circle>
|
||||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||
</svg>
|
||||
);
|
||||
case 'dark':
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
</svg>
|
||||
);
|
||||
case 'system':
|
||||
default:
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
|
||||
<line x1="8" y1="21" x2="16" y2="21"></line>
|
||||
<line x1="12" y1="17" x2="12" y2="21"></line>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getTitle = () => {
|
||||
switch (preference) {
|
||||
case 'light':
|
||||
return 'Theme: Light (click for Dark)';
|
||||
case 'dark':
|
||||
return 'Theme: Dark (click for System)';
|
||||
case 'system':
|
||||
default:
|
||||
return 'Theme: System (click for Light)';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className="theme-toggle-btn"
|
||||
onClick={cycleTheme}
|
||||
title={getTitle()}
|
||||
aria-label={getTitle()}
|
||||
>
|
||||
{getIcon()}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export type ThemePreference = 'system' | 'light' | 'dark';
|
||||
export type ResolvedTheme = 'light' | 'dark';
|
||||
|
||||
const STORAGE_KEY = 'claude-mem-theme';
|
||||
|
||||
function getSystemTheme(): ResolvedTheme {
|
||||
if (typeof window === 'undefined') return 'dark';
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function getStoredPreference(): ThemePreference {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored === 'system' || stored === 'light' || stored === 'dark') {
|
||||
return stored;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to read theme preference from localStorage:', e);
|
||||
}
|
||||
return 'system';
|
||||
}
|
||||
|
||||
function resolveTheme(preference: ThemePreference): ResolvedTheme {
|
||||
if (preference === 'system') {
|
||||
return getSystemTheme();
|
||||
}
|
||||
return preference;
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
const [preference, setPreference] = useState<ThemePreference>(getStoredPreference);
|
||||
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() =>
|
||||
resolveTheme(getStoredPreference())
|
||||
);
|
||||
|
||||
// Update resolved theme when preference changes
|
||||
useEffect(() => {
|
||||
const newResolvedTheme = resolveTheme(preference);
|
||||
setResolvedTheme(newResolvedTheme);
|
||||
document.documentElement.setAttribute('data-theme', newResolvedTheme);
|
||||
}, [preference]);
|
||||
|
||||
// Listen for system theme changes when preference is 'system'
|
||||
useEffect(() => {
|
||||
if (preference !== 'system') return;
|
||||
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
const newTheme = e.matches ? 'dark' : 'light';
|
||||
setResolvedTheme(newTheme);
|
||||
document.documentElement.setAttribute('data-theme', newTheme);
|
||||
};
|
||||
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
return () => mediaQuery.removeEventListener('change', handleChange);
|
||||
}, [preference]);
|
||||
|
||||
const setThemePreference = (newPreference: ThemePreference) => {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, newPreference);
|
||||
setPreference(newPreference);
|
||||
} catch (e) {
|
||||
console.warn('Failed to save theme preference to localStorage:', e);
|
||||
// Still update the theme even if localStorage fails
|
||||
setPreference(newPreference);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
preference,
|
||||
resolvedTheme,
|
||||
setThemePreference
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user