Better AutomationAnywhere

Enhanced Automation Anywhere developer experience. Working at CR Version 37.0.0

Author
Jamir-boop
Daily installs
0
Total installs
23
Ratings
0 0 0
Version
0.5.0
Created
2023-10-20
Updated
2025-06-07
Size
40.1 KB
License
MIT
Applies to

// ==UserScript== // @name Better AutomationAnywhere // @namespace http://tampermonkey.net/ // @version 0.5.0 // @description Enhanced Automation Anywhere developer experience. Working at CR Version 37.0.0 // @author jamir-boop // @match ://.automationanywhere.digital/* // @icon https://cmpc-1dev.my.automationanywhere.digital/favicon.ico // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @license MIT // ==/UserScript==

(function () { "use strict";

// =========================
// Section: State & Constants
// =========================

/** @type {number} Tracks the active (highlighted) prediction in the command palette */
let activePredictionIndex = -1;

/** @type {Object[]} Stores current predictions' actions for keyboard navigation */
let currentPredictionActions = [];

let initialized = false;
let updateActiveButtonIntervalId = null;

// =========================
// Section: Utility Functions
// =========================

/**
 * Waits for a DOM element to appear, then resolves.
 * @param {string} selector - CSS selector for the element.
 * @param {number} timeout - Max wait time in ms.
 * @returns {Promise<Element|null>}
 */
function waitForElement(selector, timeout = 5000) {
    return new Promise((resolve) => {
        const el = document.querySelector(selector);
        if (el) return resolve(el);

        const observer = new MutationObserver(() => {
            const found = document.querySelector(selector);
            if (found) {
                observer.disconnect();
                resolve(found);
            }
        });

        observer.observe(document.body, { childList: true, subtree: true });

        setTimeout(() => {
            observer.disconnect();
            resolve(null);
        }, timeout);
    });
}

/**
 * Sleep for a given number of milliseconds.
 * @param {number} ms
 * @returns {Promise<void>}
 */
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

/**
 * Safely query a DOM element and log a warning if not found.
 * @param {string} selector
 * @param {string} [context]
 * @returns {Element|null}
 */
function safeQuery(selector, context = "") {
    const el = document.querySelector(selector);
    if (!el) {
        console.warn(`Element not found: ${selector}${context ? " (" + context + ")" : ""}`);
    }
    return el;
}

/**
 * Adds a click event listener to an element if it exists.
 * @param {Element|null} el
 * @param {Function} handler
 */
function safeAddClick(el, handler) {
    if (el) el.addEventListener("click", handler);
}

// =========================
// Section: Command Definitions
// =========================

/** @type {Object.<string, {action: Function, aliases: string[], description: string}>} */
const commandsWithAliases = {
    addVariable: {
        action: addVariable,
        aliases: ["adv", "addvar", "add variable"],
        description: "Shows dialog to create a new variable",
    },
    showVariables: {
        action: showVariables,
        aliases: ["v", "showvars", "list variables", "variables"],
        description: "Shows variables in sidebar",
    },
    deleteUnusedVariables: {
        action: deleteUnusedVariables,
        aliases: ["duv", "delete unused", "remove unused variables"],
        description: "Shows dialog to select and delete unused variables",
    },
    redirectToPrivateRepository: {
        action: redirectToPrivateRepository,
        aliases: ["p", "private", "private bots"],
        description: "Redirects to the private bots folder",
    },
    redirectToPublicRepository: {
        action: redirectToPublicRepository,
        aliases: ["pub", "public", "public bots"],
        description: "Redirects to the public bots folder",
    },
    redirectToActivityHistorical: {
        action: redirectToActivityHistorical,
        aliases: ["historical", "history", "activity historical"],
        description: "Redirects to the activities historical tab",
    },
    redirectToInProgress: {
        action: redirectToInProgress,
        aliases: ["inprogress", "progress", "in progress"],
        description: "Redirects to the in-progress activities tab",
    },
    redirectToAuditLog: {
        action: redirectToAuditLog,
        aliases: ["audit", "audit log"],
        description: "Redirects to the activities historical tab",
    },
    redirectToAdminUsers: {
        action: redirectToAdminUsers,
        aliases: ["users", "admin users", "manage users"],
        description: "Redirects to the admin users page",
    },
    redirectToAdminRoles: {
        action: redirectToAdminRoles,
        aliases: ["roles", "admin roles", "manage roles"],
        description: "Redirects to the admin roles page",
    },
    redirectToAdminDevices: {
        action: redirectToAdminDevices,
        aliases: ["devices", "admin devices", "manage devices"],
        description: "Redirects to the admin devices page",
    },
    redirectToHome: {
        action: redirectToHome,
        aliases: ["home", "dashboard", "overview"],
        description: "Redirects to the dashboard home overview",
    },
    showHelp: {
        action: showHelp,
        aliases: ["help", "man", "show help"],
        description: "Displays help information for available commands",
    },
    universalCopy: {
        action: universalCopyCommandPalette,
        aliases: ["universal copy", "copy universal", "rocket copy"],
        description: "Copy actions between control rooms.",
    },
    universalPaste: {
        action: universalPasteCommandPalette,
        aliases: ["universal paste", "paste universal", "rocket paste"],
        description: "Paste actions between control rooms.",
    },
    exportActionToClipboard: {
        action: exportActionToClipboard,
        aliases: ["export action", "copy action json", "export copied action", "share action"],
        description: "Export the currently copied action as JSON to your clipboard.",
    },
    importActionFromJson: {
        action: importActionFromJson,
        aliases: ["import action", "paste action json", "import shared action", "load action json"],
        description: "Import an action from JSON and paste it as if copied locally.",
    },
};

// =========================
// Section: Command Palette
// =========================

/**
 * Returns the command palette element.
 * @returns {HTMLElement|null}
 */
function getCommandPalette() {
    return document.getElementById("commandPalette");
}

/**
 * Returns the command input element.
 * @returns {HTMLInputElement|null}
 */
function getCommandInput() {
    return document.getElementById("commandInput");
}

/**
 * Returns the command predictions container.
 * @returns {HTMLElement|null}
 */
function getCommandPredictions() {
    return document.getElementById("commandPredictions");
}

/**
 * Toggles the command palette visibility.
 */
function togglePaletteVisibility() {
    const commandPalette = getCommandPalette();
    if (!commandPalette) return;
    const input = getCommandInput();
    if (commandPalette.classList.contains("command_palette--visible")) {
        commandPalette.classList.remove("command_palette--visible");
        commandPalette.classList.add("command_palette--hidden");
        if (input) {
            input.value = "";
            input.blur();
        }
        clearPredictions();
        activePredictionIndex = -1;
    } else {
        commandPalette.classList.remove("command_palette--hidden");
        commandPalette.classList.add("command_palette--visible");
        if (input) {
            input.focus();
        }
    }
}

/**
 * Clears command predictions.
 */
function clearPredictions() {
    const predictions = getCommandPredictions();
    if (predictions) predictions.innerHTML = "";
}

/**
 * Updates command predictions based on input.
 * @param {string} input
 */
function updatePredictions(input) {
    clearPredictions();
    if (!input) {
        activePredictionIndex = -1;
        return;
    }

    // Check for ":<number>" syntax to scroll to a line
    const jumpToLineMatch = input.match(/^:(\d+)$/);
    if (jumpToLineMatch) {
        const lineNumber = parseInt(jumpToLineMatch[1], 10);
        const predictionItem = document.createElement("div");
        predictionItem.classList.add("command_prediction-item");
        predictionItem.innerHTML = `<strong>Go to line ${lineNumber}</strong>`;
        safeAddClick(predictionItem, () => {
            scrollToLineNumber(lineNumber);
            clearPredictions();
            togglePaletteVisibility();
        });
        getCommandPredictions().appendChild(predictionItem);
        return;
    }

    Object.entries(commandsWithAliases).forEach(
        ([, { action, aliases, description }]) => {
            const match = aliases.find((alias) =>
                alias.startsWith(input.toLowerCase())
            );
            if (match) {
                const predictionItem = document.createElement("div");
                predictionItem.classList.add("command_prediction-item");
                predictionItem.innerHTML = `<strong>${match}</strong> - ${description}`;
                safeAddClick(predictionItem, () => {
                    const inputEl = getCommandInput();
                    if (inputEl) inputEl.value = match;
                    executeCommand(action);
                    clearPredictions();
                });
                getCommandPredictions().appendChild(predictionItem);
            }
        }
    );

    // Always select the first prediction if any
    const predictionsContainer = getCommandPredictions();
    const items = predictionsContainer ? predictionsContainer.getElementsByClassName("command_prediction-item") : [];
    if (items.length > 0) {
        activePredictionIndex = 0;
        updateActivePrediction(items);
    } else {
        activePredictionIndex = -1;
    }
}

/**
 * Sets up event listeners for the command input.
 */
function setupCommandInputEventListeners() {
    const commandInput = getCommandInput();
    if (!commandInput) return;

    commandInput.addEventListener("input", function () {
        updatePredictions(this.value);
    });

    commandInput.addEventListener("keydown", navigatePredictions);
}

/**
 * Executes a command action.
 * @param {Function} action
 */
function executeCommand(action) {
    if (action) {
        action();
    } else {
        showHelp();
    }
    togglePaletteVisibility();
}

/**
 * Handles keyboard navigation in the command palette.
 * @param {KeyboardEvent} e
 */
function navigatePredictions(e) {
    const commandPredictions = getCommandPredictions();
    if (!commandPredictions) return;
    const items = commandPredictions.getElementsByClassName("command_prediction-item");
    if (!items.length) {
        if (e.key === "Escape") {
            togglePaletteVisibility();
            e.preventDefault();
        }
        return;
    }

    if (items.length === 1 && e.key === "Enter") {
        items[0].click();
        e.preventDefault();
        return;
    }

    if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) {
        e.preventDefault();
        if (e.key === "ArrowDown") {
            activePredictionIndex = (activePredictionIndex + 1) % items.length;
            updateActivePrediction(items);
        } else if (e.key === "ArrowUp") {
            activePredictionIndex = activePredictionIndex <= 0 ? items.length - 1 : activePredictionIndex - 1;
            updateActivePrediction(items);
        } else if (e.key === "Enter" && activePredictionIndex >= 0) {
            items[activePredictionIndex].click();
        }
    } else if (e.key === "Escape") {
        togglePaletteVisibility();
        e.preventDefault();
    }
}

/**
 * Updates the active prediction item in the command palette.
 * @param {HTMLCollectionOf<Element>} items
 */
function updateActivePrediction(items) {
    Array.from(items).forEach((item, index) => {
        item.classList.toggle("active", index === activePredictionIndex);
    });
}

// =========================
// Section: Keyboard Shortcuts
// =========================

/**
 * Registers all keyboard shortcuts.
 */
function registerKeyboardShortcuts() {
    document.addEventListener("keydown", function (e) {
        if (e.altKey && e.key === "p") {
            e.preventDefault();
            insertCustomEditorPaletteButtons();
            togglePaletteVisibility();
        }
    });

    document.addEventListener("keydown", function (e) {
        if (e.code === "KeyA" && e.altKey) {
            showActions();
            e.preventDefault();
        }
    });

    document.addEventListener("keydown", function (e) {
        if (e.code === "KeyV" && e.altKey) {
            showVariables();
            e.preventDefault();
        }
    });

    document.addEventListener("keydown", function (e) {
        if (e.ctrlKey && e.code === "KeyD") {
            toggleToolbar();
            e.preventDefault();
        }
    });
}

// =========================
// Section: Toolbar & Sidebar
// =========================

/**
 * Toggles the toolbar open/closed.
 */
function toggleToolbar() {
    const btn = safeQuery(
        "div.editor-layout__resize:nth-child(2) > button:nth-child(2)",
        "toggleToolbar"
    );
    if (btn) btn.click();
}

/**
 * Checks if the palette sidebar is open or closed.
 * @returns {"opened"|"closed"}
 */
function getPaletteState() {
    const paletteElement = safeQuery(".editor-layout__palette", "getPaletteState");
    if (!paletteElement) return "closed";
    return paletteElement.offsetWidth <= 8 ? "closed" : "opened";
}

// =========================
// Section: Feature Functions
// =========================

/**
 * Shows the Actions section in the palette.
 */
function showActions() {
    if (getPaletteState() === "closed") {
        toggleToolbar();
    }
    try {
        safeQuery(
            "div.editor-palette__accordion:nth-child(2) > div:nth-child(1) > header:nth-child(1) > div:nth-child(1) > button:nth-child(1) > div:nth-child(1) > div:nth-child(2)",
            "showActions"
        )?.click();
    } catch (e) {
        console.warn("Failed to show Actions:", e);
    }
    try {
        safeQuery(
            '.editor-palette-search__cancel button[type="button"][tabindex="-1"]',
            "showActions"
        )?.click();
    } catch (e) {
        console.warn("Failed to cancel search:", e);
    }
}

/**
 * Shows the Variables section in the palette.
 */
async function showVariables() {
    if (getPaletteState() === "closed") {
        toggleToolbar();
        await sleep(1000);
    }
    for (let i = 0; i < 10; i++) {
        const el = document.querySelector(
            'span.clipped-text.clipped-text--no_wrap.editor-palette-section__header-title[title="Variables"]'
        );
        if (el) {
            el.click();
            return;
        }
        await sleep(300);
    }
}

/**
 * Shows the Triggers section in the palette.
 */
function showTriggers() {
    if (getPaletteState() === "closed") {
        toggleToolbar();
    }
    safeQuery(
        'span.clipped-text.clipped-text--no_wrap.editor-palette-section__header-title[title="Triggers"]',
        "showTriggers"
    )?.click();
}

/**
 * Adds a new variable via the UI.
 */
async function addVariable() {
    if (getPaletteState() === "closed") {
        toggleToolbar();
        await sleep(800);
    }
    const addButton = safeQuery('div.editor-palette__accordion header button', "addVariable");
    if (addButton) {
        addButton.click();
    } else {
        console.warn("Add Variable button not found");
        return;
    }
    await sleep(500);
    const createButton = safeQuery('button[name="create"]', "addVariable");
    if (createButton) {
        createButton.click();
    } else {
        console.warn("Create button not found");
        return;
    }
    await sleep(500);
    const confirmButton = safeQuery('div.action-bar--theme_default button:nth-child(2)', "addVariable");
    if (confirmButton) {
        confirmButton.click();
    } else {
        console.warn("Confirm button not found");
    }
}

/**
 * Deletes unused variables via the UI.
 */
async function deleteUnusedVariables() {
    await showVariables();
    await sleep(1000);
    const dropdownMenu = safeQuery("button.action-bar__item--is_menu:nth-child(5)", "deleteUnusedVariables");
    if (dropdownMenu) dropdownMenu.click();
    await sleep(1000);
    const duvButton = safeQuery(".dropdown-options.g-scroller button.rio-focus--inset_4px:nth-child(2)", "deleteUnusedVariables");
    if (duvButton) duvButton.click();
}

/**
 * Scrolls to a specific line number in the editor.
 * @param {number} lineNumber
 */
function scrollToLineNumber(lineNumber) {
    const lineElements = document.querySelectorAll('.taskbot-canvas-list-node > .taskbot-canvas-list-node__number');
    if (lineNumber < 1 || lineNumber > lineElements.length) {
        console.warn(`Line ${lineNumber} is out of range. Total lines: ${lineElements.length}`);
        return;
    }
    const targetElement = lineElements[lineNumber - 1];
    document.querySelectorAll('.line-highlighted').forEach(el => el.classList.remove('line-highlighted'));
    targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
    targetElement.classList.add('line-highlighted');
    setTimeout(() => {
        targetElement.classList.remove('line-highlighted');
    }, 2000);
}

/**
 * Shows the help modal.
 */
function showHelp() {
    const modalOverlay = document.createElement('div');
    const modal = document.createElement('div');
    const modalContent = document.createElement('div');
    const closeButton = document.createElement('button');
    const signature = document.createElement('div');

    modalOverlay.style.position = 'fixed';
    modalOverlay.style.top = '0';
    modalOverlay.style.left = '0';
    modalOverlay.style.width = '100vw';
    modalOverlay.style.height = '100vh';
    modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
    modalOverlay.style.display = 'flex';
    modalOverlay.style.justifyContent = 'center';
    modalOverlay.style.alignItems = 'center';
    modalOverlay.style.zIndex = '1000';
    modalOverlay.style.fontSize = '16px';

    modal.style.backgroundColor = 'white';
    modal.style.padding = '20px';
    modal.style.borderRadius = '8px';
    modal.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
    modal.style.maxWidth = '800px';
    modal.style.width = '80%';
    modal.style.position = 'relative';

    let helpContent = "<h3>List of Commands:</h3><ul>";

    for (let command in commandsWithAliases) {
        const { aliases, description } = commandsWithAliases[command];
        helpContent += `<li><b>${aliases.join(', ')}</b>: ${description}</li>`;
    }
    helpContent += `<li><b>:<i>line</i></b>: Scrolls to a specific line number (e.g. <code>:25</code>)</li>`;
    helpContent += "</ul>";

    helpContent += `
        <h4>Keyboard Shortcuts:</h4>
        <ul>
            <li><b>Alt + P</b>: Open the command palette</li>
            <li><b>Alt + V</b>: Show variables</li>
            <li><b>Alt + A</b>: Show actions</li>
        </ul>

        <h4>Clipboard Slots:</h4>
        <ul>
            <li>Use the context menu (Tampermonkey menu) to:
                <ul>
                    <li><code>Copy to Slot 1</code>, <code>Slot 2</code>, <code>Slot 3</code></li>
                    <li><code>Paste from Slot 1</code>, <code>Slot 2</code>, <code>Slot 3</code></li>
                </ul>
            </li>
            <li>You can also use the rocket icons in the top action bar to quickly copy/paste</li>
        </ul>
    `;

    modalContent.innerHTML = helpContent;

    closeButton.textContent = 'Close';
    closeButton.style.marginTop = '10px';
    closeButton.style.padding = '8px 16px';
    closeButton.style.border = 'none';
    closeButton.style.backgroundColor = 'var(--color_background_interactive)';
    closeButton.style.color = 'white';
    closeButton.style.cursor = 'pointer';
    closeButton.style.borderRadius = '4px';

    signature.innerHTML = `<a href="https://github.com/Jamir-boop/automationanywhere-improvements.git" target="_blank" style="text-decoration: none; color: #888; font-size: 12px;">made by jamir-boop</a>`;
    signature.style.position = 'absolute';
    signature.style.bottom = '8px';
    signature.style.right = '12px';

    modal.appendChild(modalContent);
    modal.appendChild(closeButton);
    modal.appendChild(signature);
    modalOverlay.appendChild(modal);
    document.body.appendChild(modalOverlay);

    function closeModal() {
        document.body.removeChild(modalOverlay);
    }

    modalOverlay.addEventListener('click', (e) => {
        if (e.target === modalOverlay) {
            closeModal();
        }
    });
    document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') {
            closeModal();
        }
    });
    closeButton.addEventListener('click', closeModal);
}

