safari-mcp 2.1.1 → 2.1.2
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 +2 -0
- package/package.json +1 -1
- package/safari.js +37 -4
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
[](https://nodejs.org/)
|
|
10
10
|
[](https://www.apple.com/macos/)
|
|
11
11
|
[](https://modelcontextprotocol.io)
|
|
12
|
+
[](https://www.npmjs.com/package/safari-mcp)
|
|
13
|
+
[](https://github.com/achiya-automation/safari-mcp/stargazers)
|
|
12
14
|
[](https://glama.ai/mcp/servers/achiya-automation/safari-mcp)
|
|
13
15
|
|
|
14
16
|
**80 tools** · **Zero dependencies** · **~5ms per command** · **60% less CPU than Chrome**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safari-mcp",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
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
|
@@ -1146,18 +1146,51 @@ export async function fill({ selector, value, ref }) {
|
|
|
1146
1146
|
const sel = selector.replace(/'/g, "\\'");
|
|
1147
1147
|
// Proper escaping order: backslashes first, then quotes
|
|
1148
1148
|
const val = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
|
|
1149
|
-
|
|
1149
|
+
const result = await runJS(
|
|
1150
1150
|
`(function(){try{var el=document.querySelector('${sel}');if(!el){var q=function(r){var a=r.querySelectorAll('*');for(var i=0;i<a.length;i++){if(a[i].shadowRoot){el=a[i].shadowRoot.querySelector('${sel}');if(el)return el;el=q(a[i].shadowRoot);if(el)return el;}}return null;};el=q(document);}if(!el)return 'Element not found: ${sel}';el.focus();if(el.isContentEditable||el.getAttribute('contenteditable')==='true'){` +
|
|
1151
1151
|
// ProseMirror detection
|
|
1152
1152
|
`var pm=el.closest('.ProseMirror')||el.querySelector('.ProseMirror');if(pm){try{var v=null;if(pm.pmViewDesc&&pm.pmViewDesc.view)v=pm.pmViewDesc.view;else if(pm.cmView&&pm.cmView.view)v=pm.cmView.view;else{var keys=Object.keys(pm);for(var ki=0;ki<keys.length;ki++){var o=pm[keys[ki]];if(o&&o.state&&o.dispatch){v=o;break;}}}if(v&&v.state&&v.dispatch){var doc=v.state.doc;var hasContent=doc.textContent&&doc.textContent.trim().length>0;if(hasContent){var endPos=doc.content.size>1?doc.content.size-1:doc.content.size;v.dispatch(v.state.tr.insertText(' ${val}',endPos));v.focus();return 'Filled CE (ProseMirror append)';}else{var tr=v.state.tr;tr.replaceWith(0,doc.content.size,v.state.schema.text('${val}'));v.dispatch(tr);v.focus();return 'Filled CE (ProseMirror replace)';}}}catch(e){}}` +
|
|
1153
|
-
// Closure/Medium detection —
|
|
1154
|
-
`var isClosure=Object.keys(el).some(function(k){return k.startsWith('closure_uid_');})||location.hostname.includes('medium.com');if(isClosure){
|
|
1155
|
-
`for(var ci=0;ci<'${val}'.length;ci++){var target=document.activeElement||el;var ch='${val}'[ci];if(ch==='\\n'){target.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));document.execCommand('insertParagraph');target.dispatchEvent(new KeyboardEvent('keyup',{key:'Enter',keyCode:13,bubbles:true}));continue;}var kc=ch.charCodeAt(0);target.dispatchEvent(new KeyboardEvent('keydown',{key:ch,keyCode:kc,bubbles:true}));document.execCommand('insertText',false,ch);target.dispatchEvent(new InputEvent('input',{data:ch,inputType:'insertText',bubbles:true}));target.dispatchEvent(new KeyboardEvent('keyup',{key:ch,keyCode:kc,bubbles:true}));}return 'Filled CE (Closure char-by-char)';}` +
|
|
1153
|
+
// Closure/Medium detection — signal for native paste
|
|
1154
|
+
`var isClosure=Object.keys(el).some(function(k){return k.startsWith('closure_uid_');})||location.hostname.includes('medium.com');if(isClosure){return '__CLOSURE_NATIVE_PASTE__';}` +
|
|
1156
1155
|
// Default contenteditable: selectAll+delete+insert (safer than textContent='')
|
|
1157
1156
|
`document.execCommand('selectAll');document.execCommand('delete');document.execCommand('insertText',false,'${val}');el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('blur',{bubbles:true}));return 'Filled contenteditable';}` +
|
|
1158
1157
|
// Standard input/textarea with _valueTracker
|
|
1159
1158
|
`var t=el._valueTracker;if(t)t.setValue('');var proto=el.tagName==='TEXTAREA'?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;var s=Object.getOwnPropertyDescriptor(proto,'value');if(s&&s.set){s.set.call(el,'${val}');}else{el.value='${val}';}el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));el.dispatchEvent(new Event('blur',{bubbles:true}));el.dispatchEvent(new Event('focusout',{bubbles:true}));el.focus();return 'Filled: '+el.value.substring(0,50);}catch(e){return 'ERR: '+e.message;}})()`
|
|
1160
1159
|
);
|
|
1160
|
+
|
|
1161
|
+
// Closure/Medium editor: insert line-by-line via execCommand in small batches
|
|
1162
|
+
// No focus stealing, no System Events, no clipboard manipulation.
|
|
1163
|
+
// Medium's Closure editor accepts execCommand('insertText') for individual lines
|
|
1164
|
+
// and execCommand('insertParagraph') for line breaks — the key is doing it
|
|
1165
|
+
// within a single do-JavaScript call so the mutation observer stays in sync.
|
|
1166
|
+
if (result === "__CLOSURE_NATIVE_PASTE__") {
|
|
1167
|
+
const rawValue = value;
|
|
1168
|
+
// Split into lines, escape each for JS string
|
|
1169
|
+
const lines = rawValue.split('\n');
|
|
1170
|
+
// Build a JS script that inserts line by line
|
|
1171
|
+
const lineInserts = lines.map((line, i) => {
|
|
1172
|
+
const escaped = line.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
1173
|
+
if (i === 0) {
|
|
1174
|
+
return escaped.length > 0 ? `document.execCommand('insertText',false,'${escaped}');` : '';
|
|
1175
|
+
}
|
|
1176
|
+
return `document.execCommand('insertParagraph');` +
|
|
1177
|
+
(escaped.length > 0 ? `document.execCommand('insertText',false,'${escaped}');` : '');
|
|
1178
|
+
}).join('');
|
|
1179
|
+
|
|
1180
|
+
const fillResult = await runJS(
|
|
1181
|
+
`(function(){` +
|
|
1182
|
+
`var el=document.querySelector('${selector.replace(/'/g, "\\'")}');` +
|
|
1183
|
+
`if(!el)return 'Element not found';` +
|
|
1184
|
+
`el.focus();el.click();` +
|
|
1185
|
+
`var sel=window.getSelection();if(sel.rangeCount){var r=document.createRange();r.selectNodeContents(el);r.collapse(false);sel.removeAllRanges();sel.addRange(r);}` +
|
|
1186
|
+
lineInserts +
|
|
1187
|
+
`return 'Filled CE (Closure line-by-line)';` +
|
|
1188
|
+
`})()`
|
|
1189
|
+
);
|
|
1190
|
+
return fillResult;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
return result;
|
|
1161
1194
|
}
|
|
1162
1195
|
|
|
1163
1196
|
export async function clearField({ selector }) {
|