Skip to main content
Glama
context.ts7.38 kB
import fs from 'fs/promises'; import path from 'path'; import { spawn } from 'child_process'; export interface ContextResult { file: string; line: number; radius: number; snippet: string; numbered_lines: string[]; valid_path: boolean; } export interface ProjectMapResult { scripts: string[]; scenes: string[]; data: string[]; } export class Context { private projectRoot: string; constructor(projectRoot: string) { this.projectRoot = projectRoot; } public async getContext(file: string, line: number, radius: number = 20): Promise<ContextResult> { const fullPath = path.join(this.projectRoot, file.replace('res://', '')); try { const content = await fs.readFile(fullPath, 'utf8'); const lines = content.split('\n'); const startLine = Math.max(0, line - radius - 1); const endLine = Math.min(lines.length, line + radius); const contextLines = lines.slice(startLine, endLine); const numberedLines = contextLines.map((lineContent, index) => { const lineNum = startLine + index + 1; const marker = lineNum === line ? '→' : ' '; return `${marker}${lineNum.toString().padStart(4)}: ${lineContent}`; }); return { file, line, radius, snippet: contextLines.join('\n'), numbered_lines: numberedLines, valid_path: true }; } catch (error) { return { file, line, radius, snippet: '', numbered_lines: [], valid_path: false }; } } public async readFile(filePath: string): Promise<{ content: string; valid: boolean }> { // Security check: restrict to project root const resolvedPath = path.resolve(this.projectRoot, filePath.replace('res://', '')); const rootPath = path.resolve(this.projectRoot); if (!resolvedPath.startsWith(rootPath)) { throw new Error('Access denied: path outside project root'); } try { const content = await fs.readFile(resolvedPath, 'utf8'); return { content, valid: true }; } catch (error) { return { content: '', valid: false }; } } public async writeFile(filePath: string, content: string): Promise<{ success: boolean; error?: string }> { // Security check: restrict to project root const resolvedPath = path.resolve(this.projectRoot, filePath.replace('res://', '')); const rootPath = path.resolve(this.projectRoot); if (!resolvedPath.startsWith(rootPath)) { throw new Error('Access denied: path outside project root'); } try { // Ensure directory exists const dir = path.dirname(resolvedPath); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(resolvedPath, content, 'utf8'); return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } public async listMovesets(): Promise<{ movesets: string[] }> { try { const movesetDir = path.join(this.projectRoot, 'data', 'movesets'); const files = await fs.readdir(movesetDir); const movesets = files .filter(file => file.endsWith('.json')) .map(file => path.basename(file, '.json')); return { movesets }; } catch (error) { return { movesets: [] }; } } public async readMoveset(name: string): Promise<{ moveset: any; valid: boolean }> { try { const movesetPath = path.join(this.projectRoot, 'data', 'movesets', `${name}.json`); const content = await fs.readFile(movesetPath, 'utf8'); const moveset = JSON.parse(content); return { moveset, valid: true }; } catch (error) { return { moveset: null, valid: false }; } } public async writeMoveset(name: string, movesetData: any): Promise<{ success: boolean; error?: string }> { try { const movesetDir = path.join(this.projectRoot, 'data', 'movesets'); await fs.mkdir(movesetDir, { recursive: true }); const movesetPath = path.join(movesetDir, `${name}.json`); const content = JSON.stringify(movesetData, null, 2); await fs.writeFile(movesetPath, content, 'utf8'); return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } public async getProjectMap(): Promise<ProjectMapResult> { try { const result: ProjectMapResult = { scripts: [], scenes: [], data: [] }; // Get scripts try { result.scripts = await this.getFilesRecursive( path.join(this.projectRoot, 'scripts'), '.gd' ); } catch (e) { // Scripts directory might not exist } // Get scenes try { result.scenes = await this.getFilesRecursive( path.join(this.projectRoot, 'scenes'), '.tscn' ); } catch (e) { // Scenes directory might not exist } // Get data files try { result.data = await this.getFilesRecursive( path.join(this.projectRoot, 'data'), '.json' ); } catch (e) { // Data directory might not exist } return result; } catch (error) { return { scripts: [], scenes: [], data: [] }; } } public async getGitStatus(): Promise<string> { try { return await this.executeGit(['status', '--porcelain']); } catch (error) { return ''; } } public async getRecentDiff(): Promise<string> { try { return await this.executeGit(['diff', 'HEAD~1..HEAD']); } catch (error) { return ''; } } private async getFilesRecursive(dir: string, extension: string): Promise<string[]> { const files: string[] = []; const processDir = async (currentDir: string, relativePath: string = '') => { try { const entries = await fs.readdir(currentDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(currentDir, entry.name); const relPath = path.join(relativePath, entry.name); if (entry.isDirectory()) { await processDir(fullPath, relPath); } else if (entry.isFile() && entry.name.endsWith(extension)) { files.push(relPath.replace(/\\/g, '/')); } } } catch (error) { // Ignore errors for individual directories } }; await processDir(dir); return files.sort(); } private async executeGit(args: string[]): Promise<string> { return new Promise((resolve, reject) => { const child = spawn('git', args, { cwd: this.projectRoot }); let stdout = ''; let stderr = ''; if (child.stdout) { child.stdout.on('data', (data) => { stdout += data.toString(); }); } if (child.stderr) { child.stderr.on('data', (data) => { stderr += data.toString(); }); } child.on('close', (code) => { if (code === 0) { resolve(stdout); } else { reject(new Error(stderr || `Git command failed with code ${code}`)); } }); child.on('error', reject); }); } }

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/Snack-JPG/Godot-Sentinel-MCP'

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