launchagent-builder
Generates, validates, and installs macOS LaunchAgent plist files for scheduling recurring tasks and daemons. Triggered when a user asks to create a LaunchAgent, schedule a script, automate a recurring macOS task, or set up a background daemon. Uses the com.berna namespace and validates plist syntax before writing. Always confirms before loading.
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