// =========================
// Section: Custom Palette Buttons
// =========================

/**
 * Updates the active button in the custom palette toolbar.
 */
function updateActiveButton() {
    const activeSection = document.querySelector(
        ".editor-palette-section__header--is_active .clipped-text__string--for_presentation"
    )?.innerText;
    const buttons = document.querySelectorAll(".customActionVariableButton");
    buttons.forEach((button) => {
        if (button.textContent === activeSection) {
            button.classList.add("buttonToolbarActive");
        } else {
            button.classList.remove("buttonToolbarActive");
        }
    });
}

/**
 * Inserts custom palette buttons for Variables, Actions, and Triggers.
 */
function insertCustomEditorPaletteButtons() {
    if (document.getElementById("customActionVariableButtons")) {
        return;
    }
    const containerDiv = document.createElement("div");
    containerDiv.id = "customActionVariableButtons";

    const variableButton = document.createElement("button");
    variableButton.className = "customActionVariableButton";
    variableButton.textContent = "Variables";
    variableButton.onclick = function () {
        showVariables();
        updateActiveButton();
    };

    const actionButton = document.createElement("button");
    actionButton.className = "customActionVariableButton";
    actionButton.textContent = "Actions";
    actionButton.onclick = function () {
        showActions();
        updateActiveButton();
    };

    const triggerButton = document.createElement("button");
    triggerButton.className = "customActionVariableButton";
    triggerButton.textContent = "Triggers";
    triggerButton.onclick = function () {
        showTriggers();
        updateActiveButton();
    };

    containerDiv.appendChild(variableButton);
    containerDiv.appendChild(actionButton);
    containerDiv.appendChild(triggerButton);

    const palette = safeQuery(".editor-layout__palette", "insertCustomEditorPaletteButtons");
    if (palette) {
        palette.appendChild(containerDiv);
    }

    if (!document.getElementById("customActionVariableButtons-style")) {
        const style = document.createElement("style");
        style.id = "customActionVariableButtons-style";
        style.textContent = `
            #customActionVariableButtons {
                display: flex;
                width: 100%;
                height: 38px !important;
                background: white;
            }
            #customActionVariableButtons button {
                all: unset;
                font-size: .85rem;
                font-weight: 300;
                cursor: pointer;
                margin: 4px;
                border-radius: 5px;
                border: 1px solid transparent;
                background-color: transparent;
                color: #3c5e83;
                flex-grow: 1;
                text-align: center;
                transition: background-color 0.3s;
            }
            #customActionVariableButtons button:hover {
                background-color: #dae9f3;
            }
            .buttonToolbarActive {
                border: 1px solid #3c5e83 !important;
                text-shadow: 0.5px 0 0 #3c5e83 , -0.01px 0 0 #3c5e83 !important;
            }
            .editor-palette.g-box-sizing_border-box {
                margin-top: 38px;
            }
        `;
        document.head.appendChild(style);
    }
}

