npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gpambrozio/paseo-launchd-jobs

v0.5.0

Published

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

Readme

launchd-jobs

A Paseo plugin that schedules shell commands on your Mac through launchd. It adds a Scheduled jobs sidebar surface 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 surface shows what launchd knows about each job, the last twenty runs with their exit codes, and the tail of its log.

The Scheduled jobs surface: a job named "Update claude" in the list on the left, marked OK with
"At 06:20" and its last run; on the right its detail with Run now, Disable, Edit and Delete
buttons, the schedule, the command, the recent runs with duration and exit code, and the log
tail.

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 surface reads them back on every refresh.

Install

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

That is the shortest route on Paseo 0.9 or newer, which installs plugins straight from npm; add @<version> to pin one. 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.9.0 or newer, on the computer running the daemon and on whatever you view the surface 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 surface 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 surface shows the last twenty runs and the last 64 KB of the log. Both files live under $PASEO_HOME/plugins/launchd-jobs/ (~/.paseo/plugins/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 surface, 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: Scheduled jobs becomes Scheduled jobs (2 failing), and its icon changes to a crossed-out calendar. 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 surface is open, so the sidebar can be a minute behind a failure that has just happened. Opening the surface and pressing Refresh shows the truth immediately.

Removing

Deleting a job in the surface 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.
  • With more than one Paseo host, the sidebar row belongs to one of them. The app merges the entry contributed by every host into a single row and takes its label and icon from whichever host it lists first, so the count you see is that host's jobs, and a host still running an older version of this plugin pins the row to a plain "Scheduled jobs" with no count at all. Clicking the row still opens the host you were last on. Update the plugin everywhere, and read the count as belonging to one machine.
  • 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.