Searching for a flow that use a specific push text?

Now and then (not that very often) it get a push with the text “Homey” nothing more, just “Homey”.

I don’t understand why, because I can’t remember making a flow with a push with just the word “Homey”. Is there a way to search for specific words in flows or in push texts?

Going through all my flows to find it manually would take a long time…

You can have all flows in text returned with these commands
Homey.flow.getAdvancedFlows();
Homey.flow.getFlows();
@ https://tools.developer.homey.app/tools/api-playground

The output can be searched with your browser’s search tool, or copy/paste output into your text editor and use it’s search function

Thank you, I will give it a try tomorrow.

Indeed, Peter, so you need to repeat the export for all the flows you want to include in your search:
Then you can use your search tool to query any regex query. Your approach will, of course, show them all, although the output will vary.

Here’s a script that allows you to perform a preliminary search using Homey.flow.getAdvancedFlows(); and Homey.flow.getFlows(); in your flows.

/**
 * 
 * Homey Flow Script and Code Filter Utility
 *
 * This script fetches all Homey standard and advanced flows and provides filtered views
 * based on current settings. It supports filtering to list:
 * - Flow cards that run HomeyScript scripts,
 * - Flow cards that run HomeyScript code,
 * - Or show all flow names unfiltered.
 *
 * The filtering mode is controlled by the state in oState (SCRIPT, CODE, or OFF).
 *
 * Key Features:
 * - Retrieve all flows and advanced flows combined.
 * - Filter cards by script/codde name presence.
 * - Show script card names or excerpt code samples with length limit.
 * - Provides detailed logging of filtered results and flow counts.
 *
 * Variable Naming:
 * Uses short Hungarian notation for improved clarity:
 * - i for integers or counters
 * - a for arrays
 * - o for objects
 * - s for strings
 * - b for booleans
 *
 * Usage:
 * - Set the desired state via `oState.state` in SETTINGS: 
 *    (StateContainer.SCRIPT, StateContainer.CODE, StateContainer.OFF).
 * - The script outputs filtered flow data to the Output log.
 *
 * Author: Ruurd
 * Assistance: Perplexity AI Assistant
 * Date: November 2025
 */

//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
//-!-!-!-!-!-!-!-!-!-! PRIMARY SETTING !-!-!-!-!-!-!-!-!-!-!-!
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
class StateRecord { constructor(t, e) { this.value = t, this.description = e } } class StateContainer { static SHOW_OBJ_ID = true;  static CODE = new StateRecord("CODE", "Show CODE samples"); static SCRIPT = new StateRecord("SCRIPT", "Show script names"); static OFF = new StateRecord("OFF", "No filter, show all flow names"); static OFF_RX = new StateRecord("OFF_RX", "Regex based filter") }

//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
//-!-!-!-!-!-!-!-!-!-!-!-!- APPLY SETTINGS -!-!-!-!-!-!-!-!-!-!
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!


// StateContainer. CODE SCRIPT OFF OFF_RX
await applySettings(StateContainer.OFF);


//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!- RUN -!-!-!-!-!-!-!-!-!-!-!-!-!
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
let iCounter = 0;
let iCounterFilter = 0;
let iCounterDisabled = 0;
let sAllText = ``;

