Copy disabled (too large)
Download .txt
Showing preview only (15,944K chars total). Download the full file to get everything.
Repository: koala73/worldmonitor
Branch: main
Commit: b3644f701195
Files: 1349
Total size: 15.0 MB
Directory structure:
gitextract_xk6l94h3/
├── .dockerignore
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ ├── feature_request.yml
│ │ └── new_data_source.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── build-desktop.yml
│ ├── contributor-trust.yml
│ ├── docker-publish.yml
│ ├── lint-code.yml
│ ├── lint.yml
│ ├── proto-check.yml
│ ├── test-linux-app.yml
│ ├── test.yml
│ └── typecheck.yml
├── .gitignore
├── .husky/
│ ├── pre-commit
│ └── pre-push
├── .markdownlint-cli2.jsonc
├── .npmrc
├── .nvmrc
├── .vercelignore
├── AGENTS.md
├── ARCHITECTURE.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── Dockerfile.relay
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── SELF_HOSTING.md
├── api/
│ ├── _api-key.js
│ ├── _cors.js
│ ├── _cors.test.mjs
│ ├── _github-release.js
│ ├── _ip-rate-limit.js
│ ├── _json-response.js
│ ├── _rate-limit.js
│ ├── _relay.js
│ ├── _rss-allowed-domains.js
│ ├── _turnstile.js
│ ├── _turnstile.test.mjs
│ ├── _upstash-json.js
│ ├── ais-snapshot.js
│ ├── aviation/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── bootstrap.js
│ ├── cache-purge.js
│ ├── climate/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── conflict/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── contact.js
│ ├── cyber/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── data/
│ │ └── city-coords.ts
│ ├── displacement/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── download.js
│ ├── economic/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── eia/
│ │ └── [[...path]].js
│ ├── enrichment/
│ │ ├── _domain.js
│ │ ├── company.js
│ │ └── signals.js
│ ├── forecast/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── fwdstart.js
│ ├── geo.js
│ ├── giving/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── gpsjam.js
│ ├── health.js
│ ├── imagery/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── infrastructure/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── intelligence/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── loaders-xml-wms-regression.test.mjs
│ ├── maritime/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── market/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── mcp-proxy.js
│ ├── military/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── military-flights.js
│ ├── natural/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── news/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── og-story.js
│ ├── og-story.test.mjs
│ ├── opensky.js
│ ├── oref-alerts.js
│ ├── polymarket.js
│ ├── positive-events/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── prediction/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── radiation/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── register-interest.js
│ ├── research/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── reverse-geocode.js
│ ├── rss-proxy.js
│ ├── sanctions/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── satellites.js
│ ├── seed-health.js
│ ├── seismology/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── story.js
│ ├── supply-chain/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── telegram-feed.js
│ ├── thermal/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── trade/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── unrest/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── version.js
│ ├── webcam/
│ │ └── v1/
│ │ └── [rpc].ts
│ ├── wildfire/
│ │ └── v1/
│ │ └── [rpc].ts
│ └── youtube/
│ ├── embed.js
│ ├── embed.test.mjs
│ └── live.js
├── biome.json
├── blog-site/
│ ├── .gitignore
│ ├── .vscode/
│ │ ├── extensions.json
│ │ └── launch.json
│ ├── README.md
│ ├── astro.config.mjs
│ ├── package.json
│ ├── public/
│ │ └── robots.txt
│ ├── scripts/
│ │ └── generate-og-images.mjs
│ ├── src/
│ │ ├── content/
│ │ │ └── blog/
│ │ │ ├── ai-powered-intelligence-without-the-cloud.md
│ │ │ ├── build-on-worldmonitor-developer-api-open-source.md
│ │ │ ├── command-palette-search-everything-instantly.md
│ │ │ ├── cyber-threat-intelligence-for-security-teams.md
│ │ │ ├── five-dashboards-one-platform-worldmonitor-variants.md
│ │ │ ├── live-webcams-from-geopolitical-hotspots.md
│ │ │ ├── monitor-global-supply-chains-and-commodity-disruptions.md
│ │ │ ├── natural-disaster-monitoring-earthquakes-fires-volcanoes.md
│ │ │ ├── osint-for-everyone-open-source-intelligence-democratized.md
│ │ │ ├── prediction-markets-ai-forecasting-geopolitics.md
│ │ │ ├── real-time-market-intelligence-for-traders-and-analysts.md
│ │ │ ├── satellite-imagery-orbital-surveillance.md
│ │ │ ├── track-global-conflicts-in-real-time.md
│ │ │ ├── tracking-global-trade-routes-chokepoints-freight-costs.md
│ │ │ ├── what-is-worldmonitor-real-time-global-intelligence.md
│ │ │ ├── worldmonitor-in-21-languages-global-intelligence-for-everyone.md
│ │ │ └── worldmonitor-vs-traditional-intelligence-tools.md
│ │ ├── content.config.ts
│ │ ├── layouts/
│ │ │ ├── Base.astro
│ │ │ └── BlogPost.astro
│ │ ├── pages/
│ │ │ ├── index.astro
│ │ │ ├── posts/
│ │ │ │ └── [...id].astro
│ │ │ └── rss.xml.ts
│ │ └── styles/
│ │ └── global.css
│ └── tsconfig.json
├── convex/
│ ├── _generated/
│ │ ├── api.d.ts
│ │ ├── api.js
│ │ ├── dataModel.d.ts
│ │ ├── registerInterest.js
│ │ ├── schema.js
│ │ ├── server.d.ts
│ │ └── server.js
│ ├── contactMessages.ts
│ ├── registerInterest.ts
│ ├── schema.ts
│ └── tsconfig.json
├── data/
│ ├── gamma-irradiators-raw.json
│ ├── gamma-irradiators.json
│ └── telegram-channels.json
├── deploy/
│ └── nginx/
│ └── brotli-api-proxy.conf
├── docker/
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── Dockerfile.redis-rest
│ ├── build-handlers.mjs
│ ├── docker-entrypoint.sh
│ ├── entrypoint.sh
│ ├── nginx-security-headers.conf
│ ├── nginx.conf
│ ├── nginx.conf.template
│ ├── redis-rest-proxy.mjs
│ └── supervisord.conf
├── docker-compose.yml
├── docs/
│ ├── .mintignore
│ ├── .mintlifyignore
│ ├── COMMUNITY-PROMOTION-GUIDE.md
│ ├── Docs_To_Review/
│ │ ├── API_REFERENCE.md
│ │ ├── ARCHITECTURE.md
│ │ ├── COMPONENTS.md
│ │ ├── DATA_MODEL.md
│ │ ├── DESKTOP_CONFIGURATION.md
│ │ ├── DOCUMENTATION.md
│ │ ├── EXTERNAL_APIS.md
│ │ ├── NEWS_TRANSLATION_ANALYSIS.md
│ │ ├── PANELS.md
│ │ ├── RELEASE_PACKAGING.md
│ │ ├── STATE_MANAGEMENT.md
│ │ ├── TAURI_VALIDATION_REPORT.md
│ │ ├── TODO_Performance.md
│ │ ├── bugs.md
│ │ ├── local-backend-audit.md
│ │ ├── todo.md
│ │ └── todo_docs.md
│ ├── PRESS_KIT.md
│ ├── TAURI_VALIDATION_REPORT.md
│ ├── adding-endpoints.mdx
│ ├── ai-intelligence.mdx
│ ├── algorithms.mdx
│ ├── api/
│ │ ├── AviationService.openapi.json
│ │ ├── AviationService.openapi.yaml
│ │ ├── ClimateService.openapi.json
│ │ ├── ClimateService.openapi.yaml
│ │ ├── ConflictService.openapi.json
│ │ ├── ConflictService.openapi.yaml
│ │ ├── CyberService.openapi.json
│ │ ├── CyberService.openapi.yaml
│ │ ├── DisplacementService.openapi.json
│ │ ├── DisplacementService.openapi.yaml
│ │ ├── EconomicService.openapi.json
│ │ ├── EconomicService.openapi.yaml
│ │ ├── ForecastService.openapi.json
│ │ ├── ForecastService.openapi.yaml
│ │ ├── GivingService.openapi.json
│ │ ├── GivingService.openapi.yaml
│ │ ├── ImageryService.openapi.json
│ │ ├── ImageryService.openapi.yaml
│ │ ├── InfrastructureService.openapi.json
│ │ ├── InfrastructureService.openapi.yaml
│ │ ├── IntelligenceService.openapi.json
│ │ ├── IntelligenceService.openapi.yaml
│ │ ├── MaritimeService.openapi.json
│ │ ├── MaritimeService.openapi.yaml
│ │ ├── MarketService.openapi.json
│ │ ├── MarketService.openapi.yaml
│ │ ├── MilitaryService.openapi.json
│ │ ├── MilitaryService.openapi.yaml
│ │ ├── NaturalService.openapi.json
│ │ ├── NaturalService.openapi.yaml
│ │ ├── NewsService.openapi.json
│ │ ├── NewsService.openapi.yaml
│ │ ├── PositiveEventsService.openapi.json
│ │ ├── PositiveEventsService.openapi.yaml
│ │ ├── PredictionService.openapi.json
│ │ ├── PredictionService.openapi.yaml
│ │ ├── RadiationService.openapi.json
│ │ ├── RadiationService.openapi.yaml
│ │ ├── ResearchService.openapi.json
│ │ ├── ResearchService.openapi.yaml
│ │ ├── SanctionsService.openapi.json
│ │ ├── SanctionsService.openapi.yaml
│ │ ├── SeismologyService.openapi.json
│ │ ├── SeismologyService.openapi.yaml
│ │ ├── SupplyChainService.openapi.json
│ │ ├── SupplyChainService.openapi.yaml
│ │ ├── ThermalService.openapi.json
│ │ ├── ThermalService.openapi.yaml
│ │ ├── TradeService.openapi.json
│ │ ├── TradeService.openapi.yaml
│ │ ├── UnrestService.openapi.json
│ │ ├── UnrestService.openapi.yaml
│ │ ├── WebcamService.openapi.json
│ │ ├── WebcamService.openapi.yaml
│ │ ├── WildfireService.openapi.json
│ │ └── WildfireService.openapi.yaml
│ ├── api-key-deployment.mdx
│ ├── architecture.mdx
│ ├── changelog.mdx
│ ├── contributing.mdx
│ ├── cors.mdx
│ ├── country-instability-index.mdx
│ ├── data-sources.mdx
│ ├── desktop-app.mdx
│ ├── docs.json
│ ├── documentation.mdx
│ ├── features.mdx
│ ├── finance-data.mdx
│ ├── geographic-convergence.mdx
│ ├── getting-started.mdx
│ ├── harness-engineering-roadmap.md
│ ├── health-endpoints.mdx
│ ├── hotspots.mdx
│ ├── infrastructure-cascade.mdx
│ ├── license.mdx
│ ├── local-backend-audit.md
│ ├── map-engine.mdx
│ ├── maps-and-geocoding.mdx
│ ├── maritime-intelligence.mdx
│ ├── military-tracking.mdx
│ ├── natural-disasters.mdx
│ ├── orbital-surveillance.mdx
│ ├── overview.mdx
│ ├── premium-finance-search.mdx
│ ├── premium-finance.mdx
│ ├── relay-parameters.mdx
│ ├── release-packaging.mdx
│ ├── roadmap-pro.md
│ ├── signal-intelligence.mdx
│ ├── strategic-risk.mdx
│ ├── user-requests.md
│ └── webcam-layer.mdx
├── e2e/
│ ├── circuit-breaker-persistence.spec.ts
│ ├── deduct-situation.spec.ts
│ ├── investments-panel.spec.ts
│ ├── keyword-spike-flow.spec.ts
│ ├── map-harness.spec.ts
│ ├── mobile-map-native.spec.ts
│ ├── mobile-map-popup.spec.ts
│ ├── rag-vector-store.spec.ts
│ ├── runtime-fetch.spec.ts
│ ├── theme-toggle.spec.ts
│ ├── tsconfig.json
│ └── widget-builder.spec.ts
├── index.html
├── live-channels.html
├── middleware.ts
├── nixpacks.toml
├── package.json
├── playwright.config.ts
├── pro-test/
│ ├── .env.example
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── metadata.json
│ ├── package.json
│ ├── prerender.mjs
│ ├── src/
│ │ ├── App.tsx
│ │ ├── i18n.ts
│ │ ├── index.css
│ │ ├── locales/
│ │ │ ├── ar.json
│ │ │ ├── bg.json
│ │ │ ├── cs.json
│ │ │ ├── de.json
│ │ │ ├── el.json
│ │ │ ├── en.json
│ │ │ ├── es.json
│ │ │ ├── fr.json
│ │ │ ├── it.json
│ │ │ ├── ja.json
│ │ │ ├── ko.json
│ │ │ ├── nl.json
│ │ │ ├── pl.json
│ │ │ ├── pt.json
│ │ │ ├── ro.json
│ │ │ ├── ru.json
│ │ │ ├── sv.json
│ │ │ ├── th.json
│ │ │ ├── tr.json
│ │ │ ├── vi.json
│ │ │ └── zh.json
│ │ └── main.tsx
│ ├── tsconfig.json
│ └── vite.config.ts
├── proto/
│ ├── buf.gen.yaml
│ ├── buf.yaml
│ ├── sebuf/
│ │ └── http/
│ │ └── annotations.proto
│ └── worldmonitor/
│ ├── aviation/
│ │ └── v1/
│ │ ├── airport_delay.proto
│ │ ├── aviation_news_item.proto
│ │ ├── carrier.proto
│ │ ├── flight_instance.proto
│ │ ├── get_airport_ops_summary.proto
│ │ ├── get_carrier_ops.proto
│ │ ├── get_flight_status.proto
│ │ ├── list_airport_delays.proto
│ │ ├── list_airport_flights.proto
│ │ ├── list_aviation_news.proto
│ │ ├── position_sample.proto
│ │ ├── price_quote.proto
│ │ ├── search_flight_prices.proto
│ │ ├── service.proto
│ │ └── track_aircraft.proto
│ ├── climate/
│ │ └── v1/
│ │ ├── climate_anomaly.proto
│ │ ├── list_climate_anomalies.proto
│ │ └── service.proto
│ ├── conflict/
│ │ └── v1/
│ │ ├── acled_event.proto
│ │ ├── get_humanitarian_summary.proto
│ │ ├── get_humanitarian_summary_batch.proto
│ │ ├── humanitarian_summary.proto
│ │ ├── list_acled_events.proto
│ │ ├── list_iran_events.proto
│ │ ├── list_ucdp_events.proto
│ │ ├── service.proto
│ │ └── ucdp_event.proto
│ ├── core/
│ │ └── v1/
│ │ ├── country.proto
│ │ ├── general_error.proto
│ │ ├── geo.proto
│ │ ├── i18n.proto
│ │ ├── identifiers.proto
│ │ ├── pagination.proto
│ │ ├── severity.proto
│ │ └── time.proto
│ ├── cyber/
│ │ └── v1/
│ │ ├── cyber_threat.proto
│ │ ├── list_cyber_threats.proto
│ │ └── service.proto
│ ├── displacement/
│ │ └── v1/
│ │ ├── displacement.proto
│ │ ├── get_displacement_summary.proto
│ │ ├── get_population_exposure.proto
│ │ └── service.proto
│ ├── economic/
│ │ └── v1/
│ │ ├── bis_data.proto
│ │ ├── economic_data.proto
│ │ ├── get_bis_credit.proto
│ │ ├── get_bis_exchange_rates.proto
│ │ ├── get_bis_policy_rates.proto
│ │ ├── get_energy_capacity.proto
│ │ ├── get_energy_prices.proto
│ │ ├── get_fred_series.proto
│ │ ├── get_fred_series_batch.proto
│ │ ├── get_macro_signals.proto
│ │ ├── list_world_bank_indicators.proto
│ │ └── service.proto
│ ├── forecast/
│ │ └── v1/
│ │ ├── forecast.proto
│ │ ├── get_forecasts.proto
│ │ └── service.proto
│ ├── giving/
│ │ └── v1/
│ │ ├── get_giving_summary.proto
│ │ ├── giving.proto
│ │ └── service.proto
│ ├── imagery/
│ │ └── v1/
│ │ ├── search_imagery.proto
│ │ └── service.proto
│ ├── infrastructure/
│ │ └── v1/
│ │ ├── get_cable_health.proto
│ │ ├── get_temporal_baseline.proto
│ │ ├── infrastructure.proto
│ │ ├── list_internet_outages.proto
│ │ ├── list_service_statuses.proto
│ │ ├── list_temporal_anomalies.proto
│ │ ├── record_baseline_snapshot.proto
│ │ └── service.proto
│ ├── intelligence/
│ │ └── v1/
│ │ ├── classify_event.proto
│ │ ├── deduct_situation.proto
│ │ ├── get_country_facts.proto
│ │ ├── get_country_intel_brief.proto
│ │ ├── get_pizzint_status.proto
│ │ ├── get_risk_scores.proto
│ │ ├── intelligence.proto
│ │ ├── list_security_advisories.proto
│ │ ├── search_gdelt_documents.proto
│ │ └── service.proto
│ ├── maritime/
│ │ └── v1/
│ │ ├── get_vessel_snapshot.proto
│ │ ├── list_navigational_warnings.proto
│ │ ├── service.proto
│ │ └── vessel_snapshot.proto
│ ├── market/
│ │ └── v1/
│ │ ├── analyze_stock.proto
│ │ ├── backtest_stock.proto
│ │ ├── get_country_stock_index.proto
│ │ ├── get_sector_summary.proto
│ │ ├── get_stock_analysis_history.proto
│ │ ├── list_commodity_quotes.proto
│ │ ├── list_crypto_quotes.proto
│ │ ├── list_etf_flows.proto
│ │ ├── list_gulf_quotes.proto
│ │ ├── list_market_quotes.proto
│ │ ├── list_stablecoin_markets.proto
│ │ ├── list_stored_stock_backtests.proto
│ │ ├── market_quote.proto
│ │ └── service.proto
│ ├── military/
│ │ └── v1/
│ │ ├── get_aircraft_details.proto
│ │ ├── get_aircraft_details_batch.proto
│ │ ├── get_theater_posture.proto
│ │ ├── get_usni_fleet_report.proto
│ │ ├── get_wingbits_live_flight.proto
│ │ ├── get_wingbits_status.proto
│ │ ├── list_military_bases.proto
│ │ ├── list_military_flights.proto
│ │ ├── military_flight.proto
│ │ ├── military_vessel.proto
│ │ ├── service.proto
│ │ └── usni_fleet.proto
│ ├── natural/
│ │ └── v1/
│ │ ├── list_natural_events.proto
│ │ └── service.proto
│ ├── news/
│ │ └── v1/
│ │ ├── get_summarize_article_cache.proto
│ │ ├── list_feed_digest.proto
│ │ ├── news_item.proto
│ │ ├── service.proto
│ │ └── summarize_article.proto
│ ├── positive_events/
│ │ └── v1/
│ │ ├── list_positive_geo_events.proto
│ │ └── service.proto
│ ├── prediction/
│ │ └── v1/
│ │ ├── list_prediction_markets.proto
│ │ ├── prediction_market.proto
│ │ └── service.proto
│ ├── radiation/
│ │ └── v1/
│ │ ├── list_radiation_observations.proto
│ │ ├── radiation_observation.proto
│ │ └── service.proto
│ ├── research/
│ │ └── v1/
│ │ ├── list_arxiv_papers.proto
│ │ ├── list_hackernews_items.proto
│ │ ├── list_tech_events.proto
│ │ ├── list_trending_repos.proto
│ │ ├── research_item.proto
│ │ └── service.proto
│ ├── sanctions/
│ │ └── v1/
│ │ ├── country_sanctions_pressure.proto
│ │ ├── list_sanctions_pressure.proto
│ │ ├── program_sanctions_pressure.proto
│ │ ├── sanctions_entry.proto
│ │ └── service.proto
│ ├── seismology/
│ │ └── v1/
│ │ ├── earthquake.proto
│ │ ├── list_earthquakes.proto
│ │ └── service.proto
│ ├── supply_chain/
│ │ └── v1/
│ │ ├── get_chokepoint_status.proto
│ │ ├── get_critical_minerals.proto
│ │ ├── get_shipping_rates.proto
│ │ ├── service.proto
│ │ └── supply_chain_data.proto
│ ├── thermal/
│ │ └── v1/
│ │ ├── list_thermal_escalations.proto
│ │ ├── service.proto
│ │ └── thermal_escalation_cluster.proto
│ ├── trade/
│ │ └── v1/
│ │ ├── get_customs_revenue.proto
│ │ ├── get_tariff_trends.proto
│ │ ├── get_trade_barriers.proto
│ │ ├── get_trade_flows.proto
│ │ ├── get_trade_restrictions.proto
│ │ ├── service.proto
│ │ └── trade_data.proto
│ ├── unrest/
│ │ └── v1/
│ │ ├── list_unrest_events.proto
│ │ ├── service.proto
│ │ └── unrest_event.proto
│ ├── webcam/
│ │ └── v1/
│ │ ├── get_webcam_image.proto
│ │ ├── list_webcams.proto
│ │ └── service.proto
│ └── wildfire/
│ └── v1/
│ ├── fire_detection.proto
│ ├── list_fire_detections.proto
│ └── service.proto
├── public/
│ ├── .well-known/
│ │ └── security.txt
│ ├── a7f3e9d1b2c44e8f9a0b1c2d3e4f5a6b.txt
│ ├── data/
│ │ ├── countries.geojson
│ │ └── country-boundary-overrides.geojson
│ ├── llms-full.txt
│ ├── llms.txt
│ ├── map-styles/
│ │ ├── happy-dark.json
│ │ └── happy-light.json
│ ├── offline.html
│ ├── pro/
│ │ ├── assets/
│ │ │ ├── ar-BHa0nEOe.js
│ │ │ ├── bg-Ci69To5a.js
│ │ │ ├── cs-CqKhwIlR.js
│ │ │ ├── de-B71p-f-t.js
│ │ │ ├── el-DJwjBufy.js
│ │ │ ├── es-aR_qLKIk.js
│ │ │ ├── fr-BrtwTv_R.js
│ │ │ ├── index-DQXUpmjr.css
│ │ │ ├── index-k66dEz6-.js
│ │ │ ├── it-DHbGtQXZ.js
│ │ │ ├── ja-D8-35S3Y.js
│ │ │ ├── ko-otMG-p7A.js
│ │ │ ├── nl-B3DRC8p4.js
│ │ │ ├── pl-DqoCbf3Z.js
│ │ │ ├── pt-CqDblfWm.js
│ │ │ ├── ro-DaIMP80d.js
│ │ │ ├── ru-DN0TfVz-.js
│ │ │ ├── sv-B8YGwHj7.js
│ │ │ ├── th-Dx5iTAoX.js
│ │ │ ├── tr-DqKzKEKV.js
│ │ │ ├── vi-ByRwBJoF.js
│ │ │ └── zh-Cf0ddDO-.js
│ │ └── index.html
│ ├── robots.txt
│ └── sitemap.xml
├── scripts/
│ ├── _clustering.mjs
│ ├── _military-surges.mjs
│ ├── _prediction-scoring.mjs
│ ├── _r2-storage.mjs
│ ├── _seed-utils.mjs
│ ├── _trade-parse-utils.mjs
│ ├── ais-relay-rss.test.cjs
│ ├── ais-relay.cjs
│ ├── build-military-bases-final.mjs
│ ├── build-sidecar-handlers.mjs
│ ├── build-sidecar-sebuf.mjs
│ ├── check-unicode-safety.mjs
│ ├── data/
│ │ ├── cascade-rules.json
│ │ ├── country-codes.json
│ │ ├── curated-bases.json
│ │ ├── entity-graph.json
│ │ ├── forecast-evaluation-benchmark.json
│ │ ├── forecast-historical-benchmark.json
│ │ ├── mirta-processed.json
│ │ └── prediction-tags.json
│ ├── desktop-package.mjs
│ ├── download-node.sh
│ ├── evaluate-forecast-benchmark.mjs
│ ├── extract-forecast-benchmark-candidates.mjs
│ ├── fetch-country-boundary-overrides.mjs
│ ├── fetch-gpsjam.mjs
│ ├── fetch-mirta-bases.mjs
│ ├── fetch-osm-bases.mjs
│ ├── fetch-pizzint-bases.mjs
│ ├── generate-oref-locations.mjs
│ ├── lib/
│ │ └── thermal-escalation.mjs
│ ├── lint-boundaries.mjs
│ ├── need-work.csv
│ ├── nixpacks.toml
│ ├── package.json
│ ├── promote-forecast-benchmark-candidate.mjs
│ ├── railway-set-watch-paths.mjs
│ ├── rss-feeds-report.csv
│ ├── run-seeders.sh
│ ├── seed-airport-delays.mjs
│ ├── seed-aviation.mjs
│ ├── seed-bis-data.mjs
│ ├── seed-climate-anomalies.mjs
│ ├── seed-commodity-quotes.mjs
│ ├── seed-conflict-intel.mjs
│ ├── seed-correlation.mjs
│ ├── seed-crypto-quotes.mjs
│ ├── seed-cyber-threats.mjs
│ ├── seed-displacement-summary.mjs
│ ├── seed-earthquakes.mjs
│ ├── seed-economy.mjs
│ ├── seed-etf-flows.mjs
│ ├── seed-fire-detections.mjs
│ ├── seed-forecasts.mjs
│ ├── seed-gdelt-intel.mjs
│ ├── seed-gulf-quotes.mjs
│ ├── seed-infra.mjs
│ ├── seed-insights.mjs
│ ├── seed-internet-outages.mjs
│ ├── seed-iran-events.mjs
│ ├── seed-market-quotes.mjs
│ ├── seed-military-bases.mjs
│ ├── seed-military-flights.mjs
│ ├── seed-military-maritime-news.mjs
│ ├── seed-natural-events.mjs
│ ├── seed-prediction-markets.mjs
│ ├── seed-radiation-watch.mjs
│ ├── seed-research.mjs
│ ├── seed-sanctions-pressure.mjs
│ ├── seed-security-advisories.mjs
│ ├── seed-service-statuses.mjs
│ ├── seed-stablecoin-markets.mjs
│ ├── seed-submarine-cables.mjs
│ ├── seed-supply-chain-trade.mjs
│ ├── seed-thermal-escalation.mjs
│ ├── seed-ucdp-events.mjs
│ ├── seed-unrest-events.mjs
│ ├── seed-usa-spending.mjs
│ ├── seed-wb-indicators.mjs
│ ├── seed-weather-alerts.mjs
│ ├── seed-webcams.mjs
│ ├── seo-indexnow-submit.mjs
│ ├── shared/
│ │ ├── acled-oauth.mjs
│ │ ├── commodities.json
│ │ ├── country-names.json
│ │ ├── crypto.json
│ │ ├── etfs.json
│ │ ├── gulf.json
│ │ ├── rss-allowed-domains.cjs
│ │ ├── rss-allowed-domains.json
│ │ ├── sectors.json
│ │ ├── stablecoins.json
│ │ └── stocks.json
│ ├── sync-desktop-version.mjs
│ ├── telegram/
│ │ └── session-auth.mjs
│ ├── validate-rss-feeds.mjs
│ ├── validate-seed-migration.mjs
│ └── vercel-ignore.sh
├── server/
│ ├── _shared/
│ │ ├── acled-auth.ts
│ │ ├── acled.ts
│ │ ├── cache-keys.ts
│ │ ├── constants.ts
│ │ ├── hash.ts
│ │ ├── llm-health.ts
│ │ ├── llm-sanitize.d.ts
│ │ ├── llm-sanitize.js
│ │ ├── llm.ts
│ │ ├── normalize-list.ts
│ │ ├── parse-string-array.ts
│ │ ├── rate-limit.ts
│ │ ├── redis.ts
│ │ ├── response-headers.ts
│ │ └── sidecar-cache.ts
│ ├── cors.ts
│ ├── env.d.ts
│ ├── error-mapper.ts
│ ├── gateway.ts
│ ├── router.ts
│ └── worldmonitor/
│ ├── _bootstrap-cache-key-refs.ts
│ ├── aviation/
│ │ └── v1/
│ │ ├── _providers/
│ │ │ ├── demo_prices.ts
│ │ │ └── travelpayouts_data.ts
│ │ ├── _shared.ts
│ │ ├── get-airport-ops-summary.ts
│ │ ├── get-carrier-ops.ts
│ │ ├── get-flight-status.ts
│ │ ├── handler.ts
│ │ ├── list-airport-delays.ts
│ │ ├── list-airport-flights.ts
│ │ ├── list-aviation-news.ts
│ │ ├── search-flight-prices.ts
│ │ └── track-aircraft.ts
│ ├── climate/
│ │ └── v1/
│ │ ├── handler.ts
│ │ └── list-climate-anomalies.ts
│ ├── conflict/
│ │ └── v1/
│ │ ├── _shared.ts
│ │ ├── get-humanitarian-summary-batch.ts
│ │ ├── get-humanitarian-summary.ts
│ │ ├── handler.ts
│ │ ├── list-acled-events.ts
│ │ ├── list-iran-events.ts
│ │ └── list-ucdp-events.ts
│ ├── cyber/
│ │ └── v1/
│ │ ├── _shared.ts
│ │ ├── handler.ts
│ │ └── list-cyber-threats.ts
│ ├── displacement/
│ │ └── v1/
│ │ ├── get-displacement-summary.ts
│ │ ├── get-population-exposure.ts
│ │ └── handler.ts
│ ├── economic/
│ │ └── v1/
│ │ ├── _bis-shared.ts
│ │ ├── _fetch-with-timeout.ts
│ │ ├── _fred-shared.ts
│ │ ├── _shared.ts
│ │ ├── get-bis-credit.ts
│ │ ├── get-bis-exchange-rates.ts
│ │ ├── get-bis-policy-rates.ts
│ │ ├── get-energy-capacity.ts
│ │ ├── get-energy-prices.ts
│ │ ├── get-fred-series-batch.ts
│ │ ├── get-fred-series.ts
│ │ ├── get-macro-signals.ts
│ │ ├── handler.ts
│ │ └── list-world-bank-indicators.ts
│ ├── forecast/
│ │ └── v1/
│ │ ├── get-forecasts.ts
│ │ └── handler.ts
│ ├── giving/
│ │ └── v1/
│ │ ├── get-giving-summary.ts
│ │ └── handler.ts
│ ├── imagery/
│ │ └── v1/
│ │ ├── handler.ts
│ │ └── search-imagery.ts
│ ├── infrastructure/
│ │ └── v1/
│ │ ├── _shared.ts
│ │ ├── get-cable-health.ts
│ │ ├── get-temporal-baseline.ts
│ │ ├── handler.ts
│ │ ├── list-internet-outages.ts
│ │ ├── list-service-statuses.ts
│ │ ├── list-temporal-anomalies.ts
│ │ └── record-baseline-snapshot.ts
│ ├── intelligence/
│ │ └── v1/
│ │ ├── _batch-classify.ts
│ │ ├── _shared.ts
│ │ ├── classify-event.ts
│ │ ├── deduct-situation.ts
│ │ ├── deduction-prompt.ts
│ │ ├── get-country-facts.ts
│ │ ├── get-country-intel-brief.ts
│ │ ├── get-pizzint-status.ts
│ │ ├── get-risk-scores.ts
│ │ ├── handler.ts
│ │ ├── list-security-advisories.ts
│ │ └── search-gdelt-documents.ts
│ ├── maritime/
│ │ └── v1/
│ │ ├── get-vessel-snapshot.ts
│ │ ├── handler.ts
│ │ └── list-navigational-warnings.ts
│ ├── market/
│ │ └── v1/
│ │ ├── _shared.ts
│ │ ├── analyze-stock.ts
│ │ ├── backtest-stock.ts
│ │ ├── get-country-stock-index.ts
│ │ ├── get-sector-summary.ts
│ │ ├── get-stock-analysis-history.ts
│ │ ├── handler.ts
│ │ ├── list-commodity-quotes.ts
│ │ ├── list-crypto-quotes.ts
│ │ ├── list-etf-flows.ts
│ │ ├── list-gulf-quotes.ts
│ │ ├── list-market-quotes.ts
│ │ ├── list-stablecoin-markets.ts
│ │ ├── list-stored-stock-backtests.ts
│ │ ├── premium-stock-store.ts
│ │ └── stock-news-search.ts
│ ├── military/
│ │ └── v1/
│ │ ├── _shared.ts
│ │ ├── _wingbits-aircraft-details.ts
│ │ ├── get-aircraft-details-batch.ts
│ │ ├── get-aircraft-details.ts
│ │ ├── get-theater-posture.ts
│ │ ├── get-usni-fleet-report.ts
│ │ ├── get-wingbits-live-flight.ts
│ │ ├── get-wingbits-status.ts
│ │ ├── handler.ts
│ │ ├── list-military-bases.ts
│ │ └── list-military-flights.ts
│ ├── natural/
│ │ └── v1/
│ │ ├── handler.ts
│ │ └── list-natural-events.ts
│ ├── news/
│ │ └── v1/
│ │ ├── _classifier.ts
│ │ ├── _feeds.ts
│ │ ├── _shared.ts
│ │ ├── dedup.mjs
│ │ ├── get-summarize-article-cache.ts
│ │ ├── handler.ts
│ │ ├── list-feed-digest.ts
│ │ └── summarize-article.ts
│ ├── positive-events/
│ │ └── v1/
│ │ ├── handler.ts
│ │ └── list-positive-geo-events.ts
│ ├── prediction/
│ │ └── v1/
│ │ ├── handler.ts
│ │ └── list-prediction-markets.ts
│ ├── radiation/
│ │ └── v1/
│ │ ├── handler.ts
│ │ └── list-radiation-observations.ts
│ ├── research/
│ │ └── v1/
│ │ ├── handler.ts
│ │ ├── list-arxiv-papers.ts
│ │ ├── list-hackernews-items.ts
│ │ ├── list-tech-events.ts
│ │ └── list-trending-repos.ts
│ ├── sanctions/
│ │ └── v1/
│ │ ├── handler.ts
│ │ └── list-sanctions-pressure.ts
│ ├── seismology/
│ │ └── v1/
│ │ ├── handler.ts
│ │ └── list-earthquakes.ts
│ ├── supply-chain/
│ │ └── v1/
│ │ ├── _chokepoint-ids.ts
│ │ ├── _corridorrisk-upstream.ts
│ │ ├── _minerals-data.ts
│ │ ├── _portwatch-upstream.ts
│ │ ├── _scoring.mjs
│ │ ├── get-chokepoint-status.ts
│ │ ├── get-critical-minerals.ts
│ │ ├── get-shipping-rates.ts
│ │ └── handler.ts
│ ├── thermal/
│ │ └── v1/
│ │ ├── handler.ts
│ │ └── list-thermal-escalations.ts
│ ├── trade/
│ │ └── v1/
│ │ ├── _shared.ts
│ │ ├── get-customs-revenue.ts
│ │ ├── get-tariff-trends.ts
│ │ ├── get-trade-barriers.ts
│ │ ├── get-trade-flows.ts
│ │ ├── get-trade-restrictions.ts
│ │ └── handler.ts
│ ├── unrest/
│ │ └── v1/
│ │ ├── _shared.ts
│ │ ├── handler.ts
│ │ └── list-unrest-events.ts
│ ├── webcam/
│ │ └── v1/
│ │ ├── get-webcam-image.ts
│ │ ├── handler.ts
│ │ └── list-webcams.ts
│ └── wildfire/
│ └── v1/
│ ├── handler.ts
│ └── list-fire-detections.ts
├── settings.html
├── shared/
│ ├── commodities.json
│ ├── country-names.json
│ ├── crypto.json
│ ├── etfs.json
│ ├── gulf.json
│ ├── rss-allowed-domains.cjs
│ ├── rss-allowed-domains.json
│ ├── sectors.json
│ ├── stablecoins.json
│ └── stocks.json
├── src/
│ ├── App.ts
│ ├── app/
│ │ ├── app-context.ts
│ │ ├── country-intel.ts
│ │ ├── data-loader.ts
│ │ ├── desktop-updater.ts
│ │ ├── event-handlers.ts
│ │ ├── index.ts
│ │ ├── panel-layout.ts
│ │ ├── pending-panel-data.ts
│ │ ├── refresh-scheduler.ts
│ │ └── search-manager.ts
│ ├── bootstrap/
│ │ └── chunk-reload.ts
│ ├── components/
│ │ ├── AirlineIntelPanel.ts
│ │ ├── AviationCommandBar.ts
│ │ ├── BreakingNewsBanner.ts
│ │ ├── BreakthroughsTickerPanel.ts
│ │ ├── CIIPanel.ts
│ │ ├── CascadePanel.ts
│ │ ├── ClimateAnomalyPanel.ts
│ │ ├── CommunityWidget.ts
│ │ ├── CorrelationPanel.ts
│ │ ├── CountersPanel.ts
│ │ ├── CountryBriefPage.ts
│ │ ├── CountryBriefPanel.ts
│ │ ├── CountryDeepDivePanel.ts
│ │ ├── CountryIntelModal.ts
│ │ ├── CountryTimeline.ts
│ │ ├── CustomWidgetPanel.ts
│ │ ├── DailyMarketBriefPanel.ts
│ │ ├── DeckGLMap.ts
│ │ ├── DeductionPanel.ts
│ │ ├── DisasterCorrelationPanel.ts
│ │ ├── DisplacementPanel.ts
│ │ ├── DownloadBanner.ts
│ │ ├── ETFFlowsPanel.ts
│ │ ├── EconomicCorrelationPanel.ts
│ │ ├── EconomicPanel.ts
│ │ ├── EnergyComplexPanel.ts
│ │ ├── EscalationCorrelationPanel.ts
│ │ ├── ForecastPanel.ts
│ │ ├── GdeltIntelPanel.ts
│ │ ├── GeoHubsPanel.ts
│ │ ├── GivingPanel.ts
│ │ ├── GlobeMap.ts
│ │ ├── GoodThingsDigestPanel.ts
│ │ ├── GulfEconomiesPanel.ts
│ │ ├── HeroSpotlightPanel.ts
│ │ ├── InsightsPanel.ts
│ │ ├── IntelligenceGapBadge.ts
│ │ ├── InvestmentsPanel.ts
│ │ ├── LiveNewsPanel.ts
│ │ ├── LiveWebcamsPanel.ts
│ │ ├── LlmStatusIndicator.ts
│ │ ├── MacroSignalsPanel.ts
│ │ ├── Map.ts
│ │ ├── MapContainer.ts
│ │ ├── MapContextMenu.ts
│ │ ├── MapPopup.ts
│ │ ├── MarketPanel.ts
│ │ ├── McpConnectModal.ts
│ │ ├── McpDataPanel.ts
│ │ ├── MilitaryCorrelationPanel.ts
│ │ ├── MobileWarningModal.ts
│ │ ├── MonitorPanel.ts
│ │ ├── NewsPanel.ts
│ │ ├── OrefSirensPanel.ts
│ │ ├── Panel.ts
│ │ ├── PinnedWebcamsPanel.ts
│ │ ├── PizzIntIndicator.ts
│ │ ├── PlaybackControl.ts
│ │ ├── PopulationExposurePanel.ts
│ │ ├── PositiveNewsFeedPanel.ts
│ │ ├── PredictionPanel.ts
│ │ ├── ProBanner.ts
│ │ ├── ProgressChartsPanel.ts
│ │ ├── RadiationWatchPanel.ts
│ │ ├── RegulationPanel.ts
│ │ ├── RenewableEnergyPanel.ts
│ │ ├── RuntimeConfigPanel.ts
│ │ ├── SanctionsPressurePanel.ts
│ │ ├── SatelliteFiresPanel.ts
│ │ ├── SearchModal.ts
│ │ ├── SecurityAdvisoriesPanel.ts
│ │ ├── ServiceStatusPanel.ts
│ │ ├── SignalModal.ts
│ │ ├── SpeciesComebackPanel.ts
│ │ ├── StablecoinPanel.ts
│ │ ├── StatusPanel.ts
│ │ ├── StockAnalysisPanel.ts
│ │ ├── StockBacktestPanel.ts
│ │ ├── StoryModal.ts
│ │ ├── StrategicPosturePanel.ts
│ │ ├── StrategicRiskPanel.ts
│ │ ├── SupplyChainPanel.ts
│ │ ├── TechEventsPanel.ts
│ │ ├── TechHubsPanel.ts
│ │ ├── TechReadinessPanel.ts
│ │ ├── TelegramIntelPanel.ts
│ │ ├── ThermalEscalationPanel.ts
│ │ ├── TradePolicyPanel.ts
│ │ ├── UcdpEventsPanel.ts
│ │ ├── UnifiedSettings.ts
│ │ ├── VerificationChecklist.ts
│ │ ├── VirtualList.ts
│ │ ├── WidgetChatModal.ts
│ │ ├── WorldClockPanel.ts
│ │ └── index.ts
│ ├── config/
│ │ ├── ai-datacenters.ts
│ │ ├── ai-regulations.ts
│ │ ├── ai-research-labs.ts
│ │ ├── airports.ts
│ │ ├── basemap.ts
│ │ ├── bases-expanded.ts
│ │ ├── beta.ts
│ │ ├── commands.ts
│ │ ├── commodity-geo.ts
│ │ ├── commodity-markets.ts
│ │ ├── commodity-miners.ts
│ │ ├── countries.ts
│ │ ├── entities.ts
│ │ ├── feeds.ts
│ │ ├── finance-geo.ts
│ │ ├── geo.ts
│ │ ├── gulf-fdi.ts
│ │ ├── index.ts
│ │ ├── irradiators.ts
│ │ ├── map-layer-definitions.ts
│ │ ├── markets.ts
│ │ ├── military.ts
│ │ ├── ml-config.ts
│ │ ├── panels.ts
│ │ ├── pipelines.ts
│ │ ├── ports.ts
│ │ ├── startup-ecosystems.ts
│ │ ├── tech-companies.ts
│ │ ├── tech-geo.ts
│ │ ├── trade-routes.ts
│ │ ├── variant-meta.ts
│ │ ├── variant.ts
│ │ └── variants/
│ │ ├── base.ts
│ │ ├── commodity.ts
│ │ ├── finance.ts
│ │ ├── full.ts
│ │ ├── happy.ts
│ │ └── tech.ts
│ ├── data/
│ │ ├── conservation-wins.json
│ │ ├── renewable-installations.json
│ │ └── world-happiness.json
│ ├── e2e/
│ │ ├── map-harness.ts
│ │ ├── mobile-map-harness.ts
│ │ └── mobile-map-integration-harness.ts
│ ├── generated/
│ │ ├── client/
│ │ │ └── worldmonitor/
│ │ │ ├── aviation/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── climate/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── conflict/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── cyber/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── displacement/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── economic/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── forecast/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── giving/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── imagery/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── infrastructure/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── intelligence/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── maritime/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── market/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── military/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── natural/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── news/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── positive_events/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── prediction/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── radiation/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── research/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── sanctions/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── seismology/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── supply_chain/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── thermal/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── trade/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── unrest/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ ├── webcam/
│ │ │ │ └── v1/
│ │ │ │ └── service_client.ts
│ │ │ └── wildfire/
│ │ │ └── v1/
│ │ │ └── service_client.ts
│ │ └── server/
│ │ └── worldmonitor/
│ │ ├── aviation/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── climate/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── conflict/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── cyber/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── displacement/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── economic/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── forecast/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── giving/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── imagery/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── infrastructure/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── intelligence/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── maritime/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── market/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── military/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── natural/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── news/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── positive_events/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── prediction/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── radiation/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── research/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── sanctions/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── seismology/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── supply_chain/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── thermal/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── trade/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── unrest/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ ├── webcam/
│ │ │ └── v1/
│ │ │ └── service_server.ts
│ │ └── wildfire/
│ │ └── v1/
│ │ └── service_server.ts
│ ├── live-channels-main.ts
│ ├── live-channels-window.ts
│ ├── locales/
│ │ ├── ar.d.ts
│ │ ├── ar.json
│ │ ├── bg.json
│ │ ├── cs.json
│ │ ├── de.json
│ │ ├── el.json
│ │ ├── en.json
│ │ ├── es.d.ts
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── it.d.ts
│ │ ├── it.json
│ │ ├── ja.json
│ │ ├── ko.json
│ │ ├── nl.d.ts
│ │ ├── nl.json
│ │ ├── pl.d.ts
│ │ ├── pl.json
│ │ ├── pt.d.ts
│ │ ├── pt.json
│ │ ├── ro.json
│ │ ├── ru.d.ts
│ │ ├── ru.json
│ │ ├── sv.d.ts
│ │ ├── sv.json
│ │ ├── th.d.ts
│ │ ├── th.json
│ │ ├── tr.d.ts
│ │ ├── tr.json
│ │ ├── vi.d.ts
│ │ ├── vi.json
│ │ ├── zh.d.ts
│ │ └── zh.json
│ ├── main.ts
│ ├── pwa.d.ts
│ ├── services/
│ │ ├── activity-tracker.ts
│ │ ├── ai-classify-queue.ts
│ │ ├── ai-flow-settings.ts
│ │ ├── analysis-core.ts
│ │ ├── analysis-worker.ts
│ │ ├── analytics.ts
│ │ ├── aviation/
│ │ │ ├── index.ts
│ │ │ └── watchlist.ts
│ │ ├── bootstrap.ts
│ │ ├── breaking-news-alerts.ts
│ │ ├── cable-activity.ts
│ │ ├── cable-health.ts
│ │ ├── cached-risk-scores.ts
│ │ ├── cached-theater-posture.ts
│ │ ├── celebration.ts
│ │ ├── climate/
│ │ │ └── index.ts
│ │ ├── clustering.ts
│ │ ├── conflict/
│ │ │ └── index.ts
│ │ ├── conservation-data.ts
│ │ ├── correlation-engine/
│ │ │ ├── adapters/
│ │ │ │ ├── disaster.ts
│ │ │ │ ├── economic.ts
│ │ │ │ ├── escalation.ts
│ │ │ │ └── military.ts
│ │ │ ├── engine.ts
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── correlation.ts
│ │ ├── country-geometry.ts
│ │ ├── country-instability.ts
│ │ ├── cross-module-integration.ts
│ │ ├── cyber/
│ │ │ └── index.ts
│ │ ├── daily-market-brief.ts
│ │ ├── data-freshness.ts
│ │ ├── desktop-readiness.ts
│ │ ├── displacement/
│ │ │ └── index.ts
│ │ ├── earthquakes.ts
│ │ ├── economic/
│ │ │ └── index.ts
│ │ ├── entity-extraction.ts
│ │ ├── entity-index.ts
│ │ ├── eonet.ts
│ │ ├── feed-date.ts
│ │ ├── focal-point-detector.ts
│ │ ├── font-settings.ts
│ │ ├── forecast.ts
│ │ ├── gdelt-intel.ts
│ │ ├── geo-activity.ts
│ │ ├── geo-convergence.ts
│ │ ├── geo-hub-index.ts
│ │ ├── giving/
│ │ │ └── index.ts
│ │ ├── globe-render-settings.ts
│ │ ├── gps-interference.ts
│ │ ├── happiness-data.ts
│ │ ├── happy-share-renderer.ts
│ │ ├── hotspot-escalation.ts
│ │ ├── hub-activity-scoring.ts
│ │ ├── humanity-counters.ts
│ │ ├── i18n.ts
│ │ ├── imagery.ts
│ │ ├── index.ts
│ │ ├── infrastructure/
│ │ │ └── index.ts
│ │ ├── infrastructure-cascade.ts
│ │ ├── insights-loader.ts
│ │ ├── intelligence/
│ │ │ └── index.ts
│ │ ├── investments-focus.ts
│ │ ├── kindness-data.ts
│ │ ├── live-news.ts
│ │ ├── live-stream-settings.ts
│ │ ├── maritime/
│ │ │ └── index.ts
│ │ ├── market/
│ │ │ └── index.ts
│ │ ├── market-watchlist.ts
│ │ ├── mcp-store.ts
│ │ ├── meta-tags.ts
│ │ ├── military/
│ │ │ └── index.ts
│ │ ├── military-bases.ts
│ │ ├── military-flights.ts
│ │ ├── military-surge.ts
│ │ ├── military-vessels.ts
│ │ ├── ml-capabilities.ts
│ │ ├── ml-worker.ts
│ │ ├── news/
│ │ │ └── index.ts
│ │ ├── ollama-models.ts
│ │ ├── oref-alerts.ts
│ │ ├── oref-locations.ts
│ │ ├── parallel-analysis.ts
│ │ ├── persistent-cache.ts
│ │ ├── pizzint.ts
│ │ ├── population-exposure.ts
│ │ ├── positive-classifier.ts
│ │ ├── positive-events-geo.ts
│ │ ├── prediction/
│ │ │ └── index.ts
│ │ ├── preferences-content.ts
│ │ ├── progress-data.ts
│ │ ├── radiation.ts
│ │ ├── related-assets.ts
│ │ ├── renewable-energy-data.ts
│ │ ├── renewable-installations.ts
│ │ ├── research/
│ │ │ └── index.ts
│ │ ├── rpc-client.ts
│ │ ├── rss.ts
│ │ ├── runtime-config.ts
│ │ ├── runtime.ts
│ │ ├── sanctions-pressure.ts
│ │ ├── satellites.ts
│ │ ├── security-advisories.ts
│ │ ├── sentiment-gate.ts
│ │ ├── settings-constants.ts
│ │ ├── settings-manager.ts
│ │ ├── signal-aggregator.ts
│ │ ├── stock-analysis-history.ts
│ │ ├── stock-analysis.ts
│ │ ├── stock-backtest.ts
│ │ ├── storage.ts
│ │ ├── story-data.ts
│ │ ├── story-renderer.ts
│ │ ├── story-share.ts
│ │ ├── summarization.ts
│ │ ├── supply-chain/
│ │ │ └── index.ts
│ │ ├── tauri-bridge.ts
│ │ ├── tech-activity.ts
│ │ ├── tech-hub-index.ts
│ │ ├── telegram-intel.ts
│ │ ├── temporal-baseline.ts
│ │ ├── thermal-escalation.ts
│ │ ├── threat-classifier.ts
│ │ ├── throttled-target-requests.ts
│ │ ├── trade/
│ │ │ └── index.ts
│ │ ├── trending-keywords.ts
│ │ ├── tv-mode.ts
│ │ ├── unrest/
│ │ │ └── index.ts
│ │ ├── usa-spending.ts
│ │ ├── usni-fleet.ts
│ │ ├── velocity.ts
│ │ ├── weather.ts
│ │ ├── webcams/
│ │ │ ├── index.ts
│ │ │ └── pinned-store.ts
│ │ ├── widget-store.ts
│ │ ├── wildfires/
│ │ │ └── index.ts
│ │ └── wingbits.ts
│ ├── settings-main.ts
│ ├── settings-window.ts
│ ├── shims/
│ │ ├── child-process-proxy.ts
│ │ └── child-process.ts
│ ├── styles/
│ │ ├── base-layer.css
│ │ ├── country-deep-dive.css
│ │ ├── happy-theme.css
│ │ ├── main.css
│ │ ├── map-context-menu.css
│ │ ├── panels.css
│ │ ├── rtl-overrides.css
│ │ └── settings-window.css
│ ├── types/
│ │ └── index.ts
│ ├── utils/
│ │ ├── analysis-constants.ts
│ │ ├── circuit-breaker.ts
│ │ ├── country-flag.ts
│ │ ├── cross-domain-storage.ts
│ │ ├── distance.ts
│ │ ├── dom-utils.ts
│ │ ├── export.ts
│ │ ├── hash.ts
│ │ ├── imagery-preview.ts
│ │ ├── index.ts
│ │ ├── keyword-match.ts
│ │ ├── layer-warning.ts
│ │ ├── map-locale.ts
│ │ ├── news-context.ts
│ │ ├── proxy.ts
│ │ ├── reverse-geocode.ts
│ │ ├── sanitize.ts
│ │ ├── settings-persistence.ts
│ │ ├── sparkline.ts
│ │ ├── storage-quota.ts
│ │ ├── summary-cache-key.ts
│ │ ├── theme-colors.ts
│ │ ├── theme-manager.ts
│ │ ├── transit-chart.ts
│ │ ├── urlState.ts
│ │ ├── user-location.ts
│ │ ├── utm.ts
│ │ └── widget-sanitizer.ts
│ ├── vite-env.d.ts
│ └── workers/
│ ├── analysis.worker.ts
│ ├── ml.worker.ts
│ └── vector-db.ts
├── src-tauri/
│ ├── .cargo/
│ │ ├── config.local.toml.example
│ │ └── config.toml
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── build.rs
│ ├── capabilities/
│ │ ├── default.json
│ │ └── youtube-login.json
│ ├── icons/
│ │ └── icon.icns
│ ├── nsis/
│ │ └── installer-hooks.nsh
│ ├── sidecar/
│ │ ├── local-api-server.mjs
│ │ ├── local-api-server.test.mjs
│ │ ├── node/
│ │ │ └── .gitkeep
│ │ └── package.json
│ ├── src/
│ │ └── main.rs
│ ├── tauri.conf.json
│ ├── tauri.finance.conf.json
│ └── tauri.tech.conf.json
├── tests/
│ ├── bootstrap.test.mjs
│ ├── chokepoint-id-mapping.test.mjs
│ ├── chokepoint-transit-counter.test.mjs
│ ├── cii-scoring.test.mts
│ ├── circuit-breaker-persistent-stale-ceiling.test.mts
│ ├── clustering.test.mjs
│ ├── contact-handler.test.mjs
│ ├── corridorrisk-upstream.test.mjs
│ ├── countries-geojson.test.mjs
│ ├── country-geometry-overrides.test.mts
│ ├── crypto-config.test.mjs
│ ├── customs-revenue.test.mjs
│ ├── daily-market-brief.test.mts
│ ├── deckgl-layer-state-aliasing.test.mjs
│ ├── deduction-prompt.test.mjs
│ ├── deploy-config.test.mjs
│ ├── digest-no-reclassify.test.mjs
│ ├── download-handler.test.mjs
│ ├── edge-functions.test.mjs
│ ├── escalation-country-merge.test.mts
│ ├── flush-stale-refreshes.test.mjs
│ ├── forecast-detectors.test.mjs
│ ├── forecast-history.test.mjs
│ ├── forecast-trace-export.test.mjs
│ ├── freight-indices.test.mjs
│ ├── geo-keyword-matching.test.mts
│ ├── globe-2d-3d-parity.test.mjs
│ ├── globe-tooltip-enrichment.test.mjs
│ ├── gulf-fdi-data.test.mjs
│ ├── handlers.test.mts
│ ├── hapi-gdelt-circuit-breakers.test.mjs
│ ├── helpers/
│ │ ├── llm-health-stub.ts
│ │ └── runtime-config-panel-harness.mjs
│ ├── insights-loader.test.mjs
│ ├── lint-md-script-scope.test.mjs
│ ├── live-news-hls.test.mjs
│ ├── llm-sanitize.test.mjs
│ ├── map-fullscreen-resize.test.mjs
│ ├── map-harness.html
│ ├── map-locale.test.mts
│ ├── market-quote-cache-keying.test.mjs
│ ├── market-service-symbol-casing.test.mjs
│ ├── mdx-lint.test.mjs
│ ├── military-classification.test.mjs
│ ├── military-flight-classification.test.mjs
│ ├── military-surges.test.mjs
│ ├── mobile-map-harness.html
│ ├── mobile-map-integration-harness.html
│ ├── oref-breaking.test.mjs
│ ├── oref-locations.test.mjs
│ ├── oref-proxy.test.mjs
│ ├── panel-config-guardrails.test.mjs
│ ├── portwatch-upstream.test.mjs
│ ├── prediction-scoring.test.mjs
│ ├── premium-stock-gateway.test.mts
│ ├── redis-caching.test.mjs
│ ├── relay-helper.test.mjs
│ ├── route-cache-tier.test.mjs
│ ├── runtime-config-panel-visibility.test.mjs
│ ├── runtime-env-guards.test.mjs
│ ├── runtime-harness.html
│ ├── sanctions-pressure.test.mjs
│ ├── sanctions-seed-unit.test.mjs
│ ├── seed-utils.test.mjs
│ ├── seed-warm-ping-origin.test.mjs
│ ├── server-handlers.test.mjs
│ ├── shared-llm.test.mts
│ ├── smart-poll-loop.test.mjs
│ ├── stock-analysis-history.test.mts
│ ├── stock-analysis.test.mts
│ ├── stock-backtest.test.mts
│ ├── stock-news-search.test.mts
│ ├── summarize-reasoning.test.mjs
│ ├── supply-chain-handlers.test.mjs
│ ├── supply-chain-panel-transit-chart.test.mjs
│ ├── supply-chain-v2.test.mjs
│ ├── tech-readiness-circuit-breakers.test.mjs
│ ├── thermal-escalation-handler-guardrail.test.mjs
│ ├── thermal-escalation-model.test.mjs
│ ├── trade-policy-tariffs.test.mjs
│ ├── transit-summaries.test.mjs
│ ├── ttl-acled-ais-guards.test.mjs
│ ├── ucdp-seed-resilience.test.mjs
│ ├── urlState.test.mts
│ ├── variant-layer-guardrail.test.mjs
│ └── widget-builder.test.mjs
├── tsconfig.api.json
├── tsconfig.json
├── vercel.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
node_modules
dist
.git
.github
.windsurf
.agent
.agents
.claude
.factory
.planning
e2e
src-tauri/target
src-tauri/sidecar/node
*.log
*.md
!README.md
docs/internal
docs/Docs_To_Review
tests
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: Report a bug in World Monitor
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a bug! Please fill out the sections below so we can reproduce and fix it.
- type: dropdown
id: variant
attributes:
label: Variant
description: Which variant are you using?
options:
- worldmonitor.app (Full / Geopolitical)
- tech.worldmonitor.app (Tech / Startup)
- finance.worldmonitor.app (Finance)
- Desktop app (Windows)
- Desktop app (macOS)
- Desktop app (Linux)
validations:
required: true
- type: dropdown
id: area
attributes:
label: Affected area
description: Which part of the app is affected?
options:
- Map / Globe
- News panels / RSS feeds
- AI Insights / World Brief
- Market Radar / Crypto
- Service Status
- Trending Keywords
- Country Brief pages
- Live video streams
- Desktop app (Tauri)
- Settings / API keys
- Settings / LLMs (Ollama, Groq, OpenRouter)
- Live webcams
- Other
validations:
required: true
- type: textarea
id: description
attributes:
label: Bug description
description: A clear description of what the bug is.
placeholder: Describe the bug...
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: What you expected to happen.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots / Console errors
description: If applicable, add screenshots or paste browser console errors.
- type: input
id: browser
attributes:
label: Browser & OS
description: e.g. Chrome 120 on Windows 11, Safari 17 on macOS Sonoma
placeholder: Chrome 120 on Windows 11
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Documentation
url: https://github.com/koala73/worldmonitor/blob/main/docs/DOCUMENTATION.md
about: Read the full documentation before opening an issue
- name: Discussions
url: https://github.com/koala73/worldmonitor/discussions
about: Ask questions and share ideas in Discussions
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature Request
description: Suggest a new feature or improvement
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Have an idea for World Monitor? We'd love to hear it!
- type: dropdown
id: area
attributes:
label: Feature area
description: Which area does this feature relate to?
options:
- Map / Globe / Data layers
- News panels / RSS feeds
- AI / Intelligence analysis
- Market data / Crypto
- Desktop app
- UI / UX
- API / Backend
- Other
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: A clear description of the feature you'd like.
placeholder: I'd like to see...
validations:
required: true
- type: textarea
id: problem
attributes:
label: Problem it solves
description: What problem does this feature address? What's the use case?
placeholder: This would help with...
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: Have you considered any alternative solutions or workarounds?
- type: textarea
id: context
attributes:
label: Additional context
description: Any mockups, screenshots, links, or references that help illustrate the idea.
================================================
FILE: .github/ISSUE_TEMPLATE/new_data_source.yml
================================================
name: New Data Source
description: Suggest a new RSS feed, API, or map layer
labels: ["data-source"]
body:
- type: markdown
attributes:
value: |
World Monitor aggregates 100+ feeds and data layers. Suggest a new one!
- type: dropdown
id: type
attributes:
label: Source type
description: What kind of data source is this?
options:
- RSS / News feed
- API integration
- Map layer (geospatial data)
- Live video stream
- Status page
- Other
validations:
required: true
- type: dropdown
id: variant
attributes:
label: Target variant
description: Which variant should this appear in?
options:
- Full (Geopolitical)
- Tech (Startup)
- Finance
- All variants
validations:
required: true
- type: input
id: source-name
attributes:
label: Source name
description: Name of the source or organization.
placeholder: e.g. RAND Corporation, CoinDesk, USGS
validations:
required: true
- type: input
id: url
attributes:
label: Feed / API URL
description: Direct URL to the RSS feed, API endpoint, or data source.
placeholder: https://example.com/rss
validations:
required: true
- type: textarea
id: description
attributes:
label: Why add this source?
description: What value does this source bring? What does it cover that existing sources don't?
placeholder: This source provides coverage of...
validations:
required: true
- type: textarea
id: notes
attributes:
label: Additional notes
description: Any details about rate limits, authentication requirements, data format, or category placement.
================================================
FILE: .github/pull_request_template.md
================================================
## Summary
<!-- Brief description of what this PR does -->
## Type of change
- [ ] Bug fix
- [ ] New feature
- [ ] New data source / feed
- [ ] New map layer
- [ ] Refactor / code cleanup
- [ ] Documentation
- [ ] CI / Build / Infrastructure
## Affected areas
- [ ] Map / Globe
- [ ] News panels / RSS feeds
- [ ] AI Insights / World Brief
- [ ] Market Radar / Crypto
- [ ] Desktop app (Tauri)
- [ ] API endpoints (`/api/*`)
- [ ] Config / Settings
- [ ] Other: <!-- specify -->
## Checklist
- [ ] Tested on [worldmonitor.app](https://worldmonitor.app) variant
- [ ] Tested on [tech.worldmonitor.app](https://tech.worldmonitor.app) variant (if applicable)
- [ ] New RSS feed domains added to `api/rss-proxy.js` allowlist (if adding feeds)
- [ ] No API keys or secrets committed
- [ ] TypeScript compiles without errors (`npm run typecheck`)
## Screenshots
<!-- If applicable, add screenshots or screen recordings -->
================================================
FILE: .github/workflows/build-desktop.yml
================================================
name: 'Build Desktop App'
on:
workflow_dispatch:
inputs:
variant:
description: 'App variant'
required: true
default: 'full'
type: choice
options:
- full
- tech
draft:
description: 'Create as draft release'
required: false
default: true
type: boolean
push:
tags:
- 'v*'
concurrency:
group: desktop-build-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
jobs:
build-tauri:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- platform: 'macos-14'
args: '--target aarch64-apple-darwin'
node_target: 'aarch64-apple-darwin'
label: 'macOS-ARM64'
timeout: 180
- platform: 'macos-latest'
args: '--target x86_64-apple-darwin'
node_target: 'x86_64-apple-darwin'
label: 'macOS-x64'
timeout: 180
- platform: 'windows-latest'
args: ''
node_target: 'x86_64-pc-windows-msvc'
label: 'Windows-x64'
timeout: 120
- platform: 'ubuntu-24.04'
args: ''
node_target: 'x86_64-unknown-linux-gnu'
label: 'Linux-x64'
timeout: 120
- platform: 'ubuntu-24.04-arm'
args: '--target aarch64-unknown-linux-gnu'
node_target: 'aarch64-unknown-linux-gnu'
label: 'Linux-ARM64'
timeout: 120
runs-on: ${{ matrix.platform }}
name: Build (${{ matrix.label }})
timeout-minutes: ${{ matrix.timeout }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Start job timer
shell: bash
run: echo "JOB_START_EPOCH=$(date +%s)" >> "$GITHUB_ENV"
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '22'
cache: 'npm'
- name: Install Rust stable
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7
with:
toolchain: stable
targets: ${{ contains(matrix.platform, 'macos') && 'aarch64-apple-darwin,x86_64-apple-darwin' || (matrix.label == 'Linux-ARM64' && 'aarch64-unknown-linux-gnu' || '') }}
- name: Rust cache
uses: swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db
with:
workspaces: './src-tauri -> target'
cache-on-failure: true
- name: Install Linux system dependencies
if: contains(matrix.platform, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libappindicator3-dev \
librsvg2-dev \
patchelf \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
gstreamer1.0-libav \
gstreamer1.0-gl
- name: Install frontend dependencies
run: npm ci
- name: Check version consistency
run: npm run version:check
- name: Bundle Node.js runtime
shell: bash
env:
NODE_VERSION: '22.14.0'
NODE_TARGET: ${{ matrix.node_target }}
run: bash scripts/download-node.sh --target "$NODE_TARGET"
- name: Verify bundled Node.js payload
shell: bash
run: |
if [ "${{ matrix.node_target }}" = "x86_64-pc-windows-msvc" ]; then
test -f src-tauri/sidecar/node/node.exe
ls -lh src-tauri/sidecar/node/node.exe
else
test -f src-tauri/sidecar/node/node
test -x src-tauri/sidecar/node/node
ls -lh src-tauri/sidecar/node/node
fi
# ── Detect whether Apple signing secrets are configured ──
- name: Check Apple signing secrets
if: contains(matrix.platform, 'macos')
id: apple-signing
shell: bash
run: |
if [ -n "${{ secrets.APPLE_CERTIFICATE }}" ] && [ -n "${{ secrets.APPLE_CERTIFICATE_PASSWORD }}" ] && [ -n "${{ secrets.KEYCHAIN_PASSWORD }}" ]; then
echo "available=true" >> $GITHUB_OUTPUT
echo "Apple signing secrets detected"
else
echo "available=false" >> $GITHUB_OUTPUT
echo "No Apple signing secrets — building unsigned"
fi
# ── macOS Code Signing (only when secrets are valid) ──
- name: Import Apple Developer Certificate
if: contains(matrix.platform, 'macos') && steps.apple-signing.outputs.available == 'true'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
printf '%s' "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
CERT_SIZE=$(wc -c < certificate.p12 | tr -d ' ')
if [ "$CERT_SIZE" -lt 100 ]; then
echo "::warning::Certificate file too small ($CERT_SIZE bytes) — likely invalid. Skipping signing."
echo "SKIP_SIGNING=true" >> $GITHUB_ENV
exit 0
fi
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain
security import certificate.p12 -k build.keychain \
-P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign || {
echo "::warning::Certificate import failed — building unsigned"
echo "SKIP_SIGNING=true" >> $GITHUB_ENV
exit 0
}
security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$KEYCHAIN_PASSWORD" build.keychain
CERT_INFO=$(security find-identity -v -p codesigning build.keychain \
| grep "Developer ID Application" || true)
if [ -n "$CERT_INFO" ]; then
CERT_ID=$(echo "$CERT_INFO" | head -1 | awk -F'"' '{print $2}')
echo "APPLE_SIGNING_IDENTITY=$CERT_ID" >> $GITHUB_ENV
echo "Certificate imported: $CERT_ID"
else
echo "::warning::No Developer ID certificate found in keychain — building unsigned"
echo "SKIP_SIGNING=true" >> $GITHUB_ENV
fi
# ── Determine variant ──
- name: Set build variant
shell: bash
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "BUILD_VARIANT=${{ github.event.inputs.variant }}" >> $GITHUB_ENV
else
echo "BUILD_VARIANT=full" >> $GITHUB_ENV
fi
# ── Build with tauri-action ──
# Signed builds: only when Apple signing secrets are valid and imported
# Unsigned builds: fallback when no signing (Windows always uses this path)
# ── Build: Full variant (signed) ──
- name: Build Tauri app (full, signed)
if: env.BUILD_VARIANT == 'full' && steps.apple-signing.outputs.available == 'true' && env.SKIP_SIGNING != 'true'
uses: tauri-apps/tauri-action@79c624843491f12ae9d63592534ed49df3bc4adb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VITE_VARIANT: full
VITE_DESKTOP_RUNTIME: '1'
VITE_WS_API_URL: https://worldmonitor.app
CONVEX_URL: ${{ secrets.CONVEX_URL }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tagName: v__VERSION__
releaseName: 'World Monitor v__VERSION__'
releaseBody: 'See changelog below.'
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.draft) }}
prerelease: false
args: ${{ matrix.args }}
retryAttempts: 1
# ── Build: Full variant (unsigned — no Apple certs) ──
- name: Build Tauri app (full, unsigned)
if: env.BUILD_VARIANT == 'full' && (steps.apple-signing.outputs.available != 'true' || env.SKIP_SIGNING == 'true')
uses: tauri-apps/tauri-action@79c624843491f12ae9d63592534ed49df3bc4adb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VITE_VARIANT: full
VITE_DESKTOP_RUNTIME: '1'
VITE_WS_API_URL: https://worldmonitor.app
CONVEX_URL: ${{ secrets.CONVEX_URL }}
with:
tagName: v__VERSION__
releaseName: 'World Monitor v__VERSION__'
releaseBody: 'See changelog below.'
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.draft) }}
prerelease: false
args: ${{ matrix.args }}
retryAttempts: 1
# ── Build: Tech variant (signed) ──
- name: Build Tauri app (tech, signed)
if: env.BUILD_VARIANT == 'tech' && steps.apple-signing.outputs.available == 'true' && env.SKIP_SIGNING != 'true'
uses: tauri-apps/tauri-action@79c624843491f12ae9d63592534ed49df3bc4adb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VITE_VARIANT: tech
VITE_DESKTOP_RUNTIME: '1'
VITE_WS_API_URL: https://worldmonitor.app
CONVEX_URL: ${{ secrets.CONVEX_URL }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tagName: v__VERSION__-tech
releaseName: 'Tech Monitor v__VERSION__'
releaseBody: 'See changelog below.'
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.draft) }}
prerelease: false
tauriScript: npx tauri
args: --config src-tauri/tauri.tech.conf.json ${{ matrix.args }}
retryAttempts: 1
# ── Build: Tech variant (unsigned — no Apple certs) ──
- name: Build Tauri app (tech, unsigned)
if: env.BUILD_VARIANT == 'tech' && (steps.apple-signing.outputs.available != 'true' || env.SKIP_SIGNING == 'true')
uses: tauri-apps/tauri-action@79c624843491f12ae9d63592534ed49df3bc4adb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VITE_VARIANT: tech
VITE_DESKTOP_RUNTIME: '1'
VITE_WS_API_URL: https://worldmonitor.app
CONVEX_URL: ${{ secrets.CONVEX_URL }}
with:
tagName: v__VERSION__-tech
releaseName: 'Tech Monitor v__VERSION__'
releaseBody: 'See changelog below.'
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.draft) }}
prerelease: false
tauriScript: npx tauri
args: --config src-tauri/tauri.tech.conf.json ${{ matrix.args }}
retryAttempts: 1
- name: Verify signed macOS bundle + embedded runtime
if: contains(matrix.platform, 'macos') && steps.apple-signing.outputs.available == 'true' && env.SKIP_SIGNING != 'true'
shell: bash
run: |
APP_PATH=$(find src-tauri/target -type d -path '*/bundle/macos/*.app' | head -1)
if [ -z "$APP_PATH" ]; then
echo "::error::No macOS .app bundle found after build."
exit 1
fi
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
NODE_PATH=$(find "$APP_PATH/Contents/Resources" -type f -path '*/sidecar/node/node' | head -1)
if [ -z "$NODE_PATH" ]; then
echo "::error::Bundled Node runtime missing from app resources."
exit 1
fi
echo "Verified signed app bundle and embedded Node runtime: $NODE_PATH"
- name: Strip GPU libraries from AppImage
if: contains(matrix.platform, 'ubuntu')
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPIMAGETOOL_VERSION: '1.9.1'
APPIMAGETOOL_SHA256_X86_64: 'ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0'
APPIMAGETOOL_SHA256_AARCH64: 'f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158'
run: |
# --- Deterministic artifact selection ---
mapfile -t IMAGES < <(find src-tauri/target -path '*/bundle/appimage/*.AppImage')
if [ ${#IMAGES[@]} -eq 0 ]; then
echo "No AppImage found, skipping GPU lib strip"
exit 0
fi
if [ ${#IMAGES[@]} -gt 1 ]; then
echo "::error::Found ${#IMAGES[@]} AppImage files — expected exactly 1"
printf ' %s\n' "${IMAGES[@]}"
exit 1
fi
APPIMAGE="${IMAGES[0]}"
echo "Stripping bundled GPU/Wayland libraries from: $APPIMAGE"
chmod +x "$APPIMAGE"
# --- Clean extraction ---
rm -rf squashfs-root
"$APPIMAGE" --appimage-extract
# --- Strip GPU/Wayland libs (14 named patterns + DRI drivers) ---
GPU_LIBS=(
'libEGL.so*'
'libEGL_mesa.so*'
'libGLX.so*'
'libGLX_mesa.so*'
'libGLdispatch.so*'
'libGLESv2.so*'
'libGL.so*'
'libOpenGL.so*'
'libglapi.so*'
'libgbm.so*'
'libwayland-client.so*'
'libwayland-server.so*'
'libwayland-cursor.so*'
'libwayland-egl.so*'
)
REMOVED=0
for pattern in "${GPU_LIBS[@]}"; do
while IFS= read -r -d '' f; do
rm -f "$f"
echo " Removed: ${f#squashfs-root/}"
((REMOVED++)) || true
done < <(find squashfs-root -name "$pattern" -print0)
done
# Mesa DRI drivers
while IFS= read -r -d '' f; do
rm -f "$f"
echo " Removed DRI: ${f#squashfs-root/}"
((REMOVED++)) || true
done < <(find squashfs-root -path '*/dri/*_dri.so' -print0)
echo "Stripped $REMOVED GPU/Wayland library files"
if [ "$REMOVED" -eq 0 ]; then
echo "::error::No GPU libraries found to strip — build may have changed"
exit 1
fi
# --- Download and verify appimagetool (pinned to 1.9.1) ---
TOOL_ARCH=${{ matrix.label == 'Linux-ARM64' && 'aarch64' || 'x86_64' }}
TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/${APPIMAGETOOL_VERSION}/appimagetool-${TOOL_ARCH}.AppImage"
wget -q "$TOOL_URL" -O /tmp/appimagetool
EXPECTED_SHA=$([ "$TOOL_ARCH" = "x86_64" ] && echo "$APPIMAGETOOL_SHA256_X86_64" || echo "$APPIMAGETOOL_SHA256_AARCH64")
ACTUAL_SHA=$(sha256sum /tmp/appimagetool | awk '{print $1}')
if [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then
echo "::error::appimagetool SHA256 mismatch! Expected: $EXPECTED_SHA Got: $ACTUAL_SHA"
exit 1
fi
echo "appimagetool SHA256 verified: $ACTUAL_SHA"
chmod +x /tmp/appimagetool
# --- Repackage to temp path, then atomic mv ---
APPIMAGE_TMP="${APPIMAGE}.stripped.tmp"
ARCH=$TOOL_ARCH /tmp/appimagetool --appimage-extract-and-run squashfs-root "$APPIMAGE_TMP"
mv -f "$APPIMAGE_TMP" "$APPIMAGE"
# --- Post-repack verification: ALL banned patterns ---
rm -rf squashfs-root
"$APPIMAGE" --appimage-extract
BANNED=""
for pattern in "${GPU_LIBS[@]}"; do
found=$(find squashfs-root -name "$pattern" -print 2>/dev/null)
if [ -n "$found" ]; then BANNED+="$found"$'\n'; fi
done
found=$(find squashfs-root -path '*/dri/*_dri.so' -print 2>/dev/null)
if [ -n "$found" ]; then BANNED+="$found"$'\n'; fi
rm -rf squashfs-root
if [ -n "$BANNED" ]; then
echo "::error::Banned GPU libs still present after repack:"
echo "$BANNED"
exit 1
fi
echo "Post-repack verification passed — no banned GPU libs"
# --- Re-upload stripped AppImage to GitHub Release ---
VERSION=$(node -p "require('./package.json').version")
if [ "$BUILD_VARIANT" = "tech" ]; then
TAG_NAME="v${VERSION}-tech"
else
TAG_NAME="v${VERSION}"
fi
echo "Computed release tag: $TAG_NAME"
if gh release view "$TAG_NAME" &>/dev/null; then
echo "Re-uploading stripped AppImage to release $TAG_NAME"
gh release upload "$TAG_NAME" "$APPIMAGE" --clobber
echo "Replaced release asset: $(basename "$APPIMAGE")"
else
echo "::warning::Release $TAG_NAME not found — skipping re-upload"
fi
rm -f /tmp/appimagetool
- name: Smoke-test AppImage (Linux)
if: contains(matrix.platform, 'ubuntu')
shell: bash
run: |
sudo apt-get install -y xvfb imagemagick
APPIMAGE=$(find src-tauri/target -path '*/bundle/appimage/*.AppImage' | head -1)
if [ -z "$APPIMAGE" ]; then
echo "::error::No AppImage found after build"
exit 1
fi
chmod +x "$APPIMAGE"
# Start Xvfb with known display number
Xvfb :99 -screen 0 1440x900x24 &
export DISPLAY=:99
sleep 2
# Launch AppImage under virtual framebuffer
"$APPIMAGE" --no-sandbox &
APP_PID=$!
# Wait for app to render
sleep 15
# Screenshot the virtual display
import -window root screenshot.png || true
# Verify app is still running (didn't crash)
if kill -0 $APP_PID 2>/dev/null; then
echo "✅ AppImage launched successfully"
kill $APP_PID || true
else
echo "❌ AppImage crashed during startup"
exit 1
fi
- name: Upload smoke test screenshot
if: contains(matrix.platform, 'ubuntu')
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: linux-smoke-test-screenshot-${{ matrix.label }}
path: screenshot.png
if-no-files-found: warn
- name: Cleanup Apple signing materials
if: always() && contains(matrix.platform, 'macos')
shell: bash
run: |
rm -f certificate.p12
security delete-keychain build.keychain || true
- name: Report build duration
if: always()
shell: bash
run: |
if [ -z "${JOB_START_EPOCH:-}" ]; then
echo "::warning::JOB_START_EPOCH missing; duration unavailable."
exit 0
fi
END_EPOCH=$(date +%s)
ELAPSED=$((END_EPOCH - JOB_START_EPOCH))
MINUTES=$((ELAPSED / 60))
SECONDS=$((ELAPSED % 60))
echo "Build duration for ${{ matrix.label }}: ${MINUTES}m ${SECONDS}s"
# ── Update release notes with changelog after all builds complete ──
update-release-notes:
needs: build-tauri
if: always() && contains(needs.build-tauri.result, 'success')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Generate and update release notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
VERSION=$(jq -r .version src-tauri/tauri.conf.json)
TAG="v${VERSION}"
PREV_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
COMMITS="Initial release"
else
COMMITS=$(git log "${PREV_TAG}..${TAG}" --oneline --no-merges | sed 's/^[a-f0-9]*//' | sed 's/^ /- /')
fi
BODY=$(cat <<NOTES
## What's Changed
${COMMITS}
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG:-initial}...${TAG}
NOTES
)
gh release edit "$TAG" --notes "$BODY"
echo "Updated release notes for $TAG"
================================================
FILE: .github/workflows/contributor-trust.yml
================================================
name: Contributor Trust
on:
pull_request_target:
types: [opened, reopened]
jobs:
check:
# Skip for repo members/owners/collaborators
if: |
github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' ||
github.event.pull_request.author_association == 'FIRST_TIMER' ||
github.event.pull_request.author_association == 'CONTRIBUTOR' ||
github.event.pull_request.author_association == 'NONE'
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Check contributor trust
id: brin
run: |
AUTHOR="${{ github.event.pull_request.user.login }}"
RESPONSE=$(curl -sf --max-time 10 "https://api.brin.sh/contributor/${AUTHOR}" 2>/dev/null || true)
if [ -z "$RESPONSE" ]; then
echo "verdict=unavailable" >> "$GITHUB_OUTPUT"
else
VERDICT=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); v=d.get('verdict',''); print(v if v in ('safe','caution','suspicious','dangerous') else 'unavailable')" 2>/dev/null || echo "unavailable")
echo "verdict=$VERDICT" >> "$GITHUB_OUTPUT"
fi
- name: Apply trust label
if: |
steps.brin.outputs.verdict == 'safe' ||
steps.brin.outputs.verdict == 'caution' ||
steps.brin.outputs.verdict == 'suspicious' ||
steps.brin.outputs.verdict == 'dangerous'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
env:
BRIN_VERDICT: ${{ steps.brin.outputs.verdict }}
with:
script: |
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: [`trust:${process.env.BRIN_VERDICT}`],
});
================================================
FILE: .github/workflows/docker-publish.yml
================================================
name: Publish Docker image
on:
release:
types: [published]
workflow_dispatch:
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
id: meta
with:
images: ghcr.io/koala73/worldmonitor
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
type=sha,prefix=sha-
- uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
with:
context: .
file: docker/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
================================================
FILE: .github/workflows/lint-code.yml
================================================
name: Lint Code
on:
pull_request:
push:
branches: [main]
jobs:
biome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm run lint:unicode
- run: npm run lint
- run: npm run lint:boundaries
================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint
on:
pull_request:
paths:
- '**/*.md'
- '.markdownlint-cli2.jsonc'
jobs:
markdown:
# No secrets needed — run for all PRs including forks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm run lint:md
================================================
FILE: .github/workflows/proto-check.yml
================================================
name: Proto Generation Check
on:
pull_request:
paths:
- 'proto/**'
- 'src/generated/**'
- 'docs/api/**'
- 'Makefile'
- '.github/workflows/proto-check.yml'
jobs:
proto-freshness:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '1.23'
cache: false
- name: Cache Go binaries (buf, protoc plugins)
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
with:
path: ~/go/bin
key: go-bin-${{ runner.os }}-${{ hashFiles('Makefile') }}
- name: Install buf and protoc plugins
run: make install-buf install-plugins
env:
GOPROXY: direct
GOPRIVATE: github.com/SebastienMelki
- name: Run proto generation
run: make generate
- name: Verify generated code is up to date
run: |
if ! git diff --exit-code src/generated/ docs/api/; then
echo ""
echo "============================================================"
echo "ERROR: Proto-generated code is out of date."
echo "Run 'make generate' locally and commit the updated files."
echo "============================================================"
exit 1
fi
UNTRACKED=$(git ls-files --others --exclude-standard src/generated/ docs/api/)
if [ -n "$UNTRACKED" ]; then
echo ""
echo "============================================================"
echo "ERROR: Untracked generated files found:"
echo "$UNTRACKED"
echo "Run 'make generate' locally and commit the new files."
echo "============================================================"
exit 1
fi
echo "Proto-generated code is up to date."
================================================
FILE: .github/workflows/test-linux-app.yml
================================================
name: 'Test Linux App'
on:
workflow_dispatch:
concurrency:
group: test-linux-app-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
jobs:
test-linux-app:
runs-on: ubuntu-24.04
timeout-minutes: 120
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '22'
cache: 'npm'
- name: Install Rust stable
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7
with:
toolchain: stable
- name: Rust cache
uses: swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db
with:
workspaces: './src-tauri -> target'
cache-on-failure: true
- name: Install Linux system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libappindicator3-dev \
librsvg2-dev \
patchelf \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
xwayland-run \
xvfb \
imagemagick \
xdotool
- name: Install frontend dependencies
run: npm ci
- name: Bundle Node.js runtime
shell: bash
env:
NODE_VERSION: '22.14.0'
NODE_TARGET: 'x86_64-unknown-linux-gnu'
run: bash scripts/download-node.sh --target "$NODE_TARGET"
- name: Build Tauri app
uses: tauri-apps/tauri-action@79c624843491f12ae9d63592534ed49df3bc4adb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VITE_VARIANT: full
VITE_DESKTOP_RUNTIME: '1'
CONVEX_URL: ${{ secrets.CONVEX_URL }}
with:
args: ''
retryAttempts: 1
- name: Smoke-test AppImage
shell: bash
run: |
APPIMAGE=$(find src-tauri/target/release/bundle/appimage -name '*.AppImage' | head -1)
if [ -z "$APPIMAGE" ]; then
echo "::error::No AppImage found after build"
exit 1
fi
chmod +x "$APPIMAGE"
APPIMAGE_ABS=$(realpath "$APPIMAGE")
# Write the inner test script (runs inside the display server)
cat > /tmp/smoke-test.sh <<'SCRIPT'
#!/bin/bash
set -x
echo "DISPLAY=$DISPLAY WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-unset}"
GDK_BACKEND=x11 "$APPIMAGE_ABS" --no-sandbox 2>&1 | tee /tmp/app.log &
APP_PID=$!
sleep 20
# Screenshot via X11
import -window root /tmp/screenshot.png 2>/dev/null || true
# Verify app is still running
if kill -0 $APP_PID 2>/dev/null; then
echo "APP_STATUS=running"
else
echo "APP_STATUS=crashed"
echo "--- App log ---"
tail -50 /tmp/app.log || true
fi
# Window info
xdotool search --name "" getwindowname 2>/dev/null | head -5 || true
kill $APP_PID 2>/dev/null || true
SCRIPT
chmod +x /tmp/smoke-test.sh
export APPIMAGE_ABS
RESULT=0
# --- Try 1: xwfb-run (Xwayland on headless Wayland compositor) ---
if command -v xwfb-run &>/dev/null; then
echo "=== Using xwfb-run (Xwayland + headless compositor) ==="
timeout 90 xwfb-run -- bash /tmp/smoke-test.sh 2>&1 | tee /tmp/display-server.log || RESULT=$?
else
echo "xwfb-run not found, skipping"
RESULT=1
fi
# --- Fallback: plain Xvfb ---
if [ $RESULT -ne 0 ] || [ ! -f /tmp/screenshot.png ]; then
echo "=== Falling back to Xvfb ==="
Xvfb :99 -screen 0 1440x900x24 &
XVFB_PID=$!
export DISPLAY=:99
sleep 2
bash /tmp/smoke-test.sh 2>&1 | tee /tmp/display-server.log
kill $XVFB_PID 2>/dev/null || true
fi
# --- Copy screenshot to workspace ---
cp /tmp/screenshot.png screenshot.png 2>/dev/null || true
# --- Check results ---
if grep -q "APP_STATUS=crashed" /tmp/display-server.log 2>/dev/null; then
echo "❌ AppImage crashed during startup"
exit 1
fi
if grep -q "APP_STATUS=running" /tmp/display-server.log 2>/dev/null; then
echo "✅ AppImage launched successfully"
else
echo "⚠️ Could not determine app status"
fi
# --- Check screenshot has non-black content ---
if [ -f screenshot.png ]; then
COLORS=$(identify -verbose screenshot.png 2>/dev/null | grep "Colors:" | awk '{print $2}')
echo "Screenshot unique colors: ${COLORS:-unknown}"
if [ "${COLORS:-0}" -le 5 ]; then
echo "⚠️ Screenshot appears blank (only $COLORS colors). App may not have rendered."
else
echo "✅ Screenshot has content ($COLORS unique colors)"
fi
fi
- name: Upload smoke test screenshot
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: linux-smoke-test-screenshot
path: screenshot.png
if-no-files-found: warn
- name: Upload logs
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: linux-smoke-test-logs
path: |
/tmp/display-server.log
/tmp/app.log
if-no-files-found: warn
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on:
pull_request:
push:
branches: [main]
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm run test:data
================================================
FILE: .github/workflows/typecheck.yml
================================================
name: Typecheck
on:
pull_request:
paths-ignore:
- 'scripts/**'
push:
branches: [main]
paths-ignore:
- 'scripts/**'
jobs:
typecheck:
# No secrets needed — run for all PRs including forks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm run typecheck
- run: npm run typecheck:api
================================================
FILE: .gitignore
================================================
node_modules/
.idea/
dist/
public/blog/
.DS_Store
*.log
.env
.env.local
.playwright-mcp/
.vercel
api/\[domain\]/v1/\[rpc\].js
api/\[\[...path\]\].js
.claude/
.cursor/
CLAUDE.md
.env.vercel-backup
.env.vercel-export
.agent/
.factory/
.windsurf/
skills/
ideas/
docs/internal/
internal/
test-results/
src-tauri/sidecar/node/*
!src-tauri/sidecar/node/.gitkeep
# AI planning session state
.planning/
# Compiled sebuf gateway bundle (built by scripts/build-sidecar-sebuf.mjs)
api/[[][[].*.js
# Compiled sidecar domain handler bundles (built by scripts/build-sidecar-handlers.mjs)
api/*/v1/\[rpc\].js
.claudedocs/
# Large generated data files (reproduced by scripts/)
scripts/data/pizzint-processed.json
scripts/data/osm-military-processed.json
scripts/data/military-bases-final.json
scripts/data/dedup-dropped-pairs.json
scripts/data/pizzint-partial.json
scripts/data/gpsjam-latest.json
scripts/data/mirta-raw.geojson
scripts/data/osm-military-raw.json
# Iran events data (sensitive, not for public repo)
scripts/data/iran-events-latest.json
# Military bases rebuild script (references external Supabase URLs)
scripts/rebuild-military-bases.mjs
.wrangler
# Build artifacts (generated by esbuild/tsc, not source code)
api/data/city-coords.js
# Runtime artifacts (generated by sidecar/tools, not source code)
api-cache.json
verbose-mode.json
skills-lock.json
tmp/
================================================
FILE: .husky/pre-commit
================================================
echo "Running Unicode safety check (staged files)..."
node scripts/check-unicode-safety.mjs --staged || exit 1
================================================
FILE: .husky/pre-push
================================================
# Ensure dependencies are installed (worktrees start with no node_modules)
if [ ! -d node_modules ]; then
echo "node_modules missing, running npm install..."
npm install --prefer-offline || exit 1
fi
echo "Running type check..."
npm run typecheck || exit 1
echo "Running API type check..."
npm run typecheck:api || exit 1
echo "Running CJS syntax check..."
for f in scripts/*.cjs; do
[ -f "$f" ] && node -c "$f" || exit 1
done
echo "Running Unicode safety check..."
node scripts/check-unicode-safety.mjs || exit 1
echo "Running edge function bundle check..."
for f in api/*.js; do
case "$(basename "$f")" in _*) continue;; esac
npx esbuild "$f" --bundle --format=esm --platform=browser --outfile=/dev/null 2>/dev/null || {
echo "ERROR: esbuild failed to bundle $f — this will break Vercel deployment"
npx esbuild "$f" --bundle --format=esm --platform=browser --outfile=/dev/null
exit 1
}
done
echo "Running unit tests..."
npm run test:data || exit 1
echo "Running edge function tests..."
node --test tests/edge-functions.test.mjs || exit 1
echo "Running markdown lint..."
npm run lint:md || exit 1
echo "Running MDX lint (Mintlify compatibility)..."
node --test tests/mdx-lint.test.mjs || exit 1
echo "Running proto freshness check..."
if git diff --name-only origin/main -- proto/ src/generated/ docs/api/ Makefile | grep -q .; then
if command -v buf >/dev/null 2>&1 || [ -x "$HOME/go/bin/buf" ]; then
export PATH="$HOME/go/bin:$PATH"
fi
if command -v buf &>/dev/null && command -v protoc-gen-ts-client &>/dev/null; then
make generate
if ! git diff --exit-code src/generated/ docs/api/; then
echo ""
echo "============================================================"
echo "ERROR: Proto-generated code is out of date."
echo "Run 'make generate' locally and commit the updated files."
echo "============================================================"
exit 1
fi
UNTRACKED=$(git ls-files --others --exclude-standard src/generated/ docs/api/)
if [ -n "$UNTRACKED" ]; then
echo ""
echo "============================================================"
echo "ERROR: Untracked generated files found:"
echo "$UNTRACKED"
echo "Run 'make generate' locally and commit the new files."
echo "============================================================"
exit 1
fi
echo "Proto-generated code is up to date."
else
echo "WARNING: buf or protoc plugins not installed, skipping proto freshness check."
echo " Install with: make install-buf install-plugins"
fi
else
echo "No proto-related changes, skipping."
fi
echo "Running version sync check..."
npm run version:check || exit 1
================================================
FILE: .markdownlint-cli2.jsonc
================================================
{
// Only enforce the 3 rules from PR #72. Everything else is off.
"config": {
"default": false,
"MD012": true,
"MD022": true,
"MD032": true
},
"ignores": ["node_modules/**", "dist/**", "src-tauri/target/**", ".planning/**", "DMCA-TAKEDOWN-NOTICE.md"]
}
================================================
FILE: .npmrc
================================================
loglevel=error
================================================
FILE: .nvmrc
================================================
22
================================================
FILE: .vercelignore
================================================
# Exclude local desktop build artifacts and sidecar binaries from deployments
# These files are large and not needed by the Vercel-hosted frontend/API
# Tauri build outputs
src-tauri/target/
src-tauri/bundle/
# Sidecar and bundled node binaries
src-tauri/sidecar/
src-tauri/**/node
# macOS disk images and app bundles
**/*.dmg
**/*.app
# Rust/Cargo build artifacts (safety)
target/
# Common local artifacts
.DS_Store
================================================
FILE: AGENTS.md
================================================
# AGENTS.md
Agent entry point for WorldMonitor. Read this first, then follow links for depth.
## What This Project Is
Real-time global intelligence dashboard. TypeScript SPA (Vite + Preact) with 86 panel components, 60+ Vercel Edge API endpoints, a Tauri desktop app with Node.js sidecar, and a Railway relay service. Aggregates 30+ external data sources (geopolitics, military, finance, climate, cyber, maritime, aviation).
## Repository Map
```
.
├── src/ # Browser SPA (TypeScript, class-based components)
│ ├── app/ # App orchestration (data-loader, refresh-scheduler, panel-layout)
│ ├── components/ # 86 UI panels + map components (Panel subclasses)
│ ├── config/ # Variant configs, panel/layer definitions, market symbols
│ ├── services/ # Business logic (120+ service files, organized by domain)
│ ├── types/ # TypeScript type definitions
│ ├── utils/ # Shared utilities (circuit-breaker, theme, URL state, DOM)
│ ├── workers/ # Web Workers (analysis, ML/ONNX, vector DB)
│ ├── generated/ # Proto-generated client/server stubs (DO NOT EDIT)
│ ├── locales/ # i18n translation files
│ └── App.ts # Main application entry
├── api/ # Vercel Edge Functions (plain JS, self-contained)
│ ├── _*.js # Shared helpers (CORS, rate-limit, API key, relay)
│ ├── health.js # Health check endpoint
│ ├── bootstrap.js # Bulk data hydration endpoint
│ └── <domain>/ # Domain-specific endpoints (aviation/, climate/, etc.)
├── server/ # Server-side shared code (used by Edge Functions)
│ ├── _shared/ # Redis, rate-limit, LLM, caching, response headers
│ ├── gateway.ts # Domain gateway factory (CORS, auth, cache tiers)
│ ├── router.ts # Route matching
│ └── worldmonitor/ # Domain handlers (mirrors proto service structure)
├── proto/ # Protobuf definitions (sebuf framework)
│ ├── buf.yaml # Buf configuration
│ └── worldmonitor/ # Service definitions with HTTP annotations
├── shared/ # Cross-platform data (JSON configs for markets, RSS domains)
├── scripts/ # Seed scripts, build helpers, data fetchers
├── src-tauri/ # Tauri desktop shell (Rust + Node.js sidecar)
│ └── sidecar/ # Node.js sidecar API server
├── tests/ # Unit/integration tests (node:test runner)
├── e2e/ # Playwright E2E specs
├── docs/ # Mintlify documentation site
├── docker/ # Docker build for Railway services
├── deploy/ # Deployment configs
└── blog-site/ # Static blog (built into public/blog/)
```
## How to Run
```bash
npm install # Install deps (also runs blog-site postinstall)
npm run dev # Start Vite dev server (full variant)
npm run dev:tech # Start tech-only variant
npm run typecheck # tsc --noEmit (strict mode)
npm run typecheck:api # Typecheck API layer separately
npm run test:data # Run unit/integration tests
npm run test:sidecar # Run sidecar + API handler tests
npm run test:e2e # Run all Playwright E2E tests
make generate # Regenerate proto stubs (requires buf + sebuf plugins)
```
## Architecture Rules
### Dependency Direction
```
types -> config -> services -> components -> app -> App.ts
```
- `types/` has zero internal imports
- `config/` imports only from `types/`
- `services/` imports from `types/` and `config/`
- `components/` imports from all above
- `app/` orchestrates components and services
### API Layer Constraints
- `api/*.js` are Vercel Edge Functions: **self-contained JS only**
- They CANNOT import from `../src/` or `../server/` (different runtime)
- Only same-directory `_*.js` helpers and npm packages
- Enforced by `tests/edge-functions.test.mjs` and pre-push hook esbuild check
### Server Layer
- `server/` code is bundled INTO Edge Functions at deploy time via gateway
- `server/_shared/` contains Redis client, rate limiting, LLM helpers
- `server/worldmonitor/<domain>/` has RPC handlers matching proto services
- All handlers use `cachedFetchJson()` for Redis caching with stampede protection
### Proto Contract Flow
```
proto/ definitions -> buf generate -> src/generated/{client,server}/ -> handlers wire up
```
- GET fields need `(sebuf.http.query)` annotation
- `repeated string` fields need `parseStringArray()` in handler
- `int64` maps to `string` in TypeScript
- CI checks proto freshness via `.github/workflows/proto-check.yml`
## Variant System
The app ships multiple variants with different panel/layer configurations:
- `full` (default): All features
- `tech`: Technology-focused subset
- `finance`: Financial markets focus
- `commodity`: Commodity markets focus
- `happy`: Positive news only
Variant is set via `VITE_VARIANT` env var. Config lives in `src/config/variants/`.
## Key Patterns
### Adding a New API Endpoint
1. Define proto message in `proto/worldmonitor/<domain>/`
2. Add RPC with `(sebuf.http.config)` annotation
3. Run `make generate`
4. Create handler in `server/worldmonitor/<domain>/`
5. Wire handler in domain's `handler.ts`
6. Use `cachedFetchJson()` for caching, include request params in cache key
### Adding a New Panel
1. Create `src/components/MyPanel.ts` extending `Panel`
2. Register in `src/config/panels.ts`
3. Add to variant configs in `src/config/variants/`
4. Wire data loading in `src/app/data-loader.ts`
### Circuit Breakers
- `src/utils/circuit-breaker.ts` for client-side
- Used in data loaders to prevent cascade failures
- Separate breaker per data domain
### Caching
- Redis (Upstash) via `server/_shared/redis.ts`
- `cachedFetchJson()` coalesces concurrent cache misses
- Cache tiers: fast (5m), medium (10m), slow (30m), static (2h), daily (24h)
- Cache key MUST include request-varying params
## Testing
- **Unit/Integration**: `tests/*.test.{mjs,mts}` using `node:test` runner
- **Sidecar tests**: `api/*.test.mjs`, `src-tauri/sidecar/*.test.mjs`
- **E2E**: `e2e/*.spec.ts` using Playwright
- **Visual regression**: Golden screenshot comparison per variant
## CI Checks (GitHub Actions)
| Workflow | Trigger | What it checks |
|---|---|---|
| `typecheck.yml` | PR + push to main | `tsc --noEmit` for src and API |
| `lint.yml` | PR (markdown changes) | markdownlint-cli2 |
| `proto-check.yml` | PR (proto changes) | Generated code freshness |
| `build-desktop.yml` | Manual | Tauri desktop build |
| `test-linux-app.yml` | Manual | Linux AppImage smoke test |
## Pre-Push Hook
Runs automatically before `git push`:
1. TypeScript check (src + API)
2. CJS syntax validation
3. Edge function esbuild bundle check
4. Edge function import guardrail test
5. Markdown lint
6. MDX lint (Mintlify compatibility)
7. Version sync check
## Deployment
- **Web**: Vercel (auto-deploy on push to main)
- **Relay/Seeds**: Railway (Docker, cron services)
- **Desktop**: Tauri builds via GitHub Actions
- **Docs**: Mintlify (proxied through Vercel at `/docs`)
## Critical Conventions
- `fetch.bind(globalThis)` is BANNED. Use `(...args) => globalThis.fetch(...args)` instead
- Edge Functions cannot use `node:http`, `node:https`, `node:zlib`
- Always include `User-Agent` header in server-side fetch calls
- Yahoo Finance requests must be staggered (150ms delays)
- New data sources MUST have bootstrap hydration wired in `api/bootstrap.js`
- Redis seed scripts MUST write `seed-meta:<key>` for health monitoring
## External References
- [Architecture (system reference)](ARCHITECTURE.md)
- [Design Philosophy (why decisions were made)](docs/architecture.mdx)
- [Contributing guide](CONTRIBUTING.md)
- [Data sources catalog](docs/data-sources.mdx)
- [Health endpoints](docs/health-endpoints.mdx)
- [Adding endpoints guide](docs/adding-endpoints.mdx)
- [API reference (OpenAPI)](docs/api/)
================================================
FILE: ARCHITECTURE.md
================================================
# Architecture
> **Last verified**: 2026-03-14 against commit `24b502d0`
>
> **Ownership rule**: When deployment topology, API surface, desktop runtime, or bootstrap keys change, this document must be updated in the same PR.
> **Design philosophy**: For the "why" behind architectural decisions, intelligence tradecraft, and algorithmic choices, see [Design Philosophy](docs/architecture.mdx).
World Monitor is a real-time global intelligence dashboard built as a TypeScript single-page application. It aggregates data from dozens of external sources covering geopolitics, military activity, financial markets, cyber threats, climate events, maritime tracking, and aviation into a unified operational picture rendered through an interactive map and a grid of specialized panels.
---
## 1. System Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ Browser / Desktop │
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ DeckGLMap│ │ GlobeMap │ │ Panels │ │ Workers │ │
│ │(deck.gl) │ │(globe.gl)│ │(86 classes)│ │(ML, analysis)│ │
│ └────┬─────┘ └────┬─────┘ └─────┬──────┘ └──────────────┘ │
│ └──────────────┴──────────────┘ │
│ │ fetch /api/* │
└─────────────────────────┼───────────────────────────────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌──────▼──────┐ ┌─────▼─────┐ ┌─────▼──────┐
│ Vercel │ │ Railway │ │ Tauri │
│ Edge Funcs │ │ AIS Relay │ │ Sidecar │
│ + Middleware│ │ + Seeds │ │ (Node.js) │
└──────┬──────┘ └─────┬─────┘ └─────┬──────┘
│ │ │
└──────────────┼──────────────┘
│
┌──────▼──────┐
│ Upstash │
│ Redis │
└──────┬──────┘
│
┌───────────┼───────────┐
│ │ │
┌─────▼───┐ ┌─────▼───┐ ┌────▼────┐
│ Finnhub │ │ Yahoo │ │ ACLED │
│ OpenSky │ │ GDELT │ │ UCDP │
│ CoinGeck│ │ FRED │ │ FIRMS │
│ ... │ │ ... │ │ ... │
└─────────┘ └─────────┘ └─────────┘
30+ upstream data sources
```
**Source files**: `package.json`, `vercel.json`
---
## 2. Deployment Topology
| Service | Platform | Role |
|---------|----------|------|
| SPA + Edge Functions | Vercel | Static files, API endpoints, middleware (bot filtering, social OG) |
| AIS Relay | Railway | WebSocket proxy (AIS stream), seed loops (market, aviation, GPSJAM, risk scores, UCDP, positive events), RSS proxy, OREF polling |
| Redis | Upstash | Cache layer with stampede protection, seed-meta freshness tracking, rate limiting |
| Convex | Convex Cloud | Contact form submissions, waitlist registrations |
| Documentation | Mintlify | Public docs, proxied through Vercel at `/docs` |
| Desktop App | Tauri 2.x | macOS (ARM64, x64), Windows (x64), Linux (x64, ARM64) with bundled Node.js sidecar |
| Container Image | GHCR | Multi-arch Docker image (nginx serving built SPA, proxies API to upstream) |
**Source files**: `vercel.json`, `docker/Dockerfile`, `scripts/ais-relay.cjs`, `convex/schema.ts`, `src-tauri/tauri.conf.json`
---
## 3. Frontend Architecture
### Entry and Initialization
`src/main.ts` initializes Sentry error tracking, Vercel analytics, dynamic meta tags, runtime fetch patches (desktop sidecar redirection), theme application, and creates the `App` instance.
`App.init()` runs in 8 phases:
1. **Storage + i18n**: IndexedDB, language detection, locale loading
2. **ML Worker**: ONNX model prep (embeddings, sentiment, summarization)
3. **Sidecar**: Wait for desktop sidecar readiness (desktop only)
4. **Bootstrap**: Two-tier concurrent hydration from `/api/bootstrap` (fast 3s + slow 5s timeouts)
5. **Layout**: PanelLayoutManager renders map and panels
6. **UI**: SignalModal, IntelligenceGapBadge, BreakingNewsBanner, correlation engine
7. **Data**: Parallel `loadAllData()` + viewport-conditional `primeVisiblePanelData()`
8. **Refresh**: Variant-specific polling intervals via `startSmartPollLoop()`
### Component Model
All panels extend the `Panel` base class. Panels render via `setContent(html)` (debounced 150ms) and use event delegation on a stable `this.content` element. Panels support resizable row/col spans persisted to localStorage.
### Dual Map System
- **DeckGLMap**: WebGL rendering via deck.gl + maplibre-gl. Supports ScatterplotLayer, GeoJsonLayer, PathLayer, IconLayer, PolygonLayer, ArcLayer, HeatmapLayer, H3HexagonLayer. PMTiles protocol for self-hosted basemap tiles. Supercluster for marker clustering.
- **GlobeMap**: 3D interactive globe via globe.gl. Single merged `htmlElementsData` array with `_kind` discriminator. Earth texture, atmosphere shader, auto-rotate after idle.
Layer definitions live in `src/config/map-layer-definitions.ts`, each specifying renderer support (flat/globe), premium status, variant filtering, and i18n keys.
### State Management
No external state library. `AppContext` is a central mutable object holding: map references, panel instances, panel/layer settings, all cached data (news, markets, predictions, clusters, intelligence caches), in-flight request tracking, and UI component references. URL state syncs bidirectionally via `src/utils/urlState.ts` (debounced 250ms).
### Web Workers
- **analysis.worker.ts**: News clustering (Jaccard similarity), cross-domain correlation detection
- **ml.worker.ts**: ONNX inference via `@xenova/transformers` (MiniLM-L6 embeddings, sentiment, summarization, NER), in-worker vector store for headline memory
- **vector-db.ts**: IndexedDB-backed vector store for semantic search
### Variant System
Detected by hostname (`tech.worldmonitor.app` → tech, `finance.worldmonitor.app` → finance, etc.) or localStorage on desktop. Controls: default panels, map layers, refresh intervals, theme, UI text. Variant change resets all settings to defaults.
**Source files**: `src/main.ts`, `src/App.ts`, `src/app/`, `src/components/Panel.ts`, `src/components/DeckGLMap.ts`, `src/components/GlobeMap.ts`, `src/config/variant.ts`, `src/workers/`
---
## 4. API Layer
### Edge Functions
All API endpoints live in `api/` as self-contained JavaScript files deployed as Vercel Edge Functions. They cannot import from `../src/` or `../server/` (different runtime). Only same-directory `_*.js` helpers and npm packages are allowed. This constraint is enforced by `tests/edge-functions.test.mjs` and the pre-push esbuild bundle check.
### Shared Helpers
| File | Purpose |
|------|---------|
| `_cors.js` | Origin allowlist (worldmonitor.app, Vercel previews, tauri://localhost, localhost) |
| `_rate-limit.js` | Upstash sliding window rate limiting, IP extraction |
| `_api-key.js` | Origin-aware API key validation (desktop requires key, trusted browser exempt) |
| `_relay.js` | Factory for proxying requests to Railway relay service |
### Gateway Factory
`server/gateway.ts` provides `createDomainGateway(routes)` for per-domain Edge Function bundles. Pipeline:
1. Origin check (403 if disallowed)
2. CORS headers
3. OPTIONS preflight
4. API key validation
5. Rate limiting (endpoint-specific, then global fallback)
6. Route matching (static Map lookup, then dynamic `{param}` scan)
7. POST-to-GET compatibility (for stale clients)
8. Handler execution with error boundary
9. ETag generation (FNV-1a hash) + 304 Not Modified
10. Cache header application
### Cache Tiers
| Tier | s-maxage | Use case |
|------|----------|----------|
| fast | 300s | Live event streams, flight status |
| medium | 600s | Market quotes, stock analysis |
| slow | 1800s | ACLED events, cyber threats |
| static | 7200s | Humanitarian summaries, ETF flows |
| daily | 86400s | Critical minerals, static reference data |
| no-store | 0 | Vessel snapshots, aircraft tracking |
### Domain Handlers
`server/worldmonitor/<domain>/v1/handler.ts` exports handler objects with per-RPC functions. Each RPC function uses `cachedFetchJson()` from `server/_shared/redis.ts` for cache-miss coalescing: concurrent requests for the same key share a single upstream fetch and Redis write.
**Source files**: `api/`, `server/gateway.ts`, `server/router.ts`, `server/_shared/redis.ts`, `server/worldmonitor/`
---
## 5. Proto/RPC Contract System
The project uses the **sebuf** framework built on Protocol Buffers:
```
proto/ definitions
↓ buf generate
src/generated/client/ (TypeScript RPC client stubs)
src/generated/server/ (TypeScript server message types)
docs/api/ (OpenAPI v3 specs)
```
Service definitions use `(sebuf.http.config)` annotations to map RPCs to HTTP verbs and paths. GET fields require `(sebuf.http.query)` annotation. `repeated string` fields need `parseStringArray()` in the handler. `int64` maps to `string` in TypeScript.
CI enforces generated code freshness via `.github/workflows/proto-check.yml`: runs `make generate` and fails if output differs from committed files.
**Source files**: `proto/`, `Makefile`, `src/generated/`, `.github/workflows/proto-check.yml`
---
## 6. Data Pipeline
### Bootstrap Hydration
`/api/bootstrap` reads cached keys from Redis in a single batch call. The SPA fetches two tiers concurrently (fast + slow) with separate abort controllers and timeouts. Hydrated data is consumed on-demand by panels via `getHydratedData(key)`.
### Seed Scripts
`scripts/seed-*.mjs` fetch upstream data, transform it, and write to Redis via `atomicPublish()` from `scripts/_seed-utils.mjs`. Atomic publish acquires a Redis lock (SET NX), validates data, writes the cache key, writes `seed-meta:<key>` with `{ fetchedAt, recordCount }`, and releases the lock.
### AIS Relay Seed Loops
The Railway relay service (`scripts/ais-relay.cjs`) runs continuous seed loops:
- Market data (stocks, commodities, crypto, stablecoins, sectors, ETF flows, gulf quotes)
- Aviation (international delays)
- Positive events
- GPSJAM (GPS interference)
- Risk scores (CII)
- UCDP events
These are the primary seeders. Standalone `seed-*.mjs` scripts on Railway cron are secondary/backup.
### Refresh Scheduling
`startSmartPollLoop()` supports: exponential backoff (max 4x), viewport-conditional refresh (only if panel is near viewport), tab-pause (suspend when hidden), and staggered flush on tab visibility (150ms delays).
### Health Monitoring
`api/health.js` checks every bootstrap and standalone key. For each key it reads `seed-meta:<key>` and compares `fetchedAt` against `maxStaleMin`. Cascade groups handle fallback chains (e.g., theater-posture: live, stale, backup). Returns per-key status: OK, STALE, WARN, EMPTY.
**Source files**: `api/bootstrap.js`, `api/health.js`, `scripts/_seed-utils.mjs`, `scripts/seed-*.mjs`, `scripts/ais-relay.cjs`, `src/services/bootstrap.ts`, `src/app/refresh-scheduler.ts`
---
## 7. Desktop Architecture
### Tauri Shell
Tauri 2.x (Rust) manages the app lifecycle, system tray, and IPC commands:
- **Secret management**: Read/write platform keyring (macOS Keychain, Windows Credential Manager, Linux keyring)
- **Sidecar control**: Spawn Node.js process, probe port, inject environment variables
- **Window management**: Three trusted windows (main, settings, live-channels) with Edit menu for macOS clipboard shortcuts
### Node.js Sidecar
`src-tauri/sidecar/local-api-server.mjs` runs on a dynamic port. It dynamically loads Edge Function handler modules from `api/`, injects secrets from the keyring via environment variables, and monkey-patches `globalThis.fetch` to force IPv4 (Node.js tries IPv6 first, but many government APIs have broken IPv6).
### Fetch Patching
`installRuntimeFetchPatch()` in `src/services/runtime.ts` replaces `window.fetch` on the desktop renderer. All `/api/*` requests route to the sidecar with `Authorization: Bearer <token>` (5-min TTL from Tauri IPC). If the sidecar fails, requests fall back to the cloud API.
**Source files**: `src-tauri/src/main.rs`, `src-tauri/sidecar/local-api-server.mjs`, `src/services/runtime.ts`, `src/services/tauri-bridge.ts`
---
## 8. Security Model
### Trust Boundaries
```
Browser ↔ Vercel Edge ↔ Upstream APIs
Desktop ↔ Sidecar ↔ Cloud API / Upstream APIs
```
### Content Security Policy
Three CSP sources that must stay in sync:
1. `index.html` `<meta>` tag (development, Tauri fallback)
2. `vercel.json` HTTP header (production, overrides meta)
3. `src-tauri/tauri.conf.json` (desktop)
### Authentication
API keys are required for non-browser origins. Trusted browser origins (production domains, Vercel preview deployments, localhost) are exempt. Premium RPC paths always require a key.
### Bot Protection
`middleware.ts` filters automated traffic: blocks known crawler user-agents on API and asset paths, allows social preview bots (Twitter, Facebook, LinkedIn, Telegram, Discord) on story and OG endpoints.
### Rate Limiting
Per-IP sliding window via Upstash with per-endpoint overrides for high-traffic paths.
### Desktop Secret Storage
Secrets are stored in the platform keyring (never plaintext), injected into the sidecar via Tauri IPC, and scoped to an allowlist of environment variable keys.
**Source files**: `middleware.ts`, `vercel.json`, `index.html`, `src-tauri/tauri.conf.json`, `api/_api-key.js`, `server/_shared/rate-limit.ts`
---
## 9. Caching Architecture
### Four-Layer Hierarchy
```
Bootstrap seed (Railway writes to Redis on schedule)
↓ miss
In-memory cache (per Vercel instance, short TTL)
↓ miss
Redis (Upstash, cross-instance, cachedFetchJson coalesces concurrent misses)
↓ miss
Upstream API fetch (result cached back to Redis + seed-meta written)
```
### Cache Key Rules
Every RPC handler with shared cache MUST include request-varying parameters in the cache key. Failure to do so causes cross-request data leakage.
### ETag / Conditional Requests
`server/gateway.ts` computes an FNV-1a hash of each response body and returns it as an `ETag`. Clients send `If-None-Match` and receive `304 Not Modified` when content is unchanged.
### CDN Integration
`CDN-Cache-Control` headers give Cloudflare edge (when enabled) longer TTLs than `Cache-Control`, since CF can revalidate via ETag without full payload transfer.
### Seed Metadata
Every cache write also writes `seed-meta:<key>` with `{ fetchedAt, recordCount }`. The health endpoint reads these to determine data freshness and raise staleness alerts.
**Source files**: `server/_shared/redis.ts`, `server/gateway.ts`, `api/health.js`
---
## 10. Testing
### Unit and Integration
`node:test` runner. Test files in `tests/*.test.{mjs,mts}` cover: server handlers, cache keying, circuit breakers, edge function constraints, data validation, market quote dedup, health checks, panel config guardrails, and variant layer filtering.
### Sidecar and API Tests
`api/*.test.mjs` and `src-tauri/sidecar/*.test.mjs` test CORS handling, YouTube embed proxying, and local API server behavior.
### End-to-End
Playwright specs in `e2e/*.spec.ts` test theme toggling, circuit breaker persistence, keyword spike flows, mobile map interactions, runtime fetch patching, and visual regression via golden screenshot comparison per variant.
### Edge Function Guardrails
`tests/edge-functions.test.mjs` validates that all non-helper `api/*.js` files are self-contained: no `node:` built-in imports, no cross-directory `../server/` or `../src/` imports. The pre-push hook also runs an esbuild bundle check on each endpoint.
### Pre-Push Hook
Runs before every `git push`:
1. TypeScript check (`tsc --noEmit` for src and API)
2. CJS syntax validation
3. Edge function esbuild bundle check
4. Edge function import guardrail test
5. Markdown lint
6. MDX lint (Mintlify compatibility)
7. Version sync check
**Source files**: `tests/`, `e2e/`, `playwright.config.ts`, `.husky/pre-push`
---
## 11. CI/CD
| Workflow | Trigger | Checks |
|----------|---------|--------|
| `typecheck.yml` | PR, push to main | `tsc --noEmit` for src and API tsconfigs |
| `lint.yml` | PR (markdown changes) | markdownlint-cli2 |
| `proto-check.yml` | PR (proto changes) | Generated code matches committed output |
| `build-desktop.yml` | Release tag, manual | 5-platform matrix build, code signing (macOS), AppImage library stripping (Linux), smoke test |
| `docker-publish.yml` | Release, manual | Multi-arch image (amd64, arm64) pushed to GHCR |
| `test-linux-app.yml` | Manual | Linux AppImage build + headless smoke test with screenshot verification |
**Source files**: `.github/workflows/`, `.husky/pre-push`
---
## 12. Directory Reference
```
.
├── api/ Vercel Edge Functions (self-contained JS)
│ ├── _*.js Shared helpers (CORS, rate-limit, API key, relay)
│ └── <domain>/ Domain endpoints (aviation/, climate/, conflict/, ...)
├── blog-site/ Static blog (built into public/blog/)
├── convex/ Convex backend (contact form, waitlist)
├── data/ Static data files (conservation, renewable, happiness)
├── deploy/ Deployment configs
├── docker/ Dockerfile + nginx config for Railway
├── docs/ Mintlify documentation site
├── e2e/ Playwright E2E specs
├── proto/ Protobuf service definitions (sebuf framework)
├── scripts/ Seed scripts, build helpers, relay service
├── server/ Server-side code (bundled into Edge Functions)
│ ├── _shared/ Redis, rate-limit, LLM, caching utilities
│ ├── gateway.ts Domain gateway factory
│ ├── router.ts Route matching
│ └── worldmonitor/ Domain handlers (mirrors proto structure)
├── shared/ Cross-platform JSON configs (markets, RSS domains)
├── src/ Browser SPA (TypeScript)
│ ├── app/ App orchestration managers
│ ├── bootstrap/ Chunk reload recovery
│ ├── components/ Panel subclasses + map components
│ ├── config/ Variant, panel, layer, market configurations
│ ├── generated/ Proto-generated client/server stubs (DO NOT EDIT)
│ ├── locales/ i18n translation files
│ ├── services/ Business logic organized by domain
│ ├── types/ TypeScript type definitions
│ ├── utils/ Shared utilities (circuit-breaker, theme, URL state)
│ └── workers/ Web Workers (analysis, ML, vector DB)
├── src-tauri/ Tauri desktop shell (Rust)
│ └── sidecar/ Node.js sidecar API server
└── tests/ Unit/integration tests (node:test)
```
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to World Monitor are documented here.
## [Unreleased]
### Added
- US Treasury customs revenue in Trade Policy panel with monthly data, FYTD year-over-year comparison, and revenue spike highlighting (#1663)
- Security advisories gold standard migration: Railway cron seed fetches 24 government RSS feeds hourly, Vercel reads Redis only (#1637)
- CMD+K full panel coverage: all 55 panels now searchable (was 31), including AI forecasts, correlation panels, webcams, displacement, security advisories (#1656)
- Chokepoint transit intelligence with 3 free data sources: IMF PortWatch (vessel transit counts), CorridorRisk (risk intelligence), AISStream (24h crossing counter) (#1560)
- 13 monitored chokepoints (was 6): added Cape of Good Hope, Gibraltar, Bosporus Strait (absorbs Dardanelles), Korea, Dover, Kerch, Lombok (#1560, #1572)
- Expandable chokepoint cards with TradingView lightweight-charts 180-day time-series (tanker vs cargo) (#1560)
- Real-time transit counting with enter+dwell+exit crossing detection, 30min cooldown (#1560)
- PortWatch, CorridorRisk, and transit seed loops on Railway relay (#1560)
- R2 trace storage for forecast debugging with Cloudflare API upload (#1655)
### Fixed
- Trade Policy panel WTO gate changed from panel-wide to per-tab, so Revenue tab works on desktop without WTO API key (#1663)
- Conflict-intel seed succeeds without ACLED credentials by accepting empty events when humanitarian/PizzINT data is available (#1651)
- Seed-forecasts crash from top-level @aws-sdk/client-s3 import resolved with lazy dynamic import (#1654)
- Bootstrap desktop timeouts restored (5s/8s) while keeping aggressive web timeouts (1.2s/1.8s) (#1653)
- Service worker navigation reverted to NetworkOnly to prevent stale HTML caching on deploy (#1653)
- Railway seed watch paths fixed for 5 services (seed-insights, seed-unrest-events, seed-prediction-markets, seed-infra, seed-gpsjam)
- PortWatch ArcGIS URL, field names, and chokepoint name mappings (#1572)
## [2.6.1] - 2026-03-11
### Highlights
- **Blog Platform** — Astro-powered blog at /blog with 16 SEO-optimized posts, OG images, and site footer (#1401, #1405, #1409)
- **Country Intelligence** — country facts section with right-click context menu (#1400)
- **Satellite Imagery Overhaul** — globe-native rendering, outline-only polygons, CSP fixes (#1381, #1385, #1376)
### Added
- Astro blog at /blog with 16 SEO posts and build integration (#1401, #1403)
- Blog redesign to match /pro page design system (#1405)
- Blog SEO, OG images, favicon fix, and site footer (#1409)
- Country facts section and right-click context menu for intel panel (#1400)
- Satellite imagery panel enabled in orbital surveillance layer (#1375)
- Globe-native satellite imagery, removed sidebar panel (#1381)
- Layer search filter with synonym support (#1369)
- Close buttons on panels and Add Panel block (#1354)
- Enterprise contact form endpoint (#1365)
- Commodity and happy variants shown on all header versions (#1407)
### Fixed
- NOTAM closures merged into Aviation layer (#1408)
- Intel deep dive layout reordered, duplicate headlines removed (#1404)
- Satellite imagery outline-only polygons to eliminate alpha stacking blue tint (#1385)
- Enterprise form hardened with mandatory fields and lead qualification (#1382)
- Country intel silently dismisses when geocode cannot identify a country (#1383)
- Globe hit targets enlarged for small marker types (#1378)
- Imagery panel hidden for existing users and viewport refetch deadlock (#1377)
- CSP violations for satellite preview images (#1376)
- Safari TypeError filtering and Sentry noise patterns (#1380)
- Swedish locale 'avbruten' TypeError variant filtered (#1402)
- Satellite imagery STAC backend fix, merged into Orbital Surveillance (#1364)
- Aviation "Computed" source replaced with specific labels, reduced cache TTLs (#1374)
- Close button and hover-pause on all marker tooltips (#1371)
- Invalid 'satelliteImagery' removed from LAYER_SYNONYMS (#1370)
- Risk scores seeding gap and seed-meta key mismatch (#1366)
- Consistent LIVE header pattern across news and webcams panels (#1367)
- Globe null guards in path accessor callbacks (#1372)
- Node_modules guard in pre-push hook, pinned Node 22 (#1368)
- Typecheck CI workflow: removed paths-ignore, added push trigger (#1373)
- Theme toggle removed from header (#1407)
## [2.6.0] - 2026-03-09
### Highlights
- **Orbital Surveillance** — real-time satellite tracking layer with TLE propagation (#1278)
- **Premium Finance Suite** — stock analysis tools for Pro tier (#1268)
- **Self-hosted Basemap** — migrated from CARTO to PMTiles on Cloudflare R2 (#1064)
- **GPS Jamming v2** — migrated from gpsjam.org to Wingbits API with H3 hexagons (#1240)
- **Military Flights Overhaul** — centralized via Redis seed + edge handler with OpenSky/Wingbits fallbacks (#1263, #1274, #1275, #1276)
- **Pro Waitlist & Landing Page** — referral system, Turnstile CAPTCHA, 21-language localization (#1140, #1187)
- **Server-side AI Classification** — batch headline classification moves from client to server (#1195)
- **Commodity Variant** — new app variant focused on commodities with relevant panels & layers (#1040, #1100)
- **Health Check System** — comprehensive health endpoint with auto seed-meta freshness tracking (#1091, #1127, #1128)
### Added
- Orbital surveillance layer with real-time satellite tracking via satellite.js (#1278, #1281)
- Premium finance stock analysis suite for Pro tier (#1268)
- GPS jamming migration to Wingbits API with H3 hex grid (#1240)
- Commodity app variant with dedicated panels and map layers (#1040, #1100)
- Pro waitlist landing page with referral system and Turnstile CAPTCHA (#1140)
- Pro landing page localization — 21 languages (#1187)
- Pro page repositioning toward markets, macro & geopolitics (#1261)
- Referral invite banner when visiting via `?ref=` link (#1232)
- Server-side batch AI classification for news headlines (#1195)
- Self-hosted PMTiles basemap on Cloudflare R2, replacing CARTO (#1064)
- Per-provider map theme selector (#1101)
- Globe visual preset setting (Earth / Cosmos) with texture selection (#1090, #1076)
- Comprehensive health check endpoint for UptimeRobot (#1091)
- Auto seed-meta freshness tracking for all RPC handlers (#1127)
- Submarine cables expanded to 86 via TeleGeography API (#1224)
- Pak-Afghan conflict zone and country boundary override system (#1150)
- Sudan and Myanmar conflict zone polygon improvements (#1216)
- Iran events: 28 new location coords, 48h TTL (#1251)
- Tech HQs in Ireland data (#1244)
- BIS data seed job (#1131)
- CoinPaprika fallback for crypto/stablecoin data (#1092)
- Rudaw TV live stream and RSS feed (#1117)
- Dubai and Riyadh added to default airport watchlist (#1144)
- Cmd+K: 16 missing layer toggles (#1289), "See all commands" link with category list (#1270)
- UTM attribution tags on all outbound links (#1233)
- Performance warning dialog replaces hard layer limit (#1088)
- Unified error/retry UX with muted styling and countdown (#1115)
- Settings reorganized into collapsible groups (#1110)
- Reset Layout button with tooltip (#1267, #1250)
- Markdown lint in pre-push hook (#1166)
### Changed
- Military flights centralized via Redis seed + edge handler pattern (#1263)
- Military flights seed with OpenSky anonymous fallback + Wingbits fallback (#1274, #1275)
- Theater posture computed directly in relay instead of pinging Vercel RPC (#1259)
- Countries GeoJSON served from R2 CDN (#1164)
- Consolidated duplicated market data lists into shared JSON configs (#1212)
- Eliminate all frontend external API calls — enforce gold standard pattern (#1217)
- WB indicators seeded on Railway, never called from frontend (#1159, #1157)
- Temporal baseline for news + fires moved to server-side (#1194)
- Panel creation guarded by variant config (#1221)
- Panel tab styles unified to underline pattern across all panels (#1106, #1182, #1190, #1192)
- Reduce default map layers (#1141)
- Share dialog dismissals persist across subdomains via cookies (#1286)
- Country-wide conflict zones use actual country geometry (#1245)
- Aviation seed interval reduced to 1h (#1258)
- Replace curl with native Node.js HTTP CONNECT tunnel in seeds (#1287)
- Seed scripts use `_seed-utils.mjs` shared configs from `scripts/shared/` (#1231, #1234)
### Fixed
- **Rate Limiting**: prioritize `cf-connecting-ip` over `x-real-ip` for correct per-user rate limiting behind CF proxy (#1241)
- **Security**: harden cache keys against injection and hash collision (#1103), per-endpoint rate limits for summarize endpoints (#1161)
- **Map**: prevent ghost layers rendering without a toggle (#1264), DeckGL layer toggles getting stuck (#1248), auto-fallback to OpenFreeMap on basemap failure (#1109), CORS fallback for Carto basemap (#1142), use CORS-enabled R2 URL for PMTiles in Tauri (#1119), CII Instability layer disabled in 3D mode (#1292)
- **Layout**: reconcile ultrawide zones when map is hidden (#1246), keep settings button visible on scaled desktop widths (#1249), exit fullscreen before switching variants (#1253), apply map-hidden layout class on initial load (#1087), preserve panel column position across refresh (#1170, #1108, #1112)
- **Panels**: event delegation to survive setContent debounce (#1203), guard RPC response array access with optional chaining (#1174), clear stuck error headers and sanitize error messages (#1175), lazy panel race conditions + server feed gaps (#1113), Tech Readiness panel loading on full variant (#1208), Strategic Risk panel button listeners (#1214), World Clock green home row (#1202), Airline Intelligence CSS grid layouts (#1197)
- **Pro/Turnstile**: explicit rendering to fix widget race condition (#1189), invisible widget support (#1215), CSP allow Turnstile (#1155), handle `already_registered` state (#1183), reset on enterprise form error (#1222), registration feedback and referral code gen (#1229, #1228), no-cache header for /pro (#1179), correct API endpoint path (#1177), www redirect loop fix (#1198, #1201)
- **SEO**: comprehensive improvements for /pro and main pages (#1271)
- **Railway**: remove custom railpack.json install step causing ENOENT builds (#1296, #1290, #1288)
- **Aviation**: correct cancellation rate calculation and add 12 airports (#1209), unify NOTAM status logic (#1225)
- **Sentry**: triage 26 issues, fix 3 bugs, add 29 noise filters (#1173, #1098)
- **Health**: treat missing seed-meta as stale (#1128), resolve BIS credit and theater posture warnings (#1124), add WB seed loop (#1239), UCDP auth handling (#1252)
- **Country Brief**: formatting, duplication, and news cap fixes (#1219), prevent modal stuck on geocode failure (#1134)
- **Economic**: guard BIS and spending data against undefined (#1162, #1169)
- **Webcams**: detect blocked YouTube embeds on web (#1107), use iframe load event fallback (#1123), MTV Lebanon as live stream (#1122)
- **Desktop**: recover stranded routing fixes and unified error UX (#1160), DRY debounce, error handling, retry cap (#1084), debounce cache writes, batch secret push, lazy panels (#1077)
- **PWA**: bump SW nuke key to v2 for CF-cached 404s (#1081), one-time SW nuke on first visit (#1079)
- **Performance**: only show layer warning when adding layers, not removing (#1265), reduce unnecessary Vercel edge invocations (#1176)
- **i18n**: sync all 20 locales to en.json — zero drift (#1104), correct indentation for geocode error keys (#1147)
- **Insights**: graceful exit, LKG fallback, swap to Gemini 2.5 Flash (#1153, #1154)
- **Seeds**: prevent API quota burn and respect rate limits (#1167), gracefully skip write when validation fails (#1089), seed-meta tracking for all bootstrap keys (#1163, #1138)
## [2.5.25] - 2026-03-04
### Changed
- **Supply Chain v2** — bump chokepoints & minerals cache keys to v2; add `aisDisruptions` field to `ChokepointInfo` (proto, OpenAPI, generated types, handler, UI panel); rename Malacca Strait → Strait of Malacca; reduce chokepoint Redis TTL from 15 min to 5 min; expand description to always show warning + AIS disruption counts; remove Nickel & Copper from critical minerals data (focus on export-controlled minerals); slice top producers to 3; use full FRED series names for shipping indices; add `daily` cache tier (86400s) and move minerals route to it; align client-side circuit breaker TTLs with server TTLs; fix upstream-unavailable banner to only show when no data is present; register supply-chain routes in Vite dev server plugin
- **Cache migration**: old `supply_chain:chokepoints:v1` and `supply_chain:minerals:v1` Redis keys are no longer read by any consumer — they will expire via TTL with no action required
## [2.5.24] - 2026-03-03
### Highlights
- **UCDP conflict data** — integrated Uppsala Conflict Data Program for historical & ongoing armed conflict tracking (#760)
- **Country brief sharing** — maximize mode, shareable URLs, native browser share button, expanded sections (#743, #854)
- **Unified Vercel deployment** — consolidated 4 separate deployments into 1 via runtime variant detection (#756)
- **CDN performance overhaul** — POST→GET conversion, per-domain edge functions, tiered bootstrap for ~46% egress reduction (#753, #795, #838)
- **Security hardening** — CSP script hashes replace unsafe-inline, crypto.randomUUID() for IDs, XSS-safe i18n, Finnhub token header (#781, #844, #861, #744)
- **i18n expansion** — French support with Live TV channels, hardcoded English strings replaced with translation keys (#794, #851, #839)
### Added
- UCDP (Uppsala Conflict Data Program) integration for armed conflict tracking (#760)
- Iran & Strait of Hormuz conflict zones, upgraded Ukraine polygon (#731)
- 100 Iran war events seeded with expanded geocoder (#792)
- Country brief maximize mode, shareable URLs, expanded sections & i18n (#743)
- Native browser share button for country briefs (#854)
- French i18n support with French Live TV channels (#851)
- Geo-restricted live channel support, restored WELT (#765)
- Manage Channels UX — toggle from grid + show all channels (#745)
- Command palette: disambiguate Map vs Panel commands, split country into map/brief (#736)
- Command palette: rotating contextual tips replace static empty state (#737)
- Download App button for web users with dropdown (#734, #735)
- Reset layout button to restore default panel sizes and order (#801)
- System status moved into settings (#735)
- Vercel cron to pre-warm AviationStack cache (#776)
- Runtime variant detection — consolidate 4 Vercel deployments into 1 (#756)
- CJS syntax check in pre-push hook (#769)
### Fixed
- **Security**: XSS — wrap `t()` calls in `escapeHtml()` (#861), use `crypto.randomUUID()` instead of `Math.random()` for ID generation (#844), move Finnhub API key from query string to `X-Finnhub-Token` header (#744)
- **i18n**: replace hardcoded English strings with translation keys (#839), i18n improvements (#794)
- **Market**: parse comma-separated query params and align Railway cache keys (#856), Railway market data cron + complete missing tech feed categories (#850), Yahoo relay fallback + RSS digest relay for blocked feeds (#835), tech UNAVAILABLE feeds + Yahoo batch early-exit + sector heatmap gate (#810)
- **Aviation**: move AviationStack fetching to Railway relay, reduce to 40 airports (#858)
- **UI**: cancel pending debounced calls on component destroy (#848), guard async operations against stale DOM references (#843)
- **Sentry**: guard stale DOM refs, audio.play() compat, add 16 noise filters (#855)
- **Relay**: exponential backoff for failing RSS feeds (#853), deduplicate UCDP constants crashing Railway container (#766)
- **API**: remove `[domain]` catch-all that intercepted all RPC routes (#753 regression) (#785), pageSize bounds validation on research handlers (#819), return 405 for wrong HTTP method (#757), pagination cursor for cyber threats (#754)
- **Conflict**: bump Iran events cache-bust to v7 (#724)
- **OREF**: prevent LLM translation cache from poisoning Hebrew→English pipeline (#733), strip translation labels from World Brief input (#768)
- **Military**: harden USNI fleet report ship name regex (#805)
- **Sidecar**: add required params to ACLED API key validation probe (#804)
- **Macro**: replace hardcoded BTC mining thresholds with Mayer Multiple (#750)
- **Cyber**: reduce GeoIP per-IP timeout from 3s to 1.5s (#748)
- **CSP**: restore unsafe-inline for Vercel bot-challenge pages (#788), add missing script hash and finance variant (#798)
- **Runtime**: route all /api/* calls through CDN edge instead of direct Vercel (#780)
- **Desktop**: detect Linux node target from host arch (#742), harden Windows installer update path + map resize (#739), close update toast after clicking download (#738), only open valid http(s) links externally (#723)
- **Webcams**: replace dead Tel Aviv live stream (#732), replace stale Jerusalem feed (#849)
- Story header uses full domain WORLDMONITOR.APP (#799)
- Open variant nav links in same window instead of new tab (#721)
- Suppress map renders during resize drag (#728)
- Append deduction panel to DOM after async import resolves (#764)
- Deduplicate stale-while-revalidate background fetches in CircuitBreaker (#793)
- CORS fallback, rate-limit bump, RSS proxy allowlist (#814)
- Unavailable stream error messages updated (#759)
### Performance
- Tier slow/fast bootstrap data for ~46% CDN egress reduction (#838)
- Convert POST RPCs to GET for CDN caching (#795)
- Split monolithic edge function into per-domain functions (#753)
- Increase CDN cache TTLs + add stale-if-error across edge functions (#777)
- Bump CDN cache TTLs for oref-alerts and youtube/live (#791)
- Skip wasted direct fetch for Vercel-blocked domains in RSS proxy (#815)
### Security
- Replace CSP unsafe-inline with script hashes and add trust signals (#781)
- Expand Permissions-Policy and tighten CSP connect-src (#779)
### Changed
- Extend support for larger screens (#740)
- Green download button + retire sliding popup (#747)
- Extract shared relay helper into `_relay.js` (#782)
- Consolidate `SummarizeArticleResponse` status fields (#813)
- Consolidate `declare const process` into shared `env.d.ts` (#752)
- Deduplicate `clampInt` into `server/_shared/constants`
- Add error logging for network errors in error mapper (#746)
- Redis error logging + reduced timeouts for edge functions (#749)
---
## [2.5.21] - 2026-03-01
### Highlights
- **Iran Attacks map layer** — conflict events with severity badges, related event popups, and CII integration (#511, #527, #547, #549)
- **Telegram Intel panel** — 27 curated OSINT channels via MTProto relay (#550)
- **OREF Israel Sirens** — real-time alerts with Hebrew→English translation and 24h history bootstrap (#545, #556, #582)
- **GPS/GNSS jamming layer** — detection overlay with CII integration (#570)
- **Day/night terminator** — solar terminator overlay on map (#529)
- **Breaking news alert banner** — audio alerts for critical/high RSS items with cooldown bypass (#508, #516, #533)
- **AviationStack integration** — global airport delays for 128 airports with NOTAM closure detection (#552, #581, #583)
- **Strategic risk score** — theater posture + breaking news wired into scoring algorithm (#584)
### Added
- Iran Attacks map layer with conflict event popups, severity badges, and priority rendering (#511, #527, #549)
- Telegram Intel panel with curated OSINT channel list (#550, #600)
- OREF Israel Sirens panel with Hebrew-to-English translation (#545, #556)
- OREF 24h history bootstrap on relay startup (#582)
- GPS/GNSS jamming detection map layer + CII integration (#570)
- Day/night solar terminator overlay (#529)
- Breaking news active alert banner with audio for critical/high items (#508)
- AviationStack integration for non-US airports + NOTAM closure detection (#552, #581, #583)
- RT (Russia Today) HLS livestream + RSS feeds (#585, #586)
- Iran webcams tab with 4 feeds (#569, #572, #601)
- CBC News optional live channel (#502)
- Strategic risk score wired to theater posture + breaking news (#584)
- CII scoring: security advisories, Iran strikes, OREF sirens, GPS jamming (#547, #559, #570, #579)
- Country brief + CII signal coverage expansion (#611)
- Server-side military bases with 125K+ entries + rate limiting (#496)
- AVIATIONSTACK_API key in desktop settings (#553)
- Iran events seed script and latest data (#575)
### Fixed
- **Aviation**: stale IndexedDB cache invalidation + reduced CDN TTL (#607), broken lock replaced with direct cache + cancellation tiers (#591), query all airports instead of rotating batch (#557), NOTAM routing through Railway relay (#599), always show all monitored airports (#603)
- **Telegram**: AUTH_KEY_DUPLICATED fixes — latch to stop retry spam (#543), 60s startup delay (#587), graceful shutdown + poll guard (#562), ESM import path fixes (#537, #542), missing relay auth headers (#590)
- **Relay**: Polymarket OOM prevention — circuit breaker + concurrency limiter (#519), request deduplication (#513), queue backpressure + response slicing (#593), cache stampede fix (#592), kill switch (#523); smart quotes crash (#563); graceful shutdown (#562, #565); curl for OREF (#546, #567, #571); maxBuffer ENOBUFS (#609); rsshub.app blocked (#526); ERR_HTTP_HEADERS_SENT guard (#509); Telegram memory cleanup (#531)
- **Live news**: 7 stale YouTube fallback IDs replaced (#535, #538), broken Europe channel handles (#541), eNCA handle + VTC NOW removal + CTI News (#604), RT HLS recovery (#610), YouTube proxy auth alignment (#554, #555), residential proxy + gzip for detection (#551)
- **Breaking news**: critical alerts bypass cooldown (#516), keyword gaps filled (#517, #521), fake pubDate filter (#517), SESSION_START gate removed (#533)
- **Threat classifier**: military/conflict keyword gaps + news-to-conflict bridge (#514), Groq 429 stagger (#520)
- **Geo**: tokenization-based matching to prevent false positives (#503), 60+ missing locations in hub index (#528)
- **Iran**: CDN cache-bust pipeline v4 (#524, #532, #544), read-only handler (#518), Gulf misattribution via bbox disambiguation (#532)
- **CII**: Gulf country strike misattribution (#564), compound escalation for military action (#548)
- **Bootstrap**: 401/429 rate limiting fix (#512), hydration cache + polling hardening (#504)
- **Sentry**: guard YT player methods + GM/InvalidState noise (#602), Android OEM WebView bridge injection (#510), setView invalid preset (#580), beforeSend null-filename leak (#561)
- Rate limiting raised to 300 req/min sliding window (#515)
- Vercel preview origin regex generalized + bases cache key (#506)
- Cross-env for Windows-compatible npm scripts (#499)
- Download banner repositioned to bottom-right (#536)
- Stale/expired Polymarket markets filtered (#507)
- Cyber GeoIP centroid fallback jitter made deterministic (#498)
- Cache-control headers hardened for polymarket and rss-proxy (#613)
### Performance
- Server-side military base fetches: debounce + static edge cache tier (#497)
- RSS: refresh interval raised to 10min, cache TTL to 20min (#612)
- Polymarket cache TTL raised to 10 minutes (#568)
### Changed
- Stripped 61 debug console.log calls from 20 service files (#501)
- Bumped version to 2.5.21 (#605)
---
## [2.5.20] - 2026-02-27
### Added
- **Edge caching**: Complete Cloudflare edge cache tier coverage with degraded-response policy (#484)
- **Edge caching**: Cloudflare edge caching for proxy.worldmonitor.app (#478) and api.worldmonitor.app (#471)
- **Edge caching**: Tiered edge Cache-Control aligned to upstream TTLs (#474)
- **API migration**: Convert 52 API endpoints from POST to GET for edge caching (#468)
- **Gateway**: Configurable VITE_WS_API_URL + harden POST-to-GET shim (#480)
- **Cache**: Negative-result caching for cachedFetchJson (#466)
- **Security advisories**: New panel with government travel alerts (#460)
- **Settings**: Redesign settings window with VS Code-style sidebar layout (#461)
### Fixed
- **Commodities panel**: Was showing stocks instead of commodities — circuit breaker SWR returned stale data from a different call when cacheTtlMs=0 (#483)
- **Analytics**: Use greedy regex in PostHog ingest rewrites (#481)
- **Sentry**: Add noise filters for 4 unresolved issues (#479)
- **Gateway**: Convert stale POST requests to GET for backwards compat (#477)
- **Desktop**: Enable click-to-play YouTube embeds + CISA feed fixes (#476)
- **Tech variant**: Use rss() for CISA feed, drop build from pre-push hook (#475)
- **Security advisories**: Route feeds through RSS proxy to avoid CORS blocks (#473)
- **API routing**: Move 5 path-param endpoints to query params for Vercel routing (#472)
- **Beta**: Eagerly load T5-small model when beta mode is enabled
- **Scripts**: Handle escaped apostrophes in feed name regex (#455)
- **Wingbits**: Add 5-minute backoff on /v1/flights failures (#459)
- **Ollama**: Strip thinking tokens, raise max_tokens, fix panel summary cache (#456)
- **RSS/HLS**: RSS feed repairs, HLS native playback, summarization cache fix (#452)
### Performance
- **AIS proxy**: Increase AIS snapshot edge TTL from 2s to 10s (#482)
---
## [2.5.10] - 2026-02-26
### Fixed
- **Yahoo Finance rate-limit UX**: Show "rate limited — retrying shortly" instead of generic "Failed to load" on Markets, ETF, Commodities, and Sector panels when Yahoo returns 429 (#407)
- **Sequential Yahoo calls**: Replace `Promise.all` with staggered batching in commodity quotes, ETF flows, and macro signals to prevent 429 rate limiting (#406)
- **Sector heatmap Yahoo fallback**: Sector data now loads via Yahoo Finance when `FINNHUB_API_KEY` is missing (#406)
- **Finnhub-to-Yahoo fallback**: Market quotes route Finnhub symbols through Yahoo when API key is not configured (#407)
- **ETF early-exit on rate limit**: Skip retry loop and show rate-limit message immediately instead of waiting 60s (#407)
- **Sidecar auth resilience**: 401-retry with token refresh for stale sidecar tokens after restart; `diagFetch` auth helper for settings window diagnostics (#407)
- **Verbose toggle persistence**: Write verbose state to writable data directory instead of read-only app bundle on macOS (#407)
- **AI summary verbosity**: Tighten prompts to 2 sentences / 60 words max with `max_tokens` reduced from 150 to 100 (#404)
- **Settings modal title**: Rename from "PANELS" to "SETTINGS" across all 17 locales (#403)
- **Sentry noise filters**: CSS.escape() for news ID selectors, player.destroy guard, 11 new ignoreErrors patterns, blob: URL extension frame filter (#402)
---
## [2.5.6] - 2026-02-23
### Added
- **Greek (Ελληνικά) locale** — full translation of all 1,397 i18n keys (#256)
- **Nigeria RSS feeds** — 5 new sources: Premium Times, Vanguard, Channels TV, Daily Trust, ThisDay Live
- **Greek locale feeds** — Naftemporiki, in.gr, iefimerida.gr for Greek-language news coverage
- **Brasil Paralelo source** — Brazilian news with RSS feed and source tier (#260)
### Performance
- **AIS relay optimization** — backpressure queue with configurable watermarks, spatial indexing for chokepoint detection (O(chokepoints) vs O(chokepoints × vessels)), pre-serialized + pre-gzipped snapshot cache eliminating per-request JSON.stringify + gzip CPU (#266)
### Fixed
- **Vietnam flag country code** — corrected flag emoji in language selector (#245)
- **Sentry noise filters** — added patterns for SW FetchEvent, PostHog ingest; enabled SW POST method for PostHog analytics (#246)
- **Service Worker same-origin routing** — restricted SW route patterns to same-origin only, preventing cross-origin fetch interception (#247, #251)
- **Social preview bot allowlisting** — whitelisted Twitterbot, facebookexternalhit, and other crawlers on OG image assets (#251)
- **Windows CORS for Tauri** — allow `http://` origin from `tauri.localhost` for Windows desktop builds (#262)
- **Linux AppImage GLib crash** — fix GLib symbol mismatch on newer distros by bundling compatible libraries (#263)
---
## [2.5.2] - 2026-02-21
### Fixed
- **QuotaExceededError handling** — detect storage quota exhaustion and stop further writes to localStorage/IndexedDB instead of silently failing; shared `markStorageQuotaExceeded()` flag across persistent-cache and utility storage
- **deck.gl null.getProjection crash** — wrap `setProps()` calls in try/catch to survive map mid-teardown races in debounced/RAF callbacks
- **MapLibre "Style is not done loading"** — guard `setFilter()` in mousemove/mouseout handlers during theme switches
- **YouTube invalid video ID** — validate video ID format (`/^[\w-]{10,12}$/`) before passing to IFrame Player constructor
- **Vercel build skip on empty SHA** — guard `ignoreCommand` against unset `VERCEL_GIT_PREVIOUS_SHA` (first deploy, force deploy) which caused `git diff` to fail and cancel builds
- **Sentry noise filters** — added 7 patterns: iOS readonly property, SW FetchEvent, toLowerCase/trim/indexOf injections, QuotaExceededError
---
## [2.5.1] - 2026-02-20
### Performance
- **Batch FRED API requests** — frontend now sends a single request with comma-separated series IDs instead of 7 parallel edge function invocations, eliminating Vercel 25s timeouts
- **Parallel UCDP page fetches** — replaced sequential loop with Promise.all for up to 12 pages, cutting fetch time from ~96s worst-case to ~8s
- **Bot protection middleware** — blocks known social-media crawlers from hitting API routes, reducing unnecessary edge function invocations
- **Extended API cache TTLs** — country-intel 12h→24h, GDELT 2h→4h, nuclear 12h→24h; Vercel ignoreCommand skips non-code deploys
### Fixed
- **Partial UCDP cache poisoning** — failed page fetches no longer silently produce incomplete results cached for 6h; partial results get 10-min TTL in both Redis and memory, with `partial: true` flag propagated to CDN cache headers
- **FRED upstream error masking** — single-series failures now return 502 instead of empty 200; batch mode surfaces per-series errors and returns 502 when all fail
- **Sentry `Load failed` filter** — widened regex from `^TypeError: Load failed$` to `^TypeError: Load failed( \(.*\))?$` to catch host-suffixed variants (e.g., gamma-api.polymarket.com)
- **Tooltip XSS hardening** — replaced `rawHtml()` with `safeHtml()` allowlist sanitizer for panel info tooltips
- **UCDP country endpoint** — added missing HTTP method guards (OPTIONS/GET)
- **Middleware exact path matching** — social preview bot allowlist uses `Set.has()` instead of `startsWith()` prefix matching
### Changed
- FRED batch API supports up to 15 comma-separated series IDs with deduplication
- Missing FRED API key returns 200 with `X-Data-Status: skipped-no-api-key` header instead of silent empty response
- LAYER_TO_SOURCE config extracted from duplicate inline mappings into shared constant
---
## [2.5.0] - 2026-02-20
### Highlights
**Local LLM Support (Ollama / LM Studio)** — Run AI summarization entirely on your own hardware with zero cloud dependency. The desktop app auto-discovers models from any OpenAI-compatible local inference server (Ollama, LM Studio, llama.cpp, vLLM) and populates a selection dropdown. A 4-tier fallback chain ensures summaries always generate: Local LLM → Groq → OpenRouter → browser-side T5. Combined with the Tauri desktop app, this enables fully air-gapped intelligence analysis where no data leaves your machine.
### Added
- **Ollama / LM Studio integration** — local AI summarization via OpenAI-compatible `/v1/chat/completions` endpoint with automatic model discovery, embedding model filtering, and fallback to manual text input
- **4-tier summarization fallback chain** — Ollama (local) → Groq (cloud) → OpenRouter (cloud) → Transformers.js T5 (browser), each with 5-second timeout before silently advancing to the next
- **Shared summarization handler factory** — all three API tiers use identical logic for headline deduplication (Jaccard >0.6), variant-aware prompting, language-aware output, and Redis caching (`summary:v3:{mode}:{variant}:{lang}:{hash}`)
- **Settings window with 3 tabs** — dedicated **LLMs** tab (Ollama endpoint/model, Groq, OpenRouter), **API Keys** tab (12+ data source credentials), and **Debug & Logs** tab (traffic log, verbose mode, log file access). Each tab runs an independent verification pipeline
- **Consolidated keychain vault** — all desktop secrets stored as a single JSON blob in one OS keychain entry (`secrets-vault`), reducing macOS Keychain authorization prompts from 20+ to exactly 1 on app startup. One-time auto-migration from individual entries with cleanup
- **Cross-window secret synchronization** — saving credentials in the Settings window immediately syncs to the main dashboard via `localStorage` broadcast, with no app restart needed
- **API key verification pipeline** — each credential is validated against its provider's actual API endpoint. Network errors (timeouts, DNS failures) soft-pass to prevent transient failures from blocking key storage; only explicit 401/403 marks a key invalid
- **Plaintext URL inputs** — endpoint URLs (Ollama API, relay URLs, model names) display as readable text instead of masked password dots in Settings
- **5 new defense/intel RSS feeds** — Military Times, Task & Purpose, USNI News, Oryx OSINT, UK Ministry of Defence
- **Koeberg nuclear power plant** — added to the nuclear facilities map layer (the only commercial reactor in Africa, Cape Town, South Africa)
- **Privacy & Offline Architecture** documentation — README now details the three privacy levels: full cloud, desktop with cloud APIs, and air-gapped local with Ollama
- **AI Summarization Chain** documentation — README includes provider fallback flow diagram and detailed explanation of headline deduplication, variant-aware prompting, and cross-user cache deduplication
### Changed
- AI fallback chain now starts with Ollama (local) before cloud providers
- Feature toggles increased from 14 to 15 (added AI/Ollama)
- Desktop architecture uses consolidated vault instead of per-key keychain entries
- README expanded with ~85 lines of new content covering local LLM support, privacy architecture, summarization chain internals, and desktop readiness framework
### Fixed
- URL and model fields in Settings display as plaintext instead of masked password dots
- OpenAI-compatible endpoint flow hardened for Ollama/LM Studio response format differences (thinking tokens, missing `choices` array edge cases)
- Sentry null guard for `getProjection()` crash with 6 additional noise filters
- PathLayer cache cleared on layer toggle-off to prevent stale WebGL buffer rendering
---
## [2.4.1] - 2026-02-19
### Fixed
- **Map PathLayer cache**: Clear PathLayer on toggle-off to prevent stale WebGL buffers
- **Sentry noise**: Null guard for `getProjection()` crash and 6 additional noise filters
- **Markdown docs**: Resolve lint errors in documentation files
---
## [2.4.0] - 2026-02-19
### Added
- **Live Webcams Panel**: 2x2 grid of live YouTube webcam feeds from global hotspots with region filters (Middle East, Europe, Asia-Pacific, Americas), grid/single view toggle, idle detection, and full i18n support (#111)
- **Linux download**: added `.AppImage` option to download banner
### Changed
- **Mobile detection**: use viewport width only for mobile detection; touch-capable notebooks (e.g. ROG Flow X13) now get desktop layout (#113)
- **Webcam feeds**: curated Tel Aviv, Mecca, LA, Miami; replaced dead Tokyo feed; diverse ALL grid with Jerusalem, Tehran, Kyiv, Washington
### Fixed
- **Le Monde RSS**: English feed URL updated (`/en/rss/full.xml` → `/en/rss/une.xml`) to fix 404
- **Workbox precache**: added `html` to `globPatterns` so `navigateFallback` works for offline PWA
- **Panel ordering**: one-time migration ensures Live Webcams follows Live News for existing users
- **Mobile popups**: improved sheet/touch/controls layout (#109)
- **Intelligence alerts**: disabled on mobile to reduce noise (#110)
- **RSS proxy**: added 8 missing domains to allowlist
- **HTML tags**: repaired malformed tags in panel template literals
- **ML worker**: wrapped `unloadModel()` in try/catch to prevent unhandled timeout rejections
- **YouTube player**: optional chaining on `playVideo?.()` / `pauseVideo?.()` for initialization race
- **Panel drag**: guarded `.closest()` on non-Element event targets
- **Beta mode**: resolved race condition and timeout failures
- **Sentry noise**: added filters for Firefox `too much recursion`, maplibre `_layers`/`id`/`type` null crashes
## [2.3.9] - 2026-02-18
### Added
- **Full internationalization (14 locales)**: English, French, German, Spanish, Italian, Polish, Portuguese, Dutch, Swedish, Russian, Arabic, Chinese Simplified, Japanese — each with 1100+ translated keys
- **RTL support**: Arabic locale with `dir="rtl"`, dedicated RTL CSS overrides, regional language code normalization (e.g. `ar-SA` correctly triggers RTL)
- **Language switcher**: in-app locale picker with flag icons, persists to localStorage
- **i18n infrastructure**: i18next with browser language detection and English fallback
- **Community discussion widget**: floating pill linking to GitHub Discussions with delayed appearance and permanent dismiss
- **Linux AppImage**: added `ubuntu-22.04` to CI build matrix with webkit2gtk/appindicator dependencies
- **NHK World and Nikkei Asia**: added RSS feeds for Japan news coverage
- **Intelligence Findings badge toggle**: option to disable the findings badge in the UI
### Changed
- **Zero hardcoded English**: all UI text routed through `t()` — panels, modals, tooltips, popups, map legends, alert templates, signal descriptions
- **Trending proper-noun detection**: improved mid-sentence capitalization heuristic with all-caps fallback when ML classifier is unavailable
- **Stopword suppression**: added missing English stopwords to trending keyword filter
### Fixed
- **Dead UTC clock**: removed `#timeDisplay` element that permanently displayed `--:--:-- UTC`
- **Community widget duplicates**: added DOM idempotency guard preventing duplicate widgets on repeated news refresh cycles
- **Settings help text**: suppressed raw i18n key paths rendering when translation is missing
- **Intelligence Findings badge**: fixed toggle state and listener lifecycle
- **Context menu styles**: restored intel-findings context menu styles
- **CSS theme variables**: defined missing `--panel-bg` and `--panel-border` variables
## [2.3.8] - 2026-02-17
### Added
- **Finance variant**: Added a dedicated market-first variant (`finance.worldmonitor.app`) with finance/trading-focused feeds, panels, and map defaults
- **Finance desktop profile**: Added finance-specific desktop config and build profile for Tauri packaging
### Changed
- **Variant feed loading**: `loadNews` now enumerates categories dynamically and stages category fetches with bounded concurrency across variants
- **Feed resilience**: Replaced direct MarketWatch RSS usage in finance/full/tech paths with Google News-backed fallback queries
- **Classification pressure controls**: Tightened AI classification budgets for tech/full and tuned per-feed caps to reduce startup burst pressure
- **Timeline behavior**: Wired timeline filtering consistently across map and news panels
- **AI summarization defaults**: Switched OpenRouter summarization to auto-routed free-tier model selection
### Fixed
- **Finance panel parity**: Kept data-rich panels while adding news panels for finance instead of removing core data surfaces
- **Desktop finance map parity**: Finance variant now runs first-class Deck.GL map/layer behavior on desktop runtime
- **Polymarket fallback**: Added one-time direct connectivity probe and memoized fallback to prevent repeated `ERR_CONNECTION_RESET` storms
- **FRED fallback behavior**: Missing `FRED_API_KEY` now returns graceful empty payloads instead of repeated hard 500s
- **Preview CSP tooling**: Allowed `https://vercel.live` script in CSP so Vercel preview feedback injection is not blocked
- **Trending quality**: Suppressed noisy generic finance terms in keyword spike detection
- **Mobile UX**: Hidden desktop download prompt on mobile devices
## [2.3.7] - 2026-02-16
### Added
- **Full light mode theme**: Complete light/dark theme system with CSS custom properties, ThemeManager module, FOUC prevention, and `getCSSColor()` utility for theme-aware inline styles
- **Theme-aware maps and charts**: Deck.GL basemap, overlay layers, and CountryTimeline charts respond to theme changes in real time
- **Dark/light mode header toggle**: Sun/moon icon in the header bar for quick theme switching, replacing the duplicate UTC clock
- **Desktop update checker**: Architecture-aware download links for macOS (ARM/Intel) and Windows
- **Node.js bundled in Tauri installer**: Sidecar no longer requires system Node.js
- **Markdown linting**: Added markdownlint config and CI workflow
### Changed
- **Panels modal**: Reverted from "Settings" back to "Panels" — removed redundant Appearance section now that header has theme toggle
- **Default panels**: Enabled UCDP Conflict Events, UNHCR Displacement, Climate Anomalies, and Population Exposure panels by default
### Fixed
- **CORS for Tauri desktop**: Fixed CORS issues for desktop app requests
- **Markets panel**: Keep Yahoo-backed data visible when Finnhub API key is skipped
- **Windows UNC paths**: Preserve extended-length path prefix when sanitizing sidecar script path
- **Light mode readability**: Darkened neon semantic colors and overlay backgrounds for light mode contrast
## [2.3.6] - 2026-02-16
### Fixed
- **Windows console window**: Hide the `node.exe` console window that appeared alongside the desktop app on Windows
## [2.3.5] - 2026-02-16
### Changed
- **Panel error messages**: Differentiated error messages per panel so users see context-specific guidance instead of generic failures
- **Desktop config auto-hide**: Desktop configuration panel automatically hides on web deployments where it is not relevant
## [2.3.4] - 2026-02-16
### Fixed
- **Windows sidecar crash**: Strip `\\?\` UNC extended-length prefix from paths before passing to Node.js — Tauri `resource_dir()` on Windows returns UNC-prefixed paths that cause `EISDIR: lstat 'C:'` in Node.js module resolution
- **Windows sidecar CWD**: Set explicit `current_dir` on the Node.js Command to prevent bare drive-letter working directory issues from NSIS shortcut launcher
- **Sidecar package scope**: Add `package.json` with `"type": "module"` to sidecar directory, preventing Node.js from walking up the entire directory tree during ESM scope resolution
## [2.3.3] - 2026-02-16
### Fixed
- **Keychain persistence**: Enable `apple-native` (macOS) and `windows-native` (Windows) features for the `keyring` crate — v3 ships with no default platform backends, so API keys were stored in-memory only and lost on restart
- **Settings key verification**: Soft-pass network errors during API key verification so transient sidecar failures don't block saving
- **Resilient keychain reads**: Use `Promise.allSettled` in `loadDesktopSecrets` so a single key failure doesn't discard all loaded secrets
- **Settings window capabilities**: Add `"settings"` to Tauri capabilities window list for core plugin permissions
- **Input preservation**: Capture unsaved input values before DOM re-render in settings panel
## [2.3.0] - 2026-02-15
### Security
- **CORS hardening**: Tighten Vercel preview deployment regex to block origin spoofing (`worldmonitorEVIL.vercel.app`)
- **Sidecar auth bypass**: Move `/api/local-env-update` behind `LOCAL_API_TOKEN` auth check
- **Env key allowlist**: Restrict sidecar env mutations to 18 known secret keys (matching `SUPPORTED_SECRET_KEYS`)
- **postMessage validation**: Add `origin` and `source` checks on incoming messages in LiveNewsPanel
- **postMessage targetOrigin**: Replace wildcard `'*'` with specific embed origin
- **CORS enforcement**: Add `isDisallowedOrigin()` check to 25+ API endpoints that were missing it
- **Custom CORS migration**: Migrate `gdelt-geo` and `eia` from custom CORS to shared `_cors.js` module
- **New CORS coverage**: Add CORS headers + origin check to `firms-fires`, `stock-index`, `youtube/live`
- **YouTube embed origins**: Tighten `ALLOWED_ORIGINS` regex in `youtube/embed.js`
- **CSP hardening**: Remove `'unsafe-inline'` from `script-src` in both `index.html` and `tauri.conf.json`
- **iframe sandbox**: Add `sandbox="allow-scripts allow-same-origin allow-presentation"` to YouTube embed iframe
- **Meta tag validation**: Validate URL query params with regex allowlist in `parseStoryParams()`
### Fixed
- **Service worker stale assets**: Add `skipWaiting`, `clientsClaim`, and `cleanupOutdatedCaches` to workbox config — fixes `NS_ERROR_CORRUPTED_CONTENT` / MIME type errors when users have a cached SW serving old HTML after redeployment
## [2.2.6] - 2026-02-14
### Fixed
- Filter trending noise and fix sidecar auth
- Restore tech variant panels
- Remove Market Radar and Economic Data panels from tech variant
### Docs
- Add developer X/Twitter link to Support section
- Add cyber threat API keys to `.env.example`
## [2.2.5] - 2026-02-13
### Security
- Migrate all Vercel edge functions to CORS allowlist
- Restrict Railway relay CORS to allowed origins only
### Fixed
- Hide desktop config panel on web
- Route World Bank & Polymarket via Railway relay
## [2.2.3] - 2026-02-12
### Added
- Cyber threat intelligence map layer (Feodo Tracker, URLhaus, C2IntelFeeds, OTX, AbuseIPDB)
- Trending keyword spike detection with end-to-end flow
- Download desktop app slide-in banner for web visitors
- Country briefs in Cmd+K search
### Changed
- Redesign 4 panels with table layouts and scoped styles
- Redesign population exposure panel and reorder UCDP columns
- Dramatically increase cyber threat map density
### Fixed
- Resolve z-index conflict between pinned map and panels grid
- Cap geo enrichment at 12s timeout, prevent duplicate download banners
- Replace ipwho.is/ipapi.co with ipinfo.io/freeipapi.com for geo enrichment
- Harden trending spike processing and optimize hot paths
- Improve cyber threat tooltip/popup UX and dot visibility
## [2.2.2] - 2026-02-10
### Added
- Full-page Country Brief Page replacing modal overlay
- Download redirect API for platform-specific installers
### Fixed
- Normalize country name from GeoJSON to canonical TIER1 name
- Tighten headline relevance, add Top News section, compact markets
- Hide desktop config panel on web, fix irrelevant prediction markets
- Tone down climate anomalies heatmap to stop obscuring other layers
- macOS: hide window on close instead of quitting
### Performance
- Reduce idle CPU from pulse animation loop
- Harden regression guardrails in CI, cache, and map clustering
## [2.2.1] - 2026-02-08
### Fixed
- Consolidate variant naming and fix PWA tile caching
- Windows settings window: async command, no menu bar, no white flash
- Constrain layers menu height in DeckGLMap
- Allow Cloudflare Insights script in CSP
- macOS build failures when Apple signing secrets are missing
## [2.2.0] - 2026-02-07
Initial v2.2 release with multi-variant support (World + Tech), desktop app (Tauri), and comprehensive geopolitical intelligence features.
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in the
World Monitor community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Scope
This Code of Conduct applies within all community spaces (GitHub issues, pull
requests, discussions, and any associated communication channels) and also
applies when an individual is officially representing the community in public
spaces.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the project maintainer at **[GitHub Issues](https://github.com/koala73/worldmonitor/issues)** or by contacting the
repository owner directly through GitHub.
All complaints will be reviewed and investigated promptly and fairly. The project
team is obligated to maintain confidentiality with regard to the reporter of an
incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to World Monitor
Thank you for your interest in contributing to World Monitor! This project thrives on community contributions — whether it's code, data sources, documentation, or bug reports.
## Table of Contents
- [Architecture Overview](#architecture-overview)
- [Getting Started](#getting-started)
- [Development Setup](#development-setup)
- [How to Contribute](#how-to-contribute)
- [Pull Request Process](#pull-request-process)
- [AI-Assisted Development](#ai-assisted-development)
- [Coding Standards](#coding-standards)
- [Working with Sebuf (RPC Framework)](#working-with-sebuf-rpc-framework)
- [Adding Data Sources](#adding-data-sources)
- [Adding RSS Feeds](#adding-rss-feeds)
- [Reporting Bugs](#reporting-bugs)
- [Feature Requests](#feature-requests)
- [Code of Conduct](#code-of-conduct)
## Architecture Overview
World Monitor is a real-time OSINT dashboard built with **Vanilla TypeScript** (no UI framework), **MapLibre GL + deck.gl** for map rendering, and a custom Proto-first RPC framework called **Sebuf** for all API communication.
### Key Technologies
| Technology | Purpose |
|---|---|
| **TypeScript** | All code — frontend, edge functions, and handlers |
| **Vite** | Build tool and dev server |
| **Sebuf** | Proto-first HTTP RPC framework for typed API contracts |
| **Protobuf / Buf** | Service and message definitions across 22 domains |
| **MapLibre GL** | Base map rendering (tiles, globe mode, camera) |
| **deck.gl** | WebGL overlay layers (scatterplot, geojson, arcs, heatmaps) |
| **d3** | Charts, sparklines, and data visualization |
| **Vercel Edge Functions** | Serverless API gateway |
| **Tauri v2** | Desktop app (Windows, macOS, Linux) |
| **Convex** | Minimal backend (beta interest registration only) |
| **Playwright** | End-to-end and visual regression testing |
### Variant System
The codebase produces three app variants from the same source, each targeting a different audience:
| Variant | Command | Focus |
|---|---|---|
| `full` | `npm run dev` | Geopolitics, military, conflicts, infrastructure |
| `tech` | `npm run dev:tech` | Startups, AI/ML, cloud, cybersecurity |
| `finance` | `npm run dev:finance` | Markets, trading, central banks, commodities |
Variants share all code but differ in default panels, map layers, and RSS feeds. Variant configs live in `src/config/variants/`.
### Directory Structure
| Directory | Purpose |
|---|---|
| `src/components/` | UI components — Panel subclasses, map, modals (~50 panels) |
| `src/services/` | Data fetching modules — sebuf client wrappers, AI, signal analysis |
| `src/config/` | Static data and variant configs (feeds, geo, military, pipelines, ports) |
| `src/generated/` | Auto-generated sebuf client + server stubs (**do not edit by hand**) |
| `src/types/` | TypeScript type definitions |
| `src/locales/` | i18n JSON files (14 languages) |
| `src/workers/` | Web Workers for analysis |
| `server/` | Sebuf handler implementations for all 17 domain services |
| `api/` | Vercel Edge Functions (sebuf gateway + legacy endpoints) |
| `proto/` | Protobuf service and message definitions |
| `data/` | Static JSON datasets |
| `docs/` | Documentation + generated OpenAPI specs |
| `src-tauri/` | Tauri v2 Rust app + Node.js sidecar for desktop builds |
| `e2e/` | Playwright end-to-end tests |
| `scripts/` | Build and packaging scripts |
## Getting Started
1. **Fork** the repository on GitHub
2. **Clone** your fork locally:
```bash
git clone https://github.com/<your-username>/worldmonitor.git
cd worldmonitor
```
3. **Create a branch** for your work:
```bash
git checkout -b feature/your-feature-name
```
## Development Setup
```bash
# Install everything (buf CLI, sebuf plugins, npm deps, Playwright browsers)
make install
# Start the development server (full variant, default)
npm run dev
# Start other variants
npm run dev:tech
npm run dev:finance
# Run type checking
npm run typecheck
# Run tests
npm run test:data # Data integrity tests
npm run test:e2e # Playwright end-to-end tests
# Production build (per variant)
npm run build # full
npm run build:tech
npm run build:finance
```
The dev server runs at `http://localhost:3000`. Run `make help` to see all available make targets.
### Environment Variables (Optional)
For full functionality, copy `.env.example` to `.env.local` and fill in the API keys you need. The app runs without any API keys — external data sources will simply be unavailable.
See [API Dependencies](docs/DOCUMENTATION.md#api-dependencies) for the full list.
## How to Contribute
### Types of Contributions We Welcome
- **Bug fixes** — found something broken? Fix it!
- **New data layers** — add new geospatial data sources to the map
- **RSS feeds** — expand our 100+ feed collection with quality sources
- **UI/UX improvements** — make the dashboard more intuitive
- **Performance optimizations** — faster loading, better caching
- **Documentation** — improve docs, add examples, fix typos
- **Accessibility** — make the dashboard usable by everyone
- **Internationalization** — help make World Monitor available in more languages
- **Tests** — add unit or integration tests
### What We're Especially Looking For
- New data layers (see [Adding Data Sources](#adding-data-sources))
- Feed quality improvements and new RSS sources
- Mobile responsiveness improvements
- Performance optimizations for the map rendering pipeline
- Better anomaly detection algorithms
## Pull Request Process
1. **Update documentation** if your change affects the public API or user-facing behavior
2. **Run type checking** before submitting: `npm run typecheck`
3. **Test your changes** locally with at least the `full` variant, and any other variant your change affects
4. **Keep PRs focused** — one feature or fix per pull request
5. **Write a clear description** explaining what your PR does and why
6. **Link related issues** if applicable
### PR Title Convention
Use a descriptive title that summarizes the change:
- `feat: add earthquake magnitude filtering to map layer`
- `fix: resolve RSS feed timeout for Al Jazeera`
- `docs: update API dependencies section`
- `perf: optimize marker clustering at low zoom levels`
- `refactor: extract threat classifier into separate module`
### Review Process
- All PRs require review from a maintainer before merging
- Maintainers may request changes — this is normal and collaborative
- Once approved, a maintainer will merge your PR
## AI-Assisted Development
We fully embrace AI-assisted development. Many of our own PRs are labeled with the LLM that helped produce them (e.g., `claude`, `codex`, `cursor`), and contributors are welcome to use any AI tools they find helpful.
That said, **all code is held to the same quality bar regardless of how it was written**. AI-generated code will be reviewed with the same scrutiny as human-written code. Contributors are responsible for understanding and being able to explain every line they submit. Blindly pasting LLM output without review is discouraged — treat AI as a collaborator, not a replacement for your own judgement.
## Coding Standards
### TypeScript
- Use TypeScript for all new code
- Avoid `any` types — use proper typing or `unknown` with type guards
- Export interfaces/types for public APIs
- Use meaningful variable and function names
### Code Style
- Follow the existing code style in the repository
- Use `const` by default, `let` when reassignment is needed
- Prefer functional patterns (map, filter, reduce) over imperative loops
- Keep functions focused — one responsibility per function
- Add JSDoc comments for exported functions and complex logic
### File Organization
- Static layer/geo data and variant configs go in `src/config/`
- Sebuf handler implementations go in `server/worldmonitor/{domain}/v1/`
- Edge function gateway and legacy endpoints go in `api/`
- UI components (panels, map, modals) go in `src/components/`
- Service modules (data fetching, client wrappers) go in `src/services/`
- Proto definitions go in `proto/worldmonitor/{domain}/v1/`
## Working with Sebuf (RPC Framework)
Sebuf is the project's custom Proto-first HTTP RPC framework — a lightweight alternative to gRPC-Web. All API communication between client and server uses Sebuf.
### How It Works
1. **Proto definitions** in `proto/worldmonitor/{domain}/v1/` define services and messages
2. **Code generation** (`make generate`) produces:
- TypeScript clients in `src/generated/client/` (e.g., `MarketServiceClient`)
- Server route factories in `src/generated/server/` (e.g., `createMarketServiceRoutes`)
3. **Handlers** in `server/worldmonitor/{domain}/v1/handler.ts` implement the service interface
4. **Gateway** in `api/[domain]/v1/[rpc].ts` registers all handlers and routes requests
5. **Clients** in `src/services/{domain}/index.ts` wrap the generated client for app use
### Adding a New RPC Method
1. Add the method to the `.proto` service definition
2. Run `make generate` to regenerate client/server stubs
3. Implement the handler method in the domain's `handler.ts`
4. The client stub is auto-generated — use it from `src/services/{domain}/`
Use `make lint` to lint proto files and `make breaking` to check for breaking changes against main.
### Proto Conventions
- **Time fields**: Use `int64` (Unix epoch milliseconds), not `google.protobuf.Timestamp`
- **int64 encoding**: Apply `[(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER]` on time fields so TypeScript receives `number` instead of `string`
- **HTTP annotations**: Every RPC method needs `option (sebuf.http.config) = { path: "...", method: POST }`
### Proto Codegen Requirements
Run `make install` to install everything automatically, or install individually:
```bash
make install-buf # Install buf CLI (requires Go)
make install-plugins # Install sebuf protoc-gen plugins (requires Go)
```
## Adding Data Sources
To add a new data layer to the map:
1. **Define the data source** — identify the API or dataset you want to integrate
2. **Add the proto service** (if the data needs a backend proxy) — define messages and RPC methods in `proto/worldmonitor/{domain}/v1/`
3. **Generate stubs** — run `make generate`
4. **Implement the handler** in `server/worldmonitor/{domain}/v1/`
5. **Register the handler** in `api/[domain]/v1/[rpc].ts` and `vite.config.ts` (for local dev)
6. **Create the service module** in `src/services/{domain}/` wrapping the generated client
7. **Add the layer config** and implement the map renderer following existing layer patterns
8. **Add to layer toggles** — make it toggleable in the UI
9. **Document the source** — add it to `docs/DOCUMENTATION.md`
For endpoints that deal with non-JSON payloads (XML feeds, binary data, HTML embeds), you can add a standalone Edge Function in `api/` instead of Sebuf. For anything returning JSON, prefer Sebuf — the typed contracts are always worth it.
### Data Source Requirements
- Must be freely accessible (no paid-only APIs for core functionality)
- Must have a permissive license or be public government data
- Should update at least daily for real-time relevance
- Must include geographic coordinates or be geo-locatable
### Country boundary overrides
Country outlines are loaded from `public/data/countries.geojson`. Optional higher-resolution overrides (sourced from [Natural Earth](https://www.naturalearthdata.com/)) are served from R2 CDN. The app loads overrides after the main file and replaces geometry for any country whose `ISO3166-1-Alpha-2` (or `ISO_A2`) matches. To refresh boundary overrides from Natural Earth, run:
```bash
node scripts/fetch-country-boundary-overrides.mjs
rclone copy public/data/country-boundary-overrides.geojson r2:worldmonitor-maps/
```
## Adding RSS Feeds
To add new RSS feeds:
1. Verify the feed is reliable and actively maintained
2. Assign a **source tier** (1-4) based on editorial reliability
3. Flag any **state affiliation** or **propaganda risk**
4. Categorize the feed (geopolitics, defense, energy, tech, etc.)
5. Test that the feed parses correctly through the RSS proxy
## Reporting Bugs
When filing a bug report, please include:
- **Description** — clear description of the issue
- **Steps to reproduce** — how to trigger the bug
- **Expected behavior** — what should happen
- **Actual behavior** — what actually happens
- **Screenshots** — if applicable
- **Browser/OS** — your environment details
- **Console errors** — any relevant browser console output
Use the [Bug Report issue template](https://github.com/koala73/worldmonitor/issues/new/choose) when available.
## Feature Requests
We welcome feature ideas! When suggesting a feature:
- **Describe the problem** it solves
- **Propose a solution** with as much detail as possible
- **Consider alternatives** you've thought about
- **Provide context** — who would benefit from this feature?
Use the [Feature Request issue template](https://github.com/koala73/worldmonitor/issues/new/choose) when available.
## Code of Conduct
This project follows the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior through GitHub issues or by contacting the repository owner.
---
Thank you for helping make World Monitor better! 🌍
================================================
FILE: Dockerfile
================================================
# =============================================================================
# World Monitor — Docker Image
# =============================================================================
# Multi-stage build:
# builder — installs deps, compiles TS handlers, builds Vite frontend
# final — nginx (static) + node (API) under supervisord
# =============================================================================
# ── Stage 1: Builder ─────────────────────────────────────────────────────────
FROM node:22-alpine AS builder
WORKDIR /app
# Install root dependencies (layer-cached until package.json changes)
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
# Copy full source
COPY . .
# Compile TypeScript API handlers → self-contained ESM bundles
# Output is api/**/*.js alongside the source .ts files
RUN node docker/build-handlers.mjs
# Build Vite frontend (outputs to dist/)
# Skip blog build — blog-site has its own deps not installed here
RUN npx tsc && npx vite build
# ── Stage 2: Runtime ─────────────────────────────────────────────────────────
FROM node:22-alpine AS final
# nginx + supervisord
RUN apk add --no-cache nginx supervisor gettext && \
mkdir -p /tmp/nginx-client-body /tmp/nginx-proxy /tmp/nginx-fastcgi \
/tmp/nginx-uwsgi /tmp/nginx-scgi /var/log/supervisor && \
addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# API server
COPY --from=builder /app/src-tauri/sidecar/local-api-server.mjs ./local-api-server.mjs
COPY --from=builder /app/src-tauri/sidecar/package.json ./package.json
# API handler modules (JS originals + compiled TS bundles)
COPY --from=builder /app/api ./api
# Static data files used by handlers at runtime
COPY --from=builder /app/data ./data
# Built frontend static files
COPY --from=builder /app/dist /usr/share/nginx/html
# Nginx + supervisord configs
COPY docker/nginx.conf /etc/nginx/nginx.conf.template
COPY docker/supervisord.conf /etc/supervisor/conf.d/worldmonitor.conf
COPY docker/entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# Ensure writable dirs for non-root
RUN chown -R appuser:appgroup /app /tmp/nginx-client-body /tmp/nginx-proxy \
/tmp/nginx-fastcgi /tmp/nginx-uwsgi /tmp/nginx-scgi /var/log/supervisor \
/var/lib/nginx /var/log/nginx
USER appuser
EXPOSE 8080
# Healthcheck via nginx
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD wget -qO- http://localhost:8080/api/health || exit 1
CMD ["/app/entrypoint.sh"]
================================================
FILE: Dockerfile.relay
================================================
# =============================================================================
# AIS Relay Sidecar
# =============================================================================
# Runs scripts/ais-relay.cjs as a standalone container.
# Only dependency beyond Node stdlib is the 'ws' WebSocket library.
# Set AISSTREAM_API_KEY in docker-compose.yml.
# =============================================================================
FROM node:22-alpine
WORKDIR /app
# Install only the ws package (everything else is Node stdlib)
RUN npm install --omit=dev ws@8.19.0
# Relay script
COPY scripts/ais-relay.cjs ./scripts/ais-relay.cjs
# Shared helper required by the relay (rss-allowed-domains.cjs)
COPY shared/ ./shared/
EXPOSE 3004
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3004/health || exit 1
CMD ["node", "scripts/ais-relay.cjs"]
================================================
FILE: LICENSE
================================================
World Monitor — Real-time global intelligence dashboard
Copyright (C) 2024-2026 Elie Habib
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
================================================
FILE: Makefile
================================================
.PHONY: help lint generate breaking format check clean deps install install-buf install-plugins install-npm install-playwright
.DEFAULT_GOAL := help
# Variables
PROTO_DIR := proto
GEN_CLIENT_DIR := src/generated/client
GEN_SERVER_DIR := src/generated/server
DOCS_API_DIR := docs/api
# Go install settings
GO_PROXY := GOPROXY=direct
GO_PRIVATE := GOPRIVATE=github.com/SebastienMelki
GO_INSTALL := $(GO_PROXY) $(GO_PRIVATE) go install
# Required tool versions
BUF_VERSION := v1.64.0
SEBUF_VERSION := v0.7.0
help: ## Show this help message
@echo 'Usage: make [target]'
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
install: install-buf install-plugins install-npm install-playwright deps ## Install everything (buf, sebuf plugins, npm deps, proto deps, browsers)
install-buf: ## Install buf CLI
@if command -v buf >/dev/null 2>&1; then \
echo "buf already installed: $$(buf --version)"; \
else \
echo "Installing buf..."; \
$(GO_INSTALL) github.com/bufbuild/buf/cmd/buf@$(BUF_VERSION); \
echo "buf installed!"; \
fi
install-plugins: ## Install sebuf protoc plugins (requires Go)
@echo "Installing sebuf protoc plugins $(SEBUF_VERSION)..."
@$(GO_INSTALL) github.com/SebastienMelki/sebuf/cmd/protoc-gen-ts-client@$(SEBUF_VERSION)
@$(GO_INSTALL) github.com/SebastienMelki/sebuf/cmd/protoc-gen-ts-server@$(SEBUF_VERSION)
@$(GO_INSTALL) github.com/SebastienMelki/sebuf/cmd/protoc-gen-openapiv3@$(SEBUF_VERSION)
@echo "Plugins installed!"
install-npm: ## Install npm dependencies
npm install
install-playwright: ## Install Playwright browsers for e2e tests
npx playwright install chromium
deps: ## Install/update buf proto dependencies
cd $(PROTO_DIR) && buf dep update
lint: ## Lint protobuf files
cd $(PROTO_DIR) && buf lint
generate: clean ## Generate code from proto definitions
@mkdir -p $(GEN_CLIENT_DIR) $(GEN_SERVER_DIR) $(DOCS_API_DIR)
cd $(PROTO_DIR) && buf generate
@find $(GEN_CLIENT_DIR) $(GEN_SERVER_DIR) -name '*.ts' -exec sed -i.bak '1s;^;// @ts-nocheck\n;' {} \; -exec rm -f {}.bak \;
@echo "Code generation complete!"
breaking: ## Check for breaking changes against main
cd $(PROTO_DIR) && buf breaking --against '.git#branch=main,subdir=proto'
format: ## Format protobuf files
cd $(PROTO_DIR) && buf format -w
check: lint generate ## Run all checks (lint + generate)
clean: ## Clean generated files
@rm -rf $(GEN_CLIENT_DIR)
@rm -rf $(GEN_SERVER_DIR)
@rm -rf $(DOCS_API_DIR)
@echo "Clean complete!"
================================================
FILE: README.md
================================================
# World Monitor
**Real-time global intelligence dashboard** — AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface.
[](https://github.com/koala73/worldmonitor/stargazers)
[](https://github.com/koala73/worldmonitor/network/members)
[](https://discord.gg/re63kWKxaz)
[](https://www.gnu.org/licenses/agpl-3.0)
[](https://www.typescriptlang.org/)
[](https://github.com/koala73/worldmonitor/commits/main)
[](https://github.com/koala73/worldmonitor/releases/latest)
<p align="center">
<a href="https://worldmonitor.app"><img src="https://img.shields.io/badge/Web_App-worldmonitor.app-blue?style=for-the-badge&logo=googlechrome&logoColor=white" alt="Web App"></a>
<a href="https://tech.worldmonitor.app"><img src="https://img.shields.io/badge/Tech_Variant-tech.worldmonitor.app-0891b2?style=for-the-badge&logo=googlechrome&logoColor=white" alt="Tech Variant"></a>
<a href="https://finance.worldmonitor.app"><img src="https://img.shields.io/badge/Finance_Variant-finance.worldmonitor.app-059669?style=for-the-badge&logo=googlechrome&logoColor=white" alt="Finance Variant"></a>
<a href="https://commodity.worldmonitor.app"><img src="https://img.shields.io/badge/Commodity_Variant-commodity.worldmonitor.app-b45309?style=for-the-badge&logo=googlechrome&logoColor=white" alt="Commodity Variant"></a>
<a href="https://happy.worldmonitor.app"><img src="https://img.shields.io/badge/Happy_Variant-happy.worldmonitor.app-f59e0b?style=for-the-badge&logo=googlechrome&logoColor=white" alt="Happy Variant"></a>
</p>
<p align="center">
<a href="https://worldmonitor.app/api/download?platform=windows-exe"><img src="https://img.shields.io/badge/Download-Windows_(.exe)-0078D4?style=for-the-badge&logo=windows&logoColor=white" alt="Download Windows"></a>
<a href="https://worldmonitor.app/api/download?platform=macos-arm64"><img src="https://img.shields.io/badge/Download-macOS_Apple_Silicon-000000?style=for-the-badge&logo=apple&logoColor=white" alt="Download macOS ARM"></a>
<a href="https://worldmonitor.app/api/download?platform=macos-x64"><img src="https://img.shields.io/badge/Download-macOS_Intel-555555?style=for-the-badge&logo=apple&logoColor=white" alt="Download macOS Intel"></a>
<a href="https://worldmonitor.app/api/download?platform=linux-appimage"><img src="https://img.shields.io/badge/Download-Linux_(.AppImage)-FCC624?style=for-the-badge&logo=linux&logoColor=black" alt="Download Linux"></a>
</p>
<p align="center">
<a href="https://docs.worldmonitor.app"><strong>Documentation</strong></a> ·
<a href="https://github.com/koala73/worldmonitor/releases/latest"><strong>Releases</strong></a> ·
<a href="https://docs.worldmonitor.app/contributing"><strong>Contributing</strong></a>
</p>

---
## What It Does
- **435+ curated news feeds** across 15 categories, AI-synthesized into briefs
- **Dual map engine** — 3D globe (globe.gl) and WebGL flat map (deck.gl) with 45 data layers
- **Cross-stream correlation** — military, economic, disaster, and escalation signal convergence
- **Country Intelligence Index** — composite risk scoring across 12 signal categories
- **Finance radar** — 92 stock exchanges, commodities, crypto, and 7-signal market composite
- **Local AI** — run everything with Ollama, no API keys required
- **5 site variants** from a single codebase (world, tech, finance, commodity, happy)
- **Native desktop app** (Tauri 2) for macOS, Windows, and Linux
- **21 languages** with native-language feeds and RTL support
For the full feature list, architecture, data sources, and algorithms, see the **[documentation](https://docs.worldmonitor.app)**.
---
## Quick Start
```bash
git clone https://github.com/koala73/worldmonitor.git
cd worldmonitor
npm install
npm run dev
```
Open [localhost:5173](http://localhost:5173). No environment variables required for basic operation.
For variant-specific development:
```bash
npm run dev:tech # tech.worldmonitor.app
npm run dev:finance # finance.worldmonitor.app
npm run dev:commodity # commodity.worldmonitor.app
npm run dev:happy # happy.worldmonitor.app
```
See the **[self-hosting guide](https://docs.worldmonitor.app/getting-started)** for deployment options (Vercel, Docker, static).
---
## Tech Stack
| Category | Technologies |
|----------|-------------|
| **Frontend** | Vanilla TypeScript, Vite, globe.gl + Three.js, deck.gl + MapLibre GL |
| **Desktop** | Tauri 2 (Rust) with Node.js sidecar |
| **AI/ML** | Ollama / Groq / OpenRouter, Transformers.js (browser-side) |
| **API Contracts** | Protocol Buffers (92 protos, 22 services), sebuf HTTP annotations |
| **Deployment** | Vercel Edge Functions (60+), Railway relay, Tauri, PWA |
| **Caching** | Redis (Upstash), 3-tier cache, CDN, service worker |
Full stack details in the **[architecture docs](https://docs.worldmonitor.app/architecture)**.
---
## Contributing
Contributions welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
```bash
npm run typecheck # Type checking
npm run build:full # Production build
```
---
## License
**AGPL-3.0** for non-commercial use. **Commercial license** required for any commercial use.
| Use Case | Allowed? |
|----------|----------|
| Personal / research / educational | Yes |
| Self-hosted (non-commercial) | Yes, with attribution |
| Fork and modify (non-commercial) | Yes, share source under AGPL-3.0 |
| Commercial use / SaaS / rebranding | Requires commercial license |
See [LICENSE](LICENSE) for full terms. For commercial licensing, contact the maintainer.
Copyright (C) 2024-2026 Elie Habib. All rights reserved.
---
## Author
**Elie Habib** — [GitHub](https://github.com/koala73)
## Contributors
<a href="https://github.com/koala73/worldmonitor/graphs/contributors">
<img src="https://contrib.rocks/image?repo=koala73/worldmonitor" />
</a>
## Security Acknowledgments
We thank the following researchers for responsibly disclosing security issues:
- **Cody Richard** — Disclosed three security findings covering IPC command exposure, renderer-to-sidecar trust boundary analysis, and fetch patch credential injection architecture (2026)
See our [Security Policy](./SECURITY.md) for responsible disclosure guidelines.
---
<p align="center">
<a href="https://worldmonitor.app">worldmonitor.app</a> ·
<a href="https://docs.worldmonitor.app">docs.worldmonitor.app</a> ·
<a href="https://finance.worldmonitor.app">finance.worldmonitor.app</a> ·
<a href="https://commodity.worldmonitor.app">commodity.worldmonitor.app</a>
</p>
## Star History
<a href="https://api.star-history.com/svg?repos=koala73/worldmonitor&type=Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=koala73/worldmonitor&type=Date&type=Date&theme=dark" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=koala73/worldmonitor&type=Date&type=Date" />
</picture>
</a>
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| main | :white_check_mark: |
Only the latest version on the `main` branch is actively maintained and receives security updates.
## Reporting a Vulnerability
**Please do NOT report security vulnerabilities through public GitHub issues.**
If you discover a security vulnerability in World Monitor, please report it responsibly:
1. **GitHub Private Vulnerability Reporting**: Use [GitHub's private vulnerability reporting](https://github.com/koala73/worldmonitor/security/advisories/new) to submit your report directly through the repository.
2. **Direct Contact**: Alternatively, reach out to the repository owner [@koala73](https://github.com/koala73) directly through GitHub.
### What to Include
- A description of the vulnerability and its potential impact
- Steps to reproduce the issue
- Affected components (edge functions, client-side code, data layers, etc.)
- Any potential fixes or mitigations you've identified
### Response Timeline
- **Acknowledgment**: Within 48 hours of your report
- **Initial Assessment**: Within 1 week
- **Fix/Patch**: Depending on severity, critical issues will be prioritized
### What to Expect
- You will receive an acknowledgment of your report
- We will work with you to understand and validate the issue
- We will keep you informed of progress toward a fix
- Credit will be given to reporters in the fix commit (unless you prefer anonymity)
## Security Considerations
World Monitor is a client-side intelligence dashboard that aggregates publicly available data. Here are the key security areas:
### API Keys & Secrets
- **Web deployment**: API keys are stored server-side in Vercel Edge Functions
- **Desktop runtime**: API keys are stored in the OS keychain (macOS Keychain / Windows Credential Manager) via a consolidated vault entry, never on disk in plaintext
- No API keys should ever be committed to the repository
- Environment variables (`.env.local`) are gitignored
- The RSS proxy uses domain allowlisting to prevent SSRF
### Edge Functions & Sebuf Handlers
- All 17 domain APIs are served through Sebuf (a Proto-first RPC framework) via Vercel Edge Functions
- Edge functions and handlers should validate/sanitize all input
- CORS headers are configured per-function
- Rate limiting and circuit breakers protect against abuse
### Client-Side Security
- No sensitive data is stored in localStorage or sessionStorage
- External content (RSS feeds, news) is sanitized before rendering
- Map data layers use trusted, vetted data sources
- Content Security Policy restricts script-src to `'self'` (no unsafe-inline/eval)
### Desktop Runtime Security (Tauri)
- **IPC origin validation**: Sensitive Tauri commands (secrets, cache, token) are gated to trusted windows only; external-origin windows (e.g., YouTube login) are blocked
- **DevTools**: Disabled in production builds; gated behind an opt-in Cargo feature for development
- **Sidecar authentication**: A per-session CSPRNG token (`LOCAL_API_TOKEN`) authenticates all renderer-to-sidecar requests, preventing other local processes from accessing the API
- **Capability isolation**: The YouTube login window runs under a restricted capability with no access to secret or cache IPC commands
- **Fetch patch trust boundary**: The global fetch interceptor injects the sidecar token with a 5-minute TTL; the renderer is the intended client — if renderer integrity is compromised, Tauri IPC provides strictly more access than the fetch patch
### Data Sources
- World Monitor aggregates publicly available OSINT data
- No classified or restricted data sources are used
- State-affiliated sources are flagged with propaganda risk ratings
- All data is consumed read-only — the platform does not modify upstream sources
## Scope
The following are **in scope** for security reports:
- Vulnerabilities in the World Monitor codebase
- Edge function security issues (SSRF, injection, auth bypass)
- XSS or content injection through RSS feeds or external data
- API key exposure or secret leakage
- Tauri IPC command privilege escalation or capability bypass
- Sidecar authentication bypass or token leakage
- Dependency vulnerabilities with a viable attack vector
The following are **out of scope**:
- Vulnerabilities in third-party services we consume (report to the upstream provider)
- Social engineering attacks
- Denial of service attacks
- Issues in forked copies of the repository
- Security issues in user-provided environment configurations
## Best Practices for Contributors
- Never commit API keys, tokens, or secrets
- Use environment variables for all sensitive configuration
- Sanitize external input in edge functions
- Keep dependencies updated — run `npm audit` regularly
- Follow the principle of least privilege for API access
---
Thank you for helping keep World Monitor and its users safe! 🔒
================================================
FILE: SELF_HOSTING.md
================================================
# 🌍 Self-Hosting World Monitor
Run the full World Monitor stack locally with Docker/Podman.
## 📋 Prerequisites
- **Docker** or **Podman** (rootless works fine)
- **Docker Compose** or **podman-compose** (`pip install podman-compose` or `uvx podman-compose`)
- **Node.js 22+** (for running seed scripts on the host)
## 🚀 Quick Start
```bash
# 1. Clone and enter the repo
git clone https://github.com/koala73/worldmonitor.git
cd worldmonitor
npm install
# 2. Start the stack
docker compose up -d # or: uvx podman-compose up -d
# 3. Seed data into Redis
./scripts/run-seeders.sh
# 4. Open the dashboard
open http://localhost:3000
```
The dashboard works out of the box with public data sources (earthquakes, weather, conflicts, etc.). API keys unlock additional data feeds.
## 🔑 API Keys
Create a `docker-compose.override.yml` to inject your keys. This file is **gitignored** — your secrets stay local.
```yaml
services:
worldmonitor:
environment:
# 🤖 LLM — pick one or both (used for intelligence assessments)
GROQ_API_KEY: "" # https://console.groq.com (free, 14.4K req/day)
OPENROUTER_API_KEY: "" # https://openrouter.ai (free, 50 req/day)
# 📊 Markets & Economics
FINNHUB_API_KEY: "" # https://finnhub.io (free tier)
FRED_API_KEY: "" # https://fred.stlouisfed.org/docs/api/api_key.html (free)
EIA_API_KEY: "" # https://www.eia.gov/opendata/ (free)
# ⚔️ Conflict & Unrest
ACLED_ACCESS_TOKEN: "" # https://acleddata.com (free for researchers)
# 🛰️ Earth Observation
NASA_FIRMS_API_KEY: "" # https://firms.modaps.eosdis.nasa.gov (free)
# ✈️ Aviation
AVIATIONSTACK_API: "" # https://aviationstack.com (free tier)
# 🚢 Maritime
AISSTREAM_API_KEY: "" # https://aisstream.io (free)
# 🌐 Internet Outages (paid)
CLOUDFLARE_API_TOKEN: "" # https://dash.cloudflare.com (requires Radar access)
# 🔌 Self-hosted LLM (optional — any OpenAI-compatible endpoint)
LLM_API_URL: "" # e.g. http://localhost:11434/v1/chat/completions
LLM_API_KEY: ""
LLM_MODEL: ""
ais-relay:
environment:
AISSTREAM_API_KEY: "" # same key as above — relay needs it too
```
### 💰 Free vs Paid
| Status | Keys |
|--------|------|
| 🟢 No key needed | Earthquakes, weather, natural events, UNHCR displacement, prediction markets, stablecoins, crypto, spending, climate anomalies, submarine cables, BIS data, cyber threats |
| 🟢 Free signup | GROQ, FRED, EIA, NASA FIRMS, AISSTREAM, Finnhub, AviationStack, ACLED, OpenRouter |
| 🟡 Free (limited) | OpenSky (higher rate limits with account) |
| 🔴 Paid | Cloudflare Radar (internet outages) |
## 🌱 Seeding Data
The seed scripts fetch upstream data and write it to Redis. They run **on the host** (not inside the container) and need the Redis REST proxy to be running.
```bash
# Run all seeders (auto-sources API keys from docker-compose.override.yml)
./scripts/run-seeders.sh
```
**⚠️ Important:** Redis data persists across container restarts via the `redis-data` volume, but is lost on `docker compose down -v`. Re-run the seeders if you remove volumes or see stale data.
To automate, add a cron job:
```bash
# Re-seed every 30 minutes
*/30 * * * * cd /path/to/worldmonitor && ./scripts/run-seeders.sh >> /tmp/wm-seeders.log 2>&1
```
### 🔧 Manual seeder invocation
If you prefer to run seeders individually:
```bash
export UPSTASH_REDIS_REST_URL=http://localhost:8079
export UPSTASH_REDIS_REST_TOKEN=wm-local-token
node scripts/seed-earthquakes.mjs
node scripts/seed-military-flights.mjs
# ... etc
```
## 🏗️ Architecture
```
┌─────────────────────────────────────────────┐
│ localhost:3000 │
│ (nginx) │
├──────────────┬──────────────────────────────┤
│ Static Files │ /api/* proxy │
│ (Vite SPA) │ │
gitextract_xk6l94h3/ ├── .dockerignore ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── feature_request.yml │ │ └── new_data_source.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── build-desktop.yml │ ├── contributor-trust.yml │ ├── docker-publish.yml │ ├── lint-code.yml │ ├── lint.yml │ ├── proto-check.yml │ ├── test-linux-app.yml │ ├── test.yml │ └── typecheck.yml ├── .gitignore ├── .husky/ │ ├── pre-commit │ └── pre-push ├── .markdownlint-cli2.jsonc ├── .npmrc ├── .nvmrc ├── .vercelignore ├── AGENTS.md ├── ARCHITECTURE.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.relay ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── SELF_HOSTING.md ├── api/ │ ├── _api-key.js │ ├── _cors.js │ ├── _cors.test.mjs │ ├── _github-release.js │ ├── _ip-rate-limit.js │ ├── _json-response.js │ ├── _rate-limit.js │ ├── _relay.js │ ├── _rss-allowed-domains.js │ ├── _turnstile.js │ ├── _turnstile.test.mjs │ ├── _upstash-json.js │ ├── ais-snapshot.js │ ├── aviation/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── bootstrap.js │ ├── cache-purge.js │ ├── climate/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── conflict/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── contact.js │ ├── cyber/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── data/ │ │ └── city-coords.ts │ ├── displacement/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── download.js │ ├── economic/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── eia/ │ │ └── [[...path]].js │ ├── enrichment/ │ │ ├── _domain.js │ │ ├── company.js │ │ └── signals.js │ ├── forecast/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── fwdstart.js │ ├── geo.js │ ├── giving/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── gpsjam.js │ ├── health.js │ ├── imagery/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── infrastructure/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── intelligence/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── loaders-xml-wms-regression.test.mjs │ ├── maritime/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── market/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── mcp-proxy.js │ ├── military/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── military-flights.js │ ├── natural/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── news/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── og-story.js │ ├── og-story.test.mjs │ ├── opensky.js │ ├── oref-alerts.js │ ├── polymarket.js │ ├── positive-events/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── prediction/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── radiation/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── register-interest.js │ ├── research/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── reverse-geocode.js │ ├── rss-proxy.js │ ├── sanctions/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── satellites.js │ ├── seed-health.js │ ├── seismology/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── story.js │ ├── supply-chain/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── telegram-feed.js │ ├── thermal/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── trade/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── unrest/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── version.js │ ├── webcam/ │ │ └── v1/ │ │ └── [rpc].ts │ ├── wildfire/ │ │ └── v1/ │ │ └── [rpc].ts │ └── youtube/ │ ├── embed.js │ ├── embed.test.mjs │ └── live.js ├── biome.json ├── blog-site/ │ ├── .gitignore │ ├── .vscode/ │ │ ├── extensions.json │ │ └── launch.json │ ├── README.md │ ├── astro.config.mjs │ ├── package.json │ ├── public/ │ │ └── robots.txt │ ├── scripts/ │ │ └── generate-og-images.mjs │ ├── src/ │ │ ├── content/ │ │ │ └── blog/ │ │ │ ├── ai-powered-intelligence-without-the-cloud.md │ │ │ ├── build-on-worldmonitor-developer-api-open-source.md │ │ │ ├── command-palette-search-everything-instantly.md │ │ │ ├── cyber-threat-intelligence-for-security-teams.md │ │ │ ├── five-dashboards-one-platform-worldmonitor-variants.md │ │ │ ├── live-webcams-from-geopolitical-hotspots.md │ │ │ ├── monitor-global-supply-chains-and-commodity-disruptions.md │ │ │ ├── natural-disaster-monitoring-earthquakes-fires-volcanoes.md │ │ │ ├── osint-for-everyone-open-source-intelligence-democratized.md │ │ │ ├── prediction-markets-ai-forecasting-geopolitics.md │ │ │ ├── real-time-market-intelligence-for-traders-and-analysts.md │ │ │ ├── satellite-imagery-orbital-surveillance.md │ │ │ ├── track-global-conflicts-in-real-time.md │ │ │ ├── tracking-global-trade-routes-chokepoints-freight-costs.md │ │ │ ├── what-is-worldmonitor-real-time-global-intelligence.md │ │ │ ├── worldmonitor-in-21-languages-global-intelligence-for-everyone.md │ │ │ └── worldmonitor-vs-traditional-intelligence-tools.md │ │ ├── content.config.ts │ │ ├── layouts/ │ │ │ ├── Base.astro │ │ │ └── BlogPost.astro │ │ ├── pages/ │ │ │ ├── index.astro │ │ │ ├── posts/ │ │ │ │ └── [...id].astro │ │ │ └── rss.xml.ts │ │ └── styles/ │ │ └── global.css │ └── tsconfig.json ├── convex/ │ ├── _generated/ │ │ ├── api.d.ts │ │ ├── api.js │ │ ├── dataModel.d.ts │ │ ├── registerInterest.js │ │ ├── schema.js │ │ ├── server.d.ts │ │ └── server.js │ ├── contactMessages.ts │ ├── registerInterest.ts │ ├── schema.ts │ └── tsconfig.json ├── data/ │ ├── gamma-irradiators-raw.json │ ├── gamma-irradiators.json │ └── telegram-channels.json ├── deploy/ │ └── nginx/ │ └── brotli-api-proxy.conf ├── docker/ │ ├── .dockerignore │ ├── Dockerfile │ ├── Dockerfile.redis-rest │ ├── build-handlers.mjs │ ├── docker-entrypoint.sh │ ├── entrypoint.sh │ ├── nginx-security-headers.conf │ ├── nginx.conf │ ├── nginx.conf.template │ ├── redis-rest-proxy.mjs │ └── supervisord.conf ├── docker-compose.yml ├── docs/ │ ├── .mintignore │ ├── .mintlifyignore │ ├── COMMUNITY-PROMOTION-GUIDE.md │ ├── Docs_To_Review/ │ │ ├── API_REFERENCE.md │ │ ├── ARCHITECTURE.md │ │ ├── COMPONENTS.md │ │ ├── DATA_MODEL.md │ │ ├── DESKTOP_CONFIGURATION.md │ │ ├── DOCUMENTATION.md │ │ ├── EXTERNAL_APIS.md │ │ ├── NEWS_TRANSLATION_ANALYSIS.md │ │ ├── PANELS.md │ │ ├── RELEASE_PACKAGING.md │ │ ├── STATE_MANAGEMENT.md │ │ ├── TAURI_VALIDATION_REPORT.md │ │ ├── TODO_Performance.md │ │ ├── bugs.md │ │ ├── local-backend-audit.md │ │ ├── todo.md │ │ └── todo_docs.md │ ├── PRESS_KIT.md │ ├── TAURI_VALIDATION_REPORT.md │ ├── adding-endpoints.mdx │ ├── ai-intelligence.mdx │ ├── algorithms.mdx │ ├── api/ │ │ ├── AviationService.openapi.json │ │ ├── AviationService.openapi.yaml │ │ ├── ClimateService.openapi.json │ │ ├── ClimateService.openapi.yaml │ │ ├── ConflictService.openapi.json │ │ ├── ConflictService.openapi.yaml │ │ ├── CyberService.openapi.json │ │ ├── CyberService.openapi.yaml │ │ ├── DisplacementService.openapi.json │ │ ├── DisplacementService.openapi.yaml │ │ ├── EconomicService.openapi.json │ │ ├── EconomicService.openapi.yaml │ │ ├── ForecastService.openapi.json │ │ ├── ForecastService.openapi.yaml │ │ ├── GivingService.openapi.json │ │ ├── GivingService.openapi.yaml │ │ ├── ImageryService.openapi.json │ │ ├── ImageryService.openapi.yaml │ │ ├── InfrastructureService.openapi.json │ │ ├── InfrastructureService.openapi.yaml │ │ ├── IntelligenceService.openapi.json │ │ ├── IntelligenceService.openapi.yaml │ │ ├── MaritimeService.openapi.json │ │ ├── MaritimeService.openapi.yaml │ │ ├── MarketService.openapi.json │ │ ├── MarketService.openapi.yaml │ │ ├── MilitaryService.openapi.json │ │ ├── MilitaryService.openapi.yaml │ │ ├── NaturalService.openapi.json │ │ ├── NaturalService.openapi.yaml │ │ ├── NewsService.openapi.json │ │ ├── NewsService.openapi.yaml │ │ ├── PositiveEventsService.openapi.json │ │ ├── PositiveEventsService.openapi.yaml │ │ ├── PredictionService.openapi.json │ │ ├── PredictionService.openapi.yaml │ │ ├── RadiationService.openapi.json │ │ ├── RadiationService.openapi.yaml │ │ ├── ResearchService.openapi.json │ │ ├── ResearchService.openapi.yaml │ │ ├── SanctionsService.openapi.json │ │ ├── SanctionsService.openapi.yaml │ │ ├── SeismologyService.openapi.json │ │ ├── SeismologyService.openapi.yaml │ │ ├── SupplyChainService.openapi.json │ │ ├── SupplyChainService.openapi.yaml │ │ ├── ThermalService.openapi.json │ │ ├── ThermalService.openapi.yaml │ │ ├── TradeService.openapi.json │ │ ├── TradeService.openapi.yaml │ │ ├── UnrestService.openapi.json │ │ ├── UnrestService.openapi.yaml │ │ ├── WebcamService.openapi.json │ │ ├── WebcamService.openapi.yaml │ │ ├── WildfireService.openapi.json │ │ └── WildfireService.openapi.yaml │ ├── api-key-deployment.mdx │ ├── architecture.mdx │ ├── changelog.mdx │ ├── contributing.mdx │ ├── cors.mdx │ ├── country-instability-index.mdx │ ├── data-sources.mdx │ ├── desktop-app.mdx │ ├── docs.json │ ├── documentation.mdx │ ├── features.mdx │ ├── finance-data.mdx │ ├── geographic-convergence.mdx │ ├── getting-started.mdx │ ├── harness-engineering-roadmap.md │ ├── health-endpoints.mdx │ ├── hotspots.mdx │ ├── infrastructure-cascade.mdx │ ├── license.mdx │ ├── local-backend-audit.md │ ├── map-engine.mdx │ ├── maps-and-geocoding.mdx │ ├── maritime-intelligence.mdx │ ├── military-tracking.mdx │ ├── natural-disasters.mdx │ ├── orbital-surveillance.mdx │ ├── overview.mdx │ ├── premium-finance-search.mdx │ ├── premium-finance.mdx │ ├── relay-parameters.mdx │ ├── release-packaging.mdx │ ├── roadmap-pro.md │ ├── signal-intelligence.mdx │ ├── strategic-risk.mdx │ ├── user-requests.md │ └── webcam-layer.mdx ├── e2e/ │ ├── circuit-breaker-persistence.spec.ts │ ├── deduct-situation.spec.ts │ ├── investments-panel.spec.ts │ ├── keyword-spike-flow.spec.ts │ ├── map-harness.spec.ts │ ├── mobile-map-native.spec.ts │ ├── mobile-map-popup.spec.ts │ ├── rag-vector-store.spec.ts │ ├── runtime-fetch.spec.ts │ ├── theme-toggle.spec.ts │ ├── tsconfig.json │ └── widget-builder.spec.ts ├── index.html ├── live-channels.html ├── middleware.ts ├── nixpacks.toml ├── package.json ├── playwright.config.ts ├── pro-test/ │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── metadata.json │ ├── package.json │ ├── prerender.mjs │ ├── src/ │ │ ├── App.tsx │ │ ├── i18n.ts │ │ ├── index.css │ │ ├── locales/ │ │ │ ├── ar.json │ │ │ ├── bg.json │ │ │ ├── cs.json │ │ │ ├── de.json │ │ │ ├── el.json │ │ │ ├── en.json │ │ │ ├── es.json │ │ │ ├── fr.json │ │ │ ├── it.json │ │ │ ├── ja.json │ │ │ ├── ko.json │ │ │ ├── nl.json │ │ │ ├── pl.json │ │ │ ├── pt.json │ │ │ ├── ro.json │ │ │ ├── ru.json │ │ │ ├── sv.json │ │ │ ├── th.json │ │ │ ├── tr.json │ │ │ ├── vi.json │ │ │ └── zh.json │ │ └── main.tsx │ ├── tsconfig.json │ └── vite.config.ts ├── proto/ │ ├── buf.gen.yaml │ ├── buf.yaml │ ├── sebuf/ │ │ └── http/ │ │ └── annotations.proto │ └── worldmonitor/ │ ├── aviation/ │ │ └── v1/ │ │ ├── airport_delay.proto │ │ ├── aviation_news_item.proto │ │ ├── carrier.proto │ │ ├── flight_instance.proto │ │ ├── get_airport_ops_summary.proto │ │ ├── get_carrier_ops.proto │ │ ├── get_flight_status.proto │ │ ├── list_airport_delays.proto │ │ ├── list_airport_flights.proto │ │ ├── list_aviation_news.proto │ │ ├── position_sample.proto │ │ ├── price_quote.proto │ │ ├── search_flight_prices.proto │ │ ├── service.proto │ │ └── track_aircraft.proto │ ├── climate/ │ │ └── v1/ │ │ ├── climate_anomaly.proto │ │ ├── list_climate_anomalies.proto │ │ └── service.proto │ ├── conflict/ │ │ └── v1/ │ │ ├── acled_event.proto │ │ ├── get_humanitarian_summary.proto │ │ ├── get_humanitarian_summary_batch.proto │ │ ├── humanitarian_summary.proto │ │ ├── list_acled_events.proto │ │ ├── list_iran_events.proto │ │ ├── list_ucdp_events.proto │ │ ├── service.proto │ │ └── ucdp_event.proto │ ├── core/ │ │ └── v1/ │ │ ├── country.proto │ │ ├── general_error.proto │ │ ├── geo.proto │ │ ├── i18n.proto │ │ ├── identifiers.proto │ │ ├── pagination.proto │ │ ├── severity.proto │ │ └── time.proto │ ├── cyber/ │ │ └── v1/ │ │ ├── cyber_threat.proto │ │ ├── list_cyber_threats.proto │ │ └── service.proto │ ├── displacement/ │ │ └── v1/ │ │ ├── displacement.proto │ │ ├── get_displacement_summary.proto │ │ ├── get_population_exposure.proto │ │ └── service.proto │ ├── economic/ │ │ └── v1/ │ │ ├── bis_data.proto │ │ ├── economic_data.proto │ │ ├── get_bis_credit.proto │ │ ├── get_bis_exchange_rates.proto │ │ ├── get_bis_policy_rates.proto │ │ ├── get_energy_capacity.proto │ │ ├── get_energy_prices.proto │ │ ├── get_fred_series.proto │ │ ├── get_fred_series_batch.proto │ │ ├── get_macro_signals.proto │ │ ├── list_world_bank_indicators.proto │ │ └── service.proto │ ├── forecast/ │ │ └── v1/ │ │ ├── forecast.proto │ │ ├── get_forecasts.proto │ │ └── service.proto │ ├── giving/ │ │ └── v1/ │ │ ├── get_giving_summary.proto │ │ ├── giving.proto │ │ └── service.proto │ ├── imagery/ │ │ └── v1/ │ │ ├── search_imagery.proto │ │ └── service.proto │ ├── infrastructure/ │ │ └── v1/ │ │ ├── get_cable_health.proto │ │ ├── get_temporal_baseline.proto │ │ ├── infrastructure.proto │ │ ├── list_internet_outages.proto │ │ ├── list_service_statuses.proto │ │ ├── list_temporal_anomalies.proto │ │ ├── record_baseline_snapshot.proto │ │ └── service.proto │ ├── intelligence/ │ │ └── v1/ │ │ ├── classify_event.proto │ │ ├── deduct_situation.proto │ │ ├── get_country_facts.proto │ │ ├── get_country_intel_brief.proto │ │ ├── get_pizzint_status.proto │ │ ├── get_risk_scores.proto │ │ ├── intelligence.proto │ │ ├── list_security_advisories.proto │ │ ├── search_gdelt_documents.proto │ │ └── service.proto │ ├── maritime/ │ │ └── v1/ │ │ ├── get_vessel_snapshot.proto │ │ ├── list_navigational_warnings.proto │ │ ├── service.proto │ │ └── vessel_snapshot.proto │ ├── market/ │ │ └── v1/ │ │ ├── analyze_stock.proto │ │ ├── backtest_stock.proto │ │ ├── get_country_stock_index.proto │ │ ├── get_sector_summary.proto │ │ ├── get_stock_analysis_history.proto │ │ ├── list_commodity_quotes.proto │ │ ├── list_crypto_quotes.proto │ │ ├── list_etf_flows.proto │ │ ├── list_gulf_quotes.proto │ │ ├── list_market_quotes.proto │ │ ├── list_stablecoin_markets.proto │ │ ├── list_stored_stock_backtests.proto │ │ ├── market_quote.proto │ │ └── service.proto │ ├── military/ │ │ └── v1/ │ │ ├── get_aircraft_details.proto │ │ ├── get_aircraft_details_batch.proto │ │ ├── get_theater_posture.proto │ │ ├── get_usni_fleet_report.proto │ │ ├── get_wingbits_live_flight.proto │ │ ├── get_wingbits_status.proto │ │ ├── list_military_bases.proto │ │ ├── list_military_flights.proto │ │ ├── military_flight.proto │ │ ├── military_vessel.proto │ │ ├── service.proto │ │ └── usni_fleet.proto │ ├── natural/ │ │ └── v1/ │ │ ├── list_natural_events.proto │ │ └── service.proto │ ├── news/ │ │ └── v1/ │ │ ├── get_summarize_article_cache.proto │ │ ├── list_feed_digest.proto │ │ ├── news_item.proto │ │ ├── service.proto │ │ └── summarize_article.proto │ ├── positive_events/ │ │ └── v1/ │ │ ├── list_positive_geo_events.proto │ │ └── service.proto │ ├── prediction/ │ │ └── v1/ │ │ ├── list_prediction_markets.proto │ │ ├── prediction_market.proto │ │ └── service.proto │ ├── radiation/ │ │ └── v1/ │ │ ├── list_radiation_observations.proto │ │ ├── radiation_observation.proto │ │ └── service.proto │ ├── research/ │ │ └── v1/ │ │ ├── list_arxiv_papers.proto │ │ ├── list_hackernews_items.proto │ │ ├── list_tech_events.proto │ │ ├── list_trending_repos.proto │ │ ├── research_item.proto │ │ └── service.proto │ ├── sanctions/ │ │ └── v1/ │ │ ├── country_sanctions_pressure.proto │ │ ├── list_sanctions_pressure.proto │ │ ├── program_sanctions_pressure.proto │ │ ├── sanctions_entry.proto │ │ └── service.proto │ ├── seismology/ │ │ └── v1/ │ │ ├── earthquake.proto │ │ ├── list_earthquakes.proto │ │ └── service.proto │ ├── supply_chain/ │ │ └── v1/ │ │ ├── get_chokepoint_status.proto │ │ ├── get_critical_minerals.proto │ │ ├── get_shipping_rates.proto │ │ ├── service.proto │ │ └── supply_chain_data.proto │ ├── thermal/ │ │ └── v1/ │ │ ├── list_thermal_escalations.proto │ │ ├── service.proto │ │ └── thermal_escalation_cluster.proto │ ├── trade/ │ │ └── v1/ │ │ ├── get_customs_revenue.proto │ │ ├── get_tariff_trends.proto │ │ ├── get_trade_barriers.proto │ │ ├── get_trade_flows.proto │ │ ├── get_trade_restrictions.proto │ │ ├── service.proto │ │ └── trade_data.proto │ ├── unrest/ │ │ └── v1/ │ │ ├── list_unrest_events.proto │ │ ├── service.proto │ │ └── unrest_event.proto │ ├── webcam/ │ │ └── v1/ │ │ ├── get_webcam_image.proto │ │ ├── list_webcams.proto │ │ └── service.proto │ └── wildfire/ │ └── v1/ │ ├── fire_detection.proto │ ├── list_fire_detections.proto │ └── service.proto ├── public/ │ ├── .well-known/ │ │ └── security.txt │ ├── a7f3e9d1b2c44e8f9a0b1c2d3e4f5a6b.txt │ ├── data/ │ │ ├── countries.geojson │ │ └── country-boundary-overrides.geojson │ ├── llms-full.txt │ ├── llms.txt │ ├── map-styles/ │ │ ├── happy-dark.json │ │ └── happy-light.json │ ├── offline.html │ ├── pro/ │ │ ├── assets/ │ │ │ ├── ar-BHa0nEOe.js │ │ │ ├── bg-Ci69To5a.js │ │ │ ├── cs-CqKhwIlR.js │ │ │ ├── de-B71p-f-t.js │ │ │ ├── el-DJwjBufy.js │ │ │ ├── es-aR_qLKIk.js │ │ │ ├── fr-BrtwTv_R.js │ │ │ ├── index-DQXUpmjr.css │ │ │ ├── index-k66dEz6-.js │ │ │ ├── it-DHbGtQXZ.js │ │ │ ├── ja-D8-35S3Y.js │ │ │ ├── ko-otMG-p7A.js │ │ │ ├── nl-B3DRC8p4.js │ │ │ ├── pl-DqoCbf3Z.js │ │ │ ├── pt-CqDblfWm.js │ │ │ ├── ro-DaIMP80d.js │ │ │ ├── ru-DN0TfVz-.js │ │ │ ├── sv-B8YGwHj7.js │ │ │ ├── th-Dx5iTAoX.js │ │ │ ├── tr-DqKzKEKV.js │ │ │ ├── vi-ByRwBJoF.js │ │ │ └── zh-Cf0ddDO-.js │ │ └── index.html │ ├── robots.txt │ └── sitemap.xml ├── scripts/ │ ├── _clustering.mjs │ ├── _military-surges.mjs │ ├── _prediction-scoring.mjs │ ├── _r2-storage.mjs │ ├── _seed-utils.mjs │ ├── _trade-parse-utils.mjs │ ├── ais-relay-rss.test.cjs │ ├── ais-relay.cjs │ ├── build-military-bases-final.mjs │ ├── build-sidecar-handlers.mjs │ ├── build-sidecar-sebuf.mjs │ ├── check-unicode-safety.mjs │ ├── data/ │ │ ├── cascade-rules.json │ │ ├── country-codes.json │ │ ├── curated-bases.json │ │ ├── entity-graph.json │ │ ├── forecast-evaluation-benchmark.json │ │ ├── forecast-historical-benchmark.json │ │ ├── mirta-processed.json │ │ └── prediction-tags.json │ ├── desktop-package.mjs │ ├── download-node.sh │ ├── evaluate-forecast-benchmark.mjs │ ├── extract-forecast-benchmark-candidates.mjs │ ├── fetch-country-boundary-overrides.mjs │ ├── fetch-gpsjam.mjs │ ├── fetch-mirta-bases.mjs │ ├── fetch-osm-bases.mjs │ ├── fetch-pizzint-bases.mjs │ ├── generate-oref-locations.mjs │ ├── lib/ │ │ └── thermal-escalation.mjs │ ├── lint-boundaries.mjs │ ├── need-work.csv │ ├── nixpacks.toml │ ├── package.json │ ├── promote-forecast-benchmark-candidate.mjs │ ├── railway-set-watch-paths.mjs │ ├── rss-feeds-report.csv │ ├── run-seeders.sh │ ├── seed-airport-delays.mjs │ ├── seed-aviation.mjs │ ├── seed-bis-data.mjs │ ├── seed-climate-anomalies.mjs │ ├── seed-commodity-quotes.mjs │ ├── seed-conflict-intel.mjs │ ├── seed-correlation.mjs │ ├── seed-crypto-quotes.mjs │ ├── seed-cyber-threats.mjs │ ├── seed-displacement-summary.mjs │ ├── seed-earthquakes.mjs │ ├── seed-economy.mjs │ ├── seed-etf-flows.mjs │ ├── seed-fire-detections.mjs │ ├── seed-forecasts.mjs │ ├── seed-gdelt-intel.mjs │ ├── seed-gulf-quotes.mjs │ ├── seed-infra.mjs │ ├── seed-insights.mjs │ ├── seed-internet-outages.mjs │ ├── seed-iran-events.mjs │ ├── seed-market-quotes.mjs │ ├── seed-military-bases.mjs │ ├── seed-military-flights.mjs │ ├── seed-military-maritime-news.mjs │ ├── seed-natural-events.mjs │ ├── seed-prediction-markets.mjs │ ├── seed-radiation-watch.mjs │ ├── seed-research.mjs │ ├── seed-sanctions-pressure.mjs │ ├── seed-security-advisories.mjs │ ├── seed-service-statuses.mjs │ ├── seed-stablecoin-markets.mjs │ ├── seed-submarine-cables.mjs │ ├── seed-supply-chain-trade.mjs │ ├── seed-thermal-escalation.mjs │ ├── seed-ucdp-events.mjs │ ├── seed-unrest-events.mjs │ ├── seed-usa-spending.mjs │ ├── seed-wb-indicators.mjs │ ├── seed-weather-alerts.mjs │ ├── seed-webcams.mjs │ ├── seo-indexnow-submit.mjs │ ├── shared/ │ │ ├── acled-oauth.mjs │ │ ├── commodities.json │ │ ├── country-names.json │ │ ├── crypto.json │ │ ├── etfs.json │ │ ├── gulf.json │ │ ├── rss-allowed-domains.cjs │ │ ├── rss-allowed-domains.json │ │ ├── sectors.json │ │ ├── stablecoins.json │ │ └── stocks.json │ ├── sync-desktop-version.mjs │ ├── telegram/ │ │ └── session-auth.mjs │ ├── validate-rss-feeds.mjs │ ├── validate-seed-migration.mjs │ └── vercel-ignore.sh ├── server/ │ ├── _shared/ │ │ ├── acled-auth.ts │ │ ├── acled.ts │ │ ├── cache-keys.ts │ │ ├── constants.ts │ │ ├── hash.ts │ │ ├── llm-health.ts │ │ ├── llm-sanitize.d.ts │ │ ├── llm-sanitize.js │ │ ├── llm.ts │ │ ├── normalize-list.ts │ │ ├── parse-string-array.ts │ │ ├── rate-limit.ts │ │ ├── redis.ts │ │ ├── response-headers.ts │ │ └── sidecar-cache.ts │ ├── cors.ts │ ├── env.d.ts │ ├── error-mapper.ts │ ├── gateway.ts │ ├── router.ts │ └── worldmonitor/ │ ├── _bootstrap-cache-key-refs.ts │ ├── aviation/ │ │ └── v1/ │ │ ├── _providers/ │ │ │ ├── demo_prices.ts │ │ │ └── travelpayouts_data.ts │ │ ├── _shared.ts │ │ ├── get-airport-ops-summary.ts │ │ ├── get-carrier-ops.ts │ │ ├── get-flight-status.ts │ │ ├── handler.ts │ │ ├── list-airport-delays.ts │ │ ├── list-airport-flights.ts │ │ ├── list-aviation-news.ts │ │ ├── search-flight-prices.ts │ │ └── track-aircraft.ts │ ├── climate/ │ │ └── v1/ │ │ ├── handler.ts │ │ └── list-climate-anomalies.ts │ ├── conflict/ │ │ └── v1/ │ │ ├── _shared.ts │ │ ├── get-humanitarian-summary-batch.ts │ │ ├── get-humanitarian-summary.ts │ │ ├── handler.ts │ │ ├── list-acled-events.ts │ │ ├── list-iran-events.ts │ │ └── list-ucdp-events.ts │ ├── cyber/ │ │ └── v1/ │ │ ├── _shared.ts │ │ ├── handler.ts │ │ └── list-cyber-threats.ts │ ├── displacement/ │ │ └── v1/ │ │ ├── get-displacement-summary.ts │ │ ├── get-population-exposure.ts │ │ └── handler.ts │ ├── economic/ │ │ └── v1/ │ │ ├── _bis-shared.ts │ │ ├── _fetch-with-timeout.ts │ │ ├── _fred-shared.ts │ │ ├── _shared.ts │ │ ├── get-bis-credit.ts │ │ ├── get-bis-exchange-rates.ts │ │ ├── get-bis-policy-rates.ts │ │ ├── get-energy-capacity.ts │ │ ├── get-energy-prices.ts │ │ ├── get-fred-series-batch.ts │ │ ├── get-fred-series.ts │ │ ├── get-macro-signals.ts │ │ ├── handler.ts │ │ └── list-world-bank-indicators.ts │ ├── forecast/ │ │ └── v1/ │ │ ├── get-forecasts.ts │ │ └── handler.ts │ ├── giving/ │ │ └── v1/ │ │ ├── get-giving-summary.ts │ │ └── handler.ts │ ├── imagery/ │ │ └── v1/ │ │ ├── handler.ts │ │ └── search-imagery.ts │ ├── infrastructure/ │ │ └── v1/ │ │ ├── _shared.ts │ │ ├── get-cable-health.ts │ │ ├── get-temporal-baseline.ts │ │ ├── handler.ts │ │ ├── list-internet-outages.ts │ │ ├── list-service-statuses.ts │ │ ├── list-temporal-anomalies.ts │ │ └── record-baseline-snapshot.ts │ ├── intelligence/ │ │ └── v1/ │ │ ├── _batch-classify.ts │ │ ├── _shared.ts │ │ ├── classify-event.ts │ │ ├── deduct-situation.ts │ │ ├── deduction-prompt.ts │ │ ├── get-country-facts.ts │ │ ├── get-country-intel-brief.ts │ │ ├── get-pizzint-status.ts │ │ ├── get-risk-scores.ts │ │ ├── handler.ts │ │ ├── list-security-advisories.ts │ │ └── search-gdelt-documents.ts │ ├── maritime/ │ │ └── v1/ │ │ ├── get-vessel-snapshot.ts │ │ ├── handler.ts │ │ └── list-navigational-warnings.ts │ ├── market/ │ │ └── v1/ │ │ ├── _shared.ts │ │ ├── analyze-stock.ts │ │ ├── backtest-stock.ts │ │ ├── get-country-stock-index.ts │ │ ├── get-sector-summary.ts │ │ ├── get-stock-analysis-history.ts │ │ ├── handler.ts │ │ ├── list-commodity-quotes.ts │ │ ├── list-crypto-quotes.ts │ │ ├── list-etf-flows.ts │ │ ├── list-gulf-quotes.ts │ │ ├── list-market-quotes.ts │ │ ├── list-stablecoin-markets.ts │ │ ├── list-stored-stock-backtests.ts │ │ ├── premium-stock-store.ts │ │ └── stock-news-search.ts │ ├── military/ │ │ └── v1/ │ │ ├── _shared.ts │ │ ├── _wingbits-aircraft-details.ts │ │ ├── get-aircraft-details-batch.ts │ │ ├── get-aircraft-details.ts │ │ ├── get-theater-posture.ts │ │ ├── get-usni-fleet-report.ts │ │ ├── get-wingbits-live-flight.ts │ │ ├── get-wingbits-status.ts │ │ ├── handler.ts │ │ ├── list-military-bases.ts │ │ └── list-military-flights.ts │ ├── natural/ │ │ └── v1/ │ │ ├── handler.ts │ │ └── list-natural-events.ts │ ├── news/ │ │ └── v1/ │ │ ├── _classifier.ts │ │ ├── _feeds.ts │ │ ├── _shared.ts │ │ ├── dedup.mjs │ │ ├── get-summarize-article-cache.ts │ │ ├── handler.ts │ │ ├── list-feed-digest.ts │ │ └── summarize-article.ts │ ├── positive-events/ │ │ └── v1/ │ │ ├── handler.ts │ │ └── list-positive-geo-events.ts │ ├── prediction/ │ │ └── v1/ │ │ ├── handler.ts │ │ └── list-prediction-markets.ts │ ├── radiation/ │ │ └── v1/ │ │ ├── handler.ts │ │ └── list-radiation-observations.ts │ ├── research/ │ │ └── v1/ │ │ ├── handler.ts │ │ ├── list-arxiv-papers.ts │ │ ├── list-hackernews-items.ts │ │ ├── list-tech-events.ts │ │ └── list-trending-repos.ts │ ├── sanctions/ │ │ └── v1/ │ │ ├── handler.ts │ │ └── list-sanctions-pressure.ts │ ├── seismology/ │ │ └── v1/ │ │ ├── handler.ts │ │ └── list-earthquakes.ts │ ├── supply-chain/ │ │ └── v1/ │ │ ├── _chokepoint-ids.ts │ │ ├── _corridorrisk-upstream.ts │ │ ├── _minerals-data.ts │ │ ├── _portwatch-upstream.ts │ │ ├── _scoring.mjs │ │ ├── get-chokepoint-status.ts │ │ ├── get-critical-minerals.ts │ │ ├── get-shipping-rates.ts │ │ └── handler.ts │ ├── thermal/ │ │ └── v1/ │ │ ├── handler.ts │ │ └── list-thermal-escalations.ts │ ├── trade/ │ │ └── v1/ │ │ ├── _shared.ts │ │ ├── get-customs-revenue.ts │ │ ├── get-tariff-trends.ts │ │ ├── get-trade-barriers.ts │ │ ├── get-trade-flows.ts │ │ ├── get-trade-restrictions.ts │ │ └── handler.ts │ ├── unrest/ │ │ └── v1/ │ │ ├── _shared.ts │ │ ├── handler.ts │ │ └── list-unrest-events.ts │ ├── webcam/ │ │ └── v1/ │ │ ├── get-webcam-image.ts │ │ ├── handler.ts │ │ └── list-webcams.ts │ └── wildfire/ │ └── v1/ │ ├── handler.ts │ └── list-fire-detections.ts ├── settings.html ├── shared/ │ ├── commodities.json │ ├── country-names.json │ ├── crypto.json │ ├── etfs.json │ ├── gulf.json │ ├── rss-allowed-domains.cjs │ ├── rss-allowed-domains.json │ ├── sectors.json │ ├── stablecoins.json │ └── stocks.json ├── src/ │ ├── App.ts │ ├── app/ │ │ ├── app-context.ts │ │ ├── country-intel.ts │ │ ├── data-loader.ts │ │ ├── desktop-updater.ts │ │ ├── event-handlers.ts │ │ ├── index.ts │ │ ├── panel-layout.ts │ │ ├── pending-panel-data.ts │ │ ├── refresh-scheduler.ts │ │ └── search-manager.ts │ ├── bootstrap/ │ │ └── chunk-reload.ts │ ├── components/ │ │ ├── AirlineIntelPanel.ts │ │ ├── AviationCommandBar.ts │ │ ├── BreakingNewsBanner.ts │ │ ├── BreakthroughsTickerPanel.ts │ │ ├── CIIPanel.ts │ │ ├── CascadePanel.ts │ │ ├── ClimateAnomalyPanel.ts │ │ ├── CommunityWidget.ts │ │ ├── CorrelationPanel.ts │ │ ├── CountersPanel.ts │ │ ├── CountryBriefPage.ts │ │ ├── CountryBriefPanel.ts │ │ ├── CountryDeepDivePanel.ts │ │ ├── CountryIntelModal.ts │ │ ├── CountryTimeline.ts │ │ ├── CustomWidgetPanel.ts │ │ ├── DailyMarketBriefPanel.ts │ │ ├── DeckGLMap.ts │ │ ├── DeductionPanel.ts │ │ ├── DisasterCorrelationPanel.ts │ │ ├── DisplacementPanel.ts │ │ ├── DownloadBanner.ts │ │ ├── ETFFlowsPanel.ts │ │ ├── EconomicCorrelationPanel.ts │ │ ├── EconomicPanel.ts │ │ ├── EnergyComplexPanel.ts │ │ ├── EscalationCorrelationPanel.ts │ │ ├── ForecastPanel.ts │ │ ├── GdeltIntelPanel.ts │ │ ├── GeoHubsPanel.ts │ │ ├── GivingPanel.ts │ │ ├── GlobeMap.ts │ │ ├── GoodThingsDigestPanel.ts │ │ ├── GulfEconomiesPanel.ts │ │ ├── HeroSpotlightPanel.ts │ │ ├── InsightsPanel.ts │ │ ├── IntelligenceGapBadge.ts │ │ ├── InvestmentsPanel.ts │ │ ├── LiveNewsPanel.ts │ │ ├── LiveWebcamsPanel.ts │ │ ├── LlmStatusIndicator.ts │ │ ├── MacroSignalsPanel.ts │ │ ├── Map.ts │ │ ├── MapContainer.ts │ │ ├── MapContextMenu.ts │ │ ├── MapPopup.ts │ │ ├── MarketPanel.ts │ │ ├── McpConnectModal.ts │ │ ├── McpDataPanel.ts │ │ ├── MilitaryCorrelationPanel.ts │ │ ├── MobileWarningModal.ts │ │ ├── MonitorPanel.ts │ │ ├── NewsPanel.ts │ │ ├── OrefSirensPanel.ts │ │ ├── Panel.ts │ │ ├── PinnedWebcamsPanel.ts │ │ ├── PizzIntIndicator.ts │ │ ├── PlaybackControl.ts │ │ ├── PopulationExposurePanel.ts │ │ ├── PositiveNewsFeedPanel.ts │ │ ├── PredictionPanel.ts │ │ ├── ProBanner.ts │ │ ├── ProgressChartsPanel.ts │ │ ├── RadiationWatchPanel.ts │ │ ├── RegulationPanel.ts │ │ ├── RenewableEnergyPanel.ts │ │ ├── RuntimeConfigPanel.ts │ │ ├── SanctionsPressurePanel.ts │ │ ├── SatelliteFiresPanel.ts │ │ ├── SearchModal.ts │ │ ├── SecurityAdvisoriesPanel.ts │ │ ├── ServiceStatusPanel.ts │ │ ├── SignalModal.ts │ │ ├── SpeciesComebackPanel.ts │ │ ├── StablecoinPanel.ts │ │ ├── StatusPanel.ts │ │ ├── StockAnalysisPanel.ts │ │ ├── StockBacktestPanel.ts │ │ ├── StoryModal.ts │ │ ├── StrategicPosturePanel.ts │ │ ├── StrategicRiskPanel.ts │ │ ├── SupplyChainPanel.ts │ │ ├── TechEventsPanel.ts │ │ ├── TechHubsPanel.ts │ │ ├── TechReadinessPanel.ts │ │ ├── TelegramIntelPanel.ts │ │ ├── ThermalEscalationPanel.ts │ │ ├── TradePolicyPanel.ts │ │ ├── UcdpEventsPanel.ts │ │ ├── UnifiedSettings.ts │ │ ├── VerificationChecklist.ts │ │ ├── VirtualList.ts │ │ ├── WidgetChatModal.ts │ │ ├── WorldClockPanel.ts │ │ └── index.ts │ ├── config/ │ │ ├── ai-datacenters.ts │ │ ├── ai-regulations.ts │ │ ├── ai-research-labs.ts │ │ ├── airports.ts │ │ ├── basemap.ts │ │ ├── bases-expanded.ts │ │ ├── beta.ts │ │ ├── commands.ts │ │ ├── commodity-geo.ts │ │ ├── commodity-markets.ts │ │ ├── commodity-miners.ts │ │ ├── countries.ts │ │ ├── entities.ts │ │ ├── feeds.ts │ │ ├── finance-geo.ts │ │ ├── geo.ts │ │ ├── gulf-fdi.ts │ │ ├── index.ts │ │ ├── irradiators.ts │ │ ├── map-layer-definitions.ts │ │ ├── markets.ts │ │ ├── military.ts │ │ ├── ml-config.ts │ │ ├── panels.ts │ │ ├── pipelines.ts │ │ ├── ports.ts │ │ ├── startup-ecosystems.ts │ │ ├── tech-companies.ts │ │ ├── tech-geo.ts │ │ ├── trade-routes.ts │ │ ├── variant-meta.ts │ │ ├── variant.ts │ │ └── variants/ │ │ ├── base.ts │ │ ├── commodity.ts │ │ ├── finance.ts │ │ ├── full.ts │ │ ├── happy.ts │ │ └── tech.ts │ ├── data/ │ │ ├── conservation-wins.json │ │ ├── renewable-installations.json │ │ └── world-happiness.json │ ├── e2e/ │ │ ├── map-harness.ts │ │ ├── mobile-map-harness.ts │ │ └── mobile-map-integration-harness.ts │ ├── generated/ │ │ ├── client/ │ │ │ └── worldmonitor/ │ │ │ ├── aviation/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── climate/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── conflict/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── cyber/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── displacement/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── economic/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── forecast/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── giving/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── imagery/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── infrastructure/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── intelligence/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── maritime/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── market/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── military/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── natural/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── news/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── positive_events/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── prediction/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── radiation/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── research/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── sanctions/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── seismology/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── supply_chain/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── thermal/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── trade/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── unrest/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ ├── webcam/ │ │ │ │ └── v1/ │ │ │ │ └── service_client.ts │ │ │ └── wildfire/ │ │ │ └── v1/ │ │ │ └── service_client.ts │ │ └── server/ │ │ └── worldmonitor/ │ │ ├── aviation/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── climate/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── conflict/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── cyber/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── displacement/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── economic/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── forecast/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── giving/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── imagery/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── infrastructure/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── intelligence/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── maritime/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── market/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── military/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── natural/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── news/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── positive_events/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── prediction/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── radiation/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── research/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── sanctions/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── seismology/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── supply_chain/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── thermal/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── trade/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── unrest/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ ├── webcam/ │ │ │ └── v1/ │ │ │ └── service_server.ts │ │ └── wildfire/ │ │ └── v1/ │ │ └── service_server.ts │ ├── live-channels-main.ts │ ├── live-channels-window.ts │ ├── locales/ │ │ ├── ar.d.ts │ │ ├── ar.json │ │ ├── bg.json │ │ ├── cs.json │ │ ├── de.json │ │ ├── el.json │ │ ├── en.json │ │ ├── es.d.ts │ │ ├── es.json │ │ ├── fr.json │ │ ├── it.d.ts │ │ ├── it.json │ │ ├── ja.json │ │ ├── ko.json │ │ ├── nl.d.ts │ │ ├── nl.json │ │ ├── pl.d.ts │ │ ├── pl.json │ │ ├── pt.d.ts │ │ ├── pt.json │ │ ├── ro.json │ │ ├── ru.d.ts │ │ ├── ru.json │ │ ├── sv.d.ts │ │ ├── sv.json │ │ ├── th.d.ts │ │ ├── th.json │ │ ├── tr.d.ts │ │ ├── tr.json │ │ ├── vi.d.ts │ │ ├── vi.json │ │ ├── zh.d.ts │ │ └── zh.json │ ├── main.ts │ ├── pwa.d.ts │ ├── services/ │ │ ├── activity-tracker.ts │ │ ├── ai-classify-queue.ts │ │ ├── ai-flow-settings.ts │ │ ├── analysis-core.ts │ │ ├── analysis-worker.ts │ │ ├── analytics.ts │ │ ├── aviation/ │ │ │ ├── index.ts │ │ │ └── watchlist.ts │ │ ├── bootstrap.ts │ │ ├── breaking-news-alerts.ts │ │ ├── cable-activity.ts │ │ ├── cable-health.ts │ │ ├── cached-risk-scores.ts │ │ ├── cached-theater-posture.ts │ │ ├── celebration.ts │ │ ├── climate/ │ │ │ └── index.ts │ │ ├── clustering.ts │ │ ├── conflict/ │ │ │ └── index.ts │ │ ├── conservation-data.ts │ │ ├── correlation-engine/ │ │ │ ├── adapters/ │ │ │ │ ├── disaster.ts │ │ │ │ ├── economic.ts │ │ │ │ ├── escalation.ts │ │ │ │ └── military.ts │ │ │ ├── engine.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── correlation.ts │ │ ├── country-geometry.ts │ │ ├── country-instability.ts │ │ ├── cross-module-integration.ts │ │ ├── cyber/ │ │ │ └── index.ts │ │ ├── daily-market-brief.ts │ │ ├── data-freshness.ts │ │ ├── desktop-readiness.ts │ │ ├── displacement/ │ │ │ └── index.ts │ │ ├── earthquakes.ts │ │ ├── economic/ │ │ │ └── index.ts │ │ ├── entity-extraction.ts │ │ ├── entity-index.ts │ │ ├── eonet.ts │ │ ├── feed-date.ts │ │ ├── focal-point-detector.ts │ │ ├── font-settings.ts │ │ ├── forecast.ts │ │ ├── gdelt-intel.ts │ │ ├── geo-activity.ts │ │ ├── geo-convergence.ts │ │ ├── geo-hub-index.ts │ │ ├── giving/ │ │ │ └── index.ts │ │ ├── globe-render-settings.ts │ │ ├── gps-interference.ts │ │ ├── happiness-data.ts │ │ ├── happy-share-renderer.ts │ │ ├── hotspot-escalation.ts │ │ ├── hub-activity-scoring.ts │ │ ├── humanity-counters.ts │ │ ├── i18n.ts │ │ ├── imagery.ts │ │ ├── index.ts │ │ ├── infrastructure/ │ │ │ └── index.ts │ │ ├── infrastructure-cascade.ts │ │ ├── insights-loader.ts │ │ ├── intelligence/ │ │ │ └── index.ts │ │ ├── investments-focus.ts │ │ ├── kindness-data.ts │ │ ├── live-news.ts │ │ ├── live-stream-settings.ts │ │ ├── maritime/ │ │ │ └── index.ts │ │ ├── market/ │ │ │ └── index.ts │ │ ├── market-watchlist.ts │ │ ├── mcp-store.ts │ │ ├── meta-tags.ts │ │ ├── military/ │ │ │ └── index.ts │ │ ├── military-bases.ts │ │ ├── military-flights.ts │ │ ├── military-surge.ts │ │ ├── military-vessels.ts │ │ ├── ml-capabilities.ts │ │ ├── ml-worker.ts │ │ ├── news/ │ │ │ └── index.ts │ │ ├── ollama-models.ts │ │ ├── oref-alerts.ts │ │ ├── oref-locations.ts │ │ ├── parallel-analysis.ts │ │ ├── persistent-cache.ts │ │ ├── pizzint.ts │ │ ├── population-exposure.ts │ │ ├── positive-classifier.ts │ │ ├── positive-events-geo.ts │ │ ├── prediction/ │ │ │ └── index.ts │ │ ├── preferences-content.ts │ │ ├── progress-data.ts │ │ ├── radiation.ts │ │ ├── related-assets.ts │ │ ├── renewable-energy-data.ts │ │ ├── renewable-installations.ts │ │ ├── research/ │ │ │ └── index.ts │ │ ├── rpc-client.ts │ │ ├── rss.ts │ │ ├── runtime-config.ts │ │ ├── runtime.ts │ │ ├── sanctions-pressure.ts │ │ ├── satellites.ts │ │ ├── security-advisories.ts │ │ ├── sentiment-gate.ts │ │ ├── settings-constants.ts │ │ ├── settings-manager.ts │ │ ├── signal-aggregator.ts │ │ ├── stock-analysis-history.ts │ │ ├── stock-analysis.ts │ │ ├── stock-backtest.ts │ │ ├── storage.ts │ │ ├── story-data.ts │ │ ├── story-renderer.ts │ │ ├── story-share.ts │ │ ├── summarization.ts │ │ ├── supply-chain/ │ │ │ └── index.ts │ │ ├── tauri-bridge.ts │ │ ├── tech-activity.ts │ │ ├── tech-hub-index.ts │ │ ├── telegram-intel.ts │ │ ├── temporal-baseline.ts │ │ ├── thermal-escalation.ts │ │ ├── threat-classifier.ts │ │ ├── throttled-target-requests.ts │ │ ├── trade/ │ │ │ └── index.ts │ │ ├── trending-keywords.ts │ │ ├── tv-mode.ts │ │ ├── unrest/ │ │ │ └── index.ts │ │ ├── usa-spending.ts │ │ ├── usni-fleet.ts │ │ ├── velocity.ts │ │ ├── weather.ts │ │ ├── webcams/ │ │ │ ├── index.ts │ │ │ └── pinned-store.ts │ │ ├── widget-store.ts │ │ ├── wildfires/ │ │ │ └── index.ts │ │ └── wingbits.ts │ ├── settings-main.ts │ ├── settings-window.ts │ ├── shims/ │ │ ├── child-process-proxy.ts │ │ └── child-process.ts │ ├── styles/ │ │ ├── base-layer.css │ │ ├── country-deep-dive.css │ │ ├── happy-theme.css │ │ ├── main.css │ │ ├── map-context-menu.css │ │ ├── panels.css │ │ ├── rtl-overrides.css │ │ └── settings-window.css │ ├── types/ │ │ └── index.ts │ ├── utils/ │ │ ├── analysis-constants.ts │ │ ├── circuit-breaker.ts │ │ ├── country-flag.ts │ │ ├── cross-domain-storage.ts │ │ ├── distance.ts │ │ ├── dom-utils.ts │ │ ├── export.ts │ │ ├── hash.ts │ │ ├── imagery-preview.ts │ │ ├── index.ts │ │ ├── keyword-match.ts │ │ ├── layer-warning.ts │ │ ├── map-locale.ts │ │ ├── news-context.ts │ │ ├── proxy.ts │ │ ├── reverse-geocode.ts │ │ ├── sanitize.ts │ │ ├── settings-persistence.ts │ │ ├── sparkline.ts │ │ ├── storage-quota.ts │ │ ├── summary-cache-key.ts │ │ ├── theme-colors.ts │ │ ├── theme-manager.ts │ │ ├── transit-chart.ts │ │ ├── urlState.ts │ │ ├── user-location.ts │ │ ├── utm.ts │ │ └── widget-sanitizer.ts │ ├── vite-env.d.ts │ └── workers/ │ ├── analysis.worker.ts │ ├── ml.worker.ts │ └── vector-db.ts ├── src-tauri/ │ ├── .cargo/ │ │ ├── config.local.toml.example │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.toml │ ├── build.rs │ ├── capabilities/ │ │ ├── default.json │ │ └── youtube-login.json │ ├── icons/ │ │ └── icon.icns │ ├── nsis/ │ │ └── installer-hooks.nsh │ ├── sidecar/ │ │ ├── local-api-server.mjs │ │ ├── local-api-server.test.mjs │ │ ├── node/ │ │ │ └── .gitkeep │ │ └── package.json │ ├── src/ │ │ └── main.rs │ ├── tauri.conf.json │ ├── tauri.finance.conf.json │ └── tauri.tech.conf.json ├── tests/ │ ├── bootstrap.test.mjs │ ├── chokepoint-id-mapping.test.mjs │ ├── chokepoint-transit-counter.test.mjs │ ├── cii-scoring.test.mts │ ├── circuit-breaker-persistent-stale-ceiling.test.mts │ ├── clustering.test.mjs │ ├── contact-handler.test.mjs │ ├── corridorrisk-upstream.test.mjs │ ├── countries-geojson.test.mjs │ ├── country-geometry-overrides.test.mts │ ├── crypto-config.test.mjs │ ├── customs-revenue.test.mjs │ ├── daily-market-brief.test.mts │ ├── deckgl-layer-state-aliasing.test.mjs │ ├── deduction-prompt.test.mjs │ ├── deploy-config.test.mjs │ ├── digest-no-reclassify.test.mjs │ ├── download-handler.test.mjs │ ├── edge-functions.test.mjs │ ├── escalation-country-merge.test.mts │ ├── flush-stale-refreshes.test.mjs │ ├── forecast-detectors.test.mjs │ ├── forecast-history.test.mjs │ ├── forecast-trace-export.test.mjs │ ├── freight-indices.test.mjs │ ├── geo-keyword-matching.test.mts │ ├── globe-2d-3d-parity.test.mjs │ ├── globe-tooltip-enrichment.test.mjs │ ├── gulf-fdi-data.test.mjs │ ├── handlers.test.mts │ ├── hapi-gdelt-circuit-breakers.test.mjs │ ├── helpers/ │ │ ├── llm-health-stub.ts │ │ └── runtime-config-panel-harness.mjs │ ├── insights-loader.test.mjs │ ├── lint-md-script-scope.test.mjs │ ├── live-news-hls.test.mjs │ ├── llm-sanitize.test.mjs │ ├── map-fullscreen-resize.test.mjs │ ├── map-harness.html │ ├── map-locale.test.mts │ ├── market-quote-cache-keying.test.mjs │ ├── market-service-symbol-casing.test.mjs │ ├── mdx-lint.test.mjs │ ├── military-classification.test.mjs │ ├── military-flight-classification.test.mjs │ ├── military-surges.test.mjs │ ├── mobile-map-harness.html │ ├── mobile-map-integration-harness.html │ ├── oref-breaking.test.mjs │ ├── oref-locations.test.mjs │ ├── oref-proxy.test.mjs │ ├── panel-config-guardrails.test.mjs │ ├── portwatch-upstream.test.mjs │ ├── prediction-scoring.test.mjs │ ├── premium-stock-gateway.test.mts │ ├── redis-caching.test.mjs │ ├── relay-helper.test.mjs │ ├── route-cache-tier.test.mjs │ ├── runtime-config-panel-visibility.test.mjs │ ├── runtime-env-guards.test.mjs │ ├── runtime-harness.html │ ├── sanctions-pressure.test.mjs │ ├── sanctions-seed-unit.test.mjs │ ├── seed-utils.test.mjs │ ├── seed-warm-ping-origin.test.mjs │ ├── server-handlers.test.mjs │ ├── shared-llm.test.mts │ ├── smart-poll-loop.test.mjs │ ├── stock-analysis-history.test.mts │ ├── stock-analysis.test.mts │ ├── stock-backtest.test.mts │ ├── stock-news-search.test.mts │ ├── summarize-reasoning.test.mjs │ ├── supply-chain-handlers.test.mjs │ ├── supply-chain-panel-transit-chart.test.mjs │ ├── supply-chain-v2.test.mjs │ ├── tech-readiness-circuit-breakers.test.mjs │ ├── thermal-escalation-handler-guardrail.test.mjs │ ├── thermal-escalation-model.test.mjs │ ├── trade-policy-tariffs.test.mjs │ ├── transit-summaries.test.mjs │ ├── ttl-acled-ais-guards.test.mjs │ ├── ucdp-seed-resilience.test.mjs │ ├── urlState.test.mts │ ├── variant-layer-guardrail.test.mjs │ └── widget-builder.test.mjs ├── tsconfig.api.json ├── tsconfig.json ├── vercel.json └── vite.config.ts
Showing preview only (929K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (10433 symbols across 683 files)
FILE: api/_api-key.js
constant DESKTOP_ORIGIN_PATTERNS (line 1) | const DESKTOP_ORIGIN_PATTERNS = [
constant BROWSER_ORIGIN_PATTERNS (line 8) | const BROWSER_ORIGIN_PATTERNS = [
function isDesktopOrigin (line 17) | function isDesktopOrigin(origin) {
function isTrustedBrowserOrigin (line 21) | function isTrustedBrowserOrigin(origin) {
function extractOriginFromReferer (line 25) | function extractOriginFromReferer(referer) {
function validateApiKey (line 34) | function validateApiKey(req, options = {}) {
FILE: api/_cors.js
constant ALLOWED_ORIGIN_PATTERNS (line 1) | const ALLOWED_ORIGIN_PATTERNS = [
function isAllowedOrigin (line 12) | function isAllowedOrigin(origin) {
function getCorsHeaders (line 16) | function getCorsHeaders(req, methods = 'GET, OPTIONS') {
function getPublicCorsHeaders (line 36) | function getPublicCorsHeaders(methods = 'GET, OPTIONS') {
function isDisallowedOrigin (line 45) | function isDisallowedOrigin(req) {
FILE: api/_cors.test.mjs
function makeRequest (line 5) | function makeRequest(origin) {
FILE: api/_github-release.js
constant RELEASES_URL (line 1) | const RELEASES_URL = 'https://api.github.com/repos/koala73/worldmonitor/...
function fetchLatestRelease (line 3) | async function fetchLatestRelease(userAgent) {
FILE: api/_ip-rate-limit.js
function createIpRateLimiter (line 1) | function createIpRateLimiter({ limit, windowMs }) {
FILE: api/_json-response.js
function jsonResponse (line 1) | function jsonResponse(body, status, headers = {}) {
FILE: api/_rate-limit.js
function getRatelimit (line 7) | function getRatelimit() {
function getClientIp (line 24) | function getClientIp(request) {
function checkRateLimit (line 33) | async function checkRateLimit(request, corsHeaders) {
FILE: api/_relay.js
function getRelayBaseUrl (line 6) | function getRelayBaseUrl() {
function getRelayHeaders (line 12) | function getRelayHeaders(baseHeaders = {}) {
function fetchWithTimeout (line 23) | async function fetchWithTimeout(url, options, timeoutMs = 15000) {
function buildRelayResponse (line 36) | function buildRelayResponse(response, body, headers) {
function createRelayHandler (line 56) | function createRelayHandler(cfg) {
FILE: api/_turnstile.js
constant TURNSTILE_VERIFY_URL (line 1) | const TURNSTILE_VERIFY_URL = 'https://challenges.cloudflare.com/turnstil...
function getClientIp (line 3) | function getClientIp(request) {
function verifyTurnstile (line 13) | async function verifyTurnstile({
FILE: api/_turnstile.test.mjs
function restoreEnv (line 9) | function restoreEnv() {
FILE: api/_upstash-json.js
function readJsonFromUpstash (line 1) | async function readJsonFromUpstash(key, timeoutMs = 3_000) {
FILE: api/bootstrap.js
constant BOOTSTRAP_CACHE_KEYS (line 7) | const BOOTSTRAP_CACHE_KEYS = {
constant SLOW_KEYS (line 57) | const SLOW_KEYS = new Set([
constant FAST_KEYS (line 69) | const FAST_KEYS = new Set([
constant TIER_CACHE (line 79) | const TIER_CACHE = {
constant TIER_CDN_CACHE (line 83) | const TIER_CDN_CACHE = {
constant NEG_SENTINEL (line 88) | const NEG_SENTINEL = '__WM_NEG__';
function getCachedJsonBatch (line 90) | async function getCachedJsonBatch(keys) {
function handler (line 123) | async function handler(req) {
FILE: api/cache-purge.js
constant MAX_EXPLICIT_KEYS (line 6) | const MAX_EXPLICIT_KEYS = 20;
constant MAX_PATTERNS (line 7) | const MAX_PATTERNS = 3;
constant MAX_DELETIONS (line 8) | const MAX_DELETIONS = 200;
constant MAX_SCAN_ITERATIONS (line 9) | const MAX_SCAN_ITERATIONS = 5;
constant BLOCKLIST_PREFIXES (line 11) | const BLOCKLIST_PREFIXES = ['rl:', '__'];
constant DURABLE_DATA_PREFIXES (line 12) | const DURABLE_DATA_PREFIXES = ['military:bases:', 'conflict:iran-events:...
function getKeyPrefix (line 14) | function getKeyPrefix() {
function isBlocklisted (line 21) | function isBlocklisted(key) {
function isDurableData (line 25) | function isDurableData(key) {
function getRedisCredentials (line 29) | function getRedisCredentials() {
function redisPipeline (line 36) | async function redisPipeline(commands) {
function redisScan (line 51) | async function redisScan(pattern, maxIterations) {
function timingSafeEqual (line 77) | async function timingSafeEqual(a, b) {
function handler (line 93) | async function handler(req) {
FILE: api/contact.js
constant EMAIL_RE (line 9) | const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
constant PHONE_RE (line 10) | const PHONE_RE = /^[+(]?\d[\d\s()./-]{4,23}\d$/;
constant MAX_FIELD (line 11) | const MAX_FIELD = 500;
constant MAX_MESSAGE (line 12) | const MAX_MESSAGE = 2000;
constant FREE_EMAIL_DOMAINS (line 14) | const FREE_EMAIL_DOMAINS = new Set([
constant RATE_LIMIT (line 27) | const RATE_LIMIT = 3;
constant RATE_WINDOW_MS (line 28) | const RATE_WINDOW_MS = 60 * 60 * 1000;
function sendNotificationEmail (line 32) | async function sendNotificationEmail(name, email, organization, phone, m...
function escapeHtml (line 78) | function escapeHtml(str) {
function sanitizeForSubject (line 86) | function sanitizeForSubject(str, maxLen = 50) {
function handler (line 90) | async function handler(req) {
FILE: api/data/city-coords.ts
type CityCoord (line 6) | interface CityCoord {
constant CITY_COORDS (line 13) | const CITY_COORDS: Record<string, CityCoord> = {
FILE: api/download.js
constant RELEASES_PAGE (line 6) | const RELEASES_PAGE = 'https://github.com/koala73/worldmonitor/releases/...
constant PLATFORM_PATTERNS (line 8) | const PLATFORM_PATTERNS = {
constant VARIANT_IDENTIFIERS (line 17) | const VARIANT_IDENTIFIERS = {
function canonicalAssetName (line 24) | function canonicalAssetName(name) {
function findAssetForVariant (line 28) | function findAssetForVariant(assets, variant, platformMatcher) {
function handler (line 42) | async function handler(req) {
FILE: api/eia/[[...path]].js
function handler (line 6) | async function handler(req) {
FILE: api/enrichment/_domain.js
constant DOMAIN_SUFFIX_RE (line 1) | const DOMAIN_SUFFIX_RE = /\.(com|io|co|org|net|ai|dev|app)$/;
function toOrgSlugFromDomain (line 3) | function toOrgSlugFromDomain(domain) {
function inferCompanyNameFromDomain (line 12) | function inferCompanyNameFromDomain(domain) {
FILE: api/enrichment/company.js
constant CACHE_TTL_SECONDS (line 20) | const CACHE_TTL_SECONDS = 3600;
constant GITHUB_API_HEADERS (line 21) | const GITHUB_API_HEADERS = Object.freeze({ Accept: 'application/vnd.gith...
function fetchGitHubOrg (line 23) | async function fetchGitHubOrg(name) {
function fetchGitHubTechStack (line 46) | async function fetchGitHubTechStack(orgName) {
function fetchSECData (line 72) | async function fetchSECData(companyName) {
function fetchHackerNewsMentions (line 97) | async function fetchHackerNewsMentions(companyName) {
function getTodayISO (line 120) | function getTodayISO() {
function getDateMonthsAgo (line 124) | function getDateMonthsAgo(months) {
function toISODate (line 130) | function toISODate(date) {
function handler (line 134) | async function handler(req) {
FILE: api/enrichment/signals.js
constant UPSTREAM_TIMEOUT_MS (line 18) | const UPSTREAM_TIMEOUT_MS = 5000;
constant DEFAULT_HEADERS (line 19) | const DEFAULT_HEADERS = Object.freeze({ 'User-Agent': UA });
constant GITHUB_HEADERS (line 20) | const GITHUB_HEADERS = Object.freeze({ Accept: 'application/vnd.github.v...
constant SIGNAL_KEYWORDS (line 22) | const SIGNAL_KEYWORDS = {
function classifySignal (line 31) | function classifySignal(text) {
function scoreSignalStrength (line 41) | function scoreSignalStrength(points, comments, recencyDays) {
function fetchHNSignals (line 60) | async function fetchHNSignals(companyName) {
function fetchGitHubSignals (line 91) | async function fetchGitHubSignals(orgName) {
function fetchJobSignals (line 122) | async function fetchJobSignals(companyName) {
function handler (line 156) | async function handler(req) {
FILE: api/fwdstart.js
function handler (line 7) | async function handler(req) {
FILE: api/geo.js
function handler (line 5) | function handler(req) {
FILE: api/gpsjam.js
constant REDIS_KEY (line 7) | const REDIS_KEY = 'intelligence:gpsjam:v2';
constant REDIS_KEY_V1 (line 8) | const REDIS_KEY_V1 = 'intelligence:gpsjam:v1';
constant CACHE_TTL (line 12) | const CACHE_TTL = 300_000;
constant NEG_TTL (line 15) | const NEG_TTL = 60_000;
function fetchGpsJamData (line 17) | async function fetchGpsJamData() {
function handler (line 60) | async function handler(req) {
FILE: api/health.js
constant BOOTSTRAP_KEYS (line 5) | const BOOTSTRAP_KEYS = {
constant STANDALONE_KEYS (line 42) | const STANDALONE_KEYS = {
constant SEED_META (line 79) | const SEED_META = {
constant ON_DEMAND_KEYS (line 144) | const ON_DEMAND_KEYS = new Set([
constant EMPTY_DATA_OK_KEYS (line 156) | const EMPTY_DATA_OK_KEYS = new Set(['notamClosures', 'faaDelays', 'gpsja...
constant CASCADE_GROUPS (line 160) | const CASCADE_GROUPS = {
constant NEG_SENTINEL (line 168) | const NEG_SENTINEL = '__WM_NEG__';
function redisPipeline (line 170) | async function redisPipeline(commands) {
function parseRedisValue (line 185) | function parseRedisValue(raw) {
function dataSize (line 190) | function dataSize(parsed) {
function handler (line 208) | async function handler(req) {
FILE: api/loaders-xml-wms-regression.test.mjs
constant WMS_CAPABILITIES_XML (line 6) | const WMS_CAPABILITIES_XML = `<?xml version="1.0" encoding="UTF-8"?>
FILE: api/mcp-proxy.js
constant TIMEOUT_MS (line 6) | const TIMEOUT_MS = 15_000;
constant SSE_CONNECT_TIMEOUT_MS (line 7) | const SSE_CONNECT_TIMEOUT_MS = 10_000;
constant SSE_RPC_TIMEOUT_MS (line 8) | const SSE_RPC_TIMEOUT_MS = 12_000;
constant MCP_PROTOCOL_VERSION (line 9) | const MCP_PROTOCOL_VERSION = '2025-03-26';
constant BLOCKED_HOST_PATTERNS (line 11) | const BLOCKED_HOST_PATTERNS = [
function buildInitPayload (line 23) | function buildInitPayload() {
function validateServerUrl (line 36) | function validateServerUrl(raw) {
function buildHeaders (line 45) | function buildHeaders(customHeaders) {
function postJson (line 66) | async function postJson(url, body, headers, sessionId) {
function parseJsonRpcResponse (line 78) | async function parseJsonRpcResponse(resp) {
function sendInitialized (line 96) | async function sendInitialized(serverUrl, headers, sessionId) {
function mcpListTools (line 106) | async function mcpListTools(serverUrl, customHeaders) {
function mcpCallTool (line 123) | async function mcpCallTool(serverUrl, toolName, toolArgs, customHeaders) {
function isSseTransport (line 148) | function isSseTransport(url) {
function makeDeferred (line 153) | function makeDeferred() {
class SseSession (line 159) | class SseSession {
method constructor (line 160) | constructor(sseUrl, headers) {
method connect (line 169) | async connect() {
method _startReadLoop (line 180) | _startReadLoop() {
method send (line 247) | async send(id, method, params) {
method notify (line 273) | async notify(method, params) {
method close (line 282) | close() {
function mcpListToolsSse (line 287) | async function mcpListToolsSse(serverUrl, customHeaders) {
function mcpCallToolSse (line 307) | async function mcpCallToolSse(serverUrl, toolName, toolArgs, customHeade...
function handler (line 329) | async function handler(req) {
FILE: api/military-flights.js
constant REDIS_KEY (line 7) | const REDIS_KEY = 'military:flights:v1';
constant STALE_KEY (line 8) | const STALE_KEY = 'military:flights:stale:v1';
constant CACHE_TTL (line 12) | const CACHE_TTL = 120_000;
constant NEG_TTL (line 15) | const NEG_TTL = 30_000;
function fetchMilitaryFlightsData (line 17) | async function fetchMilitaryFlightsData() {
function handler (line 39) | async function handler(req) {
FILE: api/og-story.js
constant COUNTRY_NAMES (line 7) | const COUNTRY_NAMES = {
constant LEVEL_COLORS (line 15) | const LEVEL_COLORS = {
constant LEVEL_LABELS (line 20) | const LEVEL_LABELS = {
function normalizeLevel (line 28) | function normalizeLevel(rawLevel) {
function handler (line 33) | function handler(req, res) {
function escapeXml (line 228) | function escapeXml(str) {
FILE: api/og-story.test.mjs
function renderOgStory (line 5) | function renderOgStory(query = '') {
FILE: api/register-interest.js
constant EMAIL_RE (line 9) | const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
constant MAX_EMAIL_LENGTH (line 10) | const MAX_EMAIL_LENGTH = 320;
constant MAX_META_LENGTH (line 11) | const MAX_META_LENGTH = 100;
constant RATE_LIMIT (line 13) | const RATE_LIMIT = 5;
constant RATE_WINDOW_MS (line 14) | const RATE_WINDOW_MS = 60 * 60 * 1000;
function sendConfirmationEmail (line 18) | async function sendConfirmationEmail(email, referralCode) {
function handler (line 171) | async function handler(req) {
FILE: api/reverse-geocode.js
constant NOMINATIM_BASE (line 6) | const NOMINATIM_BASE = 'https://nominatim.openstreetmap.org/reverse';
constant CHROME_UA (line 7) | const CHROME_UA = 'WorldMonitor/2.0 (https://worldmonitor.app)';
function handler (line 9) | async function handler(req) {
FILE: api/rss-proxy.js
constant RELAY_ONLY_DOMAINS (line 12) | const RELAY_ONLY_DOMAINS = new Set([
constant DIRECT_FETCH_HEADERS (line 33) | const DIRECT_FETCH_HEADERS = Object.freeze({
function fetchViaRailway (line 39) | async function fetchViaRailway(feedUrl, timeoutMs) {
constant ALLOWED_DOMAINS (line 52) | const ALLOWED_DOMAINS = RSS_ALLOWED_DOMAINS;
function isAllowedDomain (line 54) | function isAllowedDomain(hostname) {
function handler (line 60) | async function handler(req) {
FILE: api/satellites.js
constant REDIS_KEY (line 7) | const REDIS_KEY = 'intelligence:satellites:tle:v1';
constant CACHE_TTL (line 11) | const CACHE_TTL = 600_000;
constant NEG_TTL (line 14) | const NEG_TTL = 60_000;
function fetchSatelliteData (line 16) | async function fetchSatelliteData() {
function handler (line 31) | async function handler(req) {
FILE: api/seed-health.js
constant SEED_DOMAINS (line 7) | const SEED_DOMAINS = {
function getMetaBatch (line 59) | async function getMetaBatch(keys) {
function handler (line 84) | async function handler(req) {
FILE: api/story.js
constant COUNTRY_NAMES (line 8) | const COUNTRY_NAMES = {
constant BOT_UA (line 16) | const BOT_UA = /twitterbot|facebookexternalhit|linkedinbot|slackbot|tele...
function handler (line 18) | function handler(req, res) {
function esc (line 83) | function esc(str) {
FILE: api/telegram-feed.js
function handler (line 7) | async function handler(req) {
FILE: api/version.js
function handler (line 6) | async function handler() {
FILE: api/youtube/embed.js
function parseFlag (line 3) | function parseFlag(value, fallback = '1') {
function sanitizeVideoId (line 8) | function sanitizeVideoId(value) {
constant ALLOWED_ORIGINS (line 13) | const ALLOWED_ORIGINS = [
constant ALLOWED_PARENT_ORIGINS (line 22) | const ALLOWED_PARENT_ORIGINS = [
function sanitizeAllowedOrigin (line 29) | function sanitizeAllowedOrigin(raw, fallback, allowList = ALLOWED_ORIGIN...
function sanitizeOrigin (line 42) | function sanitizeOrigin(raw) {
function sanitizeParentOrigin (line 46) | function sanitizeParentOrigin(raw, fallback) {
function handler (line 50) | async function handler(request) {
FILE: api/youtube/embed.test.mjs
function makeRequest (line 5) | function makeRequest(query = '') {
FILE: api/youtube/live.js
function handler (line 9) | async function handler(request) {
FILE: blog-site/astro.config.mjs
constant POST_DATES (line 5) | const POST_DATES = {
method serialize (line 32) | serialize(item) {
FILE: blog-site/scripts/generate-og-images.mjs
constant BLOG_DIR (line 7) | const BLOG_DIR = join(import.meta.dirname, '..', 'src', 'content', 'blog');
constant OUT_DIR (line 8) | const OUT_DIR = join(import.meta.dirname, '..', 'public', 'og');
constant WIDTH (line 9) | const WIDTH = 1200;
constant HEIGHT (line 10) | const HEIGHT = 630;
function h (line 20) | function h(type, style, children) {
FILE: blog-site/src/pages/rss.xml.ts
function GET (line 4) | async function GET(context: { site: URL }) {
FILE: convex/_generated/dataModel.d.ts
type TableNames (line 23) | type TableNames = TableNamesInDataModel<DataModel>;
type Doc (line 30) | type Doc<TableName extends TableNames> = DocumentByName<
type Id (line 48) | type Id<TableName extends TableNames | SystemTableNames> =
type DataModel (line 60) | type DataModel = DataModelFromSchemaDefinition<typeof schema>;
FILE: convex/_generated/registerInterest.js
function hashCode (line 3) | function hashCode(str) {
function generateUniqueReferralCode (line 10) | async function generateUniqueReferralCode(db, email) {
function getCounter (line 24) | async function getCounter(db, name) {
function incrementCounter (line 31) | async function incrementCounter(db, name) {
FILE: convex/_generated/server.d.ts
type QueryCtx (line 107) | type QueryCtx = GenericQueryCtx<DataModel>;
type MutationCtx (line 115) | type MutationCtx = GenericMutationCtx<DataModel>;
type ActionCtx (line 123) | type ActionCtx = GenericActionCtx<DataModel>;
type DatabaseReader (line 132) | type DatabaseReader = GenericDatabaseReader<DataModel>;
type DatabaseWriter (line 143) | type DatabaseWriter = GenericDatabaseWriter<DataModel>;
FILE: convex/registerInterest.ts
function hashCode (line 5) | function hashCode(str: string): number {
function generateUniqueReferralCode (line 13) | async function generateUniqueReferralCode(
function getCounter (line 30) | async function getCounter(db: DatabaseReader, name: string): Promise<num...
function incrementCounter (line 38) | async function incrementCounter(db: DatabaseWriter, name: string): Promi...
FILE: docker/build-handlers.mjs
function findTsHandlers (line 22) | async function findTsHandlers(dir) {
function findJsHandlers (line 45) | async function findJsHandlers(dir) {
function compileHandlers (line 58) | async function compileHandlers(handlers, label) {
FILE: docker/redis-rest-proxy.mjs
constant REDIS_URL (line 22) | const REDIS_URL = process.env.SRH_CONNECTION_STRING || process.env.REDIS...
constant TOKEN (line 23) | const TOKEN = process.env.SRH_TOKEN || '';
constant PORT (line 24) | const PORT = parseInt(process.env.PORT || '80', 10);
function checkAuth (line 31) | function checkAuth(req) {
constant ALLOWED_COMMANDS (line 43) | const ALLOWED_COMMANDS = new Set([
function runCommand (line 58) | async function runCommand(args) {
constant MAX_BODY_BYTES (line 67) | const MAX_BODY_BYTES = 1024 * 1024;
function readBody (line 69) | async function readBody(req) {
FILE: e2e/map-harness.spec.ts
type LayerSnapshot (line 3) | type LayerSnapshot = { id: string; dataCount: number };
type OverlaySnapshot (line 4) | type OverlaySnapshot = {
type VisualScenarioSummary (line 12) | type VisualScenarioSummary = {
type HarnessWindow (line 17) | type HarnessWindow = Window & {
constant EXPECTED_FULL_DECK_LAYERS (line 52) | const EXPECTED_FULL_DECK_LAYERS = [
constant EXPECTED_TECH_DECK_LAYERS (line 85) | const EXPECTED_TECH_DECK_LAYERS = [
constant EXPECTED_FINANCE_DECK_LAYERS (line 120) | const EXPECTED_FINANCE_DECK_LAYERS = [
FILE: e2e/mobile-map-native.spec.ts
constant MOBILE_VIEWPORT (line 3) | const MOBILE_VIEWPORT = devices['iPhone 14 Pro Max'];
FILE: e2e/mobile-map-popup.spec.ts
type HarnessWindow (line 3) | type HarnessWindow = Window & {
constant MOBILE_DEVICE_MATRIX (line 36) | const MOBILE_DEVICE_MATRIX = [
FILE: e2e/rag-vector-store.spec.ts
function clearVectorDB (line 27) | async function clearVectorDB() {
FILE: e2e/widget-builder.spec.ts
type MockWidgetResponse (line 3) | type MockWidgetResponse = {
function buildTallWidgetHtml (line 14) | function buildTallWidgetHtml(title: string, markerClass: string): string {
function buildWidgetSseResponse (line 47) | function buildWidgetSseResponse({ endpoint, title, html }: MockWidgetRes...
function installWidgetAgentMocks (line 57) | async function installWidgetAgentMocks(
function buildProWidgetBody (line 116) | function buildProWidgetBody(title: string, markerClass: string): string {
function installProWidgetAgentMocks (line 131) | async function installProWidgetAgentMocks(
FILE: middleware.ts
constant BOT_UA (line 1) | const BOT_UA =
constant SOCIAL_PREVIEW_UA (line 4) | const SOCIAL_PREVIEW_UA =
constant SOCIAL_PREVIEW_PATHS (line 7) | const SOCIAL_PREVIEW_PATHS = new Set(['/api/story', '/api/og-story']);
constant PUBLIC_API_PATHS (line 9) | const PUBLIC_API_PATHS = new Set(['/api/version', '/api/health']);
constant SOCIAL_IMAGE_UA (line 11) | const SOCIAL_IMAGE_UA =
constant VARIANT_HOST_MAP (line 14) | const VARIANT_HOST_MAP: Record<string, string> = {
constant VARIANT_OG (line 21) | const VARIANT_OG: Record<string, { title: string; description: string; i...
constant ALLOWED_HOSTS (line 42) | const ALLOWED_HOSTS = new Set([
constant VERCEL_PREVIEW_RE (line 46) | const VERCEL_PREVIEW_RE = /^[a-z0-9-]+-[a-z0-9]{8,}\.vercel\.app$/;
function normalizeHost (line 48) | function normalizeHost(raw: string): string {
function isAllowedHost (line 52) | function isAllowedHost(host: string): boolean {
function middleware (line 56) | function middleware(request: Request) {
FILE: pro-test/src/App.tsx
constant API_BASE (line 18) | const API_BASE = 'https://api.worldmonitor.app/api';
constant TURNSTILE_SITE_KEY (line 19) | const TURNSTILE_SITE_KEY = '0x4AAAAAACnaYgHIyxclu8Tj';
constant PRO_URL (line 20) | const PRO_URL = 'https://worldmonitor.app/pro';
type Window (line 23) | interface Window {
function renderTurnstileWidgets (line 32) | function renderTurnstileWidgets(): number {
function getRefCode (line 50) | function getRefCode(): string | undefined {
function sanitize (line 55) | function sanitize(val: unknown): string {
function showReferralSuccess (line 59) | function showReferralSuccess(formEl: HTMLFormElement, data: { referralCo...
function submitWaitlist (line 119) | async function submitWaitlist(email: string, formEl: HTMLFormElement) {
function App (line 1149) | function App() {
FILE: pro-test/src/i18n.ts
type TranslationDictionary (line 5) | type TranslationDictionary = Record<string, unknown>;
constant SUPPORTED_LANGUAGES (line 7) | const SUPPORTED_LANGUAGES = ['en', 'ar', 'bg', 'cs', 'de', 'el', 'es', '...
type SupportedLanguage (line 8) | type SupportedLanguage = typeof SUPPORTED_LANGUAGES[number];
constant SUPPORTED_SET (line 9) | const SUPPORTED_SET = new Set<SupportedLanguage>(SUPPORTED_LANGUAGES);
constant RTL_LANGUAGES (line 12) | const RTL_LANGUAGES = new Set(['ar']);
function normalize (line 19) | function normalize(lng: string): SupportedLanguage {
function ensureLoaded (line 24) | async function ensureLoaded(lng: string): Promise<SupportedLanguage> {
function initI18n (line 34) | async function initI18n(): Promise<void> {
function t (line 51) | function t(key: string, options?: Record<string, unknown>): string {
FILE: pro-test/src/main.tsx
constant TURNSTILE_SCRIPT_SELECTOR (line 7) | const TURNSTILE_SCRIPT_SELECTOR = 'script[src^="https://challenges.cloud...
FILE: public/pro/assets/index-k66dEz6-.js
function l (line 1) | function l(u){const f={};return u.integrity&&(f.integrity=u.integrity),u...
function r (line 1) | function r(u){if(u.ep)return;u.ep=!0;const f=l(u);fetch(u.href,f)}
function P1 (line 9) | function P1(){if(Ap)return ys;Ap=1;var i=Symbol.for("react.transitional....
function F1 (line 9) | function F1(){return Ep||(Ep=1,Fu.exports=P1()),Fu.exports}
function Q1 (line 17) | function Q1(){if(jp)return re;jp=1;var i=Symbol.for("react.transitional....
function Kc (line 17) | function Kc(){return Np||(Np=1,Qu.exports=Q1()),Qu.exports}
function Z1 (line 25) | function Z1(){return Dp||(Dp=1,(function(i){function a(L,G){var F=L.leng...
function J1 (line 25) | function J1(){return Mp||(Mp=1,Ju.exports=Z1()),Ju.exports}
function $1 (line 33) | function $1(){if(Cp)return ut;Cp=1;var i=Kc();function a(y){var p="https...
function W1 (line 33) | function W1(){if(Op)return Wu.exports;Op=1;function i(){if(!(typeof __RE...
function I1 (line 41) | function I1(){if(Rp)return vs;Rp=1;var i=J1(),a=Kc(),l=W1();function r(e...
function eb (line 49) | function eb(){if(Lp)return Zu.exports;Lp=1;function i(){if(!(typeof __RE...
function nb (line 49) | function nb(i){const a=te.useRef(null);return a.current===null&&(a.curre...
function Pc (line 49) | function Pc(i,a){i.indexOf(a)===-1&&i.push(a)}
function dr (line 49) | function dr(i,a){const l=i.indexOf(a);l>-1&&i.splice(l,1)}
function vy (line 49) | function vy(i){return typeof i=="object"&&i!==null}
function by (line 49) | function by(i){let a;return()=>(a===void 0&&(a=i()),a)}
class Qc (line 49) | class Qc{constructor(){this.subscriptions=[]}add(a){return Pc(this.subsc...
method constructor (line 49) | constructor(){this.subscriptions=[]}
method add (line 49) | add(a){return Pc(this.subscriptions,a),()=>dr(this.subscriptions,a)}
method notify (line 49) | notify(a,l,r){const u=this.subscriptions.length;if(u)if(u===1)this.sub...
method getSize (line 49) | getSize(){return this.subscriptions.length}
method clear (line 49) | clear(){this.subscriptions.length=0}
function Sy (line 49) | function Sy(i,a){return a?i*(1e3/a):0}
function ob (line 49) | function ob(i,a,l,r,u){let f,d,h=0;do d=a+(l-a)/2,f=wy(d,r,u)-i,f>0?l=d:...
function zs (line 49) | function zs(i,a,l,r){if(i===a&&l===r)return Yt;const u=f=>ob(f,0,1,i,l);...
function mb (line 49) | function mb(i,a){let l=new Set,r=new Set,u=!1,f=!1;const d=new WeakSet;l...
function Ry (line 49) | function Ry(i,a){let l=!1,r=!0;const u={delta:0,timestamp:0,isProcessing...
function gb (line 49) | function gb(){lr=void 0}
function zp (line 49) | function zp(i){return typeof i!="string"?!1:i.split("/*")[0].includes("v...
function xb (line 49) | function xb(i){return i==null}
function wb (line 49) | function wb(i){let a="",l="",r="",u="";return i.length>5?(a=i.substring(...
function jb (line 49) | function jb(i){var a,l;return isNaN(i)&&typeof i=="string"&&(((a=i.match...
function Cs (line 49) | function Cs(i){const a=i.toString(),l=[],r={color:[],number:[],var:[]},u...
function Uy (line 49) | function Uy(i){return Cs(i).values}
function By (line 49) | function By(i){const{split:a,types:l}=Cs(i),r=a.length;return u=>{let f=...
function Ob (line 49) | function Ob(i){const a=Uy(i);return By(i)(a.map(Cb))}
function tc (line 49) | function tc(i,a,l){return l<0&&(l+=1),l>1&&(l-=1),l<1/6?i+(a-i)*6*l:l<1/...
function Rb (line 49) | function Rb({hue:i,saturation:a,lightness:l,alpha:r}){i/=360,a/=100,l/=1...
function hr (line 49) | function hr(i,a){return l=>l>0?a:i}
function Up (line 49) | function Up(i){const a=_b(i);if(!a)return!1;let l=a.parse(i);return a===...
function zb (line 49) | function zb(i,a){return bc.has(i)?l=>l<=0?i:a:l=>l>=1?a:i}
function Vb (line 49) | function Vb(i,a){return l=>ze(i,a,l)}
function ef (line 49) | function ef(i){return typeof i=="number"?Vb:typeof i=="string"?$c(i)?hr:...
function Hy (line 49) | function Hy(i,a){const l=[...i],r=l.length,u=i.map((f,d)=>ef(f)(f,a[d]))...
function kb (line 49) | function kb(i,a){const l={...i,...a},r={};for(const u in l)i[u]!==void 0...
function Ub (line 49) | function Ub(i,a){const l=[],r={color:0,var:0,number:0};for(let u=0;u<a.v...
function qy (line 49) | function qy(i,a,l){return typeof i=="number"&&typeof a=="number"&&typeof...
function tf (line 49) | function tf(i){let a=0;const l=50;let r=i.next(a);for(;!r.done&&a<mr;)a+...
function qb (line 49) | function qb(i,a=100,l){const r=l({...i,keyframes:[0,a]}),u=Math.min(tf(r...
function Yy (line 49) | function Yy(i,a,l){const r=Math.max(a-Gb,0);return Sy(l-i(r),a-r)}
function Yb (line 49) | function Yb({duration:i=Be.duration,bounce:a=Be.bounce,velocity:l=Be.vel...
function Xb (line 49) | function Xb(i,a,l){let r=l;for(let u=1;u<Kb;u++)r=r-i(r)/a(r);return r}
function Sc (line 49) | function Sc(i,a){return i*Math.sqrt(1-a*a)}
function Hp (line 49) | function Hp(i,a){return a.some(l=>i[l]!==void 0)}
function Qb (line 49) | function Qb(i){let a={velocity:Be.velocity,stiffness:Be.stiffness,dampin...
function pr (line 49) | function pr(i=Be.visualDuration,a=Be.bounce){const l=typeof i!="object"?...
function wc (line 49) | function wc({keyframes:i,velocity:a=0,power:l=.8,timeConstant:r=325,boun...
function Zb (line 49) | function Zb(i,a,l){const r=[],u=l||An.mix||qy,f=i.length-1;for(let d=0;d...
function Jb (line 49) | function Jb(i,a,{clamp:l=!0,ease:r,mixer:u}={}){const f=i.length;if(Fc(f...
function $b (line 49) | function $b(i,a){const l=i[i.length-1];for(let r=1;r<=a;r++){const u=Ds(...
function Wb (line 49) | function Wb(i){const a=[0];return $b(a,i.length-1),a}
function Ib (line 49) | function Ib(i,a){return i.map(l=>l*a)}
function e2 (line 49) | function e2(i,a){return i.map(()=>a||Cy).splice(0,i.length-1)}
function Ts (line 49) | function Ts({duration:i=300,keyframes:a,times:l,ease:r="easeInOut"}){con...
function nf (line 49) | function nf(i,{repeat:a,repeatType:l="loop"},r,u=1){const f=i.filter(t2)...
function Ky (line 49) | function Ky(i){typeof i.type=="string"&&(i.type=n2[i.type])}
class af (line 49) | class af{constructor(){this.updateFinished()}get finished(){return this....
method constructor (line 49) | constructor(){this.updateFinished()}
method finished (line 49) | get finished(){return this._finished}
method updateFinished (line 49) | updateFinished(){this._finished=new Promise(a=>{this.resolve=a})}
method notifyFinished (line 49) | notifyFinished(){this.resolve()}
method then (line 49) | then(a,l){return this.finished.then(a,l)}
class sf (line 49) | class sf extends af{constructor(a){super(),this.state="idle",this.startT...
method constructor (line 49) | constructor(a){super(),this.state="idle",this.startTime=null,this.isSt...
method initAnimation (line 49) | initAnimation(){const{options:a}=this;Ky(a);const{type:l=Ts,repeat:r=0...
method updateTime (line 49) | updateTime(a){const l=Math.round(a-this.startTime)*this.playbackSpeed;...
method tick (line 49) | tick(a,l=!1){const{generator:r,totalDuration:u,mixKeyframes:f,mirrored...
method then (line 49) | then(a,l){return this.finished.then(a,l)}
method duration (line 49) | get duration(){return Gt(this.calculatedDuration)}
method iterationDuration (line 49) | get iterationDuration(){const{delay:a=0}=this.options||{};return this....
method time (line 49) | get time(){return Gt(this.currentTime)}
method time (line 49) | set time(a){var l;a=Zt(a),this.currentTime=a,this.startTime===null||th...
method speed (line 49) | get speed(){return this.playbackSpeed}
method speed (line 49) | set speed(a){this.updateTime(ht.now());const l=this.playbackSpeed!==a;...
method play (line 49) | play(){var u,f;if(this.isStopped)return;const{driver:a=Hb,startTime:l}...
method pause (line 49) | pause(){this.state="paused",this.updateTime(ht.now()),this.holdTime=th...
method complete (line 49) | complete(){this.state!=="running"&&this.play(),this.state="finished",t...
method finish (line 49) | finish(){var a,l;this.notifyFinished(),this.teardown(),this.state="fin...
method cancel (line 49) | cancel(){var a,l;this.holdTime=null,this.startTime=0,this.tick(0),this...
method teardown (line 49) | teardown(){this.state="idle",this.stopDriver(),this.startTime=this.hol...
method stopDriver (line 49) | stopDriver(){this.driver&&(this.driver.stop(),this.driver=void 0)}
method sample (line 49) | sample(a){return this.startTime=0,this.tick(a,!0)}
method attachTimeline (line 49) | attachTimeline(a){var l;return this.options.allowFlatten&&(this.option...
function i2 (line 49) | function i2(i){for(let a=1;a<i.length;a++)i[a]??(i[a]=i[a-1])}
function Ec (line 49) | function Ec(i){return i.includes("scale")?1:0}
function jc (line 49) | function jc(i,a){if(!i||i==="none")return Ec(a);const l=i.match(/^matrix...
function o2 (line 49) | function o2(i){return parseFloat(i.trim())}
function f2 (line 49) | function f2(i){const a=[];return c2.forEach(l=>{const r=i.getValue(l);r!...
function Xy (line 49) | function Xy(){if(Dc){const i=Array.from(Ea).filter(r=>r.needsMeasurement...
function Py (line 49) | function Py(){Ea.forEach(i=>{i.readKeyframes(),i.needsMeasurement&&(Dc=!...
function d2 (line 49) | function d2(){Mc=!0,Py(),Xy(),Mc=!1}
class lf (line 49) | class lf{constructor(a,l,r,u,f,d=!1){this.state="pending",this.isAsync=!...
method constructor (line 49) | constructor(a,l,r,u,f,d=!1){this.state="pending",this.isAsync=!1,this....
method scheduleResolve (line 49) | scheduleResolve(){this.state="scheduled",this.isAsync?(Ea.add(this),Nc...
method readKeyframes (line 49) | readKeyframes(){const{unresolvedKeyframes:a,name:l,element:r,motionVal...
method setFinalKeyframe (line 49) | setFinalKeyframe(){}
method measureInitialState (line 49) | measureInitialState(){}
method renderEndStyles (line 49) | renderEndStyles(){}
method measureEndState (line 49) | measureEndState(){}
method complete (line 49) | complete(a=!1){this.state="complete",this.onComplete(this.unresolvedKe...
method cancel (line 49) | cancel(){this.state==="scheduled"&&(Ea.delete(this),this.state="pendin...
method resume (line 49) | resume(){this.state==="pending"&&this.scheduleResolve()}
function m2 (line 49) | function m2(i,a,l){h2(a)?i.style.setProperty(a,l):i.style[a]=l}
function Fy (line 49) | function Fy(i,a){const l=by(i);return()=>p2[a]??l()}
function Zy (line 49) | function Zy(i,a){if(i)return typeof i=="function"?Qy()?Gy(i,a):"ease-out...
function y2 (line 49) | function y2(i,a,l,{delay:r=0,duration:u=300,repeat:f=0,repeatType:d="loo...
function Jy (line 49) | function Jy(i){return typeof i=="function"&&"applyToOptions"in i}
function v2 (line 49) | function v2({type:i,...a}){return Jy(i)&&Qy()?i.applyToOptions(a):(a.dur...
class $y (line 49) | class $y extends af{constructor(a){if(super(),this.finishedTime=null,thi...
method constructor (line 49) | constructor(a){if(super(),this.finishedTime=null,this.isStopped=!1,thi...
method play (line 49) | play(){this.isStopped||(this.manualStartTime=null,this.animation.play(...
method pause (line 49) | pause(){this.animation.pause()}
method complete (line 49) | complete(){var a,l;(l=(a=this.animation).finish)==null||l.call(a)}
method cancel (line 49) | cancel(){try{this.animation.cancel()}catch{}}
method stop (line 49) | stop(){if(this.isStopped)return;this.isStopped=!0;const{state:a}=this;...
method commitStyles (line 49) | commitStyles(){var l,r,u;const a=(l=this.options)==null?void 0:l.eleme...
method duration (line 49) | get duration(){var l,r;const a=((r=(l=this.animation.effect)==null?voi...
method iterationDuration (line 49) | get iterationDuration(){const{delay:a=0}=this.options||{};return this....
method time (line 49) | get time(){return Gt(Number(this.animation.currentTime)||0)}
method time (line 49) | set time(a){this.manualStartTime=null,this.finishedTime=null,this.anim...
method speed (line 49) | get speed(){return this.animation.playbackRate}
method speed (line 49) | set speed(a){a<0&&(this.finishedTime=null),this.animation.playbackRate=a}
method state (line 49) | get state(){return this.finishedTime!==null?"finished":this.animation....
method startTime (line 49) | get startTime(){return this.manualStartTime??Number(this.animation.sta...
method startTime (line 49) | set startTime(a){this.manualStartTime=this.animation.startTime=a}
method attachTimeline (line 49) | attachTimeline({timeline:a,rangeStart:l,rangeEnd:r,observe:u}){var f;r...
function x2 (line 49) | function x2(i){return i in Wy}
function b2 (line 49) | function b2(i){typeof i.ease=="string"&&x2(i.ease)&&(i.ease=Wy[i.ease])}
class S2 (line 49) | class S2 extends $y{constructor(a){b2(a),Ky(a),super(a),a.startTime!==vo...
method constructor (line 49) | constructor(a){b2(a),Ky(a),super(a),a.startTime!==void 0&&(this.startT...
method updateMotionValue (line 49) | updateMotionValue(a){const{motionValue:l,onUpdate:r,onComplete:u,eleme...
function w2 (line 49) | function w2(i){const a=i[0];if(i.length===1)return!0;for(let l=0;l<i.len...
function T2 (line 49) | function T2(i,a,l,r){const u=i[0];if(u===null)return!1;if(a==="display"|...
function Cc (line 49) | function Cc(i){i.duration=0,i.type="keyframes"}
function j2 (line 49) | function j2(i){var v;const{motionValue:a,name:l,repeatDelay:r,repeatType...
class D2 (line 49) | class D2 extends af{constructor({autoplay:a=!0,delay:l=0,type:r="keyfram...
method constructor (line 49) | constructor({autoplay:a=!0,delay:l=0,type:r="keyframes",repeat:u=0,rep...
method onKeyframesResolved (line 49) | onKeyframesResolved(a,l,r,u){var B,q;this.keyframeResolver=void 0;cons...
method finished (line 49) | get finished(){return this._animation?this.animation.finished:this._fi...
method then (line 49) | then(a,l){return this.finished.finally(a).then(()=>{})}
method animation (line 49) | get animation(){var a;return this._animation||((a=this.keyframeResolve...
method duration (line 49) | get duration(){return this.animation.duration}
method iterationDuration (line 49) | get iterationDuration(){return this.animation.iterationDuration}
method time (line 49) | get time(){return this.animation.time}
method time (line 49) | set time(a){this.animation.time=a}
method speed (line 49) | get speed(){return this.animation.speed}
method state (line 49) | get state(){return this.animation.state}
method speed (line 49) | set speed(a){this.animation.speed=a}
method startTime (line 49) | get startTime(){return this.animation.startTime}
method attachTimeline (line 49) | attachTimeline(a){return this._animation?this.stopTimeline=this.animat...
method play (line 49) | play(){this.animation.play()}
method pause (line 49) | pause(){this.animation.pause()}
method complete (line 49) | complete(){this.animation.complete()}
method cancel (line 49) | cancel(){var a;this._animation&&this.animation.cancel(),(a=this.keyfra...
function Iy (line 49) | function Iy(i,a,l,r=0,u=1){const f=Array.from(i).sort((p,v)=>p.sortNodeP...
function C2 (line 49) | function C2(i){const a=M2.exec(i);if(!a)return[,];const[,l,r,u]=a;return...
function e0 (line 49) | function e0(i,a,l=1){const[r,u]=C2(i);if(!r)return;const f=window.getCom...
function k2 (line 49) | function k2(i,{repeat:a,repeatType:l="loop"},r){const u=i.filter(V2),f=a...
function t0 (line 49) | function t0(i,a){if(i!=null&&i.inherit&&a){const{inherit:l,...r}=i;retur...
function rf (line 49) | function rf(i,a){const l=(i==null?void 0:i[a])??(i==null?void 0:i.defaul...
function U2 (line 49) | function U2({when:i,delay:a,delayChildren:l,staggerChildren:r,staggerDir...
function Fp (line 49) | function Fp(i){const a=[{},{}];return i==null||i.values.forEach((l,r)=>{...
function uf (line 49) | function uf(i,a,l,r){if(typeof a=="function"){const[u,f]=Fp(r);a=a(l!==v...
function vi (line 49) | function vi(i,a,l){const r=i.getProps();return uf(r,a,l!==void 0?l:r.cus...
class H2 (line 49) | class H2{constructor(a,l={}){this.canTrackVelocity=null,this.events={},t...
method constructor (line 49) | constructor(a,l={}){this.canTrackVelocity=null,this.events={},this.upd...
method setCurrent (line 49) | setCurrent(a){this.current=a,this.updatedAt=ht.now(),this.canTrackVelo...
method setPrevFrameValue (line 49) | setPrevFrameValue(a=this.current){this.prevFrameValue=a,this.prevUpdat...
method onChange (line 49) | onChange(a){return this.on("change",a)}
method on (line 49) | on(a,l){this.events[a]||(this.events[a]=new Qc);const r=this.events[a]...
method clearListeners (line 49) | clearListeners(){for(const a in this.events)this.events[a].clear()}
method attach (line 49) | attach(a,l){this.passiveEffect=a,this.stopPassiveEffect=l}
method set (line 49) | set(a){this.passiveEffect?this.passiveEffect(a,this.updateAndNotify):t...
method setWithVelocity (line 49) | setWithVelocity(a,l,r){this.set(l),this.prev=void 0,this.prevFrameValu...
method jump (line 49) | jump(a,l=!0){this.updateAndNotify(a),this.prev=a,this.prevUpdatedAt=th...
method dirty (line 49) | dirty(){var a;(a=this.events.change)==null||a.notify(this.current)}
method addDependent (line 49) | addDependent(a){this.dependents||(this.dependents=new Set),this.depend...
method removeDependent (line 49) | removeDependent(a){this.dependents&&this.dependents.delete(a)}
method get (line 49) | get(){return this.current}
method getPrevious (line 49) | getPrevious(){return this.prev}
method getVelocity (line 49) | getVelocity(){const a=ht.now();if(!this.canTrackVelocity||this.prevFra...
method start (line 49) | start(a){return this.stop(),new Promise(l=>{this.hasAnimated=!0,this.a...
method stop (line 49) | stop(){this.animation&&(this.animation.stop(),this.events.animationCan...
method isAnimating (line 49) | isAnimating(){return!!this.animation}
method clearAnimation (line 49) | clearAnimation(){delete this.animation}
method destroy (line 49) | destroy(){var a,l;(a=this.dependents)==null||a.clear(),(l=this.events....
function xi (line 49) | function xi(i,a){return new H2(i,a)}
function q2 (line 49) | function q2(i,a,l){i.hasValue(a)?i.getValue(a).set(l):i.addValue(a,xi(l))}
function G2 (line 49) | function G2(i){return Oc(i)?i[i.length-1]||0:i}
function Y2 (line 49) | function Y2(i,a){const l=vi(i,a);let{transitionEnd:r={},transition:u={},...
function K2 (line 49) | function K2(i){return!!(ct(i)&&i.add)}
function Rc (line 49) | function Rc(i,a){const l=i.getValue("willChange");if(K2(l))return l.add(...
function cf (line 49) | function cf(i){return i.replace(/([A-Z])/g,a=>`-${a.toLowerCase()}`)}
function i0 (line 49) | function i0(i){return i.props[a0]}
function P2 (line 49) | function P2({protectedKeys:i,needsAnimating:a},l){const r=i.hasOwnProper...
function s0 (line 49) | function s0(i,a,{delay:l=0,transitionOverride:r,type:u}={}){let{transiti...
function Lc (line 49) | function Lc(i,a,l={}){var y;const r=vi(i,a,l.type==="exit"?(y=i.presence...
function F2 (line 49) | function F2(i,a,l=0,r=0,u=0,f=1,d){const h=[];for(const y of i.variantCh...
function Q2 (line 49) | function Q2(i,a,l={}){i.notify("AnimationStart",a);let r;if(Array.isArra...
function J2 (line 49) | function J2(i){return typeof i=="number"?i===0:i!==null?i==="none"||i===...
function W2 (line 49) | function W2(i){const[a,l]=i.slice(0,-1).split("(");if(a==="drop-shadow")...
function u0 (line 49) | function u0(i,a){let l=o0(i);return nS.has(l)||(l=Jt),l.getAnimatableNon...
function iS (line 49) | function iS(i,a,l){let r=0,u;for(;r<i.length&&!u;){const f=i[r];typeof f...
class sS (line 49) | class sS extends lf{constructor(a,l,r,u,f){super(a,l,r,u,f,!0)}readKeyfr...
method constructor (line 49) | constructor(a,l,r,u,f){super(a,l,r,u,f,!0)}
method readKeyframes (line 49) | readKeyframes(){const{unresolvedKeyframes:a,element:l,name:r}=this;if(...
method resolveNoneKeyframes (line 49) | resolveNoneKeyframes(){const{unresolvedKeyframes:a,name:l}=this,r=[];f...
method measureInitialState (line 49) | measureInitialState(){const{element:a,unresolvedKeyframes:l,name:r}=th...
method measureEndState (line 49) | measureEndState(){var h;const{element:a,name:l,unresolvedKeyframes:r}=...
function c0 (line 49) | function c0(i,a,l){if(i==null)return[];if(i instanceof EventTarget)retur...
function rS (line 49) | function rS(i){return vy(i)&&"offsetHeight"in i}
function d0 (line 49) | function d0(){return Qt.x||Qt.y}
function oS (line 49) | function oS(i){return i==="x"||i==="y"?Qt[i]?null:(Qt[i]=!0,()=>{Qt[i]=!...
function h0 (line 49) | function h0(i,a){const l=c0(i),r=new AbortController,u={passive:!0,...a,...
function uS (line 49) | function uS(i){return!(i.pointerType==="touch"||d0())}
function cS (line 49) | function cS(i,a,l={}){const[r,u,f]=h0(i,l);return r.forEach(d=>{let h=!1...
function dS (line 49) | function dS(i){return fS.has(i.tagName)||i.isContentEditable===!0}
function mS (line 49) | function mS(i){return hS.has(i.tagName)||i.isContentEditable===!0}
function $p (line 49) | function $p(i){return a=>{a.key==="Enter"&&i(a)}}
function sc (line 49) | function sc(i,a){i.dispatchEvent(new PointerEvent("pointer"+a,{isPrimary...
function Wp (line 49) | function Wp(i){return hf(i)&&!d0()}
function gS (line 49) | function gS(i,a,l={}){const[r,u,f]=h0(i,l),d=h=>{const y=h.currentTarget...
function mf (line 49) | function mf(i){return vy(i)&&"ownerSVGElement"in i}
function xS (line 49) | function xS({target:i,borderBoxSize:a}){var l;(l=or.get(i))==null||l.for...
function bS (line 49) | function bS(i){i.forEach(xS)}
function SS (line 49) | function SS(){typeof ResizeObserver>"u"||($n=new ResizeObserver(bS))}
function wS (line 49) | function wS(i,a){$n||SS();const l=c0(i);return l.forEach(r=>{let u=or.ge...
function TS (line 49) | function TS(){mi=()=>{const i={get width(){return window.innerWidth},get...
function AS (line 49) | function AS(i){return ur.add(i),mi||TS(),()=>{ur.delete(i),!ur.size&&typ...
function eg (line 49) | function eg(i,a){return typeof i=="function"?AS(i):wS(i,a)}
function ES (line 49) | function ES(i){return mf(i)&&i.tagName==="svg"}
function Ar (line 49) | function Ar(i){return i!==null&&typeof i=="object"&&typeof i.start=="fun...
function Os (line 49) | function Os(i){return typeof i=="string"||Array.isArray(i)}
function Er (line 49) | function Er(i){return Ar(i.animate)||gf.some(a=>Os(i[a]))}
function g0 (line 49) | function g0(i){return!!(Er(i)||i.variants)}
function MS (line 49) | function MS(i,a,l){for(const r in a){const u=a[r],f=l[r];if(ct(u))i.addV...
function OS (line 49) | function OS(){if(y0.current=!0,!!CS)if(window.matchMedia){const i=window...
function v0 (line 49) | function v0(i){gr=i}
function RS (line 49) | function RS(){return gr}
class LS (line 49) | class LS{scrapeMotionValuesFromProps(a,l,r){return{}}constructor({parent...
method scrapeMotionValuesFromProps (line 49) | scrapeMotionValuesFromProps(a,l,r){return{}}
method constructor (line 49) | constructor({parent:a,props:l,presenceContext:r,reducedMotionConfig:u,...
method mount (line 49) | mount(a){var l,r;if(this.hasBeenMounted)for(const u in this.initialVal...
method unmount (line 49) | unmount(){var a;this.projection&&this.projection.unmount(),In(this.not...
method addChild (line 49) | addChild(a){this.children.add(a),this.enteringChildren??(this.entering...
method removeChild (line 49) | removeChild(a){this.children.delete(a),this.enteringChildren&&this.ent...
method bindToMotionValue (line 49) | bindToMotionValue(a,l){if(this.valueSubscriptions.has(a)&&this.valueSu...
method sortNodePosition (line 49) | sortNodePosition(a){return!this.current||!this.sortInstanceNodePositio...
method updateFeatures (line 49) | updateFeatures(){let a="animation";for(a in gr){const l=gr[a];if(!l)co...
method triggerBuild (line 49) | triggerBuild(){this.build(this.renderState,this.latestValues,this.props)}
method measureViewportBox (line 49) | measureViewportBox(){return this.current?this.measureInstanceViewportB...
method getStaticValue (line 49) | getStaticValue(a){return this.latestValues[a]}
method setStaticValue (line 49) | setStaticValue(a,l){this.latestValues[a]=l}
method update (line 49) | update(a,l){(a.transformTemplate||this.props.transformTemplate)&&this....
method getProps (line 49) | getProps(){return this.props}
method getVariant (line 49) | getVariant(a){return this.props.variants?this.props.variants[a]:void 0}
method getDefaultTransition (line 49) | getDefaultTransition(){return this.props.transition}
method getTransformPagePoint (line 49) | getTransformPagePoint(){return this.props.transformPagePoint}
method getClosestVariantNode (line 49) | getClosestVariantNode(){return this.isVariantNode?this:this.parent?thi...
method addVariantChild (line 49) | addVariantChild(a){const l=this.getClosestVariantNode();if(l)return l....
method addValue (line 49) | addValue(a,l){const r=this.values.get(a);l!==r&&(r&&this.removeValue(a...
method removeValue (line 49) | removeValue(a){this.values.delete(a);const l=this.valueSubscriptions.g...
method hasValue (line 49) | hasValue(a){return this.values.has(a)}
method getValue (line 49) | getValue(a,l){if(this.props.values&&this.props.values[a])return this.p...
method readValue (line 49) | readValue(a,l){let r=this.latestValues[a]!==void 0||!this.current?this...
method setBaseTarget (line 49) | setBaseTarget(a,l){this.baseTarget[a]=l}
method getBaseTarget (line 49) | getBaseTarget(a){var f;const{initial:l}=this.props;let r;if(typeof l==...
method on (line 49) | on(a,l){return this.events[a]||(this.events[a]=new Qc),this.events[a]....
method notify (line 49) | notify(a,...l){this.events[a]&&this.events[a].notify(...l)}
method scheduleRenderMicrotask (line 49) | scheduleRenderMicrotask(){df.render(this.render)}
class x0 (line 49) | class x0 extends LS{constructor(){super(...arguments),this.KeyframeResol...
method constructor (line 49) | constructor(){super(...arguments),this.KeyframeResolver=sS}
method sortInstanceNodePosition (line 49) | sortInstanceNodePosition(a,l){return a.compareDocumentPosition(l)&2?1:-1}
method getBaseTargetFromProps (line 49) | getBaseTargetFromProps(a,l){const r=a.style;return r?r[l]:void 0}
method removeValueFromRenderState (line 49) | removeValueFromRenderState(a,{vars:l,style:r}){delete l[a],delete r[a]}
method handleChildMotionValue (line 49) | handleChildMotionValue(){this.childSubscription&&(this.childSubscripti...
class ea (line 49) | class ea{constructor(a){this.isMounted=!1,this.node=a}update(){}}
method constructor (line 49) | constructor(a){this.isMounted=!1,this.node=a}
method update (line 49) | update(){}
function b0 (line 49) | function b0({top:i,left:a,right:l,bottom:r}){return{x:{min:a,max:l},y:{m...
function _S (line 49) | function _S({x:i,y:a}){return{top:a.min,right:i.max,bottom:a.max,left:i....
function zS (line 49) | function zS(i,a){if(!a)return i;const l=a({x:i.left,y:i.top}),r=a({x:i.r...
function lc (line 49) | function lc(i){return i===void 0||i===1}
function kc (line 49) | function kc({scale:i,scaleX:a,scaleY:l}){return!lc(i)||!lc(a)||!lc(l)}
function wa (line 49) | function wa(i){return kc(i)||S0(i)||i.z||i.rotate||i.rotateX||i.rotateY|...
function S0 (line 49) | function S0(i){return ig(i.x)||ig(i.y)}
function ig (line 49) | function ig(i){return i&&i!=="0%"}
function yr (line 49) | function yr(i,a,l){const r=i-l,u=a*r;return l+u}
function sg (line 49) | function sg(i,a,l,r,u){return u!==void 0&&(i=yr(i,u,r)),yr(i,l,r)+a}
function Uc (line 49) | function Uc(i,a=0,l=1,r,u){i.min=sg(i.min,a,l,r,u),i.max=sg(i.max,a,l,r,u)}
function w0 (line 49) | function w0(i,{x:a,y:l}){Uc(i.x,a.translate,a.scale,a.originPoint),Uc(i....
function VS (line 49) | function VS(i,a,l,r=!1){const u=l.length;if(!u)return;a.x=a.y=1;let f,d;...
function gi (line 49) | function gi(i,a){i.min=i.min+a,i.max=i.max+a}
function og (line 49) | function og(i,a,l,r,u=.5){const f=ze(i.min,i.max,u);Uc(i,a,l,f,r)}
function ug (line 49) | function ug(i,a){return typeof i=="string"?parseFloat(i)/100*(a.max-a.mi...
function yi (line 49) | function yi(i,a){og(i.x,ug(a.x,i.x),a.scaleX,a.scale,a.originX),og(i.y,u...
function T0 (line 49) | function T0(i,a){return b0(zS(i.getBoundingClientRect(),a))}
function kS (line 49) | function kS(i,a,l){const r=T0(i,l),{scroll:u}=a;return u&&(gi(r.x,u.offs...
function HS (line 49) | function HS(i,a,l){let r="",u=!0;for(let f=0;f<BS;f++){const d=wi[f],h=i...
function yf (line 49) | function yf(i,a,l){const{style:r,vars:u,transformOrigin:f}=i;let d=!1,h=...
function A0 (line 49) | function A0(i,{style:a,vars:l},r,u){const f=i.style;let d;for(d in a)f[d...
function cg (line 49) | function cg(i,a){return a.max===a.min?0:i/(a.max-a.min)*100}
function E0 (line 49) | function E0(i,{layout:a,layoutId:l}){return Ti.has(i)||i.startsWith("ori...
function vf (line 49) | function vf(i,a,l){var d;const r=i.style,u=a==null?void 0:a.style,f={};i...
function GS (line 49) | function GS(i){return window.getComputedStyle(i)}
class YS (line 49) | class YS extends x0{constructor(){super(...arguments),this.type="html",t...
method constructor (line 49) | constructor(){super(...arguments),this.type="html",this.renderInstance...
method readValueFromInstance (line 49) | readValueFromInstance(a,l){var r;if(Ti.has(l))return(r=this.projection...
method measureInstanceViewportBox (line 49) | measureInstanceViewportBox(a,{transformPagePoint:l}){return T0(a,l)}
method build (line 49) | build(a,l,r){yf(a,l,r.transformTemplate)}
method scrapeMotionValuesFromProps (line 49) | scrapeMotionValuesFromProps(a,l,r){return vf(a,l,r)}
function PS (line 49) | function PS(i,a,l=1,r=0,u=!0){i.pathLength=1;const f=u?KS:XS;i[f.offset]...
function j0 (line 49) | function j0(i,{attrX:a,attrY:l,attrScale:r,pathLength:u,pathSpacing:f=1,...
function QS (line 49) | function QS(i,a,l,r){A0(i,a,void 0,r);for(const u in a.attrs)i.setAttrib...
function M0 (line 49) | function M0(i,a,l){const r=vf(i,a,l);for(const u in i)if(ct(i[u])||ct(a[...
class ZS (line 49) | class ZS extends x0{constructor(){super(...arguments),this.type="svg",th...
method constructor (line 49) | constructor(){super(...arguments),this.type="svg",this.isSVGTag=!1,thi...
method getBaseTargetFromProps (line 49) | getBaseTargetFromProps(a,l){return a[l]}
method readValueFromInstance (line 49) | readValueFromInstance(a,l){if(Ti.has(l)){const r=o0(l);return r&&r.def...
method scrapeMotionValuesFromProps (line 49) | scrapeMotionValuesFromProps(a,l,r){return M0(a,l,r)}
method build (line 49) | build(a,l,r){j0(a,l,this.isSVGTag,r.transformTemplate,r.style)}
method renderInstance (line 49) | renderInstance(a,l,r,u){QS(a,l,r,u)}
method mount (line 49) | mount(a){this.isSVGTag=D0(a.tagName),super.mount(a)}
function C0 (line 49) | function C0(i){if(!i)return;if(!i.isControllingVariants){const l=i.paren...
function O0 (line 49) | function O0(i,a){if(!Array.isArray(a))return!1;const l=a.length;if(l!==i...
function IS (line 49) | function IS(i){return a=>Promise.all(a.map(({animation:l,options:r})=>Q2...
function ew (line 49) | function ew(i){let a=IS(i),l=fg(),r=!0,u=!1;const f=p=>(v,b)=>{var j;con...
function tw (line 49) | function tw(i,a){return typeof a=="string"?a!==i:Array.isArray(a)?!O0(a,...
function ba (line 49) | function ba(i=!1){return{isActive:i,protectedKeys:{},needsAnimating:{},p...
function fg (line 49) | function fg(){return{animate:ba(!0),whileInView:ba(),whileHover:ba(),whi...
function dg (line 49) | function dg(i,a){i.min=a.min,i.max=a.max}
function Ft (line 49) | function Ft(i,a){dg(i.x,a.x),dg(i.y,a.y)}
function hg (line 49) | function hg(i,a){i.translate=a.translate,i.scale=a.scale,i.originPoint=a...
function mt (line 49) | function mt(i){return i.max-i.min}
function lw (line 49) | function lw(i,a,l){return Math.abs(i-a)<=l}
function mg (line 49) | function mg(i,a,l,r=.5){i.origin=r,i.originPoint=ze(a.min,a.max,i.origin...
function As (line 49) | function As(i,a,l,r){mg(i.x,a.x,l.x,r?r.originX:void 0),mg(i.y,a.y,l.y,r...
function pg (line 49) | function pg(i,a,l){i.min=l.min+a.min,i.max=i.min+mt(a)}
function rw (line 49) | function rw(i,a,l){pg(i.x,a.x,l.x),pg(i.y,a.y,l.y)}
function gg (line 49) | function gg(i,a,l){i.min=a.min-l.min,i.max=i.min+mt(a)}
function vr (line 49) | function vr(i,a,l){gg(i.x,a.x,l.x),gg(i.y,a.y,l.y)}
function yg (line 49) | function yg(i,a,l,r,u){return i-=a,i=yr(i,1/l,r),u!==void 0&&(i=yr(i,1/u...
function ow (line 49) | function ow(i,a=0,l=1,r=.5,u,f=i,d=i){if(an.test(a)&&(a=parseFloat(a),a=...
function vg (line 49) | function vg(i,a,[l,r,u],f,d){ow(i,a[l],a[r],a[u],a.scale,f,d)}
function xg (line 49) | function xg(i,a,l,r){vg(i.x,a,uw,l?l.x:void 0,r?r.x:void 0),vg(i.y,a,cw,...
function bg (line 49) | function bg(i){return i.translate===0&&i.scale===1}
function _0 (line 49) | function _0(i){return bg(i.x)&&bg(i.y)}
function Sg (line 49) | function Sg(i,a){return i.min===a.min&&i.max===a.max}
function fw (line 49) | function fw(i,a){return Sg(i.x,a.x)&&Sg(i.y,a.y)}
function wg (line 49) | function wg(i,a){return Math.round(i.min)===Math.round(a.min)&&Math.roun...
function z0 (line 49) | function z0(i,a){return wg(i.x,a.x)&&wg(i.y,a.y)}
function Tg (line 49) | function Tg(i){return mt(i.x)/mt(i.y)}
function Ag (line 49) | function Ag(i,a){return i.translate===a.translate&&i.scale===a.scale&&i....
function tn (line 49) | function tn(i){return[i("x"),i("y")]}
function dw (line 49) | function dw(i,a,l){let r="";const u=i.x.translate/a.x,f=i.y.translate/a....
function mw (line 49) | function mw(i,a,l,r,u,f){u?(i.opacity=ze(0,l.opacity??1,pw(r)),i.opacity...
function Ng (line 49) | function Ng(i,a){return i[a]!==void 0?i[a]:i.borderRadius}
function k0 (line 49) | function k0(i,a,l){return r=>r<i?0:r>a?1:l(Ds(i,a,r))}
function yw (line 49) | function yw(i,a,l){const r=ct(i)?i:xi(i);return r.start(of("",r,a,l)),r....
function Rs (line 49) | function Rs(i,a,l,r={passive:!0}){return i.addEventListener(a,l,r),()=>i...
class xw (line 49) | class xw{constructor(){this.children=[],this.isDirty=!1}add(a){Pc(this.c...
method constructor (line 49) | constructor(){this.children=[],this.isDirty=!1}
method add (line 49) | add(a){Pc(this.children,a),this.isDirty=!0}
method remove (line 49) | remove(a){dr(this.children,a),this.isDirty=!0}
method forEach (line 49) | forEach(a){this.isDirty&&this.children.sort(vw),this.isDirty=!1,this.c...
function bw (line 49) | function bw(i,a){const l=ht.now(),r=({timestamp:u})=>{const f=u-l;f>=a&&...
function cr (line 49) | function cr(i){return ct(i)?i.get():i}
class Sw (line 49) | class Sw{constructor(){this.members=[]}add(a){Pc(this.members,a);for(let...
method constructor (line 49) | constructor(){this.members=[]}
method add (line 49) | add(a){Pc(this.members,a);for(let l=this.members.length-1;l>=0;l--){co...
method remove (line 49) | remove(a){if(dr(this.members,a),a===this.prevLead&&(this.prevLead=void...
method relegate (line 49) | relegate(a){var l;for(let r=this.members.indexOf(a)-1;r>=0;r--){const ...
method promote (line 49) | promote(a,l){var u;const r=this.lead;if(a!==r&&(this.prevLead=r,this.l...
method exitAnimationComplete (line 49) | exitAnimationComplete(){this.members.forEach(a=>{var l,r,u,f,d;(r=(l=a...
method scheduleRender (line 49) | scheduleRender(){this.members.forEach(a=>a.instance&&a.scheduleRender(...
method removeLeadSnapshot (line 49) | removeLeadSnapshot(){var a;(a=this.lead)!=null&&a.snapshot&&(this.lead...
function oc (line 49) | function oc(i,a,l,r){const{latestValues:u}=a;u[i]&&(l[i]=u[i],a.setStati...
function U0 (line 49) | function U0(i){if(i.hasCheckedOptimisedAppear=!0,i.root===i)return;const...
function B0 (line 49) | function B0({attachResizeListener:i,defaultParent:a,measureScroll:l,chec...
function Aw (line 49) | function Aw(i){i.updateLayout()}
function Ew (line 49) | function Ew(i){var l;const a=((l=i.resumeFrom)==null?void 0:l.snapshot)|...
function jw (line 49) | function jw(i){i.parent&&(i.isProjecting()||(i.isProjectionDirty=i.paren...
function Nw (line 49) | function Nw(i){i.isProjectionDirty=i.isSharedProjectionDirty=i.isTransfo...
function Dw (line 49) | function Dw(i){i.clearSnapshot()}
function Dg (line 49) | function Dg(i){i.clearMeasurements()}
function Mg (line 49) | function Mg(i){i.isLayoutDirty=!1}
function Mw (line 49) | function Mw(i){const{visualElement:a}=i.options;a&&a.getProps().onBefore...
function Cg (line 49) | function Cg(i){i.finishAnimation(),i.targetDelta=i.relativeTarget=i.targ...
function Cw (line 49) | function Cw(i){i.resolveTargetDelta()}
function Ow (line 49) | function Ow(i){i.calcProjection()}
function Rw (line 49) | function Rw(i){i.resetSkewAndRotation()}
function Lw (line 49) | function Lw(i){i.removeLeadSnapshot()}
function Og (line 49) | function Og(i,a,l){i.translate=ze(a.translate,0,l),i.scale=ze(a.scale,1,...
function Rg (line 49) | function Rg(i,a,l,r){i.min=ze(a.min,l.min,r),i.max=ze(a.max,l.max,r)}
function _w (line 49) | function _w(i,a,l,r){Rg(i.x,a.x,l.x,r),Rg(i.y,a.y,l.y,r)}
function zw (line 49) | function zw(i){return i.animationValues&&i.animationValues.opacityExit!=...
function zg (line 49) | function zg(i){i.min=_g(i.min),i.max=_g(i.max)}
function kw (line 49) | function kw(i){zg(i.x),zg(i.y)}
function H0 (line 49) | function H0(i,a,l){return i==="position"||i==="preserve-aspect"&&!lw(Tg(...
function Uw (line 49) | function Uw(i){var a;return i!==i.root&&((a=i.scroll)==null?void 0:a.was...
function Hw (line 49) | function Hw(i=!0){const a=te.useContext(Xc);if(a===null)return[!0,null];...
function qw (line 49) | function qw(){if(kg)return;const i={};for(const a in Vg)i[a]={isEnabled:...
function K0 (line 49) | function K0(){return qw(),RS()}
function Gw (line 49) | function Gw(i){const a=K0();for(const l in i)a[l]={...a[l],...i[l]};v0(a)}
function xr (line 49) | function xr(i){return i.startsWith("while")||i.startsWith("drag")&&i!=="...
function Kw (line 49) | function Kw(i){typeof i=="function"&&(X0=a=>a.startsWith("on")?!xr(a):i(...
function Xw (line 49) | function Xw(i,a,l){const r={};for(const u in i)u==="values"&&typeof i.va...
function Pw (line 49) | function Pw(i,a){if(Er(i)){const{initial:l,animate:r}=i;return{initial:l...
function Fw (line 49) | function Fw(i){const{initial:a,animate:l}=Pw(i,te.useContext(jr));return...
function Ug (line 49) | function Ug(i){return Array.isArray(i)?i.join(" "):i}
function P0 (line 49) | function P0(i,a,l){for(const r in a)!ct(a[r])&&!E0(r,l)&&(i[r]=a[r])}
function Qw (line 49) | function Qw({transformTemplate:i},a){return te.useMemo(()=>{const l=xf()...
function Zw (line 49) | function Zw(i,a){const l=i.style||{},r={};return P0(r,l,i),Object.assign...
function Jw (line 49) | function Jw(i,a){const l={},r=Zw(i,a);return i.drag&&i.dragListener!==!1...
function $w (line 49) | function $w(i,a,l,r){const u=te.useMemo(()=>{const f=F0();return j0(f,a,...
function bf (line 49) | function bf(i){return typeof i!="string"||i.includes("-")?!1:!!(Ww.index...
function Iw (line 49) | function Iw(i,a,l,{latestValues:r},u,f=!1,d){const y=(d??bf(i)?$w:Jw)(a,...
function eT (line 49) | function eT({scrapeMotionValuesFromProps:i,createRenderState:a},l,r,u){r...
function tT (line 49) | function tT(i,a,l,r){const u={},f=r(i,{});for(const w in f)u[w]=cr(f[w])...
function sT (line 49) | function sT(i,a,l){const r=te.useRef(l);te.useInsertionEffect(()=>{r.cur...
function di (line 49) | function di(i){return i&&typeof i=="object"&&Object.prototype.hasOwnProp...
function lT (line 49) | function lT(i,a,l,r,u,f){var H,X;const{visualElement:d}=te.useContext(jr...
function rT (line 49) | function rT(i,a,l,r){const{layoutId:u,layout:f,drag:d,dragConstraints:h,...
function J0 (line 49) | function J0(i){if(i)return i.options.allowProjection!==!1?i.projection:J...
function cc (line 49) | function cc(i,{forwardMotionProps:a=!1,type:l}={},r,u){r&&Gw(r);const f=...
function oT (line 49) | function oT({layoutId:i}){const a=te.useContext(gy).id;return a&&i!==voi...
function uT (line 49) | function uT(i,a){te.useContext(Y0).strict}
function cT (line 49) | function cT(i){const a=K0(),{drag:l,layout:r}=a;if(!l&&!r)return{};const...
function fT (line 49) | function fT(i,a){if(typeof Proxy>"u")return cc;const l=new Map,r=(f,d)=>...
class hT (line 49) | class hT extends ea{constructor(a){super(a),a.animationState||(a.animati...
method constructor (line 49) | constructor(a){super(a),a.animationState||(a.animationState=ew(a))}
method updateAnimationControlsSubscription (line 49) | updateAnimationControlsSubscription(){const{animate:a}=this.node.getPr...
method mount (line 49) | mount(){this.updateAnimationControlsSubscription()}
method update (line 49) | update(){const{animate:a}=this.node.getProps(),{animate:l}=this.node.p...
method unmount (line 49) | unmount(){var a;this.node.animationState.reset(),(a=this.unmountContro...
class pT (line 49) | class pT extends ea{constructor(){super(...arguments),this.id=mT++}updat...
method constructor (line 49) | constructor(){super(...arguments),this.id=mT++}
method update (line 49) | update(){if(!this.node.presenceContext)return;const{isPresent:a,onExit...
method mount (line 49) | mount(){const{register:a,onExitComplete:l}=this.node.presenceContext||...
method unmount (line 49) | unmount(){}
function ks (line 49) | function ks(i){return{point:{x:i.pageX,y:i.pageY}}}
function Es (line 49) | function Es(i,a,l,r){return Rs(i,a,yT(l),r)}
function vT (line 49) | function vT(i,a){const l=Bg(i.x,a.x),r=Bg(i.y,a.y);return Math.sqrt(l**2...
class W0 (line 49) | class W0{constructor(a,l,{transformPagePoint:r,contextWindow:u=window,dr...
method constructor (line 49) | constructor(a,l,{transformPagePoint:r,contextWindow:u=window,dragSnapT...
method startScrollTracking (line 49) | startScrollTracking(a){let l=a.parentElement;for(;l;){const r=getCompu...
method handleScroll (line 49) | handleScroll(a){const l=this.scrollPositions.get(a);if(!l)return;const...
method updateHandlers (line 49) | updateHandlers(a){this.handlers=a}
method end (line 49) | end(){this.removeListeners&&this.removeListeners(),this.removeScrollLi...
function fc (line 49) | function fc(i,a){return a?{point:a(i.point)}:i}
function qg (line 49) | function qg(i,a){return{x:i.x-a.x,y:i.y-a.y}}
function dc (line 49) | function dc({point:i},a){return{point:i,delta:qg(i,I0(a)),offset:qg(i,xT...
function xT (line 49) | function xT(i){return i[0]}
function I0 (line 49) | function I0(i){return i[i.length-1]}
function bT (line 49) | function bT(i,a){if(i.length<2)return{x:0,y:0};let l=i.length-1,r=null;c...
function ST (line 49) | function ST(i,{min:a,max:l},r){return a!==void 0&&i<a?i=r?ze(a,i,r.min):...
function Gg (line 49) | function Gg(i,a,l){return{min:a!==void 0?i.min+a:void 0,max:l!==void 0?i...
function wT (line 49) | function wT(i,{top:a,left:l,bottom:r,right:u}){return{x:Gg(i.x,l,u),y:Gg...
function Yg (line 49) | function Yg(i,a){let l=a.min-i.min,r=a.max-i.max;return a.max-a.min<i.ma...
function TT (line 49) | function TT(i,a){return{x:Yg(i.x,a.x),y:Yg(i.y,a.y)}}
function AT (line 49) | function AT(i,a){let l=.5;const r=mt(i),u=mt(a);return u>r?l=Ds(a.min,a....
function ET (line 49) | function ET(i,a){const l={};return a.min!==void 0&&(l.min=a.min-i.min),a...
function jT (line 49) | function jT(i=Hc){return i===!1?i=0:i===!0&&(i=Hc),{x:Kg(i,"left","right...
function Kg (line 49) | function Kg(i,a,l){return{min:Xg(i,a),max:Xg(i,l)}}
function Xg (line 49) | function Xg(i,a){return typeof i=="number"?i:i[a]||0}
class DT (line 49) | class DT{constructor(a){this.openDragLock=null,this.isDragging=!1,this.c...
method constructor (line 49) | constructor(a){this.openDragLock=null,this.isDragging=!1,this.currentD...
method start (line 49) | start(a,{snapToCursor:l=!1,distanceThreshold:r}={}){const{presenceCont...
method stop (line 49) | stop(a,l){const r=a||this.latestPointerEvent,u=l||this.latestPanInfo,f...
method cancel (line 49) | cancel(){this.isDragging=!1;const{projection:a,animationState:l}=this....
method endPanSession (line 49) | endPanSession(){this.panSession&&this.panSession.end(),this.panSession...
method updateAxis (line 49) | updateAxis(a,l,r){const{drag:u}=this.getProps();if(!r||!ir(a,u,this.cu...
method resolveConstraints (line 49) | resolveConstraints(){var f;const{dragConstraints:a,dragElastic:l}=this...
method resolveRefConstraints (line 49) | resolveRefConstraints(){const{dragConstraints:a,onMeasureDragConstrain...
method startAnimation (line 49) | startAnimation(a){const{drag:l,dragMomentum:r,dragElastic:u,dragTransi...
method startAxisValueAnimation (line 49) | startAxisValueAnimation(a,l){const r=this.getAxisMotionValue(a);return...
method stopAnimation (line 49) | stopAnimation(){tn(a=>this.getAxisMotionValue(a).stop())}
method getAxisMotionValue (line 49) | getAxisMotionValue(a){const l=`_drag${a.toUpperCase()}`,r=this.visualE...
method snapToCursor (line 49) | snapToCursor(a){tn(l=>{const{drag:r}=this.getProps();if(!ir(l,r,this.c...
method scalePositionWithinConstraints (line 49) | scalePositionWithinConstraints(){if(!this.visualElement.current)return...
method addListeners (line 49) | addListeners(){if(!this.visualElement.current)return;NT.set(this.visua...
method getProps (line 49) | getProps(){const a=this.visualElement.getProps(),{drag:l=!1,dragDirect...
function Pg (line 49) | function Pg(i){let a=!0;return()=>{if(a){a=!1;return}i()}}
function MT (line 49) | function MT(i,a,l){const r=eg(i,Pg(l)),u=eg(a,Pg(l));return()=>{r(),u()}}
function ir (line 49) | function ir(i,a,l){return(a===!0||a===i)&&(l===null||l===i)}
function CT (line 49) | function CT(i,a=10){let l=null;return Math.abs(i.y)>a?l="y":Math.abs(i.x...
class OT (line 49) | class OT extends ea{constructor(a){super(a),this.removeGroupControls=Yt,...
method constructor (line 49) | constructor(a){super(a),this.removeGroupControls=Yt,this.removeListene...
method mount (line 49) | mount(){const{dragControls:a}=this.node.getProps();a&&(this.removeGrou...
method update (line 49) | update(){const{dragControls:a}=this.node.getProps(),{dragControls:l}=t...
method unmount (line 49) | unmount(){this.removeGroupControls(),this.removeListeners(),this.contr...
class RT (line 49) | class RT extends ea{constructor(){super(...arguments),this.removePointer...
method constructor (line 49) | constructor(){super(...arguments),this.removePointerDownListener=Yt}
method onPointerDown (line 49) | onPointerDown(a){this.session=new W0(a,this.createPanHandlers(),{trans...
method createPanHandlers (line 49) | createPanHandlers(){const{onPanSessionStart:a,onPanStart:l,onPan:r,onP...
method mount (line 49) | mount(){this.removePointerDownListener=Es(this.node.current,"pointerdo...
method update (line 49) | update(){this.session&&this.session.updateHandlers(this.createPanHandl...
method unmount (line 49) | unmount(){this.removePointerDownListener(),this.session&&this.session....
class LT (line 49) | class LT extends te.Component{componentDidMount(){const{visualElement:a,...
method componentDidMount (line 49) | componentDidMount(){const{visualElement:a,layoutGroup:l,switchLayoutGr...
method getSnapshotBeforeUpdate (line 49) | getSnapshotBeforeUpdate(a){const{layoutDependency:l,visualElement:r,dr...
method componentDidUpdate (line 49) | componentDidUpdate(){const{projection:a}=this.props.visualElement;a&&(...
method componentWillUnmount (line 49) | componentWillUnmount(){const{visualElement:a,layoutGroup:l,switchLayou...
method safeToRemove (line 49) | safeToRemove(){const{safeToRemove:a}=this.props;a&&a()}
method render (line 49) | render(){return null}
function ev (line 49) | function ev(i){const[a,l]=Hw(),r=te.useContext(gy);return m.jsx(LT,{...i...
function Fg (line 49) | function Fg(i,a,l){const{props:r}=i;i.animationState&&r.whileHover&&i.an...
class zT (line 49) | class zT extends ea{mount(){const{current:a}=this.node;a&&(this.unmount=...
method mount (line 49) | mount(){const{current:a}=this.node;a&&(this.unmount=cS(a,(l,r)=>(Fg(th...
method unmount (line 49) | unmount(){}
class VT (line 49) | class VT extends ea{constructor(){super(...arguments),this.isActive=!1}o...
method constructor (line 49) | constructor(){super(...arguments),this.isActive=!1}
method onFocus (line 49) | onFocus(){let a=!1;try{a=this.node.current.matches(":focus-visible")}c...
method onBlur (line 49) | onBlur(){!this.isActive||!this.node.animationState||(this.node.animati...
method mount (line 49) | mount(){this.unmount=_s(Rs(this.node.current,"focus",()=>this.onFocus(...
method unmount (line 49) | unmount(){}
function Qg (line 49) | function Qg(i,a,l){const{props:r}=i;if(i.current instanceof HTMLButtonEl...
class kT (line 49) | class kT extends ea{mount(){const{current:a}=this.node;if(!a)return;cons...
method mount (line 49) | mount(){const{current:a}=this.node;if(!a)return;const{globalTapTarget:...
method unmount (line 49) | unmount(){}
function HT (line 49) | function HT({root:i,...a}){const l=i||document;pc.has(l)||pc.set(l,{});c...
function qT (line 49) | function qT(i,a,l){const r=HT(a);return qc.set(i,l),r.observe(i),()=>{qc...
class YT (line 49) | class YT extends ea{constructor(){super(...arguments),this.hasEnteredVie...
method constructor (line 49) | constructor(){super(...arguments),this.hasEnteredView=!1,this.isInView...
method startObserver (line 49) | startObserver(){this.unmount();const{viewport:a={}}=this.node.getProps...
method mount (line 49) | mount(){this.startObserver()}
method update (line 49) | update(){if(typeof IntersectionObserver>"u")return;const{props:a,prevP...
method unmount (line 49) | unmount(){}
function KT (line 49) | function KT({viewport:i={}},{viewport:a={}}={}){return l=>i[l]!==a[l]}
function f (line 229) | function f(d){const h=new Event("vite:preloadError",{cancelable:!0});if(...
class s5 (line 229) | class s5{constructor(a){this.capacity=a,this.regExpMap=new Map,this.regE...
method constructor (line 229) | constructor(a){this.capacity=a,this.regExpMap=new Map,this.regExpQueue...
method getRegExp (line 229) | getRegExp(a){const l=this.regExpMap.get(a);if(l!==void 0)return l;cons...
method log (line 229) | log(i){this.output("log",i)}
method warn (line 229) | warn(i){this.output("warn",i)}
method error (line 229) | error(i){this.output("error",i)}
method output (line 229) | output(i,a){var l,r;(r=(l=console==null?void 0:console[i])==null?void 0:...
class wr (line 229) | class wr{constructor(a,l={}){this.init(a,l)}init(a,l={}){this.prefix=l.p...
method constructor (line 229) | constructor(a,l={}){this.init(a,l)}
method init (line 229) | init(a,l={}){this.prefix=l.prefix||"i18next:",this.logger=a||u5,this.o...
method log (line 229) | log(...a){return this.forward(a,"log","",!0)}
method warn (line 229) | warn(...a){return this.forward(a,"warn","",!0)}
method error (line 229) | error(...a){return this.forward(a,"error","")}
method deprecate (line 229) | deprecate(...a){return this.forward(a,"warn","WARNING DEPRECATED: ",!0)}
method forward (line 229) | forward(a,l,r,u){return u&&!this.debug?null:(se(a[0])&&(a[0]=`${r}${th...
method create (line 229) | create(a){return new wr(this.logger,{prefix:`${this.prefix}:${a}:`,......
method clone (line 229) | clone(a){return a=a||this.options,a.prefix=a.prefix||this.prefix,new w...
class Nr (line 229) | class Nr{constructor(){this.observers={}}on(a,l){return a.split(" ").for...
method constructor (line 229) | constructor(){this.observers={}}
method on (line 229) | on(a,l){return a.split(" ").forEach(r=>{this.observers[r]||(this.obser...
method off (line 229) | off(a,l){if(this.observers[a]){if(!l){delete this.observers[a];return}...
method emit (line 229) | emit(a,...l){this.observers[a]&&Array.from(this.observers[a].entries()...
class ny (line 229) | class ny extends Nr{constructor(a,l={ns:["translation"],defaultNS:"trans...
method constructor (line 229) | constructor(a,l={ns:["translation"],defaultNS:"translation"}){super(),...
method addNamespaces (line 229) | addNamespaces(a){this.options.ns.indexOf(a)<0&&this.options.ns.push(a)}
method removeNamespaces (line 229) | removeNamespaces(a){const l=this.options.ns.indexOf(a);l>-1&&this.opti...
method getResource (line 229) | getResource(a,l,r,u={}){var p,v;const f=u.keySeparator!==void 0?u.keyS...
method addResource (line 229) | addResource(a,l,r,u,f={silent:!1}){const d=f.keySeparator!==void 0?f.k...
method addResources (line 229) | addResources(a,l,r,u={silent:!1}){for(const f in r)(se(r[f])||Array.is...
method addResourceBundle (line 229) | addResourceBundle(a,l,r,u,f,d={silent:!1,skipCopy:!1}){let h=[a,l];a.i...
method removeResourceBundle (line 229) | removeResourceBundle(a,l){this.hasResourceBundle(a,l)&&delete this.dat...
method hasResourceBundle (line 229) | hasResourceBundle(a,l){return this.getResource(a,l)!==void 0}
method getResourceBundle (line 229) | getResourceBundle(a,l){return l||(l=this.options.defaultNS),this.getRe...
method getDataByLanguage (line 229) | getDataByLanguage(a){return this.data[a]}
method hasLanguageSomeTranslations (line 229) | hasLanguageSomeTranslations(a){const l=this.getDataByLanguage(a);retur...
method toJSON (line 229) | toJSON(){return this.data}
method addPostProcessor (line 229) | addPostProcessor(i){this.processors[i.name]=i}
method handle (line 229) | handle(i,a,l,r,u){return i.forEach(f=>{var d;a=((d=this.processors[f])==...
function c5 (line 229) | function c5(){const i=[],a=Object.create(null);let l;return a.get=(r,u)=...
function Yc (line 229) | function Yc(i,a){const{[dv]:l}=i(c5());return l.join((a==null?void 0:a.k...
class Tr (line 229) | class Tr extends Nr{constructor(a,l={}){super(),IA(["resourceStore","lan...
method constructor (line 229) | constructor(a,l={}){super(),IA(["resourceStore","languageUtils","plura...
method changeLanguage (line 229) | changeLanguage(a){a&&(this.language=a)}
method exists (line 229) | exists(a,l={interpolation:{}}){const r={...l};if(a==null)return!1;cons...
method extractFromKey (line 229) | extractFromKey(a,l){let r=l.nsSeparator!==void 0?l.nsSeparator:this.op...
method translate (line 229) | translate(a,l,r){let u=typeof l=="object"?{...l}:l;if(typeof u!="objec...
method extendTranslation (line 229) | extendTranslation(a,l,r,u,f){var y,p;if((y=this.i18nFormat)!=null&&y.p...
method resolve (line 229) | resolve(a,l={}){let r,u,f,d,h;return se(a)&&(a=[a]),a.forEach(y=>{if(t...
method isValidLookup (line 229) | isValidLookup(a){return a!==void 0&&!(!this.options.returnNull&&a===nu...
method getResource (line 229) | getResource(a,l,r,u={}){var f;return(f=this.i18nFormat)!=null&&f.getRe...
method getUsedParamsDetails (line 229) | getUsedParamsDetails(a={}){const l=["defaultValue","ordinal","context"...
method hasDefaultValue (line 229) | static hasDefaultValue(a){const l="defaultValue";for(const r in a)if(O...
class iy (line 229) | class iy{constructor(a){this.options=a,this.supportedLngs=this.options.s...
method constructor (line 229) | constructor(a){this.options=a,this.supportedLngs=this.options.supporte...
method getScriptPartFromCode (line 229) | getScriptPartFromCode(a){if(a=Ls(a),!a||a.indexOf("-")<0)return null;c...
method getLanguagePartFromCode (line 229) | getLanguagePartFromCode(a){if(a=Ls(a),!a||a.indexOf("-")<0)return a;co...
method formatLanguageCode (line 229) | formatLanguageCode(a){if(se(a)&&a.indexOf("-")>-1){let l;try{l=Intl.ge...
method isSupportedCode (line 229) | isSupportedCode(a){return(this.options.load==="languageOnly"||this.opt...
method getBestMatchFromCodes (line 229) | getBestMatchFromCodes(a){if(!a)return null;let l;return a.forEach(r=>{...
method getFallbackCodes (line 229) | getFallbackCodes(a,l){if(!a)return[];if(typeof a=="function"&&(a=a(l))...
method toResolveHierarchy (line 229) | toResolveHierarchy(a,l){const r=this.getFallbackCodes((l===!1?[]:l)||t...
class f5 (line 229) | class f5{constructor(a,l={}){this.languageUtils=a,this.options=l,this.lo...
method constructor (line 229) | constructor(a,l={}){this.languageUtils=a,this.options=l,this.logger=nn...
method clearCache (line 229) | clearCache(){this.pluralRulesCache={}}
method getRule (line 229) | getRule(a,l={}){const r=Ls(a==="dev"?"en":a),u=l.ordinal?"ordinal":"ca...
method needsPlural (line 229) | needsPlural(a,l={}){let r=this.getRule(a,l);return r||(r=this.getRule(...
method getPluralFormsOfKey (line 229) | getPluralFormsOfKey(a,l,r={}){return this.getSuffixes(a,r).map(u=>`${l...
method getSuffixes (line 229) | getSuffixes(a,l={}){let r=this.getRule(a,l);return r||(r=this.getRule(...
method getSuffix (line 229) | getSuffix(a,l,r={}){const u=this.getRule(a,r);return u?`${this.options...
class oy (line 229) | class oy{constructor(a={}){var l;this.logger=nn.create("interpolator"),t...
method constructor (line 229) | constructor(a={}){var l;this.logger=nn.create("interpolator"),this.opt...
method init (line 229) | init(a={}){a.interpolation||(a.interpolation={escapeValue:!0});const{e...
method reset (line 229) | reset(){this.options&&this.init(this.options)}
method resetRegExp (line 229) | resetRegExp(){const a=(l,r)=>(l==null?void 0:l.source)===r?(l.lastInde...
method interpolate (line 229) | interpolate(a,l,r,u){var j;let f,d,h;const y=this.options&&this.option...
method nest (line 229) | nest(a,l,r={}){let u,f,d;const h=(y,p)=>{const v=this.nestingOptionsSe...
class m5 (line 229) | class m5{constructor(a={}){this.logger=nn.create("formatter"),this.optio...
method constructor (line 229) | constructor(a={}){this.logger=nn.create("formatter"),this.options=a,th...
method init (line 229) | init(a,l={interpolation:{}}){this.formatSeparator=l.interpolation.form...
method add (line 229) | add(a,l){this.formats[a.toLowerCase().trim()]=l}
method addCached (line 229) | addCached(a,l){this.formats[a.toLowerCase().trim()]=uy(l)}
method format (line 229) | format(a,l,r,u={}){const f=l.split(this.formatSeparator);if(f.length>1...
class g5 (line 229) | class g5 extends Nr{constructor(a,l,r,u={}){var f,d;super(),this.backend...
method constructor (line 229) | constructor(a,l,r,u={}){var f,d;super(),this.backend=a,this.store=l,th...
method queueLoad (line 229) | queueLoad(a,l,r,u){const f={},d={},h={},y={};return a.forEach(p=>{let ...
method loaded (line 229) | loaded(a,l,r){const u=a.split("|"),f=u[0],d=u[1];l&&this.emit("failedL...
method read (line 229) | read(a,l,r,u=0,f=this.retryTimeout,d){if(!a.length)return d(null,{});i...
method prepareLoading (line 229) | prepareLoading(a,l,r={},u){if(!this.backend)return this.logger.warn("N...
method load (line 229) | load(a,l,r){this.prepareLoading(a,l,{},r)}
method reload (line 229) | reload(a,l,r){this.prepareLoading(a,l,{reload:!0},r)}
method loadOne (line 229) | loadOne(a,l=""){const r=a.split("|"),u=r[0],f=r[1];this.read(u,f,"read...
method saveMissing (line 229) | saveMissing(a,l,r,u,f,d={},h=()=>{}){var y,p,v,b,w;if((p=(y=this.servi...
class Ns (line 229) | class Ns extends Nr{constructor(a={},l){if(super(),this.options=cy(a),th...
method constructor (line 229) | constructor(a={},l){if(super(),this.options=cy(a),this.services={},thi...
method init (line 229) | init(a={},l){this.isInitializing=!0,typeof a=="function"&&(l=a,a={}),a...
method loadResources (line 229) | loadResources(a,l=sr){var f,d;let r=l;const u=se(a)?a:this.language;if...
method reloadResources (line 229) | reloadResources(a,l,r){const u=bs();return typeof a=="function"&&(r=a,...
method use (line 229) | use(a){if(!a)throw new Error("You are passing an undefined module! Ple...
method setResolvedLanguage (line 229) | setResolvedLanguage(a){if(!(!a||!this.languages)&&!(["cimode","dev"].i...
method changeLanguage (line 229) | changeLanguage(a,l){this.isLanguageChangingTo=a;const r=bs();this.emit...
method getFixedT (line 229) | getFixedT(a,l,r){const u=(f,d,...h)=>{let y;typeof d!="object"?y=this....
method t (line 229) | t(...a){var l;return(l=this.translator)==null?void 0:l.translate(...a)}
method exists (line 229) | exists(...a){var l;return(l=this.translator)==null?void 0:l.exists(...a)}
method setDefaultNamespace (line 229) | setDefaultNamespace(a){this.options.defaultNS=a}
method hasLoadedNamespace (line 229) | hasLoadedNamespace(a,l={}){if(!this.isInitialized)return this.logger.w...
method loadNamespaces (line 229) | loadNamespaces(a,l){const r=bs();return this.options.ns?(se(a)&&(a=[a]...
method loadLanguages (line 229) | loadLanguages(a,l){const r=bs();se(a)&&(a=[a]);const u=this.options.pr...
method dir (line 229) | dir(a){var u,f;if(a||(a=this.resolvedLanguage||(((u=this.languages)==n...
method createInstance (line 229) | static createInstance(a={},l){const r=new Ns(a,l);return r.createInsta...
method cloneInstance (line 229) | cloneInstance(a={},l=sr){const r=a.forkResourceStore;r&&delete a.forkR...
method toJSON (line 229) | toJSON(){return{options:this.options,store:this.store,language:this.la...
function T5 (line 229) | function T5(i){return w5.call(S5.call(arguments,1),a=>{if(a)for(const l ...
function A5 (line 229) | function A5(i){return typeof i!="string"?!1:[/<\s*script.*?>/i,/<\s*\/\s...
method create (line 229) | create(i,a,l,r){let u=arguments.length>4&&arguments[4]!==void 0?argument...
method read (line 229) | read(i){const a=`${i}=`,l=document.cookie.split(";");for(let r=0;r<l.len...
method remove (line 229) | remove(i,a){this.create(i,"",-1,a)}
method lookup (line 229) | lookup(i){let{lookupCookie:a}=i;if(a&&typeof document<"u")return dy.read...
method cacheUserLanguage (line 229) | cacheUserLanguage(i,a){let{lookupCookie:l,cookieMinutes:r,cookieDomain:u...
method lookup (line 229) | lookup(i){var r;let{lookupQuerystring:a}=i,l;if(typeof window<"u"){let{s...
method lookup (line 229) | lookup(i){var u;let{lookupHash:a,lookupFromHashIndex:l}=i,r;if(typeof wi...
method lookup (line 229) | lookup(i){let{lookupLocalStorage:a}=i;if(a&&hy())return window.localStor...
method cacheUserLanguage (line 229) | cacheUserLanguage(i,a){let{lookupLocalStorage:l}=a;l&&hy()&&window.local...
method lookup (line 229) | lookup(i){let{lookupSessionStorage:a}=i;if(a&&my())return window.session...
method cacheUserLanguage (line 229) | cacheUserLanguage(i,a){let{lookupSessionStorage:l}=a;l&&my()&&window.ses...
method lookup (line 229) | lookup(i){const a=[];if(typeof navigator<"u"){const{languages:l,userLang...
method lookup (line 229) | lookup(i){let{htmlTag:a}=i,l;const r=a||(typeof document<"u"?document.do...
method lookup (line 229) | lookup(i){var u;let{lookupFromPathIndex:a}=i;if(typeof window>"u")return...
method lookup (line 229) | lookup(i){var u,f;let{lookupFromSubdomainIndex:a}=i;const l=typeof a=="n...
class gv (line 229) | class gv{constructor(a){let l=arguments.length>1&&arguments[1]!==void 0?...
method constructor (line 229) | constructor(a){let l=arguments.length>1&&arguments[1]!==void 0?argumen...
method init (line 229) | init(){let a=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{l...
method addDetector (line 229) | addDetector(a){return this.detectors[a.name]=a,this}
method detect (line 229) | detect(){let a=arguments.length>0&&arguments[0]!==void 0?arguments[0]:...
method cacheUserLanguage (line 229) | cacheUserLanguage(a){let l=arguments.length>1&&arguments[1]!==void 0?a...
function s3 (line 229) | function s3(i){var l;const a=((l=(i||"en").split("-")[0])==null?void 0:l...
function l3 (line 229) | async function l3(i){const a=s3(i);if(py.has(a))return a;const l=i3[`./l...
function r3 (line 229) | async function r3(){if(Ke.isInitialized)return;await Ke.use(gv).init({re...
function S (line 229) | function S(i,a){return Ke.t(i,a)}
function d3 (line 229) | function d3(){if(!window.turnstile)return 0;let i=0;return document.quer...
function bv (line 229) | function bv(){return new URLSearchParams(window.location.search).get("re...
function h3 (line 229) | function h3(i){return String(i??"").replace(/[&<>"']/g,a=>({"&":"&",...
function m3 (line 229) | function m3(i,a){if(a.referralCode==null&&a.status==null){const v=i.quer...
function Sv (line 229) | async function Sv(i,a){var y;const l=a.querySelector('button[type="submi...
function L3 (line 229) | function L3(){const[i,a]=te.useState(()=>window.location.hash.startsWith...
FILE: scripts/_clustering.mjs
constant SIMILARITY_THRESHOLD (line 3) | const SIMILARITY_THRESHOLD = 0.5;
constant STOP_WORDS (line 5) | const STOP_WORDS = new Set([
constant MILITARY_KEYWORDS (line 17) | const MILITARY_KEYWORDS = [
constant VIOLENCE_KEYWORDS (line 23) | const VIOLENCE_KEYWORDS = [
constant UNREST_KEYWORDS (line 29) | const UNREST_KEYWORDS = [
constant FLASHPOINT_KEYWORDS (line 35) | const FLASHPOINT_KEYWORDS = [
constant CRISIS_KEYWORDS (line 41) | const CRISIS_KEYWORDS = [
constant DEMOTE_KEYWORDS (line 47) | const DEMOTE_KEYWORDS = [
function tokenize (line 52) | function tokenize(text) {
function jaccardSimilarity (line 61) | function jaccardSimilarity(a, b) {
function clusterItems (line 71) | function clusterItems(items) {
function countMatches (line 134) | function countMatches(text, keywords) {
function scoreImportance (line 138) | function scoreImportance(cluster) {
function selectTopStories (line 173) | function selectTopStories(clusters, maxCount = 8) {
FILE: scripts/_military-surges.mjs
constant DEFAULT_SURGE_THRESHOLD (line 1) | const DEFAULT_SURGE_THRESHOLD = 2;
constant DEFAULT_TOTAL_SURGE_THRESHOLD (line 2) | const DEFAULT_TOTAL_SURGE_THRESHOLD = 1.5;
constant MIN_HISTORY_POINTS (line 3) | const MIN_HISTORY_POINTS = 3;
constant BASELINE_WINDOW (line 4) | const BASELINE_WINDOW = 12;
function average (line 6) | function average(values) {
function round (line 11) | function round(value, digits = 2) {
function sortCounts (line 15) | function sortCounts(record = {}) {
function normalizeSourceFamily (line 21) | function normalizeSourceFamily(sourceVersion = '') {
function getComparableTheaterSnapshots (line 26) | function getComparableTheaterSnapshots(history, theaterId, sourceVersion...
function countPersistentSnapshots (line 39) | function countPersistentSnapshots(snapshots, field, baseline, minCount, ...
function summarizeMilitaryTheaters (line 45) | function summarizeMilitaryTheaters(flights, theaters, assessedAt = Date....
function buildMilitarySurges (line 97) | function buildMilitarySurges(theaterSummaries, history, opts = {}) {
function appendMilitaryHistory (line 188) | function appendMilitaryHistory(history, historyEntry, maxRuns = 72) {
FILE: scripts/_prediction-scoring.mjs
constant EXCLUDE_KEYWORDS (line 3) | const EXCLUDE_KEYWORDS = predictionTags.excludeKeywords;
constant MEME_PATTERNS (line 5) | const MEME_PATTERNS = [
constant REGION_PATTERNS (line 10) | const REGION_PATTERNS = {
function isExcluded (line 20) | function isExcluded(title) {
function isMemeCandidate (line 25) | function isMemeCandidate(title, yesPrice) {
function tagRegions (line 30) | function tagRegions(title) {
function parseYesPrice (line 36) | function parseYesPrice(market) {
function shouldInclude (line 47) | function shouldInclude(m, relaxed = false) {
function scoreMarket (line 57) | function scoreMarket(m) {
function isExpired (line 63) | function isExpired(endDate) {
function filterAndScore (line 69) | function filterAndScore(candidates, tagFilter, limit = 25) {
FILE: scripts/_r2-storage.mjs
function loadS3SDK (line 4) | async function loadS3SDK() {
function getEnvValue (line 14) | function getEnvValue(env, keys) {
function parseBoolean (line 21) | function parseBoolean(value, fallback) {
function sleep (line 29) | function sleep(ms) {
function summarizeError (line 33) | function summarizeError(err) {
function isRetryableApiStatus (line 37) | function isRetryableApiStatus(status) {
function isRetryableR2Error (line 41) | function isRetryableR2Error(err) {
function withR2Retry (line 64) | async function withR2Retry(operation, context = {}) {
function resolveR2StorageConfig (line 82) | function resolveR2StorageConfig(env = process.env, options = {}) {
constant CLIENT_CACHE (line 127) | const CLIENT_CACHE = new Map();
function getR2StorageClient (line 129) | async function getR2StorageClient(config) {
function putR2JsonObject (line 151) | async function putR2JsonObject(config, key, payload, metadata = {}) {
function getR2JsonObject (line 197) | async function getR2JsonObject(config, key) {
FILE: scripts/_seed-utils.mjs
constant CHROME_UA (line 7) | const CHROME_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit...
constant MAX_PAYLOAD_BYTES (line 8) | const MAX_PAYLOAD_BYTES = 5 * 1024 * 1024;
function loadSharedConfig (line 14) | function loadSharedConfig(filename) {
function loadEnvFile (line 22) | function loadEnvFile(metaUrl) {
function maskToken (line 50) | function maskToken(token) {
function getRedisCredentials (line 55) | function getRedisCredentials() {
function redisCommand (line 65) | async function redisCommand(url, token, command) {
function redisGet (line 79) | async function redisGet(url, token, key) {
function redisSet (line 89) | async function redisSet(url, token, key, value, ttlSeconds) {
function redisDel (line 97) | async function redisDel(url, token, key) {
function isTransientRedisError (line 104) | function isTransientRedisError(err) {
function acquireLock (line 112) | async function acquireLock(domain, runId, ttlMs) {
function acquireLockSafely (line 119) | async function acquireLockSafely(domain, runId, ttlMs, opts = {}) {
function releaseLock (line 133) | async function releaseLock(domain, runId) {
function atomicPublish (line 144) | async function atomicPublish(canonicalKey, data, validateFn, ttlSeconds) {
function writeFreshnessMetadata (line 178) | async function writeFreshnessMetadata(domain, resource, count, source) {
function withRetry (line 190) | async function withRetry(fn, maxRetries = 3, delayMs = 1000) {
function logSeedResult (line 208) | function logSeedResult(domain, count, durationMs, extra = {}) {
function verifySeedKey (line 219) | async function verifySeedKey(key) {
function writeExtraKey (line 225) | async function writeExtraKey(key, data, ttl) {
function writeExtraKeyWithMeta (line 238) | async function writeExtraKeyWithMeta(key, data, ttl, recordCount, metaKe...
function extendExistingTtl (line 252) | async function extendExistingTtl(keys, ttlSeconds = 600) {
function sleep (line 281) | function sleep(ms) {
function parseYahooChart (line 285) | function parseYahooChart(data, symbol) {
function runSeed (line 299) | async function runSeed(domain, resource, canonicalKey, fetchFn, opts = {...
FILE: scripts/_trade-parse-utils.mjs
constant BUDGET_LAB_TARIFFS_URL (line 6) | const BUDGET_LAB_TARIFFS_URL = 'https://budgetlab.yale.edu/research/trac...
constant MONTH_MAP (line 8) | const MONTH_MAP = {
function htmlToPlainText (line 14) | function htmlToPlainText(html) {
function toIsoDate (line 31) | function toIsoDate(value) {
function parseBudgetLabEffectiveTariffHtml (line 53) | function parseBudgetLabEffectiveTariffHtml(html) {
FILE: scripts/ais-relay-rss.test.cjs
function listen (line 15) | function listen(server, port = 0) {
function fetch (line 23) | function fetch(url) {
function createMockUpstream (line 41) | function createMockUpstream() {
function createTestRssProxy (line 85) | function createTestRssProxy(upstreamPort) {
FILE: scripts/ais-relay.cjs
function requireShared (line 23) | function requireShared(name) {
constant RSS_ALLOWED_DOMAINS (line 28) | const RSS_ALLOWED_DOMAINS = new Set(requireShared('rss-allowed-domains.c...
constant AISSTREAM_URL (line 34) | const AISSTREAM_URL = 'wss://stream.aisstream.io/v0/stream';
constant API_KEY (line 35) | const API_KEY = process.env.AISSTREAM_API_KEY || process.env.VITE_AISSTR...
constant PORT (line 36) | const PORT = process.env.PORT || 3004;
constant MAX_WS_CLIENTS (line 44) | const MAX_WS_CLIENTS = 10;
constant UPSTREAM_QUEUE_HIGH_WATER (line 45) | const UPSTREAM_QUEUE_HIGH_WATER = Math.max(500, Number(process.env.AIS_U...
constant UPSTREAM_QUEUE_LOW_WATER (line 46) | const UPSTREAM_QUEUE_LOW_WATER = Math.max(
constant UPSTREAM_QUEUE_HARD_CAP (line 50) | const UPSTREAM_QUEUE_HARD_CAP = Math.max(
constant UPSTREAM_DRAIN_BATCH (line 54) | const UPSTREAM_DRAIN_BATCH = Math.max(1, Number(process.env.AIS_UPSTREAM...
constant UPSTREAM_DRAIN_BUDGET_MS (line 55) | const UPSTREAM_DRAIN_BUDGET_MS = Math.max(2, Number(process.env.AIS_UPST...
function safeInt (line 56) | function safeInt(envVal, fallback, min) {
constant MAX_VESSELS (line 61) | const MAX_VESSELS = safeInt(process.env.AIS_MAX_VESSELS, 20000, 1000);
constant MAX_VESSEL_HISTORY (line 62) | const MAX_VESSEL_HISTORY = safeInt(process.env.AIS_MAX_VESSEL_HISTORY, 2...
constant MAX_DENSITY_CELLS (line 63) | const MAX_DENSITY_CELLS = 5000;
constant MEMORY_CLEANUP_THRESHOLD_GB (line 64) | const MEMORY_CLEANUP_THRESHOLD_GB = (() => {
constant RELAY_SHARED_SECRET (line 68) | const RELAY_SHARED_SECRET = process.env.RELAY_SHARED_SECRET || '';
constant RELAY_AUTH_HEADER (line 69) | const RELAY_AUTH_HEADER = (process.env.RELAY_AUTH_HEADER || 'x-relay-key...
constant ALLOW_UNAUTHENTICATED_RELAY (line 70) | const ALLOW_UNAUTHENTICATED_RELAY = process.env.ALLOW_UNAUTHENTICATED_RE...
constant IS_PRODUCTION_RELAY (line 71) | const IS_PRODUCTION_RELAY = process.env.NODE_ENV === 'production'
constant RELAY_RATE_LIMIT_WINDOW_MS (line 75) | const RELAY_RATE_LIMIT_WINDOW_MS = Math.max(1000, Number(process.env.REL...
constant RELAY_RATE_LIMIT_MAX (line 76) | const RELAY_RATE_LIMIT_MAX = Number.isFinite(Number(process.env.RELAY_RA...
constant RELAY_OPENSKY_RATE_LIMIT_MAX (line 78) | const RELAY_OPENSKY_RATE_LIMIT_MAX = Number.isFinite(Number(process.env....
constant RELAY_RSS_RATE_LIMIT_MAX (line 80) | const RELAY_RSS_RATE_LIMIT_MAX = Number.isFinite(Number(process.env.RELA...
constant RELAY_LOG_THROTTLE_MS (line 82) | const RELAY_LOG_THROTTLE_MS = Math.max(1000, Number(process.env.RELAY_LO...
constant ALLOW_VERCEL_PREVIEW_ORIGINS (line 83) | const ALLOW_VERCEL_PREVIEW_ORIGINS = process.env.ALLOW_VERCEL_PREVIEW_OR...
constant OPENSKY_PROXY_AUTH (line 86) | const OPENSKY_PROXY_AUTH = process.env.OPENSKY_PROXY_AUTH || process.env...
constant OPENSKY_PROXY_ENABLED (line 87) | const OPENSKY_PROXY_ENABLED = !!OPENSKY_PROXY_AUTH;
constant OREF_PROXY_AUTH (line 90) | const OREF_PROXY_AUTH = process.env.OREF_PROXY_AUTH || '';
constant OREF_ALERTS_URL (line 91) | const OREF_ALERTS_URL = 'https://www.oref.org.il/WarningMessages/alert/a...
constant OREF_HISTORY_URL (line 92) | const OREF_HISTORY_URL = 'https://www.oref.org.il/WarningMessages/alert/...
constant OREF_POLL_INTERVAL_MS (line 93) | const OREF_POLL_INTERVAL_MS = Math.max(30_000, Number(process.env.OREF_P...
constant OREF_ENABLED (line 94) | const OREF_ENABLED = !!OREF_PROXY_AUTH;
constant OREF_DATA_DIR (line 95) | const OREF_DATA_DIR = process.env.OREF_DATA_DIR || '';
constant OREF_LOCAL_FILE (line 96) | const OREF_LOCAL_FILE = (() => {
constant RELAY_OREF_RATE_LIMIT_MAX (line 105) | const RELAY_OREF_RATE_LIMIT_MAX = Number.isFinite(Number(process.env.REL...
constant UPSTASH_REDIS_REST_URL (line 118) | const UPSTASH_REDIS_REST_URL = process.env.UPSTASH_REDIS_REST_URL || '';
constant UPSTASH_REDIS_REST_TOKEN (line 119) | const UPSTASH_REDIS_REST_TOKEN = process.env.UPSTASH_REDIS_REST_TOKEN ||...
constant UPSTASH_ENABLED (line 120) | const UPSTASH_ENABLED = !!(
constant RELAY_ENV_PREFIX (line 125) | const RELAY_ENV_PREFIX = process.env.RELAY_ENV ? `${process.env.RELAY_EN...
constant OREF_REDIS_KEY (line 126) | const OREF_REDIS_KEY = `${RELAY_ENV_PREFIX}relay:oref:history:v1`;
constant CHROME_UA (line 127) | const CHROME_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit...
function upstashGet (line 136) | function upstashGet(key) {
function upstashSet (line 165) | function upstashSet(key, value, ttlSeconds) {
function upstashExpire (line 193) | function upstashExpire(key, ttlSeconds) {
function upstashMGet (line 221) | function upstashMGet(keys) {
function safeEnd (line 268) | function safeEnd(res, statusCode, headers, body) {
function _acceptsEncoding (line 279) | function _acceptsEncoding(header, encoding) {
function _varyHeader (line 292) | function _varyHeader(res) {
function sendCompressed (line 300) | function sendCompressed(req, res, statusCode, headers, body) {
function sendPreGzipped (line 326) | function sendPreGzipped(req, res, statusCode, headers, rawBody, gzippedB...
constant TELEGRAM_ENABLED (line 346) | const TELEGRAM_ENABLED = Boolean(process.env.TELEGRAM_API_ID && process....
constant TELEGRAM_POLL_INTERVAL_MS (line 347) | const TELEGRAM_POLL_INTERVAL_MS = Math.max(15_000, Number(process.env.TE...
constant TELEGRAM_MAX_FEED_ITEMS (line 348) | const TELEGRAM_MAX_FEED_ITEMS = Math.max(50, Number(process.env.TELEGRAM...
constant TELEGRAM_MAX_TEXT_CHARS (line 349) | const TELEGRAM_MAX_TEXT_CHARS = Math.max(200, Number(process.env.TELEGRA...
function loadTelegramChannels (line 377) | function loadTelegramChannels() {
function normalizeTelegramMessage (line 412) | function normalizeTelegramMessage(msg, channel) {
function initTelegramClientIfNeeded (line 432) | async function initTelegramClientIfNeeded() {
constant TELEGRAM_CHANNEL_TIMEOUT_MS (line 476) | const TELEGRAM_CHANNEL_TIMEOUT_MS = 15_000;
constant TELEGRAM_POLL_CYCLE_TIMEOUT_MS (line 477) | const TELEGRAM_POLL_CYCLE_TIMEOUT_MS = 180_000;
function withTimeout (line 479) | function withTimeout(promise, ms, label) {
function pollTelegramOnce (line 489) | async function pollTelegramOnce() {
function guardedTelegramPoll (line 576) | function guardedTelegramPoll() {
constant TELEGRAM_STARTUP_DELAY_MS (line 593) | const TELEGRAM_STARTUP_DELAY_MS = Math.max(0, Number(process.env.TELEGRA...
function startTelegramPollLoop (line 595) | function startTelegramPollLoop() {
function stripBom (line 617) | function stripBom(text) {
function redactOrefError (line 621) | function redactOrefError(msg) {
function orefDateToUTC (line 625) | function orefDateToUTC(dateStr) {
function orefCurlFetch (line 648) | function orefCurlFetch(proxyAuth, url, { toFile } = {}) {
function orefFetchAlerts (line 672) | async function orefFetchAlerts() {
function orefPreSerializeResponses (line 726) | function orefPreSerializeResponses() {
function orefBootstrapHistoryFromUpstream (line 748) | async function orefBootstrapHistoryFromUpstream() {
constant OREF_PERSIST_MAX_WAVES (line 803) | const OREF_PERSIST_MAX_WAVES = 200;
constant OREF_PERSIST_TTL_SECONDS (line 804) | const OREF_PERSIST_TTL_SECONDS = 7 * 24 * 60 * 60;
function orefPersistHistory (line 806) | async function orefPersistHistory() {
function orefLoadLocalHistory (line 835) | function orefLoadLocalHistory() {
function orefSaveLocalHistory (line 861) | function orefSaveLocalHistory() {
function orefBootstrapHistoryWithRetry (line 883) | async function orefBootstrapHistoryWithRetry() {
function startOrefPollLoop (line 962) | async function startOrefPollLoop() {
constant UCDP_ACCESS_TOKEN (line 979) | const UCDP_ACCESS_TOKEN = (process.env.UCDP_ACCESS_TOKEN || process.env....
constant UCDP_REDIS_KEY (line 980) | const UCDP_REDIS_KEY = 'conflict:ucdp-events:v1';
constant UCDP_PAGE_SIZE (line 981) | const UCDP_PAGE_SIZE = 1000;
constant UCDP_MAX_PAGES (line 982) | const UCDP_MAX_PAGES = 6;
constant UCDP_MAX_EVENTS (line 983) | const UCDP_MAX_EVENTS = 2000;
constant UCDP_TRAILING_WINDOW_MS (line 984) | const UCDP_TRAILING_WINDOW_MS = 365 * 24 * 60 * 60 * 1000;
constant UCDP_POLL_INTERVAL_MS (line 985) | const UCDP_POLL_INTERVAL_MS = 6 * 60 * 60 * 1000;
constant UCDP_TTL_SECONDS (line 986) | const UCDP_TTL_SECONDS = 86400;
constant UCDP_VIOLENCE_TYPE_MAP (line 987) | const UCDP_VIOLENCE_TYPE_MAP = { 1: 'UCDP_VIOLENCE_TYPE_STATE_BASED', 2:...
function ucdpFetchPage (line 989) | function ucdpFetchPage(version, page) {
function ucdpDiscoverVersion (line 1019) | async function ucdpDiscoverVersion() {
function seedUcdpEvents (line 1036) | async function seedUcdpEvents() {
function startUcdpSeedLoop (line 1113) | async function startUcdpSeedLoop() {
constant SAT_SEED_INTERVAL_MS (line 1128) | const SAT_SEED_INTERVAL_MS = 7_200_000;
constant SAT_SEED_TTL (line 1129) | const SAT_SEED_TTL = 14_400;
constant SAT_GROUPS (line 1130) | const SAT_GROUPS = ['military', 'resource'];
constant SAT_NAME_FILTERS (line 1132) | const SAT_NAME_FILTERS = [
function satClassify (line 1145) | function satClassify(name) {
function seedSatelliteTLEs (line 1166) | async function seedSatelliteTLEs() {
function startSatelliteSeedLoop (line 1239) | async function startSatelliteSeedLoop() {
constant FINNHUB_API_KEY (line 1255) | const FINNHUB_API_KEY = process.env.FINNHUB_API_KEY || '';
constant MARKET_SEED_INTERVAL_MS (line 1256) | const MARKET_SEED_INTERVAL_MS = 300_000;
constant MARKET_SEED_TTL (line 1257) | const MARKET_SEED_TTL = 1800;
constant MARKET_SYMBOLS (line 1260) | const MARKET_SYMBOLS = [
constant COMMODITY_SYMBOLS (line 1267) | const COMMODITY_SYMBOLS = ['^VIX', 'GC=F', 'CL=F', 'NG=F', 'SI=F', 'HG=F'];
constant SECTOR_SYMBOLS (line 1269) | const SECTOR_SYMBOLS = ['XLK', 'XLF', 'XLE', 'XLV', 'XLY', 'XLI', 'XLP',...
constant YAHOO_ONLY (line 1271) | const YAHOO_ONLY = new Set(['^GSPC', '^DJI', '^IXIC', '^VIX', 'GC=F', 'C...
function fetchYahooChartDirect (line 1273) | function fetchYahooChartDirect(symbol) {
function fetchFinnhubQuoteDirect (line 1307) | function fetchFinnhubQuoteDirect(symbol, apiKey) {
function sleep (line 1333) | function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
function seedMarketQuotes (line 1335) | async function seedMarketQuotes() {
function seedCommodityQuotes (line 1377) | async function seedCommodityQuotes() {
function seedSectorSummary (line 1416) | async function seedSectorSummary() {
constant GULF_SYMBOLS (line 1457) | const GULF_SYMBOLS = [
constant GULF_SEED_TTL (line 1473) | const GULF_SEED_TTL = 5400;
function seedGulfQuotes (line 1475) | async function seedGulfQuotes() {
constant ETF_LIST (line 1497) | const ETF_LIST = [
constant ETF_SEED_TTL (line 1504) | const ETF_SEED_TTL = 5400;
function parseEtfChart (line 1506) | function parseEtfChart(chart, ticker, issuer) {
function seedEtfFlows (line 1522) | async function seedEtfFlows() {
constant CRYPTO_IDS (line 1558) | const CRYPTO_IDS = _cryptoCfg.ids;
constant CRYPTO_META (line 1559) | const CRYPTO_META = _cryptoCfg.meta;
constant CRYPTO_PAPRIKA_MAP (line 1560) | const CRYPTO_PAPRIKA_MAP = _cryptoCfg.coinpaprika;
constant CRYPTO_SEED_TTL (line 1561) | const CRYPTO_SEED_TTL = 3600;
function fetchCryptoCoinPaprika (line 1563) | async function fetchCryptoCoinPaprika() {
function seedCryptoQuotes (line 1575) | async function seedCryptoQuotes() {
constant STABLECOIN_IDS (line 1605) | const STABLECOIN_IDS = 'tether,usd-coin,dai,first-digital-usd,ethena-usde';
constant STABLECOIN_PAPRIKA_MAP (line 1606) | const STABLECOIN_PAPRIKA_MAP = { tether: 'usdt-tether', 'usd-coin': 'usd...
constant STABLECOIN_SEED_TTL (line 1607) | const STABLECOIN_SEED_TTL = 3600;
function fetchStablecoinCoinPaprika (line 1609) | async function fetchStablecoinCoinPaprika() {
function seedStablecoinMarkets (line 1624) | async function seedStablecoinMarkets() {
function seedAllMarketData (line 1654) | async function seedAllMarketData() {
function startMarketDataSeedLoop (line 1666) | async function startMarketDataSeedLoop() {
constant AVIATIONSTACK_API_KEY (line 1686) | const AVIATIONSTACK_API_KEY = process.env.AVIATIONSTACK_API || '';
constant AVIATION_SEED_INTERVAL_MS (line 1687) | const AVIATION_SEED_INTERVAL_MS = 30 * 60 * 1000;
constant AVIATION_SEED_TTL (line 1688) | const AVIATION_SEED_TTL = 10800;
constant AVIATION_REDIS_KEY (line 1689) | const AVIATION_REDIS_KEY = 'aviation:delays:intl:v3';
constant AVIATION_BATCH_CONCURRENCY (line 1690) | const AVIATION_BATCH_CONCURRENCY = 10;
constant AVIATION_MIN_FLIGHTS_FOR_CLOSURE (line 1691) | const AVIATION_MIN_FLIGHTS_FOR_CLOSURE = 10;
constant RESOLVED_STATUSES (line 1692) | const RESOLVED_STATUSES = new Set(['cancelled', 'landed', 'active', 'arr...
constant AVIATIONSTACK_AIRPORTS (line 1695) | const AVIATIONSTACK_AIRPORTS = [
constant AIRPORT_META (line 1706) | const AIRPORT_META = {
constant REGION_MAP (line 1762) | const REGION_MAP = {
constant DELAY_TYPE_MAP (line 1770) | const DELAY_TYPE_MAP = {
constant SEVERITY_MAP (line 1779) | const SEVERITY_MAP = {
function aviationDetermineSeverity (line 1787) | function aviationDetermineSeverity(avgDelay, delayedPct) {
function fetchAviationStackSingle (line 1795) | function fetchAviationStackSingle(apiKey, iata) {
function aviationAggregateFlights (line 1832) | function aviationAggregateFlights(iata, flights) {
function seedAviationDelays (line 1895) | async function seedAviationDelays() {
function startAviationSeedLoop (line 1936) | async function startAviationSeedLoop() {
constant NOTAM_SEED_INTERVAL_MS (line 1956) | const NOTAM_SEED_INTERVAL_MS = 30 * 60 * 1000;
constant NOTAM_SEED_TTL (line 1957) | const NOTAM_SEED_TTL = 10800;
constant NOTAM_REDIS_KEY (line 1958) | const NOTAM_REDIS_KEY = 'aviation:notam:closures:v2';
constant NOTAM_CLOSURE_QCODES (line 1959) | const NOTAM_CLOSURE_QCODES = new Set(['FA', 'AH', 'AL', 'AW', 'AC', 'AM']);
constant NOTAM_MONITORED_ICAO (line 1960) | const NOTAM_MONITORED_ICAO = [
function fetchIcaoNotams (line 1979) | function fetchIcaoNotams() {
function seedNotamClosures (line 2016) | async function seedNotamClosures() {
function startNotamSeedLoop (line 2061) | function startNotamSeedLoop() {
constant URLHAUS_AUTH_KEY (line 2081) | const URLHAUS_AUTH_KEY = process.env.URLHAUS_AUTH_KEY || '';
constant OTX_API_KEY (line 2082) | const OTX_API_KEY = process.env.OTX_API_KEY || '';
constant ABUSEIPDB_API_KEY (line 2083) | const ABUSEIPDB_API_KEY = process.env.ABUSEIPDB_API_KEY || '';
constant CYBER_SEED_INTERVAL_MS (line 2084) | const CYBER_SEED_INTERVAL_MS = 2 * 60 * 60 * 1000;
constant CYBER_SEED_TTL (line 2085) | const CYBER_SEED_TTL = 14400;
constant CYBER_RPC_KEY (line 2086) | const CYBER_RPC_KEY = 'cyber:threats:v2';
constant CYBER_BOOTSTRAP_KEY (line 2087) | const CYBER_BOOTSTRAP_KEY = 'cyber:threats-bootstrap:v2';
constant CYBER_MAX_CACHED (line 2088) | const CYBER_MAX_CACHED = 2000;
constant CYBER_GEO_MAX (line 2089) | const CYBER_GEO_MAX = 200;
constant CYBER_GEO_CONCURRENCY (line 2090) | const CYBER_GEO_CONCURRENCY = 12;
constant CYBER_GEO_TIMEOUT_MS (line 2091) | const CYBER_GEO_TIMEOUT_MS = 20_000;
constant CYBER_SOURCE_TIMEOUT_MS (line 2092) | const CYBER_SOURCE_TIMEOUT_MS = 15_000;
constant CYBER_COUNTRY_CENTROIDS (line 2094) | const CYBER_COUNTRY_CENTROIDS = {
constant CYBER_THREAT_TYPE_MAP (line 2116) | const CYBER_THREAT_TYPE_MAP = { c2_server:'CYBER_THREAT_TYPE_C2_SERVER',...
constant CYBER_SOURCE_MAP (line 2117) | const CYBER_SOURCE_MAP = { feodo:'CYBER_THREAT_SOURCE_FEODO', urlhaus:'C...
constant CYBER_INDICATOR_MAP (line 2118) | const CYBER_INDICATOR_MAP = { ip:'CYBER_THREAT_INDICATOR_TYPE_IP', domai...
constant CYBER_SEVERITY_MAP (line 2119) | const CYBER_SEVERITY_MAP = { low:'CRITICALITY_LEVEL_LOW', medium:'CRITIC...
constant CYBER_SEVERITY_RANK (line 2120) | const CYBER_SEVERITY_RANK = { CRITICALITY_LEVEL_CRITICAL:4, CRITICALITY_...
function cyberClean (line 2122) | function cyberClean(v, max) { if (typeof v !== 'string') return ''; retu...
function cyberToNum (line 2123) | function cyberToNum(v) { const n = typeof v === 'number' ? v : parseFloa...
function cyberValidCoords (line 2124) | function cyberValidCoords(lat, lon) { return lat !== null && lon !== nul...
function cyberIsIPv4 (line 2125) | function cyberIsIPv4(v) { if (!/^(\d{1,3}\.){3}\d{1,3}$/.test(v)) return...
function cyberIsIPv6 (line 2126) | function cyberIsIPv6(v) { return /^[0-9a-f:]+$/i.test(v) && v.includes('...
function cyberIsIp (line 2127) | function cyberIsIp(v) { return cyberIsIPv4(v) || cyberIsIPv6(v); }
function cyberNormCountry (line 2128) | function cyberNormCountry(v) { const r = cyberClean(String(v ?? ''), 64)...
function cyberToMs (line 2129) | function cyberToMs(v) {
function cyberNormTags (line 2136) | function cyberNormTags(input, max) {
function cyberDjb2 (line 2142) | function cyberDjb2(s) { let h = 5381; for (let i = 0; i < s.length; i++)...
function cyberCentroid (line 2143) | function cyberCentroid(cc, seed) {
function cyberSanitize (line 2148) | function cyberSanitize(t) {
function cyberDedupe (line 2153) | function cyberDedupe(threats) {
function cyberToProto (line 2163) | function cyberToProto(t) {
function cyberHttpGetJson (line 2167) | function cyberHttpGetJson(url, reqHeaders, timeoutMs) {
function cyberHttpGetText (line 2179) | function cyberHttpGetText(url, reqHeaders, timeoutMs) {
constant CYBER_GEO_CACHE_MAX (line 2192) | const CYBER_GEO_CACHE_MAX = 2048;
function cyberGeoCacheSet (line 2194) | function cyberGeoCacheSet(ip, geo) {
function cyberGeoLookup (line 2200) | async function cyberGeoLookup(ip) {
function cyberHydrateGeo (line 2215) | async function cyberHydrateGeo(threats) {
function cyberFetchFeodo (line 2249) | async function cyberFetchFeodo(limit, cutoffMs) {
function cyberFetchUrlhaus (line 2271) | async function cyberFetchUrlhaus(limit, cutoffMs) {
function cyberFetchC2Intel (line 2301) | async function cyberFetchC2Intel(limit) {
function cyberFetchOtx (line 2323) | async function cyberFetchOtx(limit, days) {
function cyberFetchAbuseIpDb (line 2340) | async function cyberFetchAbuseIpDb(limit) {
function seedCyberThreats (line 2357) | async function seedCyberThreats() {
function startCyberThreatsSeedLoop (line 2404) | async function startCyberThreatsSeedLoop() {
constant POSITIVE_EVENTS_INTERVAL_MS (line 2420) | const POSITIVE_EVENTS_INTERVAL_MS = 900_000;
constant POSITIVE_EVENTS_TTL (line 2421) | const POSITIVE_EVENTS_TTL = 2700;
constant POSITIVE_EVENTS_RPC_KEY (line 2422) | const POSITIVE_EVENTS_RPC_KEY = 'positive-events:geo:v1';
constant POSITIVE_EVENTS_BOOTSTRAP_KEY (line 2423) | const POSITIVE_EVENTS_BOOTSTRAP_KEY = 'positive_events:geo-bootstrap:v1';
constant POSITIVE_EVENTS_MAX (line 2424) | const POSITIVE_EVENTS_MAX = 500;
constant POSITIVE_QUERIES (line 2426) | const POSITIVE_QUERIES = [
constant POSITIVE_CATEGORY_KEYWORDS (line 2433) | const POSITIVE_CATEGORY_KEYWORDS = [
function classifyPositiveName (line 2456) | function classifyPositiveName(name) {
function fetchGdeltGeoPositive (line 2464) | function fetchGdeltGeoPositive(query) {
function seedPositiveEvents (line 2508) | async function seedPositiveEvents() {
function startPositiveEventsSeedLoop (line 2549) | async function startPositiveEventsSeedLoop() {
constant CLASSIFY_SEED_INTERVAL_MS (line 2565) | const CLASSIFY_SEED_INTERVAL_MS = 15 * 60 * 1000;
constant CLASSIFY_CACHE_TTL (line 2566) | const CLASSIFY_CACHE_TTL = 86400;
constant CLASSIFY_SKIP_TTL (line 2567) | const CLASSIFY_SKIP_TTL = 1800;
constant CLASSIFY_BATCH_SIZE (line 2568) | const CLASSIFY_BATCH_SIZE = 50;
constant CLASSIFY_VARIANTS (line 2569) | const CLASSIFY_VARIANTS = ['full', 'tech', 'finance', 'happy', 'commodit...
constant CLASSIFY_VARIANT_STAGGER_MS (line 2570) | const CLASSIFY_VARIANT_STAGGER_MS = 3 * 60 * 1000;
constant CLASSIFY_VALID_LEVELS (line 2572) | const CLASSIFY_VALID_LEVELS = ['critical', 'high', 'medium', 'low', 'inf...
constant CLASSIFY_VALID_CATEGORIES (line 2573) | const CLASSIFY_VALID_CATEGORIES = [
constant CLASSIFY_SYSTEM_PROMPT (line 2579) | const CLASSIFY_SYSTEM_PROMPT = `You classify news headlines by threat le...
function classifyCacheKey (line 2589) | function classifyCacheKey(title) {
constant CLASSIFY_LLM_PROVIDERS (line 2596) | const CLASSIFY_LLM_PROVIDERS = [
function classifyFetchLlmSingle (line 2629) | function classifyFetchLlmSingle(titles, _apiKey, apiUrl, model, headers,...
function classifyFetchLlm (line 2674) | async function classifyFetchLlm(titles) {
function seedClassifyForVariant (line 2694) | async function seedClassifyForVariant(variant) {
function seedClassify (line 2777) | async function seedClassify() {
function startClassifySeedLoop (line 2811) | async function startClassifySeedLoop() {
constant SERVICE_STATUSES_SEED_INTERVAL_MS (line 2828) | const SERVICE_STATUSES_SEED_INTERVAL_MS = 15 * 60 * 1000;
constant SERVICE_STATUSES_RPC_URL (line 2829) | const SERVICE_STATUSES_RPC_URL = 'https://api.worldmonitor.app/api/infra...
function seedServiceStatuses (line 2831) | async function seedServiceStatuses() {
function startServiceStatusesSeedLoop (line 2857) | function startServiceStatusesSeedLoop() {
constant THEATER_POSTURE_SEED_INTERVAL_MS (line 2871) | const THEATER_POSTURE_SEED_INTERVAL_MS = 600_000;
constant THEATER_POSTURE_LIVE_KEY (line 2872) | const THEATER_POSTURE_LIVE_KEY = 'theater-posture:sebuf:v1';
constant THEATER_POSTURE_STALE_KEY (line 2873) | const THEATER_POSTURE_STALE_KEY = 'theater_posture:sebuf:stale:v1';
constant THEATER_POSTURE_BACKUP_KEY (line 2874) | const THEATER_POSTURE_BACKUP_KEY = 'theater-posture:sebuf:backup:v1';
constant THEATER_POSTURE_LIVE_TTL (line 2875) | const THEATER_POSTURE_LIVE_TTL = 1200;
constant THEATER_POSTURE_STALE_TTL (line 2876) | const THEATER_POSTURE_STALE_TTL = 86400;
constant THEATER_POSTURE_BACKUP_TTL (line 2877) | const THEATER_POSTURE_BACKUP_TTL = 604800;
constant THEATER_MIL_PREFIXES (line 2879) | const THEATER_MIL_PREFIXES = [
constant THEATER_MIL_SHORT_PREFIXES (line 2896) | const THEATER_MIL_SHORT_PREFIXES = ['AE', 'RF', 'TF', 'PAT', 'SAM', 'OPS...
constant THEATER_AIRLINE_CODES (line 2897) | const THEATER_AIRLINE_CODES = new Set([
function theaterIsMilCallsign (line 2903) | function theaterIsMilCallsign(callsign) {
function theaterDetectAircraftType (line 2919) | function theaterDetectAircraftType(callsign) {
constant POSTURE_THEATERS (line 2932) | const POSTURE_THEATERS = [
constant THEATER_QUERY_REGIONS (line 2944) | const THEATER_QUERY_REGIONS = [
function handleWingbitsTrackRequest (line 2949) | async function handleWingbitsTrackRequest(req, res) {
function fetchTheaterFlightsFromOpenSky (line 3045) | async function fetchTheaterFlightsFromOpenSky() {
function fetchTheaterFlightsFromWingbits (line 3077) | async function fetchTheaterFlightsFromWingbits() {
function isStrictMilitaryVessel (line 3135) | function isStrictMilitaryVessel(v) {
function countMilitaryVesselsInBounds (line 3145) | function countMilitaryVesselsInBounds(bounds) {
function calculateTheaterPostures (line 3158) | function calculateTheaterPostures(flights) {
function seedTheaterPosture (line 3188) | async function seedTheaterPosture() {
function startTheaterPostureSeedLoop (line 3215) | function startTheaterPostureSeedLoop() {
constant CII_WARM_PING_INTERVAL_MS (line 3235) | const CII_WARM_PING_INTERVAL_MS = 8 * 60 * 1000;
constant CII_RPC_URL (line 3236) | const CII_RPC_URL = 'https://api.worldmonitor.app/api/intelligence/v1/ge...
function seedCiiWarmPing (line 3238) | async function seedCiiWarmPing() {
function startCiiWarmPingLoop (line 3262) | function startCiiWarmPingLoop() {
constant CHOKEPOINT_WARM_PING_INTERVAL_MS (line 3276) | const CHOKEPOINT_WARM_PING_INTERVAL_MS = 30 * 60 * 1000;
constant CHOKEPOINT_RPC_URL (line 3277) | const CHOKEPOINT_RPC_URL = 'https://api.worldmonitor.app/api/supply-chai...
function seedChokepointWarmPing (line 3279) | async function seedChokepointWarmPing() {
function startChokepointWarmPingLoop (line 3301) | function startChokepointWarmPingLoop() {
constant CABLE_HEALTH_WARM_PING_INTERVAL_MS (line 3314) | const CABLE_HEALTH_WARM_PING_INTERVAL_MS = 30 * 60 * 1000;
constant CABLE_HEALTH_RPC_URL (line 3315) | const CABLE_HEALTH_RPC_URL = 'https://api.worldmonitor.app/api/infrastru...
function seedCableHealthWarmPing (line 3317) | async function seedCableHealthWarmPing() {
function startCableHealthWarmPingLoop (line 3339) | function startCableHealthWarmPingLoop() {
constant WEATHER_SEED_INTERVAL_MS (line 3350) | const WEATHER_SEED_INTERVAL_MS = 15 * 60 * 1000;
constant WEATHER_REDIS_KEY (line 3351) | const WEATHER_REDIS_KEY = 'weather:alerts:v1';
constant WEATHER_CACHE_TTL (line 3352) | const WEATHER_CACHE_TTL = 5400;
function seedWeatherAlerts (line 3355) | async function seedWeatherAlerts() {
function startWeatherSeedLoop (line 3406) | async function startWeatherSeedLoop() {
constant SPENDING_SEED_INTERVAL_MS (line 3421) | const SPENDING_SEED_INTERVAL_MS = 60 * 60 * 1000;
constant SPENDING_REDIS_KEY (line 3422) | const SPENDING_REDIS_KEY = 'economic:spending:v1';
constant SPENDING_CACHE_TTL (line 3423) | const SPENDING_CACHE_TTL = 7200;
function getDateDaysAgo (line 3426) | function getDateDaysAgo(days) {
constant AWARD_TYPE_MAP (line 3432) | const AWARD_TYPE_MAP = {
function seedUsaSpending (line 3438) | async function seedUsaSpending() {
function startSpendingSeedLoop (line 3489) | async function startSpendingSeedLoop() {
constant TECH_EVENTS_SEED_INTERVAL_MS (line 3508) | const TECH_EVENTS_SEED_INTERVAL_MS = 6 * 60 * 60 * 1000;
constant TECH_EVENTS_TTL_SECONDS (line 3509) | const TECH_EVENTS_TTL_SECONDS = 86400;
constant TECH_EVENTS_REDIS_KEY (line 3510) | const TECH_EVENTS_REDIS_KEY = 'research:tech-events:v1';
constant TECH_EVENTS_BOOTSTRAP_KEY (line 3511) | const TECH_EVENTS_BOOTSTRAP_KEY = 'research:tech-events-bootstrap:v1';
constant TECH_EVENTS_ICS_URL (line 3512) | const TECH_EVENTS_ICS_URL = 'https://www.techmeme.com/newsy_events.ics';
constant TECH_EVENTS_RSS_URL (line 3513) | const TECH_EVENTS_RSS_URL = 'https://dev.events/rss.xml';
constant TECH_EVENTS_CURATED (line 3515) | const TECH_EVENTS_CURATED = [
function techEventsParseICS (line 3522) | function techEventsParseICS(icsText) {
function techEventsParseRSS (line 3556) | function techEventsParseRSS(rssText) {
function techEventsFetchUrl (line 3598) | function techEventsFetchUrl(url) {
function seedTechEvents (line 3627) | async function seedTechEvents() {
function startTechEventsSeedLoop (line 3698) | async function startTechEventsSeedLoop() {
constant WB_SEED_INTERVAL_MS (line 3714) | const WB_SEED_INTERVAL_MS = 24 * 60 * 60 * 1000;
constant WB_TTL_SECONDS (line 3715) | const WB_TTL_SECONDS = 7 * 24 * 3600;
constant WB_BOOTSTRAP_KEY (line 3716) | const WB_BOOTSTRAP_KEY = 'economic:worldbank-techreadiness:v1';
constant WB_PROGRESS_KEY (line 3717) | const WB_PROGRESS_KEY = 'economic:worldbank-progress:v1';
constant WB_RENEWABLE_KEY (line 3718) | const WB_RENEWABLE_KEY = 'economic:worldbank-renewable:v1';
constant WB_WEIGHTS (line 3720) | const WB_WEIGHTS = { internet: 30, mobile: 15, broadband: 20, rdSpend: 3...
constant WB_NORMALIZE_MAX (line 3721) | const WB_NORMALIZE_MAX = { internet: 100, mobile: 150, broadband: 50, rd...
constant WB_INDICATORS (line 3723) | const WB_INDICATORS = [
constant WB_PROGRESS_INDICATORS (line 3730) | const WB_PROGRESS_INDICATORS = [
constant WB_RENEWABLE_REGIONS (line 3737) | const WB_RENEWABLE_REGIONS = ['1W', 'EAS', 'ECS', 'LCN', 'MEA', 'NAC', '...
constant WB_RENEWABLE_REGION_NAMES (line 3738) | const WB_RENEWABLE_REGION_NAMES = {
function wbFetchJson (line 3744) | function wbFetchJson(url) {
function wbFetchIndicator (line 3765) | async function wbFetchIndicator(indicatorId, dateRange) {
function wbNormalize (line 3793) | function wbNormalize(val, max) {
function wbComputeRankings (line 3798) | function wbComputeRankings(indicatorData) {
function wbFetchProgress (line 3824) | async function wbFetchProgress() {
function wbFetchRenewable (line 3850) | async function wbFetchRenewable() {
function seedWorldBank (line 3888) | async function seedWorldBank() {
function startWorldBankSeedLoop (line 3960) | async function startWorldBankSeedLoop() {
constant PORTWATCH_ARCGIS_BASE (line 3972) | const PORTWATCH_ARCGIS_BASE = 'https://services9.arcgis.com/weJ1QsnbMYJl...
constant PORTWATCH_PAGE_SIZE (line 3973) | const PORTWATCH_PAGE_SIZE = 2000;
constant PORTWATCH_FETCH_TIMEOUT_MS (line 3974) | const PORTWATCH_FETCH_TIMEOUT_MS = 30000;
constant PORTWATCH_REDIS_KEY (line 3975) | const PORTWATCH_REDIS_KEY = 'supply_chain:portwatch:v1';
constant PORTWATCH_TTL (line 3976) | const PORTWATCH_TTL = 43200;
constant PORTWATCH_SEED_INTERVAL_MS (line 3977) | const PORTWATCH_SEED_INTERVAL_MS = 6 * 60 * 60 * 1000;
constant PORTWATCH_CHOKEPOINT_NAMES (line 3978) | const PORTWATCH_CHOKEPOINT_NAMES = [
function pwFormatDate (line 3996) | function pwFormatDate(ts) {
function pwComputeWowChangePct (line 4001) | function pwComputeWowChangePct(history) {
function pwEpochToTimestamp (line 4012) | function pwEpochToTimestamp(epochMs) {
function pwFetchAllPages (line 4018) | async function pwFetchAllPages(portname, sinceEpoch) {
function pwBuildHistory (line 4049) | function pwBuildHistory(features) {
function seedPortWatch (line 4062) | async function seedPortWatch() {
function startPortWatchSeedLoop (line 4096) | async function startPortWatchSeedLoop() {
constant CORRIDOR_RISK_BASE_URL (line 4108) | const CORRIDOR_RISK_BASE_URL = 'https://corridorrisk.io/api/corridors';
constant CORRIDOR_RISK_REDIS_KEY (line 4109) | const CORRIDOR_RISK_REDIS_KEY = 'supply_chain:corridorrisk:v1';
constant CORRIDOR_RISK_TTL (line 4110) | const CORRIDOR_RISK_TTL = 14400;
constant CORRIDOR_RISK_SEED_INTERVAL_MS (line 4111) | const CORRIDOR_RISK_SEED_INTERVAL_MS = 60 * 60 * 1000;
constant CORRIDOR_RISK_NAME_MAP (line 4113) | const CORRIDOR_RISK_NAME_MAP = [
function seedCorridorRisk (line 4124) | async function seedCorridorRisk() {
function startCorridorRiskSeedLoop (line 4187) | async function startCorridorRiskSeedLoop() {
constant USNI_URL (line 4203) | const USNI_URL = 'https://news.usni.org/wp-json/wp/v2/posts?categories=4...
constant USNI_REDIS_KEY (line 4204) | const USNI_REDIS_KEY = 'usni-fleet:sebuf:v1';
constant USNI_STALE_KEY (line 4205) | const USNI_STALE_KEY = 'usni-fleet:sebuf:stale:v1';
constant USNI_TTL (line 4206) | const USNI_TTL = 43200;
constant USNI_STALE_TTL (line 4207) | const USNI_STALE_TTL = 604800;
constant USNI_SEED_INTERVAL_MS (line 4208) | const USNI_SEED_INTERVAL_MS = 6 * 60 * 60 * 1000;
constant HULL_TYPE_MAP (line 4210) | const HULL_TYPE_MAP = {
constant USNI_REGION_COORDS (line 4223) | const USNI_REGION_COORDS = {
function usniStripHtml (line 4246) | function usniStripHtml(html) {
function usniHullToType (line 4253) | function usniHullToType(hull) {
function usniDetectStatus (line 4259) | function usniDetectStatus(text) {
function usniGetRegionCoords (line 4268) | function usniGetRegionCoords(regionText) {
function usniParseLeadingInt (line 4278) | function usniParseLeadingInt(text) {
function usniExtractBattleForceSummary (line 4283) | function usniExtractBattleForceSummary(tableHtml) {
function usniParseArticle (line 4301) | function usniParseArticle(html, articleUrl, articleDate, articleTitle) {
function seedUsniFleet (line 4378) | async function seedUsniFleet() {
function startUsniFleetSeedLoop (line 4428) | async function startUsniFleetSeedLoop() {
function gzipSyncBuffer (line 4441) | function gzipSyncBuffer(body) {
function brotliSyncBuffer (line 4449) | function brotliSyncBuffer(body) {
function getClientIp (line 4460) | function getClientIp(req, isPublic = false) {
function safeTokenEquals (line 4481) | function safeTokenEquals(provided, expected) {
function getRelaySecretFromRequest (line 4488) | function getRelaySecretFromRequest(req) {
function isAuthorizedRequest (line 4499) | function isAuthorizedRequest(req) {
function getRouteGroup (line 4506) | function getRouteGroup(pathname) {
function getRateLimitForPath (line 4521) | function getRateLimitForPath(pathname) {
function consumeRateLimit (line 4528) | function consumeRateLimit(req, pathname, isPublic = false) {
function logThrottled (line 4552) | function logThrottled(level, key, ...args) {
constant METRICS_WINDOW_SECONDS (line 4560) | const METRICS_WINDOW_SECONDS = Math.max(10, Number(process.env.RELAY_MET...
function createRelayMetricsBucket (line 4578) | function createRelayMetricsBucket() {
function getMetricsNowSec (line 4593) | function getMetricsNowSec() {
function pruneRelayMetricsBuckets (line 4597) | function pruneRelayMetricsBuckets(nowSec = getMetricsNowSec()) {
function getRelayMetricsBucket (line 4608) | function getRelayMetricsBucket(nowSec = getMetricsNowSec()) {
function incrementRelayMetric (line 4628) | function incrementRelayMetric(field, amount = 1) {
function sampleRelayQueueSize (line 4636) | function sampleRelayQueueSize(queueSize) {
function safeRatio (line 4642) | function safeRatio(numerator, denominator) {
function getRelayRollingMetrics (line 4647) | function getRelayRollingMetrics() {
constant GRID_SIZE (line 4706) | const GRID_SIZE = 2;
constant DENSITY_WINDOW (line 4707) | const DENSITY_WINDOW = 30 * 60 * 1000;
constant GAP_THRESHOLD (line 4708) | const GAP_THRESHOLD = 60 * 60 * 1000;
constant SNAPSHOT_INTERVAL_MS (line 4709) | const SNAPSHOT_INTERVAL_MS = Math.max(2000, Number(process.env.AIS_SNAPS...
constant CANDIDATE_RETENTION_MS (line 4710) | const CANDIDATE_RETENTION_MS = 2 * 60 * 60 * 1000;
constant MAX_DENSITY_ZONES (line 4711) | const MAX_DENSITY_ZONES = 200;
constant MAX_CANDIDATE_REPORTS (line 4712) | const MAX_CANDIDATE_REPORTS = 1500;
constant CHOKEPOINTS (line 4735) | const CHOKEPOINTS = [
function classifyVesselType (line 4753) | function classifyVesselType(shipType) {
constant TRANSIT_COOLDOWN_MS (line 4762) | const TRANSIT_COOLDOWN_MS = 30 * 60 * 1000;
constant TRANSIT_WINDOW_MS (line 4763) | const TRANSIT_WINDOW_MS = 24 * 60 * 60 * 1000;
constant MIN_DWELL_MS (line 4764) | const MIN_DWELL_MS = 5 * 60 * 1000;
constant CHOKEPOINT_TRANSIT_KEY (line 4765) | const CHOKEPOINT_TRANSIT_KEY = 'supply_chain:chokepoint_transits:v1';
constant CHOKEPOINT_TRANSIT_TTL (line 4766) | const CHOKEPOINT_TRANSIT_TTL = 3600;
constant CHOKEPOINT_TRANSIT_INTERVAL_MS (line 4767) | const CHOKEPOINT_TRANSIT_INTERVAL_MS = 10 * 60 * 1000;
constant NAVAL_PREFIX_RE (line 4769) | const NAVAL_PREFIX_RE = /^(USS|USNS|HMS|HMAS|HMCS|INS|JS|ROKS|TCG|FS|BNS...
function getGridKey (line 4771) | function getGridKey(lat, lon) {
function isLikelyMilitaryCandidate (line 4777) | function isLikelyMilitaryCandidate(meta) {
function getUpstreamQueueSize (line 4796) | function getUpstreamQueueSize() {
function enqueueUpstreamMessage (line 4800) | function enqueueUpstreamMessage(raw) {
function dequeueUpstreamMessage (line 4805) | function dequeueUpstreamMessage() {
function clearUpstreamQueue (line 4816) | function clearUpstreamQueue() {
function evictMapByTimestamp (line 4823) | function evictMapByTimestamp(map, maxSize, getTimestamp) {
function removeVesselFromChokepoints (line 4836) | function removeVesselFromChokepoints(mmsi) {
function updateVesselChokepoints (line 4850) | function updateVesselChokepoints(mmsi, lat, lon) {
function processRawUpstreamMessage (line 4903) | function processRawUpstreamMessage(raw) {
function processPositionReportForSnapshot (line 4934) | function processPositionReportForSnapshot(data) {
function cleanupAggregates (line 4998) | function cleanupAggregates() {
function detectDisruptions (line 5088) | function detectDisruptions() {
function calculateDensityZones (line 5150) | function calculateDensityZones() {
function getCandidateReportsSnapshot (line 5191) | function getCandidateReportsSnapshot() {
function buildSnapshot (line 5197) | function buildSnapshot() {
function seedChokepointTransits (line 5245) | async function seedChokepointTransits() {
constant TRANSIT_SUMMARY_REDIS_KEY (line 5273) | const TRANSIT_SUMMARY_REDIS_KEY = 'supply_chain:transit-summaries:v1';
constant TRANSIT_SUMMARY_TTL (line 5274) | const TRANSIT_SUMMARY_TTL = 3600;
constant TRANSIT_SUMMARY_INTERVAL_MS (line 5275) | const TRANSIT_SUMMARY_INTERVAL_MS = 10 * 60 * 1000;
constant CHOKEPOINT_THREAT_LEVELS (line 5281) | const CHOKEPOINT_THREAT_LEVELS = {
constant RELAY_NAME_TO_ID (line 5290) | const RELAY_NAME_TO_ID = {
function detectTrafficAnomalyRelay (line 5303) | function detectTrafficAnomalyRelay(history, threatLevel) {
function seedTransitSummaries (line 5316) | async function seedTransitSummaries() {
constant UCDP_CACHE_TTL_MS (line 5392) | const UCDP_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
constant UCDP_RELAY_MAX_PAGES (line 5393) | const UCDP_RELAY_MAX_PAGES = 12;
constant UCDP_FETCH_TIMEOUT (line 5394) | const UCDP_FETCH_TIMEOUT = 30000;
constant UCDP_RELAY_VIOLENCE_TYPE_MAP (line 5399) | const UCDP_RELAY_VIOLENCE_TYPE_MAP = {
function ucdpParseDateMs (line 5405) | function ucdpParseDateMs(value) {
function ucdpGetMaxDateMs (line 5410) | function ucdpGetMaxDateMs(events) {
function ucdpBuildVersionCandidates (line 5420) | function ucdpBuildVersionCandidates() {
function ucdpRelayFetchPage (line 5425) | async function ucdpRelayFetchPage(version, page) {
function ucdpRelayDiscoverVersion (line 5446) | async function ucdpRelayDiscoverVersion() {
function ucdpFetchAllEvents (line 5457) | async function ucdpFetchAllEvents() {
function handleUcdpEventsRequest (line 5517) | async function handleUcdpEventsRequest(req, res) {
constant OPENSKY_CACHE_TTL_MS (line 5578) | const OPENSKY_CACHE_TTL_MS = Number(process.env.OPENSKY_CACHE_TTL_MS) ||...
constant OPENSKY_NEGATIVE_CACHE_TTL_MS (line 5579) | const OPENSKY_NEGATIVE_CACHE_TTL_MS = Number(process.env.OPENSKY_NEGATIV...
constant OPENSKY_CACHE_MAX_ENTRIES (line 5580) | const OPENSKY_CACHE_MAX_ENTRIES = Math.max(10, Number(process.env.OPENSK...
constant OPENSKY_NEGATIVE_CACHE_MAX_ENTRIES (line 5581) | const OPENSKY_NEGATIVE_CACHE_MAX_ENTRIES = Math.max(10, Number(process.e...
constant OPENSKY_BBOX_QUANT_STEP (line 5582) | const OPENSKY_BBOX_QUANT_STEP = Number.isFinite(Number(process.env.OPENS...
constant OPENSKY_BBOX_DECIMALS (line 5584) | const OPENSKY_BBOX_DECIMALS = OPENSKY_BBOX_QUANT_STEP > 0
constant OPENSKY_DEDUP_EMPTY_RESPONSE_JSON (line 5587) | const OPENSKY_DEDUP_EMPTY_RESPONSE_JSON = JSON.stringify({ states: [], t...
constant OPENSKY_DEDUP_EMPTY_RESPONSE_GZIP (line 5588) | const OPENSKY_DEDUP_EMPTY_RESPONSE_GZIP = gzipSyncBuffer(OPENSKY_DEDUP_E...
constant OPENSKY_DEDUP_EMPTY_RESPONSE_BROTLI (line 5589) | const OPENSKY_DEDUP_EMPTY_RESPONSE_BROTLI = brotliSyncBuffer(OPENSKY_DED...
constant RSS_CACHE_TTL_MS (line 5594) | const RSS_CACHE_TTL_MS = 5 * 60 * 1000;
constant RSS_NEGATIVE_CACHE_TTL_MS (line 5595) | const RSS_NEGATIVE_CACHE_TTL_MS = 60 * 1000;
constant RSS_MAX_NEGATIVE_CACHE_TTL_MS (line 5596) | const RSS_MAX_NEGATIVE_CACHE_TTL_MS = 15 * 60 * 1000;
constant RSS_CACHE_MAX_ENTRIES (line 5597) | const RSS_CACHE_MAX_ENTRIES = 200;
function rssRecordFailure (line 5599) | function rssRecordFailure(feedUrl) {
function rssResetFailure (line 5607) | function rssResetFailure(feedUrl) {
function setBoundedCacheEntry (line 5612) | function setBoundedCacheEntry(cache, key, value, maxEntries) {
function touchCacheEntry (line 5620) | function touchCacheEntry(cache, key, entry) {
function cacheOpenSkyPositive (line 5625) | function cacheOpenSkyPositive(cacheKey, data) {
function cacheOpenSkyNegative (line 5634) | function cacheOpenSkyNegative(cacheKey, status) {
function quantizeCoordinate (line 5646) | function quantizeCoordinate(value) {
function formatCoordinate (line 5651) | function formatCoordinate(value) {
function normalizeOpenSkyBbox (line 5655) | function normalizeOpenSkyBbox(params) {
constant OPENSKY_AUTH_COOLDOWN_MS (line 5694) | const OPENSKY_AUTH_COOLDOWN_MS = 60000;
constant OPENSKY_429_COOLDOWN_MS (line 5698) | const OPENSKY_429_COOLDOWN_MS = Number(process.env.OPENSKY_429_COOLDOWN_...
constant OPENSKY_REQUEST_SPACING_MS (line 5699) | const OPENSKY_REQUEST_SPACING_MS = Number(process.env.OPENSKY_REQUEST_SP...
function getOpenSkyToken (line 5703) | async function getOpenSkyToken() {
function _openskyProxyConnect (line 5734) | function _openskyProxyConnect(targetHost, targetPort, timeoutMs = 10000) {
function _attemptOpenSkyTokenFetch (line 5773) | function _attemptOpenSkyTokenFetch(clientId, clientSecret) {
constant OPENSKY_AUTH_MAX_RETRIES (line 5855) | const OPENSKY_AUTH_MAX_RETRIES = 3;
constant OPENSKY_AUTH_RETRY_DELAYS (line 5856) | const OPENSKY_AUTH_RETRY_DELAYS = [0, 2000, 5000];
function _fetchOpenSkyToken (line 5858) | async function _fetchOpenSkyToken(clientId, clientSecret) {
function _collectDecompressed (line 5890) | function _collectDecompressed(response) {
function _openskyRawFetch (line 5904) | function _openskyRawFetch(url, token) {
function openskyQueuedFetch (line 5951) | function openskyQueuedFetch(url, token) {
function handleOpenSkyRequest (line 5968) | async function handleOpenSkyRequest(req, res, PORT) {
constant WORLDBANK_CACHE_TTL_MS (line 6154) | const WORLDBANK_CACHE_TTL_MS = 30 * 60 * 1000;
function handleWorldBankRequest (line 6156) | function handleWorldBankRequest(req, res) {
constant POLYMARKET_ENABLED (line 6354) | const POLYMARKET_ENABLED = String(process.env.POLYMARKET_ENABLED || 'tru...
constant POLYMARKET_CACHE_TTL_MS (line 6357) | const POLYMARKET_CACHE_TTL_MS = 10 * 60 * 1000;
constant POLYMARKET_NEG_TTL_MS (line 6358) | const POLYMARKET_NEG_TTL_MS = 5 * 60 * 1000;
constant POLYMARKET_CB_THRESHOLD (line 6362) | const POLYMARKET_CB_THRESHOLD = 5;
constant POLYMARKET_CB_COOLDOWN_MS (line 6363) | const POLYMARKET_CB_COOLDOWN_MS = 60 * 1000;
constant POLYMARKET_MAX_CONCURRENT (line 6366) | const POLYMARKET_MAX_CONCURRENT = 3;
constant POLYMARKET_MAX_QUEUED (line 6367) | const POLYMARKET_MAX_QUEUED = 20;
function tripPolymarketCircuitBreaker (line 6371) | function tripPolymarketCircuitBreaker() {
function releasePolymarketSlot (line 6379) | function releasePolymarketSlot() {
function acquirePolymarketSlot (line 6388) | function acquirePolymarketSlot() {
function fetchPolymarketUpstream (line 6399) | function fetchPolymarketUpstream(cacheKey, endpoint, params, tag) {
function handlePolymarketRequest (line 6455) | function handlePolymarketRequest(req, res) {
constant YAHOO_CHART_CACHE_TTL_MS (line 6587) | const YAHOO_CHART_CACHE_TTL_MS = 300_000;
constant YAHOO_SYMBOL_RE (line 6589) | const YAHOO_SYMBOL_RE = /^[A-Za-z0-9^=\-.]{1,15}$/;
function handleYahooChartRequest (line 6591) | function handleYahooChartRequest(req, res) {
constant AVIATIONSTACK_CACHE_TTL_MS (line 6667) | const AVIATIONSTACK_CACHE_TTL_MS = 120_000;
function handleAviationStackRequest (line 6669) | function handleAviationStackRequest(req, res) {
constant YOUTUBE_PROXY_URL (line 6745) | const YOUTUBE_PROXY_URL = process.env.YOUTUBE_PROXY_URL || '';
function parseProxyUrl (line 6747) | function parseProxyUrl(proxyUrl) {
function ytFetchViaProxy (line 6759) | function ytFetchViaProxy(targetUrl, proxy) {
function ytFetchDirect (line 6800) | function ytFetchDirect(targetUrl) {
function ytFetch (line 6831) | async function ytFetch(url) {
constant YT_CACHE_TTL (line 6840) | const YT_CACHE_TTL = 5 * 60 * 1000;
function handleYouTubeLiveRequest (line 6842) | function handleYouTubeLiveRequest(req, res) {
constant ICAO_API_KEY (line 6941) | const ICAO_API_KEY = process.env.ICAO_API_KEY;
constant NOTAM_CACHE_TTL (line 6943) | const NOTAM_CACHE_TTL = 30 * 60 * 1000;
function handleNotamProxyRequest (line 6945) | function handleNotamProxyRequest(req, res) {
constant ALLOWED_ORIGINS (line 7027) | const ALLOWED_ORIGINS = [
function getCorsOrigin (line 7038) | function getCorsOrigin(req) {
constant WIDGET_ALLOWED_ENDPOINTS (line 7604) | const WIDGET_ALLOWED_ENDPOINTS = new Set([
constant WIDGET_FETCH_TOOL (line 7621) | const WIDGET_FETCH_TOOL = {
constant WIDGET_SYSTEM_PROMPT (line 7634) | const WIDGET_SYSTEM_PROMPT = `You are a WorldMonitor widget builder. You...
constant WIDGET_SEARCH_TOOL (line 7758) | const WIDGET_SEARCH_TOOL = {
constant WIDGET_MAX_HTML (line 7770) | const WIDGET_MAX_HTML = 50_000;
constant WIDGET_PRO_MAX_HTML (line 7771) | const WIDGET_PRO_MAX_HTML = 80_000;
constant WIDGET_AGENT_KEY (line 7772) | const WIDGET_AGENT_KEY = (process.env.WIDGET_AGENT_KEY || '').trim();
constant PRO_WIDGET_KEY (line 7773) | const PRO_WIDGET_KEY = (process.env.PRO_WIDGET_KEY || '').trim();
constant WIDGET_ANTHROPIC_KEY (line 7774) | const WIDGET_ANTHROPIC_KEY = (process.env.ANTHROPIC_API_KEY || '').trim();
constant WIDGET_EXA_KEY (line 7775) | const WIDGET_EXA_KEY = (process.env.EXA_API_KEYS || '').split(/[\n,]+/)....
constant WIDGET_BRAVE_KEY (line 7776) | const WIDGET_BRAVE_KEY = (process.env.BRAVE_API_KEYS || '').split(/[\n,]...
function performWidgetWebSearch (line 7778) | async function performWidgetWebSearch(query) {
constant WIDGET_RATE_LIMIT (line 7837) | const WIDGET_RATE_LIMIT = 10;
constant PRO_WIDGET_RATE_LIMIT (line 7838) | const PRO_WIDGET_RATE_LIMIT = 20;
constant WIDGET_RATE_WINDOW_MS (line 7839) | const WIDGET_RATE_WINDOW_MS = 60 * 60 * 1000;
function checkWidgetRateLimit (line 7843) | function checkWidgetRateLimit(ip) {
function checkProWidgetRateLimit (line 7854) | function checkProWidgetRateLimit(ip) {
function getWidgetAgentStatus (line 7865) | function getWidgetAgentStatus() {
function getWidgetAgentProvidedProKey (line 7875) | function getWidgetAgentProvidedProKey(req) {
function getWidgetAgentProvidedKey (line 7881) | function getWidgetAgentProvidedKey(req) {
function requireWidgetAgentAccess (line 7887) | function requireWidgetAgentAccess(req, res) {
function sendWidgetSSE (line 7903) | function sendWidgetSSE(res, type, data) {
function readRequestBody (line 7909) | async function readRequestBody(req, maxBytes) {
function handleWidgetAgentHealthRequest (line 7923) | function handleWidgetAgentHealthRequest(req, res) {
function handleWidgetAgentRequest (line 7934) | async function handleWidgetAgentRequest(req, res) {
constant WIDGET_PRO_SYSTEM_PROMPT (line 8118) | const WIDGET_PRO_SYSTEM_PROMPT = `You are a WorldMonitor PRO widget buil...
function connectUpstream (line 8174) | function connectUpstream() {
function gracefulShutdown (line 8356) | async function gracefulShutdown(signal) {
FILE: scripts/build-military-bases-final.mjs
constant DATA_DIR (line 22) | const DATA_DIR = path.join(projectRoot, 'scripts', 'data');
constant PIZZINT_PATH (line 27) | const PIZZINT_PATH = path.join(DATA_DIR, 'pizzint-processed.json');
constant OSM_PATH (line 28) | const OSM_PATH = path.join(DATA_DIR, 'osm-military-processed.json');
constant MIRTA_PATH (line 29) | const MIRTA_PATH = path.join(DATA_DIR, 'mirta-processed.json');
constant CURATED_PATH (line 30) | const CURATED_PATH = path.join(DATA_DIR, 'curated-bases.json');
constant OUTPUT_PATH (line 31) | const OUTPUT_PATH = path.join(DATA_DIR, 'military-bases-final.json');
constant DEDUP_LOG_PATH (line 32) | const DEDUP_LOG_PATH = path.join(DATA_DIR, 'dedup-dropped-pairs.json');
constant PROXIMITY_THRESHOLD_M (line 37) | const PROXIMITY_THRESHOLD_M = 200;
constant EARTH_RADIUS_M (line 38) | const EARTH_RADIUS_M = 6_371_000;
constant NATO_MEMBERS (line 40) | const NATO_MEMBERS = new Set([
constant COUNTRY_TYPE_MAP (line 46) | const COUNTRY_TYPE_MAP = {
constant TIER1_KINDS (line 59) | const TIER1_KINDS = new Set([
constant TIER2_KINDS (line 63) | const TIER2_KINDS = new Set([
constant TIER3_KINDS (line 67) | const TIER3_KINDS = new Set([
function loadJson (line 75) | function loadJson(filepath, label) {
function stripHtml (line 86) | function stripHtml(str) {
function toRad (line 91) | function toRad(deg) {
function haversineMeters (line 95) | function haversineMeters(lat1, lon1, lat2, lon2) {
function assignType (line 104) | function assignType(countryIso2) {
function assignTier (line 113) | function assignTier(kind, source) {
function osmElementType (line 123) | function osmElementType(osmId) {
function nameMatch (line 130) | function nameMatch(a, b) {
function deriveCategoriesFromKind (line 135) | function deriveCategoriesFromKind(kind, name) {
function normalizePizzintEntry (line 147) | function normalizePizzintEntry(row) {
function normalizeOsmEntry (line 169) | function normalizeOsmEntry(row) {
function normalizeMirtaEntry (line 192) | function normalizeMirtaEntry(row) {
function normalizeCuratedEntry (line 214) | function normalizeCuratedEntry(row) {
function main (line 240) | function main() {
FILE: scripts/build-sidecar-handlers.mjs
constant ROOT (line 15) | const ROOT = path.resolve(__dirname, '..');
constant SKIP_DIRS (line 19) | const SKIP_DIRS = new Set(['[domain]', '[[...path]]']);
FILE: scripts/check-unicode-safety.mjs
constant ROOT (line 23) | const ROOT = process.cwd();
constant SCAN_ROOTS (line 25) | const SCAN_ROOTS = [
constant INCLUDED_EXTENSIONS (line 36) | const INCLUDED_EXTENSIONS = new Set([
constant EXCLUDED_PREFIXES (line 42) | const EXCLUDED_PREFIXES = [
constant ZERO_WIDTH (line 54) | const ZERO_WIDTH = new Set([0x200B, 0x200C, 0x200D, 0x2060, 0xFEFF]);
function isBidiControl (line 56) | function isBidiControl(cp) {
function isVariationSelectorSupplement (line 60) | function isVariationSelectorSupplement(cp) {
function isVariationSelectorSuspicious (line 64) | function isVariationSelectorSuspicious(cp) {
function getExtension (line 75) | function getExtension(path) {
function shouldScanFile (line 80) | function shouldScanFile(path) {
function walkDir (line 87) | function walkDir(rootDir, out) {
function getRepoFiles (line 108) | function getRepoFiles() {
function getStagedFiles (line 121) | function getStagedFiles() {
function formatCodePoint (line 139) | function formatCodePoint(cp) {
function classify (line 143) | function classify(cp) {
function scanFile (line 151) | function scanFile(path) {
function main (line 193) | function main() {
FILE: scripts/evaluate-forecast-benchmark.mjs
function materializeForecast (line 23) | function materializeForecast(input) {
function evaluateEntry (line 41) | function evaluateEntry(entry) {
FILE: scripts/extract-forecast-benchmark-candidates.mjs
constant NOISE_SIGNAL_TYPES (line 9) | const NOISE_SIGNAL_TYPES = new Set(['news_corroboration']);
function slugify (line 11) | function slugify(value) {
function toBenchmarkForecast (line 19) | function toBenchmarkForecast(entry) {
function summarizeObservedChange (line 35) | function summarizeObservedChange(current, prior) {
function buildBenchmarkCandidate (line 61) | function buildBenchmarkCandidate(current, prior, snapshotAt) {
function scoreCandidate (line 74) | function scoreCandidate(candidate) {
function selectBenchmarkCandidates (line 87) | function selectBenchmarkCandidates(historySnapshots, options = {}) {
function readForecastHistory (line 119) | async function readForecastHistory(key = HISTORY_KEY, limit = 60) {
FILE: scripts/fetch-country-boundary-overrides.mjs
constant NE_50M_URL (line 20) | const NE_50M_URL = 'https://raw.githubusercontent.com/nvkelso/natural-ea...
constant OUT_DIR (line 21) | const OUT_DIR = join(__dirname, '..', 'public', 'data');
constant OUT_FILE (line 22) | const OUT_FILE = join(OUT_DIR, 'country-boundary-overrides.geojson');
constant OVERRIDE_COUNTRIES (line 25) | const OVERRIDE_COUNTRIES = [
function main (line 30) | async function main() {
FILE: scripts/fetch-gpsjam.mjs
constant DATA_DIR (line 8) | const DATA_DIR = path.resolve(__dirname, 'data');
constant REDIS_KEY_V2 (line 10) | const REDIS_KEY_V2 = 'intelligence:gpsjam:v2';
constant REDIS_KEY_V1 (line 11) | const REDIS_KEY_V1 = 'intelligence:gpsjam:v1';
constant REDIS_TTL (line 12) | const REDIS_TTL = 172800;
function getArg (line 15) | function getArg(name, fallback) {
function classifyRegion (line 22) | function classifyRegion(lat, lon) {
function loadEnvFile (line 41) | function loadEnvFile() {
function maskToken (line 59) | function maskToken(token) {
function fetchWingbits (line 64) | async function fetchWingbits(apiKey) {
function processHexes (line 89) | function processHexes(rawHexes) {
function seedRedis (line 159) | async function seedRedis(output) {
function main (line 247) | async function main() {
FILE: scripts/fetch-mirta-bases.mjs
constant DATA_DIR (line 19) | const DATA_DIR = resolve(__dirname, 'data');
constant BASE (line 22) | const BASE = 'https://services7.arcgis.com/n1YM8pTrFmm7L4hs/arcgis/rest/...
constant PAGE_SIZE (line 23) | const PAGE_SIZE = 1000;
constant BRANCH_MAP (line 28) | const BRANCH_MAP = {
constant COMPONENT_MAP (line 43) | const COMPONENT_MAP = {
constant STATUS_MAP (line 58) | const STATUS_MAP = {
constant STATE_MAP (line 66) | const STATE_MAP = {
function fetchAllFeatures (line 85) | async function fetchAllFeatures(layerIndex) {
function centroid (line 124) | function centroid(geometry) {
function processFeature (line 156) | function processFeature(feature) {
function main (line 181) | async function main() {
FILE: scripts/fetch-osm-bases.mjs
constant DATA_DIR (line 8) | const DATA_DIR = join(__dirname, 'data');
constant RAW_PATH (line 9) | const RAW_PATH = join(DATA_DIR, 'osm-military-raw.json');
constant PROCESSED_PATH (line 10) | const PROCESSED_PATH = join(DATA_DIR, 'osm-military-processed.json');
constant OVERPASS_URL (line 12) | const OVERPASS_URL = 'https://overpass-api.de/api/interpreter';
constant OVERPASS_QUERY (line 13) | const OVERPASS_QUERY = `
constant TIMEOUT_MS (line 23) | const TIMEOUT_MS = 5 * 60 * 1000;
function ensureDataDir (line 25) | function ensureDataDir() {
function fetchOverpassData (line 32) | async function fetchOverpassData() {
function processFeatures (line 66) | function processFeatures(raw) {
function printSummary (line 110) | function printSummary(features) {
function main (line 136) | async function main() {
FILE: scripts/fetch-pizzint-bases.mjs
function loadEnvLocal (line 23) | function loadEnvLocal() {
constant SUPABASE_URL (line 47) | const SUPABASE_URL = process.env.SUPABASE_URL || 'https://qevdnlpgjxpwus...
constant SUPABASE_ANON_KEY (line 48) | const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY;
constant PAGE_SIZE (line 56) | const PAGE_SIZE = 1000;
constant TOTAL_PAGES (line 57) | const TOTAL_PAGES = 80;
constant RATE_LIMIT_MS (line 58) | const RATE_LIMIT_MS = 200;
constant MAX_RETRIES (line 59) | const MAX_RETRIES = 3;
constant CHECKPOINT_INTERVAL (line 60) | const CHECKPOINT_INTERVAL = 10;
constant MIN_EXPECTED_ROWS (line 61) | const MIN_EXPECTED_ROWS = 79_000;
constant SELECT_COLUMNS (line 63) | const SELECT_COLUMNS = [
constant DATA_DIR (line 70) | const DATA_DIR = path.join(projectRoot, 'scripts', 'data');
constant PARTIAL_PATH (line 71) | const PARTIAL_PATH = path.join(DATA_DIR, 'pizzint-partial.json');
constant OUTPUT_PATH (line 72) | const OUTPUT_PATH = path.join(DATA_DIR, 'pizzint-processed.json');
function sleep (line 77) | function sleep(ms) {
function ensureDataDir (line 81) | function ensureDataDir() {
function loadCheckpoint (line 85) | function loadCheckpoint() {
function saveCheckpoint (line 95) | function saveCheckpoint(state) {
function fetchPage (line 103) | async function fetchPage(pageIndex) {
function main (line 140) | async function main() {
FILE: scripts/generate-oref-locations.mjs
constant COMMIT_SHA (line 10) | const COMMIT_SHA = 'master';
constant CITIES_URL (line 11) | const CITIES_URL = `https://raw.githubusercontent.com/eladnava/pikud-hao...
constant OUTPUT (line 14) | const OUTPUT = join(__dirname, '..', 'src', 'services', 'oref-locations....
function normalize (line 16) | function normalize(s) {
function main (line 20) | async function main() {
FILE: scripts/lib/thermal-escalation.mjs
constant CLUSTER_RADIUS_KM (line 1) | const CLUSTER_RADIUS_KM = 20;
constant HISTORY_RETENTION_MS (line 2) | const HISTORY_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
constant RECENT_PERSISTENCE_MS (line 3) | const RECENT_PERSISTENCE_MS = 18 * 60 * 60 * 1000;
constant BASELINE_WINDOW_MS (line 4) | const BASELINE_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
constant OBSERVATION_WINDOW_HOURS (line 5) | const OBSERVATION_WINDOW_HOURS = 24;
constant CONFLICT_REGIONS (line 7) | const CONFLICT_REGIONS = new Set([
constant REGION_TO_COUNTRY (line 30) | const REGION_TO_COUNTRY = {
function round (line 55) | function round(value, digits = 1) {
function toRad (line 60) | function toRad(value) {
function haversineKm (line 64) | function haversineKm(a, b) {
function sortDetections (line 77) | function sortDetections(detections) {
function clusterDetections (line 81) | function clusterDetections(detections, radiusKm = CLUSTER_RADIUS_KM) {
function cellKey (line 119) | function cellKey(location) {
function average (line 125) | function average(values) {
function stdDev (line 129) | function stdDev(values, mean) {
function severityRank (line 135) | function severityRank(status) {
function relevanceRank (line 148) | function relevanceRank(relevance) {
function deriveContext (line 159) | function deriveContext(regionLabel) {
function deriveCountry (line 164) | function deriveCountry(regionLabel) {
function deriveConfidence (line 168) | function deriveConfidence(observationCount, uniqueSourceCount, baselineS...
function deriveStatus (line 174) | function deriveStatus({ observationCount, totalFrp, countDelta, frpDelta...
function deriveRelevance (line 185) | function deriveRelevance(status, context, totalFrp, persistenceHours) {
function buildNarrativeFlags (line 202) | function buildNarrativeFlags({ context, status, uniqueSourceCount, persi...
function buildSummary (line 214) | function buildSummary(clusters) {
function computeThermalEscalationWatch (line 225) | function computeThermalEscalationWatch(detections, previousHistory = { c...
function emptyThermalEscalationWatch (line 372) | function emptyThermalEscalationWatch(nowMs = 0, sourceVersion = 'thermal...
FILE: scripts/lint-boundaries.mjs
constant SRC (line 22) | const SRC = 'src';
constant ROOT (line 23) | const ROOT = process.cwd();
constant LAYERS (line 26) | const LAYERS = ['types', 'config', 'services', 'components', 'app'];
function getLayer (line 28) | function getLayer(filePath) {
function getLayerIndex (line 36) | function getLayerIndex(layer) {
function walkDir (line 40) | function walkDir(dir, ext = ['.ts', '.tsx', '.js', '.mjs']) {
FILE: scripts/promote-forecast-benchmark-candidate.mjs
constant DEFAULT_OUTPUT_PATH (line 26) | const DEFAULT_OUTPUT_PATH = join(__dirname, 'data', 'forecast-historical...
function roundPct (line 30) | function roundPct(value) {
function materializeForecast (line 34) | function materializeForecast(input) {
function buildSummaryExpectation (line 52) | function buildSummaryExpectation(pred, priorForecast) {
function buildItemExpectations (line 62) | function buildItemExpectations(pred) {
function deriveThresholds (line 68) | function deriveThresholds(candidate, options = {}) {
function toHistoricalBenchmarkEntry (line 100) | function toHistoricalBenchmarkEntry(candidate, options = {}) {
function mergeHistoricalBenchmarks (line 111) | function mergeHistoricalBenchmarks(existingEntries, nextEntry, options =...
function createJsonPatch (line 134) | function createJsonPatch(existingEntries, nextEntry, options = {}) {
function renderUnifiedDiff (line 145) | function renderUnifiedDiff(currentEntries, nextEntries, outputPath) {
function parseArgs (line 173) | function parseArgs(argv) {
function pickCandidate (line 199) | function pickCandidate(candidates, options = {}) {
function readBenchmarkFile (line 212) | function readBenchmarkFile(pathname) {
function buildPreviewPayload (line 216) | function buildPreviewPayload(args, candidate, nextEntry, currentEntries) {
FILE: scripts/railway-set-watch-paths.mjs
constant DRY_RUN (line 20) | const DRY_RUN = process.argv.includes('--dry-run');
constant PROJECT_ID (line 22) | const PROJECT_ID = '29419572-0b0d-437f-8e71-4fa68daf514f';
constant ENV_ID (line 23) | const ENV_ID = '91a05726-0b83-4d44-a33e-6aec94e58780';
constant API (line 24) | const API = 'https://backboard.railway.app/graphql/v2';
constant USES_SHARED_CONFIG (line 27) | const USES_SHARED_CONFIG = new Set([
function getToken (line 32) | function getToken() {
function gql (line 42) | async function gql(token, query, variables = {}) {
function main (line 53) | async function main() {
FILE: scripts/seed-airport-delays.mjs
constant FAA_CACHE_KEY (line 7) | const FAA_CACHE_KEY = 'aviation:delays:faa:v1';
constant NOTAM_CACHE_KEY (line 8) | const NOTAM_CACHE_KEY = 'aviation:notam:closures:v2';
constant CACHE_TTL (line 9) | const CACHE_TTL = 7200;
constant FAA_URL (line 11) | const FAA_URL = 'https://nasstatus.faa.gov/api/airport-status-information';
constant ICAO_NOTAM_URL (line 12) | const ICAO_NOTAM_URL = 'https://dataservices.icao.int/api/notams-realtim...
constant NOTAM_CLOSURE_QCODES (line 14) | const NOTAM_CLOSURE_QCODES = new Set(['FA', 'AH', 'AL', 'AW', 'AC', 'AM']);
constant FAA_AIRPORTS (line 16) | const FAA_AIRPORTS = [
constant MONITORED_AIRPORTS_ICAO (line 22) | const MONITORED_AIRPORTS_ICAO = [
function parseDelayTypeFromReason (line 41) | function parseDelayTypeFromReason(reason) {
function parseFaaXml (line 51) | function parseFaaXml(text) {
function determineSeverity (line 121) | function determineSeverity(avgDelay) {
function redisSet (line 129) | async function redisSet(url, token, key, value, ttl) {
function seedFaaDelays (line 141) | async function seedFaaDelays() {
function seedNotamClosures (line 185) | async function seedNotamClosures() {
function main (line 254) | async function main() {
FILE: scripts/seed-aviation.mjs
constant DEFAULT_AIRPORTS (line 19) | const DEFAULT_AIRPORTS = ['IST', 'ESB', 'SAW', 'LHR', 'FRA', 'CDG'];
constant OPS_CACHE_KEY (line 20) | const OPS_CACHE_KEY = `aviation:ops-summary:v1:${[...DEFAULT_AIRPORTS].s...
constant NEWS_CACHE_KEY (line 21) | const NEWS_CACHE_KEY = 'aviation:news::24:v1';
constant OPS_TTL (line 22) | const OPS_TTL = 300;
constant NEWS_TTL (line 23) | const NEWS_TTL = 900;
constant AVIATIONSTACK_URL (line 25) | const AVIATIONSTACK_URL = 'https://api.aviationstack.com/v1/flights';
function fetchAviationStackFlights (line 29) | async function fetchAviationStackFlights(airports) {
function fetchNotamClosures (line 68) | async function fetchNotamClosures() {
function getRedisCredentialsFromEnv (line 83) | function getRedisCredentialsFromEnv() {
function determineSeverity (line 90) | function determineSeverity(avgDelay, delayPct) {
function severityFromCancelRate (line 98) | function severityFromCancelRate(rate) {
function fetchAirportOpsSummary (line 106) | async function fetchAirportOpsSummary() {
constant AVIATION_RSS_FEEDS (line 170) | const AVIATION_RSS_FEEDS = [
function parseRssItems (line 182) | function parseRssItems(xml, sourceName) {
function fetchAviationNews (line 202) | async function fetchAviationNews() {
function fetchAll (line 243) | async function fetchAll() {
function validate (line 263) | function validate(data) {
FILE: scripts/seed-bis-data.mjs
constant BIS_BASE (line 7) | const BIS_BASE = 'https://stats.bis.org/api/v1/data';
constant BIS_COUNTRIES (line 9) | const BIS_COUNTRIES = {
constant BIS_COUNTRY_KEYS (line 24) | const BIS_COUNTRY_KEYS = Object.keys(BIS_COUNTRIES).join('+');
constant KEYS (line 26) | const KEYS = {
constant TTL (line 32) | const TTL = 43200;
function fetchBisCSV (line 34) | async function fetchBisCSV(dataset, key) {
function parseBisCSV (line 45) | function parseBisCSV(csv) {
function parseCSVLine (line 63) | function parseCSVLine(line) {
function parseBisNumber (line 83) | function parseBisNumber(val) {
function groupByCountry (line 89) | function groupByCountry(rows) {
function fetchPolicyRates (line 103) | async function fetchPolicyRates() {
function fetchExchangeRates (line 131) | async function fetchExchangeRates() {
function fetchCreditToGdp (line 162) | async function fetchCreditToGdp() {
function fetchAll (line 193) | async function fetchAll() {
function validate (line 205) | function validate(data) {
FILE: scripts/seed-climate-anomalies.mjs
constant CANONICAL_KEY (line 7) | const CANONICAL_KEY = 'climate:anomalies:v1';
constant CACHE_TTL (line 8) | const CACHE_TTL = 10800;
constant ZONES (line 10) | const ZONES = [
function avg (line 28) | function avg(arr) {
function classifySeverity (line 32) | function classifySeverity(tempDelta, precipDelta) {
function classifyType (line 40) | function classifyType(tempDelta, precipDelta) {
function fetchZone (line 54) | async function fetchZone(zone, startDate, endDate) {
function fetchClimateAnomalies (line 97) | async function fetchClimateAnomalies() {
function validate (line 117) | function validate(data) {
FILE: scripts/seed-commodity-quotes.mjs
constant CANONICAL_KEY (line 9) | const CANONICAL_KEY = 'market:commodities-bootstrap:v1';
constant CACHE_TTL (line 10) | const CACHE_TTL = 1800;
constant YAHOO_DELAY_MS (line 11) | const YAHOO_DELAY_MS = 200;
function fetchYahooWithRetry (line 13) | async function fetchYahooWithRetry(url, label, maxAttempts = 4) {
constant COMMODITY_SYMBOLS (line 35) | const COMMODITY_SYMBOLS = commodityConfig.commodities.map(c => c.symbol);
function fetchCommodityQuotes (line 37) | async function fetchCommodityQuotes() {
function validate (line 72) | function validate(data) {
function fetchAndStash (line 78) | async function fetchAndStash() {
FILE: scripts/seed-conflict-intel.mjs
constant ACLED_CACHE_KEY (line 23) | const ACLED_CACHE_KEY = 'conflict:acled:v1:all:0:0';
constant ACLED_TTL (line 24) | const ACLED_TTL = 900;
constant HAPI_CACHE_KEY_PREFIX (line 25) | const HAPI_CACHE_KEY_PREFIX = 'conflict:humanitarian:v1';
constant HAPI_TTL (line 26) | const HAPI_TTL = 21600;
constant PIZZINT_TTL (line 27) | const PIZZINT_TTL = 600;
constant CONFLICT_COUNTRIES (line 30) | const CONFLICT_COUNTRIES = [
constant ISO2_TO_ISO3 (line 35) | const ISO2_TO_ISO3 = {
function fetchAcledToken (line 44) | async function fetchAcledToken() {
function fetchAcledEvents (line 71) | async function fetchAcledEvents() {
function fetchHapiSummary (line 120) | async function fetchHapiSummary(countryCode) {
function fetchAllHumanitarianSummaries (line 167) | async function fetchAllHumanitarianSummaries() {
function fetchPizzintStatus (line 184) | async function fetchPizzintStatus() {
function fetchGdeltTensions (line 226) | async function fetchGdeltTensions() {
function fetchAll (line 250) | async function fetchAll() {
function validate (line 278) | function validate(data) {
FILE: scripts/seed-correlation.mjs
constant CANONICAL_KEY (line 7) | const CANONICAL_KEY = 'correlation:cards-bootstrap:v1';
constant CACHE_TTL (line 8) | const CACHE_TTL = 1200;
constant INPUT_KEYS (line 10) | const INPUT_KEYS = [
function fetchInputData (line 22) | async function fetchInputData() {
function haversineKm (line 44) | function haversineKm(lat1, lon1, lat2, lon2) {
constant COUNTRY_NAME_TO_ISO2 (line 57) | const COUNTRY_NAME_TO_ISO2 = {
constant ISO3_TO_ISO2 (line 104) | const ISO3_TO_ISO2 = {
constant COUNTRY_CENTROIDS (line 137) | const COUNTRY_CENTROIDS = {
function nearestCountryByCoords (line 172) | function nearestCountryByCoords(lat, lon) {
function normalizeToCode (line 182) | function normalizeToCode(country, lat, lon) {
constant COUNTRY_NAME_ENTRIES (line 193) | const COUNTRY_NAME_ENTRIES = Object.entries(COUNTRY_NAME_TO_ISO2)
function matchCountryNamesInText (line 198) | function matchCountryNamesInText(text) {
constant STRIKE_TYPES (line 211) | const STRIKE_TYPES = new Set(['fighter', 'bomber', 'attack']);
constant SUPPORT_TYPES (line 212) | const SUPPORT_TYPES = new Set(['tanker', 'awacs', 'surveillance', 'elect...
function collectMilitarySignals (line 214) | function collectMilitarySignals(flights) {
function generateMilitaryTitle (line 239) | function generateMilitaryTitle(cluster) {
constant ESCALATION_KEYWORDS (line 254) | const ESCALATION_KEYWORDS = /\b((?:military|armed|air)\s*(?:strike|attac...
function collectEscalationSignals (line 256) | function collectEscalationSignals(protests, outages, newsClusters) {
function generateEscalationTitle (line 329) | function generateEscalationTitle(cluster) {
constant SANCTIONS_KEYWORDS (line 343) | const SANCTIONS_KEYWORDS = /\b(sanction|tariff|embargo|trade\s+war|ban|r...
constant COMMODITY_SYMBOLS (line 344) | const COMMODITY_SYMBOLS = new Set(['CL=F', 'GC=F', 'NG=F', 'SI=F', 'HG=F...
constant SIGNIFICANT_CHANGE_PCT (line 345) | const SIGNIFICANT_CHANGE_PCT = 1.5;
function collectEconomicSignals (line 347) | function collectEconomicSignals(markets, newsClusters) {
constant KNOWN_ENTITIES (line 387) | const KNOWN_ENTITIES = /\b(Iran|Russia|China|North Korea|Venezuela|Cuba|...
constant GENERIC_ENTITY_KEYS (line 388) | const GENERIC_ENTITY_KEYS = new Set([
function generateEconomicTitle (line 393) | function generateEconomicTitle(cluster, entityKey) {
function collectDisasterSignals (line 437) | function collectDisasterSignals(earthquakes, outages, protests) {
function generateDisasterTitle (line 492) | function generateDisasterTitle(cluster) {
function clusterByProximity (line 507) | function clusterByProximity(signals, radiusKm) {
function clusterByCountry (line 566) | function clusterByCountry(signals) {
function clusterByEntity (line 582) | function clusterByEntity(signals) {
function scoreClusters (line 615) | function scoreClusters(clusters, weights, threshold) {
function toCard (line 652) | function toCard(scored, domain, titleFn) {
constant DOMAINS (line 683) | const DOMAINS = {
function computeCorrelation (line 713) | async function computeCorrelation() {
FILE: scripts/seed-crypto-quotes.mjs
constant CANONICAL_KEY (line 9) | const CANONICAL_KEY = 'market:crypto:v1';
constant CACHE_TTL (line 10) | const CACHE_TTL = 3600;
constant CRYPTO_IDS (line 12) | const CRYPTO_IDS = cryptoConfig.ids;
constant CRYPTO_META (line 13) | const CRYPTO_META = cryptoConfig.meta;
function fetchWithRateLimitRetry (line 15) | async function fetchWithRateLimitRetry(url, maxAttempts = 5, headers = {...
constant COINPAPRIKA_ID_MAP (line 33) | const COINPAPRIKA_ID_MAP = cryptoConfig.coinpaprika;
function fetchFromCoinGecko (line 35) | async function fetchFromCoinGecko() {
function fetchFromCoinPaprika (line 53) | async function fetchFromCoinPaprika() {
function fetchCryptoQuotes (line 75) | async function fetchCryptoQuotes() {
function validate (line 110) | function validate(data) {
FILE: scripts/seed-cyber-threats.mjs
constant ABUSEIPDB_RATE_KEY (line 7) | const ABUSEIPDB_RATE_KEY = 'rate:abuseipdb:last-call';
constant ABUSEIPDB_CACHE_KEY (line 8) | const ABUSEIPDB_CACHE_KEY = 'cache:abuseipdb:threats';
constant ABUSEIPDB_MIN_INTERVAL_MS (line 9) | const ABUSEIPDB_MIN_INTERVAL_MS = 2 * 60 * 60 * 1000;
constant CANONICAL_KEY (line 11) | const CANONICAL_KEY = 'cyber:threats:v2';
constant BOOTSTRAP_KEY (line 12) | const BOOTSTRAP_KEY = 'cyber:threats-bootstrap:v2';
constant CACHE_TTL (line 13) | const CACHE_TTL = 10800;
constant FEODO_URL (line 15) | const FEODO_URL = 'https://feodotracker.abuse.ch/downloads/ipblocklist.j...
constant C2INTEL_URL (line 17) | const C2INTEL_URL = 'https://raw.githubusercontent.com/drb-ra/C2IntelFee...
constant OTX_INDICATORS_URL (line 18) | const OTX_INDICATORS_URL = 'https://otx.alienvault.com/api/v1/indicators...
constant ABUSEIPDB_BLACKLIST_URL (line 19) | const ABUSEIPDB_BLACKLIST_URL = 'https://api.abuseipdb.com/api/v2/blackl...
constant UPSTREAM_TIMEOUT_MS (line 21) | const UPSTREAM_TIMEOUT_MS = 10_000;
constant MAX_LIMIT (line 22) | const MAX_LIMIT = 1000;
constant DEFAULT_DAYS (line 23) | const DEFAULT_DAYS = 14;
constant MAX_CACHED_THREATS (line 24) | const MAX_CACHED_THREATS = 2000;
constant GEO_MAX_UNRESOLVED (line 25) | const GEO_MAX_UNRESOLVED = 200;
constant GEO_CONCURRENCY (line 26) | const GEO_CONCURRENCY = 12;
constant GEO_OVERALL_TIMEOUT_MS (line 27) | const GEO_OVERALL_TIMEOUT_MS = 15_000;
constant GEO_PER_IP_TIMEOUT_MS (line 28) | const GEO_PER_IP_TIMEOUT_MS = 2000;
constant THREAT_TYPE_MAP (line 30) | const THREAT_TYPE_MAP = {
constant SOURCE_MAP (line 37) | const SOURCE_MAP = {
constant INDICATOR_TYPE_MAP (line 45) | const INDICATOR_TYPE_MAP = {
constant SEVERITY_MAP (line 51) | const SEVERITY_MAP = {
constant SEVERITY_RANK (line 58) | const SEVERITY_RANK = {
constant COUNTRY_CENTROIDS (line 66) | const COUNTRY_CENTROIDS = {
function clean (line 92) | function clean(value, maxLen = 120) {
function toNum (line 97) | function toNum(value) {
function validCoords (line 102) | function validCoords(lat, lon) {
function isIPv4 (line 106) | function isIPv4(v) {
function isIPv6 (line 111) | function isIPv6(v) { return /^[0-9a-f:]+$/i.test(v) && v.includes(':'); }
function isIp (line 113) | function isIp(v) {
function normCountry (line 118) | function normCountry(v) {
function toEpochMs (line 124) | function toEpochMs(v) {
function normTags (line 135) | function normTags(input, max = 8) {
function djb2 (line 149) | function djb2(s) {
function countryCentroid (line 155) | function countryCentroid(cc, seed) {
function sanitize (line 165) | function sanitize(t) {
function fetchGeoIp (line 190) | async function fetchGeoIp(ip, signal) {
function hydrateCoordinates (line 219) | async function hydrateCoordinates(threats) {
function fetchFeodo (line 270) | async function fetchFeodo(cutoffMs) {
function fetchUrlhaus (line 308) | async function fetchUrlhaus(cutoffMs) {
function fetchC2Intel (line 361) | async function fetchC2Intel() {
function fetchOtx (line 401) | async function fetchOtx(days) {
function fetchAbuseIpDb (line 437) | async function fetchAbuseIpDb() {
function dedupeThreats (line 498) | function dedupeThreats(threats) {
function toProto (line 513) | function toProto(raw) {
function fetchAllThreats (line 534) | async function fetchAllThreats() {
function validate (line 575) | function validate(data) {
FILE: scripts/seed-displacement-summary.mjs
constant CANONICAL_KEY_PREFIX (line 7) | const CANONICAL_KEY_PREFIX = 'displacement:summary:v1';
constant CACHE_TTL (line 8) | const CACHE_TTL = 86400;
constant COUNTRY_CENTROIDS (line 10) | const COUNTRY_CENTROIDS = {
function getCoordinates (line 23) | function getCoordinates(code) {
function fetchUnhcrYearItems (line 29) | async function fetchUnhcrYearItems(year) {
function fetchDisplacementSummary (line 65) | async function fetchDisplacementSummary() {
function validate (line 216) | function validate(data) {
FILE: scripts/seed-earthquakes.mjs
constant USGS_FEED_URL (line 7) | const USGS_FEED_URL = 'https://earthquake.usgs.gov/earthquakes/feed/v1.0...
constant CANONICAL_KEY (line 8) | const CANONICAL_KEY = 'seismology:earthquakes:v1';
constant CACHE_TTL (line 9) | const CACHE_TTL = 3600;
function fetchEarthquakes (line 11) | async function fetchEarthquakes() {
function validate (line 39) | function validate(data) {
FILE: scripts/seed-economy.mjs
constant KEYS (line 8) | const KEYS = {
constant FRED_KEY_PREFIX (line 14) | const FRED_KEY_PREFIX = 'economic:fred:v1';
constant FRED_TTL (line 15) | const FRED_TTL = 3600;
constant ENERGY_TTL (line 16) | const ENERGY_TTL = 3600;
constant CAPACITY_TTL (line 17) | const CAPACITY_TTL = 86400;
constant MACRO_TTL (line 18) | const MACRO_TTL = 1800;
constant FRED_SERIES (line 20) | const FRED_SERIES = ['WALCL', 'FEDFUNDS', 'T10Y2Y', 'UNRATE', 'CPIAUCSL'...
constant EIA_COMMODITIES (line 24) | const EIA_COMMODITIES = [
function fetchEnergyPrices (line 29) | async function fetchEnergyPrices() {
constant CAPACITY_SOURCES (line 70) | const CAPACITY_SOURCES = [
constant COAL_SUBTYPES (line 75) | const COAL_SUBTYPES = ['BIT', 'SUB', 'LIG', 'RC'];
function fetchCapacityForSource (line 77) | async function fetchCapacityForSource(sourceCode, apiKey, startYear) {
function fetchEnergyCapacity (line 107) | async function fetchEnergyCapacity() {
function fetchFredSeries (line 144) | async function fetchFredSeries() {
function fetchJsonSafe (line 198) | async function fetchJsonSafe(url, timeout = 8000) {
function extractClosePrices (line 207) | function extractClosePrices(chart) {
function extractAlignedPriceVolume (line 213) | function extractAlignedPriceVolume(chart) {
function rateOfChange (line 224) | function rateOfChange(prices, days) {
function smaCalc (line 231) | function smaCalc(prices, period) {
function fetchMacroSignals (line 237) | async function fetchMacroSignals() {
function fetchAll (line 365) | async function fetchAll() {
function validate (line 399) | function validate(data) {
FILE: scripts/seed-etf-flows.mjs
constant CANONICAL_KEY (line 9) | const CANONICAL_KEY = 'market:etf-flows:v1';
constant CACHE_TTL (line 10) | const CACHE_TTL = 3600;
constant YAHOO_DELAY_MS (line 11) | const YAHOO_DELAY_MS = 200;
constant ETF_LIST (line 13) | const ETF_LIST = etfConfig.btcSpot;
function sleep (line 15) | function sleep(ms) {
function fetchYahooWithRetry (line 19) | async function fetchYahooWithRetry(url, label, maxAttempts = 4) {
function parseEtfChartData (line 41) | function parseEtfChartData(chart, ticker, issuer) {
function fetchEtfFlows (line 81) | async function fetchEtfFlows() {
function validate (line 138) | function validate(data) {
FILE: scripts/seed-fire-detections.mjs
constant CANONICAL_KEY (line 7) | const CANONICAL_KEY = 'wildfire:fires:v1';
constant FIRMS_SOURCES (line 8) | const FIRMS_SOURCES = ['VIIRS_SNPP_NRT', 'VIIRS_NOAA20_NRT', 'VIIRS_NOAA...
constant MONITORED_REGIONS (line 10) | const MONITORED_REGIONS = {
function mapConfidence (line 22) | function mapConfidence(c) {
function parseCSV (line 31) | function parseCSV(csv) {
function parseDetectedAt (line 46) | function parseDetectedAt(acqDate, acqTime) {
function fetchRegionSource (line 53) | async function fetchRegionSource(apiKey, regionName, bbox, source) {
function fetchAllRegions (line 64) | async function fetchAllRegions(apiKey) {
function main (line 108) | async function main() {
FILE: scripts/seed-forecasts.mjs
constant CANONICAL_KEY (line 12) | const CANONICAL_KEY = 'forecast:predictions:v2';
constant PRIOR_KEY (line 13) | const PRIOR_KEY = 'forecast:predictions:prior:v2';
constant HISTORY_KEY (line 14) | const HISTORY_KEY = 'forecast:predictions:history:v1';
constant TTL_SECONDS (line 15) | const TTL_SECONDS = 6300;
constant HISTORY_MAX_RUNS (line 16) | const HISTORY_MAX_RUNS = 200;
constant HISTORY_MAX_FORECASTS (line 17) | const HISTORY_MAX_FORECASTS = 25;
constant HISTORY_TTL_SECONDS (line 18) | const HISTORY_TTL_SECONDS = 45 * 24 * 60 * 60;
constant TRACE_LATEST_KEY (line 19) | const TRACE_LATEST_KEY = 'forecast:trace:latest:v1';
constant TRACE_RUNS_KEY (line 20) | const TRACE_RUNS_KEY = 'forecast:trace:runs:v1';
constant TRACE_RUNS_MAX (line 21) | const TRACE_RUNS_MAX = 50;
constant TRACE_REDIS_TTL_SECONDS (line 22) | const TRACE_REDIS_TTL_SECONDS = 60 * 24 * 60 * 60;
constant WORLD_STATE_HISTORY_LIMIT (line 23) | const WORLD_STATE_HISTORY_LIMIT = 6;
constant FORECAST_REFRESH_REQUEST_KEY (line 24) | const FORECAST_REFRESH_REQUEST_KEY = 'forecast:refresh-request:v1';
constant PUBLISH_MIN_PROBABILITY (line 25) | const PUBLISH_MIN_PROBABILITY = 0;
constant PANEL_MIN_PROBABILITY (line 26) | const PANEL_MIN_PROBABILITY = 0.1;
constant ENRICHMENT_COMBINED_MAX (line 27) | const ENRICHMENT_COMBINED_MAX = 3;
constant ENRICHMENT_SCENARIO_MAX (line 28) | const ENRICHMENT_SCENARIO_MAX = 3;
constant ENRICHMENT_MAX_PER_DOMAIN (line 29) | const ENRICHMENT_MAX_PER_DOMAIN = 2;
constant ENRICHMENT_MIN_READINESS (line 30) | const ENRICHMENT_MIN_READINESS = 0.34;
constant ENRICHMENT_PRIORITY_DOMAINS (line 31) | const ENRICHMENT_PRIORITY_DOMAINS = ['market', 'military'];
constant DUPLICATE_SCORE_THRESHOLD (line 34) | const DUPLICATE_SCORE_THRESHOLD = 6;
constant MAX_PUBLISHED_FORECASTS_PER_SITUATION (line 35) | const MAX_PUBLISHED_FORECASTS_PER_SITUATION = 3;
constant MAX_PUBLISHED_FORECASTS_PER_SITUATION_DOMAIN (line 36) | const MAX_PUBLISHED_FORECASTS_PER_SITUATION_DOMAIN = 2;
constant MAX_PUBLISHED_FORECASTS_PER_FAMILY (line 37) | const MAX_PUBLISHED_FORECASTS_PER_FAMILY = 4;
constant MAX_PUBLISHED_FORECASTS_PER_FAMILY_DOMAIN (line 38) | const MAX_PUBLISHED_FORECASTS_PER_FAMILY_DOMAIN = 2;
constant MIN_TARGET_PUBLISHED_FORECASTS (line 39) | const MIN_TARGET_PUBLISHED_FORECASTS = 10;
constant MAX_TARGET_PUBLISHED_FORECASTS (line 40) | const MAX_TARGET_PUBLISHED_FORECASTS = 14;
constant MAX_PRESELECTED_FORECASTS_PER_FAMILY (line 41) | const MAX_PRESELECTED_FORECASTS_PER_FAMILY = 3;
constant MAX_PRESELECTED_FORECASTS_PER_SITUATION (line 42) | const MAX_PRESELECTED_FORECASTS_PER_SITUATION = 2;
constant CYBER_MIN_THREATS_PER_COUNTRY (line 43) | const CYBER_MIN_THREATS_PER_COUNTRY = 5;
constant CYBER_MAX_FORECASTS (line 44) | const CYBER_MAX_FORECASTS = 12;
constant CYBER_SCORE_TYPE_MULTIPLIER (line 45) | const CYBER_SCORE_TYPE_MULTIPLIER = 1.5;
constant CYBER_SCORE_CRITICAL_MULTIPLIER (line 46) | const CYBER_SCORE_CRITICAL_MULTIPLIER = 0.75;
constant CYBER_PROB_MAX (line 47) | const CYBER_PROB_MAX = 0.72;
constant CYBER_PROB_VOLUME_WEIGHT (line 48) | const CYBER_PROB_VOLUME_WEIGHT = 0.5;
constant CYBER_PROB_TYPE_WEIGHT (line 49) | const CYBER_PROB_TYPE_WEIGHT = 0.15;
constant MAX_MILITARY_SURGE_AGE_MS (line 50) | const MAX_MILITARY_SURGE_AGE_MS = 3 * 60 * 60 * 1000;
constant MAX_MILITARY_BUNDLE_DRIFT_MS (line 51) | const MAX_MILITARY_BUNDLE_DRIFT_MS = 5 * 60 * 1000;
constant THEATER_IDS (line 53) | const THEATER_IDS = [
constant THEATER_REGIONS (line 59) | const THEATER_REGIONS = {
constant THEATER_LABELS (line 71) | const THEATER_LABELS = {
constant THEATER_EXPECTED_ACTORS (line 83) | const THEATER_EXPECTED_ACTORS = {
constant CHOKEPOINT_COMMODITIES (line 92) | const CHOKEPOINT_COMMODITIES = {
constant CHOKEPOINT_MARKET_REGIONS (line 102) | const CHOKEPOINT_MARKET_REGIONS = {
constant REGION_KEYWORDS (line 112) | const REGION_KEYWORDS = {
constant TEXT_STOPWORDS (line 124) | const TEXT_STOPWORDS = new Set([
constant FORECAST_DOMAINS (line 136) | const FORECAST_DOMAINS = [
function getRedisCredentials (line 146) | function getRedisCredentials() {
function getDeployRevision (line 153) | function getDeployRevision() {
function redisCommand (line 160) | async function redisCommand(url, token, command) {
function redisGet (line 174) | async function redisGet(url, token, key) {
function redisDel (line 185) | async function redisDel(url, token, key) {
function normalizeChokepoints (line 190) | function normalizeChokepoints(raw) {
function normalizeGpsJamming (line 205) | function normalizeGpsJamming(raw) {
function warmPingChokepoints (line 211) | async function warmPingChokepoints() {
function readInputKeys (line 224) | async function readInputKeys() {
function forecastId (line 274) | function forecastId(domain, region, title) {
function normalize (line 281) | function normalize(value, min, max) {
function getFreshMilitaryForecastInputs (line 286) | function getFreshMilitaryForecastInputs(inputs, now = Date.now()) {
function selectPrimaryMilitarySurge (line 308) | function selectPrimaryMilitarySurge(_theaterId, surges) {
function computeTheaterActorScore (line 329) | function computeTheaterActorScore(theaterId, surge) {
function canPromoteMilitarySurge (line 344) | function canPromoteMilitarySurge(posture, surge) {
function buildMilitaryForecastTitle (line 353) | function buildMilitaryForecastTitle(_theaterId, theaterLabel, surge) {
function resolveCountryName (line 361) | function resolveCountryName(raw) {
function makePrediction (line 367) | function makePrediction(domain, region, title, probability, confidence, ...
function normalizeCiiEntry (line 393) | function normalizeCiiEntry(c) {
function resolveChokepointMarketRegion (line 418) | function resolveChokepointMarketRegion(cp) {
function extractCiiScores (line 425) | function extractCiiScores(inputs) {
function detectConflictScenarios (line 433) | function detectConflictScenarios(inputs) {
function detectMarketScenarios (line 506) | function detectMarketScenarios(inputs) {
function detectSupplyChainScenarios (line 582) | function detectSupplyChainScenarios(inputs) {
function detectPoliticalScenarios (line 634) | function detectPoliticalScenarios(inputs) {
function detectMilitaryScenarios (line 691) | function detectMilitaryScenarios(inputs) {
function detectInfraScenarios (line 821) | function detectInfraScenarios(inputs) {
function detectUcdpConflictZones (line 879) | function detectUcdpConflictZones(inputs) {
function detectCyberScenarios (line 904) | function detectCyberScenarios(inputs) {
constant MARITIME_REGIONS (line 950) | const MARITIME_REGIONS = {
function detectGpsJammingScenarios (line 958) | function detectGpsJammingScenarios(inputs) {
constant MARKET_TAG_TO_REGION (line 983) | const MARKET_TAG_TO_REGION = {
constant DOMAIN_HINTS (line 988) | const DOMAIN_HINTS = {
constant DOMAIN_ACTOR_BLUEPRINTS (line 998) | const DOMAIN_ACTOR_BLUEPRINTS = {
constant SIGNAL_TRIGGER_TEMPLATES (line 1043) | const SIGNAL_TRIGGER_TEMPLATES = {
function tokenizeText (line 1062) | function tokenizeText(text) {
function uniqueLowerTerms (line 1069) | function uniqueLowerTerms(terms) {
function countTermMatches (line 1075) | function countTermMatches(text, terms) {
function extractMeaningfulTokens (line 1088) | function extractMeaningfulTokens(text, exclude = []) {
function buildExpectedRegionTags (line 1099) | function buildExpectedRegionTags(regionTerms, region) {
function getDomainTerms (line 1106) | function getDomainTerms(domain) {
function computeHeadlineRelevance (line 1110) | function computeHeadlineRelevance(headline, terms, domain, options = {}) {
function computeMarketMatchScore (line 1137) | function computeMarketMatchScore(pred, marketTitle, regionTerms, options...
function detectFromPredictionMarkets (line 1172) | function detectFromPredictionMarkets(inputs) {
function loadEntityGraph (line 1200) | function loadEntityGraph() {
function discoverGraphCascades (line 1213) | function discoverGraphCascades(predictions, graph) {
constant DEFAULT_CASCADE_RULES (line 1243) | const DEFAULT_CASCADE_RULES = [
constant PREDICATE_EVALUATORS (line 1251) | const PREDICATE_EVALUATORS = {
function evaluateRuleConditions (line 1258) | function evaluateRuleConditions(rule, pred) {
function loadCascadeRules (line 1268) | function loadCascadeRules() {
function resolveCascades (line 1290) | function resolveCascades(predictions, rules) {
constant PROJECTION_CURVES (line 1306) | const PROJECTION_CURVES = {
function computeProjections (line 1316) | function computeProjections(predictions) {
function calibrateWithMarkets (line 1330) | function calibrateWithMarkets(predictions, markets) {
function readPriorPredictions (line 1373) | async function readPriorPredictions() {
function computeTrends (line 1380) | function computeTrends(predictions, prior) {
function loadCountryCodes (line 1397) | function loadCountryCodes() {
constant NEWS_MATCHABLE_TYPES (line 1406) | const NEWS_MATCHABLE_TYPES = new Set(['country', 'theater']);
function getSearchTermsForRegion (line 1408) | function getSearchTermsForRegion(region) {
function extractAllHeadlines (line 1451) | function extractAllHeadlines(newsInsights, newsDigest) {
function attachNewsContext (line 1471) | function attachNewsContext(predictions, newsInsights, newsDigest) {
constant SIGNAL_TO_SOURCE (line 1507) | const SIGNAL_TO_SOURCE = {
function computeConfidence (line 1522) | function computeConfidence(predictions) {
function roundPct (line 1534) | function roundPct(value) {
function slugifyValue (line 1538) | function slugifyValue(value) {
function buildCounterEvidence (line 1546) | function buildCounterEvidence(pred) {
function buildCaseTriggers (line 1571) | function buildCaseTriggers(pred) {
function buildForecastActors (line 1585) | function buildForecastActors(pred) {
function buildForecastWorldState (line 1657) | function buildForecastWorldState(pred, actors = [], triggers = [], count...
function branchTitle (line 1688) | function branchTitle(kind) {
function branchShift (line 1694) | function branchShift(kind, pred, context = {}) {
function buildBranchRounds (line 1712) | function buildBranchRounds(kind, pred, context = {}) {
function buildForecastBranches (line 1761) | function buildForecastBranches(pred, context = {}) {
function buildActorLenses (line 1793) | function buildActorLenses(pred) {
function buildForecastCase (line 1806) | function buildForecastCase(pred) {
function buildForecastCases (line 1887) | function buildForecastCases(predictions) {
function buildPriorForecastSnapshot (line 1891) | function buildPriorForecastSnapshot(pred) {
function buildHistoryForecastEntry (line 1906) | function buildHistoryForecastEntry(pred) {
function buildHistorySnapshot (line 1939) | function buildHistorySnapshot(data, options = {}) {
function appendHistorySnapshot (line 1948) | async function appendHistorySnapshot(data, options = {}) {
function getTraceMaxForecasts (line 1961) | function getTraceMaxForecasts(totalForecasts = 0) {
function getTraceCapLog (line 1968) | function getTraceCapLog(totalForecasts = 0) {
function applyTraceMeta (line 1976) | function applyTraceMeta(pred, patch) {
function buildTraceRunPrefix (line 1983) | function buildTraceRunPrefix(runId, generatedAt, basePrefix) {
function buildForecastTraceRecord (line 1990) | function buildForecastTraceRecord(pred, rank, simulationByForecastId = n...
function buildForecastRunActorRegistry (line 2038) | function buildForecastRunActorRegistry(predictions) {
function buildActorContinuitySummary (line 2086) | function buildActorContinuitySummary(currentActors, priorWorldState = nu...
function buildForecastBranchStates (line 2164) | function buildForecastBranchStates(predictions) {
function buildBranchContinuitySummary (line 2202) | function buildBranchContinuitySummary(currentBranchStates, priorWorldSta...
function uniqueSortedStrings (line 2280) | function uniqueSortedStrings(values) {
function normalizeSituationText (line 2284) | function normalizeSituationText(value) {
function formatSituationDomainLabel (line 2293) | function formatSituationDomainLabel(domains = []) {
function formatSituationLabel (line 2301) | function formatSituationLabel(cluster) {
function buildSituationReference (line 2308) | function buildSituationReference(situation) {
function hashSituationKey (line 2313) | function hashSituationKey(parts) {
function incrementSituationCounts (line 2317) | function incrementSituationCounts(target, values = []) {
function pickDominantSituationValue (line 2325) | function pickDominantSituationValue(counts = {}, fallback = []) {
function pickDominantSituationValues (line 2332) | function pickDominantSituationValues(counts = {}, fallback = [], maxValu...
constant FAMILY_GENERIC_TOKENS (line 2343) | const FAMILY_GENERIC_TOKENS = new Set([
constant REGION_LINK_NOISE_TOKENS (line 2380) | const REGION_LINK_NOISE_TOKENS = new Set([
function filterSpecificSituationTokens (line 2402) | function filterSpecificSituationTokens(tokens = []) {
function extractRegionLinkTokens (line 2410) | function extractRegionLinkTokens(values = []) {
function buildSituationCandidate (line 2416) | function buildSituationCandidate(prediction) {
function computeSituationOverlap (line 2434) | function computeSituationOverlap(candidate, cluster) {
function shouldMergeSituationCandidate (line 2446) | function shouldMergeSituationCandidate(candidate, cluster, score) {
function finalizeSituationCluster (line 2472) | function finalizeSituationCluster(cluster) {
function computeSituationSimilarity (line 2506) | function computeSituationSimilarity(currentCluster, priorCluster) {
function buildSituationClusters (line 2516) | function buildSituationClusters(predictions) {
function formatSituationFamilyLabel (line 2577) | function formatSituationFamilyLabel(family) {
function inferSituationFamilyArchetype (line 2600) | function inferSituationFamilyArchetype(input = {}) {
function buildSituationFamilyCandidate (line 2619) | function buildSituationFamilyCandidate(cluster) {
function computeSituationFamilyOverlap (line 2642) | function computeSituationFamilyOverlap(candidate, family) {
function shouldMergeSituationFamilyCandidate (line 2655) | function shouldMergeSituationFamilyCandidate(candidate, family, score) {
function finalizeSituationFamily (line 2673) | function finalizeSituationFamily(family) {
function buildSituationFamilies (line 2713) | function buildSituationFamilies(situationClusters = []) {
function buildSituationContinuitySummary (line 2774) | function buildSituationContinuitySummary(currentSituationClusters, prior...
function buildSituationSummary (line 2861) | function buildSituationSummary(situationClusters, situationContinuity) {
function clampUnitInterval (line 2880) | function clampUnitInterval(value) {
function intersectAny (line 2884) | function intersectAny(left = [], right = []) {
function summarizeSituationPressure (line 2888) | function summarizeSituationPressure(cluster, actors, branches) {
constant SIMULATION_STATE_VERSION (line 2895) | const SIMULATION_STATE_VERSION = 2;
constant SIMULATION_DOMAIN_PROFILES (line 2897) | const SIMULATION_DOMAIN_PROFILES = {
function getSimulationDomainProfile (line 2996) | function getSimulationDomainProfile(dominantDomain) {
constant PRESSURE_ACTION_MARKERS (line 3000) | const PRESSURE_ACTION_MARKERS = ['reposition', 'reprice', 'rebalance', '...
constant STABILIZING_ACTION_MARKERS (line 3001) | const STABILIZING_ACTION_MARKERS = ['prevent', 'preserve', 'contain', 'p...
constant GENERIC_ACTOR_CATEGORIES (line 3002) | const GENERIC_ACTOR_CATEGORIES = new Set(['general', 'external', 'market...
constant GENERIC_ACTOR_NAME_MARKERS (line 3003) | const GENERIC_ACTOR_NAME_MARKERS = ['regional', 'participants', 'observe...
function scoreActorSpecificity (line 3005) | function scoreActorSpecificity(actorLike = {}) {
function summarizeBranchDynamics (line 3023) | function summarizeBranchDynamics(branches = []) {
function scoreActorAction (line 3037) | function scoreActorAction(summary, stage, dominantDomain, actor) {
function inferActionChannels (line 3071) | function inferActionChannels(summary, intent, dominantDomain) {
function getTargetSensitivityChannels (line 3095) | function getTargetSensitivityChannels(domain) {
function inferSystemEffectRelationFromChannel (line 3108) | function inferSystemEffectRelationFromChannel(channel, targetDomain) {
function buildActorRoundActions (line 3131) | function buildActorRoundActions(stage, situation, actors = []) {
function buildSimulationRound (line 3157) | function buildSimulationRound(stage, situation, context) {
function summarizeSimulationOutcome (line 3249) | function summarizeSimulationOutcome(rounds = [], dominantDomain = '') {
function buildSituationSimulationState (line 3279) | function buildSituationSimulationState(worldState, priorWorldState = nul...
function buildSimulationActionLedger (line 3403) | function buildSimulationActionLedger(situationSimulations = []) {
function buildSimulationInteractionLedger (line 3452) | function buildSimulationInteractionLedger(actionLedger = [], situationSi...
function buildSimulationReplayTimeline (line 3578) | function buildSimulationReplayTimeline(situationSimulations = [], action...
function buildReportableInteractionLedger (line 3614) | function buildReportableInteractionLedger(interactionLedger = [], situat...
function buildInteractionGroups (line 3645) | function buildInteractionGroups(interactions = []) {
function computeReportableEffectConfidence (line 3711) | function computeReportableEffectConfidence(group, source, target, strong...
function describeSimulationPosture (line 3746) | function describeSimulationPosture(posture) {
function buildSituationOutcomeSummaries (line 3752) | function buildSituationOutcomeSummaries(simulationState) {
function buildSimulationReportInputs (line 3775) | function buildSimulationReportInputs(worldState) {
function inferSystemEffectRelation (line 3814) | function inferSystemEffectRelation(sourceDomain, targetDomain) {
function canEmitCrossSituationEffect (line 3832) | function canEmitCrossSituationEffect(source, strongestChannel, strongest...
function buildInteractionWatchlist (line 3848) | function buildInteractionWatchlist(interactions = []) {
function buildCrossSituationEffects (line 3859) | function buildCrossSituationEffects(simulationState) {
function attachSituationContext (line 3985) | function attachSituationContext(predictions, situationClusters = buildSi...
function buildSituationFamilyIndex (line 4012) | function buildSituationFamilyIndex(situationFamilies) {
function attachSituationFamilyContext (line 4023) | function attachSituationFamilyContext(predictions, situationFamilies = [...
function buildSituationForecastIndex (line 4046) | function buildSituationForecastIndex(situationClusters) {
function projectSituationClusters (line 4057) | function projectSituationClusters(situationClusters, predictions) {
function summarizeWorldStateHistory (line 4116) | function summarizeWorldStateHistory(priorWorldStates = []) {
function buildReportContinuity (line 4132) | function buildReportContinuity(current, priorWorldStates = []) {
function buildWorldStateReport (line 4238) | function buildWorldStateReport(worldState) {
function buildForecastDomainStates (line 4381) | function buildForecastDomainStates(predictions) {
function buildForecastRegionalStates (line 4428) | function buildForecastRegionalStates(predictions) {
function buildForecastEvidenceLedger (line 4462) | function buildForecastEvidenceLedger(predictions) {
function buildForecastRunContinuity (line 4492) | function buildForecastRunContinuity(predictions) {
function buildForecastRunWorldState (line 4531) | function buildForecastRunWorldState(data) {
function summarizeWorldStateSurface (line 4577) | function summarizeWorldStateSurface(worldState) {
function summarizeTypeCounts (line 4592) | function summarizeTypeCounts(items) {
function pickTopCountEntries (line 4604) | function pickTopCountEntries(countMap, limit = 5) {
function summarizeForecastPopulation (line 4611) | function summarizeForecastPopulation(predictions) {
function summarizeForecastTraceQuality (line 4630) | function summarizeForecastTraceQuality(predictions, tracedPredictions, e...
function buildForecastTraceArtifacts (line 4691) | function buildForecastTraceArtifacts(data, context = {}, config = {}) {
function writeForecastTracePointer (line 4843) | async function writeForecastTracePointer(pointer) {
function readPreviousForecastWorldState (line 4851) | async function readPreviousForecastWorldState(storageConfig) {
function readForecastWorldStateHistory (line 4865) | async function readForecastWorldStateHistory(storageConfig, limit = WORL...
function writeForecastTraceArtifacts (line 4891) | async function writeForecastTraceArtifacts(data, context = {}) {
function buildChangeItems (line 4956) | function buildChangeItems(pred, prev) {
function buildChangeSummary (line 5003) | function buildChangeSummary(pred, prev, changeItems) {
function annotateForecastChanges (line 5025) | function annotateForecastChanges(predictions, prior) {
function clamp01 (line 5036) | function clamp01(value) {
function scoreForecastReadiness (line 5040) | function scoreForecastReadiness(pred) {
function computeAnalysisPriority (line 5078) | function computeAnalysisPriority(pred) {
function rankForecastsForAnalysis (line 5112) | function rankForecastsForAnalysis(predictions) {
function prepareForecastMetrics (line 5124) | function prepareForecastMetrics(predictions) {
function intersectCount (line 5133) | function intersectCount(left = [], right = []) {
function getForecastSituationTokens (line 5143) | function getForecastSituationTokens(pred) {
function computeSituationDuplicateScore (line 5151) | function computeSituationDuplicateScore(current, kept) {
function shouldSuppressAsSituationDuplicate (line 5171) | function shouldSuppressAsSituationDuplicate(current, kept, duplicateScor...
function summarizePublishFiltering (line 5187) | function summarizePublishFiltering(predictions) {
function getPublishSelectionTarget (line 5235) | function getPublishSelectionTarget(predictions = []) {
function computePublishSelectionScore (line 5245) | function computePublishSelectionScore(pred) {
function selectPublishedForecastPool (line 5267) | function selectPublishedForecastPool(predictions, options = {}) {
function buildPublishedForecastArtifacts (line 5367) | function buildPublishedForecastArtifacts(candidatePool, fullRunSituation...
function markDeferredFamilySelection (line 5387) | function markDeferredFamilySelection(predictions, selectedPool) {
function filterPublishedForecasts (line 5402) | function filterPublishedForecasts(predictions, minProbability = PUBLISH_...
function applySituationFamilyCaps (line 5516) | function applySituationFamilyCaps(predictions, situationFamilies = []) {
function selectForecastsForEnrichment (line 5568) | function selectForecastsForEnrichment(predictions, options = {}) {
constant FORECAST_LLM_PROVIDERS (line 5640) | const FORECAST_LLM_PROVIDERS = [
constant FORECAST_LLM_PROVIDER_NAMES (line 5644) | const FORECAST_LLM_PROVIDER_NAMES = new Set(FORECAST_LLM_PROVIDERS.map(p...
function parseForecastProviderOrder (line 5646) | function parseForecastProviderOrder(raw) {
function getForecastLlmCallOptions (line 5659) | function getForecastLlmCallOptions(stage = 'default') {
function resolveForecastLlmProviders (line 5677) | function resolveForecastLlmProviders(options = {}) {
function summarizeForecastLlmOptions (line 5697) |
Copy disabled (too large)
Download .json
Condensed preview — 1349 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (16,537K chars).
[
{
"path": ".dockerignore",
"chars": 189,
"preview": "node_modules\ndist\n.git\n.github\n.windsurf\n.agent\n.agents\n.claude\n.factory\n.planning\ne2e\nsrc-tauri/target\nsrc-tauri/sideca"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 2293,
"preview": "name: Bug Report\ndescription: Report a bug in World Monitor\nlabels: [\"bug\"]\nbody:\n - type: markdown\n attributes:\n "
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 350,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Documentation\n url: https://github.com/koala73/worldmonitor/blob"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 1442,
"preview": "name: Feature Request\ndescription: Suggest a new feature or improvement\nlabels: [\"enhancement\"]\nbody:\n - type: markdown"
},
{
"path": ".github/ISSUE_TEMPLATE/new_data_source.yml",
"chars": 1797,
"preview": "name: New Data Source\ndescription: Suggest a new RSS feed, API, or map layer\nlabels: [\"data-source\"]\nbody:\n - type: mar"
},
{
"path": ".github/pull_request_template.md",
"chars": 926,
"preview": "## Summary\n\n<!-- Brief description of what this PR does -->\n\n## Type of change\n\n- [ ] Bug fix\n- [ ] New feature\n- [ ] Ne"
},
{
"path": ".github/workflows/build-desktop.yml",
"chars": 20827,
"preview": "name: 'Build Desktop App'\n\non:\n workflow_dispatch:\n inputs:\n variant:\n description: 'App variant'\n "
},
{
"path": ".github/workflows/contributor-trust.yml",
"chars": 1896,
"preview": "name: Contributor Trust\n\non:\n pull_request_target:\n types: [opened, reopened]\n\njobs:\n check:\n # Skip for repo me"
},
{
"path": ".github/workflows/docker-publish.yml",
"chars": 1390,
"preview": "name: Publish Docker image\n\non:\n release:\n types: [published]\n workflow_dispatch:\n\njobs:\n docker:\n runs-on: ubu"
},
{
"path": ".github/workflows/lint-code.yml",
"chars": 373,
"preview": "name: Lint Code\n\non:\n pull_request:\n push:\n branches: [main]\n\njobs:\n biome:\n runs-on: ubuntu-latest\n steps:\n"
},
{
"path": ".github/workflows/lint.yml",
"chars": 483,
"preview": "name: Lint\n\non:\n pull_request:\n paths:\n - '**/*.md'\n - '.markdownlint-cli2.jsonc'\n\njobs:\n markdown:\n #"
},
{
"path": ".github/workflows/proto-check.yml",
"chars": 2071,
"preview": "name: Proto Generation Check\n\non:\n pull_request:\n paths:\n - 'proto/**'\n - 'src/generated/**'\n - 'docs"
},
{
"path": ".github/workflows/test-linux-app.yml",
"chars": 5780,
"preview": "name: 'Test Linux App'\n\non:\n workflow_dispatch:\n\nconcurrency:\n group: test-linux-app-${{ github.ref }}\n cancel-in-pro"
},
{
"path": ".github/workflows/test.yml",
"chars": 301,
"preview": "name: Test\n\non:\n pull_request:\n push:\n branches: [main]\n\njobs:\n unit:\n runs-on: ubuntu-latest\n steps:\n "
},
{
"path": ".github/workflows/typecheck.yml",
"chars": 569,
"preview": "name: Typecheck\n\non:\n pull_request:\n paths-ignore:\n - 'scripts/**'\n push:\n branches: [main]\n paths-ignor"
},
{
"path": ".gitignore",
"chars": 1364,
"preview": "node_modules/\n.idea/\ndist/\npublic/blog/\n.DS_Store\n*.log\n.env\n.env.local\n.playwright-mcp/\n.vercel\napi/\\[domain\\]/v1/\\[rpc"
},
{
"path": ".husky/pre-commit",
"chars": 111,
"preview": "echo \"Running Unicode safety check (staged files)...\"\nnode scripts/check-unicode-safety.mjs --staged || exit 1\n"
},
{
"path": ".husky/pre-push",
"chars": 2728,
"preview": "# Ensure dependencies are installed (worktrees start with no node_modules)\nif [ ! -d node_modules ]; then\n echo \"node_m"
},
{
"path": ".markdownlint-cli2.jsonc",
"chars": 278,
"preview": "{\n // Only enforce the 3 rules from PR #72. Everything else is off.\n \"config\": {\n \"default\": false,\n \"MD012\": tr"
},
{
"path": ".npmrc",
"chars": 15,
"preview": "loglevel=error\n"
},
{
"path": ".nvmrc",
"chars": 3,
"preview": "22\n"
},
{
"path": ".vercelignore",
"chars": 422,
"preview": "# Exclude local desktop build artifacts and sidecar binaries from deployments\n# These files are large and not needed by "
},
{
"path": "AGENTS.md",
"chars": 8046,
"preview": "# AGENTS.md\n\nAgent entry point for WorldMonitor. Read this first, then follow links for depth.\n\n## What This Project Is\n"
},
{
"path": "ARCHITECTURE.md",
"chars": 18901,
"preview": "# Architecture\n\n> **Last verified**: 2026-03-14 against commit `24b502d0`\n>\n> **Ownership rule**: When deployment topolo"
},
{
"path": "CHANGELOG.md",
"chars": 47293,
"preview": "# Changelog\n\nAll notable changes to World Monitor are documented here.\n\n## [Unreleased]\n\n### Added\n\n- US Treasury custom"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 4917,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": "CONTRIBUTING.md",
"chars": 13357,
"preview": "# Contributing to World Monitor\n\nThank you for your interest in contributing to World Monitor! This project thrives on c"
},
{
"path": "Dockerfile",
"chars": 2521,
"preview": "# =============================================================================\n# World Monitor — Docker Image\n# ======="
},
{
"path": "Dockerfile.relay",
"chars": 903,
"preview": "# =============================================================================\n# AIS Relay Sidecar\n# =================="
},
{
"path": "LICENSE",
"chars": 34863,
"preview": "World Monitor — Real-time global intelligence dashboard\nCopyright (C) 2024-2026 Elie Habib\n\nThis program is free softwar"
},
{
"path": "Makefile",
"chars": 2542,
"preview": ".PHONY: help lint generate breaking format check clean deps install install-buf install-plugins install-npm install-play"
},
{
"path": "README.md",
"chars": 7764,
"preview": "# World Monitor\n\n**Real-time global intelligence dashboard** — AI-powered news aggregation, geopolitical monitoring, and"
},
{
"path": "SECURITY.md",
"chars": 4947,
"preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported |\n| ------- | ------------------ |\n| main | "
},
{
"path": "SELF_HOSTING.md",
"chars": 6878,
"preview": "# 🌍 Self-Hosting World Monitor\n\nRun the full World Monitor stack locally with Docker/Podman.\n\n## 📋 Prerequisites\n\n- **Do"
},
{
"path": "api/_api-key.js",
"chars": 2664,
"preview": "const DESKTOP_ORIGIN_PATTERNS = [\n /^https?:\\/\\/tauri\\.localhost(:\\d+)?$/,\n /^https?:\\/\\/[a-z0-9-]+\\.tauri\\.localhost("
},
{
"path": "api/_cors.js",
"chars": 1805,
"preview": "const ALLOWED_ORIGIN_PATTERNS = [\n /^https:\\/\\/(.*\\.)?worldmonitor\\.app$/,\n /^https:\\/\\/worldmonitor-[a-z0-9-]+-elie-["
},
{
"path": "api/_cors.test.mjs",
"chars": 1252,
"preview": "import { strict as assert } from 'node:assert';\nimport test from 'node:test';\nimport { getCorsHeaders, isDisallowedOrigi"
},
{
"path": "api/_github-release.js",
"chars": 344,
"preview": "const RELEASES_URL = 'https://api.github.com/repos/koala73/worldmonitor/releases/latest';\n\nexport async function fetchLa"
},
{
"path": "api/_ip-rate-limit.js",
"chars": 498,
"preview": "export function createIpRateLimiter({ limit, windowMs }) {\n const rateLimitMap = new Map();\n\n function getEntry(ip) {\n"
},
{
"path": "api/_json-response.js",
"chars": 207,
"preview": "export function jsonResponse(body, status, headers = {}) {\n return new Response(JSON.stringify(body), {\n status,\n "
},
{
"path": "api/_rate-limit.js",
"chars": 1368,
"preview": "import { Ratelimit } from '@upstash/ratelimit';\nimport { Redis } from '@upstash/redis';\nimport { jsonResponse } from './"
},
{
"path": "api/_relay.js",
"chars": 4753,
"preview": "import { getCorsHeaders, isDisallowedOrigin } from './_cors.js';\nimport { validateApiKey } from './_api-key.js';\nimport "
},
{
"path": "api/_rss-allowed-domains.js",
"chars": 6533,
"preview": "// Edge-compatible ESM wrapper for shared RSS allowed domains.\n// Source of truth: shared/rss-allowed-domains.json\n// NO"
},
{
"path": "api/_turnstile.js",
"chars": 1225,
"preview": "const TURNSTILE_VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';\n\nexport function getClientIp(r"
},
{
"path": "api/_turnstile.test.mjs",
"chars": 2267,
"preview": "import assert from 'node:assert/strict';\nimport test from 'node:test';\nimport { getClientIp, verifyTurnstile } from './_"
},
{
"path": "api/_upstash-json.js",
"chars": 556,
"preview": "export async function readJsonFromUpstash(key, timeoutMs = 3_000) {\n const url = process.env.UPSTASH_REDIS_REST_URL;\n "
},
{
"path": "api/ais-snapshot.js",
"chars": 563,
"preview": "import { createRelayHandler } from './_relay.js';\n\nexport const config = { runtime: 'edge' };\n\nexport default createRela"
},
{
"path": "api/aviation/v1/[rpc].ts",
"chars": 426,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/bootstrap.js",
"chars": 7485,
"preview": "import { getCorsHeaders, getPublicCorsHeaders, isDisallowedOrigin } from './_cors.js';\nimport { validateApiKey } from '."
},
{
"path": "api/cache-purge.js",
"chars": 6851,
"preview": "import { getCorsHeaders } from './_cors.js';\nimport { jsonResponse } from './_json-response.js';\n\nexport const config = "
},
{
"path": "api/climate/v1/[rpc].ts",
"chars": 420,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/conflict/v1/[rpc].ts",
"chars": 426,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/contact.js",
"chars": 7181,
"preview": "export const config = { runtime: 'edge' };\n\nimport { ConvexHttpClient } from 'convex/browser';\nimport { getCorsHeaders, "
},
{
"path": "api/cyber/v1/[rpc].ts",
"chars": 408,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/data/city-coords.ts",
"chars": 24108,
"preview": "/**\n * Comprehensive city geocoding database (500+ cities worldwide).\n * Extracted from the legacy api/tech-events.js en"
},
{
"path": "api/displacement/v1/[rpc].ts",
"chars": 450,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/download.js",
"chars": 2548,
"preview": "import { fetchLatestRelease } from './_github-release.js';\n\n// Non-sebuf: returns XML/HTML, stays as standalone Vercel f"
},
{
"path": "api/economic/v1/[rpc].ts",
"chars": 426,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/eia/[[...path]].js",
"chars": 3061,
"preview": "// EIA (Energy Information Administration) API proxy\n// Keeps API key server-side\nimport { getCorsHeaders, isDisallowedO"
},
{
"path": "api/enrichment/_domain.js",
"chars": 464,
"preview": "const DOMAIN_SUFFIX_RE = /\\.(com|io|co|org|net|ai|dev|app)$/;\n\nexport function toOrgSlugFromDomain(domain) {\n return (d"
},
{
"path": "api/enrichment/company.js",
"chars": 6593,
"preview": "/**\n * Company Enrichment API — Vercel Edge Function\n * Aggregates company data from multiple public sources:\n * - GitHu"
},
{
"path": "api/enrichment/signals.js",
"chars": 7417,
"preview": "/**\n * Signal Discovery API — Vercel Edge Function\n * Discovers activity signals for a company from public sources:\n * -"
},
{
"path": "api/forecast/v1/[rpc].ts",
"chars": 426,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/fwdstart.js",
"chars": 3688,
"preview": "// Non-sebuf: returns XML/HTML, stays as standalone Vercel function\nimport { getCorsHeaders, isDisallowedOrigin } from '"
},
{
"path": "api/geo.js",
"chars": 481,
"preview": "import { jsonResponse } from './_json-response.js';\n\nexport const config = { runtime: 'edge' };\n\nexport default function"
},
{
"path": "api/giving/v1/[rpc].ts",
"chars": 414,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/gpsjam.js",
"chars": 2194,
"preview": "import { getCorsHeaders, isDisallowedOrigin } from './_cors.js';\nimport { jsonResponse } from './_json-response.js';\nimp"
},
{
"path": "api/health.js",
"chars": 18677,
"preview": "import { jsonResponse } from './_json-response.js';\n\nexport const config = { runtime: 'edge' };\n\nconst BOOTSTRAP_KEYS = "
},
{
"path": "api/imagery/v1/[rpc].ts",
"chars": 420,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/infrastructure/v1/[rpc].ts",
"chars": 462,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/intelligence/v1/[rpc].ts",
"chars": 450,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/loaders-xml-wms-regression.test.mjs",
"chars": 4340,
"preview": "import { strict as assert } from 'node:assert';\nimport test from 'node:test';\nimport { XMLLoader } from '@loaders.gl/xml"
},
{
"path": "api/maritime/v1/[rpc].ts",
"chars": 426,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/market/v1/[rpc].ts",
"chars": 414,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/mcp-proxy.js",
"chars": 14076,
"preview": "import { getCorsHeaders, isDisallowedOrigin } from './_cors.js';\nimport { jsonResponse } from './_json-response.js';\n\nex"
},
{
"path": "api/military/v1/[rpc].ts",
"chars": 426,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/military-flights.js",
"chars": 1634,
"preview": "import { getCorsHeaders, isDisallowedOrigin } from './_cors.js';\nimport { jsonResponse } from './_json-response.js';\nimp"
},
{
"path": "api/natural/v1/[rpc].ts",
"chars": 420,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/news/v1/[rpc].ts",
"chars": 402,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/og-story.js",
"chars": 11697,
"preview": "// Non-sebuf: returns XML/HTML, stays as standalone Vercel function\n/**\n * Dynamic OG Image Generator for Story Sharing\n"
},
{
"path": "api/og-story.test.mjs",
"chars": 1312,
"preview": "import { strict as assert } from 'node:assert';\nimport test from 'node:test';\nimport handler from './og-story.js';\n\nfunc"
},
{
"path": "api/opensky.js",
"chars": 438,
"preview": "import { createRelayHandler } from './_relay.js';\n\nexport const config = { runtime: 'edge' };\n\nexport default createRela"
},
{
"path": "api/oref-alerts.js",
"chars": 763,
"preview": "import { createRelayHandler } from './_relay.js';\nimport { jsonResponse } from './_json-response.js';\n\nexport const conf"
},
{
"path": "api/polymarket.js",
"chars": 564,
"preview": "import { createRelayHandler } from './_relay.js';\n\nexport const config = { runtime: 'edge' };\n\nexport default createRela"
},
{
"path": "api/positive-events/v1/[rpc].ts",
"chars": 464,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/prediction/v1/[rpc].ts",
"chars": 438,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/radiation/v1/[rpc].ts",
"chars": 432,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/register-interest.js",
"chars": 14900,
"preview": "export const config = { runtime: 'edge' };\n\nimport { ConvexHttpClient } from 'convex/browser';\nimport { getCorsHeaders, "
},
{
"path": "api/research/v1/[rpc].ts",
"chars": 426,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/reverse-geocode.js",
"chars": 3132,
"preview": "import { getCorsHeaders, isDisallowedOrigin } from './_cors.js';\nimport { jsonResponse } from './_json-response.js';\n\nex"
},
{
"path": "api/rss-proxy.js",
"chars": 6522,
"preview": "import { getCorsHeaders, isDisallowedOrigin } from './_cors.js';\nimport { validateApiKey } from './_api-key.js';\nimport "
},
{
"path": "api/sanctions/v1/[rpc].ts",
"chars": 432,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/satellites.js",
"chars": 1439,
"preview": "import { getCorsHeaders, isDisallowedOrigin } from './_cors.js';\nimport { jsonResponse } from './_json-response.js';\nimp"
},
{
"path": "api/seed-health.js",
"chars": 7336,
"preview": "import { getCorsHeaders, isDisallowedOrigin } from './_cors.js';\nimport { validateApiKey } from './_api-key.js';\nimport "
},
{
"path": "api/seismology/v1/[rpc].ts",
"chars": 438,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/story.js",
"chars": 3534,
"preview": "// Non-sebuf: returns XML/HTML, stays as standalone Vercel function\n/**\n * Story Page for Social Crawlers\n * Returns HTM"
},
{
"path": "api/supply-chain/v1/[rpc].ts",
"chars": 446,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/telegram-feed.js",
"chars": 2387,
"preview": "import { getRelayBaseUrl, getRelayHeaders, fetchWithTimeout, buildRelayResponse } from './_relay.js';\nimport { getCorsHe"
},
{
"path": "api/thermal/v1/[rpc].ts",
"chars": 420,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/trade/v1/[rpc].ts",
"chars": 408,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/unrest/v1/[rpc].ts",
"chars": 414,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/version.js",
"chars": 809,
"preview": "import { fetchLatestRelease } from './_github-release.js';\nimport { jsonResponse } from './_json-response.js';\n\nexport c"
},
{
"path": "api/webcam/v1/[rpc].ts",
"chars": 414,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/wildfire/v1/[rpc].ts",
"chars": 426,
"preview": "export const config = { runtime: 'edge' };\n\nimport { createDomainGateway, serverOptions } from '../../../server/gateway'"
},
{
"path": "api/youtube/embed.js",
"chars": 8067,
"preview": "export const config = { runtime: 'edge' };\n\nfunction parseFlag(value, fallback = '1') {\n if (value === '0' || value ==="
},
{
"path": "api/youtube/embed.test.mjs",
"chars": 2285,
"preview": "import { strict as assert } from 'node:assert';\nimport test from 'node:test';\nimport handler from './embed.js';\n\nfunctio"
},
{
"path": "api/youtube/live.js",
"chars": 5153,
"preview": "// YouTube Live Stream Detection API\n// Proxies to Railway relay which uses residential proxy for YouTube scraping\n\nimpo"
},
{
"path": "biome.json",
"chars": 2097,
"preview": "{\n\t\"$schema\": \"https://biomejs.dev/schemas/2.4.7/schema.json\",\n\t\"vcs\": {\n\t\t\"enabled\": true,\n\t\t\"clientKind\": \"git\",\n\t\t\"us"
},
{
"path": "blog-site/.gitignore",
"chars": 322,
"preview": "# build output\ndist/\n\n# generated OG images (rebuilt at build time)\npublic/og/\n# generated types\n.astro/\n\n# dependencies"
},
{
"path": "blog-site/.vscode/extensions.json",
"chars": 87,
"preview": "{\n \"recommendations\": [\"astro-build.astro-vscode\"],\n \"unwantedRecommendations\": []\n}\n"
},
{
"path": "blog-site/.vscode/launch.json",
"chars": 207,
"preview": "{\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\n \"command\": \"./node_modules/.bin/astro dev\",\n \"name\": \"Dev"
},
{
"path": "blog-site/README.md",
"chars": 1553,
"preview": "# Astro Starter Kit: Minimal\n\n```sh\nnpm create astro@latest -- --template minimal\n```\n\n> 🧑🚀 **Seasoned astronaut?** Del"
},
{
"path": "blog-site/astro.config.mjs",
"chars": 2406,
"preview": "// @ts-check\nimport { defineConfig } from 'astro/config';\nimport sitemap from '@astrojs/sitemap';\n\nconst POST_DATES = {\n"
},
{
"path": "blog-site/package.json",
"chars": 542,
"preview": "{\n \"name\": \"blog-site\",\n \"type\": \"module\",\n \"version\": \"0.0.1\",\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"script"
},
{
"path": "blog-site/public/robots.txt",
"chars": 85,
"preview": "User-agent: *\nAllow: /\n\nSitemap: https://www.worldmonitor.app/blog/sitemap-index.xml\n"
},
{
"path": "blog-site/scripts/generate-og-images.mjs",
"chars": 3682,
"preview": "import satori from 'satori';\nimport sharp from 'sharp';\nimport { readFileSync, writeFileSync, mkdirSync, existsSync, rea"
},
{
"path": "blog-site/src/content/blog/ai-powered-intelligence-without-the-cloud.md",
"chars": 9352,
"preview": "---\ntitle: \"AI-Powered Intelligence Without the Cloud: World Monitor's Privacy-First Approach\"\ndescription: \"Run AI-powe"
},
{
"path": "blog-site/src/content/blog/build-on-worldmonitor-developer-api-open-source.md",
"chars": 10416,
"preview": "---\ntitle: \"Build on World Monitor: Open APIs, Proto-First Architecture, and the Developer Platform\"\ndescription: \"Build"
},
{
"path": "blog-site/src/content/blog/command-palette-search-everything-instantly.md",
"chars": 8027,
"preview": "---\ntitle: \"Cmd+K: Search Everything on the Planet in Under a Second\"\ndescription: \"Fuzzy-search 195 countries, 25+ data"
},
{
"path": "blog-site/src/content/blog/cyber-threat-intelligence-for-security-teams.md",
"chars": 9812,
"preview": "---\ntitle: \"Cyber Threat Intelligence Meets Geopolitics: World Monitor for Security Teams\"\ndescription: \"Track botnets, "
},
{
"path": "blog-site/src/content/blog/five-dashboards-one-platform-worldmonitor-variants.md",
"chars": 10241,
"preview": "---\ntitle: \"Five Dashboards, One Platform: How World Monitor Serves Every Intelligence Need\"\ndescription: \"World Monitor"
},
{
"path": "blog-site/src/content/blog/live-webcams-from-geopolitical-hotspots.md",
"chars": 8649,
"preview": "---\ntitle: \"Watch the World Live: 31 Webcam Streams from Geopolitical Hotspots\"\ndescription: \"Stream 31 live webcams fro"
},
{
"path": "blog-site/src/content/blog/monitor-global-supply-chains-and-commodity-disruptions.md",
"chars": 10237,
"preview": "---\ntitle: \"Monitor Global Supply Chains and Commodity Disruptions in Real Time\"\ndescription: \"Track commodity prices, p"
},
{
"path": "blog-site/src/content/blog/natural-disaster-monitoring-earthquakes-fires-volcanoes.md",
"chars": 10486,
"preview": "---\ntitle: \"Earthquake, Fire, Flood: Real-Time Natural Disaster Monitoring with World Monitor\"\ndescription: \"Track earth"
},
{
"path": "blog-site/src/content/blog/osint-for-everyone-open-source-intelligence-democratized.md",
"chars": 8692,
"preview": "---\ntitle: \"OSINT for Everyone: How World Monitor Democratizes Open Source Intelligence\"\ndescription: \"World Monitor bri"
},
{
"path": "blog-site/src/content/blog/prediction-markets-ai-forecasting-geopolitics.md",
"chars": 10024,
"preview": "---\ntitle: \"Predict What Happens Next: Prediction Markets and AI Forecasting in World Monitor\"\ndescription: \"World Monit"
},
{
"path": "blog-site/src/content/blog/real-time-market-intelligence-for-traders-and-analysts.md",
"chars": 8175,
"preview": "---\ntitle: \"Real-Time Market Intelligence: How Traders Use World Monitor's Finance Dashboard\"\ndescription: \"Monitor 92 s"
},
{
"path": "blog-site/src/content/blog/satellite-imagery-orbital-surveillance.md",
"chars": 7783,
"preview": "---\ntitle: \"Satellite Eyes: How World Monitor Brings Orbital Surveillance to Your Browser\"\ndescription: \"Access satellit"
},
{
"path": "blog-site/src/content/blog/track-global-conflicts-in-real-time.md",
"chars": 10877,
"preview": "---\ntitle: \"Track Global Conflicts in Real Time: World Monitor's Geopolitical Intelligence\"\ndescription: \"Monitor active"
},
{
"path": "blog-site/src/content/blog/tracking-global-trade-routes-chokepoints-freight-costs.md",
"chars": 11384,
"preview": "---\ntitle: \"Tracking Global Trade Routes, Chokepoints, and Freight Costs in Real Time\"\ndescription: \"Track 8 maritime ch"
},
{
"path": "blog-site/src/content/blog/what-is-worldmonitor-real-time-global-intelligence.md",
"chars": 7878,
"preview": "---\ntitle: \"What Is World Monitor? The Free Real-Time Global Intelligence Dashboard\"\ndescription: \"World Monitor is a fr"
},
{
"path": "blog-site/src/content/blog/worldmonitor-in-21-languages-global-intelligence-for-everyone.md",
"chars": 10121,
"preview": "---\ntitle: \"Intelligence Without Borders: World Monitor in 21 Languages\"\ndescription: \"World Monitor supports 21 languag"
},
{
"path": "blog-site/src/content/blog/worldmonitor-vs-traditional-intelligence-tools.md",
"chars": 9459,
"preview": "---\ntitle: \"World Monitor vs. Traditional Intelligence Tools: Why Free and Open Source Wins\"\ndescription: \"Compare World"
},
{
"path": "blog-site/src/content.config.ts",
"chars": 460,
"preview": "import { defineCollection, z } from 'astro:content';\nimport { glob } from 'astro/loaders';\n\nconst blog = defineCollectio"
},
{
"path": "blog-site/src/layouts/Base.astro",
"chars": 5790,
"preview": "---\ninterface Props {\n title: string;\n description: string;\n metaTitle?: string;\n keywords?: string;\n ogType?: stri"
},
{
"path": "blog-site/src/layouts/BlogPost.astro",
"chars": 2869,
"preview": "---\nimport Base from './Base.astro';\n\ninterface Props {\n title: string;\n description: string;\n metaTitle?: string;\n "
},
{
"path": "blog-site/src/pages/index.astro",
"chars": 2763,
"preview": "---\nimport Base from '../layouts/Base.astro';\nimport { getCollection } from 'astro:content';\n\nconst posts = (await getCo"
},
{
"path": "blog-site/src/pages/posts/[...id].astro",
"chars": 647,
"preview": "---\nimport { getCollection, render } from 'astro:content';\nimport BlogPost from '../../layouts/BlogPost.astro';\n\nexport "
},
{
"path": "blog-site/src/pages/rss.xml.ts",
"chars": 1160,
"preview": "import rss from '@astrojs/rss';\nimport { getCollection } from 'astro:content';\n\nexport async function GET(context: { sit"
},
{
"path": "blog-site/src/styles/global.css",
"chars": 11948,
"preview": "@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;"
},
{
"path": "blog-site/tsconfig.json",
"chars": 109,
"preview": "{\n \"extends\": \"astro/tsconfigs/strict\",\n \"include\": [\".astro/types.d.ts\", \"**/*\"],\n \"exclude\": [\"dist\"]\n}\n"
},
{
"path": "convex/_generated/api.d.ts",
"chars": 1078,
"preview": "/* eslint-disable */\n/**\n * Generated `api` utility.\n *\n * THIS CODE IS AUTOMATICALLY GENERATED.\n *\n * To regenerate, ru"
},
{
"path": "convex/_generated/api.js",
"chars": 480,
"preview": "/* eslint-disable */\n/**\n * Generated `api` utility.\n *\n * THIS CODE IS AUTOMATICALLY GENERATED.\n *\n * To regenerate, ru"
},
{
"path": "convex/_generated/dataModel.d.ts",
"chars": 1736,
"preview": "/* eslint-disable */\n/**\n * Generated data model types.\n *\n * THIS CODE IS AUTOMATICALLY GENERATED.\n *\n * To regenerate,"
},
{
"path": "convex/_generated/registerInterest.js",
"chars": 4076,
"preview": "import { mutation, query } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nfunction hashCode(str) {\n l"
},
{
"path": "convex/_generated/schema.js",
"chars": 716,
"preview": "import { defineSchema, defineTable } from \"convex/server\";\nimport { v } from \"convex/values\";\nexport default defineSchem"
},
{
"path": "convex/_generated/server.d.ts",
"chars": 5600,
"preview": "/* eslint-disable */\n/**\n * Generated utilities for implementing server-side Convex query and mutation functions.\n *\n * "
},
{
"path": "convex/_generated/server.js",
"chars": 3696,
"preview": "/* eslint-disable */\n/**\n * Generated utilities for implementing server-side Convex query and mutation functions.\n *\n * "
},
{
"path": "convex/contactMessages.ts",
"chars": 649,
"preview": "import { mutation } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const submit = mutation({\n a"
},
{
"path": "convex/registerInterest.ts",
"chars": 3835,
"preview": "import { mutation, query } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nimport { DatabaseReader, Datab"
},
{
"path": "convex/schema.ts",
"chars": 911,
"preview": "import { defineSchema, defineTable } from \"convex/server\";\nimport { v } from \"convex/values\";\n\nexport default defineSche"
},
{
"path": "convex/tsconfig.json",
"chars": 292,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ESNext\",\n \"lib\": [\"ES2021\"],\n \"module\": \"ESNext\",\n \"moduleResolution\": "
},
{
"path": "data/gamma-irradiators-raw.json",
"chars": 8920,
"preview": "{\n \"source\": \"IAEA DIIF - Tableau Public Visualization\",\n \"url\": \"https://public.tableau.com/app/profile/acceleratorkn"
},
{
"path": "data/gamma-irradiators.json",
"chars": 14328,
"preview": "{\n \"source\": \"IAEA DIIF - Database on Industrial Irradiation Facilities\",\n \"tableauUrl\": \"https://public.tableau.com/a"
},
{
"path": "data/telegram-channels.json",
"chars": 8783,
"preview": "{\n \"version\": 1,\n \"updatedAt\": \"2026-03-19T00:00:00Z\",\n \"note\": \"Product-managed curated list. Not user-configurable."
},
{
"path": "deploy/nginx/brotli-api-proxy.conf",
"chars": 1054,
"preview": "# Nginx API proxy compression baseline for WorldMonitor.\n# Requires ngx_brotli (or Nginx Plus Brotli module) to be insta"
},
{
"path": "docker/.dockerignore",
"chars": 171,
"preview": "node_modules\nnpm-debug.log*\nyarn.lock\n.pnpm-store\n\n.git\n.gitignore\n.github\n\ndist\ncoverage\n.DS_Store\n\nsrc-tauri\ne2e\nscrip"
},
{
"path": "docker/Dockerfile",
"chars": 1290,
"preview": "# syntax=docker/dockerfile:1.7\n\n# Multi-stage build for the World Monitor web app (frontend only).\n\n# Stage 1: Build the"
},
{
"path": "docker/Dockerfile.redis-rest",
"chars": 147,
"preview": "FROM node:22-alpine\nWORKDIR /app\nRUN npm init -y && npm install redis@4\nCOPY redis-rest-proxy.mjs .\nEXPOSE 80\nCMD [\"node"
},
{
"path": "docker/build-handlers.mjs",
"chars": 3520,
"preview": "/**\n * Compiles all API handlers into self-contained ESM bundles so the\n * local-api-server.mjs sidecar can discover and"
},
{
"path": "docker/docker-entrypoint.sh",
"chars": 197,
"preview": "#!/bin/sh\nset -e\nexport API_UPSTREAM=\"${API_UPSTREAM:-https://api.worldmonitor.app}\"\nenvsubst '${API_UPSTREAM}' < /etc/n"
},
{
"path": "docker/entrypoint.sh",
"chars": 614,
"preview": "#!/bin/sh\nset -e\n\n# Docker secrets → env var bridge\n# Reads /run/secrets/KEYNAME files and exports as env vars.\n# Secret"
},
{
"path": "docker/nginx-security-headers.conf",
"chars": 1924,
"preview": "# Security headers — keep in sync with vercel.json \"headers\" (source of truth).\n# Include in every location so add_heade"
},
{
"path": "docker/nginx.conf",
"chars": 3716,
"preview": "worker_processes auto;\nerror_log /dev/stderr warn;\npid /tmp/nginx.pid;\n\nevents {\n worker_connections 1024;\n}\n\nhttp {\n "
},
{
"path": "docker/nginx.conf.template",
"chars": 2680,
"preview": "worker_processes auto;\n\nevents {\n worker_connections 1024;\n}\n\nhttp {\n include /etc/nginx/mime.types;\n default_t"
},
{
"path": "docker/redis-rest-proxy.mjs",
"chars": 6326,
"preview": "#!/usr/bin/env node\n/**\n * Upstash-compatible Redis REST proxy.\n * Translates REST URL paths to raw Redis commands via r"
},
{
"path": "docker/supervisord.conf",
"chars": 529,
"preview": "[supervisord]\nnodaemon=true\nlogfile=/dev/null\nlogfile_maxbytes=0\npidfile=/tmp/supervisord.pid\n\n[program:nginx]\ncommand=/"
},
{
"path": "docker-compose.yml",
"chars": 3568,
"preview": "# =============================================================================\n# World Monitor — Docker / Podman Compos"
},
{
"path": "docs/.mintignore",
"chars": 44,
"preview": "roadmap-pro.md\nPRESS_KIT.md\nDocs_To_Review/\n"
},
{
"path": "docs/.mintlifyignore",
"chars": 158,
"preview": "Docs_To_Review/\ninternal/\nimages/\nroadmap-pro.md\nuser-requests.md\nlocal-backend-audit.md\nPRESS_KIT.md\nCOMMUNITY-PROMOTIO"
},
{
"path": "docs/COMMUNITY-PROMOTION-GUIDE.md",
"chars": 8850,
"preview": "# World Monitor — Community Promotion Guide\n\nThank you for helping spread the word about World Monitor! This guide provi"
},
{
"path": "docs/Docs_To_Review/API_REFERENCE.md",
"chars": 59901,
"preview": "# World Monitor — API Reference\n\n> Comprehensive reference for all Vercel Edge Function endpoints powering the World Mon"
},
{
"path": "docs/Docs_To_Review/ARCHITECTURE.md",
"chars": 40299,
"preview": "# Architecture (DEPRECATED)\n\n> **This document is outdated.** The current architecture reference is [`/ARCHITECTURE.md`]"
},
{
"path": "docs/Docs_To_Review/COMPONENTS.md",
"chars": 43044,
"preview": "# Component Documentation — World Monitor\n\n> Auto-generated reference for all UI components in `src/components/`.\n> Last"
},
{
"path": "docs/Docs_To_Review/DATA_MODEL.md",
"chars": 45245,
"preview": "# Data Model Reference\n\nComprehensive data model documentation for **World Monitor** — an AI-powered real-time global in"
},
{
"path": "docs/Docs_To_Review/DESKTOP_CONFIGURATION.md",
"chars": 1779,
"preview": "# Desktop Runtime Configuration Schema\n\nWorld Monitor desktop now uses a runtime configuration schema with per-feature t"
},
{
"path": "docs/Docs_To_Review/DOCUMENTATION.md",
"chars": 160900,
"preview": "# World Monitor v2\n\nAI-powered real-time global intelligence dashboard aggregating news, markets, geopolitical data, and"
},
{
"path": "docs/Docs_To_Review/EXTERNAL_APIS.md",
"chars": 40161,
"preview": "# External APIs Catalog\n\n> Comprehensive reference for every external API consumed by World Monitor.\n> Last updated: 202"
},
{
"path": "docs/Docs_To_Review/NEWS_TRANSLATION_ANALYSIS.md",
"chars": 2550,
"preview": "# News Translation Analysis\n\n## Current Architecture\n\nThe application fetches news via `src/services/rss.ts`.\n\n- **Mecha"
},
{
"path": "docs/Docs_To_Review/PANELS.md",
"chars": 44372,
"preview": "# Panel System Documentation\n\n> **World Monitor** — Config-driven panel architecture powering three site variants.\n>\n> S"
},
{
"path": "docs/Docs_To_Review/RELEASE_PACKAGING.md",
"chars": 6680,
"preview": "# Desktop Release Packaging Guide (Local, Reproducible)\n\nThis guide provides reproducible local packaging steps for both"
},
{
"path": "docs/Docs_To_Review/STATE_MANAGEMENT.md",
"chars": 24368,
"preview": "# State Management\n\nWorld Monitor is an AI-powered real-time global intelligence dashboard built with **vanilla TypeScri"
},
{
"path": "docs/Docs_To_Review/TAURI_VALIDATION_REPORT.md",
"chars": 3421,
"preview": "# Tauri Validation Report\n\n## Scope\n\nValidated desktop build readiness for the World Monitor Tauri app by checking front"
},
{
"path": "docs/Docs_To_Review/TODO_Performance.md",
"chars": 29463,
"preview": "# World Monitor — Performance Optimization Roadmap\n\nAll items below target **end-user perceived speed**: faster initial "
},
{
"path": "docs/Docs_To_Review/bugs.md",
"chars": 14920,
"preview": "# World Monitor — Bug Registry\n\nBugs are prefixed with `BUG-` and a three-digit number.\nEach entry includes severity, de"
},
{
"path": "docs/Docs_To_Review/local-backend-audit.md",
"chars": 4191,
"preview": "# Local backend parity matrix (desktop sidecar)\n\nThis matrix tracks desktop parity by mapping `src/services/*.ts` consum"
},
{
"path": "docs/Docs_To_Review/todo.md",
"chars": 37003,
"preview": "# World Monitor — Feature & Improvement Roadmap\n\nItems are prefixed with `TODO-` and a three-digit number.\nPriority: 🔴 H"
},
{
"path": "docs/Docs_To_Review/todo_docs.md",
"chars": 22643,
"preview": "# Documentation Roadmap — World Monitor\n\n> **Purpose**: Comprehensive task list to bring project documentation to produc"
},
{
"path": "docs/PRESS_KIT.md",
"chars": 15954,
"preview": "# World Monitor: Press Kit & FAQ\n\n## What Is World Monitor?\n\nWorld Monitor is a real-time global intelligence dashboard "
},
{
"path": "docs/TAURI_VALIDATION_REPORT.md",
"chars": 3421,
"preview": "# Tauri Validation Report\n\n## Scope\n\nValidated desktop build readiness for the World Monitor Tauri app by checking front"
},
{
"path": "docs/adding-endpoints.mdx",
"chars": 14895,
"preview": "---\ntitle: \"Adding API Endpoints\"\ndescription: \"All JSON API endpoints in World Monitor must use sebuf. This guide walks"
},
{
"path": "docs/ai-intelligence.mdx",
"chars": 25224,
"preview": "---\ntitle: \"AI Intelligence\"\ndescription: \"LLM chains, RAG pipelines, threat classification, deduction engines, and brow"
},
{
"path": "docs/algorithms.mdx",
"chars": 29658,
"preview": "---\ntitle: \"Algorithms & Scoring\"\ndescription: \"Detailed documentation of World Monitor's scoring formulas, detection al"
},
{
"path": "docs/api/AviationService.openapi.json",
"chars": 34883,
"preview": "{\"components\":{\"schemas\":{\"AirportDelayAlert\":{\"description\":\"AirportDelayAlert represents a flight delay advisory at an"
},
{
"path": "docs/api/AviationService.openapi.yaml",
"chars": 54902,
"preview": "openapi: 3.1.0\ninfo:\n title: AviationService API\n version: 1.0.0\npaths:\n /api/aviation/v1/list-airport-delays:\n"
},
{
"path": "docs/api/ClimateService.openapi.json",
"chars": 5446,
"preview": "{\"components\":{\"schemas\":{\"ClimateAnomaly\":{\"description\":\"ClimateAnomaly represents a temperature or precipitation devi"
},
{
"path": "docs/api/ClimateService.openapi.yaml",
"chars": 8296,
"preview": "openapi: 3.1.0\ninfo:\n title: ClimateService API\n version: 1.0.0\npaths:\n /api/climate/v1/list-climate-anomalies:"
},
{
"path": "docs/api/ConflictService.openapi.json",
"chars": 15261,
"preview": "{\"components\":{\"schemas\":{\"AcledConflictEvent\":{\"description\":\"AcledConflictEvent represents an armed conflict event fro"
},
{
"path": "docs/api/ConflictService.openapi.yaml",
"chars": 23479,
"preview": "openapi: 3.1.0\ninfo:\n title: ConflictService API\n version: 1.0.0\npaths:\n /api/conflict/v1/list-acled-events:\n "
},
{
"path": "docs/api/CyberService.openapi.json",
"chars": 8234,
"preview": "{\"components\":{\"schemas\":{\"CyberThreat\":{\"description\":\"CyberThreat represents a cyber threat indicator aggregated from "
}
]
// ... and 1149 more files (download for full content)
About this extraction
This page contains the full source code of the koala73/worldmonitor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1349 files (15.0 MB), approximately 4.0M tokens, and a symbol index with 10433 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.