A (Homey) Script to find Any Items (Devices, Zones, Apps, Variables) in any flow

Here is a script that you can use to find any Variables, Devices, Zones or Apps in any Flow or Advanced Flow.

This will be included into the Device Capabilities App and explanations for the flowcards will be placed on [APP][Pro] Device Capabilities - Enhance the capabilities of devices - Apps - Homey Community Forum.

This topic can be used for the below script.
You can use it in many ways, see the top of the script for examples.

The Find Objects in Flows Script

/// -- The Find Objects in Flows Script --
/// Search for objects in flows and advanced flows.
/// Created by Arie J. Godschalk
/// Creation date: 2022-10-05
/// Version 1.0.1
/// Script to find objects (Variables, Devices, Zones, Apps)  within Flows and AFs.

/// Define defaults
const _now = Date.now();
let _last = _now;
const time = (str)=> {
  log(str.padEnd(50, " ") + ' - Duration: ' + (Date.now()-_last)+ ', Total: ' + (Date.now()-_now));
  _last = Date.now();
 };
 
//return await FindObject('variables');
//return await FindObject('devices');
//return await FindObject('zones');
//return await FindObject('apps');

//return await FindObject('variables', 'test');
//return await FindObject('variables', 'c31ecacc-26fe-448e-ab00-d109618c8382');
//return await FindObject('devices', [{id:'f6ef2b6f-e3f2-4ecf-a205-4f919a7a6d3f'}, {name:'Test AVD'}, {name:'Light SwitCH'} ]);
//return await FindObject('zones', 'Hal*'); //Use astrix at the end means it tries to find the start of the name
return await FindObject('apps', 'Device Cap*');


