Friday's Party: Creating a Private, Agentic AI using Voice Assistant tools

Updated CRUD Controllers (cont.)

Homeassistant Script - Calendar CRUD 1.2 (Calendar multitool)
NOW with target by label!

alias: calendar CRUD controller (1.2.0)
description: |2-
    Calendar CRUD Controller for Home Assistant. Provides create, read,
    update, delete, and help actions for any calendar entity. Use '*' or '' as
    calendar_name to list all calendars. 'start' and 'end' default to today
    (midnight to midnight next day) if not set. Requires action_type and
    calendar_name for most actions. UID deletes and updates are supported.
    Supports targeting calendar entities by label. (will return all reulting
    from a combined list of all calendars matching the submitted labels)
    'attendees' and other advanced fields are reserved for future use and are not
    yet supported by the Home Assistant calendar integration. Attempts to use
    unsupported fields will return a warning. Help action gives structured usage
    details, defaults, and notes. For maximum compatibility, ensure dates are in
    ISO 8601 format.
fields:
  action_type:
    description: "'create', 'read', 'update', 'delete', 'help' (Default: help)"
    required: true
    selector:
      select:
        options:
          - create
          - read
          - update
          - delete
          - help
    default: help
  calendar_name:
    description: Calendar name or entity_id. Use '*' or '' to list calendars.
    required: false
    selector:
      text: null
  summary:
    description: Event title (used in create/delete). Required for create/delete.
    required: false
    selector:
      text:
        multiline: false
  description:
    description: Optional description text for the calendar event.
    required: false
    selector:
      text:
        multiline: true
  uid:
    description: Unique identifier for the event (used for UID-based delete).
    required: false
    selector:
      text: null
  start:
    description: >-
      Start time in ISO 8601 (e.g., 2025-06-01T09:00:00). Defaults to today if
      not provided.
    required: false
    selector:
      text: null
  end:
    description: >-
      End time in ISO 8601. Required if not all-day. Defaults to tomorrow if not
      provided.
    required: false
    selector:
      text: null
  location:
    description: Optional location of the calendar event.
    required: false
    selector:
      text: null
  attendees:
    description: Optional list of attendees (comma-separated emails or names).
    required: false
    selector:
      text:
        multiline: true
  label_targets:
    selector:
      text:
        multiple: true
    name: label_targets
    description: >-
      ONLY used for read ops. When provided, overrides any other targeting
      mechanism and aggregates all entities for the listed labels/tags into a
      single result set.
