How to get going with javascript and Omnifocus?

Hi, I’m trying some basic scripting of OmniFocus on MacOS with javascript. I’m using an actual script, not the console. It appears that the context is hugely different between the two.
However, I’m not clear why my simple basic scripting does not work. Can someone kindly tell me why this script throws an error:

#!/usr/bin/osascript -l JavaScript

var OmniFocus = new Application("/Applications/OmniFocus.app");
OmniFocus.activate();
var inbox = OmniFocus.inbox;
console.log("inbox length: "+ inbox.length)
console.log("Flattened projects length: "+ OmniFocus.flattenedProjects.length)
rcook@MacBook-Pro-2021 (wealthyvault (master)): bin/OmniLink.js 
inbox length: 0
bin/OmniLink.js: execution error: Error: Error: Can't get object. (-1728)

Is there a javascript tutorial for Omnifocus out there? I just don’t understand why my shell script cannot do the very basic stuff that I can do easily from the Console.
Thanks for any help

Two completely different programming interfaces, and two quite different JavaScript interpreter instances. One system-wide (osascript), one embedded in the Omni app itself.

The shell can only access the JavaScript for Automation interface, the objects of which are shared by the AppleScript interface (two different language options for osascript).

The console in Omni apps gives access to a completely different instance of a JavaScript interpreter (private to the particular app, unlike the system-wide osascript -l JavaScript instance) and a completely different programming interface, with different objects, methods and properties.

Current development focuses entirely on the JSContext to which the console (and Omni Automation plugins) give access.

The old osascript interface is quite different, and in legacy / sunset mode.

(Because it uses a JSContext external to the application, it also has more interface traffic overheads, and executes more slowly)

2 Likes

FWIW if you wanted to do that kind of thing in Script Editor (JS makes use of $ and various other things which don’t play well in the shell, even with a unix heredoc), you could trying writing something like the snippet below.

You will notice that it involves a different object structure (API) from the currently maintained in-app OmniJS JSContext that you can explore with the Console and omnijs plugins.

In Script Editor, with the language selector at top-left set to JavaScript:

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    const OmniFocus = Application("OmniFocus.app");
    const
        doc = OmniFocus.defaultDocument,
        inbox = doc.inboxTasks;

     OmniFocus.activate();

    console.log(`inbox length: ${inbox.length}`);
    console.log(
        `Flattened projects length: ${doc.flattenedProjects.length}`
    );
})();

And if you really wanted to do it in the shell, you would need

  • a heredoc to cope with a multi-line script,
  • escaped dollars and backticks, and
  • a return expression in lieu of console.log