async function FindObject(type, toFind) {

  let preType;
  let objectsToFind;
  if(toFind) {
    if(!_.isArray(toFind)) toFind = [toFind];
    for(let i=0;i<toFind.length;i++) {
      if(typeof(toFind[i])=='string') {
        if(type!=='apps' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/gm.test(toFind[i])) toFind[i] = {id:toFind[i]};
        else  toFind[i] = {name:toFind[i]};
      }
      if(toFind[i].name && toFind[i].name.endsWith('*')) {
        toFind[i].startsWith = true;
        toFind[i].name = toFind[i].name.substring(0,toFind[i].name.length-1);
      }
    }
  }
  console.log(toFind);
  //return toFind;
  
  time('ObjectToFind Collect - Start');
  switch(type.toLowerCase()) {
    case 'variables':
      preType = 'homey:manager:logic|';
      objectsToFind = await Homey.logic.getVariables();
      break;
    case 'devices':
      preType = 'homey:device:';
      objectsToFind = await Homey.devices.getDevices();
      break;
    case 'zones':
      preType = 'homey:zone:';
      objectsToFind = await Homey.zones.getZones();
      break;
    case 'apps':
      preType = 'homey:app:';
      objectsToFind = await Homey.apps.getApps();
      break;
  }
  time('ObjectToFind Collect - Finish');

  time('ObjectToFind Filter - Start');

if(toFind) objectsToFind = _.pickBy(objectsToFind, o=> !!_.find(toFind, find=>
    (!find.id || o.id===find.id) && 
    (!find.name || (find.startsWith ? o.name.toLowerCase().startsWith(find.name.toLowerCase()) : o.name.toLowerCase()===find.name.toLowerCase()))));
  time('ObjectToFind Filter - Finish');
  //let type = 'Variables'
  
  
  time('Get Flows - Start');
  let flows = await Homey.flow.getFlows();
  time('Get Flows - Finish');

  //flows = _.filter(flows, f=>f.name==='FIND VARIABLES');
  time('Get Advanced Flows - Start');
  let afs = await Homey.flow.getAdvancedFlows();
  time('Get Advanced Flows - Finish');

  let cardTypes = ['trigger', 'condition', 'action'];


  let objectsFound = {};

  let containsId = (str, id, preFixed)=> typeof(str)==='string' ? (str.indexOf(preFixed? (preFixed===2?'[[':'')+preType+id : id)>-1) : null;
  let containsIds = (str, preFixed)=> containsId(str, objectsToFind, preFixed);

  let addToFound = (flow, cardType, obj) => {
    if(!objectsFound[obj.id]) objectsFound[obj.id] = {id:obj.id, name:obj.name, flows:{}}
    if(!objectsFound[obj.id].flows[flow.id]) objectsFound[obj.id].flows[flow.id] = {id:flow.id, name:flow.name, enabled:flow.enabled};
    if(!objectsFound[obj.id].flows[flow.id][cardType+'s']) {
      objectsFound[obj.id].flows[flow.id][cardType+'s'] = true;//{found:true};
    }
  };

  time('Remap Flows - Start');
  flows = _.map(flows,f=>{let rFlow = {
    id:f.id,
    name:f.name
    };
    for(let i=0;i<cardTypes.length;i++) {
      let cardType = cardTypes[i];
      let c = f[cardType] ? [f[cardType]] : f[cardType+'s'];
      if(c) rFlow[cardType+'s'] = _.map(c, x=>{
        var r= {
          ownerUri:x.uri,
          args:_.filter(x.args, arg=> arg && 
        (
          (typeof(arg)==='string' && arg.indexOf('[['+preType)>-1) ||
          (typeof(arg)==='object')
        )
        )};
        if(!Object.keys(r.args).length) delete r.args;
        if(x.droptoken && x.droptoken.indexOf(preType)>-1) r.droptoken = x.droptoken;
        return r;
      });
    }
    return rFlow;
  });
  time('Remap Flows - Finish');


  time('Remap Advanced Flows - Start');
  afs = _.map(afs,f=>{let rFlow = {
    id:f.id,
    name:f.name
    };
    for(let i=0;i<cardTypes.length;i++) {
      let cardType = cardTypes[i];
      //if(f[cardType+'s']) 
      rFlow[cardType+'s'] = _.map(_.filter(f.cards, c=>c.type===cardType), x=>{
        var r= {
          ownerUri:x.ownerUri,
          args:_.filter(x.args, arg=> arg && 
        (
          (typeof(arg)==='string' && arg.indexOf('[['+preType)>-1) ||
          (typeof(arg)==='object')
        )
        )};
        if(!Object.keys(r.args).length) delete r.args;
        if(x.droptoken && x.droptoken.indexOf(preType)>-1) r.droptoken = x.droptoken;
        return r;
      });
    }
    return rFlow;
  });

  time('Remap Advanced Flows - Finish');

  time('Combining Flows and Advanced Flows - Start');
  flows = _.union(flows, afs);
  time('Combining Flows and Advanced Flows - Finish');




  time('Search Objects - Start');
  for(let objID in objectsToFind) {
    let obj = objectsToFind[objID];
    for (let iFlow=0;iFlow<flows.length;iFlow++) {
      let flow = flows[iFlow];
      for(let iCardType=0;iCardType<cardTypes.length;iCardType++) {
          let cardType = cardTypes[iCardType];
          if(flow[cardType+'s']) 
            for(let iCards=0;iCards<flow[cardType+'s'].length;iCards++) {
              let card = flow[cardType+'s'][iCards];
              if(containsId(card.droptoken, obj.id, 1)) addToFound(flow, cardType, obj);
              if(containsId(card.ownerUri, obj.id, 1)) addToFound(flow, cardType, obj);

              if(card.args) _.each(card.args, arg=> {
                switch (typeof(arg)) {
                  case 'string':
                  if(containsId(arg, obj.id, 2)) addToFound(flow, cardType, obj);
                    break;
                  case 'object':
                    for(let o in arg) {
                      if(containsId(arg[o], obj.id)) addToFound(flow, cardType, obj);
                    }               
                    break;
                }
              });
            }
          
      }
    }
  }
  time('Search Objects - Finish');

  return objectsFound;
};
3 Likes

Nice!

Is it possible to print the name of the flow, or will we have to run the script again with the identifier?

How do you mean that?
The flow names are in it already.

Sorry. My bad - I was too quick :slight_smile: Works really nice, and good to be able to select what to search for :+1:

Flowcard:
image

The flowcard with example

1 Like

