Initial commit
This commit is contained in:
324
skills/omnifocus-manager/SKILL.md
Normal file
324
skills/omnifocus-manager/SKILL.md
Normal file
@@ -0,0 +1,324 @@
|
||||
---
|
||||
name: omnifocus-manager
|
||||
description: Manage OmniFocus tasks, projects, and inbox with proper tagging and organization
|
||||
triggers:
|
||||
- "add task"
|
||||
- "create task"
|
||||
- "new task"
|
||||
- "follow up with"
|
||||
- "triage omnifocus"
|
||||
- "triage my omnifocus"
|
||||
- "omnifocus inbox"
|
||||
- "clean up omnifocus"
|
||||
- "check omnifocus"
|
||||
- "show tasks"
|
||||
- "what's due"
|
||||
- "omnifocus review"
|
||||
allowed-tools: Read, Bash
|
||||
version: 0.1.0
|
||||
---
|
||||
|
||||
# OmniFocus Manager Skill
|
||||
|
||||
Manage OmniFocus tasks with proper project assignment, tagging, and organization based on user preferences.
|
||||
|
||||
## When to Activate
|
||||
|
||||
Use this skill when user wants to:
|
||||
- Add/create tasks
|
||||
- Follow up with someone
|
||||
- Triage or review inbox
|
||||
- Clean up or organize tasks
|
||||
- Check what's due or available
|
||||
- Query task status
|
||||
|
||||
## User Preferences
|
||||
|
||||
**Task Creation Philosophy:**
|
||||
- Always assign to a project (never leave in Inbox)
|
||||
- Always set expected completion date
|
||||
- Tag with person + "Follow Up" for 1:1 discussions
|
||||
- Use location tags for shopping tasks
|
||||
|
||||
## Available Scripts
|
||||
|
||||
Scripts are in `./scripts/` directory. Run via:
|
||||
```bash
|
||||
osascript -l JavaScript ./scripts/script-name.js
|
||||
```
|
||||
|
||||
**IMPORTANT:** Always use pure JXA, NOT Omni Automation URL scheme. The URL scheme triggers security popups for every unique script. JXA runs silently.
|
||||
|
||||
Key JXA patterns:
|
||||
- `doc.inboxTasks.push(task)` - create new tasks
|
||||
- `app.add(tag, {to: task.tags})` - add existing tags (not push!)
|
||||
- `task.assignedContainer = project` - move to project
|
||||
|
||||
### get_inbox.js
|
||||
Returns remaining inbox tasks (matches OmniFocus Inbox perspective).
|
||||
|
||||
**Filter logic:** Tasks with no project + not completed + not dropped + not deferred to future
|
||||
|
||||
**Output:** JSON with count and task array (id, name, note, tags, dueDate)
|
||||
|
||||
**Use when:** Starting inbox triage
|
||||
|
||||
### get_tags.js
|
||||
Returns full tag hierarchy with groupings.
|
||||
|
||||
**Output:** JSON with all 129 tags organized by parent/children
|
||||
|
||||
**Use when:** Need to find correct tags for a task
|
||||
|
||||
### get_projects.js
|
||||
Returns full project/folder structure.
|
||||
|
||||
**Output:** JSON with projects and folder paths
|
||||
|
||||
**Use when:** Need to find correct project for a task
|
||||
|
||||
### add_task.js
|
||||
Creates a new task with proper tags and project.
|
||||
|
||||
**Parameters:** name, project, tags[], dueDate, deferDate, note, flagged
|
||||
|
||||
**Use when:** Creating new tasks
|
||||
|
||||
### update_task.js
|
||||
Updates any existing task (not just inbox).
|
||||
|
||||
**Parameters:** name or id, project, tags[], dueDate, deferDate
|
||||
|
||||
**Use when:** Triaging/moving tasks, adding tags
|
||||
|
||||
### create_tag.js
|
||||
Creates a new tag, optionally under a parent.
|
||||
|
||||
**Parameters:** name, parent (optional)
|
||||
|
||||
**Use when:** Tag doesn't exist for a person or category
|
||||
|
||||
## Tag Hierarchy Reference
|
||||
|
||||
**Top-level categories:**
|
||||
- **Activity** - What type of work (Creative, Coding, Writing, Reading, Research, etc.)
|
||||
- **Energy** - Required mental state (Full Focus, Short Dashes, Brain Dead, Low, High)
|
||||
- **Location** - Where to do it (Home, Grocery Stores, PSD Sites, Other Shopping)
|
||||
- **People** - Who's involved (Personal family, PSD staff by department)
|
||||
- **Groups** - Team meetings (Cabinet, Engineering Team, DLI Admin, etc.)
|
||||
- **Time** - When to do it (Morning, Afternoon, Evening)
|
||||
- **Communications** - How to communicate (Email, Phone, In Person, etc.)
|
||||
- **Online** - Online tools (Freshservice, Github, Google Docs)
|
||||
- **Standalone** - Follow Up, Waiting For, Waiting, Kiwanis
|
||||
|
||||
**People → PSD breakdown:**
|
||||
- Tech: Mel, Bill, Reese, Mark, Brad, Mason, Jordan, etc.
|
||||
- DCRC: Jodi, Terri, Laura
|
||||
- Comms: Danielle, Jake, Shana
|
||||
- ESC: Ashley, John Y, Patrick, Krestin, James, Wendy, Janna, etc.
|
||||
- SSOs: Moose, Brent
|
||||
|
||||
**Special tags:**
|
||||
- Geoffrey - tasks that AI can assist with
|
||||
- Full Focus - requires dedicated focus time
|
||||
|
||||
## Task Routing Rules
|
||||
|
||||
### By Task Type → Project
|
||||
|
||||
| Task Type | Project | Default Due |
|
||||
|-----------|---------|-------------|
|
||||
| Discussions with people | Meetings | 7 days |
|
||||
| Phone calls | Meetings | 7 days |
|
||||
| CoSN-related | CoSN Work | 7 days |
|
||||
| Digital Promise work | Digital Promise | 7 days |
|
||||
| AI/automation projects | AI Studio | 7 days |
|
||||
| Coding/development | Coding Projects | 7 days |
|
||||
| Research/learning | Research for Future Plans | 7 days |
|
||||
| SOP/process development | Standard Operating Procedures | 14 days |
|
||||
| Form/procedure updates | Department Procedures | 7 days |
|
||||
| District reimbursements | Purchasing & Acquisitions | 7 days |
|
||||
| Travel approvals | (appropriate project) | 14 days |
|
||||
| Data governance | Data Governance | 14 days |
|
||||
| Tech support issues | → Freshservice ticket | N/A |
|
||||
|
||||
### By Task Type → Tags
|
||||
|
||||
| Task Type | Tags |
|
||||
|-----------|------|
|
||||
| Discussion with person | [Person name], Follow Up |
|
||||
| Phone call | Phone, Follow Up |
|
||||
| Research tasks | Research |
|
||||
| AI-assistable tasks | Geoffrey |
|
||||
| Focus time needed | Full Focus |
|
||||
| Admin/organizational | Organization |
|
||||
| Safety/security related | (relevant ESC person) |
|
||||
|
||||
### Routing Signals
|
||||
|
||||
**Goes to Meetings project:**
|
||||
- "talk to [person]"
|
||||
- "discuss with"
|
||||
- "follow up with"
|
||||
- "check with"
|
||||
- "call [person/org]"
|
||||
- "get [thing] to [person]"
|
||||
|
||||
**Goes to Research for Future Plans:**
|
||||
- "look at/into"
|
||||
- "what about"
|
||||
- CISA resources
|
||||
- Training to consider
|
||||
- External resources to review
|
||||
|
||||
**Goes to Coding Projects or AI Studio:**
|
||||
- AI/automation ideas
|
||||
- "build a program"
|
||||
- Geoffrey capabilities
|
||||
- Technical tools to explore
|
||||
|
||||
**Needs Freshservice (skip for now):**
|
||||
- User-reported issues
|
||||
- Equipment requests
|
||||
- "doesn't work/load"
|
||||
- Form rebuild requests
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Add a Task
|
||||
|
||||
1. Parse user request for: task name, person (if any), context clues
|
||||
|
||||
2. Apply routing rules above to determine:
|
||||
- **Project** - based on task type
|
||||
- **Tags** - person + communication method + activity type
|
||||
- **Due date** - based on task type timing
|
||||
|
||||
3. If tag doesn't exist, create it with `create_tag.js`
|
||||
|
||||
4. Run `add_task.js` with parameters
|
||||
|
||||
5. Return standardized output
|
||||
|
||||
**Example:**
|
||||
```
|
||||
User: "Follow up with Mel about the drone program"
|
||||
|
||||
Actions:
|
||||
- Task: "Follow up with Mel about the drone program"
|
||||
- Project: PSD > General Technology > Digital Innovation Leads
|
||||
- Tags: Mel, Follow Up
|
||||
- Due: Next 1:1 date or 7 days
|
||||
```
|
||||
|
||||
### Triage Inbox
|
||||
|
||||
1. **Get inbox tasks:**
|
||||
```bash
|
||||
osascript -l JavaScript ./scripts/get_inbox.js
|
||||
```
|
||||
This returns only remaining tasks (no project, not completed, not dropped, not deferred)
|
||||
|
||||
2. **Present assumptions in batches (10-15 tasks):**
|
||||
- Read task notes for context clues
|
||||
- Apply routing rules to suggest project, tags, due date
|
||||
- Flag unclear tasks that need user input
|
||||
|
||||
3. **Ask clarifying questions:**
|
||||
- Who is [person/acronym]?
|
||||
- Which project for [ambiguous task]?
|
||||
- Should this be skipped (needs email context)?
|
||||
|
||||
4. **Batch update confirmed tasks:**
|
||||
```bash
|
||||
osascript -l JavaScript ./scripts/update_task.js '{"name":"...", "project":"...", "tags":[...], "dueDate":"..."}'
|
||||
```
|
||||
|
||||
5. **Create missing tags/projects as needed:**
|
||||
```bash
|
||||
osascript -l JavaScript ./scripts/create_tag.js '{"name":"PersonName", "parent":"ESC"}'
|
||||
```
|
||||
|
||||
6. **Skip tasks that need:**
|
||||
- Email context (user needs to read first)
|
||||
- Freshservice ticket creation
|
||||
- More information to route properly
|
||||
|
||||
**Triage output format:**
|
||||
```markdown
|
||||
## My assumptions on remaining tasks:
|
||||
|
||||
| # | Task | Project | Tags | Notes |
|
||||
|---|------|---------|------|-------|
|
||||
| 1 | task name | Meetings | Person, Follow Up | context |
|
||||
|
||||
**Questions:**
|
||||
- #X: Who is [person]?
|
||||
- #Y: Which project for this?
|
||||
|
||||
Which numbers need correction?
|
||||
```
|
||||
|
||||
### Clean Up Tasks
|
||||
|
||||
1. Find tasks that are:
|
||||
- Overdue
|
||||
- Stale (no activity)
|
||||
- Missing tags
|
||||
- In wrong project
|
||||
2. Suggest actions:
|
||||
- Complete
|
||||
- Defer
|
||||
- Delete
|
||||
- Re-tag
|
||||
- Move
|
||||
|
||||
## Error Handling
|
||||
|
||||
**If OmniFocus not running:**
|
||||
```
|
||||
Status: ❌ Failed
|
||||
Error: OmniFocus is not running. Please open OmniFocus and try again.
|
||||
```
|
||||
|
||||
**If tag not found:**
|
||||
- Check for similar tags (fuzzy match)
|
||||
- Suggest creating new tag
|
||||
- Ask user to clarify
|
||||
|
||||
**If project not found:**
|
||||
- List available projects in that domain
|
||||
- Suggest closest match
|
||||
- Ask user to specify
|
||||
|
||||
## Output Format
|
||||
|
||||
Always use standardized format:
|
||||
|
||||
```markdown
|
||||
## Summary
|
||||
Created task with proper tags and project assignment
|
||||
|
||||
## Actions
|
||||
- Created task: "[task name]"
|
||||
- Project: [full project path]
|
||||
- Tags: [tag1, tag2, tag3]
|
||||
- Due: [date]
|
||||
- Notes: [if any]
|
||||
|
||||
## Status
|
||||
✅ Complete
|
||||
|
||||
## Next Steps
|
||||
- Task appears in [relevant perspective]
|
||||
- Follow up scheduled for [date if applicable]
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] Batch task creation
|
||||
- [ ] Smart project suggestion based on content
|
||||
- [ ] Calendar integration for due dates
|
||||
- [ ] Recurring task patterns
|
||||
- [ ] Perspective queries
|
||||
- [ ] Task completion tracking
|
||||
106
skills/omnifocus-manager/scripts/add_task.js
Normal file
106
skills/omnifocus-manager/scripts/add_task.js
Normal 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}`
|
||||
});
|
||||
}
|
||||
}
|
||||
79
skills/omnifocus-manager/scripts/create_tag.js
Normal file
79
skills/omnifocus-manager/scripts/create_tag.js
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
30
skills/omnifocus-manager/scripts/get_inbox.js
Normal file
30
skills/omnifocus-manager/scripts/get_inbox.js
Normal 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);
|
||||
}
|
||||
126
skills/omnifocus-manager/scripts/get_projects.js
Normal file
126
skills/omnifocus-manager/scripts/get_projects.js
Normal 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);
|
||||
}
|
||||
71
skills/omnifocus-manager/scripts/get_tags.js
Executable file
71
skills/omnifocus-manager/scripts/get_tags.js
Executable 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);
|
||||
}
|
||||
108
skills/omnifocus-manager/scripts/update_task.js
Normal file
108
skills/omnifocus-manager/scripts/update_task.js
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user