// =========================
// Section: Universal Copy/Paste
// =========================

/**
 * Generates a random emoji string for UID.
 * @returns {string}
 */
function generateEmojiString() {
    const emojis = [
        "😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "😊", "😇", "🙂", "🙃", "😉", "😌", "😍", "🥰", "😘", "😗", "😙", "😚",
        "😋", "😛", "😝", "😜", "🤪", "🤨", "🧐", "🤓", "😎", "🤩", "🥳", "😏", "😒", "😞", "😔", "😟", "😕", "🙁", "😣",
        "😖", "😫", "😩", "🥺", "😢", "😭", "😤", "😠", "😡", "🤬", "🤯", "😳", "🥵", "🥶", "😱", "😨", "😰", "😥", "😓",
        "🤗", "🤔", "🤭", "🤫", "🤥", "😶", "😐", "😑", "😬", "🙄", "😯", "😦", "😧", "😮", "😲", "🥱", "😴", "🤤", "😪",
        "😵", "🤐", "🥴", "🤢", "🤮", "🤧", "😷", "🤒", "🤕", "🤑", "🤠", "😈", "👿", "👹", "👺", "🤡", "💩", "👻", "💀",
        "☠️", "👽", "👾", "🤖", "🎃", "😺", "😸", "😹", "😻", "😼", "😽", "🙀", "😿", "😾"
    ];
    let uniqueString = "";
    for (let i = 0; i < 10; i++) {
        uniqueString += emojis[Math.floor(Math.random() * emojis.length)];
    }
    return uniqueString;
}