Would it be possible to add an option to this script to find any flows which contain a β€œStart Flow” action card to start another flow with the specified name? Preferably including the Start Flow with text/number/yesno/picture action cards.
Something like return await FindObject(β€˜flows’, β€˜Ring Doorbell’); which then retuns a list of all flows which contain a Start Flow β€œRing Doorbell” action card.

Very handy script for me, so thank you so much …
…
Since not all variables were found always for me, I worked around some problems:

/// 
///  ──────────────────────────────────────────────────────────────
///  Find Objects in Flows Script
///  ──────────────────────────────────────────────────────────────
///  This script searches for objects (Variables, Devices, Zones, Apps) 
///  within Flows and Advanced Flows in the Homey environment.
///  
///  Created by: Arie J. Godschalk
///  Creation date: October 5, 2022
///  Version: 1.0.1
///  
///  Enhanced and commented by: RuuRd van der Noord 
///  Enhancement date: January 27, 2025
///  
///  Additional improvements and suggestions provided by: 
///  AI Assistant (Perplexity) 
///


/// Define time tracking for performance measurement
const _now = Date.now();
const bDebug = false;
let _last = _now;

/// Utility function to log duration for specific operations
const time = (str) => {
  // Log the duration of operations for performance tracking
  if (bDebug)
    console.log(str.padEnd(50, " ") + ' - Duration: ' + (Date.now() - _last) + ', Total: ' + (Date.now() - _now));
  _last = Date.now();
};

return await FindObject('apps', 'Homesh Controller');
// return await FindObject('variables', 'test');
// return await FindObject('variables', `e4036857-790c-4bbc-825f-301568f41505`);
// return await FindObject('devices', [{id:'f6ef2b6f-e3f2-4ecf-a205-4f919a7a6d3f'}, {name:'Test AVD'}, {name:'Light SwitCH'} ]);
// return await FindObject('zones', 'Hal*'); 
// Use astrix at the end means it tries to find the start of the name
// return await FindObject('apps', 'Device Cap*');

//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!

//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!

