Skip to content

Instantly share code, notes, and snippets.

@intellectronica
Last active November 27, 2025 16:36
Show Gist options
  • Select an option

  • Save intellectronica/e9302c17e2b01db9ca9ab17f0bcb8f16 to your computer and use it in GitHub Desktop.

Select an option

Save intellectronica/e9302c17e2b01db9ca9ab17f0bcb8f16 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 <[email protected]>
// @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