/**
 * Recursively clears sensitive fields in an object.
 * @param {object} obj
 */
function clearSensitiveFields(obj) {
    if (!obj || typeof obj !== "object") return;
    for (const key in obj) {
        if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
        if (key === "blob" || key === "thumbnailMetadataPath" || key === "screenshotMetadataPath") {
            obj[key] = "";
        } else if (typeof obj[key] === "object" && obj[key] !== null) {
            clearSensitiveFields(obj[key]);
        }
    }
}

/**
 * Cleans Automation Anywhere node JSON by removing sensitive metadata paths and blobs.
 * @param {string} jsonString - The input JSON string.
 * @returns {string} - The cleaned, minified JSON string.
 */
function cleanAutomationAnywhereJson(jsonString) {
    let data;
    try {
        data = JSON.parse(jsonString);
    } catch (e) {
        console.error("Invalid JSON input", e);
        return jsonString;
    }

    if (!Array.isArray(data.nodes)) return JSON.stringify(data);

    for (const node of data.nodes) {
        if (!Array.isArray(node.attributes)) continue;
        for (const attr of node.attributes) {
            if (attr.value && typeof attr.value === "object") {
                clearSensitiveFields(attr.value);
            }
        }
    }
    return JSON.stringify(data);
}

/**
 * Copies data to the specified clipboard slot.
 * @param {number} slot
 */