/// Main function to find objects of a specified type
async function FindObject(type, toFind) {
  let preType; // Prefix for identifying object types
  let objectsToFind; // Collection of objects to search

  // Ensure toFind is an array for uniform processing
  if (toFind) {
    if (!_.isArray(toFind)) toFind = [toFind];
    for (let i = 0; i < toFind.length; i++) {
      if (typeof (toFind[i]) == 'string') {
        // Convert string identifiers into object format
        if (type !== 'apps' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/gm.test(toFind[i])) {
          toFind[i] = { id: toFind[i] };
        } else {
          toFind[i] = { name: toFind[i] };
        }
      }
      // Handle wildcard searches with asterisks
      if (toFind[i].name && toFind[i].name.endsWith('*')) {
        toFind[i].startsWith = true;
        toFind[i].name = toFind[i].name.substring(0, toFind[i].name.length - 1);
      }
    }
  }

  time('ObjectToFind Collect - Start');

  // Collect objects based on the specified type
  switch (type.toLowerCase()) {
    case 'variables':
      preType = 'homey:manager:logic|';
      objectsToFind = await Homey.logic.getVariables();
      break;
    case 'devices':
      preType = 'homey:device:';
      objectsToFind = await Homey.devices.getDevices();
      break;
    case 'zones':
      preType = 'homey:zone:';
      objectsToFind = await Homey.zones.getZones();
      break;
    case 'apps':
      preType = 'homey:app:';
      objectsToFind = await Homey.apps.getApps();
      break;
    default:
      throw new Error(`Unsupported type: ${type}`);
  }

  // Log retrieved variables for debugging
  if (bDebug)
    console.log('Retrieved Variables:', JSON.stringify(objectsToFind, null, 2)); // Pretty print for readability

  time('ObjectToFind Collect - Finish');

  time('ObjectToFind Filter - Start');

  // Filter collected objects based on the search criteria
  if (toFind) {
    objectsToFind = _.pickBy(objectsToFind, obj => {
      return !!_.find(toFind, find => {
        const idMatch = !find.id || obj.id === find.id; // Exact ID match
        const nameMatch = !find.name ||
          (find.startsWith ? obj.name.toLowerCase().startsWith(find.name.toLowerCase()) :
            obj.name.toLowerCase() === find.name.toLowerCase()); // Exact name match or starts with
        return idMatch && nameMatch; // Both must match
      });
    });
  }

  // Log filtered results for debugging
  console.log('Filtered Objects:', JSON.stringify(objectsToFind, null, 2)); // Pretty print for readability

  time('ObjectToFind Filter - Finish');

  // Fetch all flows and advanced flows
  time('Get Flows - Start');
  let flows = await Homey.flow.getFlows();

  // Ensure flows is an array
  if (!Array.isArray(flows)) {
    flows = Object.values(flows); // Convert object to array if necessary
  }

  time('Get Flows - Finish');

  time('Get Advanced Flows - Start');
  let advancedFlows = await Homey.flow.getAdvancedFlows();

  // Ensure advancedFlows is an array
  if (!Array.isArray(advancedFlows)) {
    advancedFlows = Object.values(advancedFlows); // Convert object to array if necessary
  }

  time('Get Advanced Flows - Finish');

  let cardTypes = ['trigger', 'condition', 'action']; // Types of flow cards

  let objectsFound = {}; // Store found objects

  // Helper function to check if a string contains a specific ID
  const containsId = (str, id, preFixed) => typeof (str) === 'string' ?
    (str.indexOf(preFixed ? (preFixed === 2 ? '[[' : '') + preType + id : id) > -1) : null;

  // Add found object details into the results collection
  const addToFound = (flow, cardType, obj) => {
    if (!objectsFound[obj.id]) {
      objectsFound[obj.id] = { id: obj.id, name: obj.name, flows: {} };
    }
    if (!objectsFound[obj.id].flows[flow.id]) {
      objectsFound[obj.id].flows[flow.id] = { id: flow.id, name: flow.name, enabled: flow.enabled };
    }
  };

  // Process flows and advanced flows for matching objects
  flows.forEach(flow => {
    cardTypes.forEach(cardType => {
      const cards = flow[cardType] ? [flow[cardType]] : flow[cardType + 's'];
      if (cards) {
        cards.forEach(card => {
          const argsFiltered = _.filter(card.args, arg => arg &&
            ((typeof (arg) === 'string' && arg.indexOf('[[' + preType) > -1) ||
              (typeof (arg) === 'object'))
          );
          const resultCard = { ownerUri: card.uri };
          if (argsFiltered.length) resultCard.args = argsFiltered;
          if (card.droptoken && card.droptoken.indexOf(preType) > -1) resultCard.droptoken = card.droptoken;

          // Check for matches against found objects and add them accordingly
          Object.values(objectsToFind).forEach(obj => {
            if (containsId(card.droptoken, obj.id, true) || containsId(card.ownerUri, obj.id, true)) {
              addToFound(flow, cardType, obj);
            }
            if (card.args) _.each(card.args, arg => {
              switch (typeof (arg)) {
                case 'string':
                  if (containsId(arg, obj.id, false)) addToFound(flow, cardType, obj);
                  break;
                case 'object':
                  for (let o in arg) {
                    if (containsId(arg[o], obj.id)) addToFound(flow, cardType, obj);
                  }
                  break;
              }
            });
          });
        });
      }
    });
  });

  advancedFlows.forEach(advancedFlow => {
    cardTypes.forEach(cardType => {
      const cardsFiltered = _.filter(advancedFlow.cards, c => c.type === cardType);
      cardsFiltered.forEach(card => {
        const argsFiltered = _.filter(card.args, arg => arg &&
          ((typeof (arg) === 'string' && arg.indexOf('[[' + preType) > -1) ||
            (typeof (arg) === 'object'))
        );
        const resultCard = { ownerUri: card.ownerUri };
        if (argsFiltered.length) resultCard.args = argsFiltered;
        if (card.droptoken && card.droptoken.indexOf(preType) > -1) resultCard.droptoken = card.droptoken;

        // Check for matches against found objects and add them accordingly
        Object.values(objectsToFind).forEach(obj => {
          if (containsId(card.droptoken, obj.id, true) || containsId(card.ownerUri, obj.id, true)) {
            addToFound(advancedFlow, cardType, obj);
          }
          if (card.args) _.each(card.args, arg => {
            switch (typeof (arg)) {
              case 'string':
                if (containsId(arg, obj.id, false)) addToFound(advancedFlow, cardType, obj);
                break;
              case 'object':
                for (let o in arg) {
                  if (containsId(arg[o], obj.id)) addToFound(advancedFlow, cardType, obj);
                }
                break;
            }
          });
        });
      });
    });
  });

  return objectsFound; // Return the found objects with their details
}

