LaunchAgent Builder
Generate, validate, and install macOS LaunchAgent plist files for scheduled tasks and daemons.
When to Trigger
- User asks to create a LaunchAgent or LaunchDaemon
- User wants to schedule a script to run periodically
- User asks to automate a recurring task on macOS
- User wants to set up a background process or daemon
- User asks to run something "every N minutes/hours" or "daily at X"
Naming Convention
All agents use the com.berna. namespace:
com.berna.<descriptive-name>.plist
Examples:
com.berna.secret-scanner-daily.plistcom.berna.wispr-flow-watchdog.plistcom.berna.git-backup-hourly.plist
The Label value inside the plist MUST match the filename (minus .plist).
Template System
Gather these inputs from the user (or infer from context):
| Parameter | Required | Default |
|---|---|---|
| Script path | Yes | — |
| Schedule type | Yes | interval or calendar |
| Interval (seconds) | If interval | — |
| Calendar day | If calendar | — (0=Sun, 1=Mon, …, 6=Sat) |
| Calendar hour | If calendar | — |
| Calendar minute | If calendar | 0 |
| Log output path | No | ~/Reports/maintenance/<name>.log |
| KeepAlive | No | false (true for daemons) |
| RunAtLoad | No | true |
| Environment vars | No | — |
| WatchPaths | No | — (for file-triggered agents) |
Plist Generation
Interval-Based Template
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.berna.AGENT_NAME</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/path/to/script.sh</string>
</array>
<key>StartInterval</key>
<integer>SECONDS</integer>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/bbmisa/Reports/maintenance/AGENT_NAME.log</string>
<key>StandardErrorPath</key>
<string>/Users/bbmisa/Reports/maintenance/AGENT_NAME.log</string>
</dict>
</plist>
Calendar-Based Template
Replace StartInterval with:
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>HOUR</integer>
<key>Minute</key>
<integer>MINUTE</integer>
<!-- Optional: Day (0=Sunday), Weekday, Month -->
</dict>
For multiple schedules, use an array of dicts:
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<dict>
<key>Hour</key>
<integer>17</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</array>
Optional Sections
KeepAlive (for daemons):
<key>KeepAlive</key>
<true/>
Environment Variables:
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>CUSTOM_VAR</key>
<string>value</string>
</dict>
WatchPaths (file-triggered):
<key>WatchPaths</key>
<array>
<string>/path/to/watched/file</string>
</array>
Validation
Always validate the plist before writing:
plutil -lint /path/to/generated.plist
Expected output: <file>: OK
If validation fails, fix the XML and re-validate before proceeding.
Installation
Step 1: Write the plist
# Ensure target directory exists
mkdir -p ~/Library/LaunchAgents
# Write the plist (tool writes the file)
# Target: ~/Library/LaunchAgents/com.berna.<name>.plist
Step 2: Load the agent
launchctl load ~/Library/LaunchAgents/com.berna.<name>.plist
Step 3: Verify
launchctl list | grep com.berna.<name>
A successful load shows the agent with a PID (if running) or status code.
Management Commands
List all Berna agents
launchctl list | grep com.berna
Unload (stop) an agent
launchctl unload ~/Library/LaunchAgents/com.berna.<name>.plist
Reload (after editing plist)
launchctl unload ~/Library/LaunchAgents/com.berna.<name>.plist
launchctl load ~/Library/LaunchAgents/com.berna.<name>.plist
View logs
tail -f ~/Reports/maintenance/<name>.log
Check last run status
launchctl list | grep com.berna.<name>
# Column 1: PID (- if not running), Column 2: last exit status (0 = success)
Common Schedules Reference
| Schedule | StartInterval | StartCalendarInterval |
|---|---|---|
| Every 5 min | 300 | — |
| Every 30 min | 1800 | — |
| Every hour | 3600 | — |
| Daily at 9 AM | — | Hour: 9, Minute: 0 |
| Weekdays 8 AM | — | Weekday: 1-5, Hour: 8 |
| Weekly Sunday | — | Weekday: 0, Hour: 3 |
| Monthly 1st | — | Day: 1, Hour: 0 |
Safety Rules
- Always confirm before loading — show the generated plist and ask for approval
- Validate plist syntax — run
plutil -lintbefore writing to~/Library/LaunchAgents/ - Never overwrite existing agents — check if a plist with the same name exists first and ask before replacing
- Ensure log directory exists —
mkdir -p ~/Reports/maintenancebefore setting log paths - Use absolute paths only — LaunchAgents run outside shell context, so relative paths will fail
- Include PATH in EnvironmentVariables — scripts that use Homebrew tools need
/opt/homebrew/binin PATH - Test scripts independently first — recommend the user runs the script manually before scheduling it