-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Expand file tree
/
Copy pathmanage.sh
More file actions
executable file
·130 lines (116 loc) · 4.3 KB
/
manage.sh
File metadata and controls
executable file
·130 lines (116 loc) · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/bin/bash
# PAI Pulse — Process Management
# Usage: manage.sh {start|stop|restart|status|install|uninstall}
PULSE_DIR="$HOME/.claude/PAI/PULSE"
PLIST_NAME="com.pai.pulse"
PLIST_SRC="$PULSE_DIR/$PLIST_NAME.plist"
PLIST_DST="$HOME/Library/LaunchAgents/$PLIST_NAME.plist"
PID_FILE="$PULSE_DIR/state/pulse.pid"
STATE_FILE="$PULSE_DIR/state/state.json"
# Resolve bun's actual location for the launchd job. The public plist
# template ships with `__BUN_PATH__` so the job works for both brew users
# (/opt/homebrew/bin/bun) and curl-installer users (~/.bun/bin/bun).
#
# Order matters. `command -v bun` can resolve to a temporary helper shim
# inside `/private/tmp/bun-node-*/bun` when this script runs inside `bun
# install` (the child shell has its own PATH). That path is ephemeral and
# the launchd job would fail on next boot. Prefer the canonical install
# locations and fall back to `command -v bun` only if neither exists.
if [ -x "$HOME/.bun/bin/bun" ]; then
BUN_PATH="$HOME/.bun/bin/bun"
elif [ -x "/opt/homebrew/bin/bun" ]; then
BUN_PATH="/opt/homebrew/bin/bun"
elif [ -x "/usr/local/bin/bun" ]; then
BUN_PATH="/usr/local/bin/bun"
else
BUN_PATH="$(command -v bun || echo "$HOME/.bun/bin/bun")"
fi
case "$1" in
start)
if [ ! -f "$PLIST_DST" ]; then
# Substitute __HOME__ + __BUN_PATH__ placeholders (public template);
# no-op on plists that already have literal paths.
sed -e "s|__HOME__|$HOME|g" -e "s|__BUN_PATH__|$BUN_PATH|g" "$PLIST_SRC" > "$PLIST_DST"
fi
launchctl load "$PLIST_DST" 2>/dev/null
echo "PAI Pulse started"
;;
stop)
launchctl unload "$PLIST_DST" 2>/dev/null
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
kill "$PID" 2>/dev/null
echo "PAI Pulse stopped (PID $PID)"
else
echo "PAI Pulse stopped"
fi
;;
restart)
"$0" stop
sleep 2
"$0" start
;;
status)
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null 2>&1; then
UPTIME=$(ps -p "$PID" -o etime= | xargs)
echo "PAI Pulse: RUNNING (PID $PID, uptime $UPTIME)"
else
echo "PAI Pulse: DEAD (stale PID $PID)"
fi
else
echo "PAI Pulse: NOT RUNNING (no PID file)"
fi
if [ -f "$STATE_FILE" ]; then
echo ""
echo "Last job runs:"
bun -e "
const state = JSON.parse(require('fs').readFileSync('$STATE_FILE', 'utf-8'));
for (const [name, info] of Object.entries(state.jobs)) {
const ago = Math.round((Date.now() - info.lastRun) / 60000);
const status = info.consecutiveFailures > 0 ? ' [FAILING x' + info.consecutiveFailures + ']' : '';
console.log(' ' + name + ': ' + ago + ' min ago (' + info.lastResult + ')' + status);
}
" 2>/dev/null
fi
;;
install)
mkdir -p "$PULSE_DIR/state" "$PULSE_DIR/logs"
# Cleanup any prior pulse before installing fresh — prevents the stale-PID
# / unbound-port half-dead state where a previous launchd-managed pulse is
# alive with open fds but never bound :31337.
if [ -f "$PLIST_DST" ]; then
launchctl unload "$PLIST_DST" 2>/dev/null || true
fi
pkill -9 -f "bun.*pulse.ts" 2>/dev/null || true
sleep 1
# Substitute __HOME__ + __BUN_PATH__ placeholders (public template);
# no-op on plists that already have literal paths.
sed -e "s|__HOME__|$HOME|g" -e "s|__BUN_PATH__|$BUN_PATH|g" "$PLIST_SRC" > "$PLIST_DST"
launchctl load "$PLIST_DST"
# Verify pulse actually binds :31337 within 10s. Fail loud if not — prior
# behavior was silent success even when the daemon never came up.
for _ in $(seq 1 20); do
sleep 0.5
if curl -sS --max-time 1 -o /dev/null -X POST http://localhost:31337/notify \
-H "Content-Type: application/json" \
-d '{"message":"","voice_enabled":false}' 2>/dev/null; then
echo "PAI Pulse installed and verified on port 31337 (bun: $BUN_PATH)"
exit 0
fi
done
echo "ERROR: PAI Pulse plist installed but port 31337 did not bind within 10s." >&2
echo " Check: tail -50 $PULSE_DIR/logs/pulse-stderr.log" >&2
exit 1
;;
uninstall)
launchctl unload "$PLIST_DST" 2>/dev/null
rm -f "$PLIST_DST"
echo "PAI Pulse uninstalled"
;;
*)
echo "Usage: $0 {start|stop|restart|status|install|uninstall}"
exit 1
;;
esac