Files
ai-proj-helper/scripts/install-schedulers.sh
T
johnQ c6ef6820e4
skill-gate / gate (pull_request) Waiting to run
feat(governance): 节拍落到 launchd + 提案入口
- scripts/scheduled-task.sh:daily/weekly/monthly 只做确定性动作,需模型判断的部分生成 queue 文件
- scripts/install-schedulers.sh:装成用户级 launchd(daily 07:20 / weekly 周一 10:00 / monthly 1 日 10:00)
- .gitea issue/PR 模板:新技能提案走同一套 SCOPE 门槛问题
- README 一键部署 2 步 → 4 步;MAINTENANCE 增加定时任务一节与边界说明
2026-11-01 05:50:38 +08:00

144 lines
4.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 ' <key>Hour</key><integer>7</integer>\n <key>Minute</key><integer>20</integer>' ;;
weekly) printf ' <key>Weekday</key><integer>1</integer>\n <key>Hour</key><integer>10</integer>\n <key>Minute</key><integer>0</integer>' ;;
monthly) printf ' <key>Day</key><integer>1</integer>\n <key>Hour</key><integer>10</integer>\n <key>Minute</key><integer>0</integer>' ;;
esac
}
write_plist() {
local job="$1" label="$PREFIX-$job" file="$AGENTS/$PREFIX-$job.plist" cal
cal="$(calendar_xml "$job")"
cat > "$file" <<PLIST
<?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>$label</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>$TASK</string>
<string>$job</string>
</array>
<key>WorkingDirectory</key><string>$ROOT</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key><string>$HOME</string>
<key>PATH</key><string>$SCHED_PATH</string>
</dict>
<key>StandardOutPath</key><string>$LOGDIR/$job.log</string>
<key>StandardErrorPath</key><string>$LOGDIR/$job.err.log</string>
<key>RunAtLoad</key><false/>
<key>StartCalendarInterval</key>
<dict>
$cal
</dict>
</dict>
</plist>
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/}/<job>.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