skill-gate / gate (pull_request) Waiting to run
- 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 增加定时任务一节与边界说明
144 lines
4.9 KiB
Bash
Executable File
144 lines
4.9 KiB
Bash
Executable File
#!/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
|