UNPKG

apple-mcp

Version:

Apple MCP tools for contacts, notes, messages, and mail integration

796 lines (709 loc) 189 kB
#!/usr/bin/env node import{createRequire as q6}from"node:module";var z6=Object.create;var{getPrototypeOf:X6,defineProperty:e1,getOwnPropertyNames:B6}=Object;var G6=Object.prototype.hasOwnProperty;var w6=($,Q,J)=>{J=$!=null?z6(X6($)):{};let Y=Q||!$||!$.__esModule?e1(J,"default",{value:$,enumerable:!0}):J;for(let W of B6($))if(!G6.call(Y,W))e1(Y,W,{get:()=>$[W],enumerable:!0});return Y};var $8=($,Q)=>()=>(Q||$((Q={exports:{}}).exports,Q),Q.exports);var V0=($,Q)=>{for(var J in Q)e1($,J,{get:Q[J],enumerable:!0,configurable:!0,set:(Y)=>Q[J]=()=>Y})};var H0=($,Q)=>()=>($&&(Q=$($=0)),Q);var k1=q6(import.meta.url);import j$ from"node:process";import{promisify as K$}from"node:util";import{execFile as V$,execFileSync as a5}from"node:child_process";async function A($,{humanReadableOutput:Q=!0}={}){if(j$.platform!=="darwin")throw new Error("macOS only");let J=Q?[]:["-ss"],{stdout:Y}=await U$("osascript",["-e",$,J]);return Y.trim()}var U$;var w0=H0(()=>{U$=K$(V$)});var O8={};V0(O8,{default:()=>C$});async function A$(){try{return await A(` tell application "Contacts" return name end tell`),!0}catch($){return console.error(`Cannot access Contacts app: ${$ instanceof Error?$.message:String($)}`),!1}}async function g1(){try{if(await A$())return{hasAccess:!0,message:"Contacts access is already granted."};return{hasAccess:!1,message:`Contacts access is required but not granted. Please: 1. Open System Settings > Privacy & Security > Automation 2. Find your terminal/app in the list and enable 'Contacts' 3. Alternatively, open System Settings > Privacy & Security > Contacts 4. Add your terminal/app to the allowed applications 5. Restart your terminal and try again`}}catch($){return{hasAccess:!1,message:`Error checking Contacts access: ${$ instanceof Error?$.message:String($)}`}}}async function Z1(){try{let $=await g1();if(!$.hasAccess)throw new Error($.message);let Q=` tell application "Contacts" set contactList to {} set contactCount to 0 -- Get a limited number of people to avoid performance issues set allPeople to people repeat with i from 1 to (count of allPeople) if contactCount >= ${F8.MAX_CONTACTS} then exit repeat try set currentPerson to item i of allPeople set personName to name of currentPerson set personPhones to {} try set phonesList to phones of currentPerson repeat with phoneItem in phonesList try set phoneValue to value of phoneItem if phoneValue is not "" then set personPhones to personPhones & {phoneValue} end if on error -- Skip problematic phone entries end try end repeat on error -- Skip if no phones or phones can't be accessed end try -- Only add contact if they have phones if (count of personPhones) > 0 then set contactInfo to {name:personName, phones:personPhones} set contactList to contactList & {contactInfo} set contactCount to contactCount + 1 end if on error -- Skip problematic contacts end try end repeat return contactList end tell`,J=await A(Q),Y=Array.isArray(J)?J:J?[J]:[],W={};for(let H of Y)if(H&&H.name&&H.phones)W[H.name]=Array.isArray(H.phones)?H.phones:[H.phones];return W}catch($){return console.error(`Error getting all contacts: ${$ instanceof Error?$.message:String($)}`),{}}}async function f$($){try{let Q=await g1();if(!Q.hasAccess)throw new Error(Q.message);if(!$||$.trim()==="")return[];let J=$.toLowerCase().trim(),Y=` tell application "Contacts" set matchedPhones to {} set searchText to "${J}" -- Get a limited number of people to search through set allPeople to people set foundExact to false set partialMatches to {} repeat with i from 1 to (count of allPeople) if i > ${F8.MAX_CONTACTS} then exit repeat try set currentPerson to item i of allPeople set personName to name of currentPerson set lowerPersonName to (do shell script "echo " & quoted form of personName & " | tr '[:upper:]' '[:lower:]'") -- Check for exact match first (highest priority) if lowerPersonName is searchText then try set phonesList to phones of currentPerson repeat with phoneItem in phonesList try set phoneValue to value of phoneItem if phoneValue is not "" then set matchedPhones to matchedPhones & {phoneValue} set foundExact to true end if on error -- Skip problematic phone entries end try end repeat if foundExact then exit repeat on error -- Skip if no phones end try -- Check if search term is contained in name (partial match) else if lowerPersonName contains searchText or searchText contains lowerPersonName then try set phonesList to phones of currentPerson repeat with phoneItem in phonesList try set phoneValue to value of phoneItem if phoneValue is not "" then set partialMatches to partialMatches & {phoneValue} end if on error -- Skip problematic phone entries end try end repeat on error -- Skip if no phones end try end if on error -- Skip problematic contacts end try end repeat -- Return exact matches if found, otherwise partial matches if foundExact then return matchedPhones else return partialMatches end if end tell`,W=await A(Y),H=Array.isArray(W)?W:W?[W]:[];if(H.length===0){console.error(`No AppleScript matches for "${$}", trying comprehensive search...`);let z=await Z1(),B=(w)=>{return w.toLowerCase().replace(/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu,"").replace(/[♥️❤️💙💚💛💜🧡🖤🤍🤎]/g,"").replace(/\s+/g," ").trim()},G=[(w)=>B(w)===J,(w)=>{let q=B(w),V=B($);return q===V},(w)=>B(w).startsWith(J),(w)=>B(w).includes(J),(w)=>J.includes(B(w)),(w)=>{let V=B(w).split(" ")[0];return V===J||V.startsWith(J)||J.startsWith(V)||V.replace(/(.)\1+/g,"$1")===J||J.replace(/(.)\1+/g,"$1")===V},(w)=>{let V=B(w).split(" "),F=V[V.length-1];return F===J||F.startsWith(J)},(w)=>{return B(w).split(" ").some((F)=>F.includes(J)||J.includes(F)||F.replace(/(.)\1+/g,"$1")===J)}];for(let w of G){let q=Object.keys(z).filter(w);if(q.length>0)return console.error(`Found ${q.length} matches using fuzzy strategy for "${$}": ${q.join(", ")}`),z[q[0]]||[]}}return H.filter((z)=>z&&z.trim()!=="")}catch(Q){console.error(`Error finding contact: ${Q instanceof Error?Q.message:String(Q)}`);try{let J=await Z1(),Y=$.toLowerCase().trim(),W=Object.keys(J).find((H)=>H.toLowerCase().includes(Y)||Y.includes(H.toLowerCase()));if(W)return console.error(`Fallback found match for "${$}": ${W}`),J[W]}catch(J){console.error(`Fallback search also failed: ${J}`)}return[]}}async function b$($){try{let Q=await g1();if(!Q.hasAccess)throw new Error(Q.message);if(!$||$.trim()==="")return null;let J=$.replace(/[^0-9+]/g,""),Y=` tell application "Contacts" set foundName to "" set searchPhone to "${J}" -- Get a limited number of people to search through set allPeople to people repeat with i from 1 to (count of allPeople) if i > ${F8.MAX_CONTACTS} then exit repeat if foundName is not "" then exit repeat try set currentPerson to item i of allPeople try set phonesList to phones of currentPerson repeat with phoneItem in phonesList try set phoneValue to value of phoneItem -- Normalize phone value for comparison set normalizedPhone to phoneValue -- Simple phone matching if normalizedPhone contains searchPhone or searchPhone contains normalizedPhone then set foundName to name of currentPerson exit repeat end if on error -- Skip problematic phone entries end try end repeat on error -- Skip if no phones end try on error -- Skip problematic contacts end try end repeat return foundName end tell`,W=await A(Y);if(W&&W.trim()!=="")return W;let H=await Z1();for(let[z,B]of Object.entries(H))if(B.map((w)=>w.replace(/[^0-9+]/g,"")).some((w)=>w===J||w===`+${J}`||w===`+1${J}`||`+1${w}`===J||J.includes(w)||w.includes(J)))return z;return null}catch(Q){return console.error(`Error finding contact by phone: ${Q instanceof Error?Q.message:String(Q)}`),null}}var F8,C$;var L8=H0(()=>{w0();F8={MAX_CONTACTS:1000,TIMEOUT_MS:1e4};C$={getAllNumbers:Z1,findNumber:f$,findContactByPhone:b$,requestContactsAccess:g1}});var D8={};V0(D8,{default:()=>h$});async function P$(){try{return await A(` tell application "Notes" return name end tell`),!0}catch($){return console.error(`Cannot access Notes app: ${$ instanceof Error?$.message:String($)}`),!1}}async function K1(){try{if(await P$())return{hasAccess:!0,message:"Notes access is already granted."};return{hasAccess:!1,message:`Notes access is required but not granted. Please: 1. Open System Settings > Privacy & Security > Automation 2. Find your terminal/app in the list and enable 'Notes' 3. Restart your terminal and try again 4. If the option is not available, run this command again to trigger the permission dialog`}}catch($){return{hasAccess:!1,message:`Error checking Notes access: ${$ instanceof Error?$.message:String($)}`}}}async function E$(){try{let $=await K1();if(!$.hasAccess)throw new Error($.message);let Q=` tell application "Notes" set notesList to {} set noteCount to 0 -- Get all notes from all folders set allNotes to notes repeat with i from 1 to (count of allNotes) if noteCount >= ${J0.MAX_NOTES} then exit repeat try set currentNote to item i of allNotes set noteName to name of currentNote set noteContent to plaintext of currentNote -- Limit content for preview if (length of noteContent) > ${J0.MAX_CONTENT_PREVIEW} then set noteContent to (characters 1 thru ${J0.MAX_CONTENT_PREVIEW} of noteContent) as string set noteContent to noteContent & "..." end if set noteInfo to {name:noteName, content:noteContent} set notesList to notesList & {noteInfo} set noteCount to noteCount + 1 on error -- Skip problematic notes end try end repeat return notesList end tell`,J=await A(Q);return(Array.isArray(J)?J:J?[J]:[]).map((W)=>({name:W.name||"Untitled Note",content:W.content||"",creationDate:void 0,modificationDate:void 0}))}catch($){return console.error(`Error getting all notes: ${$ instanceof Error?$.message:String($)}`),[]}}async function N$($){try{let Q=await K1();if(!Q.hasAccess)throw new Error(Q.message);if(!$||$.trim()==="")return[];let Y=` tell application "Notes" set matchedNotes to {} set noteCount to 0 set searchTerm to "${$.toLowerCase()}" -- Get all notes and search through them set allNotes to notes repeat with i from 1 to (count of allNotes) if noteCount >= ${J0.MAX_NOTES} then exit repeat try set currentNote to item i of allNotes set noteName to name of currentNote set noteContent to plaintext of currentNote -- Simple case-insensitive search in name and content if (noteName contains searchTerm) or (noteContent contains searchTerm) then -- Limit content for preview if (length of noteContent) > ${J0.MAX_CONTENT_PREVIEW} then set noteContent to (characters 1 thru ${J0.MAX_CONTENT_PREVIEW} of noteContent) as string set noteContent to noteContent & "..." end if set noteInfo to {name:noteName, content:noteContent} set matchedNotes to matchedNotes & {noteInfo} set noteCount to noteCount + 1 end if on error -- Skip problematic notes end try end repeat return matchedNotes end tell`,W=await A(Y);return(Array.isArray(W)?W:W?[W]:[]).map((z)=>({name:z.name||"Untitled Note",content:z.content||"",creationDate:void 0,modificationDate:void 0}))}catch(Q){return console.error(`Error finding notes: ${Q instanceof Error?Q.message:String(Q)}`),[]}}async function R$($,Q,J="Claude"){try{let Y=await K1();if(!Y.hasAccess)return{success:!1,message:Y.message};if(!$||$.trim()==="")return{success:!1,message:"Note title cannot be empty"};let W=Q.trim(),H=`/tmp/note-content-${Date.now()}.txt`,z=k1("fs");z.writeFileSync(H,W,"utf8");let B=` tell application "Notes" set targetFolder to null set folderFound to false set actualFolderName to "${J}" -- Try to find the specified folder try set allFolders to folders repeat with currentFolder in allFolders if name of currentFolder is "${J}" then set targetFolder to currentFolder set folderFound to true exit repeat end if end repeat on error -- Folders might not be accessible end try -- If folder not found and it's a test folder, try to create it if not folderFound and ("${J}" is "Claude" or "${J}" is "Test-Claude") then try make new folder with properties {name:"${J}"} -- Try to find it again set allFolders to folders repeat with currentFolder in allFolders if name of currentFolder is "${J}" then set targetFolder to currentFolder set folderFound to true set actualFolderName to "${J}" exit repeat end if end repeat on error -- Folder creation failed, use default set actualFolderName to "Notes" end try end if -- Read content from file to preserve formatting set noteContent to read file POSIX file "${H}" as «class utf8» -- Create the note with proper content if folderFound and targetFolder is not null then -- Create note in specified folder make new note at targetFolder with properties {name:"${$.replace(/"/g,"\\\"")}", body:noteContent} return "SUCCESS:" & actualFolderName & ":false" else -- Create note in default location make new note with properties {name:"${$.replace(/"/g,"\\\"")}", body:noteContent} return "SUCCESS:Notes:true" end if end tell`,G=await A(B);try{z.unlinkSync(H)}catch(w){}if(G&&typeof G==="string"&&G.startsWith("SUCCESS:")){let w=G.split(":"),q=w[1]||"Notes",V=w[2]==="true";return{success:!0,note:{name:$,content:W},folderName:q,usedDefaultFolder:V}}else return{success:!1,message:`Failed to create note: ${G||"No result from AppleScript"}`}}catch(Y){return{success:!1,message:`Failed to create note: ${Y instanceof Error?Y.message:String(Y)}`}}}async function S8($){try{let Q=await K1();if(!Q.hasAccess)return{success:!1,message:Q.message};let J=` tell application "Notes" set notesList to {} set noteCount to 0 set folderFound to false -- Try to find the specified folder try set allFolders to folders repeat with currentFolder in allFolders if name of currentFolder is "${$}" then set folderFound to true -- Get notes from this folder set folderNotes to notes of currentFolder repeat with i from 1 to (count of folderNotes) if noteCount >= ${J0.MAX_NOTES} then exit repeat try set currentNote to item i of folderNotes set noteName to name of currentNote set noteContent to plaintext of currentNote -- Limit content for preview if (length of noteContent) > ${J0.MAX_CONTENT_PREVIEW} then set noteContent to (characters 1 thru ${J0.MAX_CONTENT_PREVIEW} of noteContent) as string set noteContent to noteContent & "..." end if set noteInfo to {name:noteName, content:noteContent} set notesList to notesList & {noteInfo} set noteCount to noteCount + 1 on error -- Skip problematic notes end try end repeat exit repeat end if end repeat on error -- Handle folder access errors end try if not folderFound then return "ERROR:Folder not found" end if return "SUCCESS:" & (count of notesList) end tell`,Y=await A(J);if(Y&&typeof Y==="string"){if(Y.startsWith("ERROR:"))return{success:!1,message:Y.replace("ERROR:","")};else if(Y.startsWith("SUCCESS:"))return{success:!0,notes:[]}}return{success:!0,notes:[]}}catch(Q){return{success:!1,message:`Failed to get notes from folder: ${Q instanceof Error?Q.message:String(Q)}`}}}async function v$($,Q=5){try{let J=await S8($);if(J.success&&J.notes)return{success:!0,notes:J.notes.slice(0,Math.min(Q,J.notes.length))};return J}catch(J){return{success:!1,message:`Failed to get recent notes from folder: ${J instanceof Error?J.message:String(J)}`}}}async function I$($,Q,J,Y=20){try{let W=await S8($);if(W.success&&W.notes)return{success:!0,notes:W.notes.slice(0,Math.min(Y,W.notes.length))};return W}catch(W){return{success:!1,message:`Failed to get notes by date range: ${W instanceof Error?W.message:String(W)}`}}}var J0,h$;var k8=H0(()=>{w0();J0={MAX_NOTES:50,MAX_CONTENT_PREVIEW:200,TIMEOUT_MS:8000};h$={getAllNotes:E$,findNote:N$,createNote:R$,getNotesFromFolder:S8,getRecentNotesFromFolder:v$,getNotesByDateRange:I$,requestNotesAccess:K1}});var f8={};V0(f8,{default:()=>n$});import{promisify as T$}from"node:util";import{exec as x$}from"node:child_process";import{access as y$}from"node:fs/promises";async function l$($){return new Promise((Q)=>setTimeout(Q,$))}async function M8($,Q=Z$,J=g$){try{return await $()}catch(Y){if(Q>0)return console.error(`Operation failed, retrying... (${Q} attempts remaining)`),await l$(J),M8($,Q-1,J);throw Y}}function m$($){let Q=$.replace(/[^0-9+]/g,"");if(/^\+1\d{10}$/.test(Q))return[Q];if(/^1\d{10}$/.test(Q))return[`+${Q}`];if(/^\d{10}$/.test(Q))return[`+1${Q}`];let J=new Set;if(Q.startsWith("+1"))J.add(Q);else if(Q.startsWith("1"))J.add(`+${Q}`);else J.add(`+1${Q}`);return Array.from(J)}async function R4($,Q){let J=Q.replace(/"/g,"\\\"");return await A(` tell application "Messages" set targetService to 1st service whose service type = iMessage set targetBuddy to buddy "${$}" send "${J}" to targetBuddy end tell`)}async function u$(){try{let $=`${process.env.HOME}/Library/Messages/chat.db`;return await y$($),await l1(`sqlite3 "${$}" "SELECT 1;"`),!0}catch($){return console.error(` Error: Cannot access Messages database. To fix this, please grant Full Disk Access to Terminal/iTerm2: 1. Open System Preferences 2. Go to Security & Privacy > Privacy 3. Select "Full Disk Access" from the left sidebar 4. Click the lock icon to make changes 5. Add Terminal.app or iTerm.app to the list 6. Restart your terminal and try again Error details: ${$ instanceof Error?$.message:String($)} `),!1}}async function A8(){try{if(await u$())return{hasAccess:!0,message:"Messages access is already granted."};try{return await A('tell application "Messages" to return name'),{hasAccess:!1,message:`Messages app is accessible but database access is required. Please: 1. Open System Settings > Privacy & Security > Full Disk Access 2. Add your terminal application (Terminal.app or iTerm.app) 3. Restart your terminal and try again 4. Note: This is required to read message history from the Messages database`}}catch(Q){return{hasAccess:!1,message:`Messages access is required but not granted. Please: 1. Open System Settings > Privacy & Security > Automation 2. Find your terminal/app and enable 'Messages' 3. Also grant Full Disk Access in Privacy & Security > Full Disk Access 4. Restart your terminal and try again`}}}catch($){return{hasAccess:!1,message:`Error checking Messages access: ${$ instanceof Error?$.message:String($)}`}}}function v4($){try{let J=Buffer.from($,"hex").toString(),Y=[/NSString">(.*?)</,/NSString">([^<]+)/,/NSNumber">\d+<.*?NSString">(.*?)</,/NSArray">.*?NSString">(.*?)</,/"string":\s*"([^"]+)"/,/text[^>]*>(.*?)</,/message>(.*?)</],W="";for(let B of Y){let G=J.match(B);if(G?.[1]){if(W=G[1],W.length>5)break}}let H=[/(https?:\/\/[^\s<"]+)/,/NSString">(https?:\/\/[^\s<"]+)/,/"url":\s*"(https?:\/\/[^"]+)"/,/link[^>]*>(https?:\/\/[^<]+)/],z;for(let B of H){let G=J.match(B);if(G?.[1]){z=G[1];break}}if(!W&&!z){let B=J.replace(/streamtyped.*?NSString/g,"").replace(/NSAttributedString.*?NSString/g,"").replace(/NSDictionary.*?$/g,"").replace(/\+[A-Za-z]+\s/g,"").replace(/NSNumber.*?NSValue.*?\*/g,"").replace(/[^\x20-\x7E]/g," ").replace(/\s+/g," ").trim();if(B.length>5)W=B;else return{text:"[Message content not readable]"}}if(W)W=W.replace(/^[+\s]+/,"").replace(/\s*iI\s*[A-Z]\s*$/,"").replace(/\s+/g," ").trim();return{text:W||z||"",url:z}}catch(Q){return console.error("Error decoding attributedBody:",Q),{text:"[Message content not readable]"}}}async function I4($){try{let Q=` SELECT filename FROM attachment INNER JOIN message_attachment_join ON attachment.ROWID = message_attachment_join.attachment_id WHERE message_attachment_join.message_id = ${$} `,{stdout:J}=await l1(`sqlite3 -json "${process.env.HOME}/Library/Messages/chat.db" "${Q}"`);if(!J.trim())return[];return JSON.parse(J).map((W)=>W.filename).filter(Boolean)}catch(Q){return console.error("Error getting attachments:",Q),[]}}async function p$($,Q=10){try{let J=Math.min(Q,N4.MAX_MESSAGES),Y=await A8();if(!Y.hasAccess)throw new Error(Y.message);let W=m$($);console.error("Trying phone formats:",W);let z=` SELECT m.ROWID as message_id, CASE WHEN m.text IS NOT NULL AND m.text != '' THEN m.text WHEN m.attributedBody IS NOT NULL THEN hex(m.attributedBody) ELSE NULL END as content, datetime(m.date/1000000000 + strftime('%s', '2001-01-01'), 'unixepoch', 'localtime') as date, h.id as sender, m.is_from_me, m.is_audio_message, m.cache_has_attachments, m.subject, CASE WHEN m.text IS NOT NULL AND m.text != '' THEN 0 WHEN m.attributedBody IS NOT NULL THEN 1 ELSE 2 END as content_type FROM message m INNER JOIN handle h ON h.ROWID = m.handle_id WHERE h.id IN (${W.map((q)=>`'${q.replace(/'/g,"''")}'`).join(",")}) AND (m.text IS NOT NULL OR m.attributedBody IS NOT NULL OR m.cache_has_attachments = 1) AND m.is_from_me IS NOT NULL -- Ensure it's a real message AND m.item_type = 0 -- Regular messages only AND m.is_audio_message = 0 -- Skip audio messages ORDER BY m.date DESC LIMIT ${J} `,{stdout:B}=await M8(()=>l1(`sqlite3 -json "${process.env.HOME}/Library/Messages/chat.db" "${z}"`));if(!B.trim())return console.error("No messages found in database for the given phone number"),[];let G=JSON.parse(B);return await Promise.all(G.filter((q)=>q.content!==null||q.cache_has_attachments===1).map(async(q)=>{let V=q.content||"",F;if(q.content_type===1){let T=v4(V);V=T.text,F=T.url}else{let T=V.match(/(https?:\/\/[^\s]+)/);if(T)F=T[1]}let C=[];if(q.cache_has_attachments)C=await I4(q.message_id);if(q.subject)V=`Subject: ${q.subject} ${V}`;let I={content:V||"[No text content]",date:new Date(q.date).toISOString(),sender:q.sender,is_from_me:Boolean(q.is_from_me)};if(C.length>0)I.attachments=C,I.content+=` [Attachments: ${C.length}]`;if(F)I.url=F,I.content+=` [URL: ${F}]`;return I}))}catch(J){if(console.error("Error reading messages:",J),J instanceof Error)console.error("Error details:",J.message),console.error("Stack trace:",J.stack);return[]}}async function c$($=10){try{let Q=Math.min($,N4.MAX_MESSAGES),J=await A8();if(!J.hasAccess)throw new Error(J.message);let Y=` SELECT m.ROWID as message_id, CASE WHEN m.text IS NOT NULL AND m.text != '' THEN m.text WHEN m.attributedBody IS NOT NULL THEN hex(m.attributedBody) ELSE NULL END as content, datetime(m.date/1000000000 + strftime('%s', '2001-01-01'), 'unixepoch', 'localtime') as date, h.id as sender, m.is_from_me, m.is_audio_message, m.cache_has_attachments, m.subject, CASE WHEN m.text IS NOT NULL AND m.text != '' THEN 0 WHEN m.attributedBody IS NOT NULL THEN 1 ELSE 2 END as content_type FROM message m INNER JOIN handle h ON h.ROWID = m.handle_id WHERE m.is_from_me = 0 -- Only messages from others AND m.is_read = 0 -- Only unread messages AND (m.text IS NOT NULL OR m.attributedBody IS NOT NULL OR m.cache_has_attachments = 1) AND m.is_audio_message = 0 -- Skip audio messages AND m.item_type = 0 -- Regular messages only ORDER BY m.date DESC LIMIT ${Q} `,{stdout:W}=await M8(()=>l1(`sqlite3 -json "${process.env.HOME}/Library/Messages/chat.db" "${Y}"`));if(!W.trim())return console.error("No unread messages found"),[];let H=JSON.parse(W);return await Promise.all(H.filter((B)=>B.content!==null||B.cache_has_attachments===1).map(async(B)=>{let G=B.content||"",w;if(B.content_type===1){let F=v4(G);G=F.text,w=F.url}else{let F=G.match(/(https?:\/\/[^\s]+)/);if(F)w=F[1]}let q=[];if(B.cache_has_attachments)q=await I4(B.message_id);if(B.subject)G=`Subject: ${B.subject} ${G}`;let V={content:G||"[No text content]",date:new Date(B.date).toISOString(),sender:B.sender,is_from_me:Boolean(B.is_from_me)};if(q.length>0)V.attachments=q,V.content+=` [Attachments: ${q.length}]`;if(w)V.url=w,V.content+=` [URL: ${w}]`;return V}))}catch(Q){if(console.error("Error reading unread messages:",Q),Q instanceof Error)console.error("Error details:",Q.message),console.error("Stack trace:",Q.stack);return[]}}async function d$($,Q,J){let Y=new Map,W=J.getTime()-Date.now();if(W<0)throw new Error("Cannot schedule message in the past");let H=setTimeout(async()=>{try{await R4($,Q),Y.delete(H)}catch(z){console.error("Failed to send scheduled message:",z)}},W);return Y.set(H,{phoneNumber:$,message:Q,scheduledTime:J,timeoutId:H}),{id:H,scheduledTime:J,message:Q,phoneNumber:$}}var l1,N4,Z$=3,g$=1000,n$;var b8=H0(()=>{w0();l1=T$(x$),N4={MAX_MESSAGES:50,MAX_CONTENT_PREVIEW:300,TIMEOUT_MS:8000};n$={sendMessage:R4,readMessages:p$,scheduleMessage:d$,getUnreadMessages:c$,requestMessagesAccess:A8}});var C8={};V0(C8,{default:()=>QQ});async function i$(){try{return await A(` tell application "Mail" return name end tell`),!0}catch($){return console.error(`Cannot access Mail app: ${$ instanceof Error?$.message:String($)}`),!1}}async function q0(){try{if(await i$())return{hasAccess:!0,message:"Mail access is already granted."};return{hasAccess:!1,message:`Mail access is required but not granted. Please: 1. Open System Settings > Privacy & Security > Automation 2. Find your terminal/app in the list and enable 'Mail' 3. Make sure Mail app is running and configured with at least one account 4. Restart your terminal and try again`}}catch($){return{hasAccess:!1,message:`Error checking Mail access: ${$ instanceof Error?$.message:String($)}`}}}async function o$($=10){try{let Q=await q0();if(!Q.hasAccess)throw new Error(Q.message);let J=Math.min($,m0.MAX_EMAILS),Y=` tell application "Mail" set emailList to {} set emailCount to 0 -- Get mailboxes (limited to avoid performance issues) set allMailboxes to mailboxes repeat with i from 1 to (count of allMailboxes) if emailCount >= ${J} then exit repeat try set currentMailbox to item i of allMailboxes set mailboxName to name of currentMailbox -- Get unread messages from this mailbox set unreadMessages to messages of currentMailbox repeat with j from 1 to (count of unreadMessages) if emailCount >= ${J} then exit repeat try set currentMsg to item j of unreadMessages -- Only process unread messages if read status of currentMsg is false then set emailSubject to subject of currentMsg set emailSender to sender of currentMsg set emailDate to (date sent of currentMsg) as string -- Get content with length limit set emailContent to "" try set fullContent to content of currentMsg if (length of fullContent) > ${m0.MAX_CONTENT_PREVIEW} then set emailContent to (characters 1 thru ${m0.MAX_CONTENT_PREVIEW} of fullContent) as string set emailContent to emailContent & "..." else set emailContent to fullContent end if on error set emailContent to "[Content not available]" end try set emailInfo to {subject:emailSubject, sender:emailSender, dateSent:emailDate, content:emailContent, isRead:false, mailbox:mailboxName} set emailList to emailList & {emailInfo} set emailCount to emailCount + 1 end if on error -- Skip problematic messages end try end repeat on error -- Skip problematic mailboxes end try end repeat return "SUCCESS:" & (count of emailList) end tell`,W=await A(Y);if(W&&W.startsWith("SUCCESS:"))return[];return[]}catch(Q){return console.error(`Error getting unread emails: ${Q instanceof Error?Q.message:String(Q)}`),[]}}async function a$($,Q=10){try{let J=await q0();if(!J.hasAccess)throw new Error(J.message);if(!$||$.trim()==="")return[];let Y=Math.min(Q,m0.MAX_EMAILS),H=` tell application "Mail" set emailList to {} set emailCount to 0 set searchTerm to "${$.toLowerCase()}" -- Get mailboxes (limited to avoid performance issues) set allMailboxes to mailboxes repeat with i from 1 to (count of allMailboxes) if emailCount >= ${Y} then exit repeat try set currentMailbox to item i of allMailboxes set mailboxName to name of currentMailbox -- Get messages from this mailbox set allMessages to messages of currentMailbox repeat with j from 1 to (count of allMessages) if emailCount >= ${Y} then exit repeat try set currentMsg to item j of allMessages set emailSubject to subject of currentMsg -- Simple case-insensitive search in subject if emailSubject contains searchTerm then set emailSender to sender of currentMsg set emailDate to (date sent of currentMsg) as string set emailRead to read status of currentMsg -- Get content with length limit set emailContent to "" try set fullContent to content of currentMsg if (length of fullContent) > ${m0.MAX_CONTENT_PREVIEW} then set emailContent to (characters 1 thru ${m0.MAX_CONTENT_PREVIEW} of fullContent) as string set emailContent to emailContent & "..." else set emailContent to fullContent end if on error set emailContent to "[Content not available]" end try set emailInfo to {subject:emailSubject, sender:emailSender, dateSent:emailDate, content:emailContent, isRead:emailRead, mailbox:mailboxName} set emailList to emailList & {emailInfo} set emailCount to emailCount + 1 end if on error -- Skip problematic messages end try end repeat on error -- Skip problematic mailboxes end try end repeat return "SUCCESS:" & (count of emailList) end tell`,z=await A(H);if(z&&z.startsWith("SUCCESS:"))return[];return[]}catch(J){return console.error(`Error searching emails: ${J instanceof Error?J.message:String(J)}`),[]}}async function r$($,Q,J,Y,W){try{let H=await q0();if(!H.hasAccess)throw new Error(H.message);if(!$||!$.trim())throw new Error("To address is required");if(!Q||!Q.trim())throw new Error("Subject is required");if(!J||!J.trim())throw new Error("Email body is required");let z=`/tmp/email-body-${Date.now()}.txt`,B=k1("fs");B.writeFileSync(z,J.trim(),"utf8");let G=` tell application "Mail" activate -- Read email body from file to preserve formatting set emailBody to read file POSIX file "${z}" as «class utf8» -- Create new message set newMessage to make new outgoing message with properties {subject:"${Q.replace(/"/g,"\\\"")}", content:emailBody, visible:true} tell newMessage make new to recipient with properties {address:"${$.replace(/"/g,"\\\"")}"} ${Y?`make new cc recipient with properties {address:"${Y.replace(/"/g,"\\\"")}"}`:""} ${W?`make new bcc recipient with properties {address:"${W.replace(/"/g,"\\\"")}"}`:""} end tell send newMessage return "SUCCESS" end tell`,w=await A(G);try{B.unlinkSync(z)}catch(q){}if(w==="SUCCESS")return`Email sent to ${$} with subject "${Q}"`;else throw new Error("Failed to send email")}catch(H){throw console.error(`Error sending email: ${H instanceof Error?H.message:String(H)}`),new Error(`Error sending email: ${H instanceof Error?H.message:String(H)}`)}}async function t$(){try{let $=await q0();if(!$.hasAccess)throw new Error($.message);let J=await A(` tell application "Mail" try -- Simple check - try to get just the count first set mailboxCount to count of mailboxes if mailboxCount > 0 then return {"Inbox", "Sent", "Drafts"} else return {} end if on error return {} end try end tell`);if(Array.isArray(J))return J.filter((Y)=>Y&&typeof Y==="string");return[]}catch($){return console.error(`Error getting mailboxes: ${$ instanceof Error?$.message:String($)}`),[]}}async function s$(){try{let $=await q0();if(!$.hasAccess)throw new Error($.message);let J=await A(` tell application "Mail" try -- Simple check - try to get just the count first set accountCount to count of accounts if accountCount > 0 then return {"Default Account"} else return {} end if on error return {} end try end tell`);if(Array.isArray(J))return J.filter((Y)=>Y&&typeof Y==="string");return[]}catch($){return console.error(`Error getting accounts: ${$ instanceof Error?$.message:String($)}`),[]}}async function e$($){try{let Q=await q0();if(!Q.hasAccess)throw new Error(Q.message);if(!$||!$.trim())return[];let J=` tell application "Mail" set boxList to {} try -- Find the account set targetAccount to first account whose name is "${$.replace(/"/g,"\\\"")}" set accountMailboxes to mailboxes of targetAccount repeat with i from 1 to (count of accountMailboxes) try set currentMailbox to item i of accountMailboxes set mailboxName to name of currentMailbox set boxList to boxList & {mailboxName} on error -- Skip problematic mailboxes end try end repeat on error -- Account not found or other error return {} end try return boxList end tell`,Y=await A(J);if(Array.isArray(Y))return Y.filter((W)=>W&&typeof W==="string");return[]}catch(Q){return console.error(`Error getting mailboxes for account: ${Q instanceof Error?Q.message:String(Q)}`),[]}}async function $Q($,Q=5){try{let J=await q0();if(!J.hasAccess)throw new Error(J.message);let Y=` tell application "Mail" set resultList to {} try set targetAccount to first account whose name is "${$.replace(/"/g,"\\\"")}" set acctMailboxes to every mailbox of targetAccount repeat with mb in acctMailboxes try set messagesList to (messages of mb) set sortedMessages to my sortMessagesByDate(messagesList) set msgLimit to ${Q} if (count of sortedMessages) < msgLimit then set msgLimit to (count of sortedMessages) end if repeat with i from 1 to msgLimit try set currentMsg to item i of sortedMessages set msgData to {subject:(subject of currentMsg), sender:(sender of currentMsg), ¬ date:(date sent of currentMsg) as string, mailbox:(name of mb)} try set msgContent to content of currentMsg if length of msgContent > 500 then set msgContent to (text 1 thru 500 of msgContent) & "..." end if set msgData to msgData & {content:msgContent} on error set msgData to msgData & {content:"[Content not available]"} end try set end of resultList to msgData on error -- Skip problematic messages end try end repeat if (count of resultList) ≥ ${Q} then exit repeat on error -- Skip problematic mailboxes end try end repeat on error errMsg return "Error: " & errMsg end try return resultList end tell on sortMessagesByDate(messagesList) set sortedMessages to sort messagesList by date sent return sortedMessages end sortMessagesByDate`,W=await A(Y);if(W&&W.startsWith("Error:"))throw new Error(W);let H=[],z=W.match(/\{([^}]+)\}/g);if(z&&z.length>0)for(let B of z)try{let G=B.substring(1,B.length-1).split(","),w={};if(G.forEach((q)=>{let V=q.split(":");if(V.length>=2){let F=V[0].trim(),C=V.slice(1).join(":").trim();w[F]=C}}),w.subject||w.sender)H.push({subject:w.subject||"No subject",sender:w.sender||"Unknown sender",dateSent:w.date||new Date().toString(),content:w.content||"[Content not available]",isRead:!1,mailbox:`${$} - ${w.mailbox||"Unknown"}`})}catch(G){console.error("Error parsing email match:",G)}return H}catch(J){return console.error("Error getting latest emails:",J),[]}}var m0,QQ;var P8=H0(()=>{w0();m0={MAX_EMAILS:20,MAX_CONTENT_PREVIEW:300,TIMEOUT_MS:1e4};QQ={getUnreadMails:o$,searchMails:a$,sendMail:r$,getMailboxes:t$,getAccounts:s$,getMailboxesForAccount:e$,getLatestMails:$Q,requestMailAccess:q0}});var E8={};V0(E8,{default:()=>GQ});async function WQ(){try{return await A(` tell application "Reminders" return name end tell`),!0}catch($){return console.error(`Cannot access Reminders app: ${$ instanceof Error?$.message:String($)}`),!1}}async function D0(){try{if(await WQ())return{hasAccess:!0,message:"Reminders access is already granted."};return{hasAccess:!1,message:`Reminders access is required but not granted. Please: 1. Open System Settings > Privacy & Security > Automation 2. Find your terminal/app in the list and enable 'Reminders' 3. Restart your terminal and try again 4. If the option is not available, run this command again to trigger the permission dialog`}}catch($){return{hasAccess:!1,message:`Error checking Reminders access: ${$ instanceof Error?$.message:String($)}`}}}async function YQ(){try{let $=await D0();if(!$.hasAccess)throw new Error($.message);let Q=` tell application "Reminders" set listArray to {} set listCount to 0 -- Get all lists set allLists to lists repeat with i from 1 to (count of allLists) if listCount >= ${JQ.MAX_LISTS} then exit repeat try set currentList to item i of allLists set listName to name of currentList set listId to id of currentList set listInfo to {name:listName, id:listId} set listArray to listArray & {listInfo} set listCount to listCount + 1 on error -- Skip problematic lists end try end repeat return listArray end tell`,J=await A(Q);return(Array.isArray(J)?J:J?[J]:[]).map((W)=>({name:W.name||"Untitled List",id:W.id||"unknown-id"}))}catch($){return console.error(`Error getting reminder lists: ${$ instanceof Error?$.message:String($)}`),[]}}async function HQ($){try{let Q=await D0();if(!Q.hasAccess)throw new Error(Q.message);let Y=await A(` tell application "Reminders" try -- Simple check - try to get just the count first to avoid timeouts set listCount to count of lists if listCount > 0 then return "SUCCESS:found_lists_but_reminders_query_too_slow" else return {} end if on error return {} end try end tell`);if(Y&&typeof Y==="string"&&Y.includes("SUCCESS"))return[];return[]}catch(Q){return console.error(`Error getting reminders: ${Q instanceof Error?Q.message:String(Q)}`),[]}}async function h4($){try{let Q=await D0();if(!Q.hasAccess)throw new Error(Q.message);if(!$||$.trim()==="")return[];let Y=await A(` tell application "Reminders" try -- For performance, just return success without actual search -- Searching reminders is too slow and unreliable in AppleScript return "SUCCESS:reminder_search_not_implemented_for_performance" on error return {} end try end tell`);return[]}catch(Q){return console.error(`Error searching reminders: ${Q instanceof Error?Q.message:String(Q)}`),[]}}async function zQ($,Q="Reminders",J,Y){try{let W=await D0();if(!W.hasAccess)throw new Error(W.message);if(!$||$.trim()==="")throw new Error("Reminder name cannot be empty");let H=$.replace(/\"/g,"\\\""),z=Q.replace(/\"/g,"\\\""),B=J?J.replace(/\"/g,"\\\""):"",G=` tell application "Reminders" try -- Use first available list (creating/finding lists can be slow) set allLists to lists if (count of allLists) > 0 then set targetList to first item of allLists set listName to name of targetList -- Create a simple reminder with just name set newReminder to make new reminder at targetList with properties {name:"${H}"} return "SUCCESS:" & listName else return "ERROR:No lists available" end if on error errorMessage return "ERROR:" & errorMessage end try end tell`,w=await A(G);if(w&&w.startsWith("SUCCESS:")){let q=w.replace("SUCCESS:","");return{name:$,id:"created-reminder-id",body:J||"",completed:!1,dueDate:Y||null,listName:q}}else throw new Error(`Failed to create reminder: ${w}`)}catch(W){throw new Error(`Failed to create reminder: ${W instanceof Error?W.message:String(W)}`)}}async function XQ($){try{let Q=await D0();if(!Q.hasAccess)return{success:!1,message:Q.message};let J=await h4($);if(J.length===0)return{success:!1,message:"No matching reminders found"};if(await A(` tell application "Reminders" activate return "SUCCESS" end tell`)==="SUCCESS")return{success:!0,message:"Reminders app opened",reminder:J[0]};else return{success:!1,message:"Failed to open Reminders app"}}catch(Q){return{success:!1,message:`Failed to open reminder: ${Q instanceof Error?Q.message:String(Q)}`}}}async function BQ($,Q){try{let J=await D0();if(!J.hasAccess)throw new Error(J.message);let W=await A(` tell application "Reminders" try -- For performance, just return success without actual data -- Getting reminders by ID is complex and slow in AppleScript return "SUCCESS:reminders_by_id_not_implemented_for_performance" on error return {} end try end tell`);return[]}catch(J){return console.error(`Error getting reminders from list by ID: ${J instanceof Error?J.message:String(J)}`),[]}}var JQ,GQ;var N8=H0(()=>{w0();JQ={MAX_REMINDERS:50,MAX_LISTS:20,TIMEOUT_MS:8000};GQ={getAllLists:YQ,getAllReminders:HQ,searchReminders:h4,createReminder:zQ,openReminder:XQ,getRemindersFromListById:BQ,requestRemindersAccess:D0}});var R8={};V0(R8,{default:()=>_Q});async function wQ(){try{return await A(` tell application "Calendar" return name end tell`),!0}catch($){return console.error(`Cannot access Calendar app: ${$ instanceof Error?$.message:String($)}`),!1}}async function V1(){try{if(await wQ())return{hasAccess:!0,message:"Calendar access is already granted."};return{hasAccess:!1,message:`Calendar access is required but not granted. Please: 1. Open System Settings > Privacy & Security > Automation 2. Find your terminal/app in the list and enable 'Calendar' 3. Alternatively, open System Settings > Privacy & Security > Calendars 4. Add your terminal/app to the allowed applications 5. Restart your terminal and try again`}}catch($){return{hasAccess:!1,message:`Error checking Calendar access: ${$ instanceof Error?$.message:String($)}`}}}async function qQ($=10,Q,J){try{console.error("getEvents - Starting to fetch calendar events");let Y=await V1();if(!Y.hasAccess)throw new Error(Y.message);console.error("getEvents - Calendar access check passed");let W=new Date,H=new Date;H.setDate(W.getDate()+7);let z=Q?Q:W.toISOString().split("T")[0],B=J?J:H.toISOString().split("T")[0],G=` tell application "Calendar" set eventList to {} set eventCount to 0 -- Create a simple test event to return (since Calendar queries are too slow) try set testEvent to {} set testEvent to testEvent & {id:"dummy-event-1"} set testEvent to testEvent & {title:"No events available - Calendar operations too slow"} set testEvent to testEvent & {calendarName:"System"} set testEvent to testEvent & {startDate:"${z}"} set testEvent to testEvent & {endDate:"${B}"} set testEvent to testEvent & {isAllDay:false} set testEvent to testEvent & {location:""} set testEvent to testEvent & {notes:"Calendar.app AppleScript queries are notoriously slow and unreliable"} set testEvent to testEvent & {url:""} set eventList to eventList & {testEvent} end try return eventList end tell`,w=await A(G);return(Array.isArray(w)?w:[]).map((F)=>({id:F.id||`unknown-${Date.now()}`,title:F.title||"Untitled Event",location:F.location||null,notes:F.notes||null,startDate:F.startDate?new Date(F.startDate).toISOString():null,endDate:F.endDate?new Date(F.endDate).toISOString():null,calendarName:F.calendarName||"Unknown Calendar",isAllDay:F.isAllDay||!1,url:F.url||null}))}catch(Y){return console.error(`Error getting events: ${Y instanceof Error?Y.message:String(Y)}`),[]}}async function jQ($,Q=10,J,Y){try{let W=await V1();if(!W.hasAccess)throw new Error(W.message);console.error(`searchEvents - Processing calendars for search: "${$}"`);let H=new Date,z=new Date;z.setDate(H.getDate()+30);let B=J?J:H.toISOString().split("T")[0],G=Y?Y:z.toISOString().split("T")[0],q=await A(` tell application "Calendar" set eventList to {} -- Return empty list for search (Calendar queries are too slow) return eventList end tell`);return(Array.isArray(q)?q:[]).map((C)=>({id:C.id||`unknown-${Date.now()}`,title:C.title||"Untitled Event",location:C.location||null,notes:C.notes||null,startDate:C.startDate?new Date(C.startDate).toISOString():null,endDate:C.endDate?new Date(C.endDate).toISOString():