Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:35:59 +08:00
commit 90883a4d25
287 changed files with 75058 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
#!/usr/bin/env osascript -l JavaScript
// Add a task to OmniFocus using pure JXA (no URL scheme, no popups)
// Usage: osascript -l JavaScript add_task.js '{"name":"Task name","project":"Project Name","tags":["Tag1","Tag2"],"dueDate":"2025-11-25","note":"Optional note"}'
function run(argv) {
const app = Application('OmniFocus');
app.includeStandardAdditions = true;
const doc = app.defaultDocument;
// Parse input
let input;
try {
input = JSON.parse(argv[0]);
} catch (e) {
return JSON.stringify({
success: false,
error: "Invalid JSON input. Expected: {name, project, tags, dueDate, note}"
});
}
const taskName = input.name;
const projectName = input.project;
const tagNames = input.tags || [];
const dueDateStr = input.dueDate;
const noteText = input.note || "";
const deferDateStr = input.deferDate;
const flagged = input.flagged || false;
if (!taskName) {
return JSON.stringify({
success: false,
error: "Task name is required"
});
}
try {
// Build task properties
const taskProps = {
name: taskName,
note: noteText,
flagged: flagged
};
// Set dates if provided
if (dueDateStr) {
taskProps.dueDate = new Date(dueDateStr);
}
if (deferDateStr) {
taskProps.deferDate = new Date(deferDateStr);
}
let task;
let targetLocation = "Inbox";
// Always create in inbox first
task = app.InboxTask(taskProps);
doc.inboxTasks.push(task);
// Then move to project if specified
if (projectName) {
const projects = doc.flattenedProjects.whose({name: projectName});
if (projects.length === 0) {
return JSON.stringify({
success: false,
error: `Project not found: ${projectName}`
});
}
const project = projects[0];
// Move task to project
task.assignedContainer = project;
targetLocation = projectName;
}
// Add tags (use app.add for existing objects, not push)
const addedTags = [];
for (const tagName of tagNames) {
const tags = doc.flattenedTags.whose({name: tagName});
if (tags.length > 0) {
app.add(tags[0], {to: task.tags});
addedTags.push(tagName);
}
}
return JSON.stringify({
success: true,
task: {
id: task.id(),
name: task.name(),
project: targetLocation,
tags: addedTags,
dueDate: dueDateStr || null,
deferDate: deferDateStr || null,
note: noteText || null,
flagged: flagged
}
});
} catch (e) {
return JSON.stringify({
success: false,
error: `Failed to create task: ${e.message}`
});
}
}

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env osascript -l JavaScript
// Create a new tag in OmniFocus
// Usage: osascript -l JavaScript create_tag.js '{"name":"Tag Name","parent":"Parent Tag Name"}'
function run(argv) {
const app = Application('OmniFocus');
app.includeStandardAdditions = true;
const doc = app.defaultDocument;
let input;
try {
input = JSON.parse(argv[0]);
} catch (e) {
return JSON.stringify({
success: false,
error: "Invalid JSON input. Expected: {name, parent (optional)}"
});
}
const tagName = input.name;
const parentName = input.parent;
if (!tagName) {
return JSON.stringify({
success: false,
error: "Tag name is required"
});
}
try {
// Check if tag already exists
const existing = doc.flattenedTags.whose({name: tagName});
if (existing.length > 0) {
return JSON.stringify({
success: true,
tag: {
id: existing[0].id(),
name: existing[0].name(),
existed: true
}
});
}
// Create new tag
const newTag = app.Tag({name: tagName});
if (parentName) {
// Find parent tag and add as child
const parents = doc.flattenedTags.whose({name: parentName});
if (parents.length === 0) {
return JSON.stringify({
success: false,
error: "Parent tag not found: " + parentName
});
}
parents[0].tags.push(newTag);
} else {
// Add to root tags
doc.tags.push(newTag);
}
return JSON.stringify({
success: true,
tag: {
id: newTag.id(),
name: newTag.name(),
parent: parentName || null,
existed: false
}
});
} catch (e) {
return JSON.stringify({
success: false,
error: "Failed to create tag: " + e.message
});
}
}

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env osascript -l JavaScript
// Get remaining inbox tasks (matches OmniFocus Inbox perspective)
// Usage: osascript -l JavaScript get_inbox.js
function run(argv) {
var app = Application("OmniFocus");
var doc = app.defaultDocument;
var now = new Date();
var tasks = doc.inboxTasks().filter(function(t) {
return t.assignedContainer() === null &&
!t.completed() &&
!t.dropped() &&
(t.deferDate() === null || t.deferDate() <= now);
}).map(function(t) {
return {
id: t.id(),
name: t.name(),
note: t.note() ? t.note().substring(0, 200) : "",
tags: t.tags().map(function(tag) { return tag.name(); }),
dueDate: t.dueDate() ? t.dueDate().toISOString().split("T")[0] : null
};
});
return JSON.stringify({
count: tasks.length,
tasks: tasks
}, null, 2);
}

View File

@@ -0,0 +1,126 @@
#!/usr/bin/env osascript -l JavaScript
// Get all OmniFocus projects with full folder hierarchy
// Returns JSON with project names, folders, and structure
function run() {
const app = Application('OmniFocus');
app.includeStandardAdditions = true;
const doc = app.defaultDocument;
const folders = doc.folders();
const projects = doc.flattenedProjects();
// Build folder structure
const folderStructure = [];
function processFolder(folder, depth = 0) {
const folderInfo = {
id: folder.id(),
name: folder.name(),
depth: depth,
projects: [],
subfolders: []
};
// Get projects in this folder
try {
const folderProjects = folder.projects();
for (let i = 0; i < folderProjects.length; i++) {
const proj = folderProjects[i];
folderInfo.projects.push({
id: proj.id(),
name: proj.name(),
status: proj.status(),
taskCount: proj.numberOfAvailableTasks()
});
}
} catch (e) {
// No projects in folder
}
// Get subfolders
try {
const subfolders = folder.folders();
for (let i = 0; i < subfolders.length; i++) {
folderInfo.subfolders.push(processFolder(subfolders[i], depth + 1));
}
} catch (e) {
// No subfolders
}
return folderInfo;
}
// Process top-level folders
for (let i = 0; i < folders.length; i++) {
folderStructure.push(processFolder(folders[i], 0));
}
// Get standalone projects (not in folders)
const standaloneProjects = [];
for (let i = 0; i < projects.length; i++) {
const proj = projects[i];
try {
const container = proj.container();
// If container is the document itself, it's standalone
if (container.class() === 'document') {
standaloneProjects.push({
id: proj.id(),
name: proj.name(),
status: proj.status(),
taskCount: proj.numberOfAvailableTasks()
});
}
} catch (e) {
// Skip if can't determine container
}
}
// Build flat list for easy lookup
const flatList = [];
function flattenStructure(folders, path = '') {
for (const folder of folders) {
const currentPath = path ? `${path} > ${folder.name}` : folder.name;
for (const proj of folder.projects) {
flatList.push({
id: proj.id,
name: proj.name,
path: `${currentPath} > ${proj.name}`,
folder: folder.name,
status: proj.status,
taskCount: proj.taskCount
});
}
if (folder.subfolders.length > 0) {
flattenStructure(folder.subfolders, currentPath);
}
}
}
flattenStructure(folderStructure);
// Add standalone projects
for (const proj of standaloneProjects) {
flatList.push({
id: proj.id,
name: proj.name,
path: proj.name,
folder: null,
status: proj.status,
taskCount: proj.taskCount
});
}
const result = {
total_projects: flatList.length,
folder_structure: folderStructure,
standalone_projects: standaloneProjects,
flat_list: flatList
};
return JSON.stringify(result, null, 2);
}

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env osascript -l JavaScript
// Get all OmniFocus tags with full hierarchy
// Returns JSON with tag names, parents, children, and groupings
function run() {
const app = Application('OmniFocus');
app.includeStandardAdditions = true;
const doc = app.defaultDocument;
const flattenedTags = doc.flattenedTags();
const tags = [];
for (let i = 0; i < flattenedTags.length; i++) {
const tag = flattenedTags[i];
const tagInfo = {
id: tag.id(),
name: tag.name(),
parent: null,
children: [],
availableTaskCount: tag.availableTaskCount(),
remainingTaskCount: tag.remainingTaskCount()
};
// Get parent tag if exists
try {
const parent = tag.container();
if (parent && parent.name) {
tagInfo.parent = parent.name();
}
} catch (e) {
// No parent (top-level tag)
}
// Get child tags
try {
const children = tag.tags();
for (let j = 0; j < children.length; j++) {
tagInfo.children.push(children[j].name());
}
} catch (e) {
// No children
}
tags.push(tagInfo);
}
// Build hierarchy structure
const hierarchy = {};
const topLevel = [];
for (const tag of tags) {
if (!tag.parent) {
topLevel.push(tag.name);
if (tag.children.length > 0) {
hierarchy[tag.name] = tag.children;
}
}
}
const result = {
total_tags: tags.length,
top_level_tags: topLevel,
hierarchy: hierarchy,
all_tags: tags
};
return JSON.stringify(result, null, 2);
}

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env osascript -l JavaScript
// Update any task with project, tags, due date
// Usage: osascript -l JavaScript update_task.js '{"name":"Task name","project":"Project Name","tags":["Tag1"],"dueDate":"2025-11-28","deferDate":"2025-01-28"}'
// Can also use "id" instead of "name" to find task by ID
function run(argv) {
const app = Application('OmniFocus');
app.includeStandardAdditions = true;
const doc = app.defaultDocument;
let input;
try {
input = JSON.parse(argv[0]);
} catch (e) {
return JSON.stringify({
success: false,
error: "Invalid JSON input"
});
}
const taskId = input.id;
const taskName = input.name;
const projectName = input.project;
const tagNames = input.tags || [];
const dueDateStr = input.dueDate;
const deferDateStr = input.deferDate;
if (!taskName && !taskId) {
return JSON.stringify({
success: false,
error: "Task name or id is required"
});
}
try {
// Find the task - search all tasks, not just inbox
let tasksRef;
if (taskId) {
tasksRef = doc.flattenedTasks.whose({id: taskId});
} else {
tasksRef = doc.flattenedTasks.whose({name: taskName});
}
const tasks = tasksRef();
if (tasks.length === 0) {
return JSON.stringify({
success: false,
error: "Task not found: " + (taskId || taskName)
});
}
const task = tasks[0];
// Set dates if provided
if (dueDateStr) {
task.dueDate = new Date(dueDateStr);
}
if (deferDateStr) {
task.deferDate = new Date(deferDateStr);
}
// Move to project if specified
let targetLocation = "Inbox";
if (projectName) {
const projectsRef = doc.flattenedProjects.whose({name: projectName});
const projects = projectsRef();
if (projects.length === 0) {
return JSON.stringify({
success: false,
error: "Project not found: " + projectName
});
}
task.assignedContainer = projects[0];
targetLocation = projectName;
}
// Add tags (preserves existing tags)
const addedTags = [];
for (const tagName of tagNames) {
const tags = doc.flattenedTags.whose({name: tagName});
if (tags.length > 0) {
app.add(tags[0], {to: task.tags});
addedTags.push(tagName);
}
}
// Get all current tags on task
const allTags = task.tags().map(function(t) { return t.name(); });
return JSON.stringify({
success: true,
task: {
id: task.id(),
name: task.name(),
project: targetLocation,
tags: allTags,
dueDate: dueDateStr || null,
deferDate: deferDateStr || null
}
});
} catch (e) {
return JSON.stringify({
success: false,
error: "Failed to update task: " + e.message
});
}
}