Your own intelligence terminal. 27 sources. One command. Zero cloud.
Enter The Signal Network
Live website: https://www.crucix.live/ Explore the public demo first, then clone the repo to run Crucix locally.
Crucix pulls satellite fire detection, flight tracking, radiation monitoring, satellite constellation tracking, economic indicators, live market prices, conflict data, sanctions lists, and social sentiment from 27 open-source intelligence feeds — in parallel, every 15 minutes — and renders everything on a single self-contained Jarvis-style dashboard.
Hook it up to an LLM and it becomes a two-way intelligence assistant — pushing multi-tier alerts to Telegram and Discord when something meaningful changes, responding to commands like /brief and /sweep from your phone, and generating actionable trade ideas grounded in real cross-domain data. Your own analyst that watches the world while you sleep.
Try the live demo first at https://www.crucix.live/, then clone the repo when you want the full local stack.
No cloud. No telemetry. No subscriptions. Just node server.mjs and you're running.
Warning
Crucix has not launched any official token, coin, NFT, airdrop, presale, or other blockchain-based asset. Any token or digital asset using the Crucix name, logo, or branding is not affiliated with or endorsed by Crucix. Do not buy it, promote it, connect a wallet to claim it, sign transactions, or send funds based on third-party posts, DMs, or websites.
Most of the world's real-time intelligence — satellite imagery, radiation levels, conflict events, economic indicators, flight tracking, maritime activity — is publicly available. It's just scattered across dozens of government APIs, research institutions, and open data feeds that nobody has time to check individually.
Crucix brings it all into one place. Not behind a paywall, not locked in an enterprise platform, not requiring a security clearance. Just open data, aggregated and cross-correlated on your own machine, updated every 15 minutes.
It was built for anyone who wants to understand what's actually happening in the world right now — researchers, journalists, traders, OSINT analysts, or just curious people who believe access to information shouldn't depend on your budget.
# 1. Clone the repo
git clone https://github.com/calesthio/Crucix.git
cd Crucix
# 2. Install dependencies (just Express)
npm install
# 3. Copy env template and add your API keys (see below)
cp .env.example .env
# 4. Start the dashboard
npm run devIf
npm run devfails silently (exits with no output), run Node directly instead:node --trace-warnings server.mjsThis bypasses npm's script runner, which can swallow errors on some systems (particularly PowerShell on Windows). You can also run
node diag.mjsto diagnose the exact issue — it checks your Node version, tests each module import individually, and verifies port availability. See Troubleshooting for more.
The dashboard opens automatically at http://localhost:3117 and immediately begins its first intelligence sweep. This initial sweep queries all 27 sources in parallel and typically takes 30–60 seconds — the dashboard will appear empty until the sweep completes and pushes the first data update. After that, it auto-refreshes every 15 minutes via SSE (Server-Sent Events). No manual page refresh needed.
Requirements: Node.js 22+ (uses native fetch, top-level await, ESM)
git clone https://github.com/calesthio/Crucix.git
cd Crucix
cp .env.example .env # add your API keys
docker compose up -dDashboard at http://localhost:3117. Sweep data persists in ./runs/ via volume mount. Includes a health check endpoint.
Compose starts two services: the Node dashboard (crucix) and the Python Border Watch ingestion service (ingest, see below). The ingestion API is only reachable from the dashboard container; it is not published to the host.
fly launch --copy-config --no-deploy # first time only; creates the app from fly.toml
fly secrets set CRUCIX_PASSWORD='your-access-code' CRUCIX_SESSION_SECRET=$(openssl rand -hex 32)
fly deploySetting CRUCIX_PASSWORD puts a login page in front of the dashboard and all /api/* routes (except /api/health). Failed attempts are rate-limited (5 per IP, 15-minute lockout). Leave it unset for local use. Anyone without the access code sees only the login page, so a deployment can stay private to whoever holds the code.
The Investigations tab is a dedicated OSINT workbench: enter any selector — domain, URL, IPv4/IPv6, MD5/SHA-1/SHA-256 hash, email, @handle, +phone, BTC/ETH address, or company name — and CRUCIX fans out to every relevant passive source in parallel and renders a dossier with rule-based risk indicators. Every blue value pivots into a new dossier without losing context; pivots accumulate into a browser-local case file (entities, relationships, notes) with a force-directed case graph, a cross-source timeline, a toolkit of type-specific search dorks and external deep links, EXIF/GPS metadata forensics for uploaded images (parsed in memory, never written to disk), an operator-initiated photo geolocation estimate for images with no GPS fix (a vision-capable LLM_PROVIDER reads visible cues — signage, plates, architecture, terrain — and returns a country / Mexican state / city, a coordinate with an uncertainty radius, confidence and the clues; Mexican places are snapped to the gazetteer or dropped, and the result is labelled MODEL ASSESSMENT — verify and can be pinned on the Cartels map as a dashed uncertainty circle, never persisted server-side; without a vision-capable model the card says so), and Markdown / JSON / SVG report export. All lookups are read-only; /api/investigate* is rate-limited per IP and URL analysis refuses private, loopback and link-local destinations.
| Source | Key needed | Returns |
|---|---|---|
RDAP WHOIS (rdap.org) |
none | registrar, registrant, dates, nameservers, DNSSEC, IP allocation/org |
| DNS-over-HTTPS (Cloudflare) | none | A/AAAA/MX/NS/TXT, SPF, DMARC, reverse DNS |
Certificate Transparency (crt.sh) |
none | hostnames seen in certificates, recent issuers |
| Shodan InternetDB | none | open ports, CVEs, hostnames, tags per IP |
| ipwho.is · Tor exit list | none | geolocation, ASN, Tor exit-node status |
| Wayback Machine · AlienVault OTX · urlscan.io | none | archive history, threat pulses, public scans |
| HTTP fingerprint | none | status, server, title, redirect chain, phishing heuristics for URLs |
| Gravatar · Keybase · GitHub | none (GITHUB_TOKEN optional) |
identity claims, avatar hash, public repos / commit-email leaks |
| Platform probes (API-verified) | none | handle presence on major platforms; generic 200s are never treated as a hit |
| mempool.space · BlockCypher · OFAC SDN | none | BTC/ETH balance, tx counts, counterparties, sanctions-list match |
| Look-alike probe | none | registered typosquat permutations of the target |
| VirusTotal | VIRUSTOTAL_API_KEY |
AV verdicts, reputation, threat label for domain/IP/hash |
| Shodan | SHODAN_API_KEY |
org, ASN, services/banners, full vuln list |
| Have I Been Pwned | HIBP_API_KEY |
breach names, dates, exposed data classes per email |
| NumVerify | NUMVERIFY_API_KEY |
carrier, line type, location for phone numbers |
| OpenCorporates | OPENCORPORATES_API_TOKEN |
company matches, jurisdiction, status, address |
| OpenSanctions | OPENSANCTIONS_API_KEY |
sanctions / PEP screening for wallets and entities |
Keyed sources are skipped (marked "no key" in the panel) when their variable is blank. Results are cached for 15 minutes.
Typosquat Watch runs in the sweep: for each domain in TYPOSQUAT_WATCHLIST (default: treasury.gov,irs.gov,cisa.gov,defense.gov,login.gov) it generates DNS-Twist-style permutations (homoglyph, omission, transposition, TLD swap, hyphenation, keyword addition, …), resolves them over DoH, and lists the registered ones, flagging any that are new since the previous sweep. Set the variable to an empty string to disable.
Keyless, registry-driven regional news collection. Outlets live in config/border-sources.json with outlet, feed URL, feed type (rss, atom or news-sitemap), language, region, discovery date, and a reliability grade (ungraded until reviewed). Each sweep polls the feeds with If-None-Match/If-Modified-Since (a 304 is a healthy "unchanged" poll), normalizes items with a content hash, pipeline version, and provenance, then fetches a bounded number of article bodies per feed via the public WordPress REST API or the article page — after a robots.txt check, with the descriptive CRUCIX User-Agent, never bypassing paywalls (paywalled or blocked articles keep their feed-level record and are flagged). Rule-based bilingual (EN/ES) topic tags (violence, narcotics, enforcement, migration, rail, trade, governance) and a border-sector gazetteer (wire datelines are ignored for place tagging) feed a per-place/topic spike detector that stays silent until at least 3 days of baseline exist.
Registered outlets: Border Report, The Texas Tribune, ValleyCentral (Rio Grande Valley), Zeta Tijuana (ES), Borderland Beat (Atom; full text in feed so article pages are never fetched), Justice in Mexico, InSight Crime's Mexico tag, El Paso Matters (WordPress REST for full text), Fronteras Desk (KJZZ public radio; summaries-only feed), and Milenio (ES; Google News sitemap advertised in its robots.txt — no RSS exists; section-filtered and place-gated; article pages never fetched). Per-source policy fields:
feedType—rss(default),atom, ornews-sitemap(Google News<urlset>withnews:newsblocks).fetchArticles: false— never request article pages/APIs for this outlet (used when the publisher's terms restrict automated access beyond the feed, or when the feed already carries full text).pathPrefixes— keep only items whose URL path starts with one of these sections (e.g. Milenio/policia,/estados).requirePlaceTag: true— drop items that do not mention a border-sector place (keeps national outlets on-topic).BORDER_FETCH_ARTICLES(defaulttrue) — setfalsefor headlines/descriptions only, globally.BORDER_MAX_ARTICLE_FETCH(default5, max20) — article bodies fetched per feed per sweep; the backlog drains on later sweeps.GET /api/border/articles?place=el-paso-tx&topic=enforcement&outlet=borderreport&days=7&limit=50— filters are whitelisted keys; anything else is a 400.
Panel states are reported per feed (LIVE, UNCHANGED, EMPTY, BLOCKED, ERROR) and per article (PAYWALL, WIRE, FEED-ONLY). Runtime state is kept under runs/border/.
The CARTELS & BORDER tab (#cartels; old #regional links redirect here) is the single page for Mexico cartel activity and the US side of the same corridor. Its lower grid is organised in four bands, each with a header showing which sources feed it and how many are live:
| Band | Panels | Fed by |
|---|---|---|
| Mexico · Cartel Landscape | Cartel Influence map (with optional CBP SW sectors layer), Graded Events, Active Wars & Truces, Recent Map Entries, START 2020 Baseline, Mexico / Northern Triangle feed |
Cartels KML, BorderNews, DOJ (see below) |
| US Border · Official CBP Data | CBP Encounters & Drug Seizures, CBP Seizures (AMO · currency · weapons), CBP Officer Safety & Use of Force, CBP Custody & Enforcement | the four CBP* adapters (see Border / CBP) |
| US–Mexico Border Reporting | Border Watch, Narco & Organized Crime (InSight Crime), Border Ingest | BorderNews, InSightCrime, BorderIngest |
| US Enforcement · DOJ & OFAC | DOJ Prosecutions, OFAC Sanctions | DOJ, OFACNarco |
The left rail keeps the narco / cartel source cards and the organisation list, plus the Sensor Grid (Border Watch, Border / CBP, Narco Intel rows) and Source Health. The tab badge combines the graded-event count with the Border Watch spike count (41 EVENTS · 10 SPIKES, red while spikes > 0), and Situation-strip Border Watch headlines open the Border Watch panel here.
The map is a Mexico-framed page that puts two deliberately separate layers side by side:
| Layer | Source | Era | Drawn as |
|---|---|---|---|
| Current influence areas, activity/crime pins, active wars, truces, alleged alliances, strongholds, alleged safehouses, government/military ops, activity in the U.S. | Active Cartels In Mexico Google My Maps KML (@MexicoCartelMap) — crowd-sourced, single maintainer | polled each sweep, 24 h cache | solid lines, maintainer's colour legend |
| Cartel density by state, CJNG footprint, trafficking flows, ports / points of entry, narcotics-concentration cities, avocado & huachicol hotspots, CJNG 2009–2019 timeline | START (University of Maryland) Tracking Cartels research briefs, transcribed by hand into config/cartels-baseline-2020.json |
June 2020, static | amber, dashed, labelled START June 2020 |
Neither layer is verified control of territory: the KML's own disclaimer says it is not 100% accurate and can go out of date quickly, and the START baseline is a point-in-time research product (two state classes were read visually from the printed choropleth and carry a note saying so). The Situation strip only ever emits an info pointer from the current layer, and only when the KML fetched live, is not a cached copy, and has dated entries within the last 7 days; the START data never generates alerts. A third panel lists Mexico / Northern Triangle items already collected by InSight Crime, Border Watch and GDELT (keyword filter — journalism, not event data).
Every KML string (names, descriptions, folder names, URLs, dates) is bounded and stripped of markup at ingestion and HTML-escaped again at render; only http(s) links survive. The trimmed summary is injected into the dashboard payload; polygon/point geometry (~400 KB) is served separately from GET /api/cartels/geo and fetched by the browser only when the tab is opened.
A third, default-off map layer — CBP SW sectors — draws the nine Southwest Border Patrol sectors (coordinates from apis/sources/cbpcommon.mjs, carried in the CBPStats projection) as circles sized by the latest month's CBP encounters, with MoM / YoY change in the popup. The layer only appears when the CBPStats encounters dataset was served this sweep (live, partial or cached-stale, labelled as such); otherwise its chip is disabled with the reason, and no zero is ever plotted.
The Cartels & Border tab also carries a normalized event layer built after every sweep from the public sources above (lib/narco/). Each Border Watch article that passes an organised-crime + Mexico relevance gate, and each DOJ release from the watched districts, becomes one narco-event/1 record: publication date, cartel(s) and faction(s) (config/cartel-groups.json, alias-matched with group names masked before geocoding), people (rule-based name extraction; person names are masked before place lookup so surnames like De Leon do not become León, Guanajuato), Mexican state / municipality / city with coordinates (config/mx-gazetteer.json, built from GeoNames by scripts/build-mx-gazetteer.mjs), event type (16-type taxonomy, headline first), casualties / arrests / seizures (weapons, drugs with unit conversion, cash, vehicles), and the outlet's own cited sources. Records describing the same incident (same type family, place, ±3 days) are clustered, and each cluster gets a corroboration grade — A: official source or 3+ independent outlets · B: two outlets · C: one established outlet · D: one citizen aggregator · E: undated or unlocated. Grades measure corroboration, not severity. Optional LLM gap-filling (NARCO_LLM_EXTRACT=true) only fills fields the rules left empty and is validated against the same schema; it may refine a state-level location to a gazetteer city inside that state and add a colonia / highway / landmark locality plus up to two location evidence sentences, all of which must appear verbatim in the article or are discarded.
- DOJ watcher (
apis/sources/doj.mjs) — polls the public DOJ press-release API for the Southern District of California, District of Arizona, District of New Mexico, Western District of Texas and Southern District of Texas, classifies each release (cartel, smugglers, trafficking organisation, weapons, money laundering, human smuggling, tunnel, violent organisation) and anchors it to the district seat when no Mexican place is named. Releases matched only as generic smuggling (export controls, pesticides) stay in the DOJ panel without becoming narco events. - OFAC narco index (
apis/sources/ofacnarco.mjs) — downloads the SDN XML at most everyOFAC_NARCO_REFRESH_HOURS, indexes the SDNTK / SDNT / ILLICIT-DRUGS-EO14059 / TCO programs (plus FTO/SDGT cartel designations) and cross-matches event people and groups conservatively (all query tokens, or 3+ shared tokens for a partial match). - Commercial slots (
apis/sources/commercialnarco.mjs) — DataInt and Lantia Intelligence appear in Source Health asNO KEYuntil*_API_KEYand*_API_URLare provided by the vendor; their dashboards are never scraped and a key without an endpoint staysnot configured, neverlive. GET /api/narco(view model),GET /api/narco/events?grade=A&type=seizure&cartel=cjng&state=Jalisco&days=30&limit=50,GET /api/narco/events/:id,GET /api/narco/doj?district=TXWD&category=tunnel,GET /api/narco/sanctions— whitelisted filters; anything else is a 400.
Events from the last 30 days draw as pins on the Cartels map (fill = event family, ring = confidence grade); 31–90-day events are a separate dashed layer. Runtime state lives under runs/narco/, runs/doj/ and runs/ofacnarco/.
Below the border-reporting group on the Cartels & Border tab sits a CJNG-only knowledge graph machine-read from InSight Crime's public archive (lib/cjng/). It is deliberately scoped to one organisation: the corpus is every post tagged Jalisco Cartel (tag 676) or El Mencho (tag 3426) plus every post whose full text matches CJNG, pulled from the public WordPress REST API (insightcrime.org/wp-json/wp/v2/posts, no key, no HTML scraping, robots.txt honoured, one request every ~1.2 s with bounded back-off on 429/5xx). Posts are de-duplicated by WordPress id and kept in the graph only when InSight Crime tagged them or the text names the group in the title / repeatedly in the body — passing mentions stay in the corpus file but out of the graph.
- Nodes — the CJNG root, its factions and the cartels named alongside it (
config/cartel-groups.json), people (rule-based name extraction with alias / nickname / spelling-variant merging; presidents, governors, prosecutors, journalists and other officials are never typed as members), Mexican states and cities (config/mx-gazetteer.json), countries and InSight Crime topic tags. - Edges —
leader_of,member_of,family_of,rival_of,allied_with,lineage(splinter / offshoot),operates_in,linked_topicand plainmentioned_with. A typed edge needs a cue word ("led by", "rival", "split from", "presence in" …) inside a short window around both names in one sentence; anything weaker is a co-mention, drawn dashed. Every edge carries article count, first / last date and up to three verbatim evidence sentences with the source article id and canonical URL. - Bounds — 400 nodes, 1,500 edges, 3 evidence sentences per edge, 800 article summaries; article bodies are never redistributed, only the evidence sentences and links.
- Refresh — the server rebuilds the corpus incrementally (
modified_afterper query) everyCJNG_GRAPH_REFRESH_HOURS(default 12;CJNG_GRAPH_REFRESH=falsedisables it) and savesruns/insightcrime/cjng/graph.json. A fresh checkout renders from the committed gzip snapshotconfig/cjng-graph-snapshot.json.gz(labelledSNAPSHOT) until the first refresh.node scripts/cjng-graph.mjs [--full|--offline]does the same from the command line. GET /api/narco/graph?type=person,org&rel=leader_of&min=3— whitelisted node types / relations and a 1–999 article-support floor; anything else is a 400. The sweep payload only carries a header summary; the browser fetches the graph on demand when the tab opens.
The panel is a D3 force layout with node-type, relation and minimum-support chips (persisted in localStorage); clicking a node lists its relations with the evidence sentences and links to the original article. Relations are as reported in the cited sentence, not verified ground truth, and rule-based cues miss relations phrased differently.
The Target Development tab turns the stores CRUCIX already holds into an analyst's targeting package (lib/targeting/). It is scoped to entities that public reporting already names — persons, organisations, facilities, vehicles, vessels, aircraft — and never to ordinary private individuals.
- Nominate — every target needs a logged basis (
kg-node,ofac-uid,doj-releaseor an HTTPSsource-url), a bounded label / alias set, the requirement it must answer (8–400 chars) and a priority 1–3. Targets persist inruns/targeting/targets.json(TARGETING_DATA_DIRoverrides); every nominate / develop / decision / close / delete is appended toruns/targeting/audit.jsonlas JSON. 60 open targets max; duplicate bases are rejected. - Find — mentions are gathered from the local CJNG graph and InSight Crime corpus, DOJ releases, the OFAC SDN index, border news, narco event clusters and the Telegram OSINT buffer; stores that are empty are reported as not available rather than fabricated, and nothing is fetched from the network during development. Aliases, nicknames and spelling variants are matched; selectors (OFAC uid / program, DOJ case numbers, IMO, registrations, domains, and official-text-only phone / e-mail) are extracted only when literally present in the text.
- Adjudicate — linked entities are classified target / associate / background with rules; when
LLM_PROVIDER+LLM_API_KEYare set (OpenAI or Anthropic) the configured model assesses each candidate, and its output is rejected unless it cites candidate / evidence indexes that exist, an allow-listed role and relation, and a confidence in[0,1]. Every link stays proposed until an analyst accepts or rejects it in the panel. - Fix — dated place mentions are snapped to the Mexico gazetteer with a precision-based uncertainty radius and drawn on a D3 map; the last known location is the newest sentence that states presence, labelled with age and caveats. Telegram is never used for location. Nothing here is a live position.
- Pattern of activity — per-target timeline, month / weekday / source / state aggregates, reporting gaps, and deviations from the target's own baseline (tempo spikes, new geography, status words).
- Graph proposals — relations the package would add to the CJNG graph, each with evidence; accepted proposals form an analyst-approved overlay at
GET /api/targeting/graph-overlay. The verified source-attributed graph is never modified. - Dossier —
GET /api/targeting/targets/:id/dossier.mdrenders a sourced Markdown package with every claim cited and its caveats.
Routes live under /api/targeting (list, nominate, get, develop, link / proposal decisions, close, delete, dossier, overlay); ids, enums and body fields are whitelisted and unexpected fields are a 400.
The UKRAINE WAR tab gives the Russia–Ukraine war its own theater view. No new upstream adapters are involved: every panel is a theater slice of something CRUCIX already sweeps, assembled by lib/ukraineview.mjs into a bounded ukraine view model and rendered next to the existing Ukraine Front panel.
- Theater map — D3 Mercator over lon 22–41 / lat 44–53 (Kharkiv → Odesa → Crimea → Kursk) with world-atlas land and borders and Ukraine highlighted. Layers (toggle chips, persisted in the browser): DeepStateMAP front polygons (occupied since 2022 / pre-2022 / contested / liberated, same colours as the globe legend), attack axes, RU units / airfields, the last 7 days of front updates, the Eastern Ukraine GPS-jamming zone, FIRMS hotspots, UA / RU nuclear sites (Zaporizhzhia in red), theater aircraft when the sweep carries positions, and KiwiSDR receivers. Geometry comes from the existing
GET /api/frontlines/georoute, fetched only when the tab opens and shared with the globe's front layer. Every polygon and marker popup links to DeepStateMAP at that place. - Front change log — per-day advance / regain bars for 7 and 30 days, the latest update rows (click → DeepStateMAP), and the assessed-occupied km² delta against the previous sweep (
front_occupied_km2from the delta engine). - Theater air & GPS jamming — the OpenSky
ukrainebox, the ADS-BUkraine/Black Seamilitary box withRF/RFFcallsigns picked out, and theEastern UkraineGPS-jamming zone. Counts track receiver coverage as much as activity. - Thermal detections — NASA FIRMS
ukrainebbox: detections, night detections, > 10 MW FRP and a bounded hotspot table. Fires, flares and industry all show up; not strike confirmation. - ZNPP & nuclear sites — Zaporizhzhia (occupied / critical), Rivne, Khmelnytskyi, South Ukraine and Kursk from the nuclear-sites registry, plus Safecast readings from the
zaporizhzhiaring. - Theater wires, channels & odds — GDELT
Ukraine/Russiatone and headlines, Telegram posts from the conflict channels (DeepStateUA, General Staff, mod_russia and peers, labelled by side), Polymarketrussia/ukrainemarkets. - UA / RU instability — the two Country Instability Index rows only.
- Left rail — DeepStateMAP source health (state, map id, map age, attribution), the Sensor Grid filtered to theater layers, and a Theater Reporting list of GDELT / Telegram / ACLED items from the same 72 h window as the front updates (what else is reporting — not proof the map is right).
DeepStateMAP is an observational map product: the areas shown are its assessment computed from its polygons, not verified ground truth, and “occupied” includes Crimea and pre-2022 ORDLO. Every panel and popup carries Map data © DeepStateMAP (deepstatemap.live). Each panel shows its own LIVE / DEGRADED / NO KEY / OFF / FAILED state from the sweep's source health (ACLED is usually NO KEY; nothing is faked to fill a gap). All third-party strings are bounded server-side and HTML-escaped at render, coordinates are range-checked before plotting, only http(s) links survive, and the browser talks only to same-origin /api/... routes. The Situation strip's Ukraine front headline routes to this tab.
The IRAN WAR LIVE tab plots the public output of IranWarLive (Tileterra Systems) on a dedicated theater map (lat 10–43, lon 28–66): kinetic events (air strikes, missile/rocket, drone, interceptions) from /feed.json merged with the published strikes Google Sheet, plus the separate ground operations, actors & casualties, airspace / sea lanes and global posturing sheets. No API key is required; all six parts are public, unauthenticated CSV/JSON polled once per sweep and cached 30 minutes.
IranWarLive is an observational, machine-extracted layer, not verified intelligence: the site describes itself as a single-operator pipeline that runs five English news-wire RSS feeds through an LLM extractor with no human review. Coordinates are city-level approximations, casualty figures are the wire's claim, and Western-aligned sources are over-represented. The dashboard says this on every panel and in every pin popup, and links each row to the cited article. The Situation strip only ever emits an info pointer, and only when feed.json fetched live, is under 6 h old and has events in the last 24 h.
Source health: LIVE (feed and strikes sheet both answered, feed < 6 h old) · LIMITED (a supporting sheet failed or the feed is older) · STALE (serving a cached copy after an upstream failure) · EMPTY (the feed answered with no events) · UNAVAILABLE. A degraded IranWarLive never changes CRUCIX's overall /api/health status. Every third-party string is bounded and stripped of markup at ingestion and HTML-escaped again at render; only http(s) source links survive. The compact view model rides in the dashboard payload; point geometry is served separately from GET /api/iranwar/geo and fetched only when the tab (or the Iran Theater main-map layers) is opened. Attribution: IranWarLive publishes under CC BY 4.0 in its feed metadata while its Terms limit reuse to research and journalistic use with attribution — keep the source line and links intact.
The CHINA / TAIWAN tab keeps official counts and OSINT visibly apart. No API key is required; all five parts are public and polled once per sweep.
- Taiwan MND daily PLA activity (
apis/sources/taiwanmnd.mjs) — the Ministry of National Defense's one-bulletin-a-day "PLA activities" release (English and Chinese lists paired by report date): PLA aircraft sorties, how many entered the ADIZ and in which sectors, PLAN ships, official ships, balloons, plus the MND's own map image link. These are aggregate counts at a fixed reference point, not tracks. The 30-day trend comes from the Skyfaring PLA-tracker CSV mirror (CC BY 4.0) and is cross-checked against the bulletin (AGREE/DISAGREE/UNCONFIRMED); Skyfaring'smedian_line_crossis a different metric from MND's "entered ADIZ" and is labelled as such. Taiwan Open Government Data License v1.0. - Taiwan Coast Guard grey-zone incidents (
apis/sources/taiwancga.mjs) — CGA press-release RSS filtered to PRC-actor / restricted-waters items (CCG intrusions around Kinmen / Matsu / Dongsha, mainland vessel incursions, research vessels). Chinese titles are kept as published; hull numbers, vessel counts, area and in/out times are extracted only when stated and shown as missing otherwise. Area markers are the area a release is about, never a vessel position. - Theater map — existing OpenSky aircraft, military ADS-B ISR/tanker/bomber airframes, carrier estimates and the PRC tension composite, plus MND ADIZ sector badges (daily counts), CGA area circles and GCA observational points. Geometry is served from
GET /api/taiwan/geoand fetched only when the tab or theTaiwan ADIZ/Grey Zone/GCA Taiwanmain-map layers are opened. - Headlines (
apis/sources/taiwannews.mjs) — Focus Taiwan (CNA) and Taipei Times RSS filtered to cross-strait / defence items; title, time and link only, bodies are not republished. Global Conflict Awareness (apis/sources/gcataiwan.mjs) — their Taiwan events feed, keyword-filtered for relevance and shown as a labelled observational strip with the required attribution and a link to their live map (CC BY-NC 4.0). Most GCA records are pinned to Taiwan's centroid; those are treated as country-level and are not drawn on the map. - Threat markets (
apis/sources/taiwanmarkets.mjs) — Polymarket YES prices for a fixed slug allow-list (invasion by date, blockade, military clash), shown as market-implied probabilities, not forecasts. - Link-outs for sources CRUCIX does not poll server-side (PLATracker / CSIS ADIZ sheets are request-based; Japan Joint Staff, INDOPACOM and 7th Fleet sit behind bot challenges), listed so the gap is visible.
Source health per part: LIVE · LIMITED (the MND bulletin is > 30 h old or a cross-check part failed) · STALE (cached copy after an upstream failure) · EMPTY · UNAVAILABLE · LINK-OUT. The compact view (lib/taiwanview.mjs) bounds every list and string; all third-party strings are HTML-escaped at render and only validated http(s) links survive. A degraded China / Taiwan part never changes CRUCIX's overall /api/health status. The Situation strip emits an elevated headline only when the MND aircraft count spikes against its 30-day average, and info pointers for recent CCG intrusions.
ingest/ is a standalone Python 3.10+ service that continuously collects, cleans, deduplicates and entity-extracts border-region reporting from English- and Spanish-language outlets, loads government/research datasets as historical baselines, and flags anomalies (e.g. a spike in violence reporting in a border county relative to its own history). The Node dashboard reads its JSON API and renders the Border Watch panel.
cd ingest
python -m venv .venv && . .venv/bin/activate
pip install -e '.[dev]'
python -m spacy download xx_ent_wiki_sm # multilingual NER model (optional; NER degrades gracefully without it)
python -m crucix_ingest sources # source registry (data, not code: crucix_ingest/data/sources.seed.json)
python -m crucix_ingest poll # one polling pass over every enabled source
python -m crucix_ingest baselines --list # structured baseline loaders and their refresh schedules
python -m crucix_ingest serve # scheduler (feeds every 15 min, baselines on their own schedules) + JSON API on 127.0.0.1:3118Then start the dashboard as usual (npm run dev); it discovers the service through INGEST_API_URL. The Python service reads its configuration from environment variables only, so export the INGEST_* block from .env (set -a; . ./.env; set +a) or run everything via Docker Compose.
- Publisher-advertised endpoints only — RSS/Atom, Google News sitemaps, and WordPress REST APIs (the Texas Tribune and InSight Crime are read through their APIs, not scraped).
- robots.txt is enforced on every request, including redirects and sitemap children; an unavailable robots policy fails closed.
- Conditional requests (
ETag/Last-Modified), a per-host delay, bounded response sizes and a fixed, descriptive User-Agent (CrucixBorderWatch/1.0 (+https://crucix.fly.dev/crawler; …)). - No paywall bypass. Paywalled items keep headline, feed summary, URL and timestamp only and are tagged
paywalled: true. HTTP 401/403 are recorded as blocked and never retried with a different identity. - Terms of use are data. Outlets whose terms prohibit crawling (Nexstar, Hearst, KRGV, Milenio) are registered with
content_policy: metadata_only— only the advertised feed is read and article pages are never requested. Every entry insources.seed.jsonrecords its terms URL, discovery method and the reason for its policy. - No stealth techniques — no proxies, no rotating identities, no headless browsers.
- Spanish text is the record of truth; machine translation is a separate, labelled derived field, and NER runs on the original language.
Structured datasets are loaded on their own schedules and exposed under /baselines: SESNSP municipal crime incidence (monthly), CBP nationwide encounters and drug seizures by AOR (monthly), FRA rail equipment and grade-crossing incidents for border states (monthly), InSight Crime publications and criminal-group profiles (weekly), Justice in Mexico Organized Crime and Violence in Mexico releases (quarterly check), and an optional one-time ACLED snapshot from a manually downloaded export (INGEST_ACLED_SNAPSHOT_PATH). Anomalies are computed per region/series against each dataset's own history and persisted alongside the news-reporting anomalies.
| Endpoint | Description |
|---|---|
GET /health |
Service status, degraded sources, last sweep, baseline status (always HTTP 200) |
GET /sources |
Source registry with polling state |
GET /articles?limit=&since=®ion=&violence=1&language=&source= |
Cleaned article metadata, regions, entities and violence terms |
GET /articles/<id> |
Full record incl. original text and derived translation |
GET /anomalies?limit=&since= |
News and baseline anomalies |
GET /baselines, GET /baselines/<dataset>/records?series=®ion=&limit= |
Baseline datasets and records |
GET /summary |
Dashboard summary |
POST /poll, POST /baselines/check |
Trigger a run — loopback clients only |
The dashboard proxies the read-only routes at /api/ingest/* (allow-list in apis/sources/borderingest.mjs) and serves the synthesized panel data at /api/border. Tests: cd ingest && pytest (recorded fixtures under ingest/tests/fixtures/), ruff check ., mypy crucix_ingest.
The Cartels & Border tab's US Border · Official CBP Data band is a consolidated panel group fed by four adapters that read what U.S. Customs and Border Protection publishes on its Public Data Portal (public domain; "This product uses U.S. Customs and Border Protection data, but is not endorsed by CBP."). All four share apis/sources/cbpcommon.mjs: each sweep first reads the official document page, picks the newest .csv link (the file name moves every month), honours robots.txt, downloads conditionally (ETag / Last-Modified) and caches under runs/cbp/, so a CBP outage degrades a dataset to STALE instead of blanking it; a changed header row or page layout is refused rather than guessed at. Fiscal-year months are converted to calendar months (FY starts 1 October). Note that cbp.gov's edge returns 403 to curl-style clients; the sources rely on Node's native fetch.
| Source | Module | Datasets | Panel |
|---|---|---|---|
CBPStats |
apis/sources/cbpstats.mjs |
Nationwide Encounters by AOR, Nationwide Drug Seizures | Southwest encounters (total, USBP vs OFO, 13-month trend, per-sector MoM/YoY, demographics, citizenships) and Southwest drug seizures by type and AOR |
CBPSeizures |
apis/sources/cbpseizures.mjs |
AMO Drug Seizures, Currency & Monetary Instrument Seizures, Weapons & Ammunition Seizures | AMO lbs / events by drug and region, currency USD by AOR and direction, weapon events (deduplicated by event ID, outbound share, weapons vs ammunition/parts) |
CBPForce |
apis/sources/cbpforce.mjs |
Assault Incidents, Assault Types, Use-of-Force Incidents, Use-of-Force Types | Assaults on officers/agents and CBP use of force: incidents, officers involved, Southern Border share, Southwest sectors, assault/force type tables |
CBPCustody |
apis/sources/cbpcustody.mjs |
Custody & Transfer Statistics page, CBP Enforcement Statistics page (HTML tables — CBP publishes no CSV) | USBP in-custody by sector, OFO custody vs capacity, dispositions, transfers, fiscal-year enforcement encounters, TSDS encounters, criminal noncitizens, rescues, gang affiliations |
Tableau-only views on publicstats.cbp.gov (dosage-unit / street-value estimates, UFLPA) are linked out from the group header rather than scraped. Tests: node --test test/cbpstats.test.mjs test/cbpportal.test.mjs (recorded fixtures under test/fixtures/cbp/).
A self-contained Jarvis-style HUD with:
- 3D WebGL globe (Globe.gl) with atmosphere glow, star field, and smooth rotation — plus a classic flat map toggle
- Map layers shared by both views (registry in
lib/maplayers.mjs): air traffic, fire detections, radiation sites, maritime chokepoints, SDR receivers, OSINT events, health alerts, geolocated news, conflict events, carrier groups, GDELT clusters, narco reporting, space stations, PRC activity, GPS jamming, military ADS-B, market intel - Signal-first defaults — each sweep the server marks every layer
signal(something notable this sweep),data(has points, nothing notable) ornone(nothing to plot, with the reason: needs key, source failed, quiet). The map starts with only thesignallayers on (max 5, padded to 3 withdatalayers); the chip row under the map toggles any layer, remembers a manual selection in local storage, andRESET TO AUTOreturns to the sweep's defaults. Globe and flat map always show the same selection - Panel captions — every panel opens with one line saying what it shows, which source or computation feeds it, and what it does not establish (
Derived.marks composites that add no independent data) - Animated 3D flight corridor arcs between air traffic hotspots and global hubs
- Region filters (World, Americas, Europe, Middle East, Asia Pacific, Africa) — rotates the globe or zooms the flat map
- Live market data — indexes, crypto, energy, commodities via Yahoo Finance (no API key needed)
- Risk gauges — VIX, high-yield spread, supply chain pressure index
- OSINT feed — English-language posts from 17 Telegram intelligence channels (expandable)
- News ticker — merged RSS + GDELT headlines + Telegram posts, auto-scrolling
- Sweep delta — live panel showing what changed since last sweep (new signals, escalations, de-escalations with severity)
- Cross-source signals — correlated intelligence across satellite, economic, conflict, and social domains
- Nuclear watch — real-time radiation readings from Safecast + EPA RadNet
- Space watch — CelesTrak satellite tracking: recent launches, ISS, military constellations, Starlink/OneWeb counts
- Leverageable ideas — AI-generated trade ideas (with LLM) or signal-correlated ideas (without)
The VISUALS FULL / VISUALS LITE button in the top bar only changes rendering behavior - it does not remove data sources or reduce sweep coverage.
When you switch to VISUALS LITE, the dashboard:
- Disables decorative background effects such as the radial/grid overlays and scanlines
- Removes expensive blur/backdrop-filter effects on panels and overlays
- Stops non-essential animations like the logo ring blink, conflict rings, and corridor flow effects
- Disables globe auto-rotation and turns off animated flight-arc dashes
- Converts the horizontal news ticker and OSINT stream into static, scrollable lists instead of continuously animated marquees
Mobile-specific behavior:
- On mobile,
VISUALS LITEalso forces the dashboard into flat map mode if you are currently on the globe - Future mobile loads will continue to start flat while low-perf mode is enabled
The preference is saved in browser local storage, so the UI will remember your last setting.
The server runs a sweep cycle every 15 minutes (configurable). Each cycle:
- Queries all 27 sources in parallel (~30s)
- Synthesizes raw data into dashboard format
- Computes delta from previous run (what changed, escalated, de-escalated) — visible in the What Changed panel on the Situation tab
- Builds the Situation strip: up to 5 rule-based headline judgments (
lib/situation.mjs— DEFCON, delta, flash alerts, radiation, PRC tension, focal points, CII, convergence, Border Watch spikes, KEV surges, Kp storms, new look-alike domains, source coverage). No LLM involved; each card links to the tab/panel holding the evidence. The same pass ranks the map layers (lib/maplayers.mjs) so the globe opens on what has signal this sweep - Generates LLM trade ideas (if configured)
- Evaluates breaking news alerts — multi-tier (FLASH / PRIORITY / ROUTINE) with semantic dedup. Sends to Telegram and/or Discord if configured. Works with LLM evaluation or falls back to rule-based alerting when LLM is unavailable.
- Pushes update to all connected browsers via SSE
Crucix doubles as an interactive Telegram bot. Beyond sending alerts, it responds to commands directly from your chat:
| Command | What It Does |
|---|---|
/status |
System health, last sweep time, source status, LLM status |
/sweep |
Trigger a manual sweep cycle |
/brief |
Compact text summary of the latest intelligence (direction, key metrics, top OSINT) |
/portfolio |
Portfolio status (if Alpaca connected) |
/alerts |
Recent alert history with tiers |
/mute / /mute 2h |
Silence alerts for 1h (or custom duration) |
/unmute |
Resume alerts |
/help |
Show all available commands |
This requires TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID in .env. The bot polls for messages every 5 seconds (configurable via TELEGRAM_POLL_INTERVAL).
Crucix also supports Discord as a full-featured bot with slash commands and rich embed alerts. It mirrors the Telegram bot's capabilities with Discord-native formatting.
| Command | What It Does |
|---|---|
/status |
System health, last sweep time, source status, LLM status |
/sweep |
Trigger a manual sweep cycle |
/brief |
Compact text summary of the latest intelligence |
/portfolio |
Portfolio status (if Alpaca connected) |
Alerts are delivered as rich embeds with color-coded sidebars: red for FLASH, yellow for PRIORITY, blue for ROUTINE. Each embed includes signal details, confidence scores, and cross-domain correlations.
Setup requires: DISCORD_BOT_TOKEN, DISCORD_CHANNEL_ID, and optionally DISCORD_GUILD_ID for instant slash command registration. See API Keys Setup for details.
Webhook fallback: If you don't want to run a full bot, set DISCORD_WEBHOOK_URL instead. This enables one-way alerts (no slash commands) with zero dependencies — no discord.js needed.
Optional dependency: The full bot requires discord.js. Install it with npm install discord.js. If it's not installed, Crucix automatically falls back to webhook-only mode.
Connect any of 8 LLM providers for enhanced analysis:
- AI trade ideas — quantitative analyst producing 5-8 actionable ideas citing specific data
- Smarter alert evaluation — LLM classifies signals into FLASH/PRIORITY/ROUTINE tiers with cross-domain correlation and confidence scoring
- Providers: Anthropic Claude, OpenAI, Google Gemini, OpenRouter (Unified API), OpenAI Codex (ChatGPT subscription), MiniMax, Mistral, Grok
- Graceful fallback — when LLM is unavailable, a rule-based engine takes over alert evaluation. LLM failures never crash the sweep cycle.
Copy .env.example to .env at the project root:
cp .env.example .env| Key | Source | How to Get |
|---|---|---|
FRED_API_KEY |
Federal Reserve Economic Data | fred.stlouisfed.org — instant, free |
FIRMS_MAP_KEY |
NASA FIRMS (satellite fire data) | firms.modaps.eosdis.nasa.gov — instant, free |
EIA_API_KEY |
US Energy Information Administration | api.eia.gov — instant, free |
These three unlock the most valuable economic and satellite data. Each takes about 60 seconds to register.
| Key | Source | How to Get |
|---|---|---|
ACLED_EMAIL + ACLED_PASSWORD |
Armed conflict event data | acleddata.com/register — free, OAuth2 |
AISSTREAM_API_KEY |
Maritime AIS vessel tracking | aisstream.io — free |
ADSB_API_KEY |
Unfiltered flight tracking | RapidAPI — ~$10/mo |
VIRUSTOTAL_API_KEY |
Investigate: domain/IP/hash reputation | virustotal.com — free |
SHODAN_API_KEY |
Investigate: full host/service data | account.shodan.io — free tier |
OPENCORPORATES_API_TOKEN |
Investigate: company registry | opencorporates.com — free for non-commercial |
HIBP_API_KEY |
Investigate: breach exposure per email | haveibeenpwned.com/API/Key — paid |
NUMVERIFY_API_KEY |
Investigate: phone carrier / line type | numverify.com — free tier |
GITHUB_TOKEN |
Investigate: higher GitHub API rate limit for handle lookups | github.com/settings/tokens — free, no scopes |
OPENSANCTIONS_API_KEY |
Investigate: wallet + entity sanctions screening | opensanctions.org/api — free for non-commercial |
Set LLM_PROVIDER to one of: anthropic, openai, gemini, codex, openrouter, minimax, mistral, grok
| Provider | Key Required | Default Model |
|---|---|---|
anthropic |
LLM_API_KEY |
claude-sonnet-4-6 |
openai |
LLM_API_KEY |
gpt-5.4 |
gemini |
LLM_API_KEY |
gemini-3.1-pro |
openrouter |
LLM_API_KEY |
openrouter/auto |
codex |
None (uses ~/.codex/auth.json) |
gpt-5.3-codex |
minimax |
LLM_API_KEY |
MiniMax-M2.5 |
mistral |
LLM_API_KEY |
mistral-large-latest |
grok |
LLM_API_KEY |
grok-4-latest |
For Codex, run npx @openai/codex login to authenticate via your ChatGPT subscription.
| Key | How to Get |
|---|---|
TELEGRAM_BOT_TOKEN |
Create via @BotFather on Telegram |
TELEGRAM_CHAT_ID |
Get via @userinfobot |
TELEGRAM_CHANNELS |
(Optional) Comma-separated extra channel IDs to monitor beyond the 17 built-in channels |
TELEGRAM_POLL_INTERVAL |
(Optional) Bot command polling interval in ms (default: 5000) |
| Key | How to Get |
|---|---|
DISCORD_BOT_TOKEN |
Create at Discord Developer Portal → Bot → Token |
DISCORD_CHANNEL_ID |
Right-click channel in Discord (Developer Mode on) → Copy Channel ID |
DISCORD_GUILD_ID |
(Optional) Right-click server → Copy Server ID. Enables instant slash command registration (otherwise takes up to 1 hour for global commands) |
DISCORD_WEBHOOK_URL |
(Optional) Channel Settings → Integrations → Webhooks → New Webhook → Copy URL. Use this for alert-only mode without a bot |
Discord bot setup:
- Go to Discord Developer Portal and create a new application
- Go to Bot → click Reset Token → copy the token to
DISCORD_BOT_TOKEN - Under Privileged Gateway Intents, enable Message Content Intent
- Go to OAuth2 → URL Generator → select
bot+applications.commandsscopes → selectSend Messages+Embed Linkspermissions - Copy the generated URL and open it in your browser to invite the bot to your server
- Install the dependency:
npm install discord.js
Alerts work with or without an LLM on both Telegram and Discord. With an LLM configured, signal evaluation is richer and more context-aware. Without one, a deterministic rule engine evaluates signals based on severity, cross-domain correlation, and signal counts.
Crucix still works with zero API keys. 18+ sources require no authentication at all. Sources that need keys return structured errors and the rest of the sweep continues normally.
crucix/
├── server.mjs # Express dev server (SSE, auto-refresh, LLM, bot commands)
├── crucix.config.mjs # Configuration with env var overrides + delta thresholds
├── diag.mjs # Diagnostic script — run if server fails to start
├── .env.example # All documented env vars
├── package.json # Runtime: express | Optional: discord.js
├── docs/ # Screenshots for README
│
├── apis/
│ ├── briefing.mjs # Master orchestrator — runs all 27 sources in parallel
│ ├── save-briefing.mjs # CLI: save timestamped + latest.json
│ ├── BRIEFING_PROMPT.md # Intelligence synthesis protocol
│ ├── BRIEFING_TEMPLATE.md # Briefing output structure
│ ├── utils/
│ │ ├── fetch.mjs # safeFetch() — timeout, retries, abort, auto-JSON
│ │ └── env.mjs # .env loader (no dotenv dependency)
│ └── sources/ # 27 self-contained source modules
│ ├── borderingest.mjs # Border Watch: read-only bridge to the Python ingestion API
│ ├── gdelt.mjs # Each exports briefing() → structured data
│ ├── fred.mjs # Can run standalone: node apis/sources/fred.mjs
│ ├── space.mjs # CelesTrak satellite tracking
│ ├── yfinance.mjs # Yahoo Finance — free live market data
│ └── ... # 23 more
│
├── dashboard/
│ ├── inject.mjs # Data synthesis + standalone HTML injection
│ └── public/
│ └── jarvis.html # Self-contained Jarvis HUD
│
├── lib/
│ ├── llm/ # LLM abstraction (8 providers, raw fetch, no SDKs)
│ │ ├── provider.mjs # Base class
│ │ ├── anthropic.mjs # Claude
│ │ ├── openai.mjs # GPT
│ │ ├── gemini.mjs # Gemini
│ │ ├── grok.mjs # Grok
│ │ ├── openrouter.mjs # OpenRouter (Unified API)
│ │ ├── codex.mjs # Codex (ChatGPT subscription)
│ │ ├── minimax.mjs # MiniMax (M2.5, 204K context)
│ │ ├── mistral.mjs # Mistral AI
│ │ ├── ideas.mjs # LLM-powered trade idea generation
│ │ └── index.mjs # Factory: createLLMProvider()
│ ├── delta/ # Change tracking between sweeps
│ │ ├── engine.mjs # Delta computation — semantic dedup, configurable thresholds, severity scoring
│ │ ├── memory.mjs # Hot memory (3 runs, atomic writes) + cold storage (daily archives)
│ │ └── index.mjs # Re-exports
│ └── alerts/
│ ├── telegram.mjs # Multi-tier alerts (FLASH/PRIORITY/ROUTINE) + two-way bot commands
│ └── discord.mjs # Discord bot (slash commands, rich embeds) + webhook fallback
│
├── ingest/ # Border Watch ingestion service (Python, own Dockerfile)
│ ├── crucix_ingest/ # registry, polite HTTP client, feed parsers, extraction, NER, geo/violence scoring, baselines, API
│ │ └── data/ # sources.seed.json (source registry) + border_regions.json (gazetteer)
│ └── tests/ # pytest suite with recorded feed/dataset fixtures
│
└── runs/ # Runtime data (gitignored)
├── latest.json # Most recent sweep output
├── memory/ # Delta memory (hot.json + cold/YYYY-MM-DD.json)
└── ingest/ # Ingestion SQLite DB + raw HTML snapshots
- Pure ESM — every file is
.mjswith explicit imports - Minimal dependencies — Express is the only runtime dependency.
discord.jsis optional (for Discord bot). LLM providers use rawfetch(), no SDKs. - Parallel execution —
Promise.allSettled()fires all 27 sources simultaneously - Graceful degradation — missing keys produce errors, not crashes. LLM failures don't kill sweeps.
- Each source is standalone — run
node apis/sources/gdelt.mjsto test any source independently - Self-contained dashboard — the HTML file works with or without the server
| Source | What It Tracks | Auth |
|---|---|---|
| GDELT | Global news events, conflict mapping (100+ languages) via the 15-minute export/GKG snapshots | None |
| OpenSky | Real-time ADS-B flight tracking, one global pull partitioned into 10 hotspot regions (falls back to adsb.lol point samples, marked fallback, when OpenSky is unreachable) |
Optional (OAuth2, 10x quota) |
| NASA FIRMS | Satellite fire/thermal anomaly detection (3hr latency) | Free key |
| Maritime/AIS | Vessel tracking, dark ships, sanctions evasion | Free key |
| Safecast | Citizen-science radiation monitoring near 6 nuclear sites | None |
| ACLED | Armed conflict events: battles, explosions, protests | Free (OAuth2) |
| ReliefWeb | UN humanitarian crisis tracking (API v2 with RELIEFWEB_APPNAME, else public RSS → HDX) |
Optional |
| WHO | Disease outbreaks and health emergencies | None |
| OFAC | US Treasury sanctions (SDN list) | None |
| OpenSanctions | Aggregated global sanctions (30+ sources) | Partial |
| ADS-B Exchange | Unfiltered flight tracking including military | Paid |
| Source | What It Tracks | Auth |
|---|---|---|
| FRED | 22 key indicators: yield curve, CPI, VIX, fed funds, M2 | Free key |
| US Treasury | National debt, yields, fiscal data | None |
| BLS | CPI, unemployment, nonfarm payrolls, PPI | None |
| EIA | WTI/Brent crude, natural gas, inventories | Free key |
| GSCPI | NY Fed Global Supply Chain Pressure Index | None |
| USAspending | Federal spending and defense contracts | None |
| UN Comtrade | Strategic commodity trade flows between major powers | None |
| Source | What It Tracks | Auth |
|---|---|---|
| NOAA/NWS | Active US weather alerts | None |
| EPA RadNet | US government radiation monitoring | None |
| USPTO Patents | Patent filings in 7 strategic tech areas | None |
| Bluesky | Social sentiment on geopolitical/market topics | None |
| Social sentiment from key subreddits | OAuth | |
| Telegram | 17 curated OSINT/conflict/finance channels (web scraping, expandable via config) | None |
| KiwiSDR | Global HF radio receiver network (~600 receivers) | None |
| Source | What It Tracks | Auth |
|---|---|---|
| CelesTrak | Satellite launches, ISS tracking, military constellations, Starlink/OneWeb counts | None |
| Source | What It Tracks | Auth |
|---|---|---|
| Yahoo Finance | Real-time prices: SPY, QQQ, BTC, Gold, WTI, VIX + 9 more | None |
| Script | Command | Description |
|---|---|---|
npm run dev |
node --trace-warnings server.mjs |
Start dashboard with auto-refresh |
npm run sweep |
node apis/briefing.mjs |
Run a single sweep, output JSON to stdout |
npm run inject |
node dashboard/inject.mjs |
Inject latest data into static HTML |
npm run brief:save |
node apis/save-briefing.mjs |
Run sweep + save timestamped JSON |
npm run diag |
node diag.mjs |
Run diagnostics (Node version, imports, port check) |
npm run cjng:graph |
node scripts/cjng-graph.mjs |
Refresh the InSight Crime CJNG corpus and rebuild the knowledge graph (--full re-pulls everything, --offline rebuilds from the cached corpus) |
npm run ingest |
python -m crucix_ingest serve |
Start the Border Watch ingestion service (needs the ingest/ venv active) |
npm run ingest:poll |
python -m crucix_ingest poll |
One polling pass over every enabled source |
npm run ingest:test |
cd ingest && python -m pytest |
Ingestion test suite (recorded fixtures, no network) |
All settings are in .env with sensible defaults:
| Variable | Default | Description |
|---|---|---|
PORT |
3117 |
Dashboard server port |
REFRESH_INTERVAL_MINUTES |
15 |
Auto-refresh interval |
OPENSKY_CLIENT_ID / OPENSKY_CLIENT_SECRET |
anonymous | OpenSky OAuth2 API client (raises quota 400 → 4,000 credits/day) |
OPENSKY_MIN_INTERVAL_MINUTES |
15 |
Minimum spacing between OpenSky global pulls (4 credits each) |
INGEST_API_URL |
http://127.0.0.1:3118 |
Border Watch ingestion service the dashboard reads from |
INGEST_* |
see .env.example |
Python ingestion service: bind address, poll interval, NER, translation, anomaly thresholds |
DOJ_PAGES_PER_SWEEP / DOJ_BACKFILL_PAGES |
2 / 8 |
DOJ press-release API pages per sweep and one-time backfill depth |
OFAC_NARCO_REFRESH_HOURS |
24 |
Hours between OFAC SDN re-downloads |
NARCO_LLM_EXTRACT / NARCO_LLM_MAX_PER_SWEEP |
false / 10 |
Optional LLM gap-filling of narco event records (rule-based fields always win) |
DATAINT_API_KEY / DATAINT_API_URL, LANTIA_API_KEY / LANTIA_API_URL |
unset (NO KEY) |
Commercial Mexico security-intelligence vendors; both key and HTTPS endpoint required |
LLM_PROVIDER |
disabled | anthropic, openai, gemini, codex, openrouter, minimax, mistral, or grok |
LLM_API_KEY |
— | API key (not needed for codex) |
LLM_MODEL |
per-provider default | Override model selection |
TELEGRAM_BOT_TOKEN |
disabled | For Telegram alerts + bot commands |
TELEGRAM_CHAT_ID |
— | Your Telegram chat ID |
TELEGRAM_CHANNELS |
— | Extra channel IDs to monitor (comma-separated) |
TELEGRAM_POLL_INTERVAL |
5000 |
Bot command polling interval (ms) |
DISCORD_BOT_TOKEN |
disabled | For Discord alerts + slash commands |
DISCORD_CHANNEL_ID |
— | Discord channel for alerts |
DISCORD_GUILD_ID |
— | Server ID (instant slash command registration) |
DISCORD_WEBHOOK_URL |
— | Webhook URL (alert-only fallback, no bot needed) |
Delta engine thresholds (how sensitive the system is to changes between sweeps) can be customized in crucix.config.mjs under the delta.thresholds section. The defaults are tuned to filter out noise while catching meaningful moves.
When running npm run dev:
| Endpoint | Description |
|---|---|
GET / |
Jarvis HUD dashboard |
GET /api/data |
Current synthesized intelligence data (JSON) |
GET /api/health |
Server status, uptime, source count, LLM status, ingestion service status |
GET /api/border |
Border Watch panel data (anomalies, regions, articles, baselines) from the last sweep |
GET /api/ingest/* |
Read-only proxy to the ingestion API (allow-listed paths and query params only) |
GET /events |
SSE stream for live push updates |
GET /api/investigate?target=<domain|ip|hash> |
On-demand OSINT dossier (&type=company for registry search) |
GET /api/investigate/status |
Which keyed enrichment sources are configured |
GET /api/typosquat |
Registered look-alike domains for the watchlist |
GET /api/cartels |
Current cartel-map summary (status, counts, organizations, wars, recent entries, disclaimer) |
GET /api/cartels/geo |
Cartel-map geometry (polygons, points, lines) for the CARTELS tab; 404 until the first successful fetch |
GET /api/narco/graph |
CJNG knowledge graph (nodes, edges with evidence, article index); optional type, rel, min filters |
This is a known issue where npm's script runner can swallow errors, particularly on Windows PowerShell. Try these in order:
1. Run Node directly (bypasses npm):
node --trace-warnings server.mjsThis is functionally identical to npm run dev but gives you full error output.
2. Run the diagnostic script:
node diag.mjsThis tests every import one by one, checks your Node.js version, and verifies port 3117 is available. It will tell you exactly what's failing.
3. Check if port 3117 is already in use:
A previous Crucix instance may still be running in the background.
# Windows PowerShell
netstat -ano | findstr 3117
taskkill /F /PID <the_PID_from_above>
# Or kill all Node processes
taskkill /F /IM node.exe# macOS / Linux
lsof -ti:3117 | xargs killThen try starting again. You can also change the port by setting PORT=3118 in your .env file.
4. Check Node.js version:
node --versionCrucix requires Node.js 22 or later. If you have an older version, download the latest LTS from nodejs.org.
This is normal — the first sweep takes 30–60 seconds to query all 27 sources. The dashboard will populate automatically once the sweep completes. Check the terminal for sweep progress logs.
Expected behavior. Sources that require API keys will return structured errors if the key isn't set. The rest of the sweep continues normally. Check the Source Integrity section in the dashboard (or the server logs) to see which sources failed and why. The 3 most impactful free keys to add are FRED_API_KEY, FIRMS_MAP_KEY, and EIA_API_KEY.
OpenSky meters /states/all in credits: 400/day anonymous, 4,000/day with an OAuth2 API client, and a global pull costs 4 credits. Crucix makes exactly one global pull per sweep (≈384 credits/day at the default 15-minute interval), so anonymous use fits under the cap with little headroom. If the sweep interval is shorter, or another process on the same IP is also hitting OpenSky, you will see HTTP 429 with a cooldown of several hours. Crucix does not try to evade that limit: it honors the x-rate-limit-retry-after-seconds header, skips OpenSky until the cooldown expires, and keeps serving the last good snapshot (flagged status: stale in source health) so the flight layer does not go blank. To lift the ceiling, create an API client at https://opensky-network.org/my-opensky (Account → API Client) and set OPENSKY_CLIENT_ID / OPENSKY_CLIENT_SECRET.
Make sure both TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID are set in .env. The bot only responds to messages from the configured chat ID (security measure). You should see [Crucix] Telegram alerts enabled and [Crucix] Bot command polling started in the server logs on startup. If not, double-check your token with curl https://api.telegram.org/bot<YOUR_TOKEN>/getMe.
Check these in order:
- Make sure
DISCORD_BOT_TOKENandDISCORD_CHANNEL_IDare set in.env - Verify
discord.jsis installed:npm ls discord.js. If missing, runnpm install discord.js - If slash commands don't appear, set
DISCORD_GUILD_ID— without it, global commands can take up to 1 hour to propagate. Guild-specific commands register instantly - Confirm the bot was invited with
bot+applications.commandsscopes and hasSend Messages+Embed Linkspermissions in the target channel - Check server logs for
[Discord] Bot logged in as ...on startup. If you see[Discord] discord.js not installed, install it and restart - Webhook-only fallback: If you just want alerts without slash commands, set
DISCORD_WEBHOOK_URLinstead of the bot token. Nodiscord.jsneeded.
The docs/ folder contains dashboard screenshots referenced by this README:
| File | Description |
|---|---|
docs/dashboard.png |
Full dashboard — hero image at the top of this README |
docs/boot.png |
Cinematic boot sequence animation |
docs/map.png |
D3 world map with marker types and flight arcs |
docs/globe.png |
3D WebGL globe view with atmosphere glow and markers |
To update them: run the dashboard, wait for a sweep to complete, then use your browser's DevTools (F12 → Ctrl+Shift+P → "Capture full size screenshot") or a tool like LICEcap for GIFs.
Found a bug? Want to add a 28th source? PRs welcome. Each source is a standalone module in apis/sources/ — just export a briefing() function that returns structured data and add it to the orchestrator in apis/briefing.mjs.
If you find this useful, a star helps others find it too.
For contribution guidelines, review expectations, and source-add rules, see CONTRIBUTING.md. For security reports, see SECURITY.md.
For partnerships, integrations, or other non-issue inquiries, you can reach me at celesthioailabs@gmail.com.
For bugs and feature requests, please use GitHub Issues so discussion stays visible and actionable.
AGPL-3.0



