Skip to content

Instantly share code, notes, and snippets.

@rvanbaalen
Last active April 20, 2025 18:22
Show Gist options
  • Save rvanbaalen/96796aee97f05133b3ad51671597e770 to your computer and use it in GitHub Desktop.
Save rvanbaalen/96796aee97f05133b3ad51671597e770 to your computer and use it in GitHub Desktop.
Automatic Actions framework for Claude AI. Copy/paste into the Developer Tools of Claude desktop app.

Claude Auto-Actions Framework

A lightweight, extensible framework for automating interactions within the Claude AI interface.

Overview

This framework allows you to automate repetitive actions in Claude's UI by defining custom "actions" that execute when specific conditions are met. It uses a mutation observer to monitor DOM changes and triggers your defined actions automatically.

Features

  • Registry System: Centralized management of multiple automation actions
  • Standardized Action Pattern: Consistent check/execute implementation across actions
  • Global Cooldown: Prevents action flooding with configurable timeouts
  • Error Handling: Robust error management for individual actions

Installation

  1. Open Claude Desktop
  2. Go to Help -> Enable Developer Mode
  3. Navigate to Developer Tools window named "Developer Tools - https://claude.ai"
  4. Go to "Console" tab
  5. Type "allow pasting" and hit Enter
  6. Paste the setup script (1-setup.js)
  7. Paste any action implementations (e.g., 2-action-auto-confirm.js)

Simplified copy/paste

This is a unified copy/paste version of the scripts below.

(()=>{class BaseAction{constructor(e){if(!e)throw new Error("Action must have a name.");this.name=e}check(){console.warn(`Action "${this.name}" is missing check() implementation.`);return!1}execute(e){console.warn(`Action "${this.name}" is missing execute() implementation.`)}}let t=0,e=2e3;window.autoActionsRegistry=window.autoActionsRegistry||[],window.myMutationObserver&&window.myMutationObserver.disconnect(),console.log("Setting up new Mutation Observer...");let o=new MutationObserver(o=>{let n=Date.now();if(n-t<e)return console.log("🕒 Global cooldown active, skipping mutation check."),void 0;for(let i of window.autoActionsRegistry)try{let o=i.check();if(o){console.log(`✅ [${i.name}] Conditions met. Preparing to execute.`),i.execute(o),t=n,console.log(`⏱️ [${i.name}] Action executed. Cooldown started.`);break}}catch(e){console.error(`Error during action check/execute for "${i.name}":`,e)}});o.observe(document.body,{childList:!0,subtree:!0}),window.myMutationObserver=o,console.log("✅ Observer started. Watching for changes..."),console.log("Registered actions:",window.autoActionsRegistry.map(e=>e.name));class n extends BaseAction{constructor(){super("AutoConfirmTool")}check(){console.log(`[${this.name}] Checking conditions...`);let e=document.querySelector('[role="dialog"]');if(!e)return null;let t=e.querySelector("button div");if(!t)return null;let o=t.textContent;if(!o||!o.includes("Run ")||!o.includes(" from"))return null;let n=o.match(/Run (\S+) from/),r=n?n[1]:"Unknown Tool";console.log(`[${this.name}] Found potential tool request dialog for: ${r}`);let a=Array.from(e.querySelectorAll("button")).find(e=>e.textContent.toLowerCase().includes("allow for this chat"));return a?(console.log(`[${this.name}] Found 'Allow' button.`),{button:a,toolName:r}):null}execute(e){if(!e||!e.button)return void console.error(`[${this.name}] Execute called without valid data.`);console.log(`🚀 [${this.name}] Auto-approving tool: ${e.toolName}`),e.button.click()}}window.autoActionsRegistry.some(e=>"AutoConfirmTool"===e.name)||(window.autoActionsRegistry.push(new n),console.log("🤖 Added AutoConfirmToolAction to registry."))})();

Included Actions

Auto-Confirm Tool

Automatically clicks "Allow for this chat" when Claude requests permission to run tools - eliminating repetitive approvals during development and testing.

Creating Custom Actions

To extend the framework, create a new action class that implements:

  • check() - Determines if conditions are met to execute the action
  • execute() - Performs the actual DOM interaction

Then register it with the framework:

window.autoActionsRegistry.push(new YourCustomAction());

Credits

Created by me, @rvanbaalen

Inspired by @RafalWilinski's original auto-approve script