function copyToSlot(slot) {
    const copyButton = safeQuery(".aa-icon-action-clipboard-copy--shared", "copyToSlot");
    if (copyButton) {
        copyButton.click();
        const globalClipboardJSON = localStorage.getItem('globalClipboard');
        try {
            const clipboardData = JSON.parse(globalClipboardJSON);
            clipboardData.uid = "🔥🔥🔥";
            GM_setValue(`universalClipboardSlot${slot}`, JSON.stringify(clipboardData));
        } catch (error) {
            console.error("Failed to copy data to slot:", error);
        }
    }
}

/**
 * Pastes data from the specified clipboard slot.
 * @param {number} slot
 */
function pasteFromSlot(slot) {
    const clipboardData = GM_getValue(`universalClipboardSlot${slot}`);
    if (!clipboardData) {
        return;
    }
    let emojiUid = generateEmojiString();
    let modifiedData = clipboardData.replace(/🔥🔥🔥/g, emojiUid);

    // Clean the JSON before pasting
    let cleanedData = cleanAutomationAnywhereJson(modifiedData);

    localStorage.setItem('globalClipboard', cleanedData);
    localStorage.setItem('globalClipboardUid', `"${emojiUid}"`);
    const pasteButton = safeQuery(".aa-icon-action-clipboard-paste--shared", "pasteFromSlot");
    if (pasteButton) {
        setTimeout(() => {
            pasteButton.click();
        }, 500);
    }
}

