Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
65 lines
3.3 KiB
Bash
Executable File
65 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Control the Claude side of the agent loop on the dev box (no cron/systemd here).
|
|
# agentctl.sh install copy scripts to ~/.local/share/jyotisha-agent/bin (run from a checkout)
|
|
# agentctl.sh start start the background loop (tick every 30 min, nightly at 23:30 Beijing)
|
|
# agentctl.sh stop stop the loop
|
|
# agentctl.sh status loop pid, last heartbeat, last log lines
|
|
# agentctl.sh once run one tick now in the foreground
|
|
# agentctl.sh pause|resume create/remove the STOP file (ticks exit immediately while paused)
|
|
# The loop runs from the installed copy, because ticks check out and clean the
|
|
# loop worktree and would otherwise rewrite the scripts under themselves.
|
|
set -euo pipefail
|
|
|
|
BIN="$HOME/.local/share/jyotisha-agent/bin"
|
|
CONF_DIR="$HOME/.config/jyotisha-agent"
|
|
STATE_DIR="${AGENT_LOOP_STATE_DIR:-$HOME/.local/state/jyotisha-agent}"
|
|
PID_FILE="$STATE_DIR/loop.pid"
|
|
HEARTBEAT="$STATE_DIR/heartbeat"
|
|
TICK_SECONDS="${AGENT_LOOP_TICK_SECONDS:-1800}"
|
|
NIGHTLY_AT="${AGENT_LOOP_NIGHTLY_AT:-23:30}" # Asia/Shanghai
|
|
mkdir -p "$STATE_DIR"
|
|
|
|
running() { [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; }
|
|
|
|
loop() {
|
|
echo $$ > "$PID_FILE"
|
|
while true; do
|
|
date -u +%FT%TZ > "$HEARTBEAT"
|
|
"$BIN/claude-tick.sh" || echo "$(date -u +%FT%TZ) tick exited $?" >> "$STATE_DIR/claude-tick.log"
|
|
today="$(TZ=Asia/Shanghai date +%F)"; now="$(TZ=Asia/Shanghai date +%H:%M)"
|
|
if [[ ! "$now" < "$NIGHTLY_AT" && "$(cat "$STATE_DIR/nightly.done" 2>/dev/null)" != "$today" ]]; then
|
|
"$BIN/nightly-review.sh" || true
|
|
echo "$today" > "$STATE_DIR/nightly.done"
|
|
fi
|
|
sleep "$TICK_SECONDS"
|
|
done
|
|
}
|
|
|
|
case "${1:-}" in
|
|
install)
|
|
src="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
mkdir -p "$BIN" "$CONF_DIR"
|
|
rm -rf "$BIN.new" && cp -R "$src" "$BIN.new" && rm -rf "$BIN" && mv "$BIN.new" "$BIN"
|
|
chmod +x "$BIN"/*.sh "$BIN"/hooks/*/pre-push
|
|
echo "installed to $BIN from $src ($(git -C "$src" rev-parse --short HEAD 2>/dev/null || echo '?'))"
|
|
[[ -r "$CONF_DIR/gitea-token" ]] && echo "gitea user: $("$BIN/gitea.sh" whoami)" || echo "WARNING: $CONF_DIR/gitea-token missing" ;;
|
|
start)
|
|
running && { echo "already running (pid $(cat "$PID_FILE"))"; exit 0; }
|
|
[[ -x "$BIN/claude-tick.sh" ]] || { echo "run '$0 install' first"; exit 1; }
|
|
setsid nohup "$BIN/agentctl.sh" __loop >> "$STATE_DIR/loop.out" 2>&1 < /dev/null &
|
|
sleep 1; running && echo "started (pid $(cat "$PID_FILE"))" || echo "failed to start, see $STATE_DIR/loop.out" ;;
|
|
__loop) loop ;;
|
|
stop)
|
|
if running; then kill -- -"$(ps -o pgid= "$(cat "$PID_FILE")" | tr -d ' ')" 2>/dev/null || kill "$(cat "$PID_FILE")"; rm -f "$PID_FILE"; echo stopped; else echo "not running"; fi ;;
|
|
status)
|
|
running && echo "running (pid $(cat "$PID_FILE"))" || echo "NOT running"
|
|
[[ -e "$CONF_DIR/STOP" ]] && echo "PAUSED (STOP file present)"
|
|
echo "last heartbeat: $(cat "$HEARTBEAT" 2>/dev/null || echo never)"
|
|
echo "--- claude-tick.log"; tail -n 8 "$STATE_DIR/claude-tick.log" 2>/dev/null || true
|
|
echo "--- nightly.log"; tail -n 3 "$STATE_DIR/nightly.log" 2>/dev/null || true ;;
|
|
once) "$BIN/claude-tick.sh"; tail -n 5 "$STATE_DIR/claude-tick.log" ;;
|
|
pause) mkdir -p "$CONF_DIR"; touch "$CONF_DIR/STOP"; echo paused ;;
|
|
resume) rm -f "$CONF_DIR/STOP"; echo resumed ;;
|
|
*) sed -n '2,9p' "$0"; exit 64 ;;
|
|
esac
|