1 Like

it didn’t seam to work for me, i spent a couple hours with copilot and got this working script.

Nice you found a solution, maybe you forgot to add Λ΅this working scriptΛΆ

i did add it but it didn’t save to my post try this

/// 
///  ──────────────────────────────────────────────────────────────
///  Comprehensive Find Objects in Flows Script
///  ──────────────────────────────────────────────────────────────
///  This script searches for specific text within Flows, Advanced Flows, 
///  Variables, and Devices in the Homey environment and returns the names 
///  of the matching flows.
///  
///  Created by: AI Assistant (Perplexity)
///  Creation date: January 29, 2025
///  Version: 3.4
/// 


const _now = Date.now();
const bDebug = false; // Reduce logging to avoid crashes
let _last = _now;

/// Utility function to log duration for specific operations
const time = (str) => {
  // Log the duration of operations for performance tracking
  if (bDebug)
    console.log(str.padEnd(50, " ") + ' - Duration: ' + (Date.now() - _last) + ', Total: ' + (Date.now() - _now));
  _last = Date.now();
};

async function searchAllFlows(toFind) {
  if (!toFind) return "No search query provided.";
  const results = await findInFlows(toFind);
  return `Objects Containing "${toFind}":\n${results.join("\n")}`;
}

async function findInFlows(toFind) {
  const results = [];

  // Collect and filter variables
  const variables = await Homey.logic.getVariables();
  results.push(...filterObjects(variables, toFind, 'Variable'));

  // Collect and filter devices
  const devices = await Homey.devices.getDevices();
  results.push(...filterObjects(devices, toFind, 'Device'));

  // Collect and filter flows
  const flows = await Homey.flow.getFlows();
  results.push(...filterFlows(flows, toFind, 'Flow'));

  // Collect and filter advanced flows
  const advancedFlows = await Homey.flow.getAdvancedFlows();
  results.push(...filterFlows(advancedFlows, toFind, 'Advanced Flow'));

  return results;
}

function filterObjects(objects, toFind, type) {
  const names = [];

  for (const objId in objects) {
    const obj = objects[objId];
    if (obj.name && obj.name.toLowerCase().includes(toFind.toLowerCase())) {
      names.push(`${type}: ${obj.name}`);
    }
  }

  return names;
}

function filterFlows(flows, toFind, type) {
  const flowNames = [];

  for (const flowId in flows) {
    const flow = flows[flowId];

    if (flow.name && flow.name.toLowerCase().includes(toFind.toLowerCase())) {
      flowNames.push(`${type}: ${flow.name}`);
      continue;
    }

    const cards = [
      ...(flow.trigger ? [flow.trigger] : []),
      ...(flow.conditions ? flow.conditions : []),
      ...(flow.actions ? flow.actions : [])
    ];

    for (const card of cards) {
      let found = false;

      for (const arg of Object.values(card.args || {})) {
        if (typeof arg === 'string' && arg.toLowerCase().includes(toFind.toLowerCase())) {
          flowNames.push(`${type}: ${flow.name}`);
          found = true;
          break;
        }

        if (typeof arg === 'object' && arg !== null) {
          for (const val of Object.values(arg)) {
            if (typeof val === 'string' && val.toLowerCase().includes(toFind.toLowerCase())) {
              flowNames.push(`${type}: ${flow.name}`);
              found = true;
              break;
            }
          }
        }
      }

      if (found) break;
    }
  }

  return flowNames;
}

// Search for the provided argument text
const searchArgument = args[0]; // Argument passed to the script
return await searchAllFlows(searchArgument);
1 Like

