Initial release — Crucix Intelligence Engine v2.0.0

26-source OSINT intelligence engine with live Jarvis dashboard,
auto-refresh via SSE, optional LLM layer (4 providers), delta/memory
system, and Telegram breaking news alerts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
calesthio
2026-03-12 23:45:46 -07:00
commit ef2c6470fb
53 changed files with 8709 additions and 0 deletions

32
apis/utils/env.mjs Normal file
View File

@@ -0,0 +1,32 @@
// Load .env file for API keys
// Searches: project root .env first, then apis/.env as fallback
import { readFileSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const paths = [
resolve(__dirname, '..', '..', '.env'), // project root
resolve(__dirname, '..', '.env'), // apis/.env (legacy)
];
function loadEnv(filePath) {
try {
const content = readFileSync(filePath, 'utf-8');
let loaded = 0;
for (const line of content.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq === -1) continue;
const key = trimmed.slice(0, eq).trim();
const val = trimmed.slice(eq + 1).trim();
if (!process.env[key]) { process.env[key] = val; loaded++; }
}
return loaded;
} catch { return -1; }
}
for (const p of paths) {
if (loadEnv(p) >= 0) break;
}

42
apis/utils/fetch.mjs Normal file
View File

@@ -0,0 +1,42 @@
// Shared fetch utility with timeout, retries, and error handling
export async function safeFetch(url, opts = {}) {
const { timeout = 15000, retries = 1, headers = {} } = opts;
let lastError;
for (let i = 0; i <= retries; i++) {
try {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
const res = await fetch(url, {
signal: controller.signal,
headers: { 'User-Agent': 'Crucix/1.0', ...headers },
});
clearTimeout(timer);
if (!res.ok) {
const body = await res.text().catch(() => '');
throw new Error(`HTTP ${res.status}: ${body.slice(0, 200)}`);
}
const text = await res.text();
try { return JSON.parse(text); } catch { return { rawText: text.slice(0, 500) }; }
} catch (e) {
lastError = e;
// GDELT needs 5s between requests, others are fine with shorter delays
if (i < retries) await new Promise(r => setTimeout(r, 2000 * (i + 1)));
}
}
return { error: lastError?.message || 'Unknown error', source: url };
}
export function ago(hours) {
return new Date(Date.now() - hours * 3600000).toISOString();
}
export function today() {
return new Date().toISOString().split('T')[0];
}
export function daysAgo(n) {
const d = new Date();
d.setDate(d.getDate() - n);
return d.toISOString().split('T')[0];
}