@iflow-mcp/kitfunso-luminus 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +454 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +488 -0
  5. package/dist/lib/audit.d.ts +3 -0
  6. package/dist/lib/audit.js +66 -0
  7. package/dist/lib/auth.d.ts +26 -0
  8. package/dist/lib/auth.js +199 -0
  9. package/dist/lib/cache.d.ts +25 -0
  10. package/dist/lib/cache.js +38 -0
  11. package/dist/lib/cli.d.ts +1 -0
  12. package/dist/lib/cli.js +10 -0
  13. package/dist/lib/corine.d.ts +31 -0
  14. package/dist/lib/corine.js +137 -0
  15. package/dist/lib/eea-natura2000.d.ts +7 -0
  16. package/dist/lib/eea-natura2000.js +53 -0
  17. package/dist/lib/entsoe-client.d.ts +22 -0
  18. package/dist/lib/entsoe-client.js +69 -0
  19. package/dist/lib/gis-sources.d.ts +33 -0
  20. package/dist/lib/gis-sources.js +392 -0
  21. package/dist/lib/natural-england.d.ts +27 -0
  22. package/dist/lib/natural-england.js +105 -0
  23. package/dist/lib/neso-gsp.d.ts +18 -0
  24. package/dist/lib/neso-gsp.js +113 -0
  25. package/dist/lib/overpass.d.ts +13 -0
  26. package/dist/lib/overpass.js +193 -0
  27. package/dist/lib/profiles.d.ts +23 -0
  28. package/dist/lib/profiles.js +149 -0
  29. package/dist/lib/schema-guard.d.ts +22 -0
  30. package/dist/lib/schema-guard.js +38 -0
  31. package/dist/lib/tool-handler.d.ts +15 -0
  32. package/dist/lib/tool-handler.js +95 -0
  33. package/dist/lib/xml-parser.d.ts +4 -0
  34. package/dist/lib/xml-parser.js +34 -0
  35. package/dist/lib/zone-codes.d.ts +12 -0
  36. package/dist/lib/zone-codes.js +127 -0
  37. package/dist/tools/acer-remit.d.ts +60 -0
  38. package/dist/tools/acer-remit.js +154 -0
  39. package/dist/tools/agricultural-land.d.ts +31 -0
  40. package/dist/tools/agricultural-land.js +210 -0
  41. package/dist/tools/ancillary-prices.d.ts +27 -0
  42. package/dist/tools/ancillary-prices.js +70 -0
  43. package/dist/tools/auctions.d.ts +15 -0
  44. package/dist/tools/auctions.js +89 -0
  45. package/dist/tools/balancing-actions.d.ts +22 -0
  46. package/dist/tools/balancing-actions.js +151 -0
  47. package/dist/tools/balancing.d.ts +21 -0
  48. package/dist/tools/balancing.js +56 -0
  49. package/dist/tools/carbon.d.ts +21 -0
  50. package/dist/tools/carbon.js +68 -0
  51. package/dist/tools/commodity-prices.d.ts +26 -0
  52. package/dist/tools/commodity-prices.js +100 -0
  53. package/dist/tools/compare-sites.d.ts +41 -0
  54. package/dist/tools/compare-sites.js +237 -0
  55. package/dist/tools/demand-forecast.d.ts +21 -0
  56. package/dist/tools/demand-forecast.js +56 -0
  57. package/dist/tools/elexon-bmrs.d.ts +72 -0
  58. package/dist/tools/elexon-bmrs.js +117 -0
  59. package/dist/tools/energi-data.d.ts +72 -0
  60. package/dist/tools/energi-data.js +170 -0
  61. package/dist/tools/energy-charts.d.ts +103 -0
  62. package/dist/tools/energy-charts.js +411 -0
  63. package/dist/tools/entsog.d.ts +71 -0
  64. package/dist/tools/entsog.js +159 -0
  65. package/dist/tools/era5-weather.d.ts +39 -0
  66. package/dist/tools/era5-weather.js +117 -0
  67. package/dist/tools/eu-gas-price.d.ts +38 -0
  68. package/dist/tools/eu-gas-price.js +110 -0
  69. package/dist/tools/fingrid.d.ts +39 -0
  70. package/dist/tools/fingrid.js +158 -0
  71. package/dist/tools/flood-risk.d.ts +33 -0
  72. package/dist/tools/flood-risk.js +166 -0
  73. package/dist/tools/flows.d.ts +23 -0
  74. package/dist/tools/flows.js +61 -0
  75. package/dist/tools/frequency.d.ts +10 -0
  76. package/dist/tools/frequency.js +35 -0
  77. package/dist/tools/gas-storage.d.ts +18 -0
  78. package/dist/tools/gas-storage.js +72 -0
  79. package/dist/tools/generation.d.ts +17 -0
  80. package/dist/tools/generation.js +80 -0
  81. package/dist/tools/grid-connection-intelligence.d.ts +42 -0
  82. package/dist/tools/grid-connection-intelligence.js +122 -0
  83. package/dist/tools/grid-connection-queue.d.ts +64 -0
  84. package/dist/tools/grid-connection-queue.js +198 -0
  85. package/dist/tools/grid-proximity.d.ts +38 -0
  86. package/dist/tools/grid-proximity.js +123 -0
  87. package/dist/tools/hydro-inflows.d.ts +34 -0
  88. package/dist/tools/hydro-inflows.js +114 -0
  89. package/dist/tools/hydro.d.ts +18 -0
  90. package/dist/tools/hydro.js +85 -0
  91. package/dist/tools/imbalance-prices.d.ts +21 -0
  92. package/dist/tools/imbalance-prices.js +56 -0
  93. package/dist/tools/intraday-prices.d.ts +21 -0
  94. package/dist/tools/intraday-prices.js +57 -0
  95. package/dist/tools/intraday-spread.d.ts +24 -0
  96. package/dist/tools/intraday-spread.js +55 -0
  97. package/dist/tools/land-constraints.d.ts +25 -0
  98. package/dist/tools/land-constraints.js +148 -0
  99. package/dist/tools/land-cover.d.ts +18 -0
  100. package/dist/tools/land-cover.js +64 -0
  101. package/dist/tools/lng-terminals.d.ts +22 -0
  102. package/dist/tools/lng-terminals.js +75 -0
  103. package/dist/tools/net-positions.d.ts +19 -0
  104. package/dist/tools/net-positions.js +74 -0
  105. package/dist/tools/nordpool-prices.d.ts +29 -0
  106. package/dist/tools/nordpool-prices.js +80 -0
  107. package/dist/tools/outages.d.ts +28 -0
  108. package/dist/tools/outages.js +107 -0
  109. package/dist/tools/power-plants.d.ts +26 -0
  110. package/dist/tools/power-plants.js +224 -0
  111. package/dist/tools/price-spread-analysis.d.ts +27 -0
  112. package/dist/tools/price-spread-analysis.js +97 -0
  113. package/dist/tools/prices.d.ts +23 -0
  114. package/dist/tools/prices.js +79 -0
  115. package/dist/tools/realtime-generation.d.ts +19 -0
  116. package/dist/tools/realtime-generation.js +141 -0
  117. package/dist/tools/ree-esios.d.ts +78 -0
  118. package/dist/tools/ree-esios.js +216 -0
  119. package/dist/tools/regelleistung.d.ts +28 -0
  120. package/dist/tools/regelleistung.js +71 -0
  121. package/dist/tools/remit-messages.d.ts +23 -0
  122. package/dist/tools/remit-messages.js +110 -0
  123. package/dist/tools/renewable-forecast.d.ts +23 -0
  124. package/dist/tools/renewable-forecast.js +75 -0
  125. package/dist/tools/rte-france.d.ts +72 -0
  126. package/dist/tools/rte-france.js +147 -0
  127. package/dist/tools/screen-site.d.ts +50 -0
  128. package/dist/tools/screen-site.js +288 -0
  129. package/dist/tools/site-revenue.d.ts +50 -0
  130. package/dist/tools/site-revenue.js +147 -0
  131. package/dist/tools/smard-data.d.ts +34 -0
  132. package/dist/tools/smard-data.js +155 -0
  133. package/dist/tools/solar.d.ts +23 -0
  134. package/dist/tools/solar.js +69 -0
  135. package/dist/tools/stormglass.d.ts +56 -0
  136. package/dist/tools/stormglass.js +172 -0
  137. package/dist/tools/terna.d.ts +69 -0
  138. package/dist/tools/terna.js +159 -0
  139. package/dist/tools/terrain-analysis.d.ts +19 -0
  140. package/dist/tools/terrain-analysis.js +120 -0
  141. package/dist/tools/transfer-capacity.d.ts +22 -0
  142. package/dist/tools/transfer-capacity.js +61 -0
  143. package/dist/tools/transmission.d.ts +29 -0
  144. package/dist/tools/transmission.js +159 -0
  145. package/dist/tools/uk-carbon.d.ts +51 -0
  146. package/dist/tools/uk-carbon.js +109 -0
  147. package/dist/tools/uk-grid.d.ts +28 -0
  148. package/dist/tools/uk-grid.js +70 -0
  149. package/dist/tools/us-gas.d.ts +30 -0
  150. package/dist/tools/us-gas.js +100 -0
  151. package/dist/tools/verify-gis-sources.d.ts +25 -0
  152. package/dist/tools/verify-gis-sources.js +119 -0
  153. package/dist/tools/weather.d.ts +27 -0
  154. package/dist/tools/weather.js +120 -0
  155. package/package.json +62 -0
