// ==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();
}
})();