{ "$schema": "homarr-widget-package-v3", "manifest": { "id": "homarr.reference.household", "version": "1.0.0", "sdkVersion": "1", "name": "Household controls", "description": "A Home Assistant control panel with a live configuration surface, owner-bound credentials and confirmed device actions.", "entrypoints": { "tile": "widget.tsx", "advanced": "widget.tsx", "server": "server.ts", "configuration": "configuration.tsx" }, "handlers": { "states": { "kind": "query", "permission": "view" }, "toggle": { "kind": "action", "permission": "modify", "inputSchema": { "type": "object", "additionalProperties": false, "required": ["entityId"], "properties": { "entityId": { "type": "string", "pattern": "^(light|switch|input_boolean|fan)\\.[A-Za-z0-9_]+$" } } }, "guestAccess": true } } }, "files": { "widget.tsx": "import type { HouseholdEntity, HouseholdOptions, ToggleInput } from \"./household-types\";\nimport { Alert, Badge, Button, Card, Group, SimpleGrid, Stack, Text } from \"@mantine/core\";\nimport { useWidgetAction, useWidgetCapabilities, useWidgetHost, useWidgetOptions, useWidgetQuery, useWidgetServices } from \"@homarr/widget-sdk\";\nexport default function Household() {\n const host = useWidgetHost();\n const options = useWidgetOptions();\n const services = useWidgetServices();\n const query = useWidgetQuery(\"states\", {}, {refetchInterval: 10000});\n const action = useWidgetAction(\"toggle\");\n const capabilities = useWidgetCapabilities();\n const error = query.error ?? action.error;\n const entities = Array.isArray(options.entities) ? options.entities : [];\n const rows = (query.data ?? []).filter((row) => entities.includes(row.entity_id));\n const toggle = async (row: HouseholdEntity) => { if (!await services.confirm({title:row.attributes.friendly_name ?? row.entity_id, message:`Toggle ${row.entity_id}?`})) return; await action.mutateAsync({entityId:row.entity_id}).catch(() => undefined); };\n return {options.title}\n {error && {error.message}}\n {rows.map((row) => {row.attributes.friendly_name ?? row.entity_id}{row.state}{host.displayMode === \"advanced\" && Changed {new Date(row.last_changed).toLocaleString(host.locale)}})}\n {!entities.length && Choose your lights and switches in the configuration preview.}\n ;\n}", "server.ts": "import type { WidgetServerContext } from \"@homarr/widget-sdk/server\";\nimport type { HouseholdEntity, HouseholdOptions, ToggleInput } from \"./household-types\";\nimport { defineWidgetServer, fetchWidgetConnection, getWidgetServerContext } from \"@homarr/widget-sdk/server\";\nconst states = async (context: WidgetServerContext) => { const result = await fetchWidgetConnection(context, {connection:\"homeassistant\",path:\"/api/states\"}); if (!result.ok) throw new Error(`Home Assistant returned HTTP ${result.status}`); return result.data; };\nexport default defineWidgetServer({\n states: (_input: unknown, context) => states(context),\n toggle: async (input: ToggleInput, context) => {\n const {options} = await getWidgetServerContext(context);\n if (!Array.isArray(options.entities) || !options.entities.includes(input?.entityId)) throw new Error(\"Choose this entity in the widget configuration first\");\n const domain = input.entityId.split(\".\")[0] ?? \"\";\n if (![\"light\",\"switch\",\"input_boolean\",\"fan\"].includes(domain)) throw new Error(\"This entity does not support the toggle operation\");\n const result = await fetchWidgetConnection(context, {connection:\"homeassistant\",path:`/api/services/${domain}/toggle`,method:\"POST\",body:{entity_id:input.entityId}});\n if (!result.ok) throw new Error(`Home Assistant returned HTTP ${result.status}`);\n return result.data;\n },\n});", "configuration.tsx": "import type { HouseholdEntity, HouseholdOptions } from \"./household-types\";\nimport { Alert, Button, MultiSelect, Stack, TextInput } from \"@mantine/core\";\nimport { useWidgetOptions, useWidgetQuery, useWidgetServices, useWidgetState } from \"@homarr/widget-sdk\";\nexport default function Configuration() {\n const options = useWidgetOptions();\n const services = useWidgetServices();\n const [title, setTitle] = useWidgetState(\"configuration-title\", options.title);\n const [entities, setEntities] = useWidgetState(\"configuration-entities\", options.entities);\n const [error, setError] = useWidgetState(\"configuration-error\", \"\");\n const query = useWidgetQuery(\"states\");\n const choices = (query.data ?? []).filter((row) => /^(light|switch|input_boolean|fan)\\./.test(row.entity_id)).map((row) => ({value:row.entity_id,label:row.attributes.friendly_name ?? row.entity_id}));\n const save = async () => { try { await services.updateOptions({...options,title,entities}); setError(\"\"); } catch (error) { setError(error instanceof Error ? error.message : String(error)); } };\n return {query.error && {query.error.message}}{error && {error}} setTitle(event.currentTarget.value)} />;\n}", "household-types.ts": "export interface HouseholdOptions extends Record { title: string; entities: string[] }\nexport interface HouseholdEntity { entity_id: string; state: string; last_changed: string; attributes: { friendly_name?: string } }\nexport interface ToggleInput { entityId: string }\n" }, "dependencies": {}, "connections": { "homeassistant": { "kind": "http", "label": "Home Assistant", "description": "Use the Home Assistant base URL and bearer token authentication." } }, "options": { "title": { "control": "text", "label": "Title", "default": "Home" }, "entities": { "control": "json", "label": "Entity IDs", "default": [] } } }