import { Project, ProjectRegistry, MCPError, ErrorCode } from '../models/types.js';
import { readFile, writeFile, mkdir, stat } from 'fs/promises';
import { homedir } from 'os';
import { join } from 'path';
export class ProjectManager {
private projectsPath: string;
private projectsDir: string;
constructor() {
this.projectsDir = join(homedir(), '.claude');
this.projectsPath = join(this.projectsDir, 'projects.json');
}
async loadProjects(): Promise<ProjectRegistry> {
try {
const data = await readFile(this.projectsPath, 'utf-8');
return JSON.parse(data) as ProjectRegistry;
} catch (error: any) {
if (error.code === 'ENOENT') {
return { projects: [] };
}
throw new MCPError(
ErrorCode.INTERNAL_ERROR,
`Failed to load projects: ${error.message}`
);
}
}
async saveProjects(registry: ProjectRegistry): Promise<void> {
try {
await mkdir(this.projectsDir, { recursive: true });
await writeFile(this.projectsPath, JSON.stringify(registry, null, 2), 'utf-8');
} catch (error: any) {
throw new MCPError(
ErrorCode.INTERNAL_ERROR,
`Failed to save projects: ${error.message}`
);
}
}
async addProject(project: Project): Promise<void> {
const registry = await this.loadProjects();
// Check for duplicates
if (registry.projects.some(p => p.id === project.id)) {
throw new MCPError(
ErrorCode.PROJECT_ALREADY_EXISTS,
`Project with ID '${project.id}' already exists`
);
}
// Check if path is already registered
const existingPath = registry.projects.find(p => p.rootPath === project.rootPath);
if (existingPath) {
throw new MCPError(
ErrorCode.PROJECT_ALREADY_EXISTS,
`Project already registered at path '${project.rootPath}' (ID: ${existingPath.id})`
);
}
registry.projects.push(project);
await this.saveProjects(registry);
}
async getProject(id: string): Promise<Project | null> {
const registry = await this.loadProjects();
return registry.projects.find(p => p.id === id) || null;
}
async getAllProjects(): Promise<Project[]> {
const registry = await this.loadProjects();
return registry.projects;
}
async validateProjectPath(path: string): Promise<boolean> {
try {
const stats = await stat(path);
return stats.isDirectory();
} catch (error) {
return false;
}
}
}