library
docs blog github
← all skills

create-system-routine

Create time-based system routines (cron/launchd) for scripts or commands. Use this for OS-level scheduling, NOT for Jazz Workflows.

source: skills/create-system-routine/SKILL.md · instruction-only

jazz skill add create-system-routine raw SKILL.md

SKILL.md

This is an instruction-only artifact. It does not grant tools or permissions, but its instructions can influence an agent. Read it for prompt injection or unsafe guidance before installing it.

---
name: create-system-routine
description: Create time-based system routines (cron/launchd) for scripts or commands. Use this for OS-level scheduling, NOT for Jazz Workflows.
---

# Create System Routine

## When to use this skill

Use this skill when the user wants to create or manage time-based routines (scheduled tasks) at the **OS level** (e.g., running a shell script, a binary, or a specific command).

- **Linux** → use `cron` (`crontab -e`, system/user crontabs)
- **macOS** → use `launchd` (LaunchAgents / LaunchDaemons with plist files)
- **Windows** → this skill should **not** create routines; instead, explain that Windows Task Scheduler must be configured manually.

## Security

A scheduled routine runs unattended, in the invoking user's context, with no terminal and no chance to answer a prompt.

- Never inline secrets (API keys, tokens, passwords) in the cron line or plist. Load them from a file with restricted permissions (e.g. `chmod 600`) inside the wrapped script, or from the environment.
- Prefer wrapping the real work in a script and calling that script. This keeps `PATH`, environment, and error handling in one auditable place.
- The routines this skill creates are **user-level** and run with the user's privileges. Do not schedule commands that escalate privileges or mutate system state without explicit confirmation.
- Always explain what will change and keep a backup (e.g. `crontab -l > /tmp/crontab.bak`) before any destructive edit.

## Workflow

1. **Detect the operating system**

   Use standard OS detection via shell commands:

   ```bash
   uname_s=$(uname -s 2>/dev/null || echo unknown)
   case "$uname_s" in
     Linux)   os=linux ;;
     Darwin)  os=macos ;;
     MINGW*|MSYS*|CYGWIN*|Windows_NT) os=windows ;;
     *)       os=unknown ;;
   esac
   ```

   - If `os=linux` → follow the **Linux / cron** workflow.
   - If `os=macos` → follow the **macOS / launchd** workflow.
   - If `os=windows` or `os=unknown` → explain that this skill does not create routines on this OS and suggest Windows Task Scheduler or another platform-specific mechanism.

2. **Gather routine parameters from the user (questionnaire if needed)**

   **Do not create the cron entry or plist until you have enough information.** If the user's request is vague (e.g. "schedule something", "run a script daily") or missing any of the items below, guide them through a short questionnaire instead of guessing.

   **You have enough info when you know:**

   - **Command or script** to run (absolute path preferred). If they only have a relative path or "a script", ask for the full path or help them resolve it. Verify the target is executable (see step 3.1 / 4.1).
   - **Schedule**: When should it run?
     - For Linux: cron expression (e.g. `0 8 * * *`)
     - For macOS: time components for `StartCalendarInterval` (e.g. Hour=8, Minute=0)
   - **Behavior if the machine is off/asleep at the scheduled time**: Explain that neither cron nor launchd will run while the machine is off; offer "run at scheduled time and on boot/login" if they want catch-up (see step 3.4 / 4.6).
   - **Output destination**: Where should logs go? Recommend a dedicated log file (e.g. `~/.local/share/jazz-routines/<name>.log`) so failures are diagnosable.

   **How to run the questionnaire:**

   - Ask **one or a few questions at a time**; don't dump a long list.
   - If they said "daily" or "every hour", convert to cron/launchd and confirm.
   - Once you have command/script, schedule, and a log destination (and optionally catch-up behavior), proceed to create the routine.

3. **Linux / cron workflow** (`os=linux`)

   1. **Validate tools and target**:

      Check for `crontab` availability:

      ```bash
      command -v crontab >/dev/null 2>&1
      ```

      If missing, explain that cron is not available and suggest `systemd` timers or another scheduler; this skill does not configure those directly.

      Verify the target script exists and is executable:

      ```bash
      test -x "<COMMAND>" || echo "warn: not executable"
      ```

      If it is a script, confirm it has a valid shebang (`#!/bin/sh`, `#!/usr/bin/env bash`, etc.). If it is merely not `+x`, offer to `chmod +x` it (with confirmation) or wrap it in a shell invocation.

   2. **Prepare the cron entry**:

      Capture both stdout and stderr to a log file so the job is diagnosable:

      ```bash
      "<CRON_SCHEDULE> <COMMAND> >> <LOG_FILE> 2>&1 # created-by-jazz-create-system-routine"
      ```

      Use absolute paths for both the command and any scripts. If environment variables are needed, recommend wrapping logic in a shell script and calling that script from cron.

   3. **Install the cron entry (user crontab)**:

      Safely append the new entry to the user's crontab, keeping a backup first:

      ```bash
      backup=$(mktemp)
      crontab -l 2>/dev/null >"$backup" || true
      tmp_cron=$(mktemp)
      crontab -l 2>/dev/null >"$tmp_cron" || true
      printf '%s\n' "<CRON_LINE>" >>"$tmp_cron"
      crontab "$tmp_cron"
      rm -f "$tmp_cron"
      ```

      - Preserve existing cron entries.
      - Tag the entry with a comment so it can be identified/removed later.

   4. **Verify the install**:

      ```bash
      crontab -l | grep 'created-by-jazz-create-system-routine'
      ```

      Confirm the new line is present before reporting success.

   5. **(Optional) Boot or login catch-up**:

      For "run at 8am or next boot" semantics, use a guard script that only runs once per day after a given time, then call it from **both** a time-based cron (e.g. `0 8 * * *`) and an `@reboot` entry:

      ```bash
      #!/bin/sh
      # <GUARD_SCRIPT> — runs the real job at most once per day after <HOUR>:00.
      marker="$HOME/.cache/jazz-routines/<name>.lastrun"
      now_epoch=$(date +%s)
      today_midnight=$(( now_epoch - (now_epoch % 86400) + <HOUR> * 3600 ))
      last=$(cat "$marker" 2>/dev/null || echo 0)
      last_day=$(( last - (last % 86400) ))
      if [ "$last_day" -lt "$(( today_midnight - last % 86400 ))" ] && [ "$now_epoch" -ge "$today_midnight" ]; then
        <REAL_COMMAND> >> <LOG_FILE> 2>&1
        date +%s > "$marker"
      fi
      ```

      (Adapt the once-per-day check to taste; the principle is a timestamp marker gating execution.)

