@stephendolan/omnifocus-cli 1.2.4 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1302 -20
- package/dist/index.js.map +1 -1
- package/package.json +10 -8
- package/dist/commands/inbox.d.ts +0 -3
- package/dist/commands/inbox.d.ts.map +0 -1
- package/dist/commands/inbox.js +0 -22
- package/dist/commands/inbox.js.map +0 -1
- package/dist/commands/perspective.d.ts +0 -3
- package/dist/commands/perspective.d.ts.map +0 -1
- package/dist/commands/perspective.js +0 -22
- package/dist/commands/perspective.js.map +0 -1
- package/dist/commands/project.d.ts +0 -3
- package/dist/commands/project.d.ts.map +0 -1
- package/dist/commands/project.js +0 -87
- package/dist/commands/project.js.map +0 -1
- package/dist/commands/search.d.ts +0 -3
- package/dist/commands/search.d.ts.map +0 -1
- package/dist/commands/search.js +0 -13
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/tag.d.ts +0 -3
- package/dist/commands/tag.d.ts.map +0 -1
- package/dist/commands/tag.js +0 -68
- package/dist/commands/tag.js.map +0 -1
- package/dist/commands/task.d.ts +0 -3
- package/dist/commands/task.d.ts.map +0 -1
- package/dist/commands/task.js +0 -101
- package/dist/commands/task.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/__tests__/display.test.d.ts +0 -2
- package/dist/lib/__tests__/display.test.d.ts.map +0 -1
- package/dist/lib/__tests__/display.test.js +0 -93
- package/dist/lib/__tests__/display.test.js.map +0 -1
- package/dist/lib/command-utils.d.ts +0 -4
- package/dist/lib/command-utils.d.ts.map +0 -1
- package/dist/lib/command-utils.js +0 -22
- package/dist/lib/command-utils.js.map +0 -1
- package/dist/lib/display.d.ts +0 -21
- package/dist/lib/display.d.ts.map +0 -1
- package/dist/lib/display.js +0 -294
- package/dist/lib/display.js.map +0 -1
- package/dist/lib/omnifocus.d.ts +0 -38
- package/dist/lib/omnifocus.d.ts.map +0 -1
- package/dist/lib/omnifocus.js +0 -860
- package/dist/lib/omnifocus.js.map +0 -1
- package/dist/lib/output.d.ts +0 -6
- package/dist/lib/output.d.ts.map +0 -1
- package/dist/lib/output.js +0 -12
- package/dist/lib/output.js.map +0 -1
- package/dist/types.d.ts +0 -164
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
package/dist/lib/omnifocus.js
DELETED
|
@@ -1,860 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'child_process';
|
|
2
|
-
import { writeFile, unlink } from 'fs/promises';
|
|
3
|
-
import { tmpdir } from 'os';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
const execFileAsync = promisify(execFile);
|
|
7
|
-
export class OmniFocus {
|
|
8
|
-
PROJECT_STATUS_MAP = {
|
|
9
|
-
'active': 'Active',
|
|
10
|
-
'on hold': 'OnHold',
|
|
11
|
-
'dropped': 'Dropped'
|
|
12
|
-
};
|
|
13
|
-
OMNI_HELPERS = `
|
|
14
|
-
function serializeTask(task) {
|
|
15
|
-
const containingProject = task.containingProject;
|
|
16
|
-
const tagNames = task.tags.map(t => t.name);
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
id: task.id.primaryKey,
|
|
20
|
-
name: task.name,
|
|
21
|
-
note: task.note || null,
|
|
22
|
-
completed: task.completed,
|
|
23
|
-
flagged: task.flagged,
|
|
24
|
-
project: containingProject ? containingProject.name : null,
|
|
25
|
-
tags: tagNames,
|
|
26
|
-
defer: task.deferDate ? task.deferDate.toISOString() : null,
|
|
27
|
-
due: task.dueDate ? task.dueDate.toISOString() : null,
|
|
28
|
-
estimatedMinutes: task.estimatedMinutes || null,
|
|
29
|
-
completionDate: task.completionDate ? task.completionDate.toISOString() : null,
|
|
30
|
-
added: task.added ? task.added.toISOString() : null,
|
|
31
|
-
modified: task.modified ? task.modified.toISOString() : null
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function serializeProject(project) {
|
|
36
|
-
const folder = project.folder;
|
|
37
|
-
const allTasks = project.flattenedTasks;
|
|
38
|
-
const remainingTasks = allTasks.filter(t => !t.completed);
|
|
39
|
-
const tagNames = project.tags.map(t => t.name);
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
id: project.id.primaryKey,
|
|
43
|
-
name: project.name,
|
|
44
|
-
note: project.note || null,
|
|
45
|
-
status: projectStatusToString(project.status),
|
|
46
|
-
folder: folder ? folder.name : null,
|
|
47
|
-
sequential: project.sequential,
|
|
48
|
-
taskCount: allTasks.length,
|
|
49
|
-
remainingCount: remainingTasks.length,
|
|
50
|
-
tags: tagNames
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function findTask(idOrName) {
|
|
55
|
-
for (const task of flattenedTasks) {
|
|
56
|
-
if (task.id.primaryKey === idOrName || task.name === idOrName) {
|
|
57
|
-
return task;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
throw new Error("Task not found: " + idOrName);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function findProject(idOrName) {
|
|
64
|
-
for (const project of flattenedProjects) {
|
|
65
|
-
if (project.id.primaryKey === idOrName || project.name === idOrName) {
|
|
66
|
-
return project;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
throw new Error("Project not found: " + idOrName);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function getTagPath(tag) {
|
|
73
|
-
const parts = [tag.name];
|
|
74
|
-
let current = tag.parent;
|
|
75
|
-
while (current) {
|
|
76
|
-
parts.unshift(current.name);
|
|
77
|
-
current = current.parent;
|
|
78
|
-
}
|
|
79
|
-
return parts.join('/');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function findTag(idOrName) {
|
|
83
|
-
for (const tag of flattenedTags) {
|
|
84
|
-
if (tag.id.primaryKey === idOrName) {
|
|
85
|
-
return tag;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (idOrName.includes('/')) {
|
|
90
|
-
for (const tag of flattenedTags) {
|
|
91
|
-
if (getTagPath(tag) === idOrName) {
|
|
92
|
-
return tag;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
throw new Error("Tag not found: " + idOrName);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const matches = flattenedTags.filter(tag => tag.name === idOrName);
|
|
99
|
-
|
|
100
|
-
if (matches.length === 0) {
|
|
101
|
-
throw new Error("Tag not found: " + idOrName);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (matches.length > 1) {
|
|
105
|
-
const paths = matches.map(getTagPath);
|
|
106
|
-
throw new Error("Multiple tags found with name '" + idOrName + "'. Please use full path:\\n " + paths.join('\\n ') + "\\nOr use tag ID: " + matches.map(t => t.id.primaryKey).join(', '));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return matches[0];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function findByName(collection, name, typeName) {
|
|
113
|
-
for (const item of collection) {
|
|
114
|
-
if (item.name === name) {
|
|
115
|
-
return item;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
throw new Error(typeName + " not found: " + name);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function assignTags(target, tagNames) {
|
|
122
|
-
for (const tagName of tagNames) {
|
|
123
|
-
const tag = findTag(tagName);
|
|
124
|
-
target.addTag(tag);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function replaceTagsOn(target, tagNames) {
|
|
129
|
-
target.clearTags();
|
|
130
|
-
assignTags(target, tagNames);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function statusToString(status, StatusEnum) {
|
|
134
|
-
if (status === StatusEnum.Active) return 'active';
|
|
135
|
-
if (status === StatusEnum.OnHold) return 'on hold';
|
|
136
|
-
return 'dropped';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function stringToStatus(str, StatusEnum) {
|
|
140
|
-
if (str === 'active') return StatusEnum.Active;
|
|
141
|
-
if (str === 'on hold') return StatusEnum.OnHold;
|
|
142
|
-
return StatusEnum.Dropped;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const projectStatusToString = (status) => statusToString(status, Project.Status);
|
|
146
|
-
const tagStatusToString = (status) => statusToString(status, Tag.Status);
|
|
147
|
-
const stringToProjectStatus = (str) => stringToStatus(str, Project.Status);
|
|
148
|
-
const stringToTagStatus = (str) => stringToStatus(str, Tag.Status);
|
|
149
|
-
|
|
150
|
-
function computeTopItems(items, keyFn, topN = 5) {
|
|
151
|
-
return items
|
|
152
|
-
.sort((a, b) => b[keyFn] - a[keyFn])
|
|
153
|
-
.slice(0, topN)
|
|
154
|
-
.map(item => ({ name: item.name, [keyFn]: item[keyFn] }));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function computeAverage(total, count) {
|
|
158
|
-
return count > 0 ? Math.round((total / count) * 10) / 10 : 0;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function serializeTag(tag, activeOnly = false) {
|
|
162
|
-
const tasks = tag.tasks;
|
|
163
|
-
const remainingTasks = tag.remainingTasks;
|
|
164
|
-
const includedTasks = activeOnly ? remainingTasks : tasks;
|
|
165
|
-
|
|
166
|
-
const dates = [];
|
|
167
|
-
if (tag.added) dates.push(tag.added);
|
|
168
|
-
if (tag.modified) dates.push(tag.modified);
|
|
169
|
-
|
|
170
|
-
for (const task of includedTasks) {
|
|
171
|
-
if (task.added) dates.push(task.added);
|
|
172
|
-
if (task.modified) dates.push(task.modified);
|
|
173
|
-
if (!activeOnly && task.completionDate) dates.push(task.completionDate);
|
|
174
|
-
if (!activeOnly && task.effectiveCompletionDate) dates.push(task.effectiveCompletionDate);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const lastActivity = dates.length > 0
|
|
178
|
-
? dates.reduce((latest, current) => current > latest ? current : latest)
|
|
179
|
-
: null;
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
id: tag.id.primaryKey,
|
|
183
|
-
name: tag.name,
|
|
184
|
-
taskCount: includedTasks.length,
|
|
185
|
-
remainingTaskCount: remainingTasks.length,
|
|
186
|
-
added: tag.added ? tag.added.toISOString() : null,
|
|
187
|
-
modified: tag.modified ? tag.modified.toISOString() : null,
|
|
188
|
-
lastActivity: lastActivity ? lastActivity.toISOString() : null,
|
|
189
|
-
active: tag.active,
|
|
190
|
-
status: tagStatusToString(tag.status),
|
|
191
|
-
parent: tag.parent ? tag.parent.name : null,
|
|
192
|
-
children: tag.children.map(c => c.name),
|
|
193
|
-
allowsNextAction: tag.allowsNextAction
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
`;
|
|
197
|
-
async executeJXA(script, timeoutMs = 30000) {
|
|
198
|
-
const tmpFile = join(tmpdir(), `omnifocus-${Date.now()}.js`);
|
|
199
|
-
try {
|
|
200
|
-
await writeFile(tmpFile, script, 'utf-8');
|
|
201
|
-
const { stdout } = await execFileAsync('osascript', ['-l', 'JavaScript', tmpFile], {
|
|
202
|
-
timeout: timeoutMs,
|
|
203
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
204
|
-
});
|
|
205
|
-
return stdout.trim();
|
|
206
|
-
}
|
|
207
|
-
finally {
|
|
208
|
-
try {
|
|
209
|
-
await unlink(tmpFile);
|
|
210
|
-
}
|
|
211
|
-
catch { /* ignore cleanup errors */ }
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
escapeString(str) {
|
|
215
|
-
return str
|
|
216
|
-
.replace(/\\/g, '\\\\')
|
|
217
|
-
.replace(/"/g, '\\"')
|
|
218
|
-
.replace(/\n/g, '\\n')
|
|
219
|
-
.replace(/\r/g, '\\r')
|
|
220
|
-
.replace(/\t/g, '\\t');
|
|
221
|
-
}
|
|
222
|
-
wrapOmniScript(omniScript) {
|
|
223
|
-
return `
|
|
224
|
-
const app = Application('OmniFocus');
|
|
225
|
-
app.includeStandardAdditions = true;
|
|
226
|
-
const result = app.evaluateJavascript(${JSON.stringify(omniScript.trim())});
|
|
227
|
-
result;
|
|
228
|
-
`.trim();
|
|
229
|
-
}
|
|
230
|
-
buildTaskFilters(filters) {
|
|
231
|
-
const conditions = [];
|
|
232
|
-
if (!filters.includeCompleted) {
|
|
233
|
-
conditions.push('if (task.completed) continue;');
|
|
234
|
-
}
|
|
235
|
-
if (!filters.includeDropped) {
|
|
236
|
-
conditions.push('if (task.dropped) continue;');
|
|
237
|
-
}
|
|
238
|
-
if (filters.flagged) {
|
|
239
|
-
conditions.push('if (!task.flagged) continue;');
|
|
240
|
-
conditions.push('if (task.taskStatus !== Task.Status.Available) continue;');
|
|
241
|
-
}
|
|
242
|
-
if (filters.project) {
|
|
243
|
-
conditions.push(`
|
|
244
|
-
if (!task.containingProject || task.containingProject.name !== "${this.escapeString(filters.project)}") {
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
`);
|
|
248
|
-
}
|
|
249
|
-
if (filters.tag) {
|
|
250
|
-
conditions.push(`
|
|
251
|
-
if (!task.tags.some(t => t.name === "${this.escapeString(filters.tag)}")) {
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
`);
|
|
255
|
-
}
|
|
256
|
-
return conditions.join('\n ');
|
|
257
|
-
}
|
|
258
|
-
buildProjectFilters(filters) {
|
|
259
|
-
const conditions = [];
|
|
260
|
-
if (!filters.includeDropped) {
|
|
261
|
-
conditions.push('if (project.status === Project.Status.Dropped) continue;');
|
|
262
|
-
}
|
|
263
|
-
if (filters.status) {
|
|
264
|
-
const statusCheck = this.PROJECT_STATUS_MAP[filters.status];
|
|
265
|
-
conditions.push(`if (project.status !== Project.Status.${statusCheck}) continue;`);
|
|
266
|
-
}
|
|
267
|
-
if (filters.folder) {
|
|
268
|
-
conditions.push(`
|
|
269
|
-
if (!project.folder || project.folder.name !== "${this.escapeString(filters.folder)}") {
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
`);
|
|
273
|
-
}
|
|
274
|
-
return conditions.join('\n ');
|
|
275
|
-
}
|
|
276
|
-
buildTaskUpdates(options) {
|
|
277
|
-
const updates = [];
|
|
278
|
-
if (options.name !== undefined) {
|
|
279
|
-
updates.push(`task.name = "${this.escapeString(options.name)}";`);
|
|
280
|
-
}
|
|
281
|
-
if (options.note !== undefined) {
|
|
282
|
-
updates.push(`task.note = "${this.escapeString(options.note)}";`);
|
|
283
|
-
}
|
|
284
|
-
if (options.flagged !== undefined) {
|
|
285
|
-
updates.push(`task.flagged = ${options.flagged};`);
|
|
286
|
-
}
|
|
287
|
-
if (options.completed !== undefined) {
|
|
288
|
-
updates.push(`task.completed = ${options.completed};`);
|
|
289
|
-
}
|
|
290
|
-
if (options.estimatedMinutes !== undefined) {
|
|
291
|
-
updates.push(`task.estimatedMinutes = ${options.estimatedMinutes};`);
|
|
292
|
-
}
|
|
293
|
-
if (options.defer !== undefined) {
|
|
294
|
-
updates.push(options.defer
|
|
295
|
-
? `task.deferDate = new Date(${JSON.stringify(options.defer)});`
|
|
296
|
-
: 'task.deferDate = null;');
|
|
297
|
-
}
|
|
298
|
-
if (options.due !== undefined) {
|
|
299
|
-
updates.push(options.due
|
|
300
|
-
? `task.dueDate = new Date(${JSON.stringify(options.due)});`
|
|
301
|
-
: 'task.dueDate = null;');
|
|
302
|
-
}
|
|
303
|
-
if (options.project !== undefined && options.project) {
|
|
304
|
-
updates.push(`
|
|
305
|
-
const targetProject = findByName(flattenedProjects, "${this.escapeString(options.project)}", "Project");
|
|
306
|
-
moveTasks([task], targetProject);
|
|
307
|
-
`);
|
|
308
|
-
}
|
|
309
|
-
if (options.tags !== undefined) {
|
|
310
|
-
updates.push(`replaceTagsOn(task, ${JSON.stringify(options.tags)});`);
|
|
311
|
-
}
|
|
312
|
-
return updates.join('\n ');
|
|
313
|
-
}
|
|
314
|
-
buildTagUpdates(options) {
|
|
315
|
-
const updates = [];
|
|
316
|
-
if (options.name !== undefined) {
|
|
317
|
-
updates.push(`tag.name = "${this.escapeString(options.name)}";`);
|
|
318
|
-
}
|
|
319
|
-
if (options.status !== undefined) {
|
|
320
|
-
updates.push(`tag.status = stringToTagStatus("${options.status}");`);
|
|
321
|
-
}
|
|
322
|
-
return updates.join('\n ');
|
|
323
|
-
}
|
|
324
|
-
buildProjectUpdates(options) {
|
|
325
|
-
const updates = [];
|
|
326
|
-
if (options.name !== undefined) {
|
|
327
|
-
updates.push(`project.name = "${this.escapeString(options.name)}";`);
|
|
328
|
-
}
|
|
329
|
-
if (options.note !== undefined) {
|
|
330
|
-
updates.push(`project.note = "${this.escapeString(options.note)}";`);
|
|
331
|
-
}
|
|
332
|
-
if (options.sequential !== undefined) {
|
|
333
|
-
updates.push(`project.sequential = ${options.sequential};`);
|
|
334
|
-
}
|
|
335
|
-
if (options.status !== undefined) {
|
|
336
|
-
updates.push(`project.status = stringToProjectStatus("${options.status}");`);
|
|
337
|
-
}
|
|
338
|
-
if (options.folder !== undefined && options.folder) {
|
|
339
|
-
updates.push(`
|
|
340
|
-
const targetFolder = findByName(flattenedFolders, "${this.escapeString(options.folder)}", "Folder");
|
|
341
|
-
moveProjects([project], targetFolder);
|
|
342
|
-
`);
|
|
343
|
-
}
|
|
344
|
-
if (options.tags !== undefined) {
|
|
345
|
-
updates.push(`replaceTagsOn(project, ${JSON.stringify(options.tags)});`);
|
|
346
|
-
}
|
|
347
|
-
return updates.join('\n ');
|
|
348
|
-
}
|
|
349
|
-
async listTasks(filters = {}) {
|
|
350
|
-
const omniScript = `
|
|
351
|
-
${this.OMNI_HELPERS}
|
|
352
|
-
(() => {
|
|
353
|
-
const results = [];
|
|
354
|
-
for (const task of flattenedTasks) {
|
|
355
|
-
${this.buildTaskFilters(filters)}
|
|
356
|
-
results.push(serializeTask(task));
|
|
357
|
-
}
|
|
358
|
-
return JSON.stringify(results);
|
|
359
|
-
})();
|
|
360
|
-
`;
|
|
361
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
362
|
-
return JSON.parse(output);
|
|
363
|
-
}
|
|
364
|
-
async createTask(options) {
|
|
365
|
-
const omniScript = `
|
|
366
|
-
${this.OMNI_HELPERS}
|
|
367
|
-
(() => {
|
|
368
|
-
${options.project
|
|
369
|
-
? `const targetProject = findByName(flattenedProjects, "${this.escapeString(options.project)}", "Project");
|
|
370
|
-
const task = new Task("${this.escapeString(options.name)}", targetProject);`
|
|
371
|
-
: `const task = new Task("${this.escapeString(options.name)}");`}
|
|
372
|
-
|
|
373
|
-
${options.note ? `task.note = "${this.escapeString(options.note)}";` : ''}
|
|
374
|
-
${options.flagged ? 'task.flagged = true;' : ''}
|
|
375
|
-
${options.estimatedMinutes ? `task.estimatedMinutes = ${options.estimatedMinutes};` : ''}
|
|
376
|
-
${options.defer ? `task.deferDate = new Date(${JSON.stringify(options.defer)});` : ''}
|
|
377
|
-
${options.due ? `task.dueDate = new Date(${JSON.stringify(options.due)});` : ''}
|
|
378
|
-
${options.tags && options.tags.length > 0 ? `assignTags(task, ${JSON.stringify(options.tags)});` : ''}
|
|
379
|
-
|
|
380
|
-
return JSON.stringify(serializeTask(task));
|
|
381
|
-
})();
|
|
382
|
-
`;
|
|
383
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
384
|
-
return JSON.parse(output);
|
|
385
|
-
}
|
|
386
|
-
async updateTask(idOrName, options) {
|
|
387
|
-
const omniScript = `
|
|
388
|
-
${this.OMNI_HELPERS}
|
|
389
|
-
(() => {
|
|
390
|
-
const task = findTask("${this.escapeString(idOrName)}");
|
|
391
|
-
${this.buildTaskUpdates(options)}
|
|
392
|
-
return JSON.stringify(serializeTask(task));
|
|
393
|
-
})();
|
|
394
|
-
`;
|
|
395
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
396
|
-
return JSON.parse(output);
|
|
397
|
-
}
|
|
398
|
-
async deleteTask(idOrName) {
|
|
399
|
-
const omniScript = `
|
|
400
|
-
${this.OMNI_HELPERS}
|
|
401
|
-
(() => {
|
|
402
|
-
deleteObject(findTask("${this.escapeString(idOrName)}"));
|
|
403
|
-
})();
|
|
404
|
-
`;
|
|
405
|
-
await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
406
|
-
}
|
|
407
|
-
async listProjects(filters = {}) {
|
|
408
|
-
const omniScript = `
|
|
409
|
-
${this.OMNI_HELPERS}
|
|
410
|
-
(() => {
|
|
411
|
-
const results = [];
|
|
412
|
-
for (const project of flattenedProjects) {
|
|
413
|
-
${this.buildProjectFilters(filters)}
|
|
414
|
-
results.push(serializeProject(project));
|
|
415
|
-
}
|
|
416
|
-
return JSON.stringify(results);
|
|
417
|
-
})();
|
|
418
|
-
`;
|
|
419
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
420
|
-
return JSON.parse(output);
|
|
421
|
-
}
|
|
422
|
-
async createProject(options) {
|
|
423
|
-
const omniScript = `
|
|
424
|
-
${this.OMNI_HELPERS}
|
|
425
|
-
(() => {
|
|
426
|
-
${options.folder
|
|
427
|
-
? `const targetFolder = findByName(flattenedFolders, "${this.escapeString(options.folder)}", "Folder");
|
|
428
|
-
const project = new Project("${this.escapeString(options.name)}", targetFolder);`
|
|
429
|
-
: `const project = new Project("${this.escapeString(options.name)}");`}
|
|
430
|
-
|
|
431
|
-
${options.note ? `project.note = "${this.escapeString(options.note)}";` : ''}
|
|
432
|
-
${options.sequential !== undefined ? `project.sequential = ${options.sequential};` : ''}
|
|
433
|
-
${options.status ? `project.status = stringToProjectStatus("${options.status}");` : ''}
|
|
434
|
-
${options.tags && options.tags.length > 0 ? `assignTags(project, ${JSON.stringify(options.tags)});` : ''}
|
|
435
|
-
|
|
436
|
-
return JSON.stringify(serializeProject(project));
|
|
437
|
-
})();
|
|
438
|
-
`;
|
|
439
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
440
|
-
return JSON.parse(output);
|
|
441
|
-
}
|
|
442
|
-
async updateProject(idOrName, options) {
|
|
443
|
-
const omniScript = `
|
|
444
|
-
${this.OMNI_HELPERS}
|
|
445
|
-
(() => {
|
|
446
|
-
const project = findProject("${this.escapeString(idOrName)}");
|
|
447
|
-
${this.buildProjectUpdates(options)}
|
|
448
|
-
return JSON.stringify(serializeProject(project));
|
|
449
|
-
})();
|
|
450
|
-
`;
|
|
451
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
452
|
-
return JSON.parse(output);
|
|
453
|
-
}
|
|
454
|
-
async deleteProject(idOrName) {
|
|
455
|
-
const omniScript = `
|
|
456
|
-
${this.OMNI_HELPERS}
|
|
457
|
-
(() => {
|
|
458
|
-
deleteObject(findProject("${this.escapeString(idOrName)}"));
|
|
459
|
-
})();
|
|
460
|
-
`;
|
|
461
|
-
await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
462
|
-
}
|
|
463
|
-
async listInboxTasks() {
|
|
464
|
-
return this.getPerspectiveTasks('Inbox');
|
|
465
|
-
}
|
|
466
|
-
async getInboxCount() {
|
|
467
|
-
const tasks = await this.getPerspectiveTasks('Inbox');
|
|
468
|
-
return tasks.length;
|
|
469
|
-
}
|
|
470
|
-
async searchTasks(query) {
|
|
471
|
-
const omniScript = `
|
|
472
|
-
${this.OMNI_HELPERS}
|
|
473
|
-
(() => {
|
|
474
|
-
const results = [];
|
|
475
|
-
const searchQuery = "${this.escapeString(query)}".toLowerCase();
|
|
476
|
-
|
|
477
|
-
for (const task of flattenedTasks) {
|
|
478
|
-
if (task.completed) continue;
|
|
479
|
-
if (task.dropped) continue;
|
|
480
|
-
|
|
481
|
-
const name = task.name.toLowerCase();
|
|
482
|
-
const note = (task.note || '').toLowerCase();
|
|
483
|
-
|
|
484
|
-
if (name.includes(searchQuery) || note.includes(searchQuery)) {
|
|
485
|
-
results.push(serializeTask(task));
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return JSON.stringify(results);
|
|
490
|
-
})();
|
|
491
|
-
`;
|
|
492
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
493
|
-
return JSON.parse(output);
|
|
494
|
-
}
|
|
495
|
-
async getTask(idOrName) {
|
|
496
|
-
const omniScript = `
|
|
497
|
-
${this.OMNI_HELPERS}
|
|
498
|
-
(() => {
|
|
499
|
-
const task = findTask("${this.escapeString(idOrName)}");
|
|
500
|
-
return JSON.stringify(serializeTask(task));
|
|
501
|
-
})();
|
|
502
|
-
`;
|
|
503
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
504
|
-
return JSON.parse(output);
|
|
505
|
-
}
|
|
506
|
-
async getProject(idOrName) {
|
|
507
|
-
const omniScript = `
|
|
508
|
-
${this.OMNI_HELPERS}
|
|
509
|
-
(() => {
|
|
510
|
-
const project = findProject("${this.escapeString(idOrName)}");
|
|
511
|
-
return JSON.stringify(serializeProject(project));
|
|
512
|
-
})();
|
|
513
|
-
`;
|
|
514
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
515
|
-
return JSON.parse(output);
|
|
516
|
-
}
|
|
517
|
-
async listPerspectives() {
|
|
518
|
-
const omniScript = `
|
|
519
|
-
(() => {
|
|
520
|
-
const results = [];
|
|
521
|
-
|
|
522
|
-
const builtInNames = ['Inbox', 'Flagged', 'Forecast', 'Projects', 'Tags', 'Nearby', 'Review'];
|
|
523
|
-
for (const name of builtInNames) {
|
|
524
|
-
results.push({ id: name, name: name });
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
const customPerspectives = Perspective.Custom.all;
|
|
528
|
-
for (const perspective of customPerspectives) {
|
|
529
|
-
results.push({ id: perspective.name, name: perspective.name });
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
return JSON.stringify(results);
|
|
533
|
-
})();
|
|
534
|
-
`;
|
|
535
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
536
|
-
return JSON.parse(output);
|
|
537
|
-
}
|
|
538
|
-
async getPerspectiveTasks(perspectiveName) {
|
|
539
|
-
const omniScript = `
|
|
540
|
-
${this.OMNI_HELPERS}
|
|
541
|
-
(() => {
|
|
542
|
-
const doc = document;
|
|
543
|
-
const windows = doc.windows;
|
|
544
|
-
|
|
545
|
-
if (windows.length === 0) {
|
|
546
|
-
throw new Error("No OmniFocus window is open. Please open an OmniFocus window and try again.");
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const win = windows[0];
|
|
550
|
-
const perspectiveName = "${this.escapeString(perspectiveName)}";
|
|
551
|
-
|
|
552
|
-
const builtInPerspectives = {
|
|
553
|
-
'inbox': Perspective.BuiltIn.Inbox,
|
|
554
|
-
'flagged': Perspective.BuiltIn.Flagged,
|
|
555
|
-
'forecast': Perspective.BuiltIn.Forecast,
|
|
556
|
-
'projects': Perspective.BuiltIn.Projects,
|
|
557
|
-
'tags': Perspective.BuiltIn.Tags,
|
|
558
|
-
'nearby': Perspective.BuiltIn.Nearby,
|
|
559
|
-
'review': Perspective.BuiltIn.Review
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
const lowerName = perspectiveName.toLowerCase();
|
|
563
|
-
if (builtInPerspectives[lowerName]) {
|
|
564
|
-
win.perspective = builtInPerspectives[lowerName];
|
|
565
|
-
} else {
|
|
566
|
-
const customPerspective = Perspective.Custom.byName(perspectiveName);
|
|
567
|
-
if (customPerspective) {
|
|
568
|
-
win.perspective = customPerspective;
|
|
569
|
-
} else {
|
|
570
|
-
throw new Error("Perspective not found: " + perspectiveName);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const content = win.content;
|
|
575
|
-
if (!content) {
|
|
576
|
-
throw new Error("No content available in window");
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
const tasks = [];
|
|
580
|
-
content.rootNode.apply(node => {
|
|
581
|
-
const obj = node.object;
|
|
582
|
-
if (obj instanceof Task) {
|
|
583
|
-
tasks.push(serializeTask(obj));
|
|
584
|
-
}
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
return JSON.stringify(tasks);
|
|
588
|
-
})();
|
|
589
|
-
`;
|
|
590
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript), 60000);
|
|
591
|
-
return JSON.parse(output);
|
|
592
|
-
}
|
|
593
|
-
async listTags(options = {}) {
|
|
594
|
-
const omniScript = `
|
|
595
|
-
${this.OMNI_HELPERS}
|
|
596
|
-
(() => {
|
|
597
|
-
const results = [];
|
|
598
|
-
const now = new Date();
|
|
599
|
-
const activeOnly = ${!!options.activeOnly};
|
|
600
|
-
|
|
601
|
-
for (const tag of flattenedTags) {
|
|
602
|
-
const serialized = serializeTag(tag, activeOnly);
|
|
603
|
-
results.push(serialized);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
${options.unusedDays ? `
|
|
607
|
-
const cutoffDate = new Date(now.getTime() - (${options.unusedDays} * 24 * 60 * 60 * 1000));
|
|
608
|
-
const filtered = results.filter(tag => {
|
|
609
|
-
if (!tag.lastActivity) return true;
|
|
610
|
-
return new Date(tag.lastActivity) < cutoffDate;
|
|
611
|
-
});
|
|
612
|
-
return JSON.stringify(filtered);
|
|
613
|
-
` : 'return JSON.stringify(results);'}
|
|
614
|
-
})();
|
|
615
|
-
`;
|
|
616
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
617
|
-
const tags = JSON.parse(output);
|
|
618
|
-
return this.sortTags(tags, options.sortBy);
|
|
619
|
-
}
|
|
620
|
-
sortTags(tags, sortBy = 'name') {
|
|
621
|
-
const sortFns = {
|
|
622
|
-
usage: (a, b) => b.taskCount - a.taskCount,
|
|
623
|
-
activity: (a, b) => {
|
|
624
|
-
if (!a.lastActivity && !b.lastActivity)
|
|
625
|
-
return 0;
|
|
626
|
-
if (!a.lastActivity)
|
|
627
|
-
return 1;
|
|
628
|
-
if (!b.lastActivity)
|
|
629
|
-
return -1;
|
|
630
|
-
return new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime();
|
|
631
|
-
},
|
|
632
|
-
name: (a, b) => a.name.localeCompare(b.name)
|
|
633
|
-
};
|
|
634
|
-
return tags.sort(sortFns[sortBy] || sortFns.name);
|
|
635
|
-
}
|
|
636
|
-
async getTagStats() {
|
|
637
|
-
const omniScript = `
|
|
638
|
-
${this.OMNI_HELPERS}
|
|
639
|
-
(() => {
|
|
640
|
-
const allTags = [];
|
|
641
|
-
for (const tag of flattenedTags) {
|
|
642
|
-
allTags.push(serializeTag(tag));
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
const activeTags = allTags.filter(t => t.active);
|
|
646
|
-
const tagsWithTasks = allTags.filter(t => t.taskCount > 0);
|
|
647
|
-
const unusedTags = allTags.filter(t => t.taskCount === 0);
|
|
648
|
-
|
|
649
|
-
const totalTasks = tagsWithTasks.reduce((sum, t) => sum + t.taskCount, 0);
|
|
650
|
-
const avgTasksPerTag = computeAverage(totalTasks, tagsWithTasks.length);
|
|
651
|
-
|
|
652
|
-
const mostUsedTags = computeTopItems(allTags, 'taskCount');
|
|
653
|
-
const leastUsedTags = computeTopItems(
|
|
654
|
-
tagsWithTasks.map(t => ({ ...t, taskCount: -t.taskCount })),
|
|
655
|
-
'taskCount'
|
|
656
|
-
).map(t => ({ name: t.name, taskCount: -t.taskCount }));
|
|
657
|
-
|
|
658
|
-
const now = new Date();
|
|
659
|
-
const thirtyDaysAgo = new Date(now.getTime() - (30 * 24 * 60 * 60 * 1000));
|
|
660
|
-
const staleTags = allTags
|
|
661
|
-
.filter(t => t.lastActivity && new Date(t.lastActivity) < thirtyDaysAgo)
|
|
662
|
-
.map(t => ({
|
|
663
|
-
name: t.name,
|
|
664
|
-
daysSinceActivity: Math.floor((now - new Date(t.lastActivity)) / (24 * 60 * 60 * 1000))
|
|
665
|
-
}))
|
|
666
|
-
.sort((a, b) => b.daysSinceActivity - a.daysSinceActivity);
|
|
667
|
-
|
|
668
|
-
return JSON.stringify({
|
|
669
|
-
totalTags: allTags.length,
|
|
670
|
-
activeTags: activeTags.length,
|
|
671
|
-
tagsWithTasks: tagsWithTasks.length,
|
|
672
|
-
unusedTags: unusedTags.length,
|
|
673
|
-
avgTasksPerTag,
|
|
674
|
-
mostUsedTags,
|
|
675
|
-
leastUsedTags,
|
|
676
|
-
staleTags
|
|
677
|
-
});
|
|
678
|
-
})();
|
|
679
|
-
`;
|
|
680
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
681
|
-
return JSON.parse(output);
|
|
682
|
-
}
|
|
683
|
-
async createTag(options) {
|
|
684
|
-
const omniScript = `
|
|
685
|
-
${this.OMNI_HELPERS}
|
|
686
|
-
(() => {
|
|
687
|
-
${options.parent
|
|
688
|
-
? `const parentTag = findTag("${this.escapeString(options.parent)}");
|
|
689
|
-
const tag = new Tag("${this.escapeString(options.name)}", parentTag);`
|
|
690
|
-
: `const tag = new Tag("${this.escapeString(options.name)}", tags.beginning);`}
|
|
691
|
-
|
|
692
|
-
${options.status ? `tag.status = stringToTagStatus("${options.status}");` : ''}
|
|
693
|
-
|
|
694
|
-
return JSON.stringify(serializeTag(tag));
|
|
695
|
-
})();
|
|
696
|
-
`;
|
|
697
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
698
|
-
return JSON.parse(output);
|
|
699
|
-
}
|
|
700
|
-
async getTag(idOrName) {
|
|
701
|
-
const omniScript = `
|
|
702
|
-
${this.OMNI_HELPERS}
|
|
703
|
-
(() => {
|
|
704
|
-
const tag = findTag("${this.escapeString(idOrName)}");
|
|
705
|
-
return JSON.stringify(serializeTag(tag));
|
|
706
|
-
})();
|
|
707
|
-
`;
|
|
708
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
709
|
-
return JSON.parse(output);
|
|
710
|
-
}
|
|
711
|
-
async updateTag(idOrName, options) {
|
|
712
|
-
const omniScript = `
|
|
713
|
-
${this.OMNI_HELPERS}
|
|
714
|
-
(() => {
|
|
715
|
-
const tag = findTag("${this.escapeString(idOrName)}");
|
|
716
|
-
${this.buildTagUpdates(options)}
|
|
717
|
-
return JSON.stringify(serializeTag(tag));
|
|
718
|
-
})();
|
|
719
|
-
`;
|
|
720
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
721
|
-
return JSON.parse(output);
|
|
722
|
-
}
|
|
723
|
-
async deleteTag(idOrName) {
|
|
724
|
-
const omniScript = `
|
|
725
|
-
${this.OMNI_HELPERS}
|
|
726
|
-
(() => {
|
|
727
|
-
deleteObject(findTag("${this.escapeString(idOrName)}"));
|
|
728
|
-
})();
|
|
729
|
-
`;
|
|
730
|
-
await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
731
|
-
}
|
|
732
|
-
async getTaskStats() {
|
|
733
|
-
const omniScript = `
|
|
734
|
-
${this.OMNI_HELPERS}
|
|
735
|
-
(() => {
|
|
736
|
-
const allTasks = Array.from(flattenedTasks);
|
|
737
|
-
const now = new Date();
|
|
738
|
-
|
|
739
|
-
const activeTasks = allTasks.filter(t => !t.completed && !t.dropped);
|
|
740
|
-
const completedTasks = allTasks.filter(t => t.completed);
|
|
741
|
-
const flaggedTasks = activeTasks.filter(t => t.flagged);
|
|
742
|
-
const overdueActiveTasks = activeTasks.filter(t => t.dueDate && t.dueDate < now);
|
|
743
|
-
|
|
744
|
-
const tasksWithEstimates = allTasks.filter(t => t.estimatedMinutes && t.estimatedMinutes > 0);
|
|
745
|
-
const totalEstimatedMinutes = tasksWithEstimates.reduce((sum, t) => sum + (t.estimatedMinutes || 0), 0);
|
|
746
|
-
const avgEstimatedMinutes = tasksWithEstimates.length > 0
|
|
747
|
-
? Math.round(totalEstimatedMinutes / tasksWithEstimates.length)
|
|
748
|
-
: null;
|
|
749
|
-
|
|
750
|
-
const totalNonDropped = allTasks.filter(t => !t.dropped).length;
|
|
751
|
-
const completionRate = totalNonDropped > 0
|
|
752
|
-
? Math.round((completedTasks.length / totalNonDropped) * 100)
|
|
753
|
-
: 0;
|
|
754
|
-
|
|
755
|
-
const projectCounts = {};
|
|
756
|
-
for (const task of allTasks) {
|
|
757
|
-
if (task.dropped) continue;
|
|
758
|
-
const projectName = task.containingProject ? task.containingProject.name : 'Inbox';
|
|
759
|
-
projectCounts[projectName] = (projectCounts[projectName] || 0) + 1;
|
|
760
|
-
}
|
|
761
|
-
const tasksByProject = computeTopItems(
|
|
762
|
-
Object.entries(projectCounts).map(([name, count]) => ({ name, taskCount: count })),
|
|
763
|
-
'taskCount'
|
|
764
|
-
);
|
|
765
|
-
|
|
766
|
-
const tagCounts = {};
|
|
767
|
-
for (const task of allTasks) {
|
|
768
|
-
if (task.dropped) continue;
|
|
769
|
-
for (const tag of task.tags) {
|
|
770
|
-
tagCounts[tag.name] = (tagCounts[tag.name] || 0) + 1;
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
const tasksByTag = computeTopItems(
|
|
774
|
-
Object.entries(tagCounts).map(([name, count]) => ({ name, taskCount: count })),
|
|
775
|
-
'taskCount'
|
|
776
|
-
);
|
|
777
|
-
|
|
778
|
-
return JSON.stringify({
|
|
779
|
-
totalTasks: allTasks.length,
|
|
780
|
-
activeTasks: activeTasks.length,
|
|
781
|
-
completedTasks: completedTasks.length,
|
|
782
|
-
flaggedTasks: flaggedTasks.length,
|
|
783
|
-
overdueActiveTasks: overdueActiveTasks.length,
|
|
784
|
-
avgEstimatedMinutes,
|
|
785
|
-
tasksWithEstimates: tasksWithEstimates.length,
|
|
786
|
-
completionRate,
|
|
787
|
-
tasksByProject,
|
|
788
|
-
tasksByTag
|
|
789
|
-
});
|
|
790
|
-
})();
|
|
791
|
-
`;
|
|
792
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
793
|
-
return JSON.parse(output);
|
|
794
|
-
}
|
|
795
|
-
async getProjectStats() {
|
|
796
|
-
const omniScript = `
|
|
797
|
-
${this.OMNI_HELPERS}
|
|
798
|
-
(() => {
|
|
799
|
-
const allProjects = Array.from(flattenedProjects);
|
|
800
|
-
|
|
801
|
-
const activeProjects = allProjects.filter(p => p.status === Project.Status.Active);
|
|
802
|
-
const onHoldProjects = allProjects.filter(p => p.status === Project.Status.OnHold);
|
|
803
|
-
const droppedProjects = allProjects.filter(p => p.status === Project.Status.Dropped);
|
|
804
|
-
const sequentialProjects = allProjects.filter(p => p.sequential);
|
|
805
|
-
const parallelProjects = allProjects.filter(p => !p.sequential);
|
|
806
|
-
|
|
807
|
-
const nonDroppedProjects = allProjects.filter(p => p.status !== Project.Status.Dropped);
|
|
808
|
-
|
|
809
|
-
const totalTasks = nonDroppedProjects.reduce((sum, p) => sum + p.flattenedTasks.length, 0);
|
|
810
|
-
const totalRemaining = nonDroppedProjects.reduce((sum, p) => {
|
|
811
|
-
return sum + p.flattenedTasks.filter(t => !t.completed).length;
|
|
812
|
-
}, 0);
|
|
813
|
-
|
|
814
|
-
const avgTasksPerProject = computeAverage(totalTasks, nonDroppedProjects.length);
|
|
815
|
-
const avgRemainingPerProject = computeAverage(totalRemaining, nonDroppedProjects.length);
|
|
816
|
-
|
|
817
|
-
const completionRates = nonDroppedProjects
|
|
818
|
-
.filter(p => p.flattenedTasks.length > 0)
|
|
819
|
-
.map(p => {
|
|
820
|
-
const total = p.flattenedTasks.length;
|
|
821
|
-
const completed = p.flattenedTasks.filter(t => t.completed).length;
|
|
822
|
-
return (completed / total) * 100;
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
const avgCompletionRate = completionRates.length > 0
|
|
826
|
-
? Math.round(completionRates.reduce((sum, rate) => sum + rate, 0) / completionRates.length)
|
|
827
|
-
: 0;
|
|
828
|
-
|
|
829
|
-
const projectsWithMostTasks = computeTopItems(
|
|
830
|
-
nonDroppedProjects.map(p => ({ name: p.name, taskCount: p.flattenedTasks.length })),
|
|
831
|
-
'taskCount'
|
|
832
|
-
);
|
|
833
|
-
|
|
834
|
-
const projectsWithMostRemaining = computeTopItems(
|
|
835
|
-
nonDroppedProjects
|
|
836
|
-
.map(p => ({ name: p.name, remainingCount: p.flattenedTasks.filter(t => !t.completed).length }))
|
|
837
|
-
.filter(p => p.remainingCount > 0),
|
|
838
|
-
'remainingCount'
|
|
839
|
-
);
|
|
840
|
-
|
|
841
|
-
return JSON.stringify({
|
|
842
|
-
totalProjects: allProjects.length,
|
|
843
|
-
activeProjects: activeProjects.length,
|
|
844
|
-
onHoldProjects: onHoldProjects.length,
|
|
845
|
-
droppedProjects: droppedProjects.length,
|
|
846
|
-
sequentialProjects: sequentialProjects.length,
|
|
847
|
-
parallelProjects: parallelProjects.length,
|
|
848
|
-
avgTasksPerProject,
|
|
849
|
-
avgRemainingPerProject,
|
|
850
|
-
avgCompletionRate,
|
|
851
|
-
projectsWithMostTasks,
|
|
852
|
-
projectsWithMostRemaining
|
|
853
|
-
});
|
|
854
|
-
})();
|
|
855
|
-
`;
|
|
856
|
-
const output = await this.executeJXA(this.wrapOmniScript(omniScript));
|
|
857
|
-
return JSON.parse(output);
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
//# sourceMappingURL=omnifocus.js.map
|