Skip to main content
Glama
localHandlers.ts6.01 kB
import * as fs from 'fs'; import * as path from 'path'; import { TreeSitterProcessor, CodeChunk, CodeSymbol } from '../local/treeSitterProcessor'; import { LocalProjectManager } from '../local/projectManager'; import { LocalSearch } from '../local/search'; import { ProjectIdentifier, ProjectInfo } from '../local/projectIdentifier'; import { logger } from '../utils/logger'; const projectManager = new LocalProjectManager(); const localSearch = new LocalSearch(); const projectIdentifier = ProjectIdentifier.getInstance(); export interface LocalProject { id: string; name: string; path: string; addedAt: Date; lastIndexed?: Date; stats?: { fileCount: number; chunkCount: number; symbolCount: number; }; } export async function handleAddLocalProject(args: any): Promise<LocalProject> { const { path: projectPath, name } = args; if (!projectPath) { throw new Error('Project path is required'); } // Validate path exists and is a directory if (!fs.existsSync(projectPath)) { throw new Error(`Path does not exist: ${projectPath}`); } const stat = fs.statSync(projectPath); if (!stat.isDirectory()) { throw new Error(`Path is not a directory: ${projectPath}`); } // Add project to manager const project = await projectManager.addProject(projectPath, name); // Start background indexing indexProjectInBackground(project); return project; } export async function handleListLocalProjects(): Promise<LocalProject[]> { return projectManager.listProjects(); } export async function handleSearchLocalContext(args: any): Promise<any[]> { const { query, project, k = 12 } = args; if (!query) { throw new Error('Query is required'); } if (!project) { throw new Error('Project is required'); } // Find project by name or path const projects = await projectManager.listProjects(); const targetProject = projects.find( p => p.name === project || p.path === project || p.id === project ); if (!targetProject) { throw new Error(`Project not found: ${project}`); } // Convert LocalProject to ProjectInfo for the new search system const projectInfo: ProjectInfo = { id: targetProject.id, name: targetProject.name, path: targetProject.path, type: 'local', workspaceRoot: targetProject.path, lastModified: targetProject.lastIndexed || new Date(), }; // Search local context const results = await localSearch.search(projectInfo, query, k); return results.map(result => ({ path: result.path, startLine: result.startLine, endLine: result.endLine, content: result.content, score: result.score, symbolName: result.symbolName, symbolType: result.symbolType, language: result.language, })); } async function indexProjectInBackground(project: LocalProject): Promise<void> { try { logger.info(`🔍 Starting background indexing for project: ${project.name}`); const processor = new TreeSitterProcessor(); const stats = { fileCount: 0, chunkCount: 0, symbolCount: 0 }; // Get all supported files in the project const files = await getAllFiles(project.path, ['.ts', '.tsx', '.js', '.jsx', '.py', '.md']); stats.fileCount = files.length; for (const filePath of files) { try { const content = fs.readFileSync(filePath, 'utf8'); const relativePath = path.relative(project.path, filePath); const language = getLanguageFromExtension(path.extname(filePath)); if (language) { const result = await processor.parseAndChunk(content, language, relativePath); // Store chunks and symbols locally // Convert LocalProject to ProjectInfo for the new search system const projectInfo: ProjectInfo = { id: project.id, name: project.name, path: project.path, type: 'local', workspaceRoot: project.path, lastModified: project.lastIndexed || new Date(), }; await localSearch.indexFile(projectInfo, { path: relativePath, content, language, chunks: result.chunks, symbols: result.symbols, }); stats.chunkCount += result.chunks.length; stats.symbolCount += result.symbols.length; } } catch (error) { logger.error(`Error indexing file ${filePath}:`, { error: error instanceof Error ? error.message : String(error), }); } } // Update project stats await projectManager.updateProjectStats(project.id, stats); logger.info( `✅ Completed indexing for ${project.name}: ${stats.fileCount} files, ${stats.chunkCount} chunks, ${stats.symbolCount} symbols` ); } catch (error) { logger.error(`❌ Error indexing project ${project.name}:`, { error: error instanceof Error ? error.message : String(error), }); } } async function getAllFiles(dir: string, extensions: string[]): Promise<string[]> { const files: string[] = []; const traverse = (currentDir: string) => { const entries = fs.readdirSync(currentDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(currentDir, entry.name); if (entry.isDirectory()) { // Skip common ignore directories if ( !['node_modules', '.git', 'dist', 'build', '.next', '__pycache__'].includes(entry.name) ) { traverse(fullPath); } } else if (entry.isFile()) { const ext = path.extname(entry.name); if (extensions.includes(ext)) { files.push(fullPath); } } } }; traverse(dir); return files; } function getLanguageFromExtension(ext: string): string | null { const languageMap: Record<string, string> = { '.ts': 'typescript', '.tsx': 'typescript', '.js': 'javascript', '.jsx': 'javascript', '.py': 'python', '.md': 'markdown', }; return languageMap[ext] || null; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sbarron/AmbianceMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server