Skip to main content
Glama
projectManager.ts4.69 kB
/** * @fileOverview: Local project state management with persistent storage in user home directory * @module: LocalProjectManager * @keyFunctions: * - addProject(): Add new project to local registry * - listProjects(): Retrieve all registered local projects * - getProject(): Find project by ID or path * - removeProject(): Remove project from local registry * @dependencies: * - fs: File system operations for persistent storage * - path: Path manipulation and resolution * - LocalProject: Project data structure from local handlers * @context: Manages local project registry with persistent storage in user's home directory, providing project lifecycle management for local development workflows */ import * as fs from 'fs'; import * as path from 'path'; import { logger } from '../utils/logger'; export interface LocalProject { id: string; name: string; path: string; addedAt: Date; lastIndexed?: Date; stats?: { fileCount: number; chunkCount: number; symbolCount: number; }; } export class LocalProjectManager { private projectsFile: string; private projects: Map<string, LocalProject>; constructor() { // Store projects in user's home directory const homeDir = process.env.HOME || process.env.USERPROFILE || process.cwd(); const ambianceDir = path.join(homeDir, '.ambiance'); // Create directory if it doesn't exist if (!fs.existsSync(ambianceDir)) { fs.mkdirSync(ambianceDir, { recursive: true }); } this.projectsFile = path.join(ambianceDir, 'local-projects.json'); this.projects = new Map(); this.loadProjects(); } private loadProjects(): void { try { if (fs.existsSync(this.projectsFile)) { const data = fs.readFileSync(this.projectsFile, 'utf8'); const projectsArray = JSON.parse(data); for (const project of projectsArray) { this.projects.set(project.id, { ...project, addedAt: new Date(project.addedAt), lastIndexed: project.lastIndexed ? new Date(project.lastIndexed) : undefined, }); } } } catch (error) { logger.error('Failed to load local projects', { error }); } } private saveProjects(): void { try { const projectsArray = Array.from(this.projects.values()); fs.writeFileSync(this.projectsFile, JSON.stringify(projectsArray, null, 2)); } catch (error) { logger.error('Failed to save local projects', { error }); } } async addProject(projectPath: string, name?: string): Promise<LocalProject> { const absolutePath = normalizePathForId(projectPath); const projectName = name || path.basename(absolutePath); const projectId = this.generateProjectId(absolutePath); // Check if project already exists const existing = Array.from(this.projects.values()).find(p => p.path === absolutePath); if (existing) { throw new Error(`Project already exists: ${existing.name}`); } const project: LocalProject = { id: projectId, name: projectName, path: absolutePath, addedAt: new Date(), }; this.projects.set(projectId, project); this.saveProjects(); return project; } async listProjects(): Promise<LocalProject[]> { return Array.from(this.projects.values()).sort( (a, b) => b.addedAt.getTime() - a.addedAt.getTime() ); } async getProject(idOrPath: string): Promise<LocalProject | undefined> { // Try by ID first const byId = this.projects.get(idOrPath); if (byId) return byId; // Try by path const absolutePath = path.resolve(idOrPath); return Array.from(this.projects.values()).find(p => p.path === absolutePath); } async removeProject(idOrPath: string): Promise<boolean> { const project = await this.getProject(idOrPath); if (!project) return false; this.projects.delete(project.id); this.saveProjects(); return true; } async updateProjectStats( projectId: string, stats: { fileCount: number; chunkCount: number; symbolCount: number } ): Promise<void> { const project = this.projects.get(projectId); if (project) { project.stats = stats; project.lastIndexed = new Date(); this.saveProjects(); } } private generateProjectId(projectPath: string): string { // Generate a deterministic ID based on the path const normalized = normalizePathForId(projectPath); const hash = require('crypto').createHash('md5').update(normalized).digest('hex'); return hash.substring(0, 12); } } // Reuse the canonical path normalizer from projectIdentifier import { normalizePathForId } from './projectIdentifier';

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