Files
the-nexus/modules/data/gitea.js
Alexander Whitestone 2c0f7f7a16
Some checks failed
CI / validate (pull_request) Failing after 12s
CI / auto-merge (pull_request) Has been skipped
feat: Phase 3 — wire panel modules (heatmap, agent-board, dual-brain, LoRA, sovereignty, earth)
- core/theme.js: export NEXUS with full NEXUS.theme.* properties used by all 6 panels
- core/ticker.js: add subscribe() convenience export so panels can self-register
- data/gitea.js: also write state.agentStatus, activeAgentCount, zoneIntensity, commits, commitHashes
- data/loaders.js: also write state.sovereignty
- data/bitcoin.js: also write state.blockHeight, lastBlockHeight, newBlockDetected, starPulseIntensity
- data/weather.js: also write state.weather
- app.js: import + init all 6 panel modules, bootstrap data polling, call globalTicker.tick()

Fixes #412
Refs #409

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 18:17:48 -04:00

202 lines
6.0 KiB
JavaScript

// modules/data/gitea.js — All Gitea API calls
// Writes to S: _activeAgentCount, _matrixCommitHashes (legacy)
// Writes to state: agentStatus, activeAgentCount, zoneIntensity, commits, commitHashes
import { S } from '../state.js';
import { state } from '../core/state.js';
// Zone intensity — agent name → author regex (mirrors panels/heatmap.js HEATMAP_ZONES)
const _ZONE_PATTERNS = [
{ name: 'Claude', pattern: /^claude$/i },
{ name: 'Timmy', pattern: /^timmy/i },
{ name: 'Kimi', pattern: /^kimi/i },
{ name: 'Perplexity', pattern: /^perplexity/i },
];
const _ZONE_MAX_WEIGHT = 8;
const GITEA_BASE = 'http://143.198.27.163:3000/api/v1';
const GITEA_TOKEN = 'dc0517a965226b7a0c5ffdd961b1ba26521ac592';
const GITEA_REPOS = ['Timmy_Foundation/the-nexus', 'Timmy_Foundation/hermes-agent'];
const AGENT_NAMES = ['Claude', 'Kimi', 'Perplexity', 'Groq', 'Grok', 'Ollama'];
const DAY_MS = 86400000;
const HOUR_MS = 3600000;
const CACHE_MS = 5 * 60 * 1000;
let _agentStatusCache = null;
let _agentStatusCacheTime = 0;
let _commitsCache = null;
let _commitsCacheTime = 0;
// --- Core fetchers ---
export async function fetchNexusCommits(limit = 50) {
const now = Date.now();
if (_commitsCache && (now - _commitsCacheTime < CACHE_MS)) return _commitsCache;
try {
const res = await fetch(
`${GITEA_BASE}/repos/Timmy_Foundation/the-nexus/commits?limit=${limit}`,
{ headers: { 'Authorization': `token ${GITEA_TOKEN}` } }
);
if (!res.ok) return [];
_commitsCache = await res.json();
_commitsCacheTime = now;
return _commitsCache;
} catch {
return [];
}
}
async function fetchRepoCommits(repo, limit = 30) {
try {
const res = await fetch(
`${GITEA_BASE}/repos/${repo}/commits?sha=main&limit=${limit}&token=${GITEA_TOKEN}`
);
if (!res.ok) return [];
return await res.json();
} catch {
return [];
}
}
async function fetchOpenPRs() {
try {
const res = await fetch(
`${GITEA_BASE}/repos/Timmy_Foundation/the-nexus/pulls?state=open&limit=50&token=${GITEA_TOKEN}`
);
if (res.ok) return await res.json();
} catch { /* ignore */ }
return [];
}
export async function fetchAgentStatus() {
const now = Date.now();
if (_agentStatusCache && (now - _agentStatusCacheTime < CACHE_MS)) return _agentStatusCache;
const allRepoCommits = await Promise.all(GITEA_REPOS.map(r => fetchRepoCommits(r)));
const openPRs = await fetchOpenPRs();
const agents = [];
for (const agentName of AGENT_NAMES) {
const nameLower = agentName.toLowerCase();
const allCommits = [];
for (const repoCommits of allRepoCommits) {
if (!Array.isArray(repoCommits)) continue;
const matching = repoCommits.filter(c =>
(c.commit?.author?.name || '').toLowerCase().includes(nameLower)
);
allCommits.push(...matching);
}
let status = 'dormant';
let lastSeen = null;
let currentWork = null;
if (allCommits.length > 0) {
allCommits.sort((a, b) =>
new Date(b.commit.author.date) - new Date(a.commit.author.date)
);
const latest = allCommits[0];
const commitTime = new Date(latest.commit.author.date).getTime();
lastSeen = latest.commit.author.date;
currentWork = latest.commit.message.split('\n')[0];
if (now - commitTime < HOUR_MS) status = 'working';
else if (now - commitTime < DAY_MS) status = 'idle';
else status = 'dormant';
}
const agentPRs = openPRs.filter(pr =>
(pr.user?.login || '').toLowerCase().includes(nameLower) ||
(pr.head?.label || '').toLowerCase().includes(nameLower)
);
agents.push({
name: nameLower,
status,
issue: currentWork,
prs_today: agentPRs.length,
local: nameLower === 'ollama',
});
}
_agentStatusCache = { agents };
_agentStatusCacheTime = now;
return _agentStatusCache;
}
// --- State updaters ---
export async function refreshCommitData() {
const commits = await fetchNexusCommits();
const hashes = commits.slice(0, 20).map(c => (c.sha || '').slice(0, 7)).filter(Boolean);
// Legacy write
S._matrixCommitHashes = hashes;
// Core state writes
state.commits = commits;
state.commitHashes = hashes;
// Compute per-zone intensity (24 h decay window)
const now = Date.now();
const rawWeights = {};
for (const c of commits) {
const author = c.commit?.author?.name || c.author?.login || '';
const age = now - new Date(c.commit?.author?.date || 0).getTime();
if (age > DAY_MS) continue;
const weight = 1 - age / DAY_MS;
for (const { name, pattern } of _ZONE_PATTERNS) {
if (pattern.test(author)) { rawWeights[name] = (rawWeights[name] || 0) + weight; break; }
}
}
state.zoneIntensity = Object.fromEntries(
_ZONE_PATTERNS.map(z => [z.name, Math.min((rawWeights[z.name] || 0) / _ZONE_MAX_WEIGHT, 1)])
);
return commits;
}
export async function refreshAgentData() {
try {
const data = await fetchAgentStatus();
const count = data.agents.filter(a => a.status === 'working').length;
S._activeAgentCount = count;
state.agentStatus = data;
state.activeAgentCount = count;
return data;
} catch {
const fallback = { agents: AGENT_NAMES.map(n => ({
name: n.toLowerCase(), status: 'unreachable', issue: null, prs_today: 0, local: false,
})) };
S._activeAgentCount = 0;
state.agentStatus = fallback;
state.activeAgentCount = 0;
return fallback;
}
}
export async function fetchMergedPRs(limit = 20) {
try {
const res = await fetch(
`${GITEA_BASE}/repos/Timmy_Foundation/the-nexus/pulls?state=closed&limit=${limit}`,
{ headers: { 'Authorization': `token ${GITEA_TOKEN}` } }
);
if (!res.ok) return [];
const data = await res.json();
return data
.filter(p => p.merged)
.map(p => ({
prNum: p.number,
title: p.title
.replace(/^\[[\w\s]+\]\s*/i, '')
.replace(/\s*\(#\d+\)\s*$/, ''),
}));
} catch {
return [];
}
}
export { GITEA_BASE, GITEA_TOKEN, GITEA_REPOS, AGENT_NAMES, CACHE_MS as AGENT_STATUS_CACHE_MS };