/**
* 🏗️ HOW TO INSTRUCTIONS
* 1. Open Claude Desktop
* 2. Go to Help -> Enable Developer Mode
* 3. Navigate Developer Tools window named "Developer Tools - https://claude.ai"
* 4. Go to "Console" tab
* 5. Type "allow pasting" and hit Enter
* 6. Paste this snippet and hit Enter
*
* Created by @rvanbaalen
* Inpired by @RafalWilinski / original version of auto-approve script:
* https://gist.github.com/RafalWilinski/3416a497f94ee2a0c589a8d930304950
*/
// --- Configuration & Registry ---
// Global cooldown tracking
let lastActionTime = 0;
const GLOBAL_COOLDOWN_MS = 2000; // 2 seconds cooldown
// Initialize the registry on the window object if it doesn't exist
window.autoActionsRegistry = window.autoActionsRegistry || [];
// --- Action Class Definitions ---
// (Optional) Base class for structure - useful for type hinting or shared logic later
class BaseAction {
constructor(name) {
if (!name) throw new Error("Action must have a name.");
this.name = name;
}
// Checks if conditions are met. Returns data needed for execute() or falsy if not met.
check() {
console.warn(`Action "${this.name}" is missing check() implementation.`);
return false;
}
// Performs the action using data from check().
execute(data) {
console.warn(`Action "${this.name}" is missing execute() implementation.`);
}
}
if (window.myMutationObserver) {
console.log("Disconnecting previous observer...");
window.myMutationObserver.disconnect();
}
console.log("Setting up new Mutation Observer...");
const observer = new MutationObserver((mutations) => {
const now = Date.now();
// Check global cooldown
if (now - lastActionTime < GLOBAL_COOLDOWN_MS) {
console.log("🕒 Global cooldown active, skipping mutation check.");
return;
}
// Iterate through registered actions
for (const actionInstance of window.autoActionsRegistry) {
try {
// Check if the action's conditions are met
const actionData = actionInstance.check();
if (actionData) {
console.log(`✅ [${actionInstance.name}] Conditions met. Preparing to execute.`);
// Conditions met, execute the action
actionInstance.execute(actionData);
// Set global cooldown
lastActionTime = now;
console.log(`⏱️ [${actionInstance.name}] Action executed. Cooldown started.`);
// IMPORTANT: Stop checking other actions for this mutation batch
// Prevents multiple actions firing for the same change and respects cooldown
break;
} else {
// console.log(`- [${actionInstance.name}] Conditions not met.`); // Can be noisy
}
} catch (error) {
console.error(`Error during action check/execute for "${actionInstance.name}":`, error);
// Optionally continue to the next action or break, depending on desired robustness
// continue;
}
}
});
// Start observing the document body for subtree and child list changes
observer.observe(document.body, {
childList: true,
subtree: true,
});
// Store the observer instance on window to manage it later (e.g., disconnect)
window.myMutationObserver = observer;
console.log("✅ Observer started. Watching for changes...");
console.log("Registered actions:", window.autoActionsRegistry.map(a => a.name));
// ⚠️ Make sure you've pasted the setup script before pasting this script!
class AutoConfirmToolAction extends BaseAction {
constructor() {
super("AutoConfirmTool");
}
check() {
console.log(`[${this.name}] Checking conditions...`);
const dialog = document.querySelector('[role="dialog"]');
if (!dialog) return null; // Condition not met
// Try to identify the specific dialog by looking for the tool request text
const buttonWithDiv = dialog.querySelector("button div");
if (!buttonWithDiv) return null; // Not the right dialog structure
const toolText = buttonWithDiv.textContent;
if (!toolText || !toolText.includes("Run ") || !toolText.includes(" from")) {
// console.log(`[${this.name}] Tool text pattern not found.`);
return null; // Not the tool request dialog
}
const toolNameMatch = toolText.match(/Run (\S+) from/);
const toolName = toolNameMatch ? toolNameMatch[1] : "Unknown Tool";
console.log(`[${this.name}] Found potential tool request dialog for: ${toolName}`);
// Find the specific confirmation button
const allowButton = Array.from(dialog.querySelectorAll("button")).find(
(button) => button.textContent.toLowerCase().includes("allow for this chat")
);
if (allowButton) {
console.log(`[${this.name}] Found 'Allow' button.`);
// Return data needed for execution: the button and the tool name for logging
return { button: allowButton, toolName: toolName };
} else {
// console.log(`[${this.name}] 'Allow' button not found.`);
}
return null; // Condition not met
}
execute(data) {
if (!data || !data.button) {
console.error(`[${this.name}] Execute called without valid data.`);
return;
}
console.log(`🚀 [${this.name}] Auto-approving tool: ${data.toolName}`);
data.button.click();
}
}
// Add instances of your actions to the registry
// Ensure you only add them once, or manage additions carefully
if (!window.autoActionsRegistry.some(action => action.name === "AutoConfirmTool")) {
window.autoActionsRegistry.push(new AutoConfirmToolAction());
console.log("🤖 Added AutoConfirmToolAction to registry.");
}
// ⚠️ DONT USE THIS ONE. ITS EXPERIMENTAL AND NOT WORKING YET.
class AutoContinue extends BaseAction {
constructor() {
super("AutoContinue");
}
check() {
const prompt = Array.from(document.querySelectorAll("p"))
.find(p => p.textContent.includes("You can write Continue to keep the chat going"));
if (prompt) {
const editableDiv = document.querySelector(
'div[contenteditable="true"][translate="no"][enterkeyhint="enter"][tabindex="0"].ProseMirror'
);
const targetP = editableDiv?.querySelector('p[data-placeholder="Reply to Claude..."]');
if (editableDiv && targetP) {
return { editableDiv, targetP };
}
}
return false;
}
execute({ editableDiv, targetP }) {
targetP.click();
const eventOptions = { bubbles: true };
// Focus and simulate input
editableDiv.focus();
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(editableDiv);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
// Simulate typing "Continue"
editableDiv.textContent = "Continue";
editableDiv.dispatchEvent(new Event("input", eventOptions));
editableDiv.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", ...eventOptions }));
editableDiv.dispatchEvent(new KeyboardEvent("keypress", { key: "Enter", ...eventOptions }));
editableDiv.dispatchEvent(new KeyboardEvent("keyup", { key: "Enter", ...eventOptions }));
console.log("✏️ Typed 'Continue' and submitted.");
}
}
// Add instances of your actions to the registry
// Ensure you only add them once, or manage additions carefully
if (!window.autoActionsRegistry.some(action => action.name === "AutoContinue")) {
window.autoActionsRegistry.push(new AutoContinue());
console.log("🤖 Added AutoContinue to registry.");
}
@rvanbaalen
Copy link
Author

Start with copy/pasting step 1, and then add each tool (e.g. AutoConfirm, or create your own) by copy/pasting that one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment