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;
})();