paseo.cafe
All plugins

launchd-jobs

by gpambrozio

Paseo plugin: schedule shell commands on your Mac through launchd, from a sidebar panel

v0.6.0 Requires Paseo >=0.11.0

Gallery

Install

npmjs · Paseo 0.9+

paseo plugin add npm:@gpambrozio/paseo-launchd-jobs@0.6.0

GitHub · Paseo 0.8 fallback

paseo plugin add gpambrozio/paseo-plugins --ref 09509ce55ab06758623262e6aa71522ade6fda0c --path launchd-jobs

Git security status and fallback installation are pinned to commit 09509ce55ab0.

From the plugin's README

paseo plugin install npm:@gpambrozio/paseo-launchd-jobs

That is the shortest route on Paseo 0.11 or newer, which installs plugins straight from npm; add @<version> to pin one. On Paseo 0.9 or 0.10, install the last release that supports them: paseo plugin install npm:@gpambrozio/paseo-launchd-jobs@0.5.0. On 0.8, install the last release that supported it from this repository instead:

paseo plugin add gpambrozio/paseo-plugins --path launchd-jobs

Or from a clone:

cd paseo-plugins/launchd-jobs
npm install
npm run typecheck
paseo plugin install "$PWD"

Requires Paseo 0.11.0 or newer, on the computer running the daemon and on whatever you view the jobs on. Both check the version themselves, so an older one reports the plugin as incompatible rather than half working.

The daemon has to be running on macOS, because that is where the agents live and where the commands run. A daemon on Linux loads the plugin and the screen says so instead of showing a list. The Paseo app can be anywhere.

README

launchd-jobs

A Paseo plugin that schedules shell commands on your Mac through launchd. It adds a Scheduled jobs screen where you create a job — a name, a command, a working directory, and either a cron expression or a fixed interval — and the plugin writes it as a LaunchAgent. From then on launchd runs it, whether or not Paseo is open. The screen shows what launchd knows about each job, the last twenty runs with their exit codes, and the tail of its log.

It is a front for launchd, not a scheduler of its own. Nothing here has to stay running: the plugin holds no timers and keeps no job database. The plists in ~/Library/LaunchAgents are the source of truth, and the screen reads them back on every refresh.

Install

paseo plugin install npm:@gpambrozio/paseo-launchd-jobs

That is the shortest route on Paseo 0.11 or newer, which installs plugins straight from npm; add @<version> to pin one. On Paseo 0.9 or 0.10, install the last release that supports them: paseo plugin install npm:@gpambrozio/paseo-launchd-jobs@0.5.0. On 0.8, install the last release that supported it from this repository instead:

paseo plugin add gpambrozio/paseo-plugins --path launchd-jobs

Or from a clone:

cd paseo-plugins/launchd-jobs
npm install
npm run typecheck
paseo plugin install "$PWD"

Requires Paseo 0.11.0 or newer, on the computer running the daemon and on whatever you view the jobs on. Both check the version themselves, so an older one reports the plugin as incompatible rather than half working.

The daemon has to be running on macOS, because that is where the agents live and where the commands run. A daemon on Linux loads the plugin and the screen says so instead of showing a list. The Paseo app can be anywhere.

What a job is

Each job is one file, ~/Library/LaunchAgents/com.paseo-plugins.launchd-jobs.<name>.plist. The plugin only ever lists, writes, and removes files whose label starts with that prefix, so your other LaunchAgents are never touched. A plist you write by hand under the prefix shows up too.

The command runs through /bin/zsh -lc, so it can be anything you would type at a prompt, and with the PATH your interactive shell reports — launchd's own PATH is /usr/bin:/bin:/usr/sbin:/sbin and nothing from Homebrew or a version manager, which is the usual reason a LaunchAgent works in the terminal and fails when scheduled. The PATH is captured when the job is saved, so if you change it, save the job again.

The working directory is optional and may start with ~/. Without one, launchd starts the command in your home directory.

