Skip to content

Instantly share code, notes, and snippets.

@pirate-barbosa
Forked from intellectronica/0.README.md
Created February 12, 2026 06:50
Show Gist options
  • Select an option

  • Save pirate-barbosa/21c7054f9a9209c93f5a41d6c2e967de to your computer and use it in GitHub Desktop.

Select an option

Save pirate-barbosa/21c7054f9a9209c93f5a41d6c2e967de to your computer and use it in GitHub Desktop.
⌘-Enter to Submit in AI Chats [MonkeyScript]

⌘-Enter to Submit in AI Chats

The cure for premature chat submission - never lose an incomplete prompt again!

Install this user script (using a monkey extension like TamperMonkey or similar) to patch the textbox in common AI chats (currently supports Claude, ChatGPT, Gemini, and Google AI mode) so that instead of using Enter to submit and Shift-Enter for new lines, they use ⌘-Enter to submit and Enter just adds a new line.


Happy Prompting!

🫶 Eleanor (@intellectronica)

// ==UserScript==
// @name Chat Cmd+Enter to Submit
// @namespace https://intellectronica.net/
// @version 1.0.0
// @description Makes Enter add a newline and Cmd+Enter (or Ctrl+Enter) submit in Claude, ChatGPT, Gemini, and Google AI Mode
// @author Eleanor Berger <eleanor@intellectronica.net>
// @license Public Domain
// @match https://claude.ai/*
// @match https://chat.openai.com/*
// @match https://chatgpt.com/*
// @match https://gemini.google.com/*
// @match https://www.google.com/search*
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
const INPUT_SELECTORS = {
claude: '.ProseMirror[contenteditable="true"], div[data-placeholder][contenteditable="true"]',
chatgpt: '#prompt-textarea, textarea[data-id="root"], div[contenteditable="true"][data-id]',
gemini: '.ql-editor[contenteditable="true"], div[contenteditable="true"][aria-label*="prompt"], rich-textarea [contenteditable="true"]',
googleai: 'textarea[aria-label="Ask anything"], textarea.ITIRGe'
};
const SUBMIT_SELECTORS = {
claude: 'button[data-testid="send-button"], button[aria-label*="Send message" i], button[aria-label="Send" i]',
chatgpt: 'button[data-testid="send-button"], button[aria-label*="Send"], form button[type="submit"]',
gemini: 'button[aria-label*="Send"], button.send-button, button[mattooltip*="Send"]',
googleai: 'button[aria-label*="Send" i], button[aria-label*="Submit" i], button[type="submit"]'
};
function getCurrentSite() {
const host = location.hostname;
if (host.includes('claude.ai')) return 'claude';
if (host.includes('openai.com') || host.includes('chatgpt.com')) return 'chatgpt';
if (host.includes('gemini.google.com')) return 'gemini';
if (host.includes('google.com') && (location.search.includes('udm=50') || document.querySelector('textarea[aria-label="Ask anything"]'))) {
return 'googleai';
}
return null;
}
function getInputElement(element, site) {
if (!element) return null;
const selector = INPUT_SELECTORS[site];
if (!selector) return null;
if (element.matches(selector)) return element;
return element.closest(selector);
}
function findSubmitButton(site, inputEl) {
const selector = SUBMIT_SELECTORS[site];
if (!selector) return null;
// Try direct selector match
const buttons = document.querySelectorAll(selector);
for (const btn of buttons) {
if (!btn.disabled && btn.offsetParent !== null) {
return btn;
}
}
// Fallback: walk up from input to find a suitable button
if ((site === 'claude' || site === 'googleai') && inputEl) {
let container = inputEl.parentElement;
for (let i = 0; i < 10 && container; i++) {
const candidates = Array.from(container.querySelectorAll('button')).filter(btn => {
if (btn.disabled || btn.offsetParent === null) return false;
const label = (btn.getAttribute('aria-label') || '').toLowerCase();
if (label.includes('menu') || label.includes('attachment') || label.includes('voice') || label.includes('microphone')) return false;
if (site === 'claude' && !btn.querySelector('svg')) return false;
return true;
});
if (candidates.length > 0) {
return candidates[candidates.length - 1];
}
container = container.parentElement;
}
}
return buttons[0] || null;
}
function triggerSubmit(site, inputEl) {
let submitBtn = findSubmitButton(site, inputEl);
if (submitBtn) {
if (submitBtn.tagName.toLowerCase() !== 'button') {
submitBtn = submitBtn.closest('button');
}
if (submitBtn) {
submitBtn.click();
return true;
}
}
// Fallback: dispatch Enter key event for sites that submit via Enter
if (inputEl) {
inputEl.dispatchEvent(new KeyboardEvent('keydown', {
key: 'Enter',
code: 'Enter',
keyCode: 13,
which: 13,
bubbles: true,
cancelable: true
}));
return true;
}
return false;
}
function handleKeyDown(event) {
const site = getCurrentSite();
if (!site) return;
const inputEl = getInputElement(event.target, site);
if (!inputEl) return;
if (event.key !== 'Enter') return;
const hasModifier = event.metaKey || event.ctrlKey;
const hasShift = event.shiftKey;
if (hasModifier && !hasShift) {
// Cmd+Enter or Ctrl+Enter: Submit
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
setTimeout(() => triggerSubmit(site, inputEl), 10);
} else if (!hasModifier && !hasShift) {
// Plain Enter: Transform into Shift+Enter to insert newline
Object.defineProperty(event, 'shiftKey', { get: () => true });
}
// Shift+Enter: Default behavior (newline)
}
document.addEventListener('keydown', handleKeyDown, true);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment