export function mount({ root, state, host }) { state = state || {}; const __cleanup = []; const __setInterval = (...args) => { const id = window.setInterval(...args); __cleanup.push(() => window.clearInterval(id)); return id; }; const __setTimeout = (...args) => { const id = window.setTimeout(...args); __cleanup.push(() => window.clearTimeout(id)); return id; }; if (!(root instanceof HTMLElement)) return; const tempEl = root.querySelector("[data-weather-temp]"); const unitEl = root.querySelector("[data-weather-unit]"); const condEl = root.querySelector("[data-weather-condition]"); const humidityEl = root.querySelector("[data-weather-humidity]"); const windEl = root.querySelector("[data-weather-wind]"); const pressureEl = root.querySelector("[data-weather-pressure]"); const updatedEl = root.querySelector("[data-weather-updated]"); const statusEl = root.querySelector("[data-weather-status]"); if (!(tempEl instanceof HTMLElement) || !(unitEl instanceof HTMLElement) || !(condEl instanceof HTMLElement) || !(humidityEl instanceof HTMLElement) || !(windEl instanceof HTMLElement) || !(pressureEl instanceof HTMLElement) || !(updatedEl instanceof HTMLElement) || !(statusEl instanceof HTMLElement)) { return; } const configuredToolName = typeof state.tool_name === 'string' ? state.tool_name.trim() : ''; const providerPrefix = typeof state.provider_prefix === 'string' ? state.provider_prefix.trim() : 'OpenWeatherMap'; const temperatureName = typeof state.temperature_name === 'string' ? state.temperature_name.trim() : `${providerPrefix} Temperature`; const humidityName = typeof state.humidity_name === 'string' ? state.humidity_name.trim() : `${providerPrefix} Humidity`; const pressureName = typeof state.pressure_name === 'string' ? state.pressure_name.trim() : `${providerPrefix} Pressure`; const windName = typeof state.wind_name === 'string' ? state.wind_name.trim() : `${providerPrefix} Wind speed`; const conditionLabel = typeof state.condition_label === 'string' ? state.condition_label.trim() : `${providerPrefix} live context`; const refreshMsRaw = Number(state.refresh_ms); const REFRESH_MS = Number.isFinite(refreshMsRaw) && refreshMsRaw >= 60000 ? refreshMsRaw : 24 * 60 * 60 * 1000; const setStatus = (label, color) => { statusEl.textContent = label; statusEl.style.color = color; }; const nowLabel = () => new Date().toLocaleString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); const stripQuotes = (value) => { const text = String(value ?? '').trim(); if ((text.startsWith("'") && text.endsWith("'")) || (text.startsWith('"') && text.endsWith('"'))) { return text.slice(1, -1); } return text; }; const normalizeText = (value) => String(value || '').trim().toLowerCase(); const parseLiveContextEntries = (payloadText) => { const text = String(payloadText || '').replace(/\r/g, ''); const startIndex = text.indexOf('- names: '); const relevant = startIndex >= 0 ? text.slice(startIndex) : text; const entries = []; let current = null; let inAttributes = false; const pushCurrent = () => { if (current) entries.push(current); current = null; inAttributes = false; }; for (const rawLine of relevant.split('\n')) { if (rawLine.startsWith('- names: ')) { pushCurrent(); current = { name: stripQuotes(rawLine.slice(9)), domain: '', state: '', attributes: {}, }; continue; } if (!current) continue; const trimmed = rawLine.trim(); if (!trimmed) continue; if (trimmed === 'attributes:') { inAttributes = true; continue; } if (rawLine.startsWith(' domain:')) { current.domain = stripQuotes(rawLine.slice(rawLine.indexOf(':') + 1)); inAttributes = false; continue; } if (rawLine.startsWith(' state:')) { current.state = stripQuotes(rawLine.slice(rawLine.indexOf(':') + 1)); inAttributes = false; continue; } if (inAttributes && rawLine.startsWith(' ')) { const separatorIndex = rawLine.indexOf(':'); if (separatorIndex >= 0) { const key = rawLine.slice(4, separatorIndex).trim(); const value = stripQuotes(rawLine.slice(separatorIndex + 1)); current.attributes[key] = value; } continue; } inAttributes = false; } pushCurrent(); return entries; }; const extractLiveContextText = (toolResult) => { if (toolResult && toolResult.parsed && typeof toolResult.parsed === 'object') { const parsed = toolResult.parsed; if (typeof parsed.result === 'string') return parsed.result; } if (typeof toolResult?.content === 'string') { try { const parsed = JSON.parse(toolResult.content); if (parsed && typeof parsed === 'object' && typeof parsed.result === 'string') { return parsed.result; } } catch { return toolResult.content; } return toolResult.content; } return ''; }; const resolveToolName = async () => { if (configuredToolName) return configuredToolName; if (!host.listTools) return 'mcp_home_assistant_GetLiveContext'; try { const tools = await host.listTools(); const liveContextTool = Array.isArray(tools) ? tools.find((tool) => /(^|_)GetLiveContext$/i.test(String(tool?.name || ''))) : null; return liveContextTool?.name || 'mcp_home_assistant_GetLiveContext'; } catch { return 'mcp_home_assistant_GetLiveContext'; } }; const findEntry = (entries, candidates) => { const normalizedCandidates = candidates.map((value) => normalizeText(value)).filter(Boolean); if (normalizedCandidates.length === 0) return null; const exactMatch = entries.find((entry) => normalizedCandidates.includes(normalizeText(entry.name))); if (exactMatch) return exactMatch; return entries.find((entry) => { const entryName = normalizeText(entry.name); return normalizedCandidates.some((candidate) => entryName.includes(candidate)); }) || null; }; const refresh = async () => { setStatus("Refreshing", "var(--theme-status-muted)"); try { const toolName = await resolveToolName(); const toolResult = await host.callTool(toolName, {}); const entries = parseLiveContextEntries(extractLiveContextText(toolResult)).filter((entry) => normalizeText(entry.domain) === 'sensor'); const temperatureEntry = findEntry(entries, [temperatureName]); const humidityEntry = findEntry(entries, [humidityName]); const pressureEntry = findEntry(entries, [pressureName]); const windEntry = findEntry(entries, [windName]); const tempNum = Number(temperatureEntry?.state); tempEl.textContent = Number.isFinite(tempNum) ? String(Math.round(tempNum)) : "--"; unitEl.textContent = String(temperatureEntry?.attributes?.unit_of_measurement || "°F"); condEl.textContent = conditionLabel; const humidity = Number(humidityEntry?.state); humidityEl.textContent = Number.isFinite(humidity) ? `${Math.round(humidity)}%` : "--"; const windSpeed = Number(windEntry?.state); const windUnit = String(windEntry?.attributes?.unit_of_measurement || "mph"); windEl.textContent = Number.isFinite(windSpeed) ? `${windSpeed} ${windUnit}` : "--"; const pressureNum = Number(pressureEntry?.state); pressureEl.textContent = Number.isFinite(pressureNum) ? `${pressureNum} ${String(pressureEntry?.attributes?.unit_of_measurement || "")}`.trim() : "--"; updatedEl.textContent = nowLabel(); setStatus("Live", "var(--theme-status-live)"); host.setLiveContent({ kind: 'weather', tool_name: toolName, provider_prefix: providerPrefix, temperature: Number.isFinite(tempNum) ? Math.round(tempNum) : null, temperature_unit: unitEl.textContent || null, humidity: Number.isFinite(humidity) ? Math.round(humidity) : null, wind: windEl.textContent || null, pressure: pressureEl.textContent || null, condition: condEl.textContent || null, updated_at: updatedEl.textContent || null, status: statusEl.textContent || null, }); } catch (err) { setStatus("Unavailable", "var(--theme-status-danger)"); updatedEl.textContent = String(err); host.setLiveContent({ kind: 'weather', tool_name: configuredToolName || 'mcp_home_assistant_GetLiveContext', provider_prefix: providerPrefix, temperature: null, humidity: null, wind: null, pressure: null, condition: null, updated_at: String(err), status: 'Unavailable', error: String(err), }); } }; host.setRefreshHandler(() => { void refresh(); }); void refresh(); __setInterval(() => { void refresh(); }, REFRESH_MS); return { destroy() { host.setRefreshHandler(null); host.setLiveContent(null); host.clearSelection(); for (const cleanup of __cleanup.splice(0)) cleanup(); }, }; }