#!/usr/bin/env bash # 安装 / 卸载 / 查看技能治理的定时任务(macOS 用户级 launchd) # # 为什么用 launchd 而不是会话提醒:会话提醒随会话消失,节拍就断了; # launchd 是宿主级,跟仓库绑定,换机器重跑一次即可复现。 # # 用法: # ./scripts/install-schedulers.sh 安装(幂等,会先卸载再装) # ./scripts/install-schedulers.sh --status 看三个任务的加载状态 # ./scripts/install-schedulers.sh --run daily 立刻跑一次(调试用) # ./scripts/install-schedulers.sh --uninstall 卸载 set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" TASK="$ROOT/scripts/scheduled-task.sh" AGENTS="$HOME/Library/LaunchAgents" LOGDIR="$ROOT/reports/_scheduler" PREFIX="com.pipexerp.ai-proj-skill" UID_N="$(id -u)" JOBS="daily weekly monthly" SCHED_PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" if command -v node >/dev/null 2>&1; then SCHED_PATH="$(dirname "$(command -v node)"):$SCHED_PATH" fi log() { printf '%s\n' "$*"; } calendar_xml() { case "$1" in daily) printf ' Hour7\n Minute20' ;; weekly) printf ' Weekday1\n Hour10\n Minute0' ;; monthly) printf ' Day1\n Hour10\n Minute0' ;; esac } write_plist() { local job="$1" label="$PREFIX-$job" file="$AGENTS/$PREFIX-$job.plist" cal cal="$(calendar_xml "$job")" cat > "$file" < Label$label ProgramArguments /bin/bash $TASK $job WorkingDirectory$ROOT EnvironmentVariables HOME$HOME PATH$SCHED_PATH StandardOutPath$LOGDIR/$job.log StandardErrorPath$LOGDIR/$job.err.log RunAtLoad StartCalendarInterval $cal PLIST } unload_one() { local label="$PREFIX-$1" launchctl bootout "gui/$UID_N/$label" >/dev/null 2>&1 || launchctl unload "$AGENTS/$label.plist" >/dev/null 2>&1 } install() { mkdir -p "$AGENTS" "$LOGDIR" chmod +x "$TASK" 2>/dev/null local job label for job in $JOBS; do label="$PREFIX-$job" write_plist "$job" unload_one "$job" if launchctl bootstrap "gui/$UID_N" "$AGENTS/$label.plist" 2>/dev/null; then log "已加载 $label" elif launchctl load -w "$AGENTS/$label.plist" 2>/dev/null; then log "已加载(load) $label" else log "加载失败 $label(检查 plist: $AGENTS/$label.plist)" fi done log "" log "触发时间:daily 每天 07:20 / weekly 每周一 10:00 / monthly 每月 1 日 10:00(1/4/7/10 月附季度清单)" log "日志:${LOGDIR#$ROOT/}/.log" log "产出:reports/skill-health/、reports/evals/queue-*.md、reports/monthly/" } status() { local job label for job in $JOBS; do label="$PREFIX-$job" if launchctl print "gui/$UID_N/$label" >/dev/null 2>&1; then printf '%-8s 已加载 %s\n' "$job" \ "$(launchctl print "gui/$UID_N/$label" 2>/dev/null | grep -E 'state =|last exit code' | tr -d '\t' | tr '\n' ' ')" else printf '%-8s 未加载\n' "$job" fi done log "" log "日志目录:${LOGDIR#$ROOT/}" log "最近产出:" ls -1t "$ROOT/reports/skill-health"/daily-*.md "$ROOT/reports/evals"/queue-*.md "$ROOT/reports/monthly"/review-*.md 2>/dev/null | head -3 | sed "s#^$ROOT/# - #" } run_now() { local job="${1:-}" case "$job" in daily|weekly|monthly) ;; *) log "用法: $0 --run daily|weekly|monthly"; exit 2 ;; esac mkdir -p "$LOGDIR" local label="$PREFIX-$job" if launchctl print "gui/$UID_N/$label" >/dev/null 2>&1; then launchctl kickstart -k "gui/$UID_N/$label" && log "已触发 $job(看 ${LOGDIR#$ROOT/}/$job.log)" else log "任务未加载,直接前台执行" /bin/bash "$TASK" "$job" fi } uninstall() { local job label for job in $JOBS; do label="$PREFIX-$job" unload_one "$job" rm -f "$AGENTS/$label.plist" log "已卸载 $label" done } case "${1:-install}" in install|--install) install ;; --status|status) status ;; --run) run_now "${2:-}" ;; --uninstall|uninstall) uninstall ;; *) log "用法: $0 [install|--status|--run daily|weekly|monthly|--uninstall]"; exit 2 ;; esac