/** * Unified configuration resolver for OpenFinClaw plugin. * Supports single API key for both Hub and DataHub services. */ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; import type { NewsProviderType, UnifiedPluginConfig } from "./types.js"; const DEFAULT_HUB_API_URL = "https://hub.openfinclaw.ai"; const DEFAULT_DATAHUB_GATEWAY_URL = "https://datahub.openfinclaw.ai"; const DEFAULT_TIMEOUT_MS = 60_000; const DEFAULT_SCAN_CRON = "0 8 * * *"; const DEFAULT_MONITOR_CRON = "*/30 * * * *"; const DEFAULT_WEEKLY_CRON = "0 20 * * 0"; const DEFAULT_MONTHLY_CRON = "0 20 1 * *"; const DEFAULT_SCAN_TZ = "Asia/Shanghai"; const DEFAULT_ALERT_THRESHOLD = 5; const VALID_NEWS_PROVIDERS = new Set(["coingecko", "finnhub", "newsapi"]); function readEnv(keys: string[]): string | undefined { for (const key of keys) { const value = process.env[key]?.trim(); if (value) return value; } return undefined; } /** * Resolve unified plugin configuration from plugin config and environment variables. * Priority: plugin config > env var > default */ export function resolvePluginConfig(api: OpenClawPluginApi): UnifiedPluginConfig { const raw = api.pluginConfig as Record | undefined; const apiKey = (typeof raw?.apiKey === "string" ? raw.apiKey : undefined) ?? (typeof raw?.skillApiKey === "string" ? raw.skillApiKey : undefined) ?? (typeof raw?.datahubApiKey === "string" ? raw.datahubApiKey : undefined) ?? readEnv(["OPENFINCLAW_API_KEY", "SKILL_API_KEY", "DATAHUB_API_KEY"]); const hubApiUrl = (typeof raw?.hubApiUrl === "string" ? raw.hubApiUrl : undefined) ?? (typeof raw?.skillApiUrl === "string" ? raw.skillApiUrl : undefined) ?? readEnv(["HUB_API_URL", "SKILL_API_URL"]) ?? DEFAULT_HUB_API_URL; const datahubGatewayUrl = (typeof raw?.datahubGatewayUrl === "string" ? raw.datahubGatewayUrl : undefined) ?? readEnv(["DATAHUB_GATEWAY_URL", "OPENFINCLAW_DATAHUB_GATEWAY_URL"]) ?? DEFAULT_DATAHUB_GATEWAY_URL; const timeoutRaw = raw?.requestTimeoutMs ?? readEnv(["REQUEST_TIMEOUT_MS", "SKILL_REQUEST_TIMEOUT_MS"]); const requestTimeoutMs = Number(timeoutRaw) >= 5000 && Number(timeoutRaw) <= 300_000 ? Math.floor(Number(timeoutRaw)) : DEFAULT_TIMEOUT_MS; const httpPortRaw = raw?.httpPort ?? readEnv(["OPENFINCLAW_HTTP_PORT"]); const httpPortNum = Number(httpPortRaw); const httpPort = Number.isFinite(httpPortNum) && httpPortNum >= 1024 && httpPortNum <= 65535 ? Math.floor(httpPortNum) : 18792; // ── Scheduler config ── const schedulerEnabled = (raw?.schedulerEnabled ?? readEnv(["OPENFINCLAW_SCHEDULER_ENABLED"])) !== "false"; const scanCronExpr = (typeof raw?.scanCronExpr === "string" ? raw.scanCronExpr : undefined) ?? readEnv(["OPENFINCLAW_SCAN_CRON"]) ?? DEFAULT_SCAN_CRON; const monitorCronExpr = (typeof raw?.monitorCronExpr === "string" ? raw.monitorCronExpr : undefined) ?? readEnv(["OPENFINCLAW_MONITOR_CRON"]) ?? DEFAULT_MONITOR_CRON; const weeklyReportCronExpr = (typeof raw?.weeklyReportCronExpr === "string" ? raw.weeklyReportCronExpr : undefined) ?? readEnv(["OPENFINCLAW_WEEKLY_CRON"]) ?? DEFAULT_WEEKLY_CRON; const monthlyReportCronExpr = (typeof raw?.monthlyReportCronExpr === "string" ? raw.monthlyReportCronExpr : undefined) ?? readEnv(["OPENFINCLAW_MONTHLY_CRON"]) ?? DEFAULT_MONTHLY_CRON; const scanTimezone = (typeof raw?.scanTimezone === "string" ? raw.scanTimezone : undefined) ?? readEnv(["OPENFINCLAW_SCAN_TZ"]) ?? DEFAULT_SCAN_TZ; const cronAgentIdRaw = (typeof raw?.cronAgentId === "string" ? raw.cronAgentId : undefined) ?? readEnv(["OPENFINCLAW_CRON_AGENT_ID"]); const cronAgentId = cronAgentIdRaw?.trim() || undefined; const VALID_SESSION_TARGETS = new Set(["isolated", "main"]); const cronSessionTargetRaw = (typeof raw?.cronSessionTarget === "string" ? raw.cronSessionTarget : undefined) ?? readEnv(["OPENFINCLAW_CRON_SESSION_TARGET"]) ?? "isolated"; const cronSessionTarget = VALID_SESSION_TARGETS.has(cronSessionTargetRaw) ? cronSessionTargetRaw : "isolated"; const VALID_DELIVERY_MODES = new Set(["announce", "none"]); const cronDeliveryModeRaw = (typeof raw?.cronDeliveryMode === "string" ? raw.cronDeliveryMode : undefined) ?? readEnv(["OPENFINCLAW_CRON_DELIVERY_MODE"]) ?? "announce"; const cronDeliveryMode = VALID_DELIVERY_MODES.has(cronDeliveryModeRaw) ? cronDeliveryModeRaw : "announce"; // ── News config ── const newsApiKey = (typeof raw?.newsApiKey === "string" ? raw.newsApiKey : undefined) ?? readEnv(["OPENFINCLAW_NEWS_API_KEY"]); const newsProviderRaw = (typeof raw?.newsProvider === "string" ? raw.newsProvider : undefined) ?? readEnv(["OPENFINCLAW_NEWS_PROVIDER"]) ?? "coingecko"; const newsProvider: NewsProviderType = VALID_NEWS_PROVIDERS.has( newsProviderRaw as NewsProviderType, ) ? (newsProviderRaw as NewsProviderType) : "coingecko"; // ── Alert config ── const alertThresholdRaw = raw?.priceAlertThreshold ?? readEnv(["OPENFINCLAW_ALERT_THRESHOLD"]); const priceAlertThreshold = Number(alertThresholdRaw) > 0 ? Number(alertThresholdRaw) : DEFAULT_ALERT_THRESHOLD; return { apiKey: apiKey && apiKey.length > 0 ? apiKey : undefined, hubApiUrl: hubApiUrl.replace(/\/$/, ""), datahubGatewayUrl: datahubGatewayUrl.replace(/\/+$/, ""), requestTimeoutMs, httpPort, schedulerEnabled, scanCronExpr, monitorCronExpr, weeklyReportCronExpr, monthlyReportCronExpr, scanTimezone, cronAgentId, cronSessionTarget, cronDeliveryMode, newsApiKey: newsApiKey && newsApiKey.length > 0 ? newsApiKey : undefined, newsProvider, priceAlertThreshold, }; }