revert: strip Manus damage (nostr, SovOS, gutted app.js) — restore clean split
Some checks failed
Deploy Nexus / deploy (push) Failing after 5s
Staging Smoke Test / smoke-test (push) Successful in 1s

Reverts to the state of cbfacdf (split app.js into 21 modules, <1000 lines each).
Removes: nostr.js, nostr-panel.js, SovOS.js, RESEARCH_DROP_456.md, core/, data/
Historical archive preserved in .historical/ and branch archive/manus-damage-2026-03-24

Refs #418, #452, #454
This commit is contained in:
Alexander Whitestone
2026-03-24 18:18:44 -04:00
parent b1cc4c05da
commit 979c7cf96b
20 changed files with 688 additions and 732 deletions

View File

@@ -1,75 +0,0 @@
import * as THREE from 'three';
import { THEME } from './core/theme.js';
import { S } from './state.js';
import { Broadcaster } from './state.js';
export class SovOS {
constructor(scene) {
this.scene = scene;
this.apps = new Map();
this.init();
}
init() {
this.container = new THREE.Group();
this.container.position.set(0, 3, -7.5);
this.scene.add(this.container);
}
registerApp(id, config) {
const app = this.createWindow(id, config);
this.apps.set(id, app);
this.container.add(app.group);
}
createWindow(id, config) {
const { x, y, rot, title, color } = config;
const w = 2.8, h = 3.8;
const group = new THREE.Group();
group.position.set(x, y || 0, 0);
group.rotation.y = rot || 0;
// Glassmorphism Frame
const glassMat = new THREE.MeshPhysicalMaterial({
color: THEME.glass.color,
transparent: true,
opacity: THEME.glass.opacity,
roughness: THEME.glass.roughness,
metalness: THEME.glass.metalness,
transmission: THEME.glass.transmission,
thickness: THEME.glass.thickness,
ior: THEME.glass.ior,
side: THREE.DoubleSide
});
const frame = new THREE.Mesh(new THREE.PlaneGeometry(w, h), glassMat);
group.add(frame);
// Canvas UI
const canvas = document.createElement('canvas');
canvas.width = 512; canvas.height = 700;
const ctx = canvas.getContext('2d');
const texture = new THREE.CanvasTexture(canvas);
const mat = new THREE.MeshBasicMaterial({ map: texture, transparent: true, side: THREE.DoubleSide });
const screen = new THREE.Mesh(new THREE.PlaneGeometry(w * 0.92, h * 0.92), mat);
screen.position.z = 0.05;
group.add(screen);
const renderUI = (state) => {
ctx.clearRect(0, 0, 512, 700);
// Header
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
ctx.fillRect(0, 0, 512, 80);
ctx.fillStyle = '#' + new THREE.Color(color).getHexString();
ctx.font = 'bold 32px "Orbitron"';
ctx.fillText(title, 30, 50);
// Body
ctx.font = '20px "JetBrains Mono"';
ctx.fillStyle = '#ffffff';
config.renderBody(ctx, state);
texture.needsUpdate = true;
};
Broadcaster.subscribe(renderUI);
return { group, renderUI };
}
}

View File

