UNPKG

agent-team-composer

Version:

Transform README files into GitHub project plans with AI-powered agent teams

248 lines 9.58 kB
import express from 'express'; import cors from 'cors'; import { promises as fs } from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { GitHubService } from '../services/github-service.js'; import { z } from 'zod'; import { asyncHandler } from '../utils/error-handler.js'; import { TelemetryService } from '../services/telemetry.js'; import { ResilienceManager } from '../services/resilience.js'; const app = express(); // Configure CORS for production const corsOptions = { origin: process.env.NODE_ENV === 'production' ? process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'] : true, credentials: true, maxAge: 86400 // 24 hours }; app.use(cors(corsOptions)); app.use(express.json()); // Health check endpoint app.get('/api/health', (req, res) => { const telemetry = TelemetryService.getInstance(); const resilience = ResilienceManager.getInstance(); res.json({ status: 'healthy', timestamp: new Date().toISOString(), version: process.env.npm_package_version || '1.0.0', metrics: { extractionSuccessRate: telemetry.getExtractionSuccessRate(), methodSuccessRates: telemetry.getMethodSuccessRates(), resilience: resilience.getStatus() } }); }); // Telemetry endpoint for monitoring app.get('/api/telemetry', (req, res) => { const telemetry = TelemetryService.getInstance(); res.json({ report: JSON.parse(telemetry.generateDailyReport()), commonErrors: telemetry.getMostCommonErrors() }); }); let projectData = null; let dataFilePath = null; // API endpoint to get project data app.get('/api/project', (req, res) => { if (!projectData) { return res.status(404).json({ error: 'No project data loaded' }); } res.json(projectData); }); // API endpoint to get README content from the current working directory app.get('/api/readme', asyncHandler(async (req, res) => { try { const readmePath = path.join(process.cwd(), 'README.md'); const content = await fs.readFile(readmePath, 'utf8'); res.json({ content }); } catch (error) { if (error.code === 'ENOENT') { return res.status(404).json({ error: 'README.md not found', details: 'No README.md file found in the current directory' }); } throw error; } })); // API endpoint to update project data app.put('/api/project', async (req, res) => { projectData = req.body; // Save to temp file if path is available if (dataFilePath) { await fs.writeFile(dataFilePath, JSON.stringify(projectData, null, 2)); } res.json({ success: true }); }); // API endpoint to generate project plan from parsed metadata app.post('/api/generate-plan', asyncHandler(async (req, res) => { const { metadata, readmeContent } = req.body; if (!metadata || !readmeContent) { return res.status(400).json({ error: 'Missing required fields', details: 'Both metadata and readmeContent are required' }); } try { // Import the LLM orchestrator const { generateProjectPlan } = await import('../services/llm-orchestrator.js'); // Generate the project plan const projectPlan = await generateProjectPlan(metadata, readmeContent); // Save to temp file projectData = projectPlan; if (dataFilePath) { await fs.writeFile(dataFilePath, JSON.stringify(projectData, null, 2)); } res.json(projectPlan); } catch (error) { console.error('Plan generation error:', error); res.status(500).json({ error: 'Failed to generate project plan', details: error instanceof Error ? error.message : 'Unknown error' }); } })); // Input validation schema const CreateIssuesSchema = z.object({ repository: z.string().regex(/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/), phases: z.array(z.any()) // Will be validated in GitHubService }); // API endpoint to check GitHub authentication app.get('/api/github/auth', asyncHandler(async (req, res) => { const githubService = new GitHubService(); const authStatus = await githubService.checkAuthentication(); res.json(authStatus); })); // API endpoint to get user repositories app.get('/api/github/repositories', asyncHandler(async (req, res) => { const githubService = new GitHubService(); // Check authentication first const authStatus = await githubService.checkAuthentication(); if (!authStatus.authenticated) { return res.status(401).json({ error: 'GitHub authentication required', details: 'Please set GITHUB_TOKEN environment variable' }); } // Fetch repositories const repos = await githubService.getUserRepositories(); res.json(repos); })); // API endpoint to get repository branches app.get('/api/github/branches', asyncHandler(async (req, res) => { const { repo } = req.query; if (!repo || typeof repo !== 'string') { return res.status(400).json({ error: 'Repository parameter required' }); } const githubService = new GitHubService(); const branches = await githubService.getRepositoryBranches(repo); res.json(branches); })); // API endpoint to create GitHub issues app.post('/api/github/create-issues', async (req, res) => { try { // Validate input const validatedInput = CreateIssuesSchema.parse(req.body); const { repository, phases } = validatedInput; console.log(`Creating issues for ${repository}...`); // Use GitHub API instead of CLI const githubService = new GitHubService(); // Check authentication first const authStatus = await githubService.checkAuthentication(); if (!authStatus.authenticated) { return res.status(401).json({ error: 'GitHub authentication required', details: 'Please set GITHUB_TOKEN environment variable' }); } // Create issues const results = await githubService.createIssuesFromPhases(repository, phases); res.json({ success: true, results }); } catch (error) { console.error('GitHub creation error:', error); if (error instanceof z.ZodError) { return res.status(400).json({ error: 'Invalid request data', details: error.errors }); } res.status(500).json({ error: 'Failed to create GitHub issues', details: error instanceof Error ? error.message : 'Unknown error' }); } }); export async function startServer(port, tempDataPath) { // Load project data dataFilePath = tempDataPath; const dataContent = await fs.readFile(tempDataPath, 'utf8'); projectData = JSON.parse(dataContent); // Determine the package root directory (where the dist files are located) // When running from npm/npx, __dirname will be inside the installed package const currentFileUrl = import.meta.url; const currentFilePath = fileURLToPath(currentFileUrl); const currentDir = path.dirname(currentFilePath); // Navigate up from dist/server to the package root const packageRoot = path.resolve(currentDir, '..', '..'); const distPath = path.join(packageRoot, 'dist'); // Always serve the built files from the package, not from cwd // This ensures we serve Agent Composer UI, not the user's local files try { // Check if dist directory exists in the package await fs.access(distPath); // Serve static files from the package's dist directory app.use(express.static(distPath)); // Fallback to index.html for client-side routing app.get('*', (req, res) => { res.sendFile(path.join(distPath, 'index.html')); }); } catch (error) { console.error('Could not find built files in package directory:', distPath); console.error('This may indicate the package was not built properly.'); throw new Error('Agent Composer UI files not found. Please reinstall the package.'); } return new Promise((resolve, reject) => { let currentPort = port; let retries = 0; const maxRetries = 10; const tryListen = () => { const server = app.listen(currentPort, () => { const url = `http://localhost:${currentPort}`; console.log(`Server running at ${url}`); resolve(url); // Graceful shutdown process.on('SIGINT', () => { console.log('\nShutting down server...'); server.close(); // Clean up temp file if (dataFilePath) { fs.unlink(dataFilePath).catch(() => { }); } process.exit(0); }); }); server.on('error', (err) => { if (err.code === 'EADDRINUSE' && retries < maxRetries) { retries++; currentPort++; console.log(`Port ${currentPort - 1} is in use, trying port ${currentPort}...`); server.close(); tryListen(); } else { reject(err); } }); }; tryListen(); }); } //# sourceMappingURL=index.js.map