mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-05 23:03:55 +00:00
Initial commit
This commit is contained in:
+164
@@ -0,0 +1,164 @@
|
||||
// Package main provides the MCP server entry point for claude-mnemonic.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/lukaszraczylo/claude-mnemonic/internal/config"
|
||||
"github.com/lukaszraczylo/claude-mnemonic/internal/db/sqlite"
|
||||
"github.com/lukaszraczylo/claude-mnemonic/internal/mcp"
|
||||
"github.com/lukaszraczylo/claude-mnemonic/internal/search"
|
||||
"github.com/lukaszraczylo/claude-mnemonic/internal/vector/chroma"
|
||||
"github.com/lukaszraczylo/claude-mnemonic/internal/watcher"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Version is set at build time via ldflags.
|
||||
var Version = "dev"
|
||||
|
||||
func main() {
|
||||
// Parse flags
|
||||
project := flag.String("project", "", "Project name (required)")
|
||||
dataDir := flag.String("data-dir", "", "Data directory (default: ~/.claude-mnemonic)")
|
||||
debug := flag.Bool("debug", false, "Enable debug logging")
|
||||
flag.Parse()
|
||||
|
||||
// Setup logging - MCP uses stdout for communication, so log to stderr
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
if *debug {
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
}
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, NoColor: true})
|
||||
|
||||
if *project == "" {
|
||||
log.Fatal().Msg("--project is required")
|
||||
}
|
||||
|
||||
// Ensure data directory, vector-db, and settings exist
|
||||
if err := config.EnsureAll(); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to ensure data directories")
|
||||
}
|
||||
|
||||
// Load config
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to load config, using defaults")
|
||||
cfg = config.Default()
|
||||
}
|
||||
|
||||
// Override data directory if specified
|
||||
dbPath := cfg.DBPath
|
||||
vectorDBPath := cfg.VectorDBPath
|
||||
if *dataDir != "" {
|
||||
dbPath = *dataDir + "/claude-mnemonic.db"
|
||||
vectorDBPath = *dataDir + "/vector-db"
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Handle signals
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-sigCh
|
||||
log.Info().Msg("Shutting down MCP server")
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// Initialize SQLite store (migrations run automatically)
|
||||
storeCfg := sqlite.StoreConfig{
|
||||
Path: dbPath,
|
||||
MaxConns: cfg.MaxConns,
|
||||
WALMode: true,
|
||||
}
|
||||
store, err := sqlite.NewStore(storeCfg)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to initialize SQLite store")
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
// Initialize stores
|
||||
observationStore := sqlite.NewObservationStore(store)
|
||||
summaryStore := sqlite.NewSummaryStore(store)
|
||||
promptStore := sqlite.NewPromptStore(store)
|
||||
|
||||
// Initialize ChromaDB client (optional)
|
||||
var chromaClient *chroma.Client
|
||||
chromaCfg := chroma.Config{
|
||||
Project: *project,
|
||||
DataDir: vectorDBPath,
|
||||
PythonVer: cfg.PythonVersion,
|
||||
BatchSize: 100,
|
||||
}
|
||||
chromaClient, err = chroma.NewClient(chromaCfg)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("ChromaDB unavailable, vector search disabled")
|
||||
} else {
|
||||
if err := chromaClient.Connect(ctx); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to connect to ChromaDB, vector search disabled")
|
||||
chromaClient = nil
|
||||
} else {
|
||||
defer chromaClient.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize search manager
|
||||
searchMgr := search.NewManager(observationStore, summaryStore, promptStore, chromaClient)
|
||||
|
||||
// Start file watchers
|
||||
startWatchers(ctx, vectorDBPath, chromaClient)
|
||||
|
||||
// Create and run MCP server
|
||||
server := mcp.NewServer(searchMgr, Version)
|
||||
log.Info().Str("project", *project).Str("version", Version).Msg("Starting MCP server")
|
||||
|
||||
if err := server.Run(ctx); err != nil {
|
||||
log.Fatal().Err(err).Msg("MCP server error")
|
||||
}
|
||||
}
|
||||
|
||||
// startWatchers initializes file watchers for vector DB and config.
|
||||
func startWatchers(ctx context.Context, vectorDBPath string, chromaClient *chroma.Client) {
|
||||
// Watch vector DB directory for deletion
|
||||
if chromaClient != nil {
|
||||
vectorWatcher, err := watcher.New(vectorDBPath, func() {
|
||||
log.Warn().Str("path", vectorDBPath).Msg("Vector database deleted, reconnecting ChromaDB...")
|
||||
if err := chromaClient.Reconnect(ctx); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to reconnect ChromaDB after deletion")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to create vector DB watcher")
|
||||
} else {
|
||||
if err := vectorWatcher.Start(); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to start vector DB watcher")
|
||||
} else {
|
||||
log.Info().Str("path", vectorDBPath).Msg("Vector DB file watcher started")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Watch config file for changes (triggers process exit for restart)
|
||||
configPath := config.SettingsPath()
|
||||
configWatcher, err := watcher.New(configPath, func() {
|
||||
log.Warn().Str("path", configPath).Msg("Config file changed, exiting for restart...")
|
||||
time.Sleep(100 * time.Millisecond) // Give logs time to flush
|
||||
os.Exit(0)
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to create config watcher")
|
||||
} else {
|
||||
if err := configWatcher.Start(); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to start config watcher")
|
||||
} else {
|
||||
log.Info().Str("path", configPath).Msg("Config file watcher started")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user