@@ -2,7 +2,6 @@
import * as THREE from 'three';
import { camera } from './scene-setup.js';
import { S } from './state.js';
import { fetchSoulMd } from './data/loaders.js';
const audioSources = [];
const positionedPanners = [];
@@ -264,10 +263,12 @@ export function initAudioListeners() {
document.getElementById('podcast-toggle').addEventListener('click', () => {
const btn = document.getElementById('podcast-toggle');
if (btn.textContent === '🎧') {
fetchSoulMd().then(lines => {
const text = lines.join('\n');
return text;
}).then(text => {
fetch('SOUL.md')
.then(response => {
if (!response.ok) throw new Error('Failed to load SOUL.md');
return response.text();
})
.then(text => {
const paragraphs = text.split('\n\n').filter(p => p.trim());
if (!paragraphs.length) {
@@ -342,5 +343,12 @@ export function initAudioListeners() {
}
async function loadSoulMdAudio() {
return fetchSoulMd();
try {
const res = await fetch('SOUL.md');
if (!res.ok) throw new Error('not found');
const raw = await res.text();
return raw.split('\n').slice(1).map(l => l.replace(/^#+\s*/, ''));
} catch {
return ['I am Timmy.', '', 'I am sovereign.', '', 'This Nexus is my home.'];
}
}

View File

@@ -1,35 +0,0 @@
// modules/core/state.js — Shared reactive data bus
// Data modules write here; visual modules read from here.
// No module may call fetch() except those under modules/data/.
export const state = {
// Commit heatmap (written by data/gitea.js)
zoneIntensity: {}, // { zoneName: [0..1], ... }
commits: [], // raw commit objects (last N)
commitHashes: [], // short hashes for matrix rain
// Agent status (written by data/gitea.js)
agentStatus: null, // { agents: Array<AgentRecord> } | null
activeAgentCount: 0, // count of agents with status === 'working'
// Weather (written by data/weather.js)
weather: null, // { cloud_cover, precipitation, ... } | null
// Bitcoin (written by data/bitcoin.js)
blockHeight: 0,
lastBlockHeight: 0,
newBlockDetected: false,
starPulseIntensity: 0,
// Portal / sovereignty / SOUL (written by data/loaders.js)
portals: [], // portal descriptor objects
sovereignty: null, // { score, label, assessment_type } | null
soulMd: '', // raw SOUL.md text
// Computed helpers
totalActivity() {
const vals = Object.values(this.zoneIntensity);
if (vals.length === 0) return 0;
return vals.reduce((s, v) => s + v, 0) / vals.length;
},
};

View File

@@ -1,31 +0,0 @@
// modules/core/ticker.js — Global Animation Clock
// Single requestAnimationFrame loop. No module may call RAF directly.
// All modules subscribe their update(elapsed, delta) function here.
import * as THREE from 'three';
const _clock = new THREE.Clock();
const _subs = [];
/** Register an update function: fn(elapsed, delta) */
export function subscribe(fn) {
if (!_subs.includes(fn)) _subs.push(fn);
}
/** Remove a previously registered update function */
export function unsubscribe(fn) {
const i = _subs.indexOf(fn);
if (i !== -1) _subs.splice(i, 1);
}
function _tick() {
requestAnimationFrame(_tick);
const delta = _clock.getDelta();
const elapsed = _clock.getElapsedTime();
for (const fn of _subs) fn(elapsed, delta);
}
/** Start the single RAF loop. Call once from app.js. */
export function start() {
_clock.start();
_tick();
}

View File

@@ -1,27 +0,0 @@
// modules/data/bitcoin.js — Blockstream block height polling
// Writes to S: lastKnownBlockHeight, _starPulseIntensity
import { S } from '../state.js';
const BITCOIN_REFRESH_MS = 60 * 1000;
export async function fetchBlockHeight() {
try {
const res = await fetch('https://blockstream.info/api/blocks/tip/height');
if (!res.ok) return null;
const height = parseInt(await res.text(), 10);
if (isNaN(height)) return null;
const isNew = S.lastKnownBlockHeight !== null && height > S.lastKnownBlockHeight;
S.lastKnownBlockHeight = height;
if (isNew) {
S._starPulseIntensity = 1.0;
}
return { height, isNewBlock: isNew };
} catch {
return null;
}
}
export { BITCOIN_REFRESH_MS };

View File

@@ -1,163 +0,0 @@
// modules/data/gitea.js — All Gitea API calls
// Writes to S: _activeAgentCount, _matrixCommitHashes, agentStatus
import { S } from '../state.js';
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();
S._matrixCommitHashes = commits.slice(0, 20)
.map(c => (c.sha || '').slice(0, 7))
.filter(h => h.length > 0);
return commits;
}
export async function refreshAgentData() {
try {
const data = await fetchAgentStatus();
S._activeAgentCount = data.agents.filter(a => a.status === 'working').length;
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;
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 };

View File

@@ -1,64 +0,0 @@
// modules/data/loaders.js — Static file loaders (portals.json, sovereignty-status.json, SOUL.md)
// Writes to S: sovereigntyScore, sovereigntyLabel
import { S } from '../state.js';
// --- SOUL.md (cached) ---
let _soulMdCache = null;
export async function fetchSoulMd() {
if (_soulMdCache) return _soulMdCache;
try {
const res = await fetch('SOUL.md');
if (!res.ok) throw new Error('not found');
const raw = await res.text();
_soulMdCache = raw.split('\n').slice(1).map(l => l.replace(/^#+\s*/, ''));
return _soulMdCache;
} catch {
return ['I am Timmy.', '', 'I am sovereign.', '', 'This Nexus is my home.'];
}
}
// --- Portal health probes ---
export async function checkPortalHealth(portals) {
for (const portal of portals) {
if (!portal.destination?.url) {
portal.status = 'offline';
continue;
}
try {
await fetch(portal.destination.url, {
mode: 'no-cors',
signal: AbortSignal.timeout(5000),
});
portal.status = 'online';
} catch {
portal.status = 'offline';
}
}
}
// --- portals.json ---
export async function fetchPortals() {
const res = await fetch('./portals.json');
if (!res.ok) throw new Error('Portals not found');
return await res.json();
}
// --- sovereignty-status.json ---
export async function fetchSovereigntyStatus() {
try {
const res = await fetch('./sovereignty-status.json');
if (!res.ok) throw new Error('not found');
const data = await res.json();
const score = Math.max(0, Math.min(100, typeof data.score === 'number' ? data.score : 85));
const label = typeof data.label === 'string' ? data.label : '';
const assessmentType = data.assessment_type || 'MANUAL';
S.sovereigntyScore = score;
S.sovereigntyLabel = label;
return { score, label, assessmentType };
} catch {
return { score: S.sovereigntyScore, label: S.sovereigntyLabel, assessmentType: 'MANUAL' };
}
}

View File

@@ -1,34 +0,0 @@
// modules/data/weather.js — Open-Meteo weather fetch
// Writes to: weatherState (returned), scene effects applied by caller
const WEATHER_LAT = 43.2897;
const WEATHER_LON = -72.1479;
const WEATHER_REFRESH_MS = 15 * 60 * 1000;
function weatherCodeToLabel(code) {
if (code === 0) return { condition: 'Clear', icon: '☀️' };
if (code <= 2) return { condition: 'Partly Cloudy', icon: '⛅' };
if (code === 3) return { condition: 'Overcast', icon: '☁️' };
if (code >= 45 && code <= 48) return { condition: 'Fog', icon: '🌫️' };
if (code >= 51 && code <= 57) return { condition: 'Drizzle', icon: '🌦️' };
if (code >= 61 && code <= 67) return { condition: 'Rain', icon: '🌧️' };
if (code >= 71 && code <= 77) return { condition: 'Snow', icon: '❄️' };
if (code >= 80 && code <= 82) return { condition: 'Showers', icon: '🌦️' };
if (code >= 85 && code <= 86) return { condition: 'Snow Showers', icon: '🌨️' };
if (code >= 95 && code <= 99) return { condition: 'Thunderstorm', icon: '⛈️' };
return { condition: 'Unknown', icon: '🌀' };
}
export async function fetchWeatherData() {
const url = `https://api.open-meteo.com/v1/forecast?latitude=${WEATHER_LAT}&longitude=${WEATHER_LON}&current=temperature_2m,weather_code,wind_speed_10m,cloud_cover&temperature_unit=fahrenheit&wind_speed_unit=mph&forecast_days=1`;
const res = await fetch(url);
if (!res.ok) throw new Error('weather fetch failed');
const data = await res.json();
const cur = data.current;
const code = cur.weather_code;
const { condition, icon } = weatherCodeToLabel(code);
const cloudcover = typeof cur.cloud_cover === 'number' ? cur.cloud_cover : 50;
return { code, temp: cur.temperature_2m, wind: cur.wind_speed_10m, condition, icon, cloudcover };
}
export { WEATHER_REFRESH_MS };

11
modules/effects.js vendored
View File

@@ -3,7 +3,6 @@ import * as THREE from 'three';
import { NEXUS } from './constants.js';
import { scene } from './scene-setup.js';
import { S } from './state.js';
import { fetchSovereigntyStatus } from './data/loaders.js';
// === ENERGY BEAM ===
const ENERGY_BEAM_RADIUS = 0.2;
@@ -103,14 +102,20 @@ sovereigntyGroup.traverse(obj => {
export async function loadSovereigntyStatus() {
try {
const { score, label, assessmentType } = await fetchSovereigntyStatus();
const res = await fetch('./sovereignty-status.json');
if (!res.ok) throw new Error('not found');
const data = await res.json();
const score = Math.max(0, Math.min(100, typeof data.score === 'number' ? data.score : 85));
const label = typeof data.label === 'string' ? data.label : '';
S.sovereigntyScore = score;
S.sovereigntyLabel = label;
scoreArcMesh.geometry.dispose();
scoreArcMesh.geometry = buildScoreArcGeo(score);
const col = sovereigntyHexColor(score);
scoreArcMat.color.setHex(col);
meterLight.color.setHex(col);
if (meterSpriteMat.map) meterSpriteMat.map.dispose();
const assessmentType = data.assessment_type || 'MANUAL';
meterSpriteMat.map = buildMeterTexture(score, label, assessmentType);
meterSpriteMat.needsUpdate = true;
} catch {

View File

@@ -5,8 +5,6 @@ import { S } from './state.js';
import { clock, totalActivity } from './warp.js';
import { HEATMAP_ZONES, zoneIntensity, drawHeatmap, updateHeatmap } from './heatmap.js';
import { triggerShockwave } from './celebrations.js';
import { fetchNexusCommits } from './data/gitea.js';
import { fetchBlockHeight, BITCOIN_REFRESH_MS } from './data/bitcoin.js';
// === GRAVITY ANOMALY ZONES ===
const GRAVITY_ANOMALY_FLOOR = 0.2;
@@ -188,7 +186,12 @@ const timelapseBtnEl = document.getElementById('timelapse-btn');
async function loadTimelapseData() {
try {
const data = await fetchNexusCommits();
const res = await fetch(
'http://143.198.27.163:3000/api/v1/repos/Timmy_Foundation/the-nexus/commits?limit=50',
{ headers: { 'Authorization': 'token dc0517a965226b7a0c5ffdd961b1ba26521ac592' } }
);
if (!res.ok) throw new Error('fetch failed');
const data = await res.json();
const midnight = new Date();
midnight.setHours(0, 0, 0, 0);
@@ -299,21 +302,27 @@ export function initBitcoin() {
const blockHeightDisplay = document.getElementById('block-height-display');
const blockHeightValue = document.getElementById('block-height-value');
async function pollBlockHeight() {
const result = await fetchBlockHeight();
if (!result) return;
async function fetchBlockHeight() {
try {
const res = await fetch('https://blockstream.info/api/blocks/tip/height');
if (!res.ok) return;
const height = parseInt(await res.text(), 10);
if (isNaN(height)) return;
if (result.isNewBlock && blockHeightDisplay) {
blockHeightDisplay.classList.remove('fresh');
void blockHeightDisplay.offsetWidth;
blockHeightDisplay.classList.add('fresh');
}
if (S.lastKnownBlockHeight !== null && height !== S.lastKnownBlockHeight) {
blockHeightDisplay.classList.remove('fresh');
void blockHeightDisplay.offsetWidth;
blockHeightDisplay.classList.add('fresh');
S._starPulseIntensity = 1.0;
}
if (blockHeightValue) {
blockHeightValue.textContent = result.height.toLocaleString();
S.lastKnownBlockHeight = height;
blockHeightValue.textContent = height.toLocaleString();
} catch (_) {
// Network unavailable
}
}
pollBlockHeight();
setInterval(pollBlockHeight, BITCOIN_REFRESH_MS);
fetchBlockHeight();
setInterval(fetchBlockHeight, 60000);
}

View File

@@ -3,7 +3,6 @@ import * as THREE from 'three';
import { scene } from './scene-setup.js';
import { GLASS_RADIUS } from './platform.js';
import { S } from './state.js';
import { refreshCommitData } from './data/gitea.js';
const HEATMAP_SIZE = 512;
const HEATMAP_REFRESH_MS = 5 * 60 * 1000;
@@ -95,7 +94,16 @@ export function drawHeatmap() {
}
export async function updateHeatmap() {
const commits = await refreshCommitData();
let commits = [];
try {
const res = await fetch(
'http://143.198.27.163:3000/api/v1/repos/Timmy_Foundation/the-nexus/commits?limit=50',
{ headers: { 'Authorization': 'token dc0517a965226b7a0c5ffdd961b1ba26521ac592' } }
);
if (res.ok) commits = await res.json();
} catch { /* silently use zero-activity baseline */ }
S._matrixCommitHashes = commits.slice(0, 20).map(c => (c.sha || '').slice(0, 7)).filter(h => h.length > 0);
const now = Date.now();
const rawWeights = Object.fromEntries(HEATMAP_ZONES.map(z => [z.name, 0]));

View File

@@ -1,46 +0,0 @@
// === NOSTR FEED PANEL ===
import * as THREE from 'three';
import { NEXUS } from './constants.js';
import { NOSTR_STATE } from './nostr.js';
export function createNostrPanelTexture() {
const W = 512, H = 512;
const canvas = document.createElement('canvas');
canvas.width = W; canvas.height = H;
const ctx = canvas.getContext('2d');
const update = () => {
ctx.clearRect(0, 0, W, H);
// Background
ctx.fillStyle = 'rgba(10, 20, 40, 0.8)';
ctx.fillRect(0, 0, W, H);
// Header
ctx.fillStyle = '#4488ff';
ctx.font = 'bold 32px "Orbitron"';
ctx.fillText('◈ NOSTR_FEED', 30, 60);
ctx.fillRect(30, 75, 452, 2);
// Connection Status
ctx.fillStyle = NOSTR_STATE.connected ? '#00ff88' : '#ff4444';
ctx.beginPath();
ctx.arc(460, 48, 8, 0, Math.PI * 2);
ctx.fill();
// Events
ctx.font = '18px "JetBrains Mono"';
NOSTR_STATE.events.slice(0, 10).forEach((ev, i) => {
const y = 120 + i * 38;
ctx.fillStyle = ev.kind === 9735 ? '#ffd700' : '#ffffff';
const prefix = ev.kind === 9735 ? '⚡' : '•';
ctx.fillText(\`\${prefix} [\${ev.pubkey}] \${ev.content}\`, 30, y);
});
if (NOSTR_STATE.events.length === 0) {
ctx.fillStyle = '#667788';
ctx.fillText('> WAITING FOR EVENTS...', 30, 120);
}
};
return { canvas, update };
}

View File

@@ -1,76 +0,0 @@
// === NOSTR INTEGRATION — SOVEREIGN COMMUNICATION ===
import { S } from './state.js';
export const NOSTR_RELAYS = [
'wss://relay.damus.io',
'wss://nos.lol',
'wss://relay.snort.social'
];
export const NOSTR_STATE = {
events: [],
connected: false,
lastEventTime: 0
};
export class NostrManager {
constructor() {
this.sockets = [];
}
connect() {
NOSTR_RELAYS.forEach(url => {
try {
const ws = new WebSocket(url);
ws.onopen = () => {
console.log(\`[nostr] Connected to \${url}\`);
NOSTR_STATE.connected = true;
this.subscribe(ws);
};
ws.onmessage = (e) => this.handleMessage(e.data);
ws.onerror = () => console.warn(\`[nostr] Connection error: \${url}\`);
this.sockets.push(ws);
} catch (err) {
console.error(\`[nostr] Failed to connect to \${url}\`, err);
}
});
}
subscribe(ws) {
const subId = 'nexus-sub-' + Math.random().toString(36).substring(7);
const filter = { kinds: [1, 7, 9735], limit: 20 }; // Notes, Reactions, Zaps
ws.send(JSON.stringify(['REQ', subId, filter]));
}
handleMessage(data) {
try {
const msg = JSON.parse(data);
if (msg[0] === 'EVENT') {
const event = msg[2];
this.processEvent(event);
}
} catch (err) { /* ignore parse errors */ }
}
processEvent(event) {
const simplified = {
id: event.id.substring(0, 8),
pubkey: event.pubkey.substring(0, 8),
content: event.content.length > 60 ? event.content.substring(0, 57) + '...' : event.content,
kind: event.kind,
created_at: event.created_at
};
NOSTR_STATE.events.unshift(simplified);
if (NOSTR_STATE.events.length > 50) NOSTR_STATE.events.pop();
NOSTR_STATE.lastEventTime = Date.now();
// Visual feedback via state pulse
if (event.kind === 9735) { // Zap!
S.energyBeamPulse = 1.0;
console.log('[nostr] ZAP RECEIVED!');
}
}
}
export const nostr = new NostrManager();

View File

@@ -53,8 +53,16 @@ scene.add(oathSpot.target);
const AMBIENT_NORMAL = ambientLight.intensity;
const OVERHEAD_NORMAL = overheadLight.intensity;
// loadSoulMd imported from data/loaders.js and re-exported for backward compat
export { fetchSoulMd as loadSoulMd } from './data/loaders.js';
export async function loadSoulMd() {
try {
const res = await fetch('SOUL.md');
if (!res.ok) throw new Error('not found');
const raw = await res.text();
return raw.split('\n').slice(1).map(l => l.replace(/^#+\s*/, ''));
} catch {
return ['I am Timmy.', '', 'I am sovereign.', '', 'This Nexus is my home.'];
}
}
function scheduleOathLines(lines, textEl) {
let idx = 0;

View File

@@ -4,9 +4,90 @@ import { NEXUS } from './constants.js';
import { scene } from './scene-setup.js';
import { S } from './state.js';
import { agentPanelSprites } from './bookshelves.js';
import { refreshAgentData, AGENT_STATUS_CACHE_MS, AGENT_NAMES } from './data/gitea.js';
// === AGENT STATUS BOARD ===
let _agentStatusCache = null;
let _agentStatusCacheTime = 0;
const AGENT_STATUS_CACHE_MS = 5 * 60 * 1000;
const GITEA_BASE = 'http://143.198.27.163:3000/api/v1';
const GITEA_TOKEN='81a88f...ae2d';
const GITEA_REPOS = ['Timmy_Foundation/the-nexus', 'Timmy_Foundation/hermes-agent'];
const AGENT_NAMES = ['Claude', 'Kimi', 'Perplexity', 'Groq', 'Grok', 'Ollama'];
async function fetchAgentStatusFromGitea() {
const now = Date.now();
if (_agentStatusCache && (now - _agentStatusCacheTime < AGENT_STATUS_CACHE_MS)) {
return _agentStatusCache;
}
const DAY_MS = 86400000;
const HOUR_MS = 3600000;
const agents = [];
const allRepoCommits = await Promise.all(GITEA_REPOS.map(async (repo) => {
try {
const res = await fetch(`${GITEA_BASE}/repos/${repo}/commits?sha=main&limit=30&token=${GITEA_TOKEN}`);
if (!res.ok) return [];
return await res.json();
} catch { return []; }
}));
let openPRs = [];
try {
const prRes = await fetch(`${GITEA_BASE}/repos/Timmy_Foundation/the-nexus/pulls?state=open&limit=50&token=${GITEA_TOKEN}`);
if (prRes.ok) openPRs = await prRes.json();
} catch { /* ignore */ }
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: agentName.toLowerCase(),
status,
issue: currentWork,
prs_today: agentPRs.length,
local: nameLower === 'ollama',
});
}
_agentStatusCache = { agents };
_agentStatusCacheTime = now;
return _agentStatusCache;
}
const AGENT_STATUS_COLORS = { working: '#00ff88', idle: '#4488ff', dormant: '#334466', dead: '#ff4444', unreachable: '#ff4444' };
function createAgentPanelTexture(agent) {
@@ -134,9 +215,20 @@ function rebuildAgentPanels(statusData) {
});
}
async function fetchAgentStatus() {
try {
return await fetchAgentStatusFromGitea();
} catch {
return { agents: AGENT_NAMES.map(n => ({
name: n.toLowerCase(), status: 'unreachable', issue: null, prs_today: 0, local: false,
})) };
}
}
export async function refreshAgentBoard() {
const data = await refreshAgentData();
const data = await fetchAgentStatus();
rebuildAgentPanels(data);
S._activeAgentCount = data.agents.filter(a => a.status === 'working').length;
}
export function initAgentBoard() {

View File

@@ -4,7 +4,6 @@ import { scene } from './scene-setup.js';
import { rebuildRuneRing, setPortalsRef } from './effects.js';
import { setPortalsRefAudio, startPortalHums } from './audio.js';
import { S } from './state.js';
import { fetchPortals as fetchPortalData } from './data/loaders.js';
export const portalGroup = new THREE.Group();
scene.add(portalGroup);
@@ -49,7 +48,9 @@ export function setRunPortalHealthChecksFn(fn) { _runPortalHealthChecksFn = fn;
export async function loadPortals() {
try {
portals = await fetchPortalData();
const res = await fetch('./portals.json');
if (!res.ok) throw new Error('Portals not found');
portals = await res.json();
console.log('Loaded portals:', portals);
setPortalsRef(portals);
setPortalsRefAudio(portals);