From 329265ce8103d3e6040e1ee080c8481fc87961bd Mon Sep 17 00:00:00 2001 From: Brian Landers Date: Sat, 1 Aug 2026 12:44:33 -0700 Subject: [PATCH 1/2] Add task-level drop/undrop support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tasks could already be filtered by dropped status, but there was no way to actually drop one — only projects supported it, via the status enum. OmniFocus's JXA API exposes this as task.drop(allOccurrences, dateDropped), distinct from the completed/markComplete pattern. - task update --drop / --undrop (CLI), dropped param (MCP update_task) - Undrop restores via task.active = true (dropDate is read-only, so drop() has no direct inverse — this is OmniFocus's own documented pattern for restoring a dropped task) - Fixed serializeTask's dropped field, which read task.dropped — a property that is always undefined via this JXA bridge regardless of actual state (confirmed via direct osascript probe). It now derives dropped status from dropDate !== null, which is reliably populated. Verified end-to-end against live OmniFocus: drop sets dropDate and flips effectiveActive/active to false; undrop clears both. --- README.md | 3 ++- src/commands/task.ts | 4 ++++ src/lib/omnifocus.ts | 5 ++++- src/mcp/server.ts | 1 + src/types.ts | 1 + 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f474497..4fcadd6 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,8 @@ of task create "Name" [options] --note # Add note of task update [options] - --complete # Mark completed + --complete / --incomplete # Mark completed/incomplete + --drop / --undrop # Mark dropped/restore to active --flag / --unflag # Toggle flag --name # Rename --project/--tag/--due/--defer # Same as create diff --git a/src/commands/task.ts b/src/commands/task.ts index fa39205..659a22d 100644 --- a/src/commands/task.ts +++ b/src/commands/task.ts @@ -71,6 +71,8 @@ export function createTaskCommand(): Command { .option('-F, --unflag', 'Unflag the task') .option('-c, --complete', 'Mark as completed') .option('-C, --incomplete', 'Mark as incomplete') + .option('--drop', 'Mark as dropped') + .option('--undrop', 'Restore a dropped task to active') .option('-e, --estimate ', 'Estimated time in minutes', parseInt) .action( withErrorHandling(async (idOrName, options) => { @@ -90,6 +92,8 @@ export function createTaskCommand(): Command { ...(options.unflag && { flagged: false }), ...(options.complete && { completed: true }), ...(options.incomplete && { completed: false }), + ...(options.drop && { dropped: true }), + ...(options.undrop && { dropped: false }), ...(options.estimate !== undefined && { estimatedMinutes: options.estimate }), }; const task = await of.updateTask(idOrName, updates); diff --git a/src/lib/omnifocus.ts b/src/lib/omnifocus.ts index 0eea0c7..03e1c9e 100644 --- a/src/lib/omnifocus.ts +++ b/src/lib/omnifocus.ts @@ -43,7 +43,7 @@ export class OmniFocus { name: task.name, note: task.note || null, completed: task.completed, - dropped: task.dropped, + dropped: task.dropDate !== null, effectivelyActive: task.effectiveActive, flagged: task.flagged, project: containingProject ? containingProject.name : null, @@ -354,6 +354,9 @@ export class OmniFocus { if (options.completed !== undefined) { updates.push(options.completed ? 'task.markComplete();' : 'task.markIncomplete();'); } + if (options.dropped !== undefined) { + updates.push(options.dropped ? 'task.drop(false, null);' : 'task.active = true;'); + } if (options.estimatedMinutes !== undefined) { updates.push(`task.estimatedMinutes = ${options.estimatedMinutes};`); } diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 56f6720..e0a4fdf 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -92,6 +92,7 @@ server.tool( flagged: z.boolean().optional().describe('Flag/unflag the task'), estimatedMinutes: z.number().optional().describe('New estimated duration'), completed: z.boolean().optional().describe('Mark complete/incomplete'), + dropped: z.boolean().optional().describe('Mark dropped, or restore (false) to active'), }, async ({ idOrName, ...options }) => jsonResponse(await of.updateTask(idOrName, options)) ); diff --git a/src/types.ts b/src/types.ts index c058dd8..af376f8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -63,6 +63,7 @@ export interface UpdateTaskOptions { flagged?: boolean; estimatedMinutes?: number; completed?: boolean; + dropped?: boolean; } export interface CreateProjectOptions { From 492c01545e2affb980e64780c0600d3bd12672f2 Mon Sep 17 00:00:00 2001 From: Brian Landers Date: Sat, 1 Aug 2026 12:50:14 -0700 Subject: [PATCH 2/2] Guard --undrop against reactivating completed tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An adversarial review flagged that task.active = true isn't specific to "dropped" — calling it on a completed task could silently un-complete it, an untested edge case in the original patch. Undrop is now a no-op on a completed task (verified live: completed stays true after --undrop). Also tightened the MCP dropped field's description to match the house style of sibling completed/flagged fields. --- src/lib/omnifocus.ts | 9 ++++++++- src/mcp/server.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lib/omnifocus.ts b/src/lib/omnifocus.ts index 03e1c9e..88539ef 100644 --- a/src/lib/omnifocus.ts +++ b/src/lib/omnifocus.ts @@ -355,7 +355,14 @@ export class OmniFocus { updates.push(options.completed ? 'task.markComplete();' : 'task.markIncomplete();'); } if (options.dropped !== undefined) { - updates.push(options.dropped ? 'task.drop(false, null);' : 'task.active = true;'); + updates.push( + options.dropped + ? 'task.drop(false, null);' + // Guard against reactivating a completed task: `active` isn't + // specific to "dropped", so undrop is a no-op on a task that's + // completed rather than dropped. + : 'if (!task.completed) { task.active = true; }' + ); } if (options.estimatedMinutes !== undefined) { updates.push(`task.estimatedMinutes = ${options.estimatedMinutes};`); diff --git a/src/mcp/server.ts b/src/mcp/server.ts index e0a4fdf..ab93388 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -92,7 +92,7 @@ server.tool( flagged: z.boolean().optional().describe('Flag/unflag the task'), estimatedMinutes: z.number().optional().describe('New estimated duration'), completed: z.boolean().optional().describe('Mark complete/incomplete'), - dropped: z.boolean().optional().describe('Mark dropped, or restore (false) to active'), + dropped: z.boolean().optional().describe('Mark dropped/undropped'), }, async ({ idOrName, ...options }) => jsonResponse(await of.updateTask(idOrName, options)) );