This card should do the trick as well:

1 Like

It was my first attempt at a script, I was trying to just search for any text regardless of the object type

1 Like

This is why we shouldn’t use AI to generate code that humans will have to maintain afterwards. In software development there is a paradigm of readability vs efficiency. For humans to create and maintain software that works without bugs, it’s important that the code is readable and understandable. This usually comes at the cost of a slight performance penalty though. That is however a price that we would be happily pay because these performance penalties are almost never an actual problem in real life. Generative AI on the other hand basically thought themselves to program and for them, efficient code is the absolute most important factor. AI won’t make the same mistakes as humans do, and being more efficient basically means being smarter. It is really hard to have AI suggest code that is also human readable and maintainable as it is really not in their nature to do so. Code that is generated by AI is almost always immediately recognizable and in our company almost exclusively rejected in code reviews.

This script is an excellent example of this. It might work but it’s full of patterns and programming styles that make it really hard to understand and therefore really hard to make changes to without breaking stuff. for instance:

  1. Using native for loops instead of array methods. While native for loops might be slightly faster, they do create problems with scoping of parameters and closures.
  2. Nesting for loops. Nested loops are more memory efficient, but it is hard to debug or to keep track of what happens at what moment in time.
  3. Not using Immutability principles. Mutating the same variable, object object or array is more memory efficient, but it makes it hard to understand what the contents of the object or array is and could lead to unexpected state and side effects.
  4. Unclear naming and non-standard naming conventions. Function and variable names do not always exactly describe their function, are abbreviated, or do not follow standard naming conventions making the code less clear.

I also took the liberty to rewrite the script and taking the above principles in mind I was able to greatly reduce the amount of code required to do the same thing:

return await searchAllFlows(args[0]);

async function searchAllFlows(searchArgument) {
    if (!searchArgument) throw "No search query provided.";
    
    const [
        variables,
        devices,
        flows,
        advancedFlows
    ] = await Promise.all([
        Homey.logic.getVariables,
        Homey.devices.getDevices,
        Homey.flow.getFlows,
        Homey.flow.getAdvancedFlows
    ])

    const results =  [
        ...filterObject(variables, searchArgument, 'Variable'),
        ...filterObject(devices, searchArgument, 'Device'),
        ...filterObject(flows, searchArgument, 'Flow'),
        ...filterObject(advancedFlows, searchArgument, 'Advanced Flow')
    ]
    return `Objects Containing "${searchArgument}":\n${results.join("\n")}`;
}

function filterObject(object, searchArgument) {
    const lowerCasedSearchArgument = searchArgument.toLowerCase()
    return Object
        .values(object)
        .filter(({name, trigger, conditions, actions}) => {
            if(name && name.toLowerCase().includes(lowerCasedSearchArgument)){
                return true;
            };
 
            if (!trigger && !conditions && !actions) {
                return false;
            }

            return [
                ...(trigger ? Object.values(trigger.args) : []),
                ...(conditions ? Object.values(conditions.args) : []),
                ...(actions ? Object.values(actions.args) : [])
            ].some((arg) => {
                if (typeof arg === 'string' && arg.toLowerCase().includes(lowerCasedSearchArgument)) {
                    return true;
                }

                if (typeof arg === 'object' && arg !== null) {
                    return Object.values(arg).some((val) => val === 'string' && val.toLowerCase().includes(lowerCasedSearchArgument))
                }

                return false;
            });
        })
        .map(({name}) => `${type}: ${name}`);
}

1 Like

I neither know how to read or write scripts, So AI is the perfect option, I have a few other scripts that I have used copilot for that work perfectly

yours didn’t work for me but this did

return await searchAll(args[0]);

