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
JavaScript
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;
}