sequence:
  - variables:
      action_type: "{{ action_type | default('read') }}"
      calendar_name: "{{ calendar_name | default('') }}"
      calendar_query: >-
        {{ calendar_name in ['', None, '*'] or action_type not in ['create',
        'read'] }}
      calendar_entity: |-
        {%- if calendar_name[0:9] == 'calendar.' -%}
          {{ calendar_name | lower | replace(' ', '_') }}
        {%- else -%}
          calendar.{{ calendar_name | lower | replace(' ', '_') }}
        {%- endif -%}
      valid_calendar_entities: "{{ states.calendar | map(attribute='entity_id') | map('lower') | list }}"
      calendar_entity_exists: "{{ calendar_entity in valid_calendar_entities }}"
      start: >-
        {{ start if start is defined else now().replace(hour=0, minute=0,
        second=0).isoformat() }}
      end: >-
        {{ end if end is defined else (now().replace(hour=0, minute=0, second=0)
        + timedelta(days=1)).isoformat() }}
      label_targets: "{{ label_targets | default([]) }}"
      label_entities: >-
        {% set all_cals = states.calendar | map(attribute='entity_id') | list %}
        {% set found = [] %} {% for label in label_targets %}
          {% set ents = label_entities(label) | select('in', all_cals) | list %}
          {% set found = found + ents %}
        {% endfor %} {{ found | list | unique | list }}
      target_calendars: >-
        {{ label_entities if label_entities | length > 0 else [calendar_entity]
        }}
  - choose:
      - conditions:
          - condition: template
            value_template: >-
              {{ (calendar_query or (action_type in ['read', 'create', 'update']
              and not calendar_entity_exists)) or action_type == 'help' }}
        sequence:
          - variables:
              cal_list: |-
                [
                  {%- for cal in states.calendar -%}
                    {
                      "entity_id": "{{ cal.entity_id }}",
                      "friendly_name": "{{ cal.attributes.friendly_name | default('') }}",
                      "state": "{{ cal.state }}",
                      "labels": [{% for lid in labels(cal.entity_id) %}"{{ label_name(lid) }}"{% if not loop.last %}, {% endif %}{% endfor %}],
                      "start_time": "{{ cal.attributes.start_time | default('') }}",
                      "end_time": "{{ cal.attributes.end_time | default('') }}",
                      "location": "{{ cal.attributes.location | default('') }}",
                      "description": "{{ cal.attributes.description | default('') }}"
                    }{% if not loop.last %}, {% endif %}
                  {%- endfor -%}
                ]
              final_response: |-
                {{
                  {
                    "status": "success",
                    "message": "Listed all available calendars.",
                    "calendars": cal_list
                  } | tojson
                }}
          - stop: Pass response variables back to LLM
            response_variable: final_response
            enabled: true
          - set_conversation_response: "{{final_response}}"
        alias: return available calendars
      - conditions:
          - condition: template
            value_template: "{{ action_type == 'read' and target_calendars|length > 0 }}"
        sequence:
          - variables:
              all_event_results: []
            alias: Before the loop, initialize an empty results list
          - repeat:
              for_each: "{{ target_calendars }}"
              sequence:
                - data:
                    start_date_time: "{{ start }}"
                    end_date_time: "{{ end }}"
                  response_variable: read_events
                  action: calendar.get_events
                  target:
                    entity_id: "{{ repeat.item }}"
                - variables:
                    events: >-
                      {{ read_events[repeat.item]['events'] if
                      read_events[repeat.item] is defined else [] }}
                    event_list: |-
                      [
                        {%- for event in events -%}
                          {
                            "summary": "{{ event.summary }}",
                            "start": "{{ event.start }}",
                            "end": "{{ event.end }}",
                            "uid": "{{ event.uid | default('') }}",
                            "description": "{{ event.description | default('') }}",
                            "location": "{{ event.location | default('') }}",
                            "all_day": {{ event.all_day | default(false) }},
                            "created": "{{ event.created | default('') }}",
                            "updated": "{{ event.updated | default('') }}"
                          }{% if not loop.last %}, {% endif %}
                        {%- endfor -%}
                      ]
                    one_result: |-
                      {
                        "calendar_entity": "{{ repeat.item }}",
                        "event_count": {{ events | length }},
                        "events": {{ event_list }}
                      }
                - variables:
                    all_event_results: "{{ all_event_results + [one_result] }}"
            alias: The repeat loop to collect events for each calendar entity
          - variables:
              final_response: |-
                {{
                  {
                    "status": "success",
                    "message": "Aggregated events from " ~ (target_calendars | length) ~ " calendars.",
                    "results": all_event_results
                  } | tojson
                }}
            alias: After the loop, construct the final response
          - stop: Pass response variables back to LLM
            response_variable: final_response
            enabled: true
          - set_conversation_response: "{{final_response}}"
        alias: read events from calendar
      - conditions:
          - condition: template
            value_template: "{{ action_type == 'create' and calendar_entity_exists }}"
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ start < end }}"
                sequence:
                  - variables:
                      create_data: >-
                        {% set is_all_day = not 'T' in start and not 'T' in end
                        %} {% set d = {
                          'entity_id': calendar_entity,
                          'summary': summary | default("Untitled Event")
                        } %} {% if is_all_day %}
                          {% set d = dict(d, start_date=start.split('T')[0], end_date=end.split('T')[0]) %}
                        {% else %}
                          {% set d = dict(d, start_date_time=start, end_date_time=end) %}
                        {% endif %} {% if description %}
                          {% set d = dict(d, description=description) %}
                        {% endif %} {% if location %}
                          {% set d = dict(d, location=location) %}
                        {% endif %} {% if attendees %}
                          {% set attendee_list = attendees.split(',') | map('trim') | list %}
                          {% set d = dict(d, attendees=attendee_list) %}
                        {% endif %} {{ d }}
                  - data: "{{ create_data }}"
                    action: calendar.create_event
                  - variables:
                      final_response: |-
                        {{
                          {
                            "status": "success",
                            "message": "Created calendar event \"" ~ create_data.summary ~ "\" on calendar \"" ~ calendar_entity ~ "\".",
                            "event": create_data
                          } | tojson
                        }}
                  - stop: Pass response variables back to LLM
                    response_variable: final_response
                    enabled: true
                  - set_conversation_response: "{{final_response}}"
              - conditions:
                  - condition: template
                    value_template: "{{ start >= end }}"
                sequence:
                  - variables:
                      final_response: |-
                        {{
                          {
                            "status": "error",
                            "message": "Invalid date range: start must be before end. Got start='" ~ start ~ "', end='" ~ end ~ "'."
                          } | tojson
                        }}
                  - stop: Pass response variables back to LLM
                    response_variable: final_response
                    enabled: true
                  - set_conversation_response: "{{final_response}}"
        alias: create calendar event
      - conditions:
          - condition: template
            value_template: "{{ action_type == 'update' and calendar_entity_exists }}"
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ uid is not defined or uid == '' }}"
                    alias: UID Not defined
                sequence:
                  - variables:
                      final_response: |-
                        {{
                          {
                            "status": "error",
                            "message": "UID is required to update an event. Provide a UID."
                          } | tojson
                        }}
                  - stop: Pass response variables back to LLM
                    response_variable: final_response
                  - set_conversation_response: "{{ final_response }}"
              - conditions:
                  - condition: template
                    value_template: "{{ uid is defined and uid != '' }}"
                    alias: UID is Defined
                sequence:
                  - variables:
                      update_data: >-
                        {% set d = { 'entity_id': calendar_entity, 'uid': uid }
                        %} {% if summary %} {% set d = dict(d, summary=summary)
                        %} {% endif %} {% if start and end %}
                          {% set is_all_day = not 'T' in start and not 'T' in end %}
                          {% if is_all_day %}
                            {% set d = dict(d, start_date=start.split('T')[0], end_date=end.split('T')[0]) %}
                          {% else %}
                            {% set d = dict(d, start_date_time=start, end_date_time=end) %}
                          {% endif %}
                        {% endif %} {% if description %}{% set d = dict(d,
                        description=description) %}{% endif %} {% if location
                        %}{% set d = dict(d, location=location) %}{% endif %} {%
                        if attendees %}
                          {% set attendee_list = attendees.split(',') | map('trim') | list %}
                          {% set d = dict(d, attendees=attendee_list) %}
                        {% endif %} {{ d }}
                  - data: "{{ update_data }}"
                    action: calendar.update_event
                  - variables:
                      final_response: |-
                        {{
                          {
                            "status": "success",
                            "message": "Updated event '" ~ uid ~ "' on '" ~ calendar_entity ~ "'.",
                            "event": update_data
                          } | tojson
                        }}
                  - stop: Pass response variables back to LLM
                    response_variable: final_response
                    enabled: true
                  - set_conversation_response: "{{ final_response }}"
        alias: update calendar event
      - conditions:
          - condition: template
            value_template: "{{ action_type == 'delete' and calendar_entity_exists }}"
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ uid is not defined or uid == '' }}"
                    alias: UID not Defined
                sequence:
                  - variables:
                      final_response: |-
                        {{
                          {
                            "status": "error",
                            "message": "UID is required for deleting events. Please provide a UID."
                          } | tojson
                        }}
                  - stop: Pass response variables back to LLM
                    response_variable: final_response
                  - set_conversation_response: "{{ final_response }}"
              - conditions:
                  - condition: template
                    value_template: "{{ uid is defined and uid != '' }}"
                    alias: UID is Defined
                sequence:
                  - variables:
                      delete_data: |-
                        {{
                          {
                            "entity_id": calendar_entity,
                            "uid": uid
                          }
                        }}
                  - data: "{{ delete_data }}"
                    action: calendar.delete_event
                  - variables:
                      final_response: |-
                        {{
                          {
                            "status": "success",
                            "message": "Deleted calendar event by UID: '" ~ uid ~ "' from '" ~ calendar_entity ~ "'.",
                            "deleted_event": delete_data
                          } | tojson
                        }}
                  - stop: Pass response variables back to LLM
                    response_variable: final_response
                    enabled: true
                  - set_conversation_response: "{{ final_response }}"
        alias: delete event from calendar
      - conditions:
          - condition: template
            value_template: "{{ action_type == 'help' }}"
        sequence:
          - variables:
              help_data: |-
                {{
                  {
                    "status": "info",
                    "message": "This is the Calendar CRUD Controller Help function.",
                    "actions": {
                      "read": "Reads events from a given calendar. Requires 'calendar_name'. Optional: 'start', 'end'.",
                      "create": "Creates a new event. Requires 'calendar_name' and 'summary'. Optional: 'description', 'start', 'end'.",
                      "delete": "Deletes an event by UID. Requires 'calendar_name' and 'uid'.",
                      "help": "Returns this help response."
                    },
                    "defaults": {
                      "start": "Defaults to today at 00:00 local time if not supplied.",
                      "end": "Defaults to tomorrow at 00:00 local time if not supplied (i.e., end of today)."
                    },
                    "notes": [
                      "Use '*' or blank for 'calendar_name' to list all calendars.",
                      "Event titles must match exactly for deletion by summary.",
                      "UID deletes are now supported. Summary-based fallback pending reimplementation.",
                      "To reschedule an event, modify start/end times, not title.",
                      "Action_type is required. If unknown or invalid, help is returned.",
                      "Label targeting via label_targets works for read ops and aggregates all matching entities."
                    ]
                  } | tojson
                }}
          - stop: Pass response variables back to LLM
            response_variable: help_data
            enabled: true
          - set_conversation_response: "{{ help_data }}"
        alias: return help for calendar CRUD controller
icon: mdi:calendar

More to come!