Plug-In: Help Me Estimate
REQUIRES: macOS 26, iOS 26, iPados 26, visionOS 26 • OmniFocus 4.8
This plug-in uses the on-device Apple Foundation Models (AFM) frameworks to assist in the estimation of time required to complete the selected task.
NOTE: Both the task title and its note text serve as the prompt for the AFM.
Related Links: AFM and Omni Automation documentation • AFM Plug-In Collection
Help Me Estimate
/*{"author": "Ken Case","targets": ["omnifocus"],"type": "action","identifier": "com.omnigroup.kcase.help-me-estimate","version": "1.1","description": "A plug-in that helps me estimate a task.","label": "Help Me Estimate","mediumLabel": "Help Me Estimate","longLabel": "Help Me Estimate","paletteLabel": "Help Me Estimate","image": "apple.intelligence"}*/(() => {const schema = LanguageModel.Schema.fromJSON({name: "task-schema",properties: [{name: "estimatedMinutes", type: "integer", isOptional: false},]});const session = new LanguageModel.Session();const propertiesForTask = (task, depth, properties) => {let result = {};for (const property of properties) {const value = task[property];if (value != null) {result[property] = value;}}if (depth > 0) {const children = task.children;if (children && children.length > 0) {result.children = children.map((child) => propertiesForTask(child, depth - 1, properties));}}return result;}const helpMeEstimateTask = async (task, selection) => {const temporaryItem = addResponseItemToTask({"name": "Thinking…", "note": ""}, task);console.log("Processing Task:", task.name);console.log("Schema:", schema);console.log("Session:", session);const taskProperties = propertiesForTask(task, 9, ["name", "note", "estimatedMinutes"]);console.log("Task properties:", taskProperties);const prompt = "You're a GTD expert helping someone estimate the duration of a task or group of tasks in OmniFocus. Provide a time estimate in minutes for the following JSON task: " + JSON.stringify(taskProperties);const responseJSON = await session.respondWithSchema(prompt, schema);console.log(responseJSON);selection.database.deleteObject(temporaryItem);const response = JSON.parse(responseJSON);console.log(response);task.estimatedMinutes = Number(response.estimatedMinutes);}const addResponseItemToTask = (responseItem, task) => {let responseTask = new Task(responseItem.name, task);responseTask.note = responseItem.note;return responseTask}const saveEdits = (selection) => {const previousSelection = selection.databaseObjects;selection.window.selectObjects([]);selection.window.selectObjects(previousSelection);}const action = new PlugIn.Action(async (selection) => {try {console.log("Help me estimate…");saveEdits(selection);const projects = selection.projects;for (const project of projects) {await helpMeEstimateTask(project.task, selection);}const tasks = selection.tasks;for (const task of tasks) {await helpMeEstimateTask(task, selection);}} catch (err) {console.error(err.name, err.message)new Alert(err.name, err.message).show()}});return action;})();