Schedules

Cron expression, five fields: minute, hour, day of month, month, weekday. Lists (1,15), ranges (9-17), steps (*/15, 9-17/2), and three-letter names (mon, jan) all work. The form shows the schedule in words as you type — "At 09:00 on Mon–Fri" — and how many launchd entries it becomes, because launchd has no expression language: every combination of the values you list is its own entry. 0,30 9-17 * * 1-5 is ninety of them. The cap is a thousand.

One difference from cron: when both a day of month and a weekday are given, cron fires when either matches, launchd only when both do. The preview says "(both must match)" so it is not a surprise.

Fixed interval: every N seconds, minutes, or hours, counted from when the job was loaded.

When it fires, and when it does not

  • Paseo closed: the job runs. launchd does not know Paseo exists.
  • Mac asleep at the scheduled time: launchd runs the job once when the Mac wakes. Several missed times collapse into one run.
  • Mac off, or you logged out: the run is missed. LaunchAgents belong to your login session.
  • Job disabled: launchd remembers that across reboots until you enable it again.

Runs and logs

Every run appends a line to the log with a timestamp, the command's combined output, and the exit code, and one record to a history file — when it started, how long it took, and how it ended. The screen shows the last twenty runs and the last 64 KB of the log. Both files live under $PASEO_HOME/plugin-data/launchd-jobs/ (~/.paseo/plugin-data/launchd-jobs/ by default). A log is rotated once it passes 1 MB, and the history keeps its last two hundred runs.

Follow shows the log as the job writes it, instead of only when you press Refresh log — useful for a job you have just started, or one that takes a while. Press it again to stop; following also ends when you switch jobs or leave the screen, so nothing is left running.

Following needs somewhere to run, and borrows one of your open workspaces: while it lasts it shows up in that workspace's terminal list as launchd: <job name>, and it goes away when you stop. With no workspace open there is nowhere to run it, and the button says so — Refresh log still works.

Run now asks launchd to start the job immediately. It does not change the schedule.

When a job fails

A job that fails at three in the morning is worth knowing about without going looking, so the sidebar says so: a red count appears beside Scheduled jobs, and its icon changes to a crossed-out calendar. Tap the count for the list of failing jobs, and tap one to open it straight on its last run. A job counts as failing when its most recent run ended with a non-zero exit code, which is the same thing the list shows as Failed (exit N).

Opening the job clears it from the count — that is all it takes, there is nothing to dismiss. The alert comes back if the job fails again, because what is remembered is the run you saw, not the job. A job whose next run succeeds drops out of the count on its own.

The count is checked about once a minute, whether or not the jobs screen is open, so the sidebar can be a minute behind a failure that has just happened. Opening the screen and pressing Refresh shows the truth immediately.

With more than one Paseo host, the row shows the jobs of the host you are looking at.

Removing

Deleting a job in the screen unloads it, deletes the plist, and deletes its log and history. Removing the plugin does not: the agents stay installed and keep running, because they are launchd's, not Paseo's. Delete the jobs first, or delete the plists by hand and run launchctl bootout gui/$UID/<label> for each.

Limitations

  • macOS only. A daemon on Linux would need a systemd user timer backend, which does not exist here.
  • The daemon has to run inside your login session — which it does when the desktop app starts it. A daemon started over SSH with no GUI session may not be able to load agents into it.
  • Six-field cron expressions with seconds are refused; launchd has no seconds field. Use a fixed interval instead.
  • The sidebar's failing count only knows about runs that finished badly. A job launchd has quietly stopped scheduling — one shown as Not loaded — is not counted, because finding that out means asking launchd about every job once a minute.
  • What launchd reports — whether a job is running, its process id, its spawn count — comes from launchctl print, whose output is prose. If a macOS release rewords it, those facts go blank until the parser is updated; the run history and the log do not depend on it.

Scanned 15 Oct 2026, 12:30 UTC from gpambrozio/paseo-plugins/launchd-jobs.