macotron (core)
The top-level members of macotron: declare a plugin and its settings, add launcher commands, listen for host events, run code on a schedule, show simple dialogs, and write to the log.
const opts = macotron.plugin({
title: "Stand Up",
description: "Reminds you to stand up on weekdays.",
options: {
message: { type: "string", label: "Message", default: "Time to stand up" },
},
});
macotron.at("10:30", { weekdays: [1, 2, 3, 4, 5] }, () => {
macotron.notify.toast("Stand Up", opts.message);
});
macotron.command("Stand Up Now", "Show the reminder", () => {
macotron.notify.toast("Stand Up", opts.message);
});
Plugin metadata
plugin(metadata): Record<string, any>
Declares the plugin's title, description, permissions, and options for Settings → Plugins. Call it once, at the top of the file. It returns the resolved option values: the user's value from Settings when there is one, else the option's default. An option with neither is absent from the result.
| Field | Meaning |
|---|---|
title | Name shown in Settings. |
description | One line shown under the name. |
help | Longer explanation shown on the plugin's Settings page. |
permissions | macOS permissions the plugin needs: "accessibility", "inputMonitoring", "screenRecording", "camera", "microphone", "calendar", "automation" (controlling other apps with Apple Events), "menuBar", or "helper" (the background helper, installed from the plugin's Settings page). Macotron adds them to the permission list it asks the user for. |
options | An object of option definitions, keyed by the name you read back from the result. |
Every option takes label (a few words), an optional default, an optional help sentence shown under the field, and an optional required. While a required option is unset, Settings shows a "Needs setup" hint; commands still run. Text-like options also take a placeholder, the grey hint in an empty field.
| type | Settings shows | The plugin gets |
|---|---|---|
"string" | Text field | A string |
"text" | Multi-line text area | A string |
"boolean" | Checkbox | true or false |
"number" | Number field, or a slider when you give both min and max (and an optional step) | A number |
"keybinding" | Shortcut recorder | A combo such as "cmd+shift+c" |
"dropdown" | Pop-up menu. Needs choices: [{ value, label }] | The chosen value |
"password" | Secure field. The secret goes in the Keychain | The secret, or "" when unset. default is ignored |
"file" | File picker | An absolute path |
"directory" | Folder picker | An absolute path |
"calendars" | Checkbox list of the user's calendars | An array of calendar titles. Empty means every calendar |
Never put a real secret in plugin source or settings.json. Use a "password" option, so the user types it into Settings and it stays in the Keychain.
module(metadata): Record<string, any>
Deprecated. The same function as plugin().
requirePermissions(list: string[]): void
Deprecated. Adds permissions to the list Macotron asks for. Pass permissions to plugin() instead, which also shows them on the plugin's Settings page.
checks(rows: Array<{ title, ok, message? }>): void
Replaces this plugin's Checks list in Settings → Plugins. A row with ok: false puts an orange warning on the plugin. Rows without a title are dropped, and a row without ok counts as failed. Pass [] to clear the list. Call it again whenever the status changes.
macotron.checks([{ title: "File index", ok: available, message }]);
settings.open(): void
Opens Settings → Plugins on this plugin, for example from a menu row that says "Settings…".
version: { app, api, modules }
api is the semver of the plugin API, such as "1.5.0". modules maps each native namespace (window, panel, and so on) to an integer version that goes up when that namespace changes. app is the Macotron version, such as "0.7.7", or "dev" in a build without one.
config(options: Record<string, any>): void
Legacy. Replaces Macotron's in-memory copy of settings.json until the next reload. Plugins should not call it. Use plugin() options.
Commands
command(name, description, handler, opts?): void
Adds a command to the launcher. name is the title the user searches for and description is the line under it. handler receives one object with the argument values, and may be async.
opts.id is a stable id for the command. The default is "{filename}/{name}". Set it when users will bind a shortcut to the command, in Settings → Plugins or with Command-K in the launcher, so the binding survives a rename. Registering a second command with the same id replaces the first.
opts.arguments is a list of fields the launcher asks for before it runs the command. Each one has a name (the key in the handler's object), a type, and optional placeholder (defaults to the name), required, and default.
| type | Value the handler gets |
|---|---|
"text" | The trimmed string. An empty field gives default, or "". |
"number" | A number. An empty field gives default, or the key is absent. |
"dropdown" | The chosen value. Needs choices: [{ title, value }] (label works in place of title). A dropdown with no choices is dropped. |
An unknown type becomes "text". The launcher refuses to run while a required field is empty, a number field holds text, or a dropdown value is not one of its choices, and it says which field is wrong. When a shortcut runs a command, the defaults fill the arguments. If a required argument has no default, the launcher opens and asks for it.
macotron.command("Generate Lorem Ipsum", "Copy placeholder text to the clipboard", (args) => {
macotron.clipboard.set(generate(args.count, args.unit));
}, {
id: "lorem-ipsum",
arguments: [
{ name: "count", type: "number", placeholder: "Count", default: 3 },
{
name: "unit",
type: "dropdown",
default: "paragraphs",
choices: [
{ title: "Words", value: "words" },
{ title: "Paragraphs", value: "paragraphs" },
],
},
],
});
For a global hotkey that runs without the launcher, use macotron.keyboard.on.
Events
on(event: string, callback: (payload?) => void): void
Calls callback each time the host emits event. Most events pass one payload object; see the page for each event. The name is any string, so a plugin can listen for an event that this host does not emit yet. Macotron removes every listener when plugins reload.
off(event: string, callback): void
Removes a listener. Pass the same function you passed to on, so keep a reference to it. A listener can remove itself while it runs.
The host emits these events:
| Event | Fires when | Documented on |
|---|---|---|
app:activated, app:launched, app:terminated | An app comes to the front, starts, or quits | macotron.app |
audio:changed | Audio devices change | macotron.audio |
clipboard:changed | The pasteboard changes | macotron.clipboard |
display:changed | Displays change | macotron.display |
hid:input | A HID device you listen to sends a report | macotron.hid |
media:changed | Now Playing changes | macotron.media |
menubar:appearance | The menu bar switches between light and dark | macotron.menubar |
panel:closed | A panel closes | macotron.panel |
space:changed | The current desktop changes | macotron.spaces |
system:idle, system:active | The user goes idle, and comes back | macotron.idle |
system:sleep, system:wake, system:lock, system:unlock | The Mac sleeps or wakes, or the screen locks or unlocks | macotron.power |
udp:message | A UDP socket receives a packet | macotron.udp |
usb:changed | A USB device connects or disconnects | macotron.usb |
wifi:changed | The Wi-Fi network changes | macotron.network |
window:created, window:focused | A window opens, or gets focus | macotron.window |
keyboard:<binding> | A plugin hotkey is pressed. keyboard.on subscribes for you | macotron.keyboard |
url:<scheme>:<host> | A URL arrives for a scheme a plugin handles. url.on subscribes for you | macotron.url |
system:sleep handlers run while the Mac is already going to sleep. Keep them short and do not wait for a promise. system:active means that idle time ended, not that the Mac woke.
Timers
Every callback, whether from a timer, an event, or a command, has 5 seconds to return. Macotron interrupts a callback that runs longer and logs the error.
every(msOrDuration: number | string, callback): () => void
Runs callback again and again, and returns a function that stops it. Calling the stop function from inside the callback is safe.
A number is an interval in milliseconds and must be greater than 0. A string is a count and a unit: "500ms", "30s", "5m", "1h", "2d". Long unit names such as "10 minutes" and "2 hours" also work. A string that does not parse throws a TypeError.
Minute counts that divide 60 and hour counts that divide 24 line up with the clock: "15m" fires at :00, :15, :30, and :45, not 15 minutes after the call. Day counts run from the time of the call. When the Mac wakes or the clock changes, a string schedule that missed its time runs once at once, then continues on schedule.
const stop = macotron.every("5m", refresh);
// later
stop();
at(time: string, callback): () => void
at(time: string, opts: { weekdays?: number[] }, callback): () => void
Runs callback every day at a clock time, in local time. time is 24-hour "07:30" or "07:30:15", or 12-hour "7am", "7:30pm". weekdays limits it to some days, numbered like Date.getDay(): 0 is Sunday and 6 is Saturday. An invalid time or weekday throws a TypeError. If the Mac was asleep at that time, the callback runs once when it wakes.
Returns a stop function, like every.
sleep(ms: number): Promise<void>
A promise that resolves after ms milliseconds. Use it with await in an async function.
For one-shot delays, setTimeout and setInterval also work. See Globals.
Dialogs
These show a standard macOS alert and block every plugin until the user answers. Each one is the same function as the global with the same name.
The 5-second callback limit keeps counting while a dialog is open. If the user takes longer to answer, Macotron interrupts the code that comes after the dialog. For anything slower than a quick yes or no, use a panel.
alert(message?: any): void
Shows message with an OK button.
confirm(message?: any): boolean
Shows message with OK and Cancel. Returns true for OK.
prompt(message?: any, defaultValue?: string): string | null
Shows message with a text field, filled with defaultValue. Returns the text for OK, or null for Cancel.
Logging
log(...args: any[]): void
The same as console.log. Joins the arguments with spaces and writes one line to the macOS log, at info level, under subsystem io.statico.macotron and category plugin. Macotron puts the plugin's file name in front of the line. Objects go through JSON.stringify, so an Error prints as {}; log e.message instead.
Info lines only show when you ask for them:
// In Terminal:
// log stream --level info --style compact --predicate 'subsystem == "io.statico.macotron" AND category == "plugin"'
See also
- Globals for
console, timers, andlocalStorage. - macotron.keyboard for global hotkeys.
- macotron.notify for toasts and notifications that do not block.
- macotron.launcher for launcher results beyond commands.