feat(openclaw): add shopping, supermarket deals, geizhals, and ebay MCP tools

This commit is contained in:
Moritz Graf
2026-09-10 21:32:22 +02:00
parent 2f2f597a67
commit 1e81cdbead
4754 changed files with 1298747 additions and 896 deletions
@@ -0,0 +1,118 @@
import type { TimeSeriesPoint } from '../types/weather'
/**
* Calculate dew point (°C) from temperature (°C) and relative humidity (%)
* using the Magnus-Tetens formula.
*/
export function calculateDewPoint(
temperature?: number | null,
humidity?: number | null
): number | null {
if (
temperature === undefined ||
temperature === null ||
humidity === undefined ||
humidity === null ||
humidity <= 0
) {
return null
}
const a = 17.625
const b = 243.04
try {
const alpha = (a * temperature) / (b + temperature) + Math.log(humidity / 100)
const dewPoint = (b * alpha) / (a - alpha)
return Math.round(dewPoint * 10) / 10
} catch {
return null
}
}
/**
* Return thermal sensation / comfort level based on dew point (°C)
*/
export function getDewPointComfort(dewPoint?: number | null): {
text: string
color: string
} {
if (dewPoint === undefined || dewPoint === null) {
return { text: 'Unknown', color: 'var(--retro-text-dim)' }
}
if (dewPoint < 10) {
return { text: 'Dry / Crisp', color: 'var(--retro-text-cyan)' }
} else if (dewPoint <= 15) {
return { text: 'Pleasant / Fresh', color: 'var(--retro-text-green)' }
} else if (dewPoint <= 18) {
return { text: 'Comfortable', color: 'var(--retro-text-amber)' }
} else if (dewPoint <= 21) {
return { text: 'Humid / Warm', color: 'var(--retro-text-amber)' }
} else {
return { text: 'Muggy / Oppressive', color: 'var(--retro-text-red)' }
}
}
/**
* Convert degrees (0-360°) to 16-wind compass rose direction
*/
export function getWindDirectionCardinal(degrees?: number | null): string {
if (degrees === undefined || degrees === null) return 'N/A'
const compassSectors = [
'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'
]
const val = Math.floor((degrees / 22.5) + 0.5)
return compassSectors[val % 16]
}
/**
* Get UV Index category & retro color
*/
export function getUVIndexLevel(uv?: number | null): {
label: string
color: string
} {
if (uv === undefined || uv === null) {
return { label: 'Low', color: 'var(--retro-text-green)' }
}
if (uv < 3) return { label: 'Low', color: 'var(--retro-text-green)' }
if (uv < 6) return { label: 'Moderate', color: 'var(--retro-text-amber)' }
if (uv < 8) return { label: 'High', color: 'var(--retro-text-amber)' }
if (uv < 11) return { label: 'Very High', color: 'var(--retro-text-red)' }
return { label: 'Extreme', color: 'var(--retro-accent-purple)' }
}
/**
* Calculate basic statistical properties (min, max, average, current) for a time series
*/
export function calculateHistoryStats(points?: TimeSeriesPoint[]): {
min: number | null
max: number | null
avg: number | null
current: number | null
} {
if (!points || points.length === 0) {
return { min: null, max: null, avg: null, current: null }
}
let min = points[0].value
let max = points[0].value
let sum = 0
for (const p of points) {
if (p.value < min) min = p.value
if (p.value > max) max = p.value
sum += p.value
}
const avg = Math.round((sum / points.length) * 10) / 10
const current = points[points.length - 1].value
return {
min: Math.round(min * 10) / 10,
max: Math.round(max * 10) / 10,
avg,
current: Math.round(current * 10) / 10
}
}