function universalCopyCommandPalette() {
    const btn = document.querySelector(".universalCopy");
    if (btn) {
        btn.click();
    } else {
        universalCopy(); // fallback
    }
}

function universalPasteCommandPalette() {
    const btn = document.querySelector(".universalPaste");
    if (btn) {
        btn.click();
    } else {
        universalPaste(); // fallback
    }
}

/**
 * Inserts universal copy/paste buttons into the action bar.
 * @param {number} attempt
 */
function insertUniversalCopyPasteButtons(attempt = 1) {
    setTimeout(() => {
        const actionBar = document.querySelector('.action-bar--theme_info');
        if (actionBar && !actionBar.querySelector('.universalCopy')) {
            const separator = document.createElement('div');
            separator.className = 'action-bar__separator';
            actionBar.appendChild(separator);

            // Universal Copy button
            const copyButton = document.createElement('button');
            copyButton.className = 'universalCopy rio-focus rio-focus--inset_0 rio-focus--border-radius_4px rio-focus--has_element-focus-visible rio-bare-button g-reset-element rio-bare-button--is_interactive rio-bare-button--rio_interactive-softest rio-bare-button--is_parent rio-bare-button--is_clickable rio-bare-button--size_14px rio-bare-button--is_square rio-bare-button--square_26x26 action-bar__item action-bar__item--is_action taskbot-editor__toolbar__action';
            copyButton.innerHTML = `<span class="icon fa fa-rocket icon--block"></span>`;
            copyButton.title = 'Universal Copy';
            copyButton.onclick = universalCopy;
            actionBar.appendChild(copyButton);

            // Universal Paste button
            const pasteButton = document.createElement('button');
            pasteButton.className = 'universalPaste rio-focus rio-focus--inset_0 rio-focus--border-radius_4px rio-focus--has_element-focus-visible rio-bare-button g-reset-element rio-bare-button--is_interactive rio-bare-button--rio_interactive-softest rio-bare-button--is_parent rio-bare-button--is_clickable rio-bare-button--size_14px rio-bare-button--is_square rio-bare-button--square_26x26 action-bar__item action-bar__item--is_action taskbot-editor__toolbar__action';
            pasteButton.innerHTML = `<span class="icon fa fa-rocket icon--block" style="transform: rotate(180deg);"></span>`;
            pasteButton.title = 'Universal Paste';
            pasteButton.onclick = universalPaste;
            actionBar.appendChild(pasteButton);
        } else if (attempt < 3) {
            insertUniversalCopyPasteButtons(attempt + 1);
        }
    }, 1000 * attempt);
}

/**
 * Universal copy action.
 */
function universalCopy() {
    const copyBtn = safeQuery(".aa-icon-action-clipboard-copy--shared", "universalCopy");
    if (copyBtn) copyBtn.click();
    const globalClipboardJSON = localStorage.getItem('globalClipboard');
    let globalClipboard = {};
    try {
        globalClipboard = JSON.parse(globalClipboardJSON);
    } catch (e) {
        console.error("Error parsing JSON:", e);
        return;
    }
    globalClipboard.uid = "🔥🔥🔥";
    GM_setValue('universalClipboard', JSON.stringify(globalClipboard));
}

/**
 * Universal paste action.
 */
