diff --git a/apis/briefing.mjs b/apis/briefing.mjs index 64a110c..4b916ba 100644 --- a/apis/briefing.mjs +++ b/apis/briefing.mjs @@ -37,7 +37,10 @@ import { briefing as reddit } from './sources/reddit.mjs'; import { briefing as telegram } from './sources/telegram.mjs'; import { briefing as kiwisdr } from './sources/kiwisdr.mjs'; -// === Tier 4: Live Market Data === +// === Tier 4: Space & Satellites === +import { briefing as space } from './sources/space.mjs'; + +// === Tier 5: Live Market Data === import { briefing as yfinance } from './sources/yfinance.mjs'; export async function runSource(name, fn, ...args) { @@ -51,7 +54,7 @@ export async function runSource(name, fn, ...args) { } export async function fullBriefing() { - console.error('[Crucix] Starting intelligence sweep — 26 sources...'); + console.error('[Crucix] Starting intelligence sweep — 27 sources...'); const start = Date.now(); const results = await Promise.allSettled([ @@ -86,7 +89,10 @@ export async function fullBriefing() { runSource('Telegram', telegram), runSource('KiwiSDR', kiwisdr), - // Tier 4: Live Market Data + // Tier 4: Space & Satellites + runSource('Space', space), + + // Tier 5: Live Market Data runSource('YFinance', yfinance), ]); diff --git a/apis/sources/space.mjs b/apis/sources/space.mjs new file mode 100644 index 0000000..33781cb --- /dev/null +++ b/apis/sources/space.mjs @@ -0,0 +1,190 @@ +// Space/CelesTrak — Satellite Activity Monitoring +// No API key required. Uses CelesTrak for public TLE data and launch info. +// Tracks: Recent launches, ISS position, satellite decay alerts, space debris. + +import { safeFetch } from '../utils/fetch.mjs'; + +const CELESTRAK_BASE = 'https://celestrak.org'; + +// Satellite categories for monitoring +const SAT_CATEGORIES = { + stations: '/NORAD/elements/gp.php?GROUP=stations&FORMAT=json', + lastDay: '/NORAD/elements/gp.php?GROUP=last-30-days&FORMAT=json', + military: '/NORAD/elements/gp.php?GROUP=military&FORMAT=json', + gps: '/NORAD/elements/gp.php?GROUP=gps-ops&FORMAT=json', + starlink: '/NORAD/elements/gp.php?GROUP=starlink&FORMAT=json', + oneweb: '/NORAD/elements/gp.php?GROUP=oneweb&FORMAT=json', +}; + +// Get TLE data for a category +async function getTLEs(category) { + const path = SAT_CATEGORIES[category]; + if (!path) return { error: 'Invalid category' }; + const data = await safeFetch(`${CELESTRAK_BASE}${path}`, { timeout: 20000 }); + return data; +} + +// Get recent launches (from last 30 days TLEs) +async function getRecentLaunches() { + const data = await getTLEs('lastDay'); + if (data.error || !Array.isArray(data)) { + return { error: data.error || 'Failed to fetch launch data' }; + } + + const launches = data.map(sat => ({ + name: sat.OBJECT_NAME, + noradId: sat.NORAD_CAT_ID, + classification: sat.CLASSIFICATION_TYPE, + launchDate: sat.LAUNCH_DATE, + decayDate: sat.DECAY_DATE, + period: sat.PERIOD, + inclination: sat.INCLINATION, + apogee: sat.APOAPSIS, + perigee: sat.PERIAPSIS, + epoch: sat.EPOCH, + country: sat.COUNTRY_CODE, + objectType: sat.OBJECT_TYPE, + })).filter(s => s.name && s.noradId); + + launches.sort((a, b) => new Date(b.epoch || 0) - new Date(a.epoch || 0)); + + const byCountry = {}; + launches.forEach(l => { + const country = l.country || 'UNK'; + byCountry[country] = (byCountry[country] || 0) + 1; + }); + + return { totalObjects: launches.length, recentLaunches: launches.slice(0, 25), byCountry }; +} + +// Get space station data +async function getStationData() { + const data = await getTLEs('stations'); + if (data.error || !Array.isArray(data)) { + return { error: data.error || 'Failed to fetch station data' }; + } + + const stations = data.map(sat => ({ + name: sat.OBJECT_NAME, + noradId: sat.NORAD_CAT_ID, + apogee: sat.APOAPSIS, + perigee: sat.PERIAPSIS, + inclination: sat.INCLINATION, + period: sat.PERIOD, + epoch: sat.EPOCH, + })).filter(s => s.name); + + const iss = stations.find(s => s.name.includes('ISS') || s.noradId === 25544); + + return { totalStations: stations.length, stations: stations.slice(0, 10), iss }; +} + +// Get military satellite count +async function getMilitaryCount() { + const data = await getTLEs('military'); + if (data.error || !Array.isArray(data)) { + return { count: 0, error: data.error }; + } + + const byCountry = {}; + data.forEach(sat => { + const country = sat.COUNTRY_CODE || 'UNK'; + byCountry[country] = (byCountry[country] || 0) + 1; + }); + + return { count: data.length, byCountry }; +} + +// Get mega-constellation stats (Starlink, OneWeb) +async function getConstellationStats() { + const [starlink, oneweb] = await Promise.all([ + getTLEs('starlink'), + getTLEs('oneweb'), + ]); + + return { + starlink: Array.isArray(starlink) ? starlink.length : 0, + oneweb: Array.isArray(oneweb) ? oneweb.length : 0, + }; +} + +// Generate signals +function generateSignals(data) { + const signals = []; + + if (data.launches?.totalObjects > 50) { + signals.push(`HIGH LAUNCH TEMPO: ${data.launches.totalObjects} new objects tracked in last 30 days`); + } + + const byCountry = data.launches?.byCountry || {}; + const cnLaunches = byCountry['PRC'] || byCountry['CN'] || 0; + const ruLaunches = byCountry['CIS'] || byCountry['RU'] || 0; + + if (cnLaunches > 10) { + signals.push(`CHINA SPACE ACTIVITY: ${cnLaunches} objects launched recently`); + } + if (ruLaunches > 5) { + signals.push(`RUSSIA SPACE ACTIVITY: ${ruLaunches} objects launched recently`); + } + if (data.military?.count > 500) { + signals.push(`MILITARY CONSTELLATION: ${data.military.count} tracked military satellites`); + } + if (data.constellations?.starlink > 6000) { + signals.push(`STARLINK MEGA-CONSTELLATION: ${data.constellations.starlink} active satellites`); + } + + return signals; +} + +// Briefing export +export async function briefing() { + try { + const [launches, stations, military, constellations] = await Promise.all([ + getRecentLaunches(), + getStationData(), + getMilitaryCount(), + getConstellationStats(), + ]); + + const hasData = !launches.error || !stations.error; + + if (!hasData) { + return { + source: 'Space/CelesTrak', + timestamp: new Date().toISOString(), + status: 'error', + error: launches.error || stations.error || 'Failed to fetch space data', + }; + } + + const data = { launches, stations, military, constellations }; + const signals = generateSignals(data); + + return { + source: 'Space/CelesTrak', + timestamp: new Date().toISOString(), + status: 'active', + recentLaunches: launches.recentLaunches || [], + totalNewObjects: launches.totalObjects || 0, + launchByCountry: launches.byCountry || {}, + spaceStations: stations.stations || [], + iss: stations.iss || null, + militarySatellites: military.count || 0, + militaryByCountry: military.byCountry || {}, + constellations: constellations || {}, + signals, + }; + } catch (e) { + return { + source: 'Space/CelesTrak', + timestamp: new Date().toISOString(), + status: 'error', + error: e.message, + }; + } +} + +if (process.argv[1]?.endsWith('space.mjs')) { + const data = await briefing(); + console.log(JSON.stringify(data, null, 2)); +} diff --git a/dashboard/inject.mjs b/dashboard/inject.mjs index b573c13..15e6b44 100644 --- a/dashboard/inject.mjs +++ b/dashboard/inject.mjs @@ -319,6 +319,22 @@ export async function synthesize(data) { })); const noaa = { totalAlerts: data.sources.NOAA?.totalSevereAlerts || 0 }; + // Space/CelesTrak satellite data + const spaceData = data.sources.Space || {}; + const space = { + totalNewObjects: spaceData.totalNewObjects || 0, + militarySats: spaceData.militarySatellites || 0, + militaryByCountry: spaceData.militaryByCountry || {}, + constellations: spaceData.constellations || {}, + iss: spaceData.iss || null, + recentLaunches: (spaceData.recentLaunches || []).slice(0, 10).map(l => ({ + name: l.name, country: l.country, epoch: l.epoch, + apogee: l.apogee, perigee: l.perigee, type: l.objectType + })), + launchByCountry: spaceData.launchByCountry || {}, + signals: spaceData.signals || [], + }; + // ACLED conflict events const acledData = data.sources.ACLED || {}; const acled = acledData.error ? { totalEvents: 0, totalFatalities: 0, byRegion: {}, byType: {}, deadliestEvents: [] } : { @@ -391,7 +407,7 @@ export async function synthesize(data) { meta: data.crucix, air, thermal, tSignals, chokepoints, nuke, nukeSignals, sdr: { total: sdrNet.totalReceivers || 0, online: sdrNet.online || 0, zones: sdrZones }, tg: { posts: tgData.totalPosts || 0, urgent: tgUrgent, topPosts: tgTop }, - who, fred, energy, bls, treasury, gscpi, defense, noaa, acled, gdelt, health, news, + who, fred, energy, bls, treasury, gscpi, defense, noaa, acled, gdelt, space, health, news, markets, // Live Yahoo Finance market data ideas: [], ideasSource: 'disabled', // newsFeed for ticker (merged RSS + GDELT + Telegram) diff --git a/dashboard/public/jarvis.html b/dashboard/public/jarvis.html index 59e2500..2a9bfeb 100644 --- a/dashboard/public/jarvis.html +++ b/dashboard/public/jarvis.html @@ -79,6 +79,7 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s .ldot.maritime{background:#b388ff;box-shadow:0 0 6px rgba(179,136,255,0.4)} .ldot.health{background:#69f0ae;box-shadow:0 0 6px rgba(105,240,174,0.4)} .ldot.news{background:#81d4fa;box-shadow:0 0 6px rgba(129,212,250,0.4)} +.ldot.space{background:#e0b0ff;box-shadow:0 0 6px rgba(224,176,255,0.4)} .layer-name{font-size:12px;font-weight:500} .layer-sub{font-size:10px;color:var(--dim)} .layer-count{font-family:var(--mono);font-size:13px;font-weight:600;color:var(--accent)} @@ -328,7 +329,8 @@ function renderLeftRail(){ {name:'Conflict Events',count:conflictEvents,dot:'thermal',sub:`${conflictFatal.toLocaleString()} fatalities`}, {name:'Health Watch',count:D.who.length,dot:'health',sub:'WHO alerts'}, {name:'World News',count:newsCount,dot:'news',sub:'RSS geolocated'}, - {name:'OSINT Feed',count:D.tg.posts,dot:'incident',sub:`${D.tg.urgent.length} urgent`} + {name:'OSINT Feed',count:D.tg.posts,dot:'incident',sub:`${D.tg.urgent.length} urgent`}, + {name:'Satellites',count:D.space?.militarySats||0,dot:'space',sub:`${D.space?.totalNewObjects||0} new (30d)`} ]; const allNormal=D.nuke.every(s=>!s.anom); const nukeHtml=D.nuke.map(s=>`
${s.site}${s.n>0?(s.cpm?.toFixed(1)||'--')+' CPM':'No data'}
`).join(''); @@ -358,6 +360,18 @@ function renderLeftRail(){
30Y Mortgage${mort?.value||'--'}%
M2 Supply$${(m2?.value/1000)?.toFixed(1)||'--'}T
Nat. Debt$${(parseFloat(D.treasury.totalDebt)/1e12).toFixed(2)}T
+ +
+

Space Watch

CELESTRAK
+ ${D.space ? ` +
New Objects (30d)${D.space.totalNewObjects||0}
+
Military Sats${D.space.militarySats||0}
+
Starlink${D.space.constellations?.starlink||0}
+
OneWeb${D.space.constellations?.oneweb||0}
+ ${D.space.iss ? `
ISSALT ${((D.space.iss.apogee+D.space.iss.perigee)/2).toFixed(0)} km
` : ''} + ${Object.entries(D.space.militaryByCountry||{}).sort((a,b)=>b[1]-a[1]).slice(0,4).map(([c,n])=>`
${c}${n} mil sats
`).join('')} + ${(D.space.signals||[]).length ? `
${D.space.signals.slice(0,2).join('
')}
` : ''} + ` : '
NO SPACE DATA
'}
`; }