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
155 lines (154 loc) ⢠6.12 kB
JavaScript
import { executeOmniFocusScript } from '../../utils/scriptExecution.js';
export async function getCustomPerspective(options) {
const { perspectiveName, perspectiveId, hideCompleted = true, limit = 100 } = options;
if (!perspectiveName && !perspectiveId) {
throw new Error("Either perspectiveName or perspectiveId must be provided");
}
try {
// Execute the custom perspective script
const result = await executeOmniFocusScript('@customPerspective.js', {
perspectiveName,
perspectiveId,
hideCompleted,
limit
});
if (typeof result === 'string') {
return result;
}
// If result is an object, format it
if (result && typeof result === 'object') {
const data = result;
if (data.error) {
throw new Error(data.error);
}
// Format the custom perspective result
let output = `# šÆ CUSTOM PERSPECTIVE: ${data.perspectiveName || perspectiveName || perspectiveId}\n\n`;
if (data.note) {
output += `ā ļø ${data.note}\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 += "- Use `list_custom_perspectives` to see available perspectives\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 += formatCustomPerspectiveTask(task);
output += '\n';
});
if (tasksByProject.size > 1) {
output += '\n';
}
});
// Add perspective metadata
if (data.perspectiveId) {
output += `\nš **Perspective ID**: ${data.perspectiveId}\n`;
}
if (data.isBuiltIn) {
output += `š” **Built-in Perspective**: Consider using dedicated tools for better performance\n`;
}
}
}
else {
output += "No task data available from perspective\n";
}
return output;
}
return "Unexpected result format from OmniFocus";
}
catch (error) {
console.error("Error in getCustomPerspective:", error);
throw new Error(`Failed to get custom perspective: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// Group tasks by project for better organization
function groupTasksByProject(tasks) {
const groups = new Map();
tasks.forEach(task => {
const projectName = task.projectName || (task.inInbox ? 'š„ Inbox' : 'š No Project');
if (!groups.has(projectName)) {
groups.set(projectName, []);
}
groups.get(projectName).push(task);
});
return groups;
}
// Format a task from custom perspective
function formatCustomPerspectiveTask(task) {
let output = '';
// Task basic information with status emoji
const flagSymbol = task.flagged ? 'š© ' : '';
const statusEmoji = getStatusEmoji(task.taskStatus);
output += `${statusEmoji} ${flagSymbol}${task.name}`;
// Date information
const dateInfo = [];
if (task.dueDate) {
const dueDateStr = new Date(task.dueDate).toLocaleDateString();
const isOverdue = new Date(task.dueDate) < new Date();
dateInfo.push(isOverdue ? `ā ļø DUE: ${dueDateStr}` : `š
DUE: ${dueDateStr}`);
}
if (task.deferDate) {
const deferDateStr = new Date(task.deferDate).toLocaleDateString();
dateInfo.push(`š DEFER: ${deferDateStr}`);
}
if (task.completedDate) {
const completedDateStr = new Date(task.completedDate).toLocaleDateString();
dateInfo.push(`ā
DONE: ${completedDateStr}`);
}
if (dateInfo.length > 0) {
output += ` [${dateInfo.join(', ')}]`;
}
// Additional information
const additionalInfo = [];
if (task.taskStatus && task.taskStatus !== 'Available') {
additionalInfo.push(task.taskStatus);
}
if (task.estimatedMinutes) {
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`;
}
// Tags
if (task.tags && task.tags.length > 0) {
const tagNames = task.tags.map((tag) => tag.name).join(', ');
output += ` š· ${tagNames}\n`;
}
return output;
}
// Get status emoji for task status
function getStatusEmoji(status) {
const statusMap = {
'Available': 'āŖ',
'Next': 'šµ',
'Blocked': 'š“',
'DueSoon': 'š”',
'Overdue': 'š“',
'Completed': 'ā
',
'Dropped': 'ā«'
};
return statusMap[status] || 'āŖ';
}