/** * Gitea Routes Module * Adds Gitea integration routes to Express app */ const GiteaIntegration = require('./gitea-integration.js'); function setupGiteaRoutes(app) { // Initialize Gitea client const giteaConfig = { baseUrl: process.env.GITEA_URL || 'https://gitea.tophermayor.com', token: process.env.GITEA_TOKEN, owner: 'TopherMayor', cacheTimeout: 30000 }; const gitea = new GiteaIntegration(giteaConfig); // Gitea API routes app.get('/api/gitea/swarm', async (req, res) => { try { const summary = await gitea.getSwarmSummary(); res.json(summary); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/gitea/reviews', async (req, res) => { try { const reviews = await gitea.getPendingReviews(); res.json(reviews); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/gitea/activity', async (req, res) => { try { const activity = await gitea.getRecentActivity(); res.json(activity); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/gitea/user', async (req, res) => { try { const user = await gitea.getUser(); res.json(user); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/gitea/repos/:repo', async (req, res) => { try { const repo = await gitea.getRepo(req.params.repo); res.json(repo); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/gitea/repos/:repo/pulls', async (req, res) => { try { const state = req.query.state || 'open'; const prs = await gitea.getPullRequests(req.params.repo, state); res.json(prs); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/gitea/repos/:repo/issues', async (req, res) => { try { const state = req.query.state || 'open'; const issues = await gitea.getIssues(req.params.repo, state); res.json(issues); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/gitea/repos/:repo/commits', async (req, res) => { try { const branch = req.query.branch || 'main'; const limit = parseInt(req.query.limit) || 10; const commits = await gitea.getCommits(req.params.repo, branch, limit); res.json(commits); } catch (error) { res.status(500).json({ error: error.message }); } }); app.get('/api/gitea/repos/:repo/branches', async (req, res) => { try { const branches = await gitea.getBranches(req.params.repo); res.json(branches); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/gitea/cache/clear', (req, res) => { gitea.clearCache(); res.json({ success: true, message: 'Cache cleared' }); }); // Repository health check app.get('/api/gitea/health', async (req, res) => { try { const repos = await gitea.getRepos(); const health = []; for (const repo of repos) { const updated = new Date(repo.updated_at); const daysSinceUpdate = Math.floor((Date.now() - updated.getTime()) / (1000 * 60 * 60 * 24)); let status = 'healthy'; if (daysSinceUpdate > 30) status = 'stale'; else if (daysSinceUpdate > 7) status = 'inactive'; let openPRs = 0; try { const prs = await gitea.getPullRequests(repo.name, 'open'); openPRs = prs.length; } catch {} health.push({ name: repo.name, status, daysSinceUpdate, updated_at: repo.updated_at, openPRs, html_url: repo.html_url }); } res.json({ repos: health, summary: { total: health.length, healthy: health.filter(r => r.status === 'healthy').length, inactive: health.filter(r => r.status === 'inactive').length, stale: health.filter(r => r.status === 'stale').length } }); } catch (error) { res.status(500).json({ error: error.message }); } }); // GitOps deployment status app.get('/api/gitops/deployments', async (req, res) => { try { const repos = await gitea.getRepos(); const deployments = []; for (const repo of repos) { try { // Get latest release if exists const releases = await gitea.request(`/api/v1/repos/${giteaConfig.owner}/${repo.name}/releases?limit=1`); const latestRelease = releases[0]; deployments.push({ repo: repo.name, full_name: repo.full_name, updated_at: repo.updated_at, latest_release: latestRelease ? { tag: latestRelease.tag_name, created: latestRelease.created_at, url: latestRelease.html_url } : null, html_url: repo.html_url }); } catch { deployments.push({ repo: repo.name, full_name: repo.full_name, updated_at: repo.updated_at, latest_release: null, html_url: repo.html_url }); } } res.json({ deployments, lastUpdated: new Date().toISOString() }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Get deployment status for a specific repo app.get('/api/gitops/deployments/:repo', async (req, res) => { const { repo } = req.params; try { const releases = await gitea.request(`/api/v1/repos/${giteaConfig.owner}/${repo}/releases?limit=5`); const commits = await gitea.request(`/api/v1/repos/${giteaConfig.owner}/${repo}/commits?limit=5`); res.json({ repo, releases: releases.map(r => ({ tag: r.tag_name, name: r.name, created: r.created_at, url: r.html_url })), commits: commits.map(c => ({ sha: c.sha?.substring(0, 7), message: c.commit?.message?.split('\n')[0], author: c.commit?.author?.name, date: c.commit?.author?.date })) }); } catch (error) { res.status(500).json({ error: error.message }); } }); console.log('āœ… Gitea integration loaded'); } module.exports = { setupGiteaRoutes }; // Weekly swarm status report app.get('/api/swarm/weekly-report', async (req, res) => { try { const repos = await gitea.getRepos(); const tasks = await fetch(`${process.env.TASKBOARD_URL || 'http://localhost:8395'}/api/tasks`).then(r => r.json()); const agents = await fetch(`${process.env.TASKBOARD_URL || 'http://localhost:8395'}/api/agents`).then(r => r.json()); // Get usage data const usageRes = await fetch(`${process.env.TASKBOARD_URL || 'http://localhost:8395'}/api/usage/real`); const usage = await usageRes.json(); // Generate report const report = { generated: new Date().toISOString(), weekOf: new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }), repositories: { total: repos.length, healthy: repos.filter(r => { const days = Math.floor((Date.now() - new Date(r.updated_at).getTime()) / 86400000); return days <= 7; }).length, stale: repos.filter(r => { const days = Math.floor((Date.now() - new Date(r.updated_at).getTime()) / 86400000); return days > 30; }).length }, tasks: { total: tasks.length, done: tasks.filter(t => t.status === 'Done').length, inProgress: tasks.filter(t => t.status === 'In Progress').length, backlog: tasks.filter(t => t.status === 'Backlog').length, byPriority: { Critical: tasks.filter(t => t.priority === 'Critical').length, High: tasks.filter(t => t.priority === 'High').length, Medium: tasks.filter(t => t.priority === 'Medium').length, Low: tasks.filter(t => t.priority === 'Low').length } }, agents: { total: agents.length, byStatus: { active: agents.filter(a => a.status === 'active').length, idle: agents.filter(a => a.status === 'idle').length, offline: agents.filter(a => a.status === 'offline').length } }, usage: { totalTokens: usage.totals?.total || 0, totalCost: usage.totals?.cost || 0, topAgents: Object.entries(usage.agents || {}) .map(([k, v]) => ({ agent: k, tokens: v.total })) .sort((a, b) => b.tokens - a.tokens) .slice(0, 5) }, summary: `Swarm weekly report for ${new Date().toLocaleDateString('en-US', { weekday: 'long' })}.\n\nšŸ“Š **Repositories:** ${repos.length} total (${repos.filter(r => r.updated_at && new Date(r.updated_at) > new Date(Date.now() - 7 * 86400000)).length} active this week)\n\nšŸ“‹ **Tasks:** ${tasks.length} total (${tasks.filter(t => t.status === 'Done').length} done, ${tasks.filter(t => t.status === 'In Progress').length} in progress)\n\nšŸ¤– **Agents:** ${agents.length} total\n\nšŸ’° **Usage:** ${new Intl.NumberFormat('en-US').format(usage.totals?.total || 0)} tokens this week` }; // Generate markdown const markdown = generateWeeklyReportMarkdown(report); // Save to wiki const wikiPath = path.join(process.env.WIKI_DIR || '/app/wiki', `weekly-report-${new Date().toISOString().slice(0, 10)}.md`); fs.writeFileSync(wikiPath, JSON.stringify({ title: `Weekly Report - ${new Date().toLocaleDateString()}`, content: markdown, tags: ['report', 'weekly', 'swarm', 'auto-generated'], created: new Date().toISOString(), modified: new Date().toISOString() }, 'utf8'); res.json({ success: true, report, wikiEntry: { title: `Weekly Report - ${new Date().toLocaleDateString()}`, filename: path.basename(wikiPath) } }); } catch (error) { res.status(500).json({ error: error.message }); } }); function generateWeeklyReportMarkdown(report) { return `# Weekly Swarm Status Report **Generated:** ${new Date(report.generated).toLocaleString()} ## šŸ“Š Repositories | Metric | Count | |--------|-------| | Total | ${report.repositories.total} | | Healthy (≤7d) | ${report.repositories.healthy} | | Stale (>30d) | ${report.repositories.stale} | ## šŸ“‹ Tasks | Priority | Count | |----------|-------| | Critical | ${tasks.byPriority.Critical} | | High | ${tasks.byPriority.High} | | Medium | ${tasks.byPriority.Medium} | | Low | ${tasks.byPriority.Low} | **Summary:** - Total: ${report.tasks.total} - Done: ${report.tasks.done} - In Progress: ${report.tasks.inProgress} - Backlog: ${report.tasks.backlog} ## šŸ¤– Agents **Total Agents:** ${report.agents.total} | Status | Count | |--------|-------| | Active | ${report.agents.byStatus.active} | | Idle | ${report.agents.byStatus.idle} | | Offline | ${report.agents.byStatus.offline} | ## šŸ’° Usage **Total Tokens:** ${new Intl.NumberFormat('en-US').format(report.usage.totalTokens)} **Total Cost:** $${report.usage.totalCost.toFixed(2)} ### Top 5 Agents by Usage | Rank | Agent | Tokens | |------|-------|--------| ${report.usage.topAgents.map((a, i) => `| ${i + 1} | \`${a.agent}\` | ${new Intl.NumberFormat('en-US').format(a.tokens)} |`).join('\n')} --- *Report auto-generated by OpenClaw Swarm Orchestrator* `; }