Skip to main content
Glama
git-manager.ts4.01 kB
import { simpleGit, SimpleGit, CleanOptions } from 'simple-git'; import { AuthManager } from './auth-manager.js'; import fs from 'fs-extra'; import path from 'path'; export class GitManager { private authManager: AuthManager; constructor() { this.authManager = new AuthManager(); } private getGitUrl(projectId: string, email?: string, token?: string): string { // If credentials are provided directly, use them if (email && token) { // Encode email to handle special characters like '@' const encodedEmail = encodeURIComponent(email); return `https://${encodedEmail}:${token}@git.overleaf.com/${projectId}`; } return `https://git.overleaf.com/${projectId}`; } async cloneProject(projectId: string, localPath: string, email?: string, token?: string) { // Ensure parent directory exists await fs.ensureDir(path.dirname(localPath)); let gitUrl = ''; // Try to get credentials if not provided if (!email || !token) { const creds = await this.authManager.getCredentials(); if (creds) { gitUrl = this.getGitUrl(projectId, creds.email, creds.token); } else { // Fallback to no-auth URL (will likely fail or prompt if interactive, but MCP is non-interactive) // Ideally we should error here if no auth is available for a private repo, // but let's try to construct it. gitUrl = this.getGitUrl(projectId); } } else { gitUrl = this.getGitUrl(projectId, email, token); } const git: SimpleGit = simpleGit(); try { await git.clone(gitUrl, localPath); console.error(`Successfully cloned project ${projectId} to ${localPath}`); return { success: true, message: `Cloned to ${localPath}` }; } catch (error: any) { console.error('Clone failed:', error); throw new Error(`Failed to clone project: ${error.message}`); } } async pullChanges(localPath: string) { if (!await fs.pathExists(localPath)) { throw new Error(`Directory ${localPath} does not exist`); } const git: SimpleGit = simpleGit(localPath); try { await git.pull(); return { success: true, message: 'Pulled latest changes' }; } catch (error: any) { throw new Error(`Failed to pull changes: ${error.message}`); } } async pushChanges(localPath: string, message: string) { if (!await fs.pathExists(localPath)) { throw new Error(`Directory ${localPath} does not exist`); } const git: SimpleGit = simpleGit(localPath); try { await git.add('.'); const status = await git.status(); if (status.staged.length === 0 && status.created.length === 0 && status.modified.length === 0 && status.deleted.length === 0 && status.renamed.length === 0) { return { success: true, message: 'No changes to commit' }; } await git.commit(message); await git.push(); return { success: true, message: 'Pushed changes to Overleaf' }; } catch (error: any) { throw new Error(`Failed to push changes: ${error.message}`); } } async getStatus(localPath: string) { if (!await fs.pathExists(localPath)) { throw new Error(`Directory ${localPath} does not exist`); } const git: SimpleGit = simpleGit(localPath); try { const status = await git.status(); const diff = await git.diff(['--stat']); return { success: true, status: status, diff: diff }; } catch (error: any) { throw new Error(`Failed to get status: ${error.message}`); } } }

Implementation Reference

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/juho127/overleafMCP'

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