Ocelot File Manager

All pages

Process plugins

A process plugin is an executable the app starts and talks to over stdin and stdout. Write it in any language; the reference plugin, Checksum, is about ninety lines of Swift in PluginSDK/checksum.

Build and install

The manifest says "runtime": "process" and names the executable in entry. The executable sits beside the manifest, with the executable bit set:

cd PluginSDK/checksum
swift build -c release
mkdir -p ~/Library/Application\ Support/OcelotFM/Plugins/checksum
cp manifest.json .build/release/checksum ~/Library/Application\ Support/OcelotFM/Plugins/checksum/

Then Settings ▸ Plugins ▸ Reload All, and enable it.

Lifecycle

The worker starts at the first command or column call, inside the plugin's sandbox (see Permissions), with the plugin's folder as its read-only working directory and a minimal environment: a fixed PATH, HOME, USER, LANG, LC_ALL, SHELL, TMPDIR (a folder of its own) and OCELOT_PLUGIN_DATA (its persistent folder). It stays running between calls and is stopped when the plugin is disabled, reloaded, or the app quits.

A call that runs out of time (10 seconds for a command, 2 for a column batch) ends the worker: SIGTERM to its whole process group, SIGKILL a second later for whatever ignored it. That is a strike; so is a crash, a worker that exits on its own, a malformed message, or a failed initialize. Three strikes disable the plugin. The next call after a stop starts a fresh worker.

Framing

Every message, in both directions, is a header and a JSON body:

Content-Length: 61\r\n
\r\n
{"id":1,"method":"execute","params":{...}}

A message nested deeper than 32 levels, or a stream of headers that never ends in a body, is malformed. Lines on stderr go to the plugin's log.

Messages from the app

MethodParamsReply
initialize{hostAPIVersion, grantedPermissions}{name, version}; the handshake must succeed before anything else
execute{command, context} with context = {selectionPaths, directoryPath, paneID, surface}null
columnValues{column, paths, context}an object from path to value (see Columns)
shutdown—none: a notification; exit

A reply is {"id": …, "result": …} or {"id": …, "result": null, "error": "message"}. Reply to a method you do not know with an error rather than silence.

Messages to the app

Notifications, without an id, sent at any time:

MethodParamsNotes
notify{title, body}only while an execute is running; dropped with a log line otherwise. Cut at 4 096 characters, at most 10 a minute
log{level, message}the plugin's log; 100 lines a second at most
invalidateColumn{column, paths}the values of that column changed; paths optional, see Columns

Storage and tools

The worker writes only to $OCELOT_PLUGIN_DATA and $TMPDIR; /tmp is not writable, so avoid tools that hard-code it. Outbound network needs "network.client" in the manifest and "hostAPI": {"min": "1.3"}. A process plugin runs its own tools itself, inside the same sandbox; the process.spawn permission is for JavaScript plugins.