Expand disclosure triangle to view shell script
osascript -l JavaScript <<JXA_END 2>/dev/null
(() => {
    "use strict";

    const
        OmniFocus = Application("OmniFocus.app"),
        doc = OmniFocus.defaultDocument,
        inbox = doc.inboxTasks;

    OmniFocus.activate();

    return (
        [
            \`inbox length: \${inbox.length}\`,
            \`Flattened projects length: \${doc.flattenedProjects.length}\`
        ].join("\n")
    );
})();
JXA_END
3 Likes

I assume you’ve already seen https://omni-automation.com/? That’s the best I have found.

The omni-automation site is certainly the fullest resource on the API, but it’s not very pedagogic, or even very self-confident or reliable, on JavaScript.

Probably worth looking at the first 5 chapters of “Eloquent JavaScript” for a more solid and graduated introduction to the basics, including things like:

  • why not to use var
  • why to use === and not ==
  • preferring [] and {} to new Array() and new Object()
  • etc etc

Eloquent JavaScript

https://eloquentjavascript.net/

2 Likes

That’s what I was hoping for! Thanks! Yes, I really want to do it in the shell. Thanks for your helpful answer.

Yes but it’s all fluff and no business. Sample code is few and far between for how to do this. I need examples personally, not treatises and class diagrams. I am not good enough at this to infer from the class heirarchy the exact syntax to do what I want in a fairly new programming language. Looking for sample code really.

Yes, I really want to do it in the shell.

OK, well in that case, omni-automation is no help (it deals with the omniJS interface – the object model available through the Console and plugins) what you need for shell scripts is the listing of the osascript objects and methods which you can get from Script Editor.

  • In Script Editor set the language selector at top left to JavaScript
  • In the Script Editor menu system: File > Open Dictionary...
  • Choose OmniFocus.app
1 Like

Probably best just to describe a few cases here, and we can sketch out sample code for you.

1 Like

Thanks! You have helped me tremendously with just your short snippet. If I may, how did you come to know how to do those simple things? Did you attend a secret meeting? Intuition from years of experience? Do you work for Omni? :-) Wondering how I might more easily improve. Thanks

I have looked in the Dictionary but it’s difficult for me to map that onto javascript as it’s all in Applescript. Mapping that to javascript has been a head-scratcher so far for me. Any advice there?

Not if you have selected JavaScript at top left in Script Editor – the library browser then shows the JavaScript forms.


And there’s a language selector in the library browser too:

1 Like

Just a cycle of experimentation – picking up concepts while needing particular things, and finding out how to get them.

1 Like

Thanks, that’s very cool about Script Editor.

On the contrary, the site has an extensive collection of sample code for all kinds of uses of the API, most illustrated and commented around either a ready-to-install plug-in or start-to-finish code. Look in the hamburger menu and drill down into every section — the site navigation is a bit unusual but everything’s there. This is then adaptable to your workflow or preferred JS coding style.

( Only for the omniJS API – not for the osascript API that the OP needs for the shell )

1 Like

I see. I assumed Omni Automation was being referred to, because the ‘treatises and class diagrams’ (which I found quite useful!) are in that section, not in the minimal osascript coverage.

Just an idea, which I haven’t tried myself: instead of using the legacy osascript API, would the following work?

  1. shell script calls a ‘shortcut’ (in macOS Monterey)
  2. the shortcut contains an OmniFocus ‘Omni Automation Script’ action (which runs a script)
  3. that action contains the desired script.

Advantage is that the new API is being used. The script in the shortcut action doesn’t even have to be hard-coded, it can be retrieved from somewhere else beforehand in the shortcut. I see also that the shortcuts run shell command can pass input to the shortcut with the –input-path flag, so could the entire script to execute be passed?

I think we would have to understand more about the context in which the OP needs to use a shell script.

It’s not technically impossible to launch omniJS code from the shell – you can pass a code string to the osascript interface’s .evaluateJavascript method:

const evalOmniJSWithArgs = (appName, f, ...args) =>
    Application(appName).evaluateJavascript(
        `(${f})(${args.map(JSON.stringify)})`
    );

but adding a third layer of string within string (omniJS string within osascript string within shell script string) risks wasting time fiddling with the layered escaping of special characters.

4 Likes

The simplest example of such a shell script – the main function is evaluated by the shell in the osascript JSContext, which converts the omniJS function to a string and evaluates that code string in the omniJS JSContext, getting back a string result which, in turn, it passes back to the shell.

(Not that all dollars and backticks have to be escaped to stop the shell from applying its default interpretations to those characters)

osascript -l JavaScript <<JXA_END 2>/dev/null
(() => {
    "use strict";

    // omniJS :: () -> IO String
    const omniJS = () => [
        \`Inbox length: \${inbox.length}\`,
        \`Flattened projects length: \${flattenedProjects.length}\`
    ].join("\n");

    // main :: IO ()
    const main = () =>
        evalOmniJSWithArgs("OmniFocus", omniJS);

    // --------------------- GENERIC ---------------------

    const evalOmniJSWithArgs = (appName, f, ...args) =>
        Application(appName).evaluateJavascript(
            \`(\${f})(\${args.map(JSON.stringify)})\`
        );

    return main();
})();
JXA_END
1 Like