Is there a functional way to read the "Tags" or use "Argument(s)"

i can not find a way to use the args array in HomyScript FlowCard. you can only transfer 1 argument.
i can not find a way to get the “Tag” thing to work. NO

let flowTokens = await Homey.flowtoken.getFlowTokens();
var data = “”;
for (const flowToken of Object.values(flowTokens)) {
data += flowToken.uri + ">: " + flowToken.title + " → " + flowToken.value + “\r\n”
}

throw new Error(data); //<- this is because log() doesn’t really work in a flow or i am to stupid to find the output of that.

does NOT list the tags in the flow.
and yes flowToken.value is always undefined. nice

i have not jet find a real way to get any information from the current flow. and no something like create a json object in the args field as a text string and parse it later is the dumbest workaround i have ever seen. this falls flat for user input that could contain " or any other stupid thing

i guess the devs themselves don’t know because their example just shows how to set a tag with
await tag(‘My String’, ‘abcdef’); but they never read it back. stupid.

and a absolute nogo is the realy sad documentation.
why is that “(async) getFlowTokens(): Promise.<Object.<string, HomeyAPIV2.ManagerFlowToken.FlowToken>>” even a thing? like give an example? like every body else?
this: (async) getVariables(): Promise.<Object.<string, HomeyAPIV2.ManagerLogic.Variable>> doesn’t give me an answere as well.

like the documentation is a list of functioncalls with the result type with no description - nothing
like some one asked “how bad should we create our docs” and all screamed YEEEESSSSSSSS

any solutions? anything or is it just not possible?

I don’t understand what you’re trying to achieve here. getFlowTokens gets all available tags (similar to when you click the tag icon next to f.e. a text field). The await tag() likely creates a tag to return to Homey so the output of the script can be used by the rest of the Flow.

I assume that you refer to Homeyscript flowcards. … Indeed there is the string argument (args[0]) from the argument part of a Homeyscript flowcard. (don’t forget in the argument parser below too put Boolean items between braces {{bBool}} to receive 0 or 1 from any Boolean variable. In the case you do this you can simplify the isBool function

You can enter a JSON structue and parse that, or you can use a function like:

const aArg = (args[0] || '').split(';').map((v, i) => {
  const sTrimmed = (v || '').trim();
  if (i === 2) return isBool(sTrimmed);  // Boolean
  else if (i >= 10) return sTrimmed;     // String
  else {
    const n = Number(sTrimmed);          // Number
    return isNaN(n) ? null : n;
  }
});

log(`parse arguments - aArg: ${aArg.map((val, i) => `"${i}=${val}" (type: ${typeof val})`).join(', ')}`);

function isBool(sValue) { // with early 0 & 1 quit
   if (DO_LOG) log(`isBool1 sValue:${sValue}`);

   if (typeof sValue === 'boolean') return sValue;

   const sNormalized = String(sValue).trim().toLowerCase();
   if (DO_LOG) log(`isBool2 sNormalized:${sNormalized}`);

   // Exact guards FIRST - block substring issues
   if (sNormalized === '1') return true;
   if (sNormalized === '0') return false;

   // Now safe arrays with numbers
   const aTruthy = ['true', 'yes', 'ja', 'on', '✓', '✔', '✅', '⨁', '●', '1', 1];
   const aFalsy = ['false', 'no', 'nee', 'off', 'uit', '✗', '✘', '❌', '0', 0];

   if (aFalsy.includes(sNormalized)) return false;
   if (aTruthy.includes(sNormalized)) return true;

   return null;  // Default safe
}

I hope this gives you an idea of ​​the possibilities.

To give you an idea how you can work with flows in script this might give you an idea how to do this

/**
 * Homey Flow Push, Script and Code Filter Utility
 * - Version 2026-02-17 
 * - 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
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
/*const bShowFlowNames = true;

let bPush = false;
let bScript = false;
let bCode = true;
let bOff = false;
let bOff_rx = false;
const bShowPushCard = false;
*/
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!

//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!
// SHOW ALL FLOWS (Name + ID)
const bShowFlowNames = true;
let bPush = false;     // Push notifications
let bScript = false;   // Script cards  
let bCode = false;     // Code cards
let bOff = true;       // ← ALLE FLOWS tonen
let bOff_rx = false;   // Regex filter

globalThis.b_ENABLED_ONLY = false;
//-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!


// 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`;
        sAllText += `'${oFlow.name} (${oFlow.id})',\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
sAllText = doSort(sAllText); // sorting year && (20 
sAllText =
  log(    
    `${bShowFlowNames ? `\n` : ``}## 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 = false;
  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 (bShowFlowNames)
      log(`${oFlow.name} (${oFlow.id})`);
    // log(oFlow.name);
    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
  }
}

function doSort(sAllTextIn) {
  // Split the string on newlines, filter out empty lines
  const aLines = sAllTextIn.split('\n').filter(sLine => sLine.trim() !== '');

  // Sort by year descending, then lexicographically
  const aSorted = aLines.sort((a, b) => {
    const sYearA = (a.match(/\((\d{4})-\d{2}-\d{2}\)/) || [, '0000'])[1];
    const sYearB = (b.match(/\((\d{4})-\d{2}-\d{2}\)/) || [, '0000'])[1];

    const iYearDiff = Number(sYearB) - Number(sYearA);
    if (iYearDiff !== 0) return iYearDiff;  // ← use iYearDiff here

    return a.localeCompare(b);
  });

  // Join back into a single string with \n
  return aSorted.join('\n');
}


why ? ai really?

won’t work. user input can always include like a “;” or “”" or …

as i have stated before → no it does not somehow. i think i am not completely incompitent…
who knows.

let’s see… get these fields or tags into this script. don’t say yeah create a json string - won’t work if Text contains ".

as i said homey.flowtoken.getFlowTokens does not have this info. regardless even i i deliberately put them in “Argument” this api does not find them.

Maybe not what you want, but you can assign the tags to a Homey variable and use them in a Homey script.

const text = await tag(‘whatsapp_text’);