package/dist/index.js ADDED
@@ -0,0 +1,488 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import dotenv from "dotenv";
5
+ import { z } from "zod";
6
+ import { toolHandler } from "./lib/tool-handler.js";
7
+ import { hasRequiredKeys, isKeyConfigured, preloadKeyFile, TOOL_KEY_REQUIREMENTS } from "./lib/auth.js";
8
+ import { logToolCall } from "./lib/audit.js";
9
+ import { parseProfileArg } from "./lib/cli.js";
10
+ import { resolveProfile, getProfileNames, getProfileDescription, isValidProfile, PROFILES, TOTAL_TOOLS, } from "./lib/profiles.js";
11
+ import { generationSchema, getGenerationMix } from "./tools/generation.js";
12
+ import { pricesSchema, getDayAheadPrices } from "./tools/prices.js";
13
+ import { flowsSchema, getCrossBorderFlows } from "./tools/flows.js";
14
+ import { carbonSchema, getCarbonIntensity } from "./tools/carbon.js";
15
+ import { gasStorageSchema, getGasStorage } from "./tools/gas-storage.js";
16
+ import { weatherSchema, getWeatherForecast } from "./tools/weather.js";
17
+ import { usGasSchema, getUsGasData } from "./tools/us-gas.js";
18
+ import { ukCarbonSchema, getUkCarbonIntensity } from "./tools/uk-carbon.js";
19
+ import { ukGridSchema, getUkGridDemand } from "./tools/uk-grid.js";
20
+ import { balancingSchema, getBalancingPrices } from "./tools/balancing.js";
21
+ import { renewableForecastSchema, getRenewableForecast } from "./tools/renewable-forecast.js";
22
+ import { demandForecastSchema, getDemandForecast } from "./tools/demand-forecast.js";
23
+ import { powerPlantsSchema, getPowerPlants } from "./tools/power-plants.js";
24
+ import { auctionSchema, getAuctionResults } from "./tools/auctions.js";
25
+ import { outagesSchema, getOutages } from "./tools/outages.js";
26
+ import { lngTerminalsSchema, getLngTerminals } from "./tools/lng-terminals.js";
27
+ import { solarSchema, getSolarIrradiance } from "./tools/solar.js";
28
+ import { netPositionsSchema, getNetPositions } from "./tools/net-positions.js";
29
+ import { transferCapacitySchema, getTransferCapacity } from "./tools/transfer-capacity.js";
30
+ import { frequencySchema, getEuFrequency } from "./tools/frequency.js";
31
+ import { hydroSchema, getHydroReservoir } from "./tools/hydro.js";
32
+ import { transmissionSchema, getTransmissionLines } from "./tools/transmission.js";
33
+ import { intradayPricesSchema, getIntradayPrices } from "./tools/intraday-prices.js";
34
+ import { imbalancePricesSchema, getImbalancePrices } from "./tools/imbalance-prices.js";
35
+ import { intradaySpreadSchema, getIntradayDaSpread } from "./tools/intraday-spread.js";
36
+ import { realtimeGenerationSchema, getRealtimeGeneration } from "./tools/realtime-generation.js";
37
+ import { balancingActionsSchema, getBalancingActions } from "./tools/balancing-actions.js";
38
+ import { ancillaryPricesSchema, getAncillaryPrices } from "./tools/ancillary-prices.js";
39
+ import { remitMessagesSchema, getRemitMessages } from "./tools/remit-messages.js";
40
+ import { priceSpreadAnalysisSchema, getPriceSpreadAnalysis } from "./tools/price-spread-analysis.js";
41
+ import { euGasPriceSchema, getEuGasPrice } from "./tools/eu-gas-price.js";
42
+ import { energyChartsSchema, getEnergyCharts } from "./tools/energy-charts.js";
43
+ import { commodityPricesSchema, getCommodityPrices } from "./tools/commodity-prices.js";
44
+ import { nordpoolSchema, getNordpoolPrices } from "./tools/nordpool-prices.js";
45
+ import { smardSchema, getSmardData } from "./tools/smard-data.js";
46
+ import { entsogSchema, getEntsogData } from "./tools/entsog.js";
47
+ import { elexonBmrsSchema, getElexonBmrs } from "./tools/elexon-bmrs.js";
48
+ import { era5WeatherSchema, getEra5Weather } from "./tools/era5-weather.js";
49
+ import { regelleistungSchema, getRegelleistung } from "./tools/regelleistung.js";
50
+ import { rteFranceSchema, getRteFrance } from "./tools/rte-france.js";
51
+ import { energiDataSchema, getEnergiData } from "./tools/energi-data.js";
52
+ import { fingridSchema, getFingridData } from "./tools/fingrid.js";
53
+ import { hydroInflowsSchema, getHydroInflows } from "./tools/hydro-inflows.js";
54
+ import { acerRemitSchema, getAcerRemit } from "./tools/acer-remit.js";
55
+ import { ternaSchema, getTernaData } from "./tools/terna.js";
56
+ import { reeEsiosSchema, getReeEsios } from "./tools/ree-esios.js";
57
+ import { stormglassSchema, getStormglass } from "./tools/stormglass.js";
58
+ import { terrainAnalysisSchema, getTerrainAnalysis } from "./tools/terrain-analysis.js";
59
+ import { gridProximitySchema, getGridProximity } from "./tools/grid-proximity.js";
60
+ import { gridConnectionQueueSchema, getGridConnectionQueue } from "./tools/grid-connection-queue.js";
61
+ import { gridConnectionIntelligenceSchema, getGridConnectionIntelligence } from "./tools/grid-connection-intelligence.js";
62
+ import { landConstraintsSchema, getLandConstraints } from "./tools/land-constraints.js";
63
+ import { landCoverSchema, getLandCover } from "./tools/land-cover.js";
64
+ import { agriculturalLandSchema, getAgriculturalLand } from "./tools/agricultural-land.js";
65
+ import { floodRiskSchema, getFloodRisk } from "./tools/flood-risk.js";
66
+ import { screenSiteSchema, screenSite } from "./tools/screen-site.js";
67
+ import { verifyGisSourcesSchema, verifyGisSources } from "./tools/verify-gis-sources.js";
68
+ import { compareSitesSchema, compareSites } from "./tools/compare-sites.js";
69
+ import { siteRevenueSchema, estimateSiteRevenue } from "./tools/site-revenue.js";
70
+ // ---------------------------------------------------------------------------
71
+ // Startup configuration
72
+ // ---------------------------------------------------------------------------
73
+ dotenv.config();
74
+ let profile = "full";
75
+ try {
76
+ profile = parseProfileArg(process.argv);
77
+ }
78
+ catch (err) {
79
+ process.stderr.write(`[luminus] ${err instanceof Error ? err.message : String(err)} ` +
80
+ `Valid profiles: full, ${getProfileNames().join(", ")}\n`);
81
+ process.exit(1);
82
+ }
83
+ if (!isValidProfile(profile)) {
84
+ process.stderr.write(`[luminus] Unknown profile "${profile}". ` +
85
+ `Valid profiles: full, ${getProfileNames().join(", ")}\n`);
86
+ process.exit(1);
87
+ }
88
+ const allowedTools = resolveProfile(profile) ?? null; // null = all tools
89
+ const skippedByProfile = [];
90
+ const skippedByKeys = [];
91
+ function shouldRegister(toolName) {
92
+ if (allowedTools && !allowedTools.includes(toolName)) {
93
+ skippedByProfile.push(toolName);
94
+ return false;
95
+ }
96
+ if (!hasRequiredKeys(toolName)) {
97
+ skippedByKeys.push(toolName);
98
+ return false;
99
+ }
100
+ return true;
101
+ }
102
+ /** Track registered data tool names for discovery/status meta-tools. */
103
+ const registeredToolNames = [];
104
+ const server = new McpServer({
105
+ name: "luminus",
106
+ version: "0.2.0",
107
+ });
108
+ /**
109
+ * Wrap toolHandler with audit logging. Logs tool name and params
110
+ * before delegating to the actual handler.
111
+ */
112
+ function auditedToolHandler(toolName, schema, handler) {
113
+ const inner = toolHandler(schema, handler);
114
+ return async (params) => {
115
+ logToolCall(toolName, (params ?? {}));
116
+ return inner(params);
117
+ };
118
+ }
119
+ // ---------------------------------------------------------------------------
120
+ // Conditional tool registration
121
+ // ---------------------------------------------------------------------------
122
+ function registerDataTools() {
123
+ // --- Generation & Prices ---
124
+ if (shouldRegister("get_generation_mix")) {
125
+ registeredToolNames.push("get_generation_mix");
126
+ server.tool("get_generation_mix", "Current generation mix by fuel type (MW) for a European zone. Wind, solar, gas, nuclear, hydro, coal, etc.", generationSchema.shape, auditedToolHandler("get_generation_mix", generationSchema, getGenerationMix));
127
+ }
128
+ if (shouldRegister("get_day_ahead_prices")) {
129
+ registeredToolNames.push("get_day_ahead_prices");
130
+ server.tool("get_day_ahead_prices", "Day-ahead hourly prices (EUR/MWh) for a European zone. Includes min/max/mean stats.", pricesSchema.shape, auditedToolHandler("get_day_ahead_prices", pricesSchema, getDayAheadPrices));
131
+ }
132
+ if (shouldRegister("get_cross_border_flows")) {
133
+ registeredToolNames.push("get_cross_border_flows");
134
+ server.tool("get_cross_border_flows", "Cross-border electricity flows (MW) between two European zones. Hourly data with stats.", flowsSchema.shape, auditedToolHandler("get_cross_border_flows", flowsSchema, getCrossBorderFlows));
135
+ }
136
+ if (shouldRegister("get_carbon_intensity")) {
137
+ registeredToolNames.push("get_carbon_intensity");
138
+ server.tool("get_carbon_intensity", "Carbon intensity (gCO2/kWh) for a European zone. Fuel breakdown, emission factors, renewable/fossil %.", carbonSchema.shape, auditedToolHandler("get_carbon_intensity", carbonSchema, getCarbonIntensity));
139
+ }
140
+ if (shouldRegister("get_gas_storage")) {
141
+ registeredToolNames.push("get_gas_storage");
142
+ server.tool("get_gas_storage", "Gas storage levels (TWh, % fill, injection/withdrawal, YoY trend) from GIE AGSI+.", gasStorageSchema.shape, auditedToolHandler("get_gas_storage", gasStorageSchema, getGasStorage));
143
+ }
144
+ if (shouldRegister("get_weather_forecast")) {
145
+ registeredToolNames.push("get_weather_forecast");
146
+ server.tool("get_weather_forecast", "Weather forecast: hourly temperature, wind speed, solar radiation. Accepts country code or lat/lon.", weatherSchema.shape, auditedToolHandler("get_weather_forecast", weatherSchema, getWeatherForecast));
147
+ }
148
+ if (shouldRegister("get_us_gas_data")) {
149
+ registeredToolNames.push("get_us_gas_data");
150
+ server.tool("get_us_gas_data", "US gas data from EIA: weekly storage (Bcf), Henry Hub prices (USD/MMBtu).", usGasSchema.shape, auditedToolHandler("get_us_gas_data", usGasSchema, getUsGasData));
151
+ }
152
+ // --- UK Specific ---
153
+ if (shouldRegister("get_uk_carbon_intensity")) {
154
+ registeredToolNames.push("get_uk_carbon_intensity");
155
+ server.tool("get_uk_carbon_intensity", "UK carbon intensity (gCO2/kWh) and generation mix from National Grid ESO. National, regional, or historical.", ukCarbonSchema.shape, auditedToolHandler("get_uk_carbon_intensity", ukCarbonSchema, getUkCarbonIntensity));
156
+ }
157
+ if (shouldRegister("get_uk_grid_demand")) {
158
+ registeredToolNames.push("get_uk_grid_demand");
159
+ server.tool("get_uk_grid_demand", "UK demand (MW actual + forecast) and grid frequency (Hz) from National Grid ESO.", ukGridSchema.shape, auditedToolHandler("get_uk_grid_demand", ukGridSchema, getUkGridDemand));
160
+ }
161
+ // --- Balancing & Forecasts ---
162
+ if (shouldRegister("get_balancing_prices")) {
163
+ registeredToolNames.push("get_balancing_prices");
164
+ server.tool("get_balancing_prices", "Balancing/imbalance prices (EUR/MWh) per settlement period for a European zone. Min/max/mean stats.", balancingSchema.shape, auditedToolHandler("get_balancing_prices", balancingSchema, getBalancingPrices));
165
+ }
166
+ if (shouldRegister("get_renewable_forecast")) {
167
+ registeredToolNames.push("get_renewable_forecast");
168
+ server.tool("get_renewable_forecast", "Day-ahead wind/solar forecast (MW) for a European zone. Hourly, per source (onshore, offshore, solar).", renewableForecastSchema.shape, auditedToolHandler("get_renewable_forecast", renewableForecastSchema, getRenewableForecast));
169
+ }
170
+ if (shouldRegister("get_demand_forecast")) {
171
+ registeredToolNames.push("get_demand_forecast");
172
+ server.tool("get_demand_forecast", "Day-ahead demand forecast (MW) for a European zone. Hourly with min/max/mean and total energy.", demandForecastSchema.shape, auditedToolHandler("get_demand_forecast", demandForecastSchema, getDemandForecast));
173
+ }
174
+ // --- Grid Infrastructure ---
175
+ if (shouldRegister("get_power_plants")) {
176
+ registeredToolNames.push("get_power_plants");
177
+ server.tool("get_power_plants", "European power plant registry from OPSD. Name, capacity (MW), fuel, location, year. Filter by country/fuel/capacity.", powerPlantsSchema.shape, auditedToolHandler("get_power_plants", powerPlantsSchema, getPowerPlants));
178
+ }
179
+ if (shouldRegister("get_auction_results")) {
180
+ registeredToolNames.push("get_auction_results");
181
+ server.tool("get_auction_results", "Cross-border capacity auction results from JAO. Allocated MW, price (EUR/MW), offered capacity per corridor.", auctionSchema.shape, auditedToolHandler("get_auction_results", auctionSchema, getAuctionResults));
182
+ }
183
+ if (shouldRegister("get_outages")) {
184
+ registeredToolNames.push("get_outages");
185
+ server.tool("get_outages", "Generation/transmission outages from ENTSO-E. Unit, fuel, available/unavailable MW, dates, reason.", outagesSchema.shape, auditedToolHandler("get_outages", outagesSchema, getOutages));
186
+ }
187
+ if (shouldRegister("get_lng_terminals")) {
188
+ registeredToolNames.push("get_lng_terminals");
189
+ server.tool("get_lng_terminals", "LNG terminal data from GIE ALSI. Inventory (mcm), send-out, capacity, days-to-storage per terminal.", lngTerminalsSchema.shape, auditedToolHandler("get_lng_terminals", lngTerminalsSchema, getLngTerminals));
190
+ }
191
+ if (shouldRegister("get_solar_irradiance")) {
192
+ registeredToolNames.push("get_solar_irradiance");
193
+ server.tool("get_solar_irradiance", "Solar irradiance and PV yield from PVGIS. Monthly kWh/m2, optimal angle, annual yield. No API key.", solarSchema.shape, auditedToolHandler("get_solar_irradiance", solarSchema, getSolarIrradiance));
194
+ }
195
+ if (shouldRegister("get_net_positions")) {
196
+ registeredToolNames.push("get_net_positions");
197
+ server.tool("get_net_positions", "Net import/export position (MW) for a European zone. Total + per-border breakdown. Positive = net importer.", netPositionsSchema.shape, auditedToolHandler("get_net_positions", netPositionsSchema, getNetPositions));
198
+ }
199
+ if (shouldRegister("get_transfer_capacities")) {
200
+ registeredToolNames.push("get_transfer_capacities");
201
+ server.tool("get_transfer_capacities", "Net transfer capacity (MW) between two European zones from ENTSO-E. Hourly NTC with min/max/mean.", transferCapacitySchema.shape, auditedToolHandler("get_transfer_capacities", transferCapacitySchema, getTransferCapacity));
202
+ }
203
+ if (shouldRegister("get_eu_frequency")) {
204
+ registeredToolNames.push("get_eu_frequency");
205
+ server.tool("get_eu_frequency", "Real-time EU grid frequency (Hz), deviation (mHz), status. Deviations signal supply-demand imbalance.", frequencySchema.shape, auditedToolHandler("get_eu_frequency", frequencySchema, getEuFrequency));
206
+ }
207
+ if (shouldRegister("get_hydro_reservoir")) {
208
+ registeredToolNames.push("get_hydro_reservoir");
209
+ server.tool("get_hydro_reservoir", "Hydro reservoir fill (MWh) from ENTSO-E. Weekly data. Best coverage: NO, SE, AT, CH, ES, PT.", hydroSchema.shape, auditedToolHandler("get_hydro_reservoir", hydroSchema, getHydroReservoir));
210
+ }
211
+ if (shouldRegister("get_transmission_lines")) {
212
+ registeredToolNames.push("get_transmission_lines");
213
+ server.tool("get_transmission_lines", "HV transmission lines from OSM. Voltage (kV), operator, cables, coordinates. Filter by country/bbox. 220kV+ default. Rate-limited.", transmissionSchema.shape, auditedToolHandler("get_transmission_lines", transmissionSchema, getTransmissionLines));
214
+ }
215
+ // --- Intraday & Balancing ---
216
+ if (shouldRegister("get_intraday_prices")) {
217
+ registeredToolNames.push("get_intraday_prices");
218
+ server.tool("get_intraday_prices", "Intraday continuous electricity prices (EUR/MWh) for a European zone. Hourly with stats.", intradayPricesSchema.shape, auditedToolHandler("get_intraday_prices", intradayPricesSchema, getIntradayPrices));
219
+ }
220
+ if (shouldRegister("get_imbalance_prices")) {
221
+ registeredToolNames.push("get_imbalance_prices");
222
+ server.tool("get_imbalance_prices", "Imbalance settlement prices (EUR/MWh) per period. Price for deviations from scheduled position.", imbalancePricesSchema.shape, auditedToolHandler("get_imbalance_prices", imbalancePricesSchema, getImbalancePrices));
223
+ }
224
+ if (shouldRegister("get_intraday_da_spread")) {
225
+ registeredToolNames.push("get_intraday_da_spread");
226
+ server.tool("get_intraday_da_spread", "Intraday vs day-ahead spread per hour. Directional signal: premium = post-auction change (outage, forecast miss, demand spike).", intradaySpreadSchema.shape, auditedToolHandler("get_intraday_da_spread", intradaySpreadSchema, getIntradayDaSpread));
227
+ }
228
+ if (shouldRegister("get_realtime_generation")) {
229
+ registeredToolNames.push("get_realtime_generation");
230
+ server.tool("get_realtime_generation", "Real-time generation by fuel type (MW). ENTSO-E for EU, Elexon BMRS for GB. 5-15 min resolution.", realtimeGenerationSchema.shape, auditedToolHandler("get_realtime_generation", realtimeGenerationSchema, getRealtimeGeneration));
231
+ }
232
+ if (shouldRegister("get_balancing_actions")) {
233
+ registeredToolNames.push("get_balancing_actions");
234
+ server.tool("get_balancing_actions", "Activated balancing energy (MW): up/down regulation per period. ENTSO-E for EU, Elexon BOD for GB.", balancingActionsSchema.shape, auditedToolHandler("get_balancing_actions", balancingActionsSchema, getBalancingActions));
235
+ }
236
+ // --- BESS & Ancillary ---
237
+ if (shouldRegister("get_ancillary_prices")) {
238
+ registeredToolNames.push("get_ancillary_prices");
239
+ server.tool("get_ancillary_prices", "FCR/aFRR/mFRR reserve prices (EUR/MW) per period. Key BESS revenue stream (30-50%, often 3-5x arbitrage).", ancillaryPricesSchema.shape, auditedToolHandler("get_ancillary_prices", ancillaryPricesSchema, getAncillaryPrices));
240
+ }
241
+ if (shouldRegister("get_remit_messages")) {
242
+ registeredToolNames.push("get_remit_messages");
243
+ server.tool("get_remit_messages", "REMIT urgent market messages: forced outages, capacity reductions, market-moving events. Early spike signal.", remitMessagesSchema.shape, auditedToolHandler("get_remit_messages", remitMessagesSchema, getRemitMessages));
244
+ }
245
+ if (shouldRegister("get_price_spread_analysis")) {
246
+ registeredToolNames.push("get_price_spread_analysis");
247
+ server.tool("get_price_spread_analysis", "BESS arbitrage analysis: optimal charge/discharge schedule, revenue per MW, signal strength.", priceSpreadAnalysisSchema.shape, auditedToolHandler("get_price_spread_analysis", priceSpreadAnalysisSchema, getPriceSpreadAnalysis));
248
+ }
249
+ // --- Gas & LNG ---
250
+ if (shouldRegister("get_eu_gas_price")) {
251
+ registeredToolNames.push("get_eu_gas_price");
252
+ server.tool("get_eu_gas_price", "EU gas prices (EUR/MWh): TTF or NBP. Latest + daily history. No API key. Spark spread, gas-power switching.", euGasPriceSchema.shape, auditedToolHandler("get_eu_gas_price", euGasPriceSchema, getEuGasPrice));
253
+ }
254
+ // --- Regional Specialists ---
255
+ if (shouldRegister("get_energy_charts")) {
256
+ registeredToolNames.push("get_energy_charts");
257
+ server.tool("get_energy_charts", "Energy-Charts (Fraunhofer ISE): prices (15-min), generation by fuel, cross-border flows. All EU except GB. No API key. Faster than ENTSO-E.", energyChartsSchema.shape, auditedToolHandler("get_energy_charts", energyChartsSchema, getEnergyCharts));
258
+ }
259
+ if (shouldRegister("get_commodity_prices")) {
260
+ registeredToolNames.push("get_commodity_prices");
261
+ server.tool("get_commodity_prices", "EU commodity prices: EUA carbon, Brent crude, TTF gas. Latest + 5-day history + stats. No API key.", commodityPricesSchema.shape, auditedToolHandler("get_commodity_prices", commodityPricesSchema, getCommodityPrices));
262
+ }
263
+ if (shouldRegister("get_nordpool_prices")) {
264
+ registeredToolNames.push("get_nordpool_prices");
265
+ server.tool("get_nordpool_prices", "Nordic/Baltic day-ahead prices (15-min) from Nordpool. SE1-4, NO1-5, DK1-2, FI. No API key.", nordpoolSchema.shape, auditedToolHandler("get_nordpool_prices", nordpoolSchema, getNordpoolPrices));
266
+ }
267
+ if (shouldRegister("get_smard_data")) {
268
+ registeredToolNames.push("get_smard_data");
269
+ server.tool("get_smard_data", "German electricity from SMARD (BNetzA): hourly generation, consumption, market data. No API key.", smardSchema.shape, auditedToolHandler("get_smard_data", smardSchema, getSmardData));
270
+ }
271
+ if (shouldRegister("get_entsog_data")) {
272
+ registeredToolNames.push("get_entsog_data");
273
+ server.tool("get_entsog_data", "ENTSOG gas pipeline data: physical flows (GWh/d), nominations, interruptions, capacities. All EU TSOs. No API key.", entsogSchema.shape, auditedToolHandler("get_entsog_data", entsogSchema, getEntsogData));
274
+ }
275
+ if (shouldRegister("get_elexon_bmrs")) {
276
+ registeredToolNames.push("get_elexon_bmrs");
277
+ server.tool("get_elexon_bmrs", "GB balancing mechanism from Elexon BMRS: cashout prices, generation by fuel, bids/offers, system warnings, interconnectors. No API key.", elexonBmrsSchema.shape, auditedToolHandler("get_elexon_bmrs", elexonBmrsSchema, getElexonBmrs));
278
+ }
279
+ if (shouldRegister("get_era5_weather")) {
280
+ registeredToolNames.push("get_era5_weather");
281
+ server.tool("get_era5_weather", "ERA5 weather reanalysis via Open-Meteo: hourly wind (10m/100m), solar (GHI/DNI), temperature. 1940 to ~5 days ago. No API key.", era5WeatherSchema.shape, auditedToolHandler("get_era5_weather", era5WeatherSchema, getEra5Weather));
282
+ }
283
+ if (shouldRegister("get_regelleistung")) {
284
+ registeredToolNames.push("get_regelleistung");
285
+ server.tool("get_regelleistung", "Regelleistung.net: FCR/aFRR/mFRR reserve tender prices and volumes. Primary BESS revenue data source.", regelleistungSchema.shape, auditedToolHandler("get_regelleistung", regelleistungSchema, getRegelleistung));
286
+ }
287
+ if (shouldRegister("get_rte_france")) {
288
+ registeredToolNames.push("get_rte_france");
289
+ server.tool("get_rte_france", "French electricity from RTE (eco2mix): real-time generation, consumption, exchanges, outages. No API key.", rteFranceSchema.shape, auditedToolHandler("get_rte_france", rteFranceSchema, getRteFrance));
290
+ }
291
+ if (shouldRegister("get_energi_data")) {
292
+ registeredToolNames.push("get_energi_data");
293
+ server.tool("get_energi_data", "Danish electricity from Energi Data Service: CO2 emissions, production, spot prices (DK1/DK2), balance. No API key.", energiDataSchema.shape, auditedToolHandler("get_energi_data", energiDataSchema, getEnergiData));
294
+ }
295
+ if (shouldRegister("get_fingrid_data")) {
296
+ registeredToolNames.push("get_fingrid_data");
297
+ server.tool("get_fingrid_data", "Finnish grid from Fingrid: consumption, generation, imports/exports, frequency, reserve prices. 3-min resolution. Requires FINGRID_API_KEY.", fingridSchema.shape, auditedToolHandler("get_fingrid_data", fingridSchema, getFingridData));
298
+ }
299
+ // --- Hydropower ---
300
+ if (shouldRegister("get_hydro_inflows")) {
301
+ registeredToolNames.push("get_hydro_inflows");
302
+ server.tool("get_hydro_inflows", "Hydro inflow proxy from ERA5-Land: precipitation, snowfall, snowmelt, temperature for NO/SE/CH/AT/FR/IT/ES/PT/FI/RO. No API key.", hydroInflowsSchema.shape, auditedToolHandler("get_hydro_inflows", hydroInflowsSchema, getHydroInflows));
303
+ }
304
+ if (shouldRegister("get_acer_remit")) {
305
+ registeredToolNames.push("get_acer_remit");
306
+ server.tool("get_acer_remit", "ACER REMIT: UMMs and outage events from Inside Information Platforms. Forced outages, capacity reductions under EU REMIT.", acerRemitSchema.shape, auditedToolHandler("get_acer_remit", acerRemitSchema, getAcerRemit));
307
+ }
308
+ if (shouldRegister("get_terna_data")) {
309
+ registeredToolNames.push("get_terna_data");
310
+ server.tool("get_terna_data", "Italian electricity from Terna: generation, demand, exchanges, zonal prices. NORD/CNOR/CSUD/SUD/SICI/SARD zones.", ternaSchema.shape, auditedToolHandler("get_terna_data", ternaSchema, getTernaData));
311
+ }
312
+ if (shouldRegister("get_ree_esios")) {
313
+ registeredToolNames.push("get_ree_esios");
314
+ server.tool("get_ree_esios", "Spanish electricity from REE ESIOS: prices, demand, generation, wind/solar forecast, interconnectors. Requires ESIOS_API_TOKEN.", reeEsiosSchema.shape, auditedToolHandler("get_ree_esios", reeEsiosSchema, getReeEsios));
315
+ }
316
+ // --- Weather ---
317
+ if (shouldRegister("get_stormglass")) {
318
+ registeredToolNames.push("get_stormglass");
319
+ server.tool("get_stormglass", "Marine/offshore weather from Storm Glass: wind, waves, swell, SST, visibility. 48h forecast. Requires STORMGLASS_API_KEY. 10 req/day free.", stormglassSchema.shape, auditedToolHandler("get_stormglass", stormglassSchema, getStormglass));
320
+ }
321
+ // --- GIS Site Prospecting ---
322
+ if (shouldRegister("get_terrain_analysis")) {
323
+ registeredToolNames.push("get_terrain_analysis");
324
+ server.tool("get_terrain_analysis", "Terrain analysis for a location: elevation (m), slope (degrees), aspect (cardinal), flatness score. Uses Open-Meteo elevation API (Copernicus EU-DEM). No API key.", terrainAnalysisSchema.shape, auditedToolHandler("get_terrain_analysis", terrainAnalysisSchema, getTerrainAnalysis));
325
+ }
326
+ if (shouldRegister("get_grid_proximity")) {
327
+ registeredToolNames.push("get_grid_proximity");
328
+ server.tool("get_grid_proximity", "Nearest grid infrastructure (substations, HV lines) within a radius. Distance, voltage, operator. Uses OSM Overpass. No API key.", gridProximitySchema.shape, auditedToolHandler("get_grid_proximity", gridProximitySchema, getGridProximity));
329
+ }
330
+ if (shouldRegister("get_grid_connection_queue")) {
331
+ registeredToolNames.push("get_grid_connection_queue");
332
+ server.tool("get_grid_connection_queue", "GB transmission connection-register signal from NESO's public TEC register. Search by connection site, project, host TO, technology, status, or agreement type and get matched projects plus aggregated MW by connection site. This is not a DNO headroom map or a guaranteed connection offer. No API key.", gridConnectionQueueSchema.shape, auditedToolHandler("get_grid_connection_queue", gridConnectionQueueSchema, getGridConnectionQueue));
333
+ }
334
+ if (shouldRegister("get_grid_connection_intelligence")) {
335
+ registeredToolNames.push("get_grid_connection_intelligence");
336
+ server.tool("get_grid_connection_intelligence", "GB grid connection intelligence: finds nearest GSP, queries the TEC register for connection activity at that GSP, and shows nearby substations. Combines spatial GSP lookup with NESO queue data. Not a connection offer or capacity guarantee.", gridConnectionIntelligenceSchema.shape, auditedToolHandler("get_grid_connection_intelligence", gridConnectionIntelligenceSchema, getGridConnectionIntelligence));
337
+ }
338
+ if (shouldRegister("get_land_constraints")) {
339
+ registeredToolNames.push("get_land_constraints");
340
+ server.tool("get_land_constraints", "Land-constraint screening within a radius. GB uses Natural England protected areas, EU member states use EEA Natura 2000 protected sites. Hard exclusion check for PV/BESS siting. No API key.", landConstraintsSchema.shape, auditedToolHandler("get_land_constraints", landConstraintsSchema, getLandConstraints));
341
+ }
342
+ if (shouldRegister("get_land_cover")) {
343
+ registeredToolNames.push("get_land_cover");
344
+ server.tool("get_land_cover", "Land-cover classification for a point using CORINE Land Cover 2018. Returns the 3-digit CLC code, human label, top-level land-cover group, and a conservative planning-exclusion flag for wetlands, water bodies, and woodland. EU27 + EEA/EFTA only. Great Britain is not covered by CORINE 2018. No API key.", landCoverSchema.shape, auditedToolHandler("get_land_cover", landCoverSchema, getLandCover));
345
+ }
346
+ if (shouldRegister("get_agricultural_land")) {
347
+ registeredToolNames.push("get_agricultural_land");
348
+ server.tool("get_agricultural_land", "Agricultural Land Classification for an English site. Prefers detailed post-1988 Natural England surveys, falls back to provisional ALC, and flags Best and Most Versatile land risk. GB input, England coverage only. No API key.", agriculturalLandSchema.shape, auditedToolHandler("get_agricultural_land", agriculturalLandSchema, getAgriculturalLand));
349
+ }
350
+ if (shouldRegister("get_flood_risk")) {
351
+ registeredToolNames.push("get_flood_risk");
352
+ server.tool("get_flood_risk", "Flood-planning screen for an English site. Checks Environment Agency Flood Zone 2, Flood Zone 3, and flood storage areas, then summarises planning risk. GB input, England coverage only. No API key.", floodRiskSchema.shape, auditedToolHandler("get_flood_risk", floodRiskSchema, getFloodRisk));
353
+ }
354
+ if (shouldRegister("screen_site")) {
355
+ registeredToolNames.push("screen_site");
356
+ server.tool("screen_site", "Composite PV/BESS site screening for GB and EU locations. GB: terrain, grid, solar, land constraints, agricultural land, flood risk. EU: terrain, grid, solar, land constraints (Natura 2000), land cover (CORINE). Returns pass/warn/fail verdict with layers_available/layers_unavailable. No API key.", screenSiteSchema.shape, auditedToolHandler("screen_site", screenSiteSchema, screenSite));
357
+ }
358
+ if (shouldRegister("verify_gis_sources")) {
359
+ registeredToolNames.push("verify_gis_sources");
360
+ server.tool("verify_gis_sources", "Health check for GIS data sources. Pings each upstream provider or dataset endpoint (Open-Meteo, Overpass, NESO TEC register, Natural England protected areas, EEA Natura 2000, CORINE Land Cover, Natural England ALC, Environment Agency Flood Map, PVGIS) and reports status, response time, and source metadata. Use before relying on GIS tool results.", verifyGisSourcesSchema.shape, auditedToolHandler("verify_gis_sources", verifyGisSourcesSchema, verifyGisSources));
361
+ }
362
+ if (shouldRegister("compare_sites")) {
363
+ registeredToolNames.push("compare_sites");
364
+ server.tool("compare_sites", "Compare and rank 2-10 candidate PV/BESS sites. Runs screen_site on each point, then scores and ranks by verdict, solar resource, grid proximity, and terrain. Returns transparent heuristic reasoning. GB and EU member states supported. No API key.", compareSitesSchema.shape, auditedToolHandler("compare_sites", compareSitesSchema, compareSites));
365
+ }
366
+ if (shouldRegister("estimate_site_revenue")) {
367
+ registeredToolNames.push("estimate_site_revenue");
368
+ server.tool("estimate_site_revenue", "Estimate annual PV generation revenue or BESS arbitrage revenue for a candidate site. Combines solar resource with day-ahead prices (PV) or spread analysis (BESS). Not a financial model.", siteRevenueSchema.shape, auditedToolHandler("estimate_site_revenue", siteRevenueSchema, estimateSiteRevenue));
369
+ }
370
+ } // end registerDataTools
371
+ // ---------------------------------------------------------------------------
372
+ // Discovery meta-tools (always registered regardless of profile)
373
+ // ---------------------------------------------------------------------------
374
+ function registerMetaTools() {
375
+ server.tool("luminus_discover", "List available Luminus tools and profiles", {
376
+ profile: z.string().optional().describe("Filter by profile name"),
377
+ category: z.string().optional().describe("Deprecated alias for profile"),
378
+ }, async ({ profile: filterProfile, category }) => {
379
+ const requestedProfile = filterProfile ?? category;
380
+ if (requestedProfile) {
381
+ const profileTools = PROFILES[requestedProfile];
382
+ if (!profileTools) {
383
+ return {
384
+ content: [{
385
+ type: "text",
386
+ text: JSON.stringify({
387
+ error: `Unknown profile "${requestedProfile}"`,
388
+ validProfiles: ["full", ...getProfileNames()],
389
+ }, null, 2),
390
+ }],
391
+ };
392
+ }
393
+ const tools = profileTools.map((name) => ({
394
+ name,
395
+ registered: registeredToolNames.includes(name),
396
+ hasKeys: hasRequiredKeys(name),
397
+ }));
398
+ return {
399
+ content: [{
400
+ type: "text",
401
+ text: JSON.stringify({
402
+ profile: requestedProfile,
403
+ description: getProfileDescription(requestedProfile),
404
+ toolCount: tools.length,
405
+ tools,
406
+ }, null, 2),
407
+ }],
408
+ };
409
+ }
410
+ // List all profiles with summary info
411
+ const profiles = ["full", ...getProfileNames()].map((name) => {
412
+ const tools = name === "full" ? null : PROFILES[name];
413
+ return {
414
+ name,
415
+ description: getProfileDescription(name),
416
+ toolCount: tools ? tools.length : TOTAL_TOOLS,
417
+ };
418
+ });
419
+ return {
420
+ content: [{
421
+ type: "text",
422
+ text: JSON.stringify({
423
+ activeProfile: profile,
424
+ registeredToolCount: registeredToolNames.length,
425
+ profiles,
426
+ }, null, 2),
427
+ }],
428
+ };
429
+ });
430
+ server.tool("luminus_status", "Server status: registered tools, active profile, configured API keys", {}, async () => {
431
+ const allKeyNames = new Set();
432
+ const configuredKeys = [];
433
+ const missingKeys = [];
434
+ for (const keys of Object.values(TOOL_KEY_REQUIREMENTS)) {
435
+ for (const key of keys) {
436
+ allKeyNames.add(key);
437
+ }
438
+ }
439
+ for (const key of allKeyNames) {
440
+ if (isKeyConfigured(key)) {
441
+ configuredKeys.push(key);
442
+ }
443
+ else {
444
+ missingKeys.push(key);
445
+ }
446
+ }
447
+ return {
448
+ content: [{
449
+ type: "text",
450
+ text: JSON.stringify({
451
+ profile,
452
+ registeredTools: registeredToolNames.length,
453
+ totalAvailable: TOTAL_TOOLS,
454
+ skippedByProfile: skippedByProfile.length,
455
+ skippedByMissingKeys: skippedByKeys.length,
456
+ configuredKeys: configuredKeys.sort(),
457
+ missingKeys: missingKeys.sort(),
458
+ }, null, 2),
459
+ }],
460
+ };
461
+ });
462
+ } // end registerMetaTools
463
+ // ---------------------------------------------------------------------------
464
+ // Main
465
+ // ---------------------------------------------------------------------------
466
+ async function main() {
467
+ // Pre-load key file BEFORE registration so hasRequiredKeys sees file-based keys
468
+ await preloadKeyFile();
469
+ // Register data tools (after key file is loaded)
470
+ registerDataTools();
471
+ // Register discovery meta-tools (always, regardless of profile)
472
+ registerMetaTools();
473
+ process.stderr.write(`[luminus] Profile: ${profile} | ` +
474
+ `Registered: ${registeredToolNames.length + 2} tools ` +
475
+ `(${registeredToolNames.length} data + 2 meta)\n`);
476
+ if (skippedByProfile.length > 0) {
477
+ process.stderr.write(`[luminus] Skipped by profile: ${skippedByProfile.length} tools\n`);
478
+ }
479
+ if (skippedByKeys.length > 0) {
480
+ process.stderr.write(`[luminus] Skipped (missing API keys): ${skippedByKeys.join(", ")}\n`);
481
+ }
482
+ const transport = new StdioServerTransport();
483
+ await server.connect(transport);
484
+ }
485
+ main().catch((err) => {
486
+ process.stderr.write(`[luminus] Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
487
+ process.exit(1);
488
+ });
@@ -0,0 +1,3 @@
1
+ export declare function logToolCall(tool: string, params: Record<string, unknown>): void;
2
+ export declare function getAuditLogPath(): string;
3
+ export declare function rotateIfNeeded(maxSizeMB?: number): void;
@@ -0,0 +1,66 @@
1
+ import { appendFile, mkdir, rename, stat } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ const AUDIT_DIR = join(homedir(), ".luminus");
5
+ const AUDIT_FILE = join(AUDIT_DIR, "audit.jsonl");
6
+ const SENSITIVE_PATTERN = /key|token|secret|password|auth|credential/i;
7
+ function redactParams(params) {
8
+ const redacted = {};
9
+ for (const [k, v] of Object.entries(params)) {
10
+ redacted[k] = SENSITIVE_PATTERN.test(k) ? "[REDACTED]" : v;
11
+ }
12
+ return redacted;
13
+ }
14
+ let dirEnsured = false;
15
+ async function ensureDir() {
16
+ if (dirEnsured)
17
+ return;
18
+ try {
19
+ await mkdir(AUDIT_DIR, { recursive: true });
20
+ dirEnsured = true;
21
+ }
22
+ catch (err) {
23
+ debugLog(`mkdir failed: ${err instanceof Error ? err.message : String(err)}`);
24
+ }
25
+ }
26
+ function debugLog(message) {
27
+ if (process.env.LUMINUS_DEBUG === "1") {
28
+ process.stderr.write(`[luminus-audit] ${message}\n`);
29
+ }
30
+ }
31
+ export function logToolCall(tool, params) {
32
+ const entry = {
33
+ ts: new Date().toISOString(),
34
+ tool,
35
+ params: redactParams(params),
36
+ };
37
+ const line = JSON.stringify(entry) + "\n";
38
+ // Fire-and-forget: never throw, never reject to caller
39
+ void (async () => {
40
+ try {
41
+ await ensureDir();
42
+ await appendFile(AUDIT_FILE, line, "utf-8");
43
+ rotateIfNeeded();
44
+ }
45
+ catch (err) {
46
+ debugLog(`write failed: ${err instanceof Error ? err.message : String(err)}`);
47
+ }
48
+ })();
49
+ }
50
+ export function getAuditLogPath() {
51
+ return AUDIT_FILE;
52
+ }
53
+ export function rotateIfNeeded(maxSizeMB = 50) {
54
+ const maxBytes = maxSizeMB * 1024 * 1024;
55
+ void (async () => {
56
+ try {
57
+ const info = await stat(AUDIT_FILE);
58
+ if (info.size <= maxBytes)
59
+ return;
60
+ await rename(AUDIT_FILE, AUDIT_FILE + ".1");
61
+ }
62
+ catch (err) {
63
+ debugLog(`rotate failed: ${err instanceof Error ? err.message : String(err)}`);
64
+ }
65
+ })();
66
+ }
@@ -0,0 +1,26 @@
1
+ export declare class ConfigurationError extends Error {
2
+ constructor(keyName: string);
3
+ }
4
+ /**
5
+ * Resolve an API key by name. Tries environment variables first, then
6
+ * ~/.luminus/keys.json. Throws ConfigurationError if not found.
7
+ * Resolved values are cached in memory for the process lifetime.
8
+ */
9
+ export declare function resolveApiKey(name: string): Promise<string>;
10
+ /** Constant-time string comparison. Reserved for future client authentication. */
11
+ export declare function timingSafeCompare(a: string, b: string): boolean;
12
+ /**
13
+ * Map of tool name to required API key names.
14
+ * Empty array = public API, no key needed.
15
+ */
16
+ export declare const TOOL_KEY_REQUIREMENTS: Readonly<Record<string, readonly string[]>>;
17
+ /**
18
+ * Check whether all API keys for a tool are resolvable right now.
19
+ * Checks environment variables and the cached key file synchronously.
20
+ * Returns true for tools with no key requirements.
21
+ */
22
+ export declare function hasRequiredKeys(toolName: string): boolean;
23
+ /** Check whether a specific API key name is available (env or key file). */
24
+ export declare function isKeyConfigured(keyName: string): boolean;
25
+ /** Pre-load keys.json so hasRequiredKeys works immediately. */
26
+ export declare function preloadKeyFile(): Promise<void>;