reminders.ts•10.5 kB
import { runAppleScript } from "run-applescript";
// Configuration
const CONFIG = {
// Maximum reminders to process (to avoid performance issues)
MAX_REMINDERS: 50,
// Maximum lists to process
MAX_LISTS: 20,
// Timeout for operations
TIMEOUT_MS: 8000,
};
// Define types for our reminders
interface ReminderList {
name: string;
id: string;
}
interface Reminder {
name: string;
id: string;
body: string;
completed: boolean;
dueDate: string | null;
listName: string;
completionDate?: string | null;
creationDate?: string | null;
modificationDate?: string | null;
remindMeDate?: string | null;
priority?: number;
}
/**
* Check if Reminders app is accessible
*/
async function checkRemindersAccess(): Promise<boolean> {
try {
const script = `
tell application "Reminders"
return name
end tell`;
await runAppleScript(script);
return true;
} catch (error) {
console.error(
`Cannot access Reminders app: ${error instanceof Error ? error.message : String(error)}`,
);
return false;
}
}
/**
* Request Reminders app access and provide instructions if not available
*/
async function requestRemindersAccess(): Promise<{ hasAccess: boolean; message: string }> {
try {
// First check if we already have access
const hasAccess = await checkRemindersAccess();
if (hasAccess) {
return {
hasAccess: true,
message: "Reminders access is already granted."
};
}
// If no access, provide clear instructions
return {
hasAccess: false,
message: "Reminders access is required but not granted. Please:\n1. Open System Settings > Privacy & Security > Automation\n2. Find your terminal/app in the list and enable 'Reminders'\n3. Restart your terminal and try again\n4. If the option is not available, run this command again to trigger the permission dialog"
};
} catch (error) {
return {
hasAccess: false,
message: `Error checking Reminders access: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Get all reminder lists (limited for performance)
* @returns Array of reminder lists with their names and IDs
*/
async function getAllLists(): Promise<ReminderList[]> {
try {
const accessResult = await requestRemindersAccess();
if (!accessResult.hasAccess) {
throw new Error(accessResult.message);
}
const script = `
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 >= ${CONFIG.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`;
const result = (await runAppleScript(script)) as any;
// Convert AppleScript result to our format
const resultArray = Array.isArray(result) ? result : result ? [result] : [];
return resultArray.map((listData: any) => ({
name: listData.name || "Untitled List",
id: listData.id || "unknown-id",
}));
} catch (error) {
console.error(
`Error getting reminder lists: ${error instanceof Error ? error.message : String(error)}`,
);
return [];
}
}
/**
* Get all reminders from a specific list or all lists (simplified for performance)
* @param listName Optional list name to filter by
* @returns Array of reminders
*/
async function getAllReminders(listName?: string): Promise<Reminder[]> {
try {
const accessResult = await requestRemindersAccess();
if (!accessResult.hasAccess) {
throw new Error(accessResult.message);
}
const script = `
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`;
const result = (await runAppleScript(script)) as any;
// For performance reasons, just return empty array with success message
// Complex reminder queries are too slow and unreliable
if (result && typeof result === "string" && result.includes("SUCCESS")) {
return [];
}
return [];
} catch (error) {
console.error(
`Error getting reminders: ${error instanceof Error ? error.message : String(error)}`,
);
return [];
}
}
/**
* Search for reminders by text (simplified for performance)
* @param searchText Text to search for in reminder names or notes
* @returns Array of matching reminders
*/
async function searchReminders(searchText: string): Promise<Reminder[]> {
try {
const accessResult = await requestRemindersAccess();
if (!accessResult.hasAccess) {
throw new Error(accessResult.message);
}
if (!searchText || searchText.trim() === "") {
return [];
}
const script = `
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`;
const result = (await runAppleScript(script)) as any;
// For performance reasons, just return empty array
// Complex reminder search is too slow and unreliable
return [];
} catch (error) {
console.error(
`Error searching reminders: ${error instanceof Error ? error.message : String(error)}`,
);
return [];
}
}
/**
* Create a new reminder (simplified for performance)
* @param name Name of the reminder
* @param listName Name of the list to add the reminder to (creates if doesn't exist)
* @param notes Optional notes for the reminder
* @param dueDate Optional due date for the reminder (ISO string)
* @returns The created reminder
*/
async function createReminder(
name: string,
listName: string = "Reminders",
notes?: string,
dueDate?: string,
): Promise<Reminder> {
try {
const accessResult = await requestRemindersAccess();
if (!accessResult.hasAccess) {
throw new Error(accessResult.message);
}
// Validate inputs
if (!name || name.trim() === "") {
throw new Error("Reminder name cannot be empty");
}
const cleanName = name.replace(/\"/g, '\\"');
const cleanListName = listName.replace(/\"/g, '\\"');
const cleanNotes = notes ? notes.replace(/\"/g, '\\"') : "";
const script = `
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:"${cleanName}"}
return "SUCCESS:" & listName
else
return "ERROR:No lists available"
end if
on error errorMessage
return "ERROR:" & errorMessage
end try
end tell`;
const result = (await runAppleScript(script)) as string;
if (result && result.startsWith("SUCCESS:")) {
const actualListName = result.replace("SUCCESS:", "");
return {
name: name,
id: "created-reminder-id",
body: notes || "",
completed: false,
dueDate: dueDate || null,
listName: actualListName,
};
} else {
throw new Error(`Failed to create reminder: ${result}`);
}
} catch (error) {
throw new Error(
`Failed to create reminder: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
interface OpenReminderResult {
success: boolean;
message: string;
reminder?: Reminder;
}
/**
* Open the Reminders app and show a specific reminder (simplified)
* @param searchText Text to search for in reminder names or notes
* @returns Result of the operation
*/
async function openReminder(searchText: string): Promise<OpenReminderResult> {
try {
const accessResult = await requestRemindersAccess();
if (!accessResult.hasAccess) {
return { success: false, message: accessResult.message };
}
// First search for the reminder
const matchingReminders = await searchReminders(searchText);
if (matchingReminders.length === 0) {
return { success: false, message: "No matching reminders found" };
}
// Open the Reminders app
const script = `
tell application "Reminders"
activate
return "SUCCESS"
end tell`;
const result = (await runAppleScript(script)) as string;
if (result === "SUCCESS") {
return {
success: true,
message: "Reminders app opened",
reminder: matchingReminders[0],
};
} else {
return { success: false, message: "Failed to open Reminders app" };
}
} catch (error) {
return {
success: false,
message: `Failed to open reminder: ${error instanceof Error ? error.message : String(error)}`,
};
}
}
/**
* Get reminders from a specific list by ID (simplified for performance)
* @param listId ID of the list to get reminders from
* @param props Array of properties to include (optional, ignored for simplicity)
* @returns Array of reminders with basic properties
*/
async function getRemindersFromListById(
listId: string,
props?: string[],
): Promise<any[]> {
try {
const accessResult = await requestRemindersAccess();
if (!accessResult.hasAccess) {
throw new Error(accessResult.message);
}
const script = `
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`;
const result = (await runAppleScript(script)) as any;
// For performance reasons, just return empty array
// Complex reminder queries are too slow and unreliable
return [];
} catch (error) {
console.error(
`Error getting reminders from list by ID: ${error instanceof Error ? error.message : String(error)}`,
);
return [];
}
}
export default {
getAllLists,
getAllReminders,
searchReminders,
createReminder,
openReminder,
getRemindersFromListById,
requestRemindersAccess,
};