{"id":"launchd-jobs","repo":"gpambrozio/paseo-plugins","url":"https://github.com/gpambrozio/paseo-plugins/tree/main/launchd-jobs","package":"@gpambrozio/paseo-launchd-jobs","npm":{"package":"@gpambrozio/paseo-launchd-jobs","version":"0.6.0","integrity":"sha512-kxv5of01I3OkR6h6qnJpqEhdo4erUzTdZC/YnZki61RSRTAH9+Ssjaa1WPJGmVvK7SbQs4O/PJY9aaLb6eFeig==","publishedAt":"2026-10-07T17:06:47.121Z","downloadsLast30Days":1267},"name":"launchd-jobs","description":"Paseo plugin: schedule shell commands on your Mac through launchd, from a sidebar panel","categories":["automation"],"platforms":["macos"],"caveats":["Requires an active login session — won't run from a headless SSH-only daemon"],"images":["https://raw.githubusercontent.com/gpambrozio/paseo-plugins/09509ce55ab06758623262e6aa71522ade6fda0c/launchd-jobs/docs/screenshot.png"],"themes":[],"health":{"manifestValid":true,"hasReadme":true,"hasLicense":true,"hasTests":true,"hasTypecheckScript":true,"updatedRecently":true},"scannedAt":"2026-10-15T12:30:11.755Z","addedAt":"2026-09-06T12:28:49+01:00","npmSecurity":{"status":"passed","blockingFindings":0,"advisoryFindings":0,"scannedAt":"2026-10-15T12:14:24.405Z","version":"0.6.0","integrity":"sha512-kxv5of01I3OkR6h6qnJpqEhdo4erUzTdZC/YnZki61RSRTAH9+Ssjaa1WPJGmVvK7SbQs4O/PJY9aaLb6eFeig=="},"path":"launchd-jobs","version":"0.6.0","security":{"status":"passed","blockingFindings":0,"advisoryFindings":0,"scannedAt":"2026-10-15T12:14:24.405Z","commit":"09509ce55ab06758623262e6aa71522ade6fda0c"},"license":"MIT","paseoVersionRequirement":">=0.11.0","descriptionNodes":[{"type":"text","text":"Paseo plugin: schedule shell commands on your Mac through launchd, from a sidebar panel"}],"caveatNodes":[[{"type":"text","text":"Requires an active login session — won't run from a headless SSH-only daemon"}]],"manifest":{"id":"launchd-jobs","description":"Schedule shell commands on your Mac through launchd","requirements":{"paseo":">=0.11.0"}},"repoMeta":{"stars":14,"defaultBranch":"main","pushedAt":"2026-10-09T21:12:13Z"},"owner":{"login":"gpambrozio","avatarUrl":"https://avatars.githubusercontent.com/u/672135?v=4"},"installNotesHtml":"<pre><code class=\"language-bash\">paseo plugin install npm:@gpambrozio/paseo-launchd-jobs\n</code></pre>\n<p>That is the shortest route on <strong>Paseo 0.11 or newer</strong>, which installs plugins straight from npm; add\n<code>@&#x3C;version></code> to pin one. On Paseo 0.9 or 0.10, install the last release that supports them:\n<code>paseo plugin install npm:@gpambrozio/paseo-launchd-jobs@0.5.0</code>. On 0.8, install the last release that supported it from this repository instead:</p>\n<pre><code class=\"language-bash\">paseo plugin add gpambrozio/paseo-plugins --path launchd-jobs\n</code></pre>\n<p>Or from a clone:</p>\n<pre><code class=\"language-bash\">cd paseo-plugins/launchd-jobs\nnpm install\nnpm run typecheck\npaseo plugin install \"$PWD\"\n</code></pre>\n<p>Requires <strong>Paseo 0.11.0 or newer</strong>, on the computer running the daemon and on whatever you view the\njobs on. Both check the version themselves, so an older one reports the plugin as incompatible\nrather than half working.</p>\n<p>The <strong>daemon</strong> has to be running on macOS, because that is where the agents live and where the\ncommands run. A daemon on Linux loads the plugin and the screen says so instead of showing a list.\nThe Paseo app can be anywhere.</p>","limitationsNotesHtml":"<ul>\n<li>macOS only. A daemon on Linux would need a systemd user timer backend, which does not exist here.</li>\n<li>The daemon has to run inside your login session — which it does when the desktop app starts it.\nA daemon started over SSH with no GUI session may not be able to load agents into it.</li>\n<li>Six-field cron expressions with seconds are refused; launchd has no seconds field. Use a fixed\ninterval instead.</li>\n<li>The sidebar's failing count only knows about runs that finished badly. A job launchd has quietly\nstopped scheduling — one shown as <strong>Not loaded</strong> — is not counted, because finding that out means\nasking launchd about every job once a minute.</li>\n<li>What launchd reports — whether a job is running, its process id, its spawn count — comes from\n<code>launchctl print</code>, whose output is prose. If a macOS release rewords it, those facts go blank\nuntil the parser is updated; the run history and the log do not depend on it.</li>\n</ul>","readmeText":"# launchd-jobs\n\nA Paseo plugin that schedules shell commands on your Mac through launchd. It adds a **Scheduled\njobs** screen where you create a job — a name, a command, a working directory, and either\na cron expression or a fixed interval — and the plugin writes it as a LaunchAgent. From then on\nlaunchd runs it, whether or not Paseo is open. The screen shows what launchd knows about each job,\nthe last twenty runs with their exit codes, and the tail of its log.\n\n![The Scheduled jobs screen: a job named \"Update claude\" in the list on the left, marked OK with\n\"At 06:20\" and its last run; on the right its detail with Run now, Disable, Edit and Delete\nbuttons, the schedule, the command, the recent runs with duration and exit code, and the log\ntail.](docs/screenshot.png)\n\nIt is a front for launchd, not a scheduler of its own. Nothing here has to stay running: the plugin\nholds no timers and keeps no job database. The plists in `~/Library/LaunchAgents` are the source of\ntruth, and the screen reads them back on every refresh.\n\n## Install\n\n```bash\npaseo plugin install npm:@gpambrozio/paseo-launchd-jobs\n```\n\nThat is the shortest route on **Paseo 0.11 or newer**, which installs plugins straight from npm; add\n`@<version>` to pin one. On Paseo 0.9 or 0.10, install the last release that supports them:\n`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:\n\n```bash\npaseo plugin add gpambrozio/paseo-plugins --path launchd-jobs\n```\n\nOr from a clone:\n\n```bash\ncd paseo-plugins/launchd-jobs\nnpm install\nnpm run typecheck\npaseo plugin install \"$PWD\"\n```\n\nRequires **Paseo 0.11.0 or newer**, on the computer running the daemon and on whatever you view the\njobs on. Both check the version themselves, so an older one reports the plugin as incompatible\nrather than half working.\n\nThe **daemon** has to be running on macOS, because that is where the agents live and where the\ncommands run. A daemon on Linux loads the plugin and the screen says so instead of showing a list.\nThe Paseo app can be anywhere.\n\n## What a job is\n\nEach job is one file, `~/Library/LaunchAgents/com.paseo-plugins.launchd-jobs.<name>.plist`. The\nplugin only ever lists, writes, and removes files whose label starts with that prefix, so your other\nLaunchAgents are never touched. A plist you write by hand under the prefix shows up too.\n\nThe command runs through `/bin/zsh -lc`, so it can be anything you would type at a prompt, and\nwith the PATH your interactive shell reports — launchd's own PATH is `/usr/bin:/bin:/usr/sbin:/sbin`\nand nothing from Homebrew or a version manager, which is the usual reason a LaunchAgent works in\nthe terminal and fails when scheduled. The PATH is captured when the job is saved, so if you change\nit, save the job again.\n\nThe working directory is optional and may start with `~/`. Without one, launchd starts the command\nin your home directory.\n\n### Schedules\n\n**Cron expression**, five fields: minute, hour, day of month, month, weekday. Lists (`1,15`),\nranges (`9-17`), steps (`*/15`, `9-17/2`), and three-letter names (`mon`, `jan`) all work. The form\nshows the schedule in words as you type — \"At 09:00 on Mon–Fri\" — and how many launchd entries it\nbecomes, because launchd has no expression language: every combination of the values you list is\nits own entry. `0,30 9-17 * * 1-5` is ninety of them. The cap is a thousand.\n\nOne difference from cron: when both a day of month and a weekday are given, cron fires when\n*either* matches, launchd only when *both* do. The preview says \"(both must match)\" so it is not a\nsurprise.\n\n**Fixed interval**: every N seconds, minutes, or hours, counted from when the job was loaded.\n\n### When it fires, and when it does not\n\n- **Paseo closed**: the job runs. launchd does not know Paseo exists.\n- **Mac asleep at the scheduled time**: launchd runs the job once when the Mac wakes. Several\n  missed times collapse into one run.\n- **Mac off, or you logged out**: the run is missed. LaunchAgents belong to your login session.\n- **Job disabled**: launchd remembers that across reboots until you enable it again.\n\n## Runs and logs\n\nEvery run appends a line to the log with a timestamp, the command's combined output, and the exit\ncode, and one record to a history file — when it started, how long it took, and how it ended. The\nscreen shows the last twenty runs and the last 64 KB of the log. Both files live under\n`$PASEO_HOME/plugin-data/launchd-jobs/` (`~/.paseo/plugin-data/launchd-jobs/` by default). A log is rotated\nonce it passes 1 MB, and the history keeps its last two hundred runs.\n\n**Follow** shows the log as the job writes it, instead of only when you press **Refresh log** —\nuseful for a job you have just started, or one that takes a while. Press it again to stop;\nfollowing also ends when you switch jobs or leave the screen, so nothing is left running.\n\nFollowing needs somewhere to run, and borrows one of your open workspaces: while it lasts it shows\nup in that workspace's terminal list as `launchd: <job name>`, and it goes away when you stop. With\nno workspace open there is nowhere to run it, and the button says so — **Refresh log** still works.\n\n**Run now** asks launchd to start the job immediately. It does not change the schedule.\n\n## When a job fails\n\nA job that fails at three in the morning is worth knowing about without going looking, so the\nsidebar says so: a red count appears beside **Scheduled jobs**, and its icon changes to a\ncrossed-out calendar. Tap the count for the list of failing jobs, and tap one to open it straight on\nits last run. A job counts as failing when its most recent run ended with a non-zero exit code, which\nis the same thing the list shows as **Failed (exit N)**.\n\nOpening the job clears it from the count — that is all it takes, there is nothing to dismiss. The\nalert comes back if the job fails again, because what is remembered is the run you saw, not the\njob. A job whose next run succeeds drops out of the count on its own.\n\nThe count is checked about once a minute, whether or not the jobs screen is open, so the sidebar can\nbe a minute behind a failure that has just happened. Opening the screen and pressing **Refresh** shows\nthe truth immediately.\n\nWith more than one Paseo host, the row shows the jobs of the host you are looking at.\n\n## Removing\n\nDeleting a job in the screen unloads it, deletes the plist, and deletes its log and history.\nRemoving the *plugin* does not: the agents stay installed and keep running, because they are\nlaunchd's, not Paseo's. Delete the jobs first, or delete the plists by hand and run\n`launchctl bootout gui/$UID/<label>` for each.\n\n## Limitations\n\n- macOS only. A daemon on Linux would need a systemd user timer backend, which does not exist here.\n- The daemon has to run inside your login session — which it does when the desktop app starts it.\n  A daemon started over SSH with no GUI session may not be able to load agents into it.\n- Six-field cron expressions with seconds are refused; launchd has no seconds field. Use a fixed\n  interval instead.\n- The sidebar's failing count only knows about runs that finished badly. A job launchd has quietly\n  stopped scheduling — one shown as **Not loaded** — is not counted, because finding that out means\n  asking launchd about every job once a minute.\n- What launchd reports — whether a job is running, its process id, its spawn count — comes from\n  `launchctl print`, whose output is prose. If a macOS release rewords it, those facts go blank\n  until the parser is updated; the run history and the log do not depend on it.\n"}