function universalPaste() {
    const copyBtn = safeQuery(".aa-icon-action-clipboard-copy--shared", "universalPaste");
    if (copyBtn) copyBtn.click();
    let universalClipboard = GM_getValue('universalClipboard');
    if (universalClipboard) {
        let emojiUid = generateEmojiString();
        universalClipboard = universalClipboard.replace(/'/g, '"');
        universalClipboard = universalClipboard.replace(/🔥🔥🔥/g, emojiUid);

        // Clean the JSON before pasting
        let cleanedData = cleanAutomationAnywhereJson(universalClipboard);

        localStorage.setItem("globalClipboard", cleanedData);
        localStorage.setItem("globalClipboardUid", `"${emojiUid}"`);
    }
    setTimeout(() => {
        const pasteBtn = safeQuery(".aa-icon-action-clipboard-paste--shared", "universalPaste");
        if (pasteBtn) pasteBtn.click();
    }, 1000);
}

// =========================
// Section: Redirect Utility
// =========================

/**
 * Redirects to a given path within the Automation Anywhere domain.
 * @param {string} targetPath
 */
function redirectToPath(targetPath) {
    const currentUrl = window.location.href;
    const pattern = /^(https:\/\/[^\/]*\.automationanywhere\.digital)/;
    const match = currentUrl.match(pattern);
    if (match) {
        const baseUrl = match[1];
        const newUrl = baseUrl + targetPath;
        window.location.href = newUrl;
    } else {
        console.error("Pattern didn't match. The URL might not be in the expected format.");
    }
}

function redirectToPublicRepository() {
    redirectToPath('/#/bots/repository/public/');
}
function redirectToPrivateRepository() {
    redirectToPath('/#/bots/repository/private/');
}
function redirectToActivityHistorical() {
    redirectToPath('/#/activity/historical/');
}
function redirectToInProgress() {
    redirectToPath('/#/activity/inprogress/');
}
function redirectToAuditLog() {
    redirectToPath('/#/audit');
}
function redirectToAdminUsers() {
    redirectToPath('/#/admin/users/');
}
function redirectToAdminRoles() {
    redirectToPath('/#/admin/roles/');
}
function redirectToAdminDevices() {
    redirectToPath('/#/devices/');
}
function redirectToHome() {
    redirectToPath('/#/dashboard/home/overview');
}

// =========================
// Section: Command Palette Insertion
// =========================

/**
 * Inserts the command palette into the DOM.
 * @param {number} retryCount
 */
function insertCommandPalette(retryCount = 0) {
    if (document.querySelector("#commandPalette")) {
        return;
    }
    const containerDiv = document.createElement("div");
    containerDiv.id = "commandPalette";
    containerDiv.className = "command_palette--hidden";
    containerDiv.innerHTML = `
        <input type="text" id="commandInput" placeholder="Enter command...">
        <div id="commandPredictions" class="command_predictions"></div>
    `;
    document.body.appendChild(containerDiv);

    if (!document.getElementById("commandPalette-style")) {
        const style = document.createElement("style");
        style.id = "commandPalette-style";
        style.type = "text/css";
        style.appendChild(document.createTextNode(`
            #commandPalette * { font-size: 1.15rem; font-family: Museo Sans,sans-serif; }
            #commandPalette { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                background-color: white; border-radius: 10px 10px 0 0; display: flex; flex-direction: column;
                align-items: center; min-width: 30vw; max-width: 80vw; width: 600px; z-index: 99999;
                box-shadow: 0px 0px 0px 5000px #00000054; }
            #commandInput, #commandInput:focus-visible, #commandInput:active {
                unset: all; padding: 10px; width: 93%; margin-bottom: 10px; border: 2px solid transparent; border-radius: 5px;
            }
            #commandPalette:focus, #commandPalette:active { border-color: orange; }
            #commandPredictions { position: absolute; top: 100%; left: 0; width: 100%; background-color: white;
                box-shadow: 0 4px 8px rgba(0,0,0,0.1); border-radius: 0 0 10px 10px; max-height: 200px; overflow-y: auto; z-index: 100000; }
            .command_prediction-item.active { background-color: #f0f0f0; }
            .command_prediction-item strong { font-weight: bold; }
            .command_prediction-item { padding: 8px; cursor: pointer; border-bottom: 1px solid #eee; }
            .command_prediction-item:hover, .command_prediction-item.active { background-color: #f0f0f0; }
            @keyframes fadeIn { from { opacity: 0; transform: translate(-50%, -50%) scale(0.85); }
                to { opacity: 1; transform: translate(-50%, -50%) scale(1); } }
            @keyframes fadeOut { from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
                to { opacity: 0; transform: translate(-50%, -50%) scale(0.95); } }
            .command_palette--visible { display: block; animation: fadeIn 0.25s ease-out forwards; }
            .command_palette--hidden { animation: fadeOut 0.25s ease-out forwards; display: none; pointer-events: none; opacity: 0; z-index: -1; }
        `));
        document.head.appendChild(style);
    }

    setupCommandInputEventListeners();

    if (!document.querySelector("#commandPalette")) {
        if (retryCount < 5) {
            setTimeout(() => insertCommandPalette(retryCount + 1), 3000);
        } else {
            console.error("Failed to insert command palette after 5 retries.");
        }
    }
}

/**
 * Exports the currently copied action as JSON to the user's clipboard,
 * with uid set to 🔥🔥🔥 (for universal sharing).
 */
async function exportActionToClipboard() {
    try {
        // Use universalCopy to set GM storage with 🔥🔥🔥 as uid
        universalCopy();
        // Wait a short moment to ensure GM_setValue is complete
        await sleep(200);
        const universalClipboard = GM_getValue('universalClipboard');
        if (!universalClipboard) {
            return;
        }
        await navigator.clipboard.writeText(universalClipboard);
    } catch (e) {
        console.warn("Failed to export action to clipboard:", e);
    }
}

function importActionFromJson() {
    // Modal setup
    const overlay = document.createElement('div');
    overlay.style.position = 'fixed';
    overlay.style.top = '0';
    overlay.style.left = '0';
    overlay.style.width = '100vw';
    overlay.style.height = '100vh';
    overlay.style.background = 'rgba(0,0,0,0.5)';
    overlay.style.zIndex = '100000';
    overlay.style.display = 'flex';
    overlay.style.justifyContent = 'center';
    overlay.style.alignItems = 'center';

    const modal = document.createElement('div');
    modal.style.background = 'white';
    modal.style.padding = '24px';
    modal.style.borderRadius = '8px';
    modal.style.maxWidth = '600px';
    modal.style.width = '90%';
    modal.style.boxShadow = '0 4px 16px rgba(0,0,0,0.15)';
    modal.style.display = 'flex';
    modal.style.flexDirection = 'column';
    modal.style.alignItems = 'stretch';

    const label = document.createElement('label');
    label.textContent = "Paste Automation Anywhere Action JSON:";
    label.style.marginBottom = '8px';

    const textarea = document.createElement('textarea');
    textarea.style.width = '100%';
    textarea.style.height = '180px';
    textarea.style.marginBottom = '12px';
    textarea.style.fontFamily = 'monospace';
    textarea.style.fontSize = '1rem';

    const buttonRow = document.createElement('div');
    buttonRow.style.display = 'flex';
    buttonRow.style.justifyContent = 'flex-end';
    buttonRow.style.gap = '8px';

    const importBtn = document.createElement('button');
    importBtn.textContent = "Import & Paste";
    importBtn.style.padding = '8px 16px';
    importBtn.style.background = 'var(--color_background_interactive, #3c5e83)';
    importBtn.style.color = 'white';
    importBtn.style.border = 'none';
    importBtn.style.borderRadius = '4px';
    importBtn.style.cursor = 'pointer';

    const cancelBtn = document.createElement('button');
    cancelBtn.textContent = "Cancel";
    cancelBtn.style.padding = '8px 16px';
    cancelBtn.style.background = '#ccc';
    cancelBtn.style.color = '#222';
    cancelBtn.style.border = 'none';
    cancelBtn.style.borderRadius = '4px';
    cancelBtn.style.cursor = 'pointer';

    buttonRow.appendChild(cancelBtn);
    buttonRow.appendChild(importBtn);

    modal.appendChild(label);
    modal.appendChild(textarea);
    modal.appendChild(buttonRow);
    overlay.appendChild(modal);
    document.body.appendChild(overlay);
    textarea.focus();

    function closeModal() {
        document.body.removeChild(overlay);
    }

    cancelBtn.onclick = closeModal;
    overlay.onclick = (e) => { if (e.target === overlay) closeModal(); };
    document.addEventListener('keydown', function escListener(e) {
        if (e.key === 'Escape') {
            closeModal();
            document.removeEventListener('keydown', escListener);
        }
    });

    importBtn.onclick = async function () {
        let input = textarea.value.trim();
        if (!input) {
            return;
        }
        try {
            // Validate JSON
            JSON.parse(input);
        } catch (e) {
            return;
        }
        // Set GM storage for universal paste
        GM_setValue('universalClipboard', input);
        closeModal();
        // Wait a short moment to ensure GM_setValue is complete
        await sleep(200);
        universalPaste();
    };
}

// =========================
// Section: Initialization
// =========================

/**
 * Runs all startup functions, using MutationObserver for palette and action bar.
 */
function initialize() {
    if (!document.querySelector("#commandPalette")) {
        insertCommandPalette();
    }
    if (!document.getElementById("customActionVariableButtons")) {
        insertCustomEditorPaletteButtons();
    }
    insertUniversalCopyPasteButtons();
    removeInlineWidth();

    // Only set up listeners and intervals once
    if (!initialized) {
        registerKeyboardShortcuts();
        updateActiveButtonIntervalId = setInterval(updateActiveButton, 1000);
        initialized = true;

        // Observe for SPA navigation changes
        let lastHref = document.location.href;
        setInterval(function () {
            const currentHref = document.location.href;
            if (lastHref !== currentHref) {
                lastHref = currentHref;
                insertCommandPalette();
                insertCustomEditorPaletteButtons();
                insertUniversalCopyPasteButtons();
                removeInlineWidth();
            }
        }, 5000);
    }
}

/**
 * Removes inline width from the navigation panel.
 */
function removeInlineWidth() {
    const nav = document.querySelector('.main-layout__navigation');
    const pathfinderCollapsed = document.querySelector('.pathfinder--is_collapsed');
    if (pathfinderCollapsed) {
        if (nav?.style?.width) {
            nav.style.removeProperty('width');
        }
        return;
    }
    const collapseButton = document.querySelector('button[aria-label="Collapse"]');
    if (collapseButton) {
        collapseButton.click();
        setTimeout(() => {
            if (nav?.style?.width) {
                nav.style.removeProperty('width');
            }
        }, 500);
    } else {
        console.warn('Collapse button not found');
    }
}

// =========================
// Section: Tampermonkey Menu Commands
// =========================

// Register menu commands for selecting copy/paste slots
GM_registerMenuCommand("Copy to Slot 1", () => copyToSlot(1));
GM_registerMenuCommand("Copy to Slot 2", () => copyToSlot(2));
GM_registerMenuCommand("Copy to Slot 3", () => copyToSlot(3));
GM_registerMenuCommand("Paste from Slot 1", () => pasteFromSlot(1));
GM_registerMenuCommand("Paste from Slot 2", () => pasteFromSlot(2));
GM_registerMenuCommand("Paste from Slot 3", () => pasteFromSlot(3));

// =========================
// Section: DOM Ready
// =========================

function callInitializeRepeatedly(times = 3, interval = 5000) {
    let count = 0;
    const intervalId = setInterval(() => {
        initialize();
        count++;
        if (count >= times) {
            clearInterval(intervalId);
        }
    }, interval);
}

if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", () => callInitializeRepeatedly());
} else {
    callInitializeRepeatedly();
}

})();