4. **macOS / launchd workflow** (`os=macos`)

   1. **Validate the target**:

      Confirm the target script exists and is executable:

      ```bash
      test -x "<COMMAND>" || echo "warn: not executable"
      ```

      If it is a script, confirm it has a valid shebang. Offer to `chmod +x` it (with confirmation) or invoke it via a shell in `ProgramArguments`.

   2. **Choose target: LaunchAgent vs LaunchDaemon**:

      - Prefer **LaunchAgent** for user-level routines:
        - Location: `~/Library/LaunchAgents`
        - Runs in the context of the logged-in user.
      - Use LaunchDaemon only for system-wide services (not typical for personal routines).

   3. **Create LaunchAgents directory if needed**:

      Ensure `~/Library/LaunchAgents` exists before writing plist files.

   4. **Define a unique label**:

      Use a reverse-DNS-style label matching the skill name, e.g. `com.jazz.create-system-routine.<name>`.

   5. **Write a plist file**

      Create a plist at:

      ```text
      ~/Library/LaunchAgents/com.jazz.create-system-routine.<name>.plist
      ```

      Example template:

      ```xml
      <?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.jazz.create-system-routine.<name></string>

          <key>ProgramArguments</key>
          <array>
            <string>/bin/zsh</string>
            <string>-lc</string>
            <string>/absolute/path/to/script.sh</string>
          </array>

          <key>WorkingDirectory</key>
          <string>/absolute/working/directory</string>

          <key>StandardOutPath</key>
          <string>/absolute/path/to/<name>.out.log</string>

          <key>StandardErrorPath</key>
          <string>/absolute/path/to/<name>.err.log</string>

          <key>StartCalendarInterval</key>
          <dict>
            <key>Hour</key>
            <integer>8</integer>
            <key>Minute</key>
            <integer>0</integer>
          </dict>

          <!-- Optional: run when the agent is loaded (e.g. on login) -->
          <key>RunAtLoad</key>
          <true/>
        </dict>
      </plist>
      ```

      Notes:
      - Use absolute paths.
      - If you need environment variables or PATH modifications, do them inside the script.
      - `StandardOutPath` / `StandardErrorPath` make failures diagnosable; without them output is discarded.

   6. **Load (or reload) the LaunchAgent**

      Use the modern `bootstrap`/`bootout` verbs (the older `load`/`unload` verbs are deprecated on current macOS). `gui/$UID` targets the current user's session:

      ```bash
      uid=$(id -u)
      launchctl bootout "gui/$uid/com.jazz.create-system-routine.<name>" 2>/dev/null || true
      launchctl bootstrap "gui/$uid" ~/Library/LaunchAgents/com.jazz.create-system-routine.<name>.plist
      launchctl kickstart "gui/$uid/com.jazz.create-system-routine.<name>"
      ```

   7. **Verify the install**:

      ```bash
      launchctl list | grep 'com.jazz.create-system-routine.<name>'
      ```

      Confirm the job appears (the status column will populate after first run).

   8. **Explain off/asleep behavior**

      Clarify that:
      - If the Mac is off or fully asleep at the scheduled time, launchd will not run the job at that moment.
      - For "run at 8am or next login" behavior, combine `StartCalendarInterval` with `RunAtLoad` and implement a small guard in the script that only runs once per day after a certain time (see step 3.5 for a guard pattern).

5. **Windows / unsupported workflow** (`os=windows` or `os=unknown`)

   - Do **not** attempt to create or modify tasks.
   - Respond with clear guidance:
     - Explain that this skill does not manage Windows Task Scheduler.
     - Suggest the user configure a task manually in Task Scheduler or use another tool.

6. **Removal / update of routines**

   When the user wants to remove or update routines created by this skill:

   - **Linux (cron)**:
     - Back up first: `crontab -l > /tmp/crontab.bak`.
     - Read the current crontab: `crontab -l`.
     - Filter out or edit lines with `# created-by-jazz-create-system-routine`.
     - Write back the modified crontab.

   - **macOS (launchd)**:
     - Unload the LaunchAgent via `launchctl bootout "gui/$uid/com.jazz.create-system-routine.<name>"`.
     - Edit or remove the corresponding plist file under `~/Library/LaunchAgents`.

   Always explain what will be changed and keep backups where practical before destructive edits.