UNPKG

omnifocus-mcp-enhanced

Version:

šŸš€ NEW: Native Custom Perspective Access! Enhanced MCP server with OmniFocus custom perspective support, hierarchical task display, AI-optimized tool selection, and comprehensive task management

186 lines (185 loc) • 7.17 kB
import { executeAppleScript } from '../../utils/scriptExecution.js'; import { readFileSync } from 'fs'; import { join } from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; export async function getPerspectiveTasks(options) { const { perspectiveName, hideCompleted = true, limit = 100 } = options; if (!perspectiveName) { throw new Error("perspectiveName is required"); } try { // Get the AppleScript file path const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const scriptPath = join(__dirname, '..', '..', 'utils', 'omnifocusScripts', 'perspectiveAccess.scpt'); // Read the AppleScript content const scriptContent = readFileSync(scriptPath, 'utf8'); // Execute the AppleScript with parameters const result = await executeAppleScript(scriptContent, { perspectiveName, hideCompleted, limit }); if (typeof result === 'string') { // Try to parse as JSON try { const parsedResult = JSON.parse(result); return formatPerspectiveResult(parsedResult, perspectiveName); } catch { // If parsing fails, return as is return result; } } // If result is already an object, format it if (result && typeof result === 'object') { return formatPerspectiveResult(result, perspectiveName); } return "Unexpected result format from OmniFocus AppleScript"; } catch (error) { console.error("Error in getPerspectiveTasks:", error); throw new Error(`Failed to get perspective tasks: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Format the perspective result for display function formatPerspectiveResult(data, perspectiveName) { let output = `# šŸŽÆ PERSPECTIVE: ${data.perspectiveName || perspectiveName}\n\n`; if (data.error) { output += `āŒ **Error**: ${data.error}\n\n`; output += "**Suggestions**:\n"; output += "- Check if the perspective name is correct\n"; output += "- Ensure OmniFocus is running\n"; output += "- Try using `list_custom_perspectives` to see available perspectives\n"; return output; } if (!data.success) { output += "āš ļø Failed to access perspective\n"; return output; } if (data.isRealPerspective) { output += "āœ… **Real OmniFocus Perspective Access** (via AppleScript)\n\n"; } if (data.tasks && Array.isArray(data.tasks)) { if (data.tasks.length === 0) { output += "šŸ“Ŗ No tasks found in this perspective.\n"; output += "\n**Tips**:\n"; output += "- Check if the perspective has any tasks\n"; output += "- Try adjusting hideCompleted setting\n"; output += "- Verify the perspective name is correct\n"; } else { const taskCount = data.tasks.length; output += `Found ${taskCount} task${taskCount === 1 ? '' : 's'}:\n\n`; // Group tasks by project for better organization const tasksByProject = groupTasksByProject(data.tasks); tasksByProject.forEach((tasks, projectName) => { if (tasksByProject.size > 1) { output += `## šŸ“ ${projectName}\n`; } tasks.forEach((task) => { output += formatPerspectiveTask(task); output += '\n'; }); if (tasksByProject.size > 1) { output += '\n'; } }); // Add perspective metadata output += `\nšŸ“Š **Total Tasks**: ${data.taskCount || taskCount}\n`; output += `šŸ” **Perspective**: ${data.perspectiveName}\n`; output += `āš™ļø **Method**: Real AppleScript Access\n`; } } else { output += "No task data available from perspective\n"; } return output; } // Group tasks by project for better organization function groupTasksByProject(tasks) { const groups = new Map(); tasks.forEach(task => { const projectName = task.projectName || 'šŸ“‚ No Project'; if (!groups.has(projectName)) { groups.set(projectName, []); } groups.get(projectName).push(task); }); return groups; } // Format a task from perspective function formatPerspectiveTask(task) { let output = ''; // Task basic information with status const flagSymbol = task.flagged ? '🚩 ' : ''; const completedSymbol = task.completed ? 'āœ… ' : '⚪ '; output += `${completedSymbol}${flagSymbol}${task.name}`; // Date information const dateInfo = []; if (task.dueDate && task.dueDate !== "") { try { const dueDate = new Date(task.dueDate); const dueDateStr = dueDate.toLocaleDateString(); const isOverdue = dueDate < new Date() && !task.completed; dateInfo.push(isOverdue ? `āš ļø DUE: ${dueDateStr}` : `šŸ“… DUE: ${dueDateStr}`); } catch { dateInfo.push(`šŸ“… DUE: ${task.dueDate}`); } } if (task.deferDate && task.deferDate !== "") { try { const deferDate = new Date(task.deferDate); const deferDateStr = deferDate.toLocaleDateString(); dateInfo.push(`šŸš€ DEFER: ${deferDateStr}`); } catch { dateInfo.push(`šŸš€ DEFER: ${task.deferDate}`); } } if (task.completionDate && task.completionDate !== "") { try { const completedDate = new Date(task.completionDate); const completedDateStr = completedDate.toLocaleDateString(); dateInfo.push(`āœ… DONE: ${completedDateStr}`); } catch { dateInfo.push(`āœ… DONE: ${task.completionDate}`); } } if (dateInfo.length > 0) { output += ` [${dateInfo.join(', ')}]`; } // Additional information const additionalInfo = []; if (task.estimatedMinutes && task.estimatedMinutes > 0) { const hours = Math.floor(task.estimatedMinutes / 60); const minutes = task.estimatedMinutes % 60; if (hours > 0) { additionalInfo.push(`ā± ${hours}h${minutes > 0 ? `${minutes}m` : ''}`); } else { additionalInfo.push(`ā± ${minutes}m`); } } if (additionalInfo.length > 0) { output += ` (${additionalInfo.join(', ')})`; } output += '\n'; // Task note if (task.note && task.note.trim()) { output += ` šŸ“ ${task.note.trim()}\n`; } // Project information if available if (task.projectName && task.projectName !== "") { output += ` šŸ“ Project: ${task.projectName}\n`; } // Tags if available if (task.tags && Array.isArray(task.tags) && task.tags.length > 0) { const tagNames = task.tags.join(', '); output += ` šŸ· ${tagNames}\n`; } return output; }