diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..2bd5a0a
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+22
diff --git a/dashboard/public/jarvis.html b/dashboard/public/jarvis.html
index d12e2ed..59e2500 100644
--- a/dashboard/public/jarvis.html
+++ b/dashboard/public/jarvis.html
@@ -1058,14 +1058,8 @@ document.addEventListener('DOMContentLoaded', () => {
.then(r => r.json())
.then(data => { D = data; init(); connectSSE(); })
.catch(() => {
- // API not ready yet — use inline data as fallback if available
- if (D && D.meta) { init(); }
- else { document.getElementById('bootLines').innerHTML = '
Waiting for first sweep...
'; }
- // Retry after a delay
- setTimeout(() => {
- fetch('/api/data').then(r => r.json()).then(data => { D = data; init(); connectSSE(); }).catch(() => {});
- }, 10000);
- connectSSE();
+ // Should not reach here — server routes to loading.html when no data
+ if (D && D.meta) { init(); connectSSE(); }
});
} else if (D && D.meta) {
// File mode: use inline data
diff --git a/dashboard/public/loading.html b/dashboard/public/loading.html
new file mode 100644
index 0000000..196f6ab
--- /dev/null
+++ b/dashboard/public/loading.html
@@ -0,0 +1,154 @@
+
+
+
+
+
+CRUCIX — Initializing
+
+
+
+
+
+
+
+ CX
+
+
+
+
+
+
+ COLLECTING DATA...
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index 2ac3593..5d9085e 100644
--- a/package.json
+++ b/package.json
@@ -4,18 +4,22 @@
"description": "Local intelligence engine — 26 OSINT sources, live dashboard, auto-refresh, optional LLM layer.",
"type": "module",
"scripts": {
+ "start": "node server.mjs",
"dev": "node --trace-warnings server.mjs",
"sweep": "node apis/briefing.mjs",
"inject": "node dashboard/inject.mjs",
"brief": "node apis/briefing.mjs",
"brief:save": "node apis/save-briefing.mjs",
- "diag": "node diag.mjs"
+ "diag": "node diag.mjs",
+ "clean": "node scripts/clean.mjs",
+ "fresh-start": "npm run clean && npm start"
},
"keywords": ["osint", "intelligence", "dashboard", "geopolitical"],
"author": "Crucix",
"license": "ISC",
"engines": {
- "node": ">=22"
+ "node": ">=22",
+ "npm": ">=10"
},
"dependencies": {
"express": "^5.1.0"
diff --git a/scripts/clean.mjs b/scripts/clean.mjs
new file mode 100644
index 0000000..1d87f42
--- /dev/null
+++ b/scripts/clean.mjs
@@ -0,0 +1,18 @@
+import { rm, access } from 'fs/promises';
+import { join } from 'path';
+
+const targets = [
+ 'runs/latest.json',
+ 'runs/memory',
+];
+
+for (const target of targets) {
+ const full = join(process.cwd(), target);
+ try {
+ await access(full);
+ await rm(full, { recursive: true });
+ console.log(`removed: ${target}`);
+ } catch {
+ // not found — skip silently
+ }
+}
diff --git a/server.mjs b/server.mjs
index f6173f2..86e26b5 100644
--- a/server.mjs
+++ b/server.mjs
@@ -29,6 +29,7 @@ for (const dir of [RUNS_DIR, MEMORY_DIR, join(MEMORY_DIR, 'cold')]) {
// === State ===
let currentData = null; // Current synthesized dashboard data
let lastSweepTime = null; // Timestamp of last sweep
+let sweepStartedAt = null; // Timestamp when current/last sweep started
let sweepInProgress = false;
const startTime = Date.now();
const sseClients = new Set();
@@ -230,9 +231,13 @@ if (discordAlerter.isConfigured) {
const app = express();
app.use(express.static(join(ROOT, 'dashboard/public')));
-// Serve jarvis.html as the root page
+// Serve loading page until first sweep completes, then the dashboard
app.get('/', (req, res) => {
- res.sendFile(join(ROOT, 'dashboard/public/jarvis.html'));
+ if (!currentData) {
+ res.sendFile(join(ROOT, 'dashboard/public/loading.html'));
+ } else {
+ res.sendFile(join(ROOT, 'dashboard/public/jarvis.html'));
+ }
});
// API: current data
@@ -251,6 +256,7 @@ app.get('/api/health', (req, res) => {
? new Date(new Date(lastSweepTime).getTime() + config.refreshIntervalMinutes * 60000).toISOString()
: null,
sweepInProgress,
+ sweepStartedAt,
sourcesOk: currentData?.meta?.sourcesOk || 0,
sourcesFailed: currentData?.meta?.sourcesFailed || 0,
llmEnabled: !!config.llm.provider,
@@ -288,7 +294,8 @@ async function runSweepCycle() {
}
sweepInProgress = true;
- broadcast({ type: 'sweep_start', timestamp: new Date().toISOString() });
+ sweepStartedAt = new Date().toISOString();
+ broadcast({ type: 'sweep_start', timestamp: sweepStartedAt });
console.log(`\n${'='.repeat(60)}`);
console.log(`[Crucix] Starting sweep at ${new Date().toLocaleTimeString()}`);
console.log(`${'='.repeat(60)}`);