for (let oFlow of aAllFlows) { // StateContainer.OFF or StateContainer.OFF_RX

  /**
   * Check if flow should be processed based on enabled flag and filter setting.
   *
   * @param {Object} oFlow - The flow object to check.
   * @param {boolean} b_ENABLED_ONLY - Flag to enable filtering by flow enabled state.
   * @returns {boolean} True if the flow passes the enabled state check, false otherwise.
   */

  if (!b_ENABLED_ONLY || oFlow.enabled) {
    if (!bFilter) {
      // regex to replace unallowed filename characters with "_"
      if (oState.state === StateContainer.OFF_RX) {
        const sResult = (SHOW_OBJ_ID ? `{name: '${oFlow.name}'` : `${oFlow.name.replace(sFilter, (match) => aReplacements[match] || "")}`) + (SHOW_OBJ_ID ? `, id: '${oFlow.id}'}` : '');
        // log(`${oFlow.name.replace(sFilter, '_')}`); // ...  /[\\\/:\*\?"<>\|]/g
        sAllText += (`${sResult}\n`);
        iCounter++;
      }
      else if (oState.state === StateContainer.OFF) { // Show all flow names unfiltered
        sAllText += (`${oFlow.name}\n`);
        iCounter++;
      }
      else {
        sAllText = (`unknown STATE`);
      }
    }
    if (bFilter && oFlow.cards) {
      const sResult = filter(oFlow.cards, oFlow); // Filter & show results
      if (sResult !== null) {
        const sJsonString = JSON.stringify(sResult, null, 2);
        const sStrippedString = sJsonString.replace(/"/g, ''); // Remove double quotes for readability
        sAllText += (`${sStrippedString}\n`);
        // iCounter++;
        iCounterFilter++;
      }
    }
  }
  else {
    iCounterDisabled++;
  }
}

// -!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
// SHOW - RETURN
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
const sCounters = iCounterFilter == 0 ? `(Enabled:${iCounter}, Disabled:${iCounterDisabled})` :
  `(Filtered:${iCounterFilter})`;

log(
  `## State: ${JSON.stringify(oState.state)}\n` +
  `## Filter ${b_ENABLED_ONLY ? 'Enabled' : ''}: ${bFilter ? `-> "${sFilter}"` : StateContainer.OFF.value}\n` +
  `## allFlows: ${aAllFlows.length} ${sCounters}\n` +
  `## Enabled Flows Only: ${b_ENABLED_ONLY}\n` +
  `## Show as Object: ${SHOW_OBJ_ID} (Show as Object with ID)\n\n` +
  (SHOW_OBJ_ID ? 'const aFlows = [\n' : '') +
  sAllText +
  (SHOW_OBJ_ID ? ']\n' : '')
);

return iCounter;


//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
//-!-!-!-!-!-!-!-!-!-!-!-!- FUNCTIONS -!-!-!-!-!-!-!-!-!-!-!-!
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!

/**
 * applySettings function initializes and sets global constants,
 * variables, and state for flow filtering in HomeyScript.
 *
 * @param {string} sState - The desired state to set on initialization.
 *                          Accepts one of: "CODE", "SCRIPT", "OFF", "OFF_RX".
 *                          Defaults to "OFF_RX" if an unknown state is passed.
 *
 * @returns {Promise<void>} - Returns a promise as it performs asynchronous flow data fetching.
 */
async function applySettings(sStateValue) {
  // 
  globalThis.i_CODE_LEN = 20;           // length of Code samples to display
  globalThis.b_ENABLED_ONLY = true;     // filter by enabled status

  globalThis.aReplacements = {          // replacement chars for filenames
    '\\': "_",
    '/': "_",
    ':': "_",
    '*': "_",
    '?': "_",
    '"': "_",
    '<': "_",
    '>': "_",
    '|': "__"
  };

  // Define stateful variables globally too
  globalThis.bScriptCard = false;
  globalThis.bFilter = false;
  globalThis.sFilter = '';
  globalThis.SHOW_OBJ_ID = StateContainer.SHOW_OBJ_ID;;

  globalThis.oState = {
    state: StateContainer.OFF_RX,
    evaluate() {
      if (sStateValue === StateContainer.SCRIPT) {
        globalThis.bScriptCard = true;
        globalThis.bFilter = true;
        globalThis.sFilter = "homey:app:com.athom.homeyscript:run";
        return StateContainer.SCRIPT;
      } else if (sStateValue === StateContainer.CODE) {
        globalThis.bScriptCard = false;
        globalThis.bFilter = true;
        globalThis.sFilter = "homey:app:com.athom.homeyscript:runCode";
        return StateContainer.CODE;
      } else if (sStateValue === StateContainer.OFF) {
        globalThis.bFilter = false;
        globalThis.sFilter = '';
        // globalThis.SHOW_OBJ_ID = StateContainer.SHOW_OBJ_ID;
        return StateContainer.OFF;
      } else if (sStateValue === StateContainer.OFF_RX) {
        globalThis.bFilter = false;
        globalThis.sFilter = new RegExp("[\\\\/:*?\"<>|]", "g");
        return StateContainer.OFF_RX;
      } else {
        console.log("Unknown state");
      }
    }
  };

  globalThis.aStandardFlows = Object.values(await Homey.flow.getFlows()); // Get all standard flows
  globalThis.aAdvancedFlows = Object.values(await Homey.flow.getAdvancedFlows()); // Get all advanced flows
  globalThis.aAllFlows = await getAllFlowsRaw(); // Combined flows array

  // Set state as requested - CODE SCRIPT OFF OFF_RX
  if (sStateValue === 'CODE') {
    globalThis.oState.state = StateContainer.CODE;
  } else if (sStateValue === 'SCRIPT') {
    globalThis.oState.state = StateContainer.SCRIPT;
  } else if (sStateValue === 'OFF') {
    globalThis.oState.state = StateContainer.OFF;
  } else if (sStateValue === 'OFF_RX') {
    globalThis.oState.state = StateContainer.OFF_RX;
  } else {
    globalThis.oState.state = StateContainer.OFF_RX; // default
  }
  globalThis.oState.evaluate(); // 
}

/**
 * Retrieves all flows including advanced flows, merged into a single array without duplicates.
 * @returns {Array<Object>} - Combined array of standard and advanced flow objects.
 */
async function getAllFlowsRaw() {
  return _.union(aStandardFlows, aAdvancedFlows);
}

/**
 * Filters flow cards based on current filter criteria.
 * @param {Object} oCards - Flow cards object indexed by card ids.
 * @param {Object} oFlow - Parent flow object for context.
 * @returns {string|null} - Summary string of filtered card names or code, or null if none match.
 */
function filter(oCards, oFlow = {}) {
  // Filter cards matching filter string and script condition
  const aFilteredEntries = Object.entries(oCards)
    .filter(([sKey, oCard]) =>
      oCard.id && oCard.id.includes(sFilter) &&
      (bScriptCard ? oCard.args?.script?.name : true)
    );

  // Build filtered object from entries
  const oFilteredCards = aFilteredEntries.reduce((obj, [sKey, oCard]) => {
    obj[sKey] = oCard;
    return obj;
  }, {});

  const iFilteredCount = aFilteredEntries.length;
  let sCardNames = '';
  let sCode = '';

  if (bScriptCard) {
    // Join script names with commas
    sCardNames = aFilteredEntries
      .map(([_, oCard]) => oCard.args.script.name)
      .join(', ')
      .replace(/, $/, ''); // Trim trailing comma/space
  } else {
    // Extract code samples truncated to i_CODE_LEN
    let sCodeSamples = '';
    let iCounter = 0;
    for (let oCard2 of Object.values(oFilteredCards)) {
      if (oCard2.args.code) {
        iCounter++;
        sCodeSamples += ` #${iCounter}${oCard2.args.code.substring(0, i_CODE_LEN)}`;
      }
    }
    sCode = sCodeSamples !== '' ? sCodeSamples : '"Code" not found';
  }

  if (iFilteredCount > 0) {
    return oFlow.name + (bScriptCard ? ` (${iFilteredCount} -> ${sCardNames})` : ` (${sCode.trim()})`);
  }
  return null;
}

That’s beyond my knowledge level. I got another issue to deal with right now, a broken wifi router. :man_facepalming:t3:

Ah, can you tell me how, Ruurd?
The new “tool” (with the ‘ALT’ button involved) can only export one flow at a time, as far as I know.

Peter, you can also use the Homey Experiments SSH Sandbox to run a custom Node.js to export flows, like:



async function exportAllFlows() {
  try {
    // Fetch all standard flows
    const standardFlows = await Homey.flow.getFlows();

    // Fetch all advanced flows
    const advancedFlows = await Homey.flow.getAdvancedFlows();

    // Save each flow to individual JSON files
    await fs.writeFile('standardFlows.json', JSON.stringify(standardFlows, null, 2));
    await fs.writeFile('advancedFlows.json', JSON.stringify(advancedFlows, null, 2));

    console.log('Flows exported successfully!');
  } catch (error) {
    console.error('Error exporting flows:', error);
  }
}

// Run the export function
exportAllFlows();

type or paste code here

Peter, Kim, this is the flow export script with PUSH cards added:

/**
 * 
 * Homey Flow Push, Script and Code Filter Utility
 *
 * This script fetches all Homey standard and advanced flows and provides filtered views
 * based on current settings. It supports filtering to list:
 * - Flow cards that run HomeyScript scripts,
 * - Flow cards that run HomeyScript code,
 * - Or show all flow names unfiltered.
 *
 * The filtering mode is controlled by the state in oState (SCRIPT, CODE, or OFF).
 *
 * Key Features:
 * - Retrieve all flows and advanced flows combined.
 * - Filter cards by push, script/code name presence.
 * - Show script card names or excerpt code samples with length limit.
 * - Provides detailed logging of filtered results and flow counts.
 *
 * Variable Naming:
 * Uses short Hungarian notation for improved clarity:
 * - i for integers or counters
 * - a for arrays
 * - o for objects
 * - s for strings
 * - b for booleans
 *
 * Usage:
 * - Set the desired state via `oState.state` in SETTINGS: 
 *    (StateContainer.PUSH, StateContainer.SCRIPT, StateContainer.CODE, StateContainer.OFF).
 * - The script outputs filtered flow data to the Output log.
 *
 * Author: Ruurd
 * Assistance: Perplexity AI Assistant
 * Date: December 2025 - Version 2025-12-02 - improved formatted output
 * 
 */


class StateRecord {
  constructor(value, description) {
    this.value = value;
    this.description = description;
  }
}

class StateContainer {
  static SHOW_OBJ_ID = true;
  static CODE = new StateRecord("CODE", "Show CODE samples");
  static SCRIPT = new StateRecord("SCRIPT", "Show script names");
  static PUSH = new StateRecord("PUSH", "Show Push cards contents");
  static OFF = new StateRecord("OFF", "No filter, show all flow names");
  static OFF_RX = new StateRecord("OFF_RX", "Regex based filter");

  static allStates = [
    StateContainer.CODE,
    StateContainer.SCRIPT,
    StateContainer.PUSH,
    StateContainer.OFF,
    StateContainer.OFF_RX,
  ];
}

await applySettings(StateContainer.PUSH); // Change State here for testing (CODE, SCRIPT, PUSH, OFF, OFF_RX)

let iCounter = 0;
let iCounterFilter = 0;
let iCounterDisabled = 0;
let sAllText = "";

for (const oFlow of aAllFlows) {
  if (!b_ENABLED_ONLY || oFlow.enabled) {
    if (!bFilter) {
      if (oState.state.value === StateContainer.OFF_RX.value) {
        const sResult = (SHOW_OBJ_ID
          ? `{name: '${oFlow.name}'`
          : `${oFlow.name.replace(sFilter, (match) => aReplacements[match] || "")}`
        ) + (SHOW_OBJ_ID ? `, id: '${oFlow.id}'}` : "");
        sAllText += `${sResult}\n`;
        iCounter++;
      } else if (oState.state.value === StateContainer.OFF.value) {
        sAllText += `${oFlow.name}\n`;
        iCounter++;
      }
    }
    if (bFilter && oFlow.cards) {
      const sResult = filter(oFlow.cards, oFlow);
      if (sResult !== null) {
        sAllText += `${sResult}\n`;
        iCounterFilter++;
      }
    }
  } else {
    iCounterDisabled++;
  }
}

const sCounters =
  iCounterFilter === 0
    ? `(Enabled:${iCounter}, Disabled:${iCounterDisabled})`
    : `(Filtered:${iCounterFilter})`;

log(
  `## State: ${JSON.stringify(oState.state)}\n` +
  `## Filter ${b_ENABLED_ONLY ? "Enabled" : ""}: ${bFilter
    ? typeof sFilter === "string"
      ? `"${sFilter}"`
      : sFilter.toString()
    : StateContainer.OFF.value
  }\n` +
  `## allFlows: ${aAllFlows.length} ${sCounters}\n` +
  `## Enabled Flows Only: ${b_ENABLED_ONLY}\n` +
  `## Show as Object: ${SHOW_OBJ_ID} (Show as Object with ID)\n\n` +
  (SHOW_OBJ_ID ? "const aFlows = [\n" : "") +
  sAllText +
  (SHOW_OBJ_ID ? "]\n" : "")
);

return iCounter;

async function applySettings(sStateValue) {
  globalThis.i_CODE_LEN = 20;
  globalThis.b_ENABLED_ONLY = true;
  globalThis.aReplacements = {
    "\\": "_",
    "/": "_",
    ":": "_",
    "*": "_",
    "?": "_",
    '"': "_",
    "<": "_",
    ">": "_",
    "|": "__",
  };

  globalThis.bScriptCard = false;
  globalThis.bFilter = false;
  globalThis.sFilter = "";
  globalThis.SHOW_OBJ_ID = StateContainer.SHOW_OBJ_ID;

  globalThis.oState = {
    state: StateContainer.OFF_RX,
    evaluate() {
      if (sStateValue.value === StateContainer.SCRIPT.value) {
        globalThis.bScriptCard = true;
        globalThis.bFilter = true;
        globalThis.sFilter = "homey:app:com.athom.homeyscript:run";
        return StateContainer.SCRIPT;
      } else if (sStateValue.value === StateContainer.CODE.value) {
        globalThis.bScriptCard = false;
        globalThis.bFilter = true;
        globalThis.sFilter = "homey:app:com.athom.homeyscript:runCode";
        return StateContainer.CODE;
      } else if (sStateValue.value === StateContainer.PUSH.value) {
        globalThis.bScriptCard = false;
        globalThis.bFilter = true;
        globalThis.sFilter = "homey:manager:mobile";
        return StateContainer.PUSH;
      } else if (sStateValue.value === StateContainer.OFF.value) {
        globalThis.bFilter = false;
        globalThis.sFilter = "";
        return StateContainer.OFF;
      } else if (sStateValue.value === StateContainer.OFF_RX.value) {
        globalThis.bFilter = false;
        globalThis.sFilter = new RegExp("[\\\\/:*?\"<>|]", "g");
        return StateContainer.OFF_RX;
      } else {
        console.log("Unknown state");
      }
    },
  };

  globalThis.aStandardFlows = Object.values(await Homey.flow.getFlows());
  globalThis.aAdvancedFlows = Object.values(await Homey.flow.getAdvancedFlows());
  globalThis.aAllFlows = await getAllFlowsRaw();
  globalThis.sSep = `\n-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!\n`;

  oState.state = oState.evaluate();
}

async function getAllFlowsRaw() {
  // Combine standard and advanced flows, remove duplicates by id
  const map = new Map();
  aStandardFlows.forEach((flow) => map.set(flow.id, flow));
  aAdvancedFlows.forEach((flow) => map.set(flow.id, flow));
  return Array.from(map.values());
}

function filter(oCards, oFlow = {}) {
  const aFilteredEntries = Object.entries(oCards).filter(([sKey, oCard]) => {
    if (oState.state.value === StateContainer.SCRIPT.value) {
      return oCard.id && oCard.id.includes(sFilter) && oCard.args?.script?.name;
    } else if (oState.state.value === StateContainer.CODE.value) {
      return oCard.id && oCard.id.includes(sFilter);
    }
    else if (oState.state.value === StateContainer.PUSH.value) {
      const allowlist = ["homey:manager:mobile", "homey:manager:mobile:push_text"]; // mobile push notifications
      const blocklist = [
        "homey:manager:cron", // cron scheduling
        "homey:manager:date", // calendar/date selection
        "homey:manager:logic", // logic group cards
        "homey:manager:time", // time related cards
        "homey:manager:system", // system internal cards
        "homey:manager:schedule", // scheduling cards
        "homey:manager:event", // event trigger cards
        "homey:manager:flow", // flow-related cards
        "homey:manager:energy", // energy monitoring cards
      ];

      const id = oCard.id || "";
      const allowed = allowlist.some((prefix) => id.startsWith(prefix));
      const blocked = blocklist.some((prefix) => id.startsWith(prefix));
      return allowed && !blocked;
    }
    return false;
  });

  const oFilteredCards = aFilteredEntries.reduce((obj, [sKey, oCard]) => {
    obj[sKey] = oCard;
    return obj;
  }, {});

  const iFilteredCount = aFilteredEntries.length;
  let sCardNames = "";
  let sCode = "";
  let sPushContent = "";

  if (oState.state.value === StateContainer.SCRIPT.value) {
    sCardNames = aFilteredEntries
      .map(([_, oCard]) => oCard.args.script.name)
      .join(", ")
      .replace(/, $/, "");
  } else if (oState.state.value === StateContainer.CODE.value) {
    let sCodeSamples = "";
    let iCounter = 0;
    for (const oCard2 of Object.values(oFilteredCards)) {
      //if (oCard2.args?.code) {
      if (oCard2.id?.includes("homey:app:com.athom.homeyscript:runCode") || (oCard2.args?.code)) { //  
        iCounter++;
        sCodeSamples += (`\n#${iCounter} ${oCard2.args.code.substring(0, i_CODE_LEN).replace(/[\r\n]+|\s{2,}/g, ' ')}`)
        // Replace newlines and multiple spaces with single space
      }
    }
    sCode = sCodeSamples !== "" ? sCodeSamples : 'Code not found';
  }
  else if (oState.state.value === StateContainer.PUSH.value) {
    let iCounter = 0;
    let rawText = "";

    // log(Object.values(oFilteredCards).length)
    for (const oCard2 of Object.values(oFilteredCards)) {
      iCounter++;
      if (oCard2.id?.includes("homey:manager:mobile:push_text") || oCard2.uri?.includes("homey:manager:mobile")) { // 
        // This is the push card message
        rawText = oCard2.args?.text || ""; // || oCard2.args?.message 
      }/*
      else
        rawText = `#${iCounter}` + JSON.stringify(oCard2);*/

      const cleanText = rawText
        .replace(/\[\[.*?\]\]/g, '')  // Remove [[...]] tokens
        .replace(/[\r\n]+/g, ' ')     // Replace newlines with space (keep before collapsing spaces)
        // .replace(/\s{2,}/g, ' ')      // Collapse multiple spaces into one
        .replace(/- /g, '')           // Remove "- " sequences
        .replace(/\s{2,}/g, ' ')      // Collapse multiple spaces into one
        .trim();

      sPushContent += `#${iCounter} ${cleanText}\n`;
    }
  }

  if (iFilteredCount > 0) {
    if (oState.state.value === StateContainer.PUSH.value)
      return `${sSep}${oFlow.name}${sSep}${sPushContent.trim()}`;
    else if (oState.state.value === StateContainer.SCRIPT.value)
      return `${sSep}${oFlow.name}${sSep}${iFilteredCount} ->> ${sCardNames}`;
    else return `${sSep}${oFlow.name}${sSep}${sCode.trim()}${sSep}`;
  }
  return null;
}

output example:

-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
sensTuin
-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
#1 Het is buiten nu koeler dan binnen!
#2 Het is buiten nu warmer dan binnen!
#3 Het is voor nu warmer dan in de studio!
#4 Het is voor nu koeler dan in de studio!
#5 De buitentemperatuur is nu < 0℃, zet warmtepomp SPAARSTAND LAGER. (niveau 2 of 1)

-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
Storing
-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
#1 WP Buiten is offline.
#2 WP Binnen is offline.
#3 :: Ecoflow online: werd weer ingeschakeld.
#4 :: KruipQlima online: werd weer ingeschakeld.
#5 Storing Net@EcoHW OFFLINE zie: sInterim ; bInterim
#6 Storing Net@KruipQ OFFLINE zie: sInterim ; bInterim
#7 Storing Net@Ecoflow OFFLINE zie: sInterim ; bInterim
#8 Storing Net@Ecoflow OFFLINE zie: sInterim ; bInterim

I see. No Pro 2023 yet :man_shrugging::winking_face_with_tongue:

The project is under development (specially to add the get and replace varibles that are used in a push notification),.
Next to my 2023 I use a 2019, so testing can be done.
The script runs fine on Pro 2019 (here).

output for my 2019:

State: {“value”:“PUSH”,“description”:“Show Push notification cards contents”}

Filter Enabled: “homey:manager:mobile”

allFlows: 56 (Filtered:6)

Enabled Flows Only: true

Show as Object: true (Show as Object with ID)

const aFlows = [

-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
triggerGordijnenZ
-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
#1 date time : GordijnOpen = UITGEZET Nu: iGordijnPositie: ba5f62b2-7184-434e-84fe-7ae8eda46f03 bGordijnenOpen: fcdeede8-c355-4214-b834-d868da2b7c00 - date
#2 date time : GordijnClose = UITGEZET Nu: iGordijnPositie: ba5f62b2-7184-434e-84fe-7ae8eda46f03 bGordijnenOpen: fcdeede8-c355-4214-b834-d868da2b7c00 - date

-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
AlarmNachtCheck
-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
#1 date time ERROR Timers/AlarmNachtCheck GordijnOpen en/of GrordijnClose was niet uit. - date

-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
triggerPRO2023
-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
#1 date time triggerPRO2023 - date

-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
receiveSens
-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
#1 testHomesh - testHomesh

-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
DiogenesD Aangezet
-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
#1 DiogenesD werd naar dda8f647-f9f6-4bf8-b757-b9a8085de3c2 gedimd. - DiogenesD

-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
triggerLampenZ
-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
#1 TolomeoS :boolean1 - TolomeoS
#2 TolomeoR :boolean1 - TolomeoR
#3 DiogenesD :boolean1 - DiogenesD
]

Thank you, I tried it and it found my notifications but not the one with just “Homey”.

Sorry I was hospitalized for a while.

There’s no obvious cause for your problem with the script. Here’s the current version, which prints all push notification cards to the output log, with the variable bShowPushCard set to true. This should help.

/**
 * Homey Flow Push, Script and Code Filter Utility
 * - Version 2025-12-03 
 * - Added improved formatted output
 * - Added support for variables in Push notification
 * 
 * This script fetches all Homey standard and advanced flows and provides filtered views
 * based on current settings. It supports filtering to list:
 * - Flow cards that run HomeyScript scripts,
 * - Flow cards that run HomeyScript code,
 * - Flow cards that run push notifications
 * - Or show all flow names unfiltered.
 * 
 * The filtering mode is controlled by the state in oState (PUSH, SCRIPT, CODE, OFF).
 * 
 * Key Features:
 * - Retrieve all flows and advanced flows combined (deduplicated).
 * - Filter cards by push, script/code name presence, or ID patterns.
 * - Show script card names or excerpt code samples with configurable length limit.
 * - Process push notification text by replacing [[variable tokens]] with actual values.
 * - Provides detailed logging of filtered results and flow counts.
 * 
 * Variable Naming:
 * Uses short Hungarian notation for improved type clarity:
 * - i for integers or counters
 * - a for arrays
 * - o for objects
 * - s for strings
 * - b for booleans
 * 
 * Usage:
 * - Jump to `applySettings(StateContainer.PUSH)` for testing:
 *   - StateContainer.PUSH, StateContainer.SCRIPT, StateContainer.CODE, 
 *   - StateContainer.OFF, StateContainer.OFF_RX
 * - The script outputs filtered flow data to the Output log.
 * 
 * Author: Ruurd
 * Assistance: Perplexity AI Assistant (for comments)
 * 
 *  165 - 206 ... globalThis.oState = { ... added: globalThis.SHOW_OBJ_ID = false/true;
 *  361 function replaceUsername(sUserName) {
 *  97 -sAllText += `'${oFlow.name}',\n`;
 */

//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
// Choose your option: only make one true, else defaults to bPush
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
let bPush = true;
let bScript = false;
let bCode = false;
let bOff = false;
let bOff_rx = false;
const bShowPushCard = true;
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!

// State management classes
class StateRecord {
  constructor(value, description) {
    this.value = value;
    this.description = description;
  }
}

class StateContainer {
  static SHOW_OBJ_ID = true;  // Toggle object vs string output format

  static CODE = new StateRecord("CODE", "Show CODE samples");
  static SCRIPT = new StateRecord("SCRIPT", "Show script names");
  static PUSH = new StateRecord("PUSH", "Show Push notification cards contents");
  static OFF = new StateRecord("OFF", "No filter, show all flow names");
  static OFF_RX = new StateRecord("OFF_RX", "Regex based filter");

  static allStates = [
    StateContainer.CODE,
    StateContainer.SCRIPT,
    StateContainer.PUSH,
    StateContainer.OFF,
    StateContainer.OFF_RX,
  ];
}

let sSettings = bPush ? StateContainer.PUSH :
  bScript ? StateContainer.SCRIPT :
    bCode ? StateContainer.CODE :
      bOff ? StateContainer.OFF :
        StateContainer.OFF_RX;

/*
Implicit Boolean-to-number coercion (true=1, false=0) makes counting trivial.
*/
let iTrue = (bPush + bScript + bCode + bOff + bOff_rx);
if (iTrue !== 1) {
  bPush = bScript = bCode = bOff = bOff_rx = false;
  bPush = true; // default
}

// OUTPUT
// console.log(`bPush: ${bPush}, bScript: ${bScript}, bCode: ${bCode}, bOff: ${bOff}, bOff_rx: ${bOff_rx}`);        

// Initialize settings and load all Homey data (flows + variables)
await applySettings(sSettings); // StateContainer.PUSH Change State here for testing

let iCounter = 0;           // Total processed flows
let iCounterFilter = 0;     // Filtered results count
let iCounterDisabled = 0;   // Disabled flows count
let sAllText = "";          // Accumulated output

// Main flow processing loop
for (const oFlow of aAllFlows) {
  if (!b_ENABLED_ONLY || oFlow.enabled) {  // Skip disabled if b_ENABLED_ONLY=true
    if (!bFilter) {  // Unfiltered modes (OFF, OFF_RX)
      if (oState.state.value === StateContainer.OFF_RX.value) {
        // OFF_RX: Sanitize filenames using regex replacements
        const sResult = (SHOW_OBJ_ID
          ? `{name: '${oFlow.name}', id: '${oFlow.id}'}`
          : `${oFlow.name.replace(sFilter, (match) => aReplacements[match] || "")}`
        );

        sAllText += `${sResult},\n`;

        iCounter++;
      } else if (oState.state.value === StateContainer.OFF.value) {
        // OFF: Simple flow name list

        sAllText += `'${oFlow.name}',\n`;

        iCounter++;
      }
    }

    // Filtered mode: process cards through filter() function
    if (bFilter && oFlow.cards) {
      const sResult = filter(oFlow.cards, oFlow);
      if (sResult !== null) {
        sAllText += `${sResult}\n`;
        iCounterFilter++;
      }
    }
  } else {
    iCounterDisabled++;  // Count skipped disabled flows
  }

}

// matches comma + any trailing whitespace at end
if (globalThis.SHOW_OBJ_ID)
  sAllText = sAllText.replace(/,\s*$/, '');

// Generate summary counters
const sCounters =
  iCounterFilter === 0
    ? `(Enabled:${iCounter}, Disabled:${iCounterDisabled})`
    : `(Filtered:${iCounterFilter})`;

// Comprehensive logging output with settings summary
log(
  `## State: ${JSON.stringify(oState.state)}\n` +
  `## Filter ${b_ENABLED_ONLY ? "Enabled" : "All"}: ${bFilter
    ? typeof sFilter === "string"
      ? `"${sFilter}"`
      : sFilter.toString()
    : StateContainer.OFF.value
  }\n` +
  `## allFlows: ${aAllFlows.length} ${sCounters}\n` +
  `## Enabled Flows Only: ${b_ENABLED_ONLY}\n` +
  `## Show as Object: ${SHOW_OBJ_ID} (Show as Object with ID)\n\n` +
  (SHOW_OBJ_ID ? "const aFlows = [\n" : "") +
  sAllText +
  (SHOW_OBJ_ID ? "\n]\n" : "")
);

return iCounter;  // Return total processed count


/**
 * Initializes global settings and state based on selected mode (SCRIPT/CODE/PUSH/OFF).
 * Prepares filtering, variables, and flows for card analysis.
 * 
 * @param {Object} sStateValue - State selector with .value property (e.g. StateContainer.SCRIPT)
 * @returns {void}
 */
async function applySettings(sStateValue) {
  // Set global constants for code preview length and filename sanitization
  globalThis.i_CODE_LEN = 20;
  globalThis.b_ENABLED_ONLY = true;
  globalThis.aReplacements = {
    "\\": "_",  // Windows invalid filename chars _ underscores
    "/": "_",
    ":": "_",
    "*": "_",
    "?": "_",
    '"': "_",
    "<": "_",
    ">": "_",
    "|": "__",
  };

  // Reset filtering flags and SHOW_OBJ_ID from state container
  globalThis.bScriptCard = false;
  globalThis.bFilter = false;
  globalThis.sFilter = "";
  globalThis.SHOW_OBJ_ID = StateContainer.SHOW_OBJ_ID;

  // State machine with evaluate() method that sets mode-specific filters
  globalThis.oState = {
    state: StateContainer.OFF_RX,  // Default initial state
    evaluate() {
      if (sStateValue.value === StateContainer.SCRIPT.value) {
        // SCRIPT mode: filter HomeyScript "run" cards
        globalThis.SHOW_OBJ_ID = false;
        globalThis.bScriptCard = true;
        globalThis.bFilter = true;
        globalThis.sFilter = "homey:app:com.athom.homeyscript:run";
        return StateContainer.SCRIPT;
      } else if (sStateValue.value === StateContainer.CODE.value) {
        // CODE mode: filter HomeyScript "runCode" cards
        globalThis.SHOW_OBJ_ID = false;
        globalThis.bScriptCard = false;
        globalThis.bFilter = true;
        globalThis.sFilter = "homey:app:com.athom.homeyscript:runCode";
        return StateContainer.CODE;
      } else if (sStateValue.value === StateContainer.PUSH.value) {
        // PUSH mode: filter mobile notifications
        globalThis.SHOW_OBJ_ID = false;
        globalThis.bScriptCard = false;
        globalThis.bFilter = true;
        globalThis.sFilter = "homey:manager:mobile";
        return StateContainer.PUSH;
      } else if (sStateValue.value === StateContainer.OFF.value) {
        // OFF mode: disable filtering
        globalThis.SHOW_OBJ_ID = true;
        globalThis.bFilter = false;
        globalThis.sFilter = "";
        return StateContainer.OFF;
      } else if (sStateValue.value === StateContainer.OFF_RX.value) {
        // OFF_RX mode: regex for invalid filename chars only
        globalThis.SHOW_OBJ_ID = true;
        globalThis.bFilter = false;
        globalThis.sFilter = new RegExp("[\\\\/:*?\"<>|]", "g");
        return StateContainer.OFF_RX;
      } else {
        console.log("Unknown state");
        return StateContainer.OFF_RX;  // Missing return - should default
      }
    },
  };

  // Load all Homey data: flows, advanced flows, variables
  globalThis.aStandardFlows = Object.values(await Homey.flow.getFlows());
  globalThis.aAdvancedFlows = Object.values(await Homey.flow.getAdvancedFlows());
  globalThis.aAllFlows = await getAllFlowsRaw();  // Deduplicated combined flows
  globalThis.oAllVariables = await Homey.logic.getVariables();
  // log(Array.isArray(oAllVariables)); 
  // Flow separator for output formatting
  globalThis.sSep = `\n-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!\n`;

  // Apply evaluated state (incomplete line: globalThis.sInitial ?)
  globalThis.oState.state = globalThis.oState.evaluate();
}

/**
 * Filters cards from flows based on state (SCRIPT/CODE/PUSH) and generates summary strings.
 * 
 * @param {Object} oCards - Cards object with card data keyed by ID
 * @param {Object} [oFlow={}] - Flow metadata (uses .name for output)
 * @returns {string|null} Formatted summary string or null if no matching cards
 */
async function getAllFlowsRaw() {
  // Combine standard and advanced flows, deduplicate by ID using Map
  const map = new Map();
  aStandardFlows.forEach((flow) => map.set(flow.id, flow));
  aAdvancedFlows.forEach((flow) => map.set(flow.id, flow));
  return Array.from(map.values()); // Return unique flows array
}

function filter(oCards, oFlow = {}) {
  // Filter cards based on current state and search filter
  const aFilteredEntries = Object.entries(oCards).filter(([sKey, oCard]) => {
    if (oState.state.value === StateContainer.SCRIPT.value) {
      // SCRIPT mode: match ID filter + require script name
      return oCard.id && oCard.id.includes(sFilter) && oCard.args?.script?.name;
    } else if (oState.state.value === StateContainer.CODE.value) {
      // CODE mode: match ID filter only
      return oCard.id && oCard.id.includes(sFilter);
    } else if (oState.state.value === StateContainer.PUSH.value) {
      // PUSH mode: only allow mobile push notification cards
      const allowlist = ["homey:manager:mobile", "homey:manager:mobile:push_text"];
      const id = oCard.id || "";
      if (bShowPushCard)
        log(JSON.stringify(oCard));
      return allowlist.some((prefix) => id.startsWith(prefix));
    }
    return false;
  });

  // Rebuild filtered object preserving original keys
  const oFilteredCards = aFilteredEntries.reduce((obj, [sKey, oCard]) => {
    obj[sKey] = oCard;
    return obj;
  }, {});

  const iFilteredCount = aFilteredEntries.length;
  let sCardNames = "", sCode = "", sPushContent = "";

  // Generate state-specific summaries
  if (oState.state.value === StateContainer.SCRIPT.value) {
    // List script names comma-separated
    sCardNames = aFilteredEntries
      .map(([_, oCard]) => oCard.args.script.name)
      .join(", ")
      .replace(/, $/, "");
  } else if (oState.state.value === StateContainer.CODE.value) {
    // Extract code snippets from HomeyScript cards (first i_CODE_LEN chars)
    let sCodeSamples = "";
    let iCounter = 0;
    for (const oCard2 of Object.values(oFilteredCards)) {
      if (oCard2.id?.includes("homey:app:com.athom.homeyscript:runCode") || oCard2.args?.code) {
        iCounter++;
        sCodeSamples += `\n#${iCounter} ${oCard2.args.code.substring(0, i_CODE_LEN).replace(/[\r\n]+|\s{2,}/g, ' ')}`;
      }
    }
    sCode = sCodeSamples !== "" ? sCodeSamples : 'Code not found';
  } else if (oState.state.value === StateContainer.PUSH.value) {
    // Process mobile push notifications: extract text, replace [[tokens]] with variable values
    let iCounter = 0, sRawText = "", sUser = '', sID = '';

    for (const oCard2 of Object.values(oFilteredCards)) {
      iCounter++;
      if (oCard2.id?.includes("homey:manager:mobile:push_text") || oCard2.uri?.includes("homey:manager:mobile")) {
        sRawText = oCard2.args?.text || "";
        sUser = replaceUsername(oCard2.args?.user.name || '');
        sID = oCard2.args?.user.id || "";

        if (sRawText) {
          // Replace Homey [[tokens]] with actual variable values using tryVariableValue()
          let sProcessedText = sRawText.replace(/\[\[([^\[\]]+?)\]\]/g, (match, sTokenContent) => {
            // Extract variable name after last '::' or '|' separator
            let iLastColon = sTokenContent.lastIndexOf('::');
            let iLastPipe = sTokenContent.lastIndexOf('|');
            let iSeparatorPos = Math.max(iLastColon, iLastPipe);

            if (iSeparatorPos > -1) {
              const sVarName = sTokenContent.substring(iSeparatorPos + 1);
              const uVarValue = tryVariableValue(sVarName); // Get real variable value
              return `[${uVarValue}]`;
            }
            return 'unknown'; // Fallback for unparseable tokens
          });

          // Clean up processed text: normalize whitespace, remove newlines
          let sCleanText = sProcessedText
            .replace(/[\r\n]+/g, ' ')
            .replace(/- /g, '')
            .replace(/\s{2,}/g, ' ')
            .trim();

          sPushContent += `#${iCounter} ${sCleanText} ${sUser}\n`;
        }
      }
    }
  }

  // Return formatted result or null
  if (iFilteredCount > 0) {
    if (oState.state.value === StateContainer.PUSH.value)
      return `${sSep}${oFlow.name}${sSep}${sPushContent.trim()}`;
    else if (oState.state.value === StateContainer.SCRIPT.value)
      return `${sSep}${oFlow.name}${sSep}${iFilteredCount} ->> ${sCardNames}`;
    return `${sSep}${oFlow.name}${sSep}${sCode.trim()}${sSep}`;
  }
  return null;
}


/**
 * Converts username to short @mention format (first 2 chars uppercase).
 * Handles empty/missing/single-char usernames gracefully:
 * - 2+ chars: first 2 uppercase - "@JO"  
 * - 1 char: pad with "?" - "@J?"
 * - Empty/null/undefined: "??"
 * 
 * @param {string} sUserName - Full username (e.g. "john.doe") or empty/null
 * @returns {string} Always 4-char @mention: "@XY"
 * 
 * Examples:
 *   replaceUsername("john.doe")    - "@JO"
 *   replaceUsername("alice")       - "@AL" 
 *   replaceUsername("j")           - "@J?"
 *   replaceUsername("")            - "@??"
 */
function replaceUsername(sUserName) {
  if (!sUserName) return '@??';  // null/undefined/empty - "@??"

  const sClean = sUserName.toString().substring(0, 2).toUpperCase();
  const sPadded = sClean.length === 1 ? sClean + '?' : sClean;

  return `@${sPadded}`;
}

//
/**
 * Attempts to get the value of a HomeyScript variable by its ID.
 * 
 * @param {string} sInput - Input string that might be a variable ID.
 * @returns {*} The variable's value if sInput is a valid ID and found,
 *            otherwise returns the original sInput.
 */
function tryVariableValue(sInput) {
  // Check if input is a string and matches UUID format for variable IDs
  if (typeof sInput !== 'string' ||
    !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sInput)) {
    return sInput;  // If not a valid ID, return input unchanged
  }

  try {
    // Search the global variable store for a matching variable by ID
    const oVar = Object.values(globalThis.oAllVariables).find(o => o.id === sInput);
    if (!oVar) {
      console.warn(`Variable with ID "${sInput}" does not exist.`);
      return sInput;  // Variable not found, return input
    }
    return oVar.value; // Return the variable's value if found
  } catch (oError) {
    console.error(`Error getting variable with ID "${sInput}":`, oError);
    return sInput;  // On error, return the input unchanged or handle otherwise
  }
}