diff --git a/dashboard/app/api/demo/inject/route.js b/dashboard/app/api/demo/inject/route.js
new file mode 100644
index 0000000..e8dfc0c
--- /dev/null
+++ b/dashboard/app/api/demo/inject/route.js
@@ -0,0 +1,215 @@
+import { NextResponse } from 'next/server';
+import { randomUUID } from 'node:crypto';
+import { db } from '../../../../lib/firebase-admin.js';
+
+const INJECT_TIMEOUT_MS = 15_000;
+const EVENT_BUS_TIMEOUT_MS = 5_000;
+
+const configuredDisruptionAgentUrl =
+ process.env.DISRUPTION_AGENT_URL ||
+ process.env.NEXT_PUBLIC_DISRUPTION_AGENT_URL ||
+ '';
+
+const DISRUPTION_AGENT_URL =
+ configuredDisruptionAgentUrl || (process.env.NODE_ENV === 'development' ? 'http://localhost:3001' : '');
+
+const SCENARIOS = {
+ pacific_storm: {
+ label: 'Super Typhoon Mawar',
+ type: 'WEATHER',
+ severity: 8,
+ location: 'Western Pacific Shipping Corridor',
+ epicenterLat: 28.2,
+ epicenterLng: 143.4,
+ confidence: 0.86,
+ affectedZones: ['Western Pacific', 'North Pacific Route'],
+ description:
+ 'Super Typhoon approaching Western Pacific, Category 5. Maximum sustained winds 185 km/h. Direct path over major trans-Pacific shipping corridors. 12 vessels currently in projected storm path between Japan and Los Angeles. Port of Yokohama issuing storm warnings.',
+ },
+ suez_closure: {
+ label: 'Suez Canal Emergency',
+ type: 'GEOPOLITICAL',
+ severity: 9,
+ location: 'Suez Canal / Red Sea',
+ epicenterLat: 29.9668,
+ epicenterLng: 32.5498,
+ confidence: 0.88,
+ affectedZones: ['Red Sea', 'Gulf of Aden', 'Suez Canal'],
+ description:
+ 'The Suez Canal Authority has announced an emergency closure. Houthi missile attacks on Red Sea vessels. Forty-three vessels held. $12B daily trade affected. Minimum 21-day closure expected. All Asia-Europe shipments via southern route ordered to divert via Cape of Good Hope.',
+ },
+ port_strike: {
+ label: 'Mumbai JNPT Strike',
+ type: 'STRIKE',
+ severity: 7,
+ location: 'North Sea Port Cluster',
+ epicenterLat: 51.9225,
+ epicenterLng: 4.4792,
+ confidence: 0.83,
+ affectedZones: ['Rotterdam', 'Hamburg', 'Antwerp'],
+ description:
+ 'International Transport Workers Federation confirms indefinite strike action at Port of Rotterdam, Hamburg, and Antwerp. All container terminal operations suspended. 80+ vessels at anchor awaiting berth. Estimated 2-week minimum disruption to Europe-bound cargo.',
+ },
+};
+
+function isTimeoutError(error) {
+ const name = String(error?.name || '').toLowerCase();
+ const message = String(error?.message || '').toLowerCase();
+ return name.includes('timeout') || name.includes('abort') || message.includes('timeout') || message.includes('aborted');
+}
+
+async function parseUpstreamBody(response) {
+ const contentType = String(response.headers.get('content-type') || '').toLowerCase();
+ if (contentType.includes('application/json')) {
+ return response.json().catch(() => null);
+ }
+
+ const text = await response.text().catch(() => '');
+ if (!text) return null;
+ return { message: text.slice(0, 500) };
+}
+
+function buildSyntheticDisruption(scenarioMeta) {
+ return {
+ id: `disruption-${randomUUID()}`,
+ type: scenarioMeta.type,
+ severity: scenarioMeta.severity,
+ location: scenarioMeta.location,
+ epicenterLat: scenarioMeta.epicenterLat,
+ epicenterLng: scenarioMeta.epicenterLng,
+ confidence: scenarioMeta.confidence,
+ affectedZones: scenarioMeta.affectedZones,
+ rawDescription: scenarioMeta.description,
+ detectedAt: new Date().toISOString(),
+ source: 'dashboard-demo-fallback',
+ unverified: false,
+ corroboratingSources: 1,
+ };
+}
+
+async function publishSyntheticDisruption(disruptionEvent, traceId) {
+ const eventBusUrl = process.env.EVENT_BUS_URL || 'http://localhost:4000';
+ const response = await fetch(`${eventBusUrl}/publish`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ topic: 'disruption-events',
+ payload: {
+ agentId: 'monitor',
+ traceId,
+ timestamp: new Date().toISOString(),
+ payload: disruptionEvent,
+ },
+ }),
+ signal: AbortSignal.timeout(EVENT_BUS_TIMEOUT_MS),
+ });
+
+ if (!response.ok) {
+ const msg = await response.text().catch(() => '');
+ throw new Error(`Event bus publish failed [${response.status}]${msg ? `: ${msg}` : ''}`);
+ }
+}
+
+async function injectSyntheticDisruption(scenario, scenarioMeta, reason = 'upstream unavailable') {
+ const traceId = randomUUID();
+ const disruptionEvent = buildSyntheticDisruption(scenarioMeta);
+ const results = await Promise.allSettled([
+ db.collection('disruptions').doc(disruptionEvent.id).set(disruptionEvent, { merge: true }),
+ publishSyntheticDisruption(disruptionEvent, traceId),
+ ]);
+
+ const persisted = results[0].status === 'fulfilled';
+ const published = results[1].status === 'fulfilled';
+
+ if (!persisted && !published) {
+ const persistErr = results[0].reason?.message || 'persist failed';
+ const publishErr = results[1].reason?.message || 'publish failed';
+ throw new Error(`Synthetic injection failed: ${persistErr}; ${publishErr}`);
+ }
+
+ return NextResponse.json(
+ {
+ ok: true,
+ synthetic: true,
+ scenario,
+ label: scenarioMeta.label,
+ traceId,
+ disruptionId: disruptionEvent.id,
+ persisted,
+ published,
+ warning: reason,
+ },
+ { status: 202 }
+ );
+}
+
+export async function POST(req) {
+ try {
+ const { scenario } = await req.json();
+ const scenarioKey = String(scenario || '').trim().toLowerCase();
+ const scenarioMeta = SCENARIOS[scenarioKey];
+
+ if (!scenarioMeta) {
+ return NextResponse.json(
+ { error: `Unknown scenario. Available: ${Object.keys(SCENARIOS).join(', ')}` },
+ { status: 400 }
+ );
+ }
+
+ if (!DISRUPTION_AGENT_URL) {
+ return injectSyntheticDisruption(
+ scenarioKey,
+ scenarioMeta,
+ 'DISRUPTION_AGENT_URL is not configured; synthetic fallback used'
+ );
+ }
+
+ const headers = { 'Content-Type': 'application/json' };
+ if (process.env.INTERNAL_TOKEN) {
+ headers.Authorization = `Bearer ${process.env.INTERNAL_TOKEN}`;
+ }
+
+ try {
+ const upstream = await fetch(`${DISRUPTION_AGENT_URL}/events`, {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({ description: scenarioMeta.description }),
+ signal: AbortSignal.timeout(INJECT_TIMEOUT_MS),
+ });
+
+ const result = await parseUpstreamBody(upstream);
+ if (!upstream.ok) {
+ return injectSyntheticDisruption(
+ scenarioKey,
+ scenarioMeta,
+ `Disruption agent returned ${upstream.status}; synthetic fallback used`
+ );
+ }
+
+ return NextResponse.json({
+ ok: true,
+ synthetic: false,
+ disruptionId: result?.data?.id || null,
+ traceId: result?.traceId || null,
+ scenario: scenarioKey,
+ label: scenarioMeta.label,
+ published: result?.published ?? false,
+ });
+ } catch (err) {
+ if (isTimeoutError(err)) {
+ return injectSyntheticDisruption(
+ scenarioKey,
+ scenarioMeta,
+ `Disruption agent timed out after ${Math.floor(INJECT_TIMEOUT_MS / 1000)} seconds; synthetic fallback used`
+ );
+ }
+ return injectSyntheticDisruption(
+ scenarioKey,
+ scenarioMeta,
+ `Disruption agent unavailable (${err.message}); synthetic fallback used`
+ );
+ }
+ } catch (err) {
+ return NextResponse.json({ error: err.message || 'Inject failed' }, { status: 500 });
+ }
+}
diff --git a/dashboard/app/api/webhooks/disruption/route.js b/dashboard/app/api/webhooks/disruption/route.js
index 43ab6d8..f57a0f8 100644
--- a/dashboard/app/api/webhooks/disruption/route.js
+++ b/dashboard/app/api/webhooks/disruption/route.js
@@ -1,13 +1,131 @@
import { NextResponse } from 'next/server';
+import { randomUUID } from 'node:crypto';
import { db } from '../../../../lib/firebase-admin.js'; // server-side admin import
import { verifyInternalToken } from '../../_internal-auth.js';
+const INJECT_TIMEOUT_MS = 15_000;
+const EVENT_BUS_TIMEOUT_MS = 5_000;
+
const SCENARIO_MAP = {
- suez_closure: 'The Suez Canal Authority has announced an emergency closure. Houthi missile attacks on Red Sea vessels. Forty-three vessels held. $12B daily trade affected. Minimum 21-day closure expected. All Asia-Europe shipments via southern route ordered to divert via Cape of Good Hope.',
- pacific_storm: 'Super Typhoon approaching Western Pacific, Category 5. Maximum sustained winds 185 km/h. Direct path over major trans-Pacific shipping corridors. 12 vessels currently in projected storm path between Japan and Los Angeles. Port of Yokohama issuing storm warnings.',
- port_strike: 'International Transport Workers Federation confirms indefinite strike action at Port of Rotterdam, Hamburg, and Antwerp. All container terminal operations suspended. 80+ vessels at anchor awaiting berth. Estimated 2-week minimum disruption to Europe-bound cargo.',
+ suez_closure: {
+ description: 'The Suez Canal Authority has announced an emergency closure. Houthi missile attacks on Red Sea vessels. Forty-three vessels held. $12B daily trade affected. Minimum 21-day closure expected. All Asia-Europe shipments via southern route ordered to divert via Cape of Good Hope.',
+ type: 'GEOPOLITICAL',
+ severity: 9,
+ location: 'Suez Canal / Red Sea',
+ epicenterLat: 29.9668,
+ epicenterLng: 32.5498,
+ affectedZones: ['Red Sea', 'Gulf of Aden', 'Suez Canal'],
+ confidence: 0.88,
+ },
+ pacific_storm: {
+ description: 'Super Typhoon approaching Western Pacific, Category 5. Maximum sustained winds 185 km/h. Direct path over major trans-Pacific shipping corridors. 12 vessels currently in projected storm path between Japan and Los Angeles. Port of Yokohama issuing storm warnings.',
+ type: 'WEATHER',
+ severity: 8,
+ location: 'Western Pacific Shipping Corridor',
+ epicenterLat: 28.2,
+ epicenterLng: 143.4,
+ affectedZones: ['Western Pacific', 'North Pacific Route'],
+ confidence: 0.86,
+ },
+ port_strike: {
+ description: 'International Transport Workers Federation confirms indefinite strike action at Port of Rotterdam, Hamburg, and Antwerp. All container terminal operations suspended. 80+ vessels at anchor awaiting berth. Estimated 2-week minimum disruption to Europe-bound cargo.',
+ type: 'STRIKE',
+ severity: 7,
+ location: 'North Sea Port Cluster',
+ epicenterLat: 51.9225,
+ epicenterLng: 4.4792,
+ affectedZones: ['Rotterdam', 'Hamburg', 'Antwerp'],
+ confidence: 0.83,
+ },
};
+function isTimeoutError(error) {
+ const name = String(error?.name || '').toLowerCase();
+ const message = String(error?.message || '').toLowerCase();
+ return name.includes('timeout') || name.includes('abort') || message.includes('timeout') || message.includes('aborted');
+}
+
+async function parseUpstreamBody(response) {
+ const contentType = String(response.headers.get('content-type') || '').toLowerCase();
+ if (contentType.includes('application/json')) {
+ return response.json().catch(() => null);
+ }
+
+ const text = await response.text().catch(() => '');
+ if (!text) return null;
+ return { message: text.slice(0, 500) };
+}
+
+function buildSyntheticDisruption(scenarioMeta) {
+ return {
+ id: `disruption-${randomUUID()}`,
+ type: scenarioMeta.type,
+ severity: scenarioMeta.severity,
+ location: scenarioMeta.location,
+ epicenterLat: scenarioMeta.epicenterLat,
+ epicenterLng: scenarioMeta.epicenterLng,
+ confidence: scenarioMeta.confidence,
+ affectedZones: scenarioMeta.affectedZones,
+ rawDescription: scenarioMeta.description,
+ detectedAt: new Date().toISOString(),
+ source: 'dashboard-webhook-fallback',
+ unverified: false,
+ corroboratingSources: 1,
+ };
+}
+
+async function publishSyntheticDisruption(disruptionEvent, traceId) {
+ const eventBusUrl = process.env.EVENT_BUS_URL || 'http://localhost:4000';
+ const response = await fetch(`${eventBusUrl}/publish`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ topic: 'disruption-events',
+ payload: {
+ agentId: 'monitor',
+ traceId,
+ timestamp: new Date().toISOString(),
+ payload: disruptionEvent,
+ },
+ }),
+ signal: AbortSignal.timeout(EVENT_BUS_TIMEOUT_MS),
+ });
+ if (!response.ok) {
+ const msg = await response.text().catch(() => '');
+ throw new Error(`Event bus publish failed [${response.status}]${msg ? `: ${msg}` : ''}`);
+ }
+}
+
+async function injectSyntheticDisruption(scenario, scenarioMeta, reason) {
+ const traceId = randomUUID();
+ const disruptionEvent = buildSyntheticDisruption(scenarioMeta);
+ const results = await Promise.allSettled([
+ db.collection('disruptions').doc(disruptionEvent.id).set(disruptionEvent, { merge: true }),
+ publishSyntheticDisruption(disruptionEvent, traceId),
+ ]);
+ const persisted = results[0].status === 'fulfilled';
+ const published = results[1].status === 'fulfilled';
+ if (!persisted && !published) {
+ const persistErr = results[0].reason?.message || 'persist failed';
+ const publishErr = results[1].reason?.message || 'publish failed';
+ throw new Error(`Synthetic injection failed: ${persistErr}; ${publishErr}`);
+ }
+
+ return NextResponse.json(
+ {
+ ok: true,
+ synthetic: true,
+ scenario,
+ traceId,
+ disruptionId: disruptionEvent.id,
+ persisted,
+ published,
+ warning: reason,
+ },
+ { status: 202 }
+ );
+}
+
/**
* POST /api/webhooks/disruption
* Receives pushes from the event bus and writes them to Firestore.
@@ -32,24 +150,53 @@ export async function POST(req) {
}
if (body.scenario) {
- const description = SCENARIO_MAP[body.scenario];
- if (!description) {
+ const scenarioMeta = SCENARIO_MAP[body.scenario];
+ if (!scenarioMeta) {
return NextResponse.json({ error: `Unknown scenario: ${body.scenario}` }, { status: 400 });
}
- const disruptionUrl = process.env.DISRUPTION_AGENT_URL || 'http://localhost:3001';
- const upstream = await fetch(`${disruptionUrl}/events`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- ...(process.env.INTERNAL_TOKEN ? { Authorization: `Bearer ${process.env.INTERNAL_TOKEN}` } : {}),
- },
- body: JSON.stringify({ description }),
- signal: AbortSignal.timeout(15_000),
- });
+ try {
+ const disruptionUrl = process.env.DISRUPTION_AGENT_URL || 'http://localhost:3001';
+ const upstream = await fetch(`${disruptionUrl}/events`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(process.env.INTERNAL_TOKEN ? { Authorization: `Bearer ${process.env.INTERNAL_TOKEN}` } : {}),
+ },
+ body: JSON.stringify({ description: scenarioMeta.description }),
+ signal: AbortSignal.timeout(INJECT_TIMEOUT_MS),
+ });
- const result = await upstream.json().catch(() => ({}));
- return NextResponse.json({ ok: upstream.ok, scenario: body.scenario, ...result }, { status: upstream.status });
+ const upstreamBody = await parseUpstreamBody(upstream);
+ if (!upstream.ok) {
+ return injectSyntheticDisruption(
+ body.scenario,
+ scenarioMeta,
+ `Disruption agent returned ${upstream.status}; synthetic fallback used`
+ );
+ }
+ return NextResponse.json(
+ {
+ ok: upstream.ok,
+ scenario: body.scenario,
+ ...(upstreamBody && typeof upstreamBody === 'object' ? upstreamBody : {}),
+ },
+ { status: upstream.status }
+ );
+ } catch (err) {
+ if (isTimeoutError(err)) {
+ return injectSyntheticDisruption(
+ body.scenario,
+ scenarioMeta,
+ `Disruption agent timed out after ${Math.floor(INJECT_TIMEOUT_MS / 1000)} seconds; synthetic fallback used`
+ );
+ }
+ return injectSyntheticDisruption(
+ body.scenario,
+ scenarioMeta,
+ `Disruption agent unavailable (${err.message}); synthetic fallback used`
+ );
+ }
}
const { agentId, traceId, timestamp, payload } = body;
diff --git a/dashboard/app/components/NavBar.jsx b/dashboard/app/components/NavBar.jsx
index 42959b6..dd704a6 100644
--- a/dashboard/app/components/NavBar.jsx
+++ b/dashboard/app/components/NavBar.jsx
@@ -2,8 +2,8 @@
import Link from 'next/link';
import { usePathname, useRouter } from 'next/navigation';
-import { useEffect } from 'react';
-import { Activity, BarChart3, Globe, Package, RotateCcw, Settings, Moon, Sun, Workflow, AlertCircle, Code2 } from 'lucide-react';
+import { useEffect, useState } from 'react';
+import { Activity, BarChart3, Globe, Package, RotateCcw, Settings, Moon, Sun, Workflow, AlertCircle, Code2, Zap } from 'lucide-react';
import { useAlertStore } from '../store/alertStore.js';
import { useTheme } from '../providers/ThemeProvider.jsx';
import { motion } from 'framer-motion';
@@ -12,6 +12,7 @@ const NAV_ITEMS = [
{ href: '/', label: 'Globe', icon: Globe, section: 'live' },
{ href: '/shipments', label: 'Shipments', icon: Package, section: 'live' },
{ href: '/analytics', label: 'Analytics', icon: BarChart3, section: 'live' },
+ { href: '/demo', label: 'Demo', icon: Zap, section: 'live' },
{ href: '/replay', label: 'Replay', icon: RotateCcw, section: 'analysis' },
{ href: '/visualize', label: 'Visualize', icon: Workflow, section: 'analysis' },
{ href: '/developers', label: 'API', icon: Code2, section: 'analysis' },
@@ -19,16 +20,24 @@ const NAV_ITEMS = [
];
function ThemeToggleButton({ theme, onToggle }) {
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ const titleText = mounted ? `Switch to ${theme === 'dark' ? 'light' : 'dark'} mode` : 'Toggle theme';
+
return (
);
}
diff --git a/dashboard/app/demo/page.js b/dashboard/app/demo/page.js
new file mode 100644
index 0000000..aa300bb
--- /dev/null
+++ b/dashboard/app/demo/page.js
@@ -0,0 +1,1294 @@
+'use client';
+
+import { useCallback, useEffect, useRef, useState } from 'react';
+import {
+ collection,
+ getDocs,
+ limit,
+ onSnapshot,
+ orderBy,
+ query,
+ where,
+} from 'firebase/firestore';
+import { db, isFirebaseConfigured } from '../lib/firebase.js';
+import NavBar from '../components/NavBar.jsx';
+
+const SCENARIOS = [
+ {
+ id: 'pacific_storm',
+ label: 'Super Typhoon Mawar',
+ region: 'Western Pacific',
+ severity: 9,
+ icon: '🌀',
+ tag: 'CAT 5 STORM',
+ tagColor: 'var(--accent-red)',
+ preview: 'Shanghai → LA corridor at risk. Manila, Kaohsiung, HK vessel advisories active.',
+ },
+ {
+ id: 'suez_closure',
+ label: 'Suez Canal Emergency',
+ region: 'Red Sea / Egypt',
+ severity: 10,
+ icon: 'âš“',
+ tag: 'CANAL BLOCKED',
+ tagColor: 'var(--accent-amber)',
+ preview: "$12B daily trade halted. 43 vessels held. Lloyd's suspended war-risk coverage.",
+ },
+ {
+ id: 'port_strike',
+ label: 'Mumbai JNPT Strike',
+ region: 'South Asia',
+ severity: 7,
+ icon: '🏗',
+ tag: 'PORT STRIKE',
+ tagColor: 'var(--accent-blue)',
+ preview: '4,800 dockworkers AWOL. 5M TEU/yr port offline. Mundra at 85% capacity.',
+ },
+];
+
+const STAGES = [
+ { id: 'idle', label: 'Ready', icon: 'â—Ž', color: 'var(--text-muted)' },
+ { id: 'injected', label: 'Disruption Injected',icon: '⚡', color: 'var(--accent-amber)' },
+ { id: 'monitoring',label: 'Monitor Agent', icon: '📡', color: 'var(--accent-blue)' },
+ { id: 'impact', label: 'Impact Analysis', icon: '📊', color: 'var(--accent-red)' },
+ { id: 'resolution',label: 'AI Resolution', icon: '🤖', color: 'var(--accent-cyan)' },
+ { id: 'decision', label: 'Human Decision', icon: '⚖️', color: 'var(--accent-amber)' },
+ { id: 'applied', label: 'Protocol Applied', icon: 'âś“', color: 'var(--accent-green)' },
+ { id: 'report', label: 'Report Ready', icon: 'đź“„', color: 'var(--accent-cyan)' },
+];
+
+const STAGE_INDEX = Object.fromEntries(STAGES.map((s, i) => [s.id, i]));
+
+const OPTION_CFG = {
+ 1: { label: 'Recommended', accent: 'var(--accent-cyan)', border: 'rgba(34,211,238,0.35)' },
+ 2: { label: 'Fastest Path', accent: 'var(--accent-blue)', border: 'rgba(59,130,246,0.35)' },
+ 3: { label: 'Cost Efficient',accent: 'var(--accent-amber)', border: 'rgba(245,158,11,0.35)' },
+};
+
+function fmt$(v) { return `$${Number(v || 0).toLocaleString()}`; }
+function fmtD(v) { return v >= 0 ? `+${v}d` : `${v}d`; }
+function fmtCO2(v){ return `${Math.round(Number(v || 0) / 1000)}t COâ‚‚`; }
+
+function Pulse({ color = 'var(--accent-cyan)' }) {
+ return (
+
+ );
+}
+
+function StageNode({ stage, index, currentIndex }) {
+ const done = index < currentIndex;
+ const active = index === currentIndex;
+ const pending = index > currentIndex;
+ const color = active ? stage.color : done ? 'var(--accent-green)' : 'var(--text-muted)';
+
+ return (
+
+
+ {done ? 'âś“' : stage.icon}
+
+
+ {stage.label}
+
+
+ );
+}
+
+function PipelineBar({ currentStage }) {
+ const currentIndex = STAGE_INDEX[currentStage] ?? 0;
+ const pct = currentIndex > 0 ? ((currentIndex) / (STAGES.length - 1)) * 100 : 0;
+
+ return (
+
+
+
+
+ {STAGES.map((stage, i) => (
+
+
+
+ ))}
+
+
+ );
+}
+
+function LogLine({ line }) {
+ const colors = {
+ info: 'var(--accent-cyan)',
+ success: 'var(--accent-green)',
+ warn: 'var(--accent-amber)',
+ error: 'var(--accent-red)',
+ };
+ return (
+
+ {line.ts}
+ [{line.type.toUpperCase()}]
+ {line.msg}
+
+ );
+}
+
+function SeverityBar({ value }) {
+ const pct = (value / 10) * 100;
+ const color = value >= 8 ? 'var(--accent-red)' : value >= 5 ? 'var(--accent-amber)' : 'var(--accent-green)';
+ return (
+
+ );
+}
+
+function Chip({ label, value, accent }) {
+ return (
+
+
+ {label}
+
+ {value}
+
+ );
+}
+
+function OptionCardInline({ option, onApprove, isApproving, approvedRank }) {
+ const cfg = OPTION_CFG[option.rank] || OPTION_CFG[3];
+ const disabled = isApproving || approvedRank !== null;
+ const selected = option.rank === approvedRank;
+
+ return (
+
+
+
+ {cfg.label}
+
+
+ Option {option.rank}
+
+
+
+
+
+ {option.title || option.name || `Resolution Strategy ${option.rank}`}
+
+
+ {option.description || option.summary || '—'}
+
+
+
+
+
+
+
+
+
+ {option.confidence != null && (
+
+
+ AI Confidence
+
+
+
+
{option.confidence}%
+
+
+ )}
+
+ {selected ? (
+
+ âś“ Protocol Deployed
+
+ ) : (
+
+ )}
+
+ );
+}
+
+export default function DemoPage() {
+ const [selectedScenario, setSelectedScenario] = useState(null);
+ const [stage, setStage] = useState('idle');
+ const [disruptionId, setDisruptionId] = useState(null);
+ const [traceId, setTraceId] = useState(null);
+ const [disruption, setDisruption] = useState(null);
+ const [impactReport, setImpactReport] = useState(null);
+ const [resolution, setResolution] = useState(null);
+ const [options, setOptions] = useState([]);
+ const [approvedRank, setApprovedRank] = useState(null);
+ const [isApproving, setIsApproving] = useState(false);
+ const [isGeneratingReport, setIsGeneratingReport] = useState(false);
+ const [reportReady, setReportReady] = useState(false);
+ const [reportData, setReportData] = useState(null);
+ const [logs, setLogs] = useState([]);
+ const [launching, setLaunching] = useState(false);
+ const [error, setError] = useState(null);
+ const unsubsRef = useRef([]);
+ const logEndRef = useRef(null);
+
+ const log = useCallback((msg, type = 'info') => {
+ const ts = new Date().toLocaleTimeString('en-US', { hour12: false });
+ setLogs((prev) => [...prev.slice(-60), { ts, msg, type }]);
+ }, []);
+
+ useEffect(() => {
+ logEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ }, [logs]);
+
+ useEffect(() => {
+ return () => unsubsRef.current.forEach((u) => u?.());
+ }, []);
+
+ const watchResolutionRef = useRef(null);
+ const watchImpactRef = useRef(null);
+
+ const watchResolution = useCallback(
+ (dId) => {
+ if (!isFirebaseConfigured || !db) return;
+ log('Impact complete — AI resolution agent active…', 'info');
+
+ const q = query(
+ collection(db, 'resolutions'),
+ where('disruptionId', '==', dId),
+ orderBy('createdAt', 'desc'),
+ limit(1)
+ );
+
+ const unsub = onSnapshot(q, async (snap) => {
+ if (snap.empty) return;
+
+ const doc = snap.docs[0];
+ const res = { id: doc.id, ...doc.data() };
+
+ try {
+ const optSnap = await getDocs(
+ query(collection(db, 'resolutions', doc.id, 'options'), orderBy('rank'))
+ );
+ const opts = optSnap.docs.map((d) => ({ id: d.id, ...d.data() }));
+
+ if (opts.length > 0) {
+ setResolution(res);
+ setOptions(opts);
+ setStage('resolution');
+ log(`âś“ AI generated ${opts.length} resolution strategies`, 'success');
+ setTimeout(() => setStage('decision'), 1200);
+ }
+ } catch (err) {
+ log(`Options fetch error: ${err.message}`, 'warn');
+ }
+ });
+
+ unsubsRef.current.push(unsub);
+ },
+ [log]
+ );
+
+ const watchImpact = useCallback(
+ (dId) => {
+ if (!isFirebaseConfigured || !db) return;
+ log('Monitor agent processing… watching impact_reports…', 'info');
+
+ const q = query(
+ collection(db, 'impact_reports'),
+ where('disruptionId', '==', dId),
+ orderBy('createdAt', 'desc'),
+ limit(1)
+ );
+
+ const unsub = onSnapshot(q, (snap) => {
+ if (!snap.empty) {
+ const data = snap.docs[0].data();
+ setImpactReport(data);
+ setStage('impact');
+ log(
+ `âś“ Impact scored: $${Number(data.totalCargoAtRiskUSD || 0).toLocaleString()} at risk across ${(data.affectedShipments || []).length} shipments`,
+ 'success'
+ );
+ if (watchResolutionRef.current) watchResolutionRef.current(dId);
+ }
+ });
+
+ unsubsRef.current.push(unsub);
+ },
+ [log]
+ );
+
+ useEffect(() => {
+ watchResolutionRef.current = watchResolution;
+ watchImpactRef.current = watchImpact;
+ }, [watchResolution, watchImpact]);
+
+ const watchDisruption = useCallback(
+ (dId) => {
+ if (!isFirebaseConfigured || !db) {
+ log('Firebase not configured — polling REST fallback', 'warn');
+ return;
+ }
+
+ log(`Listening for disruption ${dId} in Firestore…`, 'info');
+
+ const q = query(
+ collection(db, 'disruptions'),
+ where('__name__', '==', dId),
+ limit(1)
+ );
+
+ const unsub = onSnapshot(q, (snap) => {
+ if (!snap.empty) {
+ const data = { id: snap.docs[0].id, ...snap.docs[0].data() };
+ setDisruption(data);
+ setStage('monitoring');
+ log(`âś“ Disruption confirmed: ${data.title || data.type || dId}`, 'success');
+ if (watchImpactRef.current) watchImpactRef.current(dId);
+ }
+ });
+
+ unsubsRef.current.push(unsub);
+ },
+ [log]
+ );
+
+ const handleLaunch = async () => {
+ if (!selectedScenario) return;
+ setError(null);
+ setLaunching(true);
+ setLogs([]);
+ setDisruption(null);
+ setImpactReport(null);
+ setResolution(null);
+ setOptions([]);
+ setApprovedRank(null);
+ setReportReady(false);
+ setReportData(null);
+ unsubsRef.current.forEach((u) => u?.());
+ unsubsRef.current = [];
+
+ log(`Injecting scenario: ${selectedScenario}…`, 'info');
+ setStage('injected');
+
+ try {
+ const res = await fetch('/api/demo/inject', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ scenario: selectedScenario }),
+ });
+
+ const data = await res.json();
+
+ if (!res.ok) {
+ throw new Error(data.error || `HTTP ${res.status}`);
+ }
+
+ const dId = data.disruptionId;
+ const tId = data.traceId;
+ setDisruptionId(dId);
+ setTraceId(tId);
+
+ log(`✓ Disruption published — ID: ${dId}`, 'success');
+ log(` TraceId: ${tId}`, 'info');
+ log('Event bus fan-out complete. Agent pipeline starting…', 'info');
+
+ watchDisruption(dId);
+ } catch (err) {
+ setError(err.message);
+ setStage('idle');
+ log(`âś— Injection failed: ${err.message}`, 'error');
+ } finally {
+ setLaunching(false);
+ }
+ };
+
+ const handleApprove = async (rank) => {
+ if (!resolution || !options.length) return;
+ setIsApproving(true);
+ setStage('applied');
+
+ const selected = options.find((o) => o.rank === rank);
+ log(`Deploying protocol: Option ${rank} — ${selected?.title || ''}`, 'info');
+
+ try {
+ const res = await fetch('/api/execute', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ resolutionId: resolution.id,
+ selectedOptionRank: rank,
+ disruptionId,
+ }),
+ });
+
+ const data = await res.json().catch(() => ({}));
+
+ if (!res.ok) {
+ log(`Execute warning (${res.status}): ${data.error || 'Non-fatal'}`, 'warn');
+ } else {
+ log(`✓ Protocol ${rank} deployed — shipments rerouting`, 'success');
+ }
+ } catch (err) {
+ log(`Execute error: ${err.message}`, 'warn');
+ }
+
+ setApprovedRank(rank);
+ setIsApproving(false);
+ log('Generating executive incident report…', 'info');
+
+ setIsGeneratingReport(true);
+ try {
+ const reportRes = await fetch('/api/generate-report', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ disruption,
+ resolution,
+ options,
+ impactReport,
+ }),
+ });
+ const payload = await reportRes.json();
+ if (payload.report) {
+ setReportData(payload.report);
+ setReportReady(true);
+ setStage('report');
+ log('✓ Executive report ready — click Download to save PDF', 'success');
+ } else {
+ log(`Report gen warning: ${payload.error || 'No report text'}`, 'warn');
+ setStage('report');
+ }
+ } catch (err) {
+ log(`Report error: ${err.message}`, 'warn');
+ setStage('report');
+ } finally {
+ setIsGeneratingReport(false);
+ }
+ };
+
+ const handleDownload = async () => {
+ if (!reportData) return;
+ try {
+ const { generateReportPdf } = await import('../lib/generateReportPdf.js');
+ const doc = generateReportPdf({ reportText: reportData, disruption, traceId });
+ doc.save(`opentrade-incident-${traceId || Date.now()}.pdf`);
+ log('âś“ PDF downloaded', 'success');
+ } catch (err) {
+ log(`PDF error: ${err.message}`, 'error');
+ }
+ };
+
+ const handleReset = () => {
+ unsubsRef.current.forEach((u) => u?.());
+ unsubsRef.current = [];
+ setStage('idle');
+ setSelectedScenario(null);
+ setDisruptionId(null);
+ setTraceId(null);
+ setDisruption(null);
+ setImpactReport(null);
+ setResolution(null);
+ setOptions([]);
+ setApprovedRank(null);
+ setReportReady(false);
+ setReportData(null);
+ setLogs([]);
+ setError(null);
+ };
+
+ const currentStageIndex = STAGE_INDEX[stage] ?? 0;
+ const activeScenario = SCENARIOS.find((s) => s.id === selectedScenario);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ Live Pipeline Demo
+
+ {stage !== 'idle' && stage !== 'injected' &&
}
+
+
+ OpenTrade Command Demo
+
+
+ End-to-end pipeline: Disruption Injection → AI resolution → Human Decision → Protocol Deployment → PDF Report
+
+
+
+
+
+ {error && (
+
+ âš
+ Error: {error} — Check that all agents are running and env vars are set.
+
+ )}
+
+
+
+
+
+ {stage === 'idle' && (
+
+
+
+ Choose Scenario
+
+
+ {SCENARIOS.map((sc) => (
+
+ ))}
+
+
+
+
+
+ )}
+
+ {stage === 'injected' && (
+
+
+
+ Disruption published to Event Bus
+
+
+ Waiting for Monitor Agent to confirm detection…
+
+
+
+ )}
+
+ {stage === 'monitoring' && disruption && (
+
+
+
+
+ Monitor Agent — Disruption Confirmed
+
+
+
+ {disruption.title || activeScenario?.label || 'Disruption Detected'}
+
+
+ {disruption.summary || disruption.description || '—'}
+
+ {disruption.severity != null && (
+
+ )}
+
+ Running impact analysis across all active shipments…
+
+
+
+ )}
+
+ {stage === 'impact' && impactReport && (
+
+
+
+
+
+ Impact Analysis Complete
+
+
+
+
+
+
+
+
+ Resolution agent generating strategic options…
+
+
+
+
+ )}
+
+ {(stage === 'resolution' || stage === 'decision' || stage === 'applied' || stage === 'report') && options.length > 0 && (
+
+
+ {stage === 'decision' &&
}
+ {stage === 'resolution' &&
}
+
+ {stage === 'report'
+ ? '✓ Protocol Applied — Report Ready'
+ : stage === 'applied'
+ ? '⚡ Executing Protocol…'
+ : stage === 'decision'
+ ? 'Human Decision Required'
+ : 'AI Generated 3 Resolution Strategies'}
+
+
+
+ {impactReport && (
+
+
+
+
+ )}
+
+
+ {options.map((opt) => (
+
+ ))}
+
+
+ {stage === 'decision' && approvedRank === null && (
+
+ ↑ Select a protocol to deploy. Keyboard shortcuts: press{' '}
+
+ 1
+ {' '}
+
+ 2
+ {' '}
+
+ 3
+
+
+ )}
+
+ )}
+
+ {stage === 'report' && (
+
+
+
+ âś“ Pipeline Complete
+
+
+ Executive incident report generated. Download the PDF or run another scenario.
+
+
+
+ {reportReady && (
+
+ )}
+
+
+
+ )}
+
+
+
+
+ {(disruptionId || stage !== 'idle') && (
+
+
+ Session
+
+ {activeScenario && (
+
+
Scenario
+
+ {activeScenario.icon} {activeScenario.label}
+
+
+ )}
+ {disruptionId && (
+
+
Disruption ID
+
+ {disruptionId}
+
+
+ )}
+ {traceId && (
+
+
Trace ID
+
+ {traceId}
+
+
+ )}
+
+
Stage
+
+ {STAGES[currentStageIndex]?.icon} {STAGES[currentStageIndex]?.label}
+
+
+
+ )}
+
+
+
+
+ Agent Console
+
+ {stage !== 'idle' && stage !== 'report' &&
}
+
+
+ {logs.length === 0 ? (
+
+ Awaiting launch…
+
+ ) : (
+ logs.map((line, i) =>
)
+ )}
+
+
+
+
+ {stage !== 'idle' && stage !== 'report' && (
+
+ )}
+
+
+
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/dashboard/app/layout.js b/dashboard/app/layout.js
index 97edca0..e86960f 100644
--- a/dashboard/app/layout.js
+++ b/dashboard/app/layout.js
@@ -39,26 +39,7 @@ export default function RootLayout({ children }) {
return (
-
+
diff --git a/dashboard/next.config.mjs b/dashboard/next.config.mjs
index de50340..cd164bc 100644
--- a/dashboard/next.config.mjs
+++ b/dashboard/next.config.mjs
@@ -9,10 +9,10 @@ const require = createRequire(import.meta.url);
const cesiumRoot = dirname(require.resolve('cesium/package.json'));
const cspHeader = `
default-src 'self';
- script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cesium.com;
+ script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cesium.com https://vercel.live;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: blob: https://*.tile.openstreetmap.org https://ion.cesium.com https://dev.virtualearth.net https://*.virtualearth.net;
- connect-src 'self' https://*.supabase.co https://firestore.googleapis.com https://*.googleapis.com https://api.cesium.com https://ion.cesium.com https://assets.ion.cesium.com https://dev.virtualearth.net https://*.virtualearth.net https://*.tile.openstreetmap.org https://tile.openstreetmap.org https://aisstream.io wss://stream.aisstream.io https://*.onrender.com wss: ws: http://localhost:*;
+ connect-src 'self' https://*.supabase.co https://firestore.googleapis.com https://*.googleapis.com https://api.cesium.com https://ion.cesium.com https://assets.ion.cesium.com https://dev.virtualearth.net https://*.virtualearth.net https://*.tile.openstreetmap.org https://tile.openstreetmap.org https://aisstream.io wss://stream.aisstream.io https://*.onrender.com https://vercel.live wss: ws: http://localhost:*;
font-src 'self' https://fonts.gstatic.com;
worker-src 'self' blob:;
frame-src 'none';
@@ -66,4 +66,4 @@ const nextConfig = {
},
};
-export default nextConfig;
\ No newline at end of file
+export default nextConfig;
diff --git a/dashboard/public/theme-init.js b/dashboard/public/theme-init.js
new file mode 100644
index 0000000..be11b36
--- /dev/null
+++ b/dashboard/public/theme-init.js
@@ -0,0 +1,12 @@
+(function() {
+ try {
+ var t = localStorage.getItem('gdg_theme');
+ if (t === 'light' || t === 'dark') {
+ document.documentElement.setAttribute('data-theme', t);
+ } else {
+ document.documentElement.setAttribute('data-theme', 'dark');
+ }
+ } catch(e) {
+ document.documentElement.setAttribute('data-theme', 'dark');
+ }
+})();
diff --git a/disruption/package-lock.json b/disruption/package-lock.json
index 933f60c..96d2110 100644
--- a/disruption/package-lock.json
+++ b/disruption/package-lock.json
@@ -18,7 +18,9 @@
"eventsource": "^2.0.2",
"fastify": "^5.0.0",
"firebase-admin": "^12.0.0",
+ "playwright-core": "^1.40.0",
"puppeteer": "^21.0.0",
+ "resend": "^1.0.0",
"uuid": "^9.0.0",
"ws": "^8.17.1"
},
@@ -418,6 +420,102 @@
"node": ">=6"
}
},
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "license": "MIT"
+ },
+ "node_modules/@isaacs/cliui/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/@js-sdsl/ordered-map": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
@@ -441,6 +539,12 @@
"license": "MIT",
"optional": true
},
+ "node_modules/@one-ini/wasm": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
+ "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
+ "license": "MIT"
+ },
"node_modules/@opentelemetry/api": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
@@ -1879,6 +1983,16 @@
"integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
"license": "MIT"
},
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -1987,6 +2101,34 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"license": "MIT"
},
+ "node_modules/@react-email/render": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/@react-email/render/-/render-0.0.7.tgz",
+ "integrity": "sha512-hMMhxk6TpOcDC5qnKzXPVJoVGEwfm+U5bGOPH+MyTTlx0F02RLQygcATBKsbP7aI/mvkmBAZoFbgPIHop7ovug==",
+ "license": "MIT",
+ "dependencies": {
+ "html-to-text": "9.0.3",
+ "pretty": "2.0.0",
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@selderee/plugin-htmlparser2": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.10.0.tgz",
+ "integrity": "sha512-gW69MEamZ4wk1OsOq1nG1jcyhXIQcnrsX5JwixVw/9xaiav8TCyjESAruu1Rz9yyInhgBXxkNwMeygKnN2uxNA==",
+ "license": "MIT",
+ "dependencies": {
+ "domhandler": "^5.0.3",
+ "selderee": "^0.10.0"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/killymxi"
+ }
+ },
"node_modules/@supabase/auth-js": {
"version": "2.105.1",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.105.1.tgz",
@@ -2247,6 +2389,15 @@
"@types/node": "*"
}
},
+ "node_modules/abbrev": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
+ "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -2832,6 +2983,39 @@
"node": ">= 0.8"
}
},
+ "node_modules/commander": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/condense-newlines": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/condense-newlines/-/condense-newlines-0.2.1.tgz",
+ "integrity": "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==",
+ "license": "MIT",
+ "dependencies": {
+ "extend-shallow": "^2.0.1",
+ "is-whitespace": "^0.3.0",
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/config-chain": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
+ "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ini": "^1.3.4",
+ "proto-list": "~1.2.1"
+ }
+ },
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
@@ -2880,6 +3064,20 @@
"node-fetch": "^2.6.12"
}
},
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/css-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
@@ -2934,6 +3132,15 @@
}
}
},
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/degenerator": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
@@ -3068,6 +3275,12 @@
"stream-shift": "^1.0.2"
}
},
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "license": "MIT"
+ },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -3077,6 +3290,54 @@
"safe-buffer": "^5.0.1"
}
},
+ "node_modules/editorconfig": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz",
+ "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==",
+ "license": "MIT",
+ "dependencies": {
+ "@one-ini/wasm": "0.1.1",
+ "commander": "^10.0.0",
+ "minimatch": "^9.0.1",
+ "semver": "^7.5.3"
+ },
+ "bin": {
+ "editorconfig": "bin/editorconfig"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/editorconfig/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/editorconfig/node_modules/brace-expansion": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/editorconfig/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -3279,6 +3540,18 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
+ "node_modules/extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+ "license": "MIT",
+ "dependencies": {
+ "is-extendable": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
@@ -3594,6 +3867,22 @@
"uuid": "dist/bin/uuid"
}
},
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/form-data": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
@@ -3771,6 +4060,27 @@
"node": ">= 14"
}
},
+ "node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -3784,6 +4094,36 @@
"node": ">= 6"
}
},
+ "node_modules/glob/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/google-auth-library": {
"version": "9.15.1",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
@@ -3932,6 +4272,41 @@
"license": "MIT",
"optional": true
},
+ "node_modules/html-to-text": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.3.tgz",
+ "integrity": "sha512-hxDF1kVCF2uw4VUJ3vr2doc91pXf2D5ngKcNviSitNkhP9OMOaJkDrFIFL6RMvko7NisWTEiqGpQ9LAxcVok1w==",
+ "license": "MIT",
+ "dependencies": {
+ "@selderee/plugin-htmlparser2": "^0.10.0",
+ "deepmerge": "^4.2.2",
+ "dom-serializer": "^2.0.0",
+ "htmlparser2": "^8.0.1",
+ "selderee": "^0.10.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/html-to-text/node_modules/htmlparser2": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
+ "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1",
+ "entities": "^4.4.0"
+ }
+ },
"node_modules/htmlparser2": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
@@ -4081,6 +4456,12 @@
"license": "ISC",
"optional": true
},
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "license": "ISC"
+ },
"node_modules/ip-address": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
@@ -4118,6 +4499,21 @@
"node": ">=8"
}
},
+ "node_modules/is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "license": "MIT"
+ },
+ "node_modules/is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -4173,6 +4569,36 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-whitespace": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/is-whitespace/-/is-whitespace-0.3.0.tgz",
+ "integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
"node_modules/jose": {
"version": "4.15.9",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
@@ -4182,6 +4608,36 @@
"url": "https://github.com/sponsors/panva"
}
},
+ "node_modules/js-beautify": {
+ "version": "1.15.4",
+ "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz",
+ "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==",
+ "license": "MIT",
+ "dependencies": {
+ "config-chain": "^1.1.13",
+ "editorconfig": "^1.0.4",
+ "glob": "^10.4.2",
+ "js-cookie": "^3.0.5",
+ "nopt": "^7.2.1"
+ },
+ "bin": {
+ "css-beautify": "js/bin/css-beautify.js",
+ "html-beautify": "js/bin/html-beautify.js",
+ "js-beautify": "js/bin/js-beautify.js"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/js-cookie": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -4299,6 +4755,27 @@
"safe-buffer": "^5.0.1"
}
},
+ "node_modules/kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/leac": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
+ "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://ko-fi.com/killymxi"
+ }
+ },
"node_modules/light-my-request": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz",
@@ -4407,6 +4884,18 @@
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -4491,6 +4980,15 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
@@ -4602,6 +5100,21 @@
"url": "https://opencollective.com/nodemon"
}
},
+ "node_modules/nopt": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
+ "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
+ "license": "ISC",
+ "dependencies": {
+ "abbrev": "^2.0.0"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -4700,6 +5213,12 @@
"node": ">= 14"
}
},
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "license": "BlueOak-1.0.0"
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -4779,6 +5298,19 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/parseley": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.11.0.tgz",
+ "integrity": "sha512-VfcwXlBWgTF+unPcr7yu3HSSA6QUdDaDnrHcytVfj5Z8azAyKBDrYnSIfeSxlrEayndNcLmrXzg+Vxbo6DWRXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "leac": "^0.6.0",
+ "peberminta": "^0.8.0"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/killymxi"
+ }
+ },
"node_modules/path-expression-matcher": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz",
@@ -4795,6 +5327,46 @@
"node": ">=14.0.0"
}
},
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
+ "node_modules/peberminta": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.8.0.tgz",
+ "integrity": "sha512-YYEs+eauIjDH5nUEGi18EohWE0nV2QbGTqmxQcqgZ/0g+laPCQmuIqq7EBLVi9uim9zMgfJv0QBZEnQ3uHw/Tw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://ko-fi.com/killymxi"
+ }
+ },
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -4888,6 +5460,18 @@
"integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
"license": "MIT"
},
+ "node_modules/playwright-core": {
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
+ "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
@@ -4927,6 +5511,20 @@
"node": ">=0.10.0"
}
},
+ "node_modules/pretty": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pretty/-/pretty-2.0.0.tgz",
+ "integrity": "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==",
+ "license": "MIT",
+ "dependencies": {
+ "condense-newlines": "^0.2.1",
+ "extend-shallow": "^2.0.1",
+ "js-beautify": "^1.6.12"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/process-warning": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
@@ -4952,6 +5550,12 @@
"node": ">=0.4.0"
}
},
+ "node_modules/proto-list": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
+ "license": "ISC"
+ },
"node_modules/proto3-json-serializer": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz",
@@ -5126,6 +5730,31 @@
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
"license": "MIT"
},
+ "node_modules/react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -5194,6 +5823,19 @@
"node": ">=9.3.0 || >=8.10.0 <9.0.0"
}
},
+ "node_modules/resend": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/resend/-/resend-1.1.0.tgz",
+ "integrity": "sha512-it8TIDVT+/gAiJsUlv2tdHuvzwCCv4Zwu+udDqIm/dIuByQwe68TtFDcPccxqpSVVrNCBxxXLzsdT1tsV+P3GA==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-email/render": "0.0.7",
+ "type-fest": "3.13.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -5310,6 +5952,15 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
"node_modules/secure-json-parse": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
@@ -5326,6 +5977,18 @@
],
"license": "BSD-3-Clause"
},
+ "node_modules/selderee": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.10.0.tgz",
+ "integrity": "sha512-DEL/RW/f4qLw/NrVg97xKaEBC8IpzIG2fvxnzCp3Z4yk4jQ3MXom+Imav9wApjxX2dfS3eW7x0DXafJr85i39A==",
+ "license": "MIT",
+ "dependencies": {
+ "parseley": "^0.11.0"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/killymxi"
+ }
+ },
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
@@ -5344,6 +6007,39 @@
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -5475,6 +6171,21 @@
"node": ">=8"
}
},
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -5487,6 +6198,19 @@
"node": ">=8"
}
},
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strnum": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz",
@@ -5682,6 +6406,18 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/type-fest": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.0.tgz",
+ "integrity": "sha512-Gur3yQGM9qiLNs0KPP7LPgeRbio2QTt4xXouobMCarR0/wyW3F+F/+OWwshg3NG0Adon7uQfSZBpB46NfhoF1A==",
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/unbzip2-stream": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
@@ -5810,6 +6546,21 @@
"webidl-conversions": "^3.0.0"
}
},
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -5827,6 +6578,24 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/news-intel/package.json b/news-intel/package.json
index 752c188..1a17438 100644
--- a/news-intel/package.json
+++ b/news-intel/package.json
@@ -9,6 +9,7 @@
},
"dependencies": {
"@fastify/cors": "^11.2.0",
+ "@supabase/supabase-js": "^2.45.0",
"@opentelemetry/auto-instrumentations-node": "^0.73.0",
"@opentelemetry/sdk-node": "^0.215.0",
"@supabase/supabase-js": "^2.105.1",