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.plist
  • com.berna.wispr-flow-watchdog.plist
  • com.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):

ParameterRequiredDefault
Script pathYes
Schedule typeYesinterval or calendar
Interval (seconds)If interval
Calendar dayIf calendar— (0=Sun, 1=Mon, …, 6=Sat)
Calendar hourIf calendar
Calendar minuteIf calendar0
Log output pathNo~/Reports/maintenance/<name>.log
KeepAliveNofalse (true for daemons)
RunAtLoadNotrue
Environment varsNo
WatchPathsNo— (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

ScheduleStartIntervalStartCalendarInterval
Every 5 min300
Every 30 min1800
Every hour3600
Daily at 9 AMHour: 9, Minute: 0
Weekdays 8 AMWeekday: 1-5, Hour: 8
Weekly SundayWeekday: 0, Hour: 3
Monthly 1stDay: 1, Hour: 0

Safety Rules

  1. Always confirm before loading — show the generated plist and ask for approval
  2. Validate plist syntax — run plutil -lint before writing to ~/Library/LaunchAgents/
  3. Never overwrite existing agents — check if a plist with the same name exists first and ask before replacing
  4. Ensure log directory existsmkdir -p ~/Reports/maintenance before setting log paths
  5. Use absolute paths only — LaunchAgents run outside shell context, so relative paths will fail
  6. Include PATH in EnvironmentVariables — scripts that use Homebrew tools need /opt/homebrew/bin in PATH
  7. Test scripts independently first — recommend the user runs the script manually before scheduling it