async function searchAll(searchArgument) {
    if (!searchArgument) throw "No search query provided.";
    
    const [
        variables,
        devices,
        flows,
        advancedFlows
    ] = await Promise.all([
        Homey.logic.getVariables().catch(() => ({})),
        Homey.devices.getDevices().catch(() => ({})),
        Homey.flow.getFlows().catch(() => ({})),
        Homey.flow.getAdvancedFlows().catch(() => ({}))
    ]);

    const idToNameMap = {
        ...createIdToNameMap(variables, 'Variable'),
        ...createIdToNameMap(devices, 'Device'),
        ...createIdToNameMap(flows, 'Flow'),
        ...createIdToNameMap(advancedFlows, 'Advanced Flow')
    };

    const results = [
        ...filterObject(variables, searchArgument, 'Variable', idToNameMap),
        ...filterObject(devices, searchArgument, 'Device', idToNameMap),
        ...filterObject(flows, searchArgument, 'Flow', idToNameMap),
        ...filterObject(advancedFlows, searchArgument, 'Advanced Flow', idToNameMap)
    ];
    
    return `Objects Containing "${searchArgument}":\n${results.join("\n")}`;
}

function createIdToNameMap(object, type) {
    if (!object || typeof object !== 'object') {
        return {};
    }

    return Object.keys(object).reduce((map, id) => {
        map[id] = `${type} (${object[id].name})`;
        return map;
    }, {});
}

function filterObject(object, searchArgument, type, idToNameMap) {
    if (!object || typeof object !== 'object') {
        return [];
    }
    
    const lowerCasedSearchArgument = searchArgument.toLowerCase();
    return Object
        .entries(object)
        .flatMap(([id, item]) => searchWithinObject(id, item, lowerCasedSearchArgument, type, idToNameMap));
}

function searchWithinObject(id, object, searchArgument, type, idToNameMap) {
    if (!object || typeof object !== 'object') {
        return [];
    }

    const results = [];
    
    if (object.name && object.name.toLowerCase().includes(searchArgument)) {
        results.push(`${idToNameMap[id]}: ${object.name}`);
    }

    if (object.trigger || object.conditions || object.actions) {
        [
            ...(object.trigger ? Object.values(object.trigger.args || {}) : []),
            ...(object.conditions ? Object.values(object.conditions.args || {}) : []),
            ...(object.actions ? Object.values(object.actions.args || {}) : [])
        ].forEach(arg => {
            if (typeof arg === 'string' && arg.toLowerCase().includes(searchArgument)) {
                results.push(`${idToNameMap[id]}: ${arg}`);
            }

            if (typeof arg === 'object' && arg !== null) {
                results.push(...searchWithinObject(id, arg, searchArgument, type, idToNameMap));
            }
        });
    }

    return results;
}

Oops, I made some modifications and didn’t tested it again afterwards. here’s the working script:

return await searchAllFlows(args[0]);

async function searchAllFlows(searchArgument) {
    if (!searchArgument) throw "No search query provided.";
    
    const [
        variables,
        devices,
        flows,
        advancedFlows
    ] = await Promise.all([
        Homey.logic.getVariables(),
        Homey.devices.getDevices(),
        Homey.flow.getFlows(),
        Homey.flow.getAdvancedFlows()
    ])

    const results =  [
        ...filterObject(variables, searchArgument, 'Variable'),
        ...filterObject(devices, searchArgument, 'Device'),
        ...filterObject(flows, searchArgument, 'Flow'),
        ...filterObject(advancedFlows, searchArgument, 'Advanced Flow')
    ]

    return `Objects Containing "${searchArgument}":\n${results.join("\n")}`;
}

function filterObject(object, searchArgument, type) {
    const lowerCasedSearchArgument = searchArgument.toLowerCase()
    return Object
        .values(object)
        .filter(({name, trigger, conditions, actions}) => {
            if(name && name.toLowerCase().includes(lowerCasedSearchArgument)){
                return true;
            };

            return [
                ...(trigger?.args ? Object.values(trigger.args) : []),
                ...(conditions?.args ? Object.values(conditions.args) : []),
                ...(actions?.args ? Object.values(actions.args) : [])
            ].some((arg) => {
                if (typeof arg === 'string' && arg.toLowerCase().includes(lowerCasedSearchArgument)) {
                    return true;
                }

                if (typeof arg === 'object' && arg !== null) {
                    return Object.values(arg).some((val) => val === 'string' && val.toLowerCase().includes(lowerCasedSearchArgument))
                }

                return false;
            });
        })
        .map(({name}) => `${type}: ${name}`);
}