safari-mcp 2.7.11 → 2.7.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,6 +13,7 @@
13
13
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
14
14
  [![macOS](https://img.shields.io/badge/macOS-only-blue)](https://www.apple.com/macos/)
15
15
  [![GitHub stars](https://img.shields.io/github/stars/achiya-automation/safari-mcp?style=social)](https://github.com/achiya-automation/safari-mcp/stargazers)
16
+ [![Featured in CLI-Anything](https://img.shields.io/badge/Featured%20in-CLI--Anything-blueviolet)](https://github.com/HKUDS/CLI-Anything/pull/212)
16
17
 
17
18
  <a href="vscode:mcp/install?%7B%22safari-mcp%22%3A%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22safari-mcp%22%5D%7D%7D"><img src="https://img.shields.io/badge/VS_Code-Install_Server-0078d4?logo=visual-studio-code&logoColor=white" alt="Install in VS Code"></a>
18
19
  <a href="https://insiders.vscode.dev/redirect?url=vscode-insiders:mcp/install?%7B%22safari-mcp%22%3A%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22safari-mcp%22%5D%7D%7D"><img src="https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?logo=visual-studio-code&logoColor=white" alt="Install in VS Code Insiders"></a>
package/index.js CHANGED
@@ -1022,6 +1022,20 @@ server.tool(
1022
1022
  }
1023
1023
  );
1024
1024
 
1025
+ server.tool(
1026
+ "safari_native_type",
1027
+ "Insert text into ANY editor via OS-level clipboard paste (CGEvent Cmd+V targeted to Safari window). Unlike safari_fill which manipulates DOM directly (breaking React/ProseMirror state), this goes through the real paste pipeline — ProseMirror/Slate/Draft.js process the paste event natively and update their internal model. After native_type, pressing Enter (via safari_native_keyboard) will actually submit the form because the framework state matches the DOM. Saves and restores the user's clipboard. No focus stealing. Use for Discord, Slack, and any editor where safari_fill works visually but the content isn't 'really there' when you try to submit.",
1028
+ {
1029
+ value: z.string().describe("Text to insert via clipboard paste"),
1030
+ selector: z.string().optional().describe("CSS selector of the editor element to focus first"),
1031
+ ref: z.string().optional().describe("Ref ID from safari_snapshot to focus first"),
1032
+ },
1033
+ async (args) => {
1034
+ const result = await safari.nativeType(args);
1035
+ return { content: [{ type: "text", text: typeof result === 'string' ? result : JSON.stringify(result) }] };
1036
+ }
1037
+ );
1038
+
1025
1039
  // ========== FORM INPUT ==========
1026
1040
 
1027
1041
  server.tool(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safari-mcp",
3
- "version": "2.7.11",
3
+ "version": "2.7.12",
4
4
  "mcpName": "io.github.achiya-automation/safari-mcp",
5
5
  "description": "Safari browser automation for AI agents — native macOS, zero Chrome overhead. 80 tools via AppleScript + JavaScript.",
6
6
  "type": "module",
package/safari.js CHANGED
@@ -1419,6 +1419,24 @@ const macKeyCodeMap = {
1419
1419
  // to the Safari window WITHOUT activating Safari or moving the mouse.
1420
1420
  // This produces isTrusted:true events that bypass React trust checks (Discord ProseMirror,
1421
1421
  // Slack virtualized editors, etc.) without any focus stealing. Requires Safari window ID.
1422
+ // Native type via clipboard paste (CGEvent Cmd+V targeted to Safari window).
1423
+ // Inserts text into ANY editor (ProseMirror, Slate, Draft.js, regular inputs,
1424
+ // contenteditable, cross-origin iframes) by going through the real paste pipeline.
1425
+ // Unlike safari_fill's synthetic paste, this updates the framework's internal
1426
+ // model state, so subsequent operations (like pressing Enter to submit in Discord)
1427
+ // see the text as "really there".
1428
+ export async function nativeType({ value, selector, ref }) {
1429
+ await ensureHelpers();
1430
+ if (!value) throw new Error("nativeType requires 'value'");
1431
+ // Focus the target element if selector/ref provided
1432
+ if (ref || selector) {
1433
+ const sel = ref ? refSelector(ref) : selector.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1434
+ await runJS(`(function(){ var el=document.querySelector('${sel}'); if(el){el.focus();el.click();} return el?'focused':'not found'; })()`);
1435
+ await new Promise(r => setTimeout(r, 50));
1436
+ }
1437
+ return await _nativeTypeViaClipboard(value);
1438
+ }
1439
+
1422
1440
  export async function nativeKeyboard({ key, modifiers = [] }) {
1423
1441
  await ensureHelpers();
1424
1442
  if (!key) throw new Error("nativeKeyboard requires 'key'");
@@ -1561,13 +1579,16 @@ export async function pressKey({ key, modifiers = [] }) {
1561
1579
  return `Pressed: ${modifiers.length ? modifiers.join("+") + "+" : ""}${key}`;
1562
1580
  }
1563
1581
 
1564
- // Native typing via clipboard paste — for cross-origin iframes (Intercom, Zendesk, etc.)
1565
- // JS can't access content inside cross-origin iframes, so we use OS-level CGEvent:
1566
- // 1. Save current clipboard
1567
- // 2. Set clipboard to our text
1568
- // 3. Cmd+V via CGEvent keyboard (targeted to window — NO focus steal, NO activate)
1569
- // 4. Restore clipboard
1570
- // Requires nativeClick to have focused the input inside the iframe first.
1582
+ // ========== NATIVE TYPE VIA CLIPBOARD PASTE ==========
1583
+ // Uses OS-level clipboard + CGEvent Cmd+V to insert text. Produces a REAL paste event
1584
+ // that ProseMirror/Slate/Draft.js process through their native paste handlers, updating
1585
+ // internal model state (not just the DOM). Also works for cross-origin iframes.
1586
+ // NO focus steal — sends CGEvent Cmd+V targeted to Safari window ID.
1587
+ //
1588
+ // Why this matters: synthetic DOM manipulation (safari_fill's "synthetic paste") writes
1589
+ // to the DOM but doesn't update React/ProseMirror state. Discord's onSubmit reads from
1590
+ // state, not DOM, so Enter submits empty. This function fixes that by going through the
1591
+ // real paste pipeline.
1571
1592
  async function _nativeTypeViaClipboard(text) {
1572
1593
  await _acquireClipboardLock();
1573
1594
  try {