A bunch of Tool updates to prep for File Tool
Script - Zen Index 2.6:
(REQUIRES Inspect)
alias: DojoTOOLS Zen Index
description: >-
The Zen Indexer (2.6.0) is a DIRECT replacement and the next version of the Library
~INDEX~ Command. ALWAYS prefer it over old index tools in place of THE
LIBRARY ~INDEX~. Set logic on two lists of entity_ids (each resolved from a
label or list of entities). - If only one side provided, returns that set. -
If both, applies operator (AND, OR, NOT, XOR). - If neither, returns an error
or (special case) full label index (operator = '*'). - Optional expand to
resolve groups/areas. Returns matching entities, adjacent labels, expansion
can include state and attribute list. result.simple ALWAYS returns a list (for
chaining). If index_command is set, fires zen_indexer_request event and exits
for recursive resolution.
fields:
index_command:
name: Zen Index Command
description: >-
Structured query string (if set, all other fields are ignored and this
triggers recursive index resolution via event). If parsing fails or times
out, will fall back to using the other fields.
required: false
selector:
text: {}
entities_1:
name: Entities 1
description: List of entity_ids for operand 1 (used if label_1 is blank)
required: false
selector:
entity:
multiple: true
label_1:
name: Label 1
description: Label for operand 1 (used if entities_1 is empty)
required: false
selector:
text: {}
entities_2:
name: Entities 2
description: List of entity_ids for operand 2 (used if label_2 is blank)
required: false
selector:
entity:
multiple: true
label_2:
name: Label 2
description: Label for operand 2 (used if entities_2 is empty)
required: false
selector:
text: {}
operator:
name: Set Operator
description: Logical set operator to apply between operand 1 and 2 (AND, OR, NOT, XOR)
required: true
default: AND
selector:
select:
options:
- AND
- OR
- NOT
- XOR
expand_entities:
name: Expand results
description: >-
If true, expand each resulting entity to include its state and attribute
keys (may produce a large output). Best practice: use labels to narrow
results before expanding. Default: false.
required: false
default: false
selector:
boolean: {}
timeout:
name: Timeout
description: >-
Timeout (in seconds) to wait for index resolution via event if using
index_command. Default 2 seconds (max 5). If timeout occurs, script will
fall back to using provided fields or full index.
required: false
default: 2
selector:
number:
min: 0
max: 5
step: 0.25
sequence:
- variables:
is_index_command: "{{ index_command is defined and index_command | length > 0 }}"
correlation_id: |-
{% if index_command is defined and index_command | length > 0 %}
{{ now().isoformat() ~ '-' ~ (range(1000) | random) }}
{% else %}
''
{% endif %}
timeout_seconds: "{{ timeout | default(2) }}"
- choose:
- conditions:
- condition: template
value_template: "{{ is_index_command }}"
sequence:
- alias: Send Index Command to Parser
event: zen_indexer_request
event_data:
index_command: "{{ index_command }}"
correlation_id: "{{ correlation_id }}"
- alias: Wait for Parser Response ({{ timeout_seconds }}s)
wait_for_trigger:
- event_type: zen_index_response
event_data:
correlation_id: "{{ id }}"
trigger: event
timeout:
seconds: "{{ timeout_seconds }}"
- choose:
- conditions:
- condition: template
value_template: "{{ wait.trigger is defined and wait.trigger is not none }}"
sequence:
- variables:
simple: "{{ wait.trigger.event.data.response.result.simple }}"
index_timeout: false
- conditions: []
sequence:
- variables:
simple: []
index_timeout: true
timeout_error_msg: Indexer call timeout {{ timeout_seconds }}s
alias: Send Index Command to Parser and resolve to fields
- variables:
index_command: ""
op1: >-
{% set ents = simple if (simple is defined and simple) else [] %} {% if
ents | length == 0 %}
{% set ents = entities_1 if (entities_1 is defined and entities_1) else [] %}
{% if ents | length == 0 and label_1 %}
{{ label_entities(label_1) }} # If still empty and label_1 provided, retrieve entities by label_1
{% else %}
{{ ents }}
{% endif %}
{% else %}
{{ ents }}
{% endif %}
op2: >-
{% set ents = entities_2 if (entities_2 is defined and entities_2) else
[] %} {% if ents | length == 0 and label_2 %}
{{ label_entities(label_2) }}
{% else %}
{{ ents }}
{% endif %}
op1_empty: "{{ op1 | length == 0 }}"
op2_empty: "{{ op2 | length == 0 }}"
setop: "{{ operator | default('AND') | upper }}"
entities_base: |-
{% if not op1_empty and op2_empty %}
{{ op1 }}
{% elif not op2_empty and op1_empty %}
{{ op2 }}
{% elif not op1_empty and not op2_empty %}
{% if setop == 'AND' %}
{{ op1 | intersect(op2) }}
{% elif setop == 'OR' %}
{{ op1 + op2 | unique | list }}
{% elif setop == 'NOT' %}
{{ op1 | difference(op2) }}
{% elif setop == 'XOR' %}
{{ op1 | symmetric_difference(op2) }}
{% else %}
{{ op1 + op2 | unique | list }}
{% endif %}
{% else %}
[] # Both op1 and op2 empty: no entities in base set (will trigger full index fallback below)
{% endif %}
- if:
- condition: template
value_template: "{{ expand_entities }}"
alias: if expand_entities
then:
- action: script.dojotools_zen_inspect
metadata: {}
data:
entity_id: "{{entities_base}}"
response_variable: response
- stop: Pass Variable
response_variable: response
- variables:
expanded_entities: "{{ response }}"
else:
- variables:
expanded_entities: "{{ entities_base | tojson }}"
- variables:
result_simple: "{{ entities_base | tojson }}"
adjacent_labels: |-
{%- set all_labels = [] -%} {%- for e in entities_base -%}
{%- set all_labels = all_labels + (labels(e) | list) -%}
{%- endfor -%} {{ all_labels | unique | list | tojson }}
result_index: |-
{%- set index_list = [] -%} {%- for e in entities_base -%}
{%- set index_list = index_list + [[ e, (labels(e) | list) ]] -%}
{%- endfor -%} {{ index_list | tojson }}
error_msg: |-
{% set msg = '' %} {% if index_timeout is defined and index_timeout %}
{% set msg = 'Indexer call timeout ' ~ timeout_seconds ~ 's. Fallback path used.' %}
{% elif op1_empty and op2_empty %}
{% set msg = 'Both operands are empty. Returned full label index.' %}
{% endif %} {{ msg }} # msg will be empty string if no error
label_entity_logic_result_raw: |-
{
"result": {
"simple": {{ result_simple }},
"expanded": {{ expanded_entities }},
"adjacent_labels": {{ adjacent_labels }},
"index": {% if op1_empty and op2_empty %} {{ labels() | list | tojson }} {% else %} {{ result_index }} {% endif %}
},
"operator": "{{ "*" if (op1_empty and op2_empty) else setop }}",
"inputs": {
"entities_1": {{ (entities_1 | default([])) | tojson }},
"label_1": {{ (label_1 | default('')) | tojson }},
"entities_2": {{ (entities_2 | default([])) | tojson }},
"label_2": {{ (label_2 | default('')) | tojson }},
"expand_entities": {{ (expand_entities | default(false)) | tojson }}
},
"error": {{ (error_msg if error_msg != '' else none) | tojson }}
}
label_entity_logic_result: "{{ label_entity_logic_result_raw | from_json | default({}) }}"
- stop: Zen Index Call Complete
response_variable: label_entity_logic_result
mode: parallel
max: 10
Automation - Index Event Handler:
alias: DojoTools Zen Index Event Handler
description: >
Event Listener (2.0.0) to recurse index calls. REQUIRES DojoTOOLS Zen Index (2.0.0 or
greater)
triggers:
- event_type: zen_indexer_request
trigger: event
actions:
- variables:
index_command: "{{ trigger.event.data.index_command | default('{}', true) }}"
correlation_id: "{{ trigger.event.data.correlation_id }}"
depth: "{{ trigger.event.data.depth if 'depth' in trigger.event.data else 1 }}"
is_mapping: |-
{% set cmd = index_command | trim %} {% if cmd.startswith('{') %}
{{ (cmd | from_json) is mapping }}
{% else %}
false
{% endif %}
parsed_cmd: >
{% set reserved = ['AND', 'OR', 'XOR', 'NOT'] %} {% set true_tokens =
['true', 't', 'y', 'yes', 'on', '1'] %} {% set false_tokens = ['false',
'f', 'n', 'no', 'off', '0'] %} {% set raw = index_command | string |
trim %} {% set is_json = raw.lstrip().startswith('{') %} {% if is_json
%}
{{ raw }}
{% elif raw == "" or raw == "*" %}
{ "label_1": "*", "operator": "AND", "expand_entities": false }
{% else %}
{% set tokens = raw.split() %}
{% set exp = false %}
{% if tokens[-1] | lower in true_tokens %}
{% set exp = true %}
{% set tokens = tokens[:-1] %}
{% elif tokens[-1] | lower in false_tokens %}
{% set tokens = tokens[:-1] %}
{% endif %}
{% set op_index = namespace(idx = none) %}
{% for i in range(tokens | length) %}
{% if tokens[i] | upper in reserved %}
{% set op_index.idx = i %}
{% break %}
{% endif %}
{% endfor %}
{% if op_index.idx is not none %}
{% set label_1 = tokens[:op_index.idx] | join(" ") | trim("'\"") %}
{% set op = tokens[op_index.idx] | upper %}
{% set label_2 = tokens[op_index.idx+1:] | join(" ") | trim("'\"") %}
{% else %}
{% set label_1 = tokens | join(" ") | trim("'\"") %}
{% set op = "AND" %}
{% set label_2 = "" %}
{% endif %}
{
"label_1": "{{ label_1 }}",
"label_2": "{{ label_2 }}",
"operator": "{{ op }}",
"expand_entities": {{ exp }}
}
{% endif %}
entities_1: >-
{% set ents = parsed_cmd.entities_1 if parsed_cmd.entities_1 is defined
and parsed_cmd.entities_1 else [] %} {% if ents is string %}
{% set ents = ents | from_json | default([]) %}
{% endif %} {% if ents | length == 0 and parsed_cmd.label_1 %}
{{ label_entities(parsed_cmd.label_1) | default([]) }}
{% else %}
{{ ents }}
{% endif %}
label_1: "{{ parsed_cmd.label_1 if parsed_cmd.label_1 is defined else '' }}"
entities_2: >-
{% set ents = parsed_cmd.entities_2 if parsed_cmd.entities_2 is defined
and parsed_cmd.entities_2 else [] %} {% if ents is string %}
{% set ents = ents | from_json | default([]) %}
{% endif %} {% if ents | length == 0 and parsed_cmd.label_1 %}
{{ label_entities(parsed_cmd.label_1) | default([]) }}
{% else %}
{{ ents }}
{% endif %}
label_2: "{{ parsed_cmd.label_2 if parsed_cmd.label_2 is defined else '' }}"
operator: |-
{% if parsed_cmd.operator is defined and parsed_cmd.operator %}
{{ parsed_cmd.operator }}
{% else %}
AND
{% endif %}
expand_entities: >-
{{ parsed_cmd.expand_entities if parsed_cmd.expand_entities is defined
else false }}
- alias: Dispatch Zen Index Command
data:
entities_1: "{{ entities_1 }}"
label_1: "{{ label_1 }}"
entities_2: "{{ entities_2 }}"
label_2: "{{ label_2 }}"
operator: "{{ operator }}"
expand_entities: "{{ expand_entities }}"
response_variable: zen_index_response
action: script.dojotools_zen_index
- alias: Return Zen Indexer Result
event: zen_index_response
event_data:
correlation_id: "{{ correlation_id }}"
response: "{{ zen_index_response }}"
mode: queued
Script - Inspect: (Attribute dumper, plus, note bug still outputs stringified JSON)
alias: DojoTOOLS Zen Inspect
mode: parallel
description: >-
Inspect (1.2.0) Deep dive on one or more entities. THis tool lprovides the expand function of
the Zen Indexer. Accepts a list of entity_ids - dumps attributes Exposes
volume headers for volumes in the library, Highlights device trackers for
person domain entities and more! Optional inference support is coming soon for
an deeper analysis on provided data.
fields:
entity_id:
required: true
selector:
entity:
multiple: true
infer:
required: false
selector:
boolean: {}
description: Use local inference to add context to the result (VERY EXPENSIVE call)
timeout:
selector:
number:
min: 30
max: 300
step: 0.25
name: Timeout
description: >-
Timeout, in seconds to wait for the inference return in seconds (Default -
60, Min - 30, Max - 300, Step - 5)
default: 60
required: false
sequence:
- variables:
entity_list: "{{ entity_id }}"
results: []
- repeat:
for_each: "{{ entity_list }}"
sequence:
- variables:
current_entity: "{{ repeat.item }}"
labels_raw: "{{ labels(current_entity) }}"
adjacent_labels: "{{ labels_raw | unique | sort | list | tojson }}"
raw_volume: "{{ state_attr(current_entity, 'variables') }}"
is_person: "{{ current_entity.startswith('person.') }}"
user_id: "{{ state_attr(current_entity, 'user_id') if is_person else none }}"
last_updated: >-
{{ as_timestamp(states[current_entity].last_updated, default=0) |
timestamp_local }}
device_trackers: >-
{{ (state_attr(current_entity, 'device_trackers') if is_person
else [] ) | tojson }}
parsed_volume: |
{% if raw_volume is string and raw_volume.startswith('{') %}
{{ raw_volume | from_json }}
{% elif raw_volume is mapping %}
{{ raw_volume }}
{% else %}
{}
{% endif %}
cab_valid: |
{{ raw_volume is mapping and
raw_volume.AI_Cabinet_VolumeInfo is defined and
raw_volume.AI_Cabinet_VolumeInfo.value is mapping and
raw_volume.AI_Cabinet_VolumeInfo.value.validation == "ALLYOURBASEAREBELONGTOUS" }}
cab_response: |
{% if cab_valid %}
{{
{
"id": raw_volume.AI_Cabinet_VolumeInfo.value.id | default(""),
"friendly_name": raw_volume.AI_Cabinet_VolumeInfo.value.friendly_name | default(""),
"description": raw_volume.AI_Cabinet_VolumeInfo.value.description | default(""),
"timestamp": raw_volume.AI_Cabinet_VolumeInfo.timestamp | default(""),
"flags": raw_volume.AI_Cabinet_VolumeInfo.flags | default({}),
"acls": {
"owner": (
raw_volume.AI_Cabinet_VolumeInfo.acls.owner[0].entity_id
if raw_volume.AI_Cabinet_VolumeInfo.acls is defined and
raw_volume.AI_Cabinet_VolumeInfo.acls.owner is defined and
raw_volume.AI_Cabinet_VolumeInfo.acls.owner[0].entity_id is defined
else ""
)
},
"index": (
raw_volume._label_index.value
if raw_volume._label_index is defined and raw_volume._label_index.value is defined
else []
)
}
}}
{% else %}
{ "defined": false, "error": "Invalid or missing AI Cabinet volume." }
{% endif %}
attr_dict: >
{%- set attrs = states[current_entity].attributes if
states[current_entity] else {} %}
{%- set keys = attrs.keys() | list %}
{%- set ns = namespace(vals=[]) %}
{%- for v in attrs.values() %}
{%- if v is none %}
{%- set ns.vals = ns.vals + [none] %}
{%- elif v is datetime %}
{%- set ns.vals = ns.vals + [v.isoformat()] %}
{%- elif v is mapping %}
{%- set ns.vals = ns.vals + [v] %}
{%- elif v is iterable and not v is string %}
{%- set ns.vals = ns.vals + [v | list] %}
{%- elif v is string or v is number or v is boolean %}
{%- set ns.vals = ns.vals + [v] %}
{%- else %}
{# If it's anything else (enum, object, who-knows-what): DROP IT! #}
{%- endif %}
{%- endfor %}
{{ dict(zip(keys, ns.vals)) }}
fallback_response: |-
{{
{
"entity": current_entity | default(''),
"state": states(current_entity) | default(''),
"last_updated": last_updated,
"labels": labels_raw | default([]),
"attributes": attr_dict,
"user_id": user_id | default(''),
"device_trackers": device_trackers | default([])
} | tojson
}}
- variables:
inference: {}
result: |-
{{
{
"entity": current_entity,
"inspection": fallback_response,
"inference": {
"unsupported": "infer in development, coming soon",
}
} | tojson
}}
results: "{{ results + [result] }}"
- variables:
final_response: |
{{
{
"results": results
} | tojson
}}
- stop: Multi-entity investigate complete
response_variable: final_response
Script - Calculator:
alias: Zen DojoTOOLS Calculator
mode: parallel
description: >-
Ultimate calculator for AI use. Accepts two numbers and an operator. Supports
add, subtract, multiply, divide, exponent, sqrt, cbrt, sin, cos, tan,
degrees_to_radians, radians_to_degrees, log, ln, percent_of,
increase_by_percent, decrease_by_percent, round, floor, ceil, modulus,
fahrenheit_to_celsius, celsius_to_fahrenheit, and a GUID Generator! Returns
JSON output. Drops inputs as necessary if operator does not require one or
both. (v.1.3.0)
fields:
number_1:
name: First Number
required: false
selector:
number:
min: -1000000000000
max: 1000000000000
mode: box
number_2:
name: Second Number
required: false
selector:
number:
min: -1000000000000
max: 1000000000000
mode: box
operator:
name: Operator
required: true
selector:
select:
options:
- add
- subtract
- multiply
- divide
- exponent
- sqrt
- cbrt
- sin
- cos
- tan
- degrees_to_radians
- radians_to_degrees
- log
- ln
- percent_of
- increase_by_percent
- decrease_by_percent
- round
- floor
- ceil
- modulus
- fahrenheit_to_celsius
- celsius_to_fahrenheit
- guidgen
translation_key: operator
custom_value: false
sequence:
- variables:
n1: "{{ number_1 | default(0) | float(0) }}"
n2: "{{ number_2 | default(0) | float(0) }}"
op: "{{ operator | lower }}"
result: |
{% if op == 'add' %}
{{ n1 + n2 }}
{% elif op == 'subtract' %}
{{ n1 - n2 }}
{% elif op == 'multiply' %}
{{ n1 * n2 }}
{% elif op == 'divide' %}
{% if n2 != 0 %}
{{ n1 / n2 }}
{% else %}
'Error: Division by zero'
{% endif %}
{% elif op == 'exponent' %}
{{ n1 ** n2 }}
{% elif op == 'sqrt' %}
{% if n1 >= 0 %}
{{ n1 ** 0.5 }}
{% else %}
'Error: Square root of negative'
{% endif %}
{% elif op == 'cbrt' %}
{{ n1 ** (1/3) }}
{% elif op == 'sin' %}
{{ n1 | sin }}
{% elif op == 'cos' %}
{{ n1 | cos }}
{% elif op == 'tan' %}
{{ n1 | tan }}
{% elif op == 'degrees_to_radians' %}
{{ n1 * 3.141592653589793 / 180 }}
{% elif op == 'radians_to_degrees' %}
{{ n1 * 180 / 3.141592653589793 }}
{% elif op == 'log' %}
{% if n1 > 0 %}
{{ n1 | log10 }}
{% else %}
'Error: Log undefined for n1 ≤ 0'
{% endif %}
{% elif op == 'ln' %}
{% if n1 > 0 %}
{{ n1 | log }}
{% else %}
'Error: Natural log undefined for n1 ≤ 0'
{% endif %}
{% elif op == 'percent_of' %}
{{ (n1 * n2) / 100 }}
{% elif op == 'increase_by_percent' %}
{{ n1 * (1 + n2/100) }}
{% elif op == 'decrease_by_percent' %}
{{ n1 * (1 - n2/100) }}
{% elif op == 'round' %}
{{ n1 | round(n2 | int(0)) }}
{% elif op == 'floor' %}
{{ n1 | floor }}
{% elif op == 'ceil' %}
{{ n1 | ceil }}
{% elif op == 'modulus' %}
{% if n2 != 0 %}
{{ n1 % n2 }}
{% else %}
'Error: Modulus by zero'
{% endif %}
{% elif op == 'fahrenheit_to_celsius' %}
{{ (n1 - 32) * 5 / 9 }}
{% elif op == 'celsius_to_fahrenheit' %}
{{ (n1 * 9 / 5) + 32 }}
{% elif op == 'guidgen' %}
{% macro random_string(len) -%}{% for i in range(0,len) -%}{{ [0,1,2,3,4,5,6,7,8,9,"a","b","c","d","e","f"]|random }}{% endfor %}{%- endmacro -%}
{% macro random_guid() -%} {{ random_string(8) + "-" + random_string(4) + "-" + random_string(4) + "-" + random_string(4) + "-" + random_string(12) }}{%- endmacro -%}
{% set myGUID = random_guid() %}
{{ myGUID }}
{% else %}
'Error: Unknown operator'
{% endif %}
out: |
{{
{
"number_1": n1,
"number_2": n2,
"operator": op,
"result": result
} | tojson
}}
- stop: Calculator done
response_variable: out
Script - Die Roller:
alias: Zen DojoTOOLs Dice Roller (1.0.0)
mode: parallel
description: >-
Rolls dice (DnD style), flips a coin, or generates a random number. Supports
classic dice notation (e.g., '2d6', 'd20'), coin flip, random integer, and
random float.
fields:
mode:
name: Mode
required: true
selector:
select:
options:
- Dice
- Random Integer
- Random Float
- Coin Flip
dice_notation:
name: Dice Notation (e.g. 3d6, d20)
required: false
selector:
text: null
description: >-
Only used if Mode is 'Dice'. Format XdY or dY (e.g. 2d8, d20) only
supports one dice type per roll (e.g., 3d6 or 2d20 but not 2d6+1d8)
min:
name: Minimum Value
required: false
selector:
number:
min: -1000000000000
max: 1000000000000
step: 1
mode: box
description: Only used for random number modes.
max:
name: Maximum Value
required: false
selector:
number:
min: -1000000000000
max: 1000000000000
step: 1
mode: box
description: Only used for random number modes.
sequence:
- variables:
md: "{{ mode | lower }}"
dice: "{{ dice_notation | default('d6') }}"
minval: "{{ min | int(1) }}"
maxval: "{{ max | int(6) }}"
result: >
{% set m = dice.lower().replace(' ', '') %} {% set parts = m.split('d')
%} {% set n = 1 if parts[0] == '' else parts[0] | int(1) %} {% set die =
parts[1] | int(6) %} {% if md == 'dice' %}
{% set faces = range(1, die+1) %}
{% set ns = namespace(rolls=[]) %}
{% for _ in range(n) %}
{% set ns.rolls = ns.rolls + [faces | random] %}
{% endfor %}
{{ {
"mode": "dice",
"dice": m,
"individual_rolls": ns.rolls,
"total": ns.rolls | sum
} }}
{% elif md == 'random integer' %}
{% if maxval >= minval %}
{{ {
"mode": "random_integer",
"min": minval,
"max": maxval,
"value": (range(minval, maxval+1) | random)
} }}
{% else %}
{{ { "error": "Max must be >= min" } }}
{% endif %}
{% elif md == 'random float' %}
{% if maxval >= minval %}
{% set f = (range(0, 100000) | random) / 100000 %}
{% set val = minval + (maxval - minval) * f %}
{{ {
"mode": "random_float",
"min": minval,
"max": maxval,
"value": val
} }}
{% else %}
{{ { "error": "Max must be >= min" } }}
{% endif %}
{% elif md == 'coin flip' %}
{{ {
"mode": "coin_flip",
"result": ['heads', 'tails'] | random
} }}
{% else %}
{{ { "error": "Unknown mode" } }}
{% endif %}
out: |
{{ result }}
- stop: Dice/Random complete
response_variable: out
icon: mdi:dice-d20-outline