UNPKG

somali-exchange-rates

Version:

πŸ‡ΈπŸ‡΄ Comprehensive Somali Exchange Rates platform with real-time rates, transfer fees, alerts, multi-language support, and advanced financial tools

754 lines (739 loc) β€’ 23.1 kB
#!/usr/bin/env node import { RateStreamServer, calculateTransferFee, compareTransferOptions, convert, createRateStream, exportAnalysisReport, exportRates, getBestTransferOption, getRates, getUserConfig, listRateAlerts, loadUserConfig, quote, removeRateAlert, runConfigWizard, setRateAlert, startAlertMonitoring } from "./chunk-A7YWPFGN.mjs"; import { __require, analyzeMarket, getHistoricalRates, getRateHistory, getSomaliaMarketData, getVolatility } from "./chunk-GBQMRN7V.mjs"; // src/tui.ts import * as blessed from "blessed"; import * as contrib from "blessed-contrib"; var ExchangeRateTUI = class { screen; grid; rateTable; chartLine; alertsList; logBox; statusBar; currentRates = {}; selectedCurrency = "USD"; constructor() { this.screen = blessed.screen({ smartCSR: true, title: "Somali Exchange Rates Monitor" }); this.setupLayout(); this.setupEventHandlers(); this.startDataUpdates(); } setupLayout() { this.grid = new contrib.grid({ rows: 12, cols: 12, screen: this.screen }); this.rateTable = this.grid.set(0, 0, 6, 6, blessed.table, { keys: true, fg: "white", selectedFg: "white", selectedBg: "blue", interactive: true, label: "Exchange Rates (1 SOS = X)", width: "50%", height: "50%", border: { type: "line", fg: "cyan" }, columnSpacing: 2, columnWidth: [8, 12, 10] }); this.chartLine = this.grid.set(0, 6, 6, 6, contrib.line, { style: { line: "yellow", text: "green", baseline: "black" }, xLabelPadding: 3, xPadding: 5, label: `${this.selectedCurrency}/SOS Rate History`, border: { type: "line", fg: "cyan" } }); this.alertsList = this.grid.set(6, 0, 4, 6, blessed.list, { keys: true, fg: "white", selectedFg: "white", selectedBg: "green", interactive: true, label: "Active Alerts", border: { type: "line", fg: "cyan" }, scrollable: true, alwaysScroll: true, scrollbar: { ch: " ", track: { bg: "cyan" }, style: { inverse: true } } }); this.logBox = this.grid.set(6, 6, 4, 6, blessed.log, { fg: "green", selectedFg: "green", label: "Activity Log", border: { type: "line", fg: "cyan" }, scrollable: true, alwaysScroll: true, scrollbar: { ch: " ", track: { bg: "cyan" }, style: { inverse: true } } }); this.statusBar = this.grid.set(10, 0, 2, 12, blessed.text, { content: "Press q to quit, r to refresh, c to change currency, a to view alerts", style: { fg: "white", bg: "blue" }, height: 2 }); } setupEventHandlers() { this.screen.key(["escape", "q", "C-c"], () => { return process.exit(0); }); this.screen.key(["r"], async () => { await this.refreshData(); }); this.screen.key(["c"], () => { this.showCurrencySelector(); }); this.screen.key(["a"], async () => { await this.refreshAlerts(); }); this.rateTable.on("select", (item) => { const currency = item.content.split(" ")[0]; if (currency && currency !== "Currency") { this.selectedCurrency = currency; this.updateChart(); this.log(`Selected currency: ${currency}`); } }); this.alertsList.on("select", (item) => { this.log(`Selected alert: ${item.content}`); }); } async startDataUpdates() { await this.refreshData(); await this.refreshAlerts(); await this.updateChart(); try { const stream = createRateStream(["USD", "EUR", "GBP", "KES", "ETB"]); stream.on("rate-updates", (updates) => { updates.forEach((update) => { this.log(`Rate update: ${update.to} = ${update.rate.toFixed(6)} (${update.changePercent > 0 ? "+" : ""}${update.changePercent.toFixed(2)}%)`); }); this.refreshData(); }); stream.on("error", (error) => { this.log(`Stream error: ${error.message}`); }); this.log("Connected to real-time rate stream"); } catch (error) { this.log("Real-time stream not available, using periodic updates"); setInterval(async () => { await this.refreshData(); }, 6e4); } } async refreshData() { try { this.log("Refreshing exchange rates..."); const rates = await getRates(); this.currentRates = rates; this.updateRateTable(rates); this.log("Exchange rates updated"); } catch (error) { this.log(`Error refreshing rates: ${error}`); } } updateRateTable(rates) { const data = [["Currency", "Rate", "Change"]]; Object.entries(rates).forEach(([currency, rate]) => { if (currency === "SOS") return; const formattedRate = rate.toFixed(6); const change = "\u2014"; data.push([currency, formattedRate, change]); }); this.rateTable.setData(data); this.screen.render(); } async updateChart() { try { this.log(`Loading chart data for ${this.selectedCurrency}...`); const endDate = /* @__PURE__ */ new Date(); const startDate = /* @__PURE__ */ new Date(); startDate.setDate(startDate.getDate() - 30); const history = await getRateHistory( this.selectedCurrency, startDate.toISOString().split("T")[0], endDate.toISOString().split("T")[0] ); const chartData = { title: this.selectedCurrency, style: { line: "red" }, x: history.map((h) => h.date.slice(-5)), // Show MM-DD y: history.map((h) => h.rate) }; this.chartLine.setData([chartData]); this.chartLine.setLabel(`${this.selectedCurrency}/SOS Rate History (30 days)`); this.screen.render(); this.log(`Chart updated for ${this.selectedCurrency}`); } catch (error) { this.log(`Error updating chart: ${error}`); } } async refreshAlerts() { try { const alerts = await listRateAlerts(); const alertItems = alerts.filter((alert) => alert.active).map((alert) => `${alert.from}/${alert.to} ${alert.direction} ${alert.threshold}`); this.alertsList.setItems(alertItems); this.screen.render(); this.log(`Loaded ${alertItems.length} active alerts`); } catch (error) { this.log(`Error loading alerts: ${error}`); } } showCurrencySelector() { const currencies = ["USD", "EUR", "GBP", "KES", "ETB", "AED", "SAR", "TRY", "CNY"]; const list2 = blessed.list({ parent: this.screen, top: "center", left: "center", width: 20, height: 12, border: { type: "line" }, label: "Select Currency", keys: true, vi: true, items: currencies, style: { selected: { bg: "blue" } } }); list2.on("select", (item) => { this.selectedCurrency = item.content; this.updateChart(); list2.destroy(); this.screen.render(); }); list2.on("keypress", (ch, key) => { if (key.name === "escape") { list2.destroy(); this.screen.render(); } }); list2.focus(); this.screen.render(); } log(message) { const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString(); this.logBox.log(`[${timestamp}] ${message}`); this.screen.render(); } run() { this.screen.render(); this.log("Somali Exchange Rates Monitor started"); this.log("Press q to quit, r to refresh, c to change currency, a to view alerts"); } }; function startTUI() { const tui = new ExchangeRateTUI(); tui.run(); } // src/cli.ts function help() { console.log( ` \u{1F1F8}\u{1F1F4} sosx \u2014 Somali Exchange Rates (SOS) BASIC USAGE: sosx rate <FROM> <TO> [AMOUNT] Show conversion (default amount 1) sosx quote <FROM> <TO> [AMOUNT] Pretty formatted conversion sosx rates Show all current rates HISTORICAL DATA: sosx history <CURRENCY> [DAYS] Show rate history (default 30 days) sosx historical <DATE> Get rates for specific date (YYYY-MM-DD) sosx volatility <CURRENCY> [DAYS] Calculate volatility (default 30 days) ALERTS & MONITORING: sosx alert set <FROM> <TO> <THRESHOLD> <above|below> [--email EMAIL] [--webhook URL] sosx alert list List all alerts sosx alert remove <ID> Remove alert by ID sosx monitor Start alert monitoring sosx tui Launch interactive terminal UI sosx stream [PORT] Start WebSocket rate stream server TRANSFER FEES: sosx transfer <AMOUNT> <FROM> <TO> <PROVIDER> <METHOD> sosx compare <AMOUNT> <FROM> <TO> <METHOD> sosx best <AMOUNT> <FROM> <TO> Find best transfer option MARKET ANALYSIS: sosx analyze <FROM> <TO> [PERIOD] Market analysis (default 30d) sosx somalia Somalia regional market data sosx anomaly <FROM> <TO> [THRESHOLD] [WINDOW] EXPORT & REPORTING: sosx export <FORMAT> <CURRENCIES> <PERIOD> [--output FILE] sosx report <CURRENCIES> <PERIOD> [--output FILE] CONFIGURATION: sosx config Show current configuration sosx config wizard Run configuration wizard sosx config set <KEY> <VALUE> Set configuration value sosx config language <LANGUAGE> Set language (en|so|ar) EXAMPLES: sosx rate USD SOS 100 sosx quote SOS USD 500000 sosx history USD 7 sosx alert set USD SOS 600 above --email user@example.com sosx transfer 1000 USD SOS remitly mobile-money sosx analyze USD SOS 30d sosx export csv USD,EUR,GBP 30d sosx tui SUPPORTED CURRENCIES: SOS (Somali Shilling), USD, EUR, GBP, KES, ETB, AED, SAR, TRY, CNY TRANSFER PROVIDERS: western-union, remitly, worldremit, wise TRANSFER METHODS: bank-transfer, cash-pickup, mobile-money For more help: sosx help <command> `.trim() ); } async function main() { const args = process.argv.slice(2); const [cmd, ...params] = args; if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") { if (params[0]) { return showCommandHelp(params[0]); } return help(); } try { await loadUserConfig(); switch (cmd) { case "rate": await handleRate(params); break; case "quote": await handleQuote(params); break; case "rates": await handleRates(); break; case "history": await handleHistory(params); break; case "historical": await handleHistorical(params); break; case "volatility": await handleVolatility(params); break; case "alert": await handleAlert(params); break; case "monitor": await handleMonitor(); break; case "tui": startTUI(); break; case "stream": await handleStream(params); break; case "transfer": await handleTransfer(params); break; case "compare": await handleCompare(params); break; case "best": await handleBest(params); break; case "analyze": await handleAnalyze(params); break; case "somalia": await handleSomalia(); break; case "anomaly": await handleAnomaly(params); break; case "export": await handleExport(params); break; case "report": await handleReport(params); break; case "config": await handleConfig(params); break; default: console.error(`Unknown command: ${cmd}`); console.log("Run 'sosx help' for usage information."); process.exit(1); } } catch (e) { console.error("Error:", e.message || e); process.exit(1); } } async function handleRate(params) { const [from, to, amountStr] = params; if (!from || !to) { console.error("Usage: sosx rate <FROM> <TO> [AMOUNT]"); return; } const amount = amountStr ? Number(amountStr) : 1; const result = await convert(amount, from, to); console.log(result); } async function handleQuote(params) { const [from, to, amountStr] = params; if (!from || !to) { console.error("Usage: sosx quote <FROM> <TO> [AMOUNT]"); return; } const amount = amountStr ? Number(amountStr) : 1; const result = await quote(from, to, amount); console.log(result); } async function handleRates() { const rates = await getRates(); console.log("Current exchange rates (1 SOS = X):"); Object.entries(rates).forEach(([currency, rate]) => { if (currency !== "SOS") { console.log(` ${currency}: ${rate.toFixed(6)}`); } }); } async function handleHistory(params) { const [currency, daysStr] = params; if (!currency) { console.error("Usage: sosx history <CURRENCY> [DAYS]"); return; } const days = daysStr ? parseInt(daysStr) : 30; const endDate = /* @__PURE__ */ new Date(); const startDate = /* @__PURE__ */ new Date(); startDate.setDate(startDate.getDate() - days); const history = await getRateHistory( currency, startDate.toISOString().split("T")[0], endDate.toISOString().split("T")[0] ); console.log(`Rate history for ${currency} (last ${days} days):`); history.forEach((record) => { console.log(` ${record.date}: ${record.rate.toFixed(6)}`); }); } async function handleHistorical(params) { const [date] = params; if (!date) { console.error("Usage: sosx historical <DATE> (YYYY-MM-DD)"); return; } const rates = await getHistoricalRates(date); console.log(`Exchange rates for ${date}:`); Object.entries(rates).forEach(([currency, rate]) => { if (currency !== "SOS") { console.log(` ${currency}: ${rate.toFixed(6)}`); } }); } async function handleVolatility(params) { const [currency, daysStr] = params; if (!currency) { console.error("Usage: sosx volatility <CURRENCY> [DAYS]"); return; } const period = daysStr ? `${daysStr}d` : "30d"; const volatility = await getVolatility(currency, period); console.log(`Volatility for ${currency} (${period}): ${(volatility * 100).toFixed(2)}%`); } async function handleAlert(params) { const [action, ...args] = params; switch (action) { case "set": const [from, to, thresholdStr, direction, ...flags] = args; if (!from || !to || !thresholdStr || !direction) { console.error("Usage: sosx alert set <FROM> <TO> <THRESHOLD> <above|below> [--email EMAIL] [--webhook URL]"); return; } const threshold = Number(thresholdStr); const emailIndex = flags.indexOf("--email"); const webhookIndex = flags.indexOf("--webhook"); const email = emailIndex >= 0 ? flags[emailIndex + 1] : void 0; const webhook = webhookIndex >= 0 ? flags[webhookIndex + 1] : void 0; const newAlertId = await setRateAlert( from, to, threshold, direction, { ...email && { email }, ...webhook && { webhook } } ); console.log(`Alert created with ID: ${newAlertId}`); break; case "list": const alerts = await listRateAlerts(); if (alerts.length === 0) { console.log("No alerts configured."); } else { console.log("Active alerts:"); alerts.forEach((alert) => { console.log(` ${alert.id}: ${alert.from}/${alert.to} ${alert.direction} ${alert.threshold} (${alert.active ? "active" : "inactive"})`); }); } break; case "remove": const [removeAlertId] = args; if (!removeAlertId) { console.error("Usage: sosx alert remove <ID>"); return; } await removeRateAlert(removeAlertId); console.log(`Alert ${removeAlertId} removed.`); break; default: console.error("Usage: sosx alert <set|list|remove> ..."); } } async function handleMonitor() { console.log("Starting alert monitoring..."); startAlertMonitoring(); console.log("Alert monitoring started. Press Ctrl+C to stop."); process.on("SIGINT", () => { console.log("\nStopping alert monitoring..."); process.exit(0); }); setInterval(() => { }, 1e3); } async function handleStream(params) { const port = params[0] ? parseInt(params[0]) : 8080; console.log(`Starting WebSocket rate stream server on port ${port}...`); const server = new RateStreamServer(port); server.startUpdates(); console.log("Rate stream server started. Press Ctrl+C to stop."); process.on("SIGINT", () => { console.log("\nStopping rate stream server..."); server.close(); process.exit(0); }); } async function handleTransfer(params) { const [amountStr, from, to, provider, method] = params; if (!amountStr || !from || !to || !provider || !method) { console.error("Usage: sosx transfer <AMOUNT> <FROM> <TO> <PROVIDER> <METHOD>"); return; } const result = await calculateTransferFee({ amount: Number(amountStr), from, to, provider, method }); console.log(`Transfer Fee Calculation:`); console.log(`Provider: ${result.provider}`); console.log(`Fee: $${result.fee.toFixed(2)}`); console.log(`Exchange Rate: ${result.exchangeRate.toFixed(6)}`); console.log(`Total Cost: $${result.totalCost.toFixed(2)}`); console.log(`Recipient Gets: ${result.recipientAmount.toFixed(2)}`); console.log(`Estimated Time: ${result.estimatedTime}`); } async function handleCompare(params) { const [amountStr, from, to, method] = params; if (!amountStr || !from || !to || !method) { console.error("Usage: sosx compare <AMOUNT> <FROM> <TO> <METHOD>"); return; } const results = await compareTransferOptions( Number(amountStr), from, to, method ); console.log(`Transfer comparison for ${method}:`); results.forEach((result, index) => { console.log(` ${index + 1}. ${result.provider}:`); console.log(` Fee: $${result.fee.toFixed(2)}`); console.log(` Total Cost: $${result.totalCost.toFixed(2)}`); console.log(` Recipient Gets: ${result.recipientAmount.toFixed(2)}`); console.log(` Time: ${result.estimatedTime}`); }); } async function handleBest(params) { const [amountStr, from, to] = params; if (!amountStr || !from || !to) { console.error("Usage: sosx best <AMOUNT> <FROM> <TO>"); return; } const { method, result } = await getBestTransferOption( Number(amountStr), from, to ); console.log(`Best transfer option:`); console.log(`Method: ${method}`); console.log(`Provider: ${result.provider}`); console.log(`Fee: $${result.fee.toFixed(2)}`); console.log(`Total Cost: $${result.totalCost.toFixed(2)}`); console.log(`Recipient Gets: ${result.recipientAmount.toFixed(2)}`); console.log(`Time: ${result.estimatedTime}`); } async function handleAnalyze(params) { const [from, to, period] = params; if (!from || !to) { console.error("Usage: sosx analyze <FROM> <TO> [PERIOD]"); return; } const analysis = await analyzeMarket(from, to, period || "30d"); console.log(`Market Analysis for ${from}/${to}:`); console.log(`Volatility: ${(analysis.volatility * 100).toFixed(2)}%`); console.log(`Trend: ${analysis.trend}`); console.log(`Support: ${analysis.support.toFixed(6)}`); console.log(`Resistance: ${analysis.resistance.toFixed(6)}`); console.log(`RSI: ${analysis.rsi.toFixed(2)}`); console.log(`SMA (7/14/30): ${analysis.sma.map((v) => v.toFixed(6)).join(" / ")}`); console.log(`EMA (7/14/30): ${analysis.ema.map((v) => v.toFixed(6)).join(" / ")}`); } async function handleSomalia() { const data = await getSomaliaMarketData(); console.log("Somalia Regional Market Data:"); Object.entries(data.regions).forEach(([region, info]) => { console.log(` ${region.toUpperCase()}:`); console.log(` Official Rate: ${info.officialRate}`); if (info.blackMarketRate) { console.log(` Black Market Rate: ${info.blackMarketRate}`); console.log(` Spread: ${(info.spread * 100).toFixed(1)}%`); } console.log(` Daily Volume: $${info.volume.toLocaleString()}`); console.log(` Last Updated: ${info.lastUpdated.toLocaleString()}`); }); } async function handleAnomaly(params) { const [from, to, thresholdStr, window] = params; if (!from || !to) { console.error("Usage: sosx anomaly <FROM> <TO> [THRESHOLD] [WINDOW]"); return; } const threshold = thresholdStr ? Number(thresholdStr) : 0.05; const timeWindow = window || "1h"; const { detectAnomalies } = await import("./analysis-DXKAXUTL.mjs"); const result = await detectAnomalies(from, to, { threshold, timeWindow }); console.log(result.message); if (result.anomaly) { console.log(`Deviation: ${(result.deviation * 100).toFixed(2)}%`); } } async function handleExport(params) { const [format, currenciesStr, period, ...flags] = params; if (!format || !currenciesStr || !period) { console.error("Usage: sosx export <FORMAT> <CURRENCIES> <PERIOD> [--output FILE]"); return; } const currencies = currenciesStr.split(","); const outputIndex = flags.indexOf("--output"); const output = outputIndex >= 0 ? flags[outputIndex + 1] : void 0; const exportOptions = { format, currencies, period }; if (output) { exportOptions.output = output; } const filename = await exportRates(exportOptions); console.log(`Data exported to: ${filename}`); } async function handleReport(params) { const [currenciesStr, period, ...flags] = params; if (!currenciesStr || !period) { console.error("Usage: sosx report <CURRENCIES> <PERIOD> [--output FILE]"); return; } const currencies = currenciesStr.split(","); const outputIndex = flags.indexOf("--output"); const output = outputIndex >= 0 ? flags[outputIndex + 1] : void 0; const filename = await exportAnalysisReport(currencies, period, output); console.log(`Analysis report exported to: ${filename}`); } async function handleConfig(params) { const [action, key, value] = params; if (!action) { const config = getUserConfig(); console.log("Current configuration:"); console.log(JSON.stringify(config, null, 2)); return; } switch (action) { case "wizard": await runConfigWizard(); break; case "set": if (!key || !value) { console.error("Usage: sosx config set <KEY> <VALUE>"); return; } console.log(`Setting ${key} = ${value} (not implemented yet)`); break; case "language": if (!key) { console.error("Usage: sosx config language <LANGUAGE>"); return; } console.log(`Setting language to ${key} (not implemented yet)`); break; default: console.error("Usage: sosx config [wizard|set|language]"); } } function showCommandHelp(command) { console.log(`Help for command: ${command}`); console.log("(Detailed help not implemented yet)"); } if (__require.main === module) { main(); } export { help, main };