aidevops 3.1.117 → 3.1.119

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/setup.sh CHANGED
@@ -10,7 +10,7 @@ shopt -s inherit_errexit 2>/dev/null || true
10
10
  # AI Assistant Server Access Framework Setup Script
11
11
  # Helps developers set up the framework for their infrastructure
12
12
  #
13
- # Version: 3.1.117
13
+ # Version: 3.1.119
14
14
  #
15
15
  # Quick Install:
16
16
  # npm install -g aidevops && aidevops update (recommended)
@@ -617,6 +617,10 @@ source "$(dirname "${BASH_SOURCE[0]}")/setup-modules/agent-deploy.sh"
617
617
  source "$(dirname "${BASH_SOURCE[0]}")/setup-modules/config.sh"
618
618
  # shellcheck disable=SC1091
619
619
  source "$(dirname "${BASH_SOURCE[0]}")/setup-modules/plugins.sh"
620
+ # shellcheck disable=SC1091
621
+ source "$(dirname "${BASH_SOURCE[0]}")/setup-modules/schedulers.sh"
622
+ # shellcheck disable=SC1091
623
+ source "$(dirname "${BASH_SOURCE[0]}")/setup-modules/post-setup.sh"
620
624
 
621
625
  parse_args() {
622
626
  while [[ $# -gt 0 ]]; do
@@ -863,1040 +867,19 @@ main() {
863
867
  echo ""
864
868
  print_success "Setup complete!"
865
869
 
866
- # Enable auto-update if not already enabled
867
- # Check both launchd (macOS) and cron (Linux) for existing installation
868
- # Respects config: aidevops config set updates.auto_update false
869
- local auto_update_script="$HOME/.aidevops/agents/scripts/auto-update-helper.sh"
870
- if [[ -x "$auto_update_script" ]] && is_feature_enabled auto_update 2>/dev/null; then
871
- local _auto_update_installed=false
872
- if _scheduler_detect_installed \
873
- "Auto-update" \
874
- "com.aidevops.aidevops-auto-update" \
875
- "com.aidevops.auto-update" \
876
- "aidevops-auto-update" \
877
- "$auto_update_script" \
878
- "enable" \
879
- "aidevops auto-update enable"; then
880
- _auto_update_installed=true
881
- fi
882
- if [[ "$_auto_update_installed" == "false" ]]; then
883
- if [[ "$NON_INTERACTIVE" == "true" ]]; then
884
- # Non-interactive: enable silently
885
- bash "$auto_update_script" enable >/dev/null 2>&1 || true
886
- print_info "Auto-update enabled (every 10 min). Disable: aidevops auto-update disable"
887
- else
888
- echo ""
889
- echo "Auto-update keeps aidevops current by checking every 10 minutes."
890
- echo "Safe to run while AI sessions are active."
891
- echo ""
892
- read -r -p "Enable auto-update? [Y/n]: " enable_auto
893
- if [[ "$enable_auto" =~ ^[Yy]?$ || -z "$enable_auto" ]]; then
894
- bash "$auto_update_script" enable
895
- else
896
- print_info "Skipped. Enable later: aidevops auto-update enable"
897
- fi
898
- fi
899
- fi
900
- fi
901
-
902
- # Supervisor pulse scheduler — consent-gated autonomous orchestration.
903
- # Uses pulse-wrapper.sh which handles dedup, orphan cleanup, and RAM-based concurrency.
904
- # macOS: launchd plist invoking wrapper | Linux: cron entry invoking wrapper
905
- # The plist is ALWAYS regenerated on setup.sh to pick up config changes (env vars,
906
- # thresholds). Only the first-install prompt is gated on consent state.
907
- #
908
- # Ensure crontab has a global PATH= line (Linux only; macOS uses launchd env).
909
- # Must run before any cron entries are installed so they inherit the PATH.
910
- if [[ "$_os" != "Darwin" ]]; then
911
- _ensure_cron_path
912
- fi
913
-
914
- # Consent model (GH#2926):
915
- # - Default OFF: supervisor_pulse defaults to false in all config layers
916
- # - Explicit consent required: user must type "y" (prompt defaults to [y/N])
917
- # - Consent persisted: written to config.jsonc so it survives updates
918
- # - Never silently re-enabled: if config says false, skip entirely
919
- # - Non-interactive: only installs if config explicitly says true
920
- local wrapper_script="$HOME/.aidevops/agents/scripts/pulse-wrapper.sh"
921
- local pulse_label="com.aidevops.aidevops-supervisor-pulse"
922
- # Read explicit user consent from config.jsonc (not merged defaults).
923
- # Empty = user never configured this; "true"/"false" = explicit choice.
924
- local _pulse_user_config=""
925
- if type _jsonc_get_raw &>/dev/null && [[ -f "${JSONC_USER:-$HOME/.config/aidevops/config.jsonc}" ]]; then
926
- _pulse_user_config=$(_jsonc_get_raw "${JSONC_USER:-$HOME/.config/aidevops/config.jsonc}" "orchestration.supervisor_pulse")
927
- fi
928
-
929
- # Also check legacy .conf user override
930
- if [[ -z "$_pulse_user_config" && -f "${FEATURE_TOGGLES_USER:-$HOME/.config/aidevops/feature-toggles.conf}" ]]; then
931
- local _legacy_val
932
- # Use awk instead of grep|tail|cut — grep exits 1 on no match, which
933
- # aborts the script under set -euo pipefail. awk always exits 0.
934
- _legacy_val=$(awk -F= '/^supervisor_pulse=/{val=$2} END{print val}' "${FEATURE_TOGGLES_USER:-$HOME/.config/aidevops/feature-toggles.conf}")
935
- if [[ -n "$_legacy_val" ]]; then
936
- _pulse_user_config="$_legacy_val"
937
- fi
938
- fi
939
-
940
- # Also check env var override (highest priority)
941
- if [[ -n "${AIDEVOPS_SUPERVISOR_PULSE:-}" ]]; then
942
- _pulse_user_config="$AIDEVOPS_SUPERVISOR_PULSE"
943
- fi
944
-
945
- # Determine action based on consent state
946
- local _do_install=false
947
- local _pulse_lower
948
- _pulse_lower=$(echo "$_pulse_user_config" | tr '[:upper:]' '[:lower:]')
949
-
950
- if [[ "$_pulse_lower" == "false" ]]; then
951
- # User explicitly declined — never prompt, never install
952
- _do_install=false
953
- elif [[ "$_pulse_lower" == "true" ]]; then
954
- # User explicitly consented — install/regenerate
955
- _do_install=true
956
- elif [[ -z "$_pulse_user_config" ]]; then
957
- # No explicit config — fresh install or never configured
958
- if [[ "$NON_INTERACTIVE" == "true" ]]; then
959
- # Non-interactive: default OFF, do not install without consent
960
- _do_install=false
961
- elif [[ -f "$wrapper_script" ]]; then
962
- # Interactive: prompt with default-no
963
- echo ""
964
- echo "The supervisor pulse enables autonomous orchestration."
965
- echo "It will act under your GitHub identity and consume API credits:"
966
- echo " - Dispatches AI workers to implement tasks from GitHub issues"
967
- echo " - Creates PRs, merges passing PRs, files improvement issues"
968
- echo " - 4-hourly strategic review (opus-tier) for queue health"
969
- echo " - Circuit breaker pauses dispatch on consecutive failures"
970
- echo ""
971
- read -r -p "Enable supervisor pulse? [y/N]: " enable_pulse
972
- if [[ "$enable_pulse" =~ ^[Yy]$ ]]; then
973
- _do_install=true
974
- # Record explicit consent
975
- if type cmd_set &>/dev/null; then
976
- cmd_set "orchestration.supervisor_pulse" "true" || true
977
- fi
978
- else
979
- _do_install=false
980
- # Record explicit decline so we never re-prompt on updates
981
- if type cmd_set &>/dev/null; then
982
- cmd_set "orchestration.supervisor_pulse" "false" || true
983
- fi
984
- print_info "Skipped. Enable later: aidevops config set orchestration.supervisor_pulse true && ./setup.sh"
985
- fi
986
- fi
987
- fi
988
-
989
- # Guard: wrapper must exist
990
- if [[ "$_do_install" == "true" && ! -f "$wrapper_script" ]]; then
991
- # Wrapper not deployed yet — skip (will install on next run after rsync)
992
- _do_install=false
993
- fi
994
-
995
- # Detect if pulse is already installed (for upgrade messaging)
996
- # Uses shared helper to check both launchd and cron consistently
997
- local _pulse_installed=false
998
- if _scheduler_detect_installed \
999
- "Supervisor pulse" \
1000
- "$pulse_label" \
1001
- "" \
1002
- "pulse-wrapper" \
1003
- "" \
1004
- "" \
1005
- ""; then
1006
- _pulse_installed=true
1007
- fi
1008
-
1009
- # Detect opencode binary location
1010
- local opencode_bin
1011
- opencode_bin=$(command -v opencode 2>/dev/null || echo "/opt/homebrew/bin/opencode")
1012
-
1013
- if [[ "$_do_install" == "true" ]]; then
1014
- mkdir -p "$HOME/.aidevops/logs"
1015
-
1016
- if [[ "$_os" == "Darwin" ]]; then
1017
- # macOS: use launchd plist with wrapper
1018
- local pulse_plist="$HOME/Library/LaunchAgents/${pulse_label}.plist"
1019
-
1020
- # Unload old plist if upgrading
1021
- if _launchd_has_agent "$pulse_label"; then
1022
- launchctl unload "$pulse_plist" || true
1023
- pkill -f 'Supervisor Pulse' 2>/dev/null || true
1024
- fi
1025
-
1026
- # Also clean up old label if present
1027
- local old_plist="$HOME/Library/LaunchAgents/com.aidevops.supervisor-pulse.plist"
1028
- if [[ -f "$old_plist" ]]; then
1029
- launchctl unload "$old_plist" || true
1030
- rm -f "$old_plist"
1031
- fi
1032
-
1033
- # XML-escape paths for safe plist embedding (prevents injection
1034
- # if $HOME or paths contain &, <, > characters)
1035
- local _xml_wrapper_script _xml_home _xml_opencode_bin _xml_pulse_dir _xml_path
1036
- local _headless_xml_env=""
1037
- _xml_wrapper_script=$(_xml_escape "$wrapper_script")
1038
- _xml_home=$(_xml_escape "$HOME")
1039
- _xml_opencode_bin=$(_xml_escape "$opencode_bin")
1040
- # Use neutral workspace path for PULSE_DIR so supervisor sessions
1041
- # are not associated with any specific managed repo (GH#5136).
1042
- _xml_pulse_dir=$(_xml_escape "${HOME}/.aidevops/.agent-workspace")
1043
- _xml_path=$(_xml_escape "$PATH")
1044
- if [[ -n "${AIDEVOPS_HEADLESS_MODELS:-}" ]]; then
1045
- local _xml_headless_models
1046
- _xml_headless_models=$(_xml_escape "$AIDEVOPS_HEADLESS_MODELS")
1047
- _headless_xml_env+=$'\n'
1048
- _headless_xml_env+=$'\t\t<key>AIDEVOPS_HEADLESS_MODELS</key>'
1049
- _headless_xml_env+=$'\n'
1050
- _headless_xml_env+=$'\t\t'"<string>${_xml_headless_models}</string>"
1051
- fi
1052
- if [[ -n "${AIDEVOPS_HEADLESS_PROVIDER_ALLOWLIST:-}" ]]; then
1053
- local _xml_headless_allowlist
1054
- _xml_headless_allowlist=$(_xml_escape "$AIDEVOPS_HEADLESS_PROVIDER_ALLOWLIST")
1055
- _headless_xml_env+=$'\n'
1056
- _headless_xml_env+=$'\t\t<key>AIDEVOPS_HEADLESS_PROVIDER_ALLOWLIST</key>'
1057
- _headless_xml_env+=$'\n'
1058
- _headless_xml_env+=$'\t\t'"<string>${_xml_headless_allowlist}</string>"
1059
- fi
1060
-
1061
- # Write the plist (always regenerated to pick up config changes)
1062
- cat >"$pulse_plist" <<PLIST
1063
- <?xml version="1.0" encoding="UTF-8"?>
1064
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1065
- <plist version="1.0">
1066
- <dict>
1067
- <key>Label</key>
1068
- <string>${pulse_label}</string>
1069
- <key>ProgramArguments</key>
1070
- <array>
1071
- <string>/bin/bash</string>
1072
- <string>${_xml_wrapper_script}</string>
1073
- </array>
1074
- <key>StartInterval</key>
1075
- <integer>120</integer>
1076
- <key>StandardOutPath</key>
1077
- <string>${_xml_home}/.aidevops/logs/pulse-wrapper.log</string>
1078
- <key>StandardErrorPath</key>
1079
- <string>${_xml_home}/.aidevops/logs/pulse-wrapper.log</string>
1080
- <key>EnvironmentVariables</key>
1081
- <dict>
1082
- <key>PATH</key>
1083
- <string>${_xml_path}</string>
1084
- <key>HOME</key>
1085
- <string>${_xml_home}</string>
1086
- <key>OPENCODE_BIN</key>
1087
- <string>${_xml_opencode_bin}</string>
1088
- <key>PULSE_DIR</key>
1089
- <string>${_xml_pulse_dir}</string>
1090
- <key>PULSE_STALE_THRESHOLD</key>
1091
- <string>1800</string>
1092
- ${_headless_xml_env}
1093
- </dict>
1094
- <key>RunAtLoad</key>
1095
- <true/>
1096
- <key>KeepAlive</key>
1097
- <false/>
1098
- </dict>
1099
- </plist>
1100
- PLIST
1101
-
1102
- if launchctl load "$pulse_plist"; then
1103
- if [[ "$_pulse_installed" == "true" ]]; then
1104
- print_info "Supervisor pulse updated (launchd config regenerated)"
1105
- else
1106
- print_info "Supervisor pulse enabled (launchd, every 2 min)"
1107
- fi
1108
- else
1109
- print_warning "Failed to load supervisor pulse LaunchAgent"
1110
- fi
1111
- else
1112
- # Linux: use cron entry with wrapper
1113
- # Remove old-style cron entries (direct opencode invocation)
1114
- # Shell-escape all interpolated paths to prevent command injection
1115
- # via $(…) or backticks if paths contain shell metacharacters
1116
- # PATH is managed globally by _ensure_cron_path() — do NOT set inline
1117
- # PATH= here, it overrides the global line and breaks nvm/bun/cargo.
1118
- # OPENCODE_BIN removed — resolved from PATH at runtime via command -v.
1119
- # See #4099 and #4240 for history.
1120
- local _cron_pulse_dir _cron_wrapper_script _cron_headless_env=""
1121
- # Use neutral workspace path for PULSE_DIR (GH#5136)
1122
- _cron_pulse_dir=$(_cron_escape "${HOME}/.aidevops/.agent-workspace")
1123
- _cron_wrapper_script=$(_cron_escape "$wrapper_script")
1124
- if [[ -n "${AIDEVOPS_HEADLESS_MODELS:-}" ]]; then
1125
- local _cron_headless_models
1126
- _cron_headless_models=$(_cron_escape "$AIDEVOPS_HEADLESS_MODELS")
1127
- _cron_headless_env+=" AIDEVOPS_HEADLESS_MODELS=${_cron_headless_models}"
1128
- fi
1129
- if [[ -n "${AIDEVOPS_HEADLESS_PROVIDER_ALLOWLIST:-}" ]]; then
1130
- local _cron_headless_allowlist
1131
- _cron_headless_allowlist=$(_cron_escape "$AIDEVOPS_HEADLESS_PROVIDER_ALLOWLIST")
1132
- _cron_headless_env+=" AIDEVOPS_HEADLESS_PROVIDER_ALLOWLIST=${_cron_headless_allowlist}"
1133
- fi
1134
- (
1135
- crontab -l 2>/dev/null | grep -v 'aidevops: supervisor-pulse'
1136
- echo "*/2 * * * * PULSE_DIR=${_cron_pulse_dir}${_cron_headless_env} /bin/bash ${_cron_wrapper_script} >> \"\$HOME/.aidevops/logs/pulse-wrapper.log\" 2>&1 # aidevops: supervisor-pulse"
1137
- ) | crontab - || true
1138
- if crontab -l 2>/dev/null | grep -qF "aidevops: supervisor-pulse"; then
1139
- print_info "Supervisor pulse enabled (cron, every 2 min). Disable: crontab -e and remove the supervisor-pulse line"
1140
- else
1141
- print_warning "Failed to install supervisor pulse cron entry. See runners.md for manual setup."
1142
- fi
1143
- fi
1144
- elif [[ "$_pulse_lower" == "false" && "$_pulse_installed" == "true" ]]; then
1145
- # User explicitly disabled but pulse is still installed — clean up
1146
- if [[ "$_os" == "Darwin" ]]; then
1147
- local pulse_plist="$HOME/Library/LaunchAgents/${pulse_label}.plist"
1148
- if _launchd_has_agent "$pulse_label"; then
1149
- launchctl unload "$pulse_plist" || true
1150
- rm -f "$pulse_plist"
1151
- pkill -f 'Supervisor Pulse' 2>/dev/null || true
1152
- print_info "Supervisor pulse disabled (launchd agent removed per config)"
1153
- fi
1154
- else
1155
- if crontab -l 2>/dev/null | grep -qF "pulse-wrapper"; then
1156
- crontab -l 2>/dev/null | grep -v 'aidevops: supervisor-pulse' | crontab - || true
1157
- print_info "Supervisor pulse disabled (cron entry removed per config)"
1158
- fi
1159
- fi
1160
- fi
1161
-
1162
- # Enable stats-wrapper — runs quality sweep and health issue updates
1163
- # separately from the pulse (t1429). Only installed when the supervisor
1164
- # pulse is enabled (stats are useless without it).
1165
- local stats_script="$HOME/.aidevops/agents/scripts/stats-wrapper.sh"
1166
- local stats_label="com.aidevops.aidevops-stats-wrapper"
1167
- if [[ -x "$stats_script" ]] && [[ "$_pulse_lower" == "true" ]]; then
1168
- # Always regenerate to pick up config/format changes (matches pulse behavior)
1169
- if [[ "$(uname -s)" == "Darwin" ]]; then
1170
- local stats_plist="$HOME/Library/LaunchAgents/${stats_label}.plist"
1171
-
1172
- local _xml_stats_script _xml_stats_home _xml_stats_path
1173
- _xml_stats_script=$(_xml_escape "$stats_script")
1174
- _xml_stats_home=$(_xml_escape "$HOME")
1175
- _xml_stats_path=$(_xml_escape "$PATH")
1176
- local stats_plist_content
1177
- stats_plist_content=$(
1178
- cat <<PLIST
1179
- <?xml version="1.0" encoding="UTF-8"?>
1180
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1181
- <plist version="1.0">
1182
- <dict>
1183
- <key>Label</key>
1184
- <string>${stats_label}</string>
1185
- <key>ProgramArguments</key>
1186
- <array>
1187
- <string>/bin/bash</string>
1188
- <string>${_xml_stats_script}</string>
1189
- </array>
1190
- <key>StartInterval</key>
1191
- <integer>900</integer>
1192
- <key>StandardOutPath</key>
1193
- <string>${_xml_stats_home}/.aidevops/logs/stats.log</string>
1194
- <key>StandardErrorPath</key>
1195
- <string>${_xml_stats_home}/.aidevops/logs/stats.log</string>
1196
- <key>EnvironmentVariables</key>
1197
- <dict>
1198
- <key>PATH</key>
1199
- <string>${_xml_stats_path}</string>
1200
- <key>HOME</key>
1201
- <string>${_xml_stats_home}</string>
1202
- </dict>
1203
- <key>RunAtLoad</key>
1204
- <true/>
1205
- <key>KeepAlive</key>
1206
- <false/>
1207
- </dict>
1208
- </plist>
1209
- PLIST
1210
- )
1211
- if _launchd_install_if_changed "$stats_label" "$stats_plist" "$stats_plist_content"; then
1212
- print_info "Stats wrapper enabled (launchd, every 15 min)"
1213
- else
1214
- print_warning "Failed to load stats wrapper LaunchAgent"
1215
- fi
1216
- else
1217
- local _cron_stats_script
1218
- _cron_stats_script=$(_cron_escape "$stats_script")
1219
- (
1220
- crontab -l 2>/dev/null | grep -v 'aidevops: stats-wrapper'
1221
- echo "*/15 * * * * /bin/bash ${_cron_stats_script} >> \"\$HOME/.aidevops/logs/stats.log\" 2>&1 # aidevops: stats-wrapper"
1222
- ) | crontab - || true
1223
- if crontab -l 2>/dev/null | grep -qF "aidevops: stats-wrapper"; then
1224
- print_info "Stats wrapper enabled (cron, every 15 min)"
1225
- fi
1226
- fi
1227
- elif [[ "$_pulse_lower" == "false" ]]; then
1228
- # Remove stats scheduler if pulse is disabled
1229
- if [[ "$(uname -s)" == "Darwin" ]]; then
1230
- local stats_plist="$HOME/Library/LaunchAgents/${stats_label}.plist"
1231
- if _launchd_has_agent "$stats_label"; then
1232
- launchctl unload "$stats_plist" || true
1233
- rm -f "$stats_plist"
1234
- print_info "Stats wrapper disabled (launchd agent removed — pulse is off)"
1235
- fi
1236
- else
1237
- if crontab -l 2>/dev/null | grep -qF "aidevops: stats-wrapper"; then
1238
- crontab -l 2>/dev/null | grep -v 'aidevops: stats-wrapper' | crontab - || true
1239
- print_info "Stats wrapper disabled (cron entry removed — pulse is off)"
1240
- fi
1241
- fi
1242
- fi
1243
-
1244
- # Enable repo-sync scheduler if not already installed
1245
- # Keeps local git repos up to date with daily ff-only pulls
1246
- # Respects config: aidevops config set orchestration.repo_sync false
1247
- local repo_sync_script="$HOME/.aidevops/agents/scripts/repo-sync-helper.sh"
1248
- if [[ -x "$repo_sync_script" ]] && is_feature_enabled repo_sync 2>/dev/null; then
1249
- local _repo_sync_installed=false
1250
- if _launchd_has_agent "com.aidevops.aidevops-repo-sync"; then
1251
- _repo_sync_installed=true
1252
- elif crontab -l 2>/dev/null | grep -qF "aidevops-repo-sync"; then
1253
- _repo_sync_installed=true
1254
- fi
1255
- if [[ "$_repo_sync_installed" == "false" ]]; then
1256
- if [[ "$NON_INTERACTIVE" == "true" ]]; then
1257
- bash "$repo_sync_script" enable >/dev/null 2>&1 || true
1258
- print_info "Repo sync enabled (daily). Disable: aidevops repo-sync disable"
1259
- else
1260
- echo ""
1261
- echo "Repo sync keeps your local git repos up to date by running"
1262
- echo "git pull --ff-only daily on clean repos on their default branch."
1263
- echo ""
1264
- read -r -p "Enable daily repo sync? [Y/n]: " enable_repo_sync
1265
- if [[ "$enable_repo_sync" =~ ^[Yy]?$ || -z "$enable_repo_sync" ]]; then
1266
- bash "$repo_sync_script" enable
1267
- else
1268
- print_info "Skipped. Enable later: aidevops repo-sync enable"
1269
- fi
1270
- fi
1271
- fi
1272
- fi
1273
-
1274
- # Process guard — kills runaway AI processes (ShellCheck bloat, stuck workers)
1275
- # before they exhaust memory and cause kernel panics. Always installed when the
1276
- # script exists; no consent needed (safety net, not autonomous action).
1277
- # macOS: launchd plist (30s interval, RunAtLoad=true) | Linux: cron (every minute)
1278
- local guard_script="$HOME/.aidevops/agents/scripts/process-guard-helper.sh"
1279
- local guard_label="sh.aidevops.process-guard"
1280
- if [[ -x "$guard_script" ]]; then
1281
- mkdir -p "$HOME/.aidevops/logs"
1282
-
1283
- if [[ "$(uname -s)" == "Darwin" ]]; then
1284
- local guard_plist="$HOME/Library/LaunchAgents/${guard_label}.plist"
1285
-
1286
- # XML-escape paths for safe plist embedding (prevents injection
1287
- # if $HOME or paths contain &, <, > characters)
1288
- local _xml_guard_script _xml_guard_home _xml_guard_path
1289
- _xml_guard_script=$(_xml_escape "$guard_script")
1290
- _xml_guard_home=$(_xml_escape "$HOME")
1291
- _xml_guard_path=$(_xml_escape "$PATH")
1292
-
1293
- local guard_plist_content
1294
- guard_plist_content=$(
1295
- cat <<GUARD_PLIST
1296
- <?xml version="1.0" encoding="UTF-8"?>
1297
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1298
- <plist version="1.0">
1299
- <dict>
1300
- <key>Label</key>
1301
- <string>${guard_label}</string>
1302
- <key>ProgramArguments</key>
1303
- <array>
1304
- <string>/bin/bash</string>
1305
- <string>${_xml_guard_script}</string>
1306
- <string>kill-runaways</string>
1307
- </array>
1308
- <key>StartInterval</key>
1309
- <integer>30</integer>
1310
- <key>StandardOutPath</key>
1311
- <string>${_xml_guard_home}/.aidevops/logs/process-guard.log</string>
1312
- <key>StandardErrorPath</key>
1313
- <string>${_xml_guard_home}/.aidevops/logs/process-guard.log</string>
1314
- <key>EnvironmentVariables</key>
1315
- <dict>
1316
- <key>PATH</key>
1317
- <string>${_xml_guard_path}</string>
1318
- <key>HOME</key>
1319
- <string>${_xml_guard_home}</string>
1320
- <key>SHELLCHECK_RSS_LIMIT_KB</key>
1321
- <string>524288</string>
1322
- <key>SHELLCHECK_RUNTIME_LIMIT</key>
1323
- <string>120</string>
1324
- <key>CHILD_RSS_LIMIT_KB</key>
1325
- <string>8388608</string>
1326
- <key>CHILD_RUNTIME_LIMIT</key>
1327
- <string>7200</string>
1328
- </dict>
1329
- <key>RunAtLoad</key>
1330
- <true/>
1331
- <key>KeepAlive</key>
1332
- <false/>
1333
- </dict>
1334
- </plist>
1335
- GUARD_PLIST
1336
- )
1337
-
1338
- if _launchd_install_if_changed "$guard_label" "$guard_plist" "$guard_plist_content"; then
1339
- print_info "Process guard enabled (launchd, every 30s, survives reboot)"
1340
- else
1341
- print_warning "Failed to load process guard LaunchAgent"
1342
- fi
1343
- else
1344
- # Linux: cron entry (every minute — cron minimum granularity)
1345
- # Always regenerate to pick up config changes (matches macOS behavior)
1346
- # Shell-escape path to prevent command injection via metacharacters
1347
- local _cron_guard_script
1348
- _cron_guard_script=$(_cron_escape "$guard_script")
1349
- (
1350
- crontab -l 2>/dev/null | grep -v 'aidevops: process-guard'
1351
- echo "* * * * * SHELLCHECK_RSS_LIMIT_KB=524288 SHELLCHECK_RUNTIME_LIMIT=120 CHILD_RSS_LIMIT_KB=8388608 CHILD_RUNTIME_LIMIT=7200 /bin/bash ${_cron_guard_script} kill-runaways >> \"\$HOME/.aidevops/logs/process-guard.log\" 2>&1 # aidevops: process-guard"
1352
- ) | crontab - || true
1353
- if crontab -l 2>/dev/null | grep -qF "aidevops: process-guard"; then
1354
- print_info "Process guard enabled (cron, every minute)"
1355
- else
1356
- print_warning "Failed to install process guard cron entry"
1357
- fi
1358
- fi
1359
- fi
1360
-
1361
- # Memory pressure monitor — process-focused memory watchdog (t1398.5, GH#2915).
1362
- # Monitors individual process RSS, runtime, session count, and aggregate memory.
1363
- # Auto-kills runaway ShellCheck (language server respawns them). Always installed
1364
- # when the script exists; no consent needed (safety net, not autonomous action).
1365
- # macOS: launchd plist (60s interval, RunAtLoad=true) | Linux: cron (every minute)
1366
- local monitor_script="$HOME/.aidevops/agents/scripts/memory-pressure-monitor.sh"
1367
- local monitor_label="sh.aidevops.memory-pressure-monitor"
1368
- if [[ -x "$monitor_script" ]]; then
1369
- mkdir -p "$HOME/.aidevops/logs"
1370
-
1371
- if [[ "$(uname -s)" == "Darwin" ]]; then
1372
- local monitor_plist="$HOME/Library/LaunchAgents/${monitor_label}.plist"
1373
-
1374
- local monitor_plist_content
1375
- monitor_plist_content=$(
1376
- cat <<MONITOR_PLIST
1377
- <?xml version="1.0" encoding="UTF-8"?>
1378
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1379
- <plist version="1.0">
1380
- <dict>
1381
- <key>Label</key>
1382
- <string>${monitor_label}</string>
1383
- <key>ProgramArguments</key>
1384
- <array>
1385
- <string>/bin/bash</string>
1386
- <string>${monitor_script}</string>
1387
- </array>
1388
- <key>StartInterval</key>
1389
- <integer>60</integer>
1390
- <key>StandardOutPath</key>
1391
- <string>${HOME}/.aidevops/logs/memory-pressure-launchd.log</string>
1392
- <key>StandardErrorPath</key>
1393
- <string>${HOME}/.aidevops/logs/memory-pressure-launchd.log</string>
1394
- <key>EnvironmentVariables</key>
1395
- <dict>
1396
- <key>PATH</key>
1397
- <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
1398
- <key>HOME</key>
1399
- <string>${HOME}</string>
1400
- </dict>
1401
- <key>RunAtLoad</key>
1402
- <true/>
1403
- <key>KeepAlive</key>
1404
- <false/>
1405
- <key>ProcessType</key>
1406
- <string>Background</string>
1407
- <key>LowPriorityBackgroundIO</key>
1408
- <true/>
1409
- <key>Nice</key>
1410
- <integer>10</integer>
1411
- </dict>
1412
- </plist>
1413
- MONITOR_PLIST
1414
- )
1415
-
1416
- if _launchd_install_if_changed "$monitor_label" "$monitor_plist" "$monitor_plist_content"; then
1417
- print_info "Memory pressure monitor enabled (launchd, every 60s, survives reboot)"
1418
- else
1419
- print_warning "Failed to load memory pressure monitor LaunchAgent"
1420
- fi
1421
- else
1422
- # Linux: cron entry (every minute — cron minimum granularity)
1423
- (
1424
- crontab -l 2>/dev/null | grep -v 'aidevops: memory-pressure-monitor'
1425
- echo "* * * * * /bin/bash \"${monitor_script}\" >> \"\$HOME/.aidevops/logs/memory-pressure-launchd.log\" 2>&1 # aidevops: memory-pressure-monitor"
1426
- ) | crontab - 2>/dev/null || true
1427
- if crontab -l 2>/dev/null | grep -qF "aidevops: memory-pressure-monitor" 2>/dev/null; then
1428
- print_info "Memory pressure monitor enabled (cron, every minute)"
1429
- else
1430
- print_warning "Failed to install memory pressure monitor cron entry"
1431
- fi
1432
- fi
1433
- fi
1434
-
1435
- # Screen time snapshot — captures daily screen time for contributor stats.
1436
- # Accumulates data in screen-time.jsonl (macOS Knowledge DB retains only ~28 days).
1437
- # Always installed when the script exists; no consent needed (data collection only).
1438
- # macOS: launchd plist (every 6h, RunAtLoad=true) | Linux: cron (every 6h)
1439
- local st_script="$HOME/.aidevops/agents/scripts/screen-time-helper.sh"
1440
- local st_label="sh.aidevops.screen-time-snapshot"
1441
- if [[ -x "$st_script" ]]; then
1442
- mkdir -p "$HOME/.aidevops/.agent-workspace/logs"
1443
-
1444
- if [[ "$(uname -s)" == "Darwin" ]]; then
1445
- local st_plist="$HOME/Library/LaunchAgents/${st_label}.plist"
1446
-
1447
- # XML-escape paths for safe plist embedding
1448
- local _xml_st_script _xml_st_home
1449
- _xml_st_script=$(_xml_escape "$st_script")
1450
- _xml_st_home=$(_xml_escape "$HOME")
1451
-
1452
- local st_plist_content
1453
- st_plist_content=$(
1454
- cat <<ST_PLIST
1455
- <?xml version="1.0" encoding="UTF-8"?>
1456
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1457
- <plist version="1.0">
1458
- <dict>
1459
- <key>Label</key>
1460
- <string>${st_label}</string>
1461
- <key>ProgramArguments</key>
1462
- <array>
1463
- <string>/bin/bash</string>
1464
- <string>${_xml_st_script}</string>
1465
- <string>snapshot</string>
1466
- </array>
1467
- <key>StartInterval</key>
1468
- <integer>21600</integer>
1469
- <key>StandardOutPath</key>
1470
- <string>${_xml_st_home}/.aidevops/.agent-workspace/logs/screen-time-snapshot.log</string>
1471
- <key>StandardErrorPath</key>
1472
- <string>${_xml_st_home}/.aidevops/.agent-workspace/logs/screen-time-snapshot.log</string>
1473
- <key>EnvironmentVariables</key>
1474
- <dict>
1475
- <key>PATH</key>
1476
- <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
1477
- <key>HOME</key>
1478
- <string>${_xml_st_home}</string>
1479
- </dict>
1480
- <key>RunAtLoad</key>
1481
- <true/>
1482
- <key>KeepAlive</key>
1483
- <false/>
1484
- <key>ProcessType</key>
1485
- <string>Background</string>
1486
- <key>LowPriorityBackgroundIO</key>
1487
- <true/>
1488
- <key>Nice</key>
1489
- <integer>10</integer>
1490
- </dict>
1491
- </plist>
1492
- ST_PLIST
1493
- )
1494
-
1495
- if _launchd_install_if_changed "$st_label" "$st_plist" "$st_plist_content"; then
1496
- print_info "Screen time snapshot enabled (launchd, every 6h, survives reboot)"
1497
- else
1498
- print_warning "Failed to load screen time snapshot LaunchAgent"
1499
- fi
1500
- else
1501
- # Linux: cron entry (every 6 hours)
1502
- local _cron_st_script
1503
- _cron_st_script=$(_cron_escape "$st_script")
1504
- (
1505
- crontab -l 2>/dev/null | grep -v 'aidevops: screen-time-snapshot'
1506
- echo "0 */6 * * * /bin/bash ${_cron_st_script} snapshot >> \"\$HOME/.aidevops/.agent-workspace/logs/screen-time-snapshot.log\" 2>&1 # aidevops: screen-time-snapshot"
1507
- ) | crontab - 2>/dev/null || true
1508
- if crontab -l 2>/dev/null | grep -qF "aidevops: screen-time-snapshot" 2>/dev/null; then
1509
- print_info "Screen time snapshot enabled (cron, every 6h)"
1510
- else
1511
- print_warning "Failed to install screen time snapshot cron entry"
1512
- fi
1513
- fi
1514
- fi
1515
-
1516
- # Contribution watch — monitors external issues/PRs for new activity (t1554).
1517
- # Auto-seeds on first run (discovers authored/commented issues/PRs), then installs
1518
- # a launchd/cron job to scan periodically. Requires gh CLI authenticated.
1519
- # No consent needed — this is passive monitoring (read-only notifications API),
1520
- # not autonomous action. Comment bodies are never processed by LLM in automated context.
1521
- # Respects config: aidevops config set orchestration.contribution_watch false
1522
- local cw_script="$HOME/.aidevops/agents/scripts/contribution-watch-helper.sh"
1523
- local cw_label="sh.aidevops.contribution-watch"
1524
- local cw_state="$HOME/.aidevops/cache/contribution-watch.json"
1525
- if [[ -x "$cw_script" ]] && is_feature_enabled orchestration.contribution_watch 2>/dev/null && command -v gh &>/dev/null && gh auth status &>/dev/null 2>&1; then
1526
- # Resolve log directory from config (paths.log_dir), expanding ~ to $HOME.
1527
- # Falls back to the default if config is unavailable or jq is missing.
1528
- # Validate before expansion to guard against shell metacharacter injection.
1529
- local _cw_log_dir
1530
- # shellcheck disable=SC2088 # Tilde is intentionally literal here; expanded below via ${/#\~/$HOME}
1531
- if type _jsonc_get &>/dev/null; then
1532
- _cw_log_dir=$(_jsonc_get "paths.log_dir" "~/.aidevops/logs")
1533
- else
1534
- _cw_log_dir="~/.aidevops/logs"
1535
- fi
1536
- if [[ "$_cw_log_dir" == *['`$']* ]]; then
1537
- print_error "Invalid characters in paths.log_dir: $_cw_log_dir"
1538
- return 1
1539
- fi
1540
- _cw_log_dir="${_cw_log_dir/#\~/$HOME}"
1541
- mkdir -p "$HOME/.aidevops/cache" "$_cw_log_dir"
1542
-
1543
- # Auto-seed on first run (populates state file with existing contributions)
1544
- if [[ ! -f "$cw_state" ]]; then
1545
- print_info "Discovering external contributions for contribution watch..."
1546
- if bash "$cw_script" seed >/dev/null 2>&1; then
1547
- print_info "Contribution watch seeded (external issues/PRs discovered)"
1548
- else
1549
- print_warning "Contribution watch seed failed (non-fatal, will retry on next run)"
1550
- fi
1551
- fi
1552
-
1553
- # Install/update scheduled scanner
1554
- if [[ "$(uname -s)" == "Darwin" ]]; then
1555
- local cw_plist="$HOME/Library/LaunchAgents/${cw_label}.plist"
1556
-
1557
- local _xml_cw_script _xml_cw_home _xml_cw_log_dir
1558
- _xml_cw_script=$(_xml_escape "$cw_script")
1559
- _xml_cw_home=$(_xml_escape "$HOME")
1560
- _xml_cw_log_dir=$(_xml_escape "$_cw_log_dir")
1561
-
1562
- local cw_plist_content
1563
- cw_plist_content=$(
1564
- cat <<CW_PLIST
1565
- <?xml version="1.0" encoding="UTF-8"?>
1566
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1567
- <plist version="1.0">
1568
- <dict>
1569
- <key>Label</key>
1570
- <string>${cw_label}</string>
1571
- <key>ProgramArguments</key>
1572
- <array>
1573
- <string>/bin/bash</string>
1574
- <string>${_xml_cw_script}</string>
1575
- <string>scan</string>
1576
- </array>
1577
- <key>StartInterval</key>
1578
- <integer>3600</integer>
1579
- <key>StandardOutPath</key>
1580
- <string>${_xml_cw_log_dir}/contribution-watch.log</string>
1581
- <key>StandardErrorPath</key>
1582
- <string>${_xml_cw_log_dir}/contribution-watch.log</string>
1583
- <key>EnvironmentVariables</key>
1584
- <dict>
1585
- <key>PATH</key>
1586
- <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
1587
- <key>HOME</key>
1588
- <string>${_xml_cw_home}</string>
1589
- </dict>
1590
- <key>RunAtLoad</key>
1591
- <false/>
1592
- <key>KeepAlive</key>
1593
- <false/>
1594
- <key>ProcessType</key>
1595
- <string>Background</string>
1596
- <key>LowPriorityBackgroundIO</key>
1597
- <true/>
1598
- <key>Nice</key>
1599
- <integer>10</integer>
1600
- </dict>
1601
- </plist>
1602
- CW_PLIST
1603
- )
1604
-
1605
- if _launchd_install_if_changed "$cw_label" "$cw_plist" "$cw_plist_content"; then
1606
- print_info "Contribution watch enabled (launchd, hourly scan)"
1607
- else
1608
- print_warning "Failed to load contribution watch LaunchAgent"
1609
- fi
1610
- else
1611
- # Linux: cron entry (hourly)
1612
- local _cron_cw_script
1613
- _cron_cw_script=$(_cron_escape "$cw_script")
1614
- (
1615
- crontab -l 2>/dev/null | grep -v 'aidevops: contribution-watch'
1616
- echo "0 * * * * /bin/bash ${_cron_cw_script} scan >> \"${_cw_log_dir}/contribution-watch.log\" 2>&1 # aidevops: contribution-watch"
1617
- ) | crontab - 2>/dev/null || true
1618
- if crontab -l 2>/dev/null | grep -qF "aidevops: contribution-watch" 2>/dev/null; then
1619
- print_info "Contribution watch enabled (cron, hourly scan)"
1620
- else
1621
- print_warning "Failed to install contribution watch cron entry"
1622
- fi
1623
- fi
1624
- fi
1625
-
1626
- # Draft responses — private repo + local draft storage for reviewing AI-drafted
1627
- # replies to external contributions (t1555). Creates private draft-responses
1628
- # repo for GitHub notification-driven approval flow.
1629
- # Respects config: aidevops config set orchestration.draft_responses false
1630
- local dr_script="$HOME/.aidevops/agents/scripts/draft-response-helper.sh"
1631
- if [[ -x "$dr_script" ]] && is_feature_enabled draft_responses 2>/dev/null && is_feature_enabled contribution_watch 2>/dev/null && command -v gh &>/dev/null && gh auth status &>/dev/null 2>&1; then
1632
- mkdir -p "$HOME/.aidevops/.agent-workspace/draft-responses"
1633
- if bash "$dr_script" init >/dev/null 2>&1; then
1634
- print_info "Draft responses ready (private repo + local drafts)"
1635
- else
1636
- print_warning "Draft responses repo setup failed (non-fatal, local drafts still work)"
1637
- fi
1638
- fi
1639
-
1640
- # Profile README — auto-create repo and seed README if not already set up.
1641
- # Requires gh CLI authenticated. Creates username/username repo, seeds README
1642
- # with stat markers, registers in repos.json with priority: "profile".
1643
- local pr_script="$HOME/.aidevops/agents/scripts/profile-readme-helper.sh"
1644
- local pr_label="sh.aidevops.profile-readme-update"
1645
- local repos_json="$HOME/.config/aidevops/repos.json"
1646
- if [[ -x "$pr_script" ]] && command -v gh &>/dev/null && gh auth status &>/dev/null; then
1647
- # Initialize profile repo if not already set up.
1648
- # Always run init — it's idempotent and handles:
1649
- # - Fresh installs (no profile repo)
1650
- # - Missing markers (injects them into existing README)
1651
- # - Diverged history (repo deleted and recreated on GitHub)
1652
- # - Already-initialized repos (returns early with no changes)
1653
- print_info "Checking GitHub profile README..."
1654
- if bash "$pr_script" init; then
1655
- print_info "Profile README ready."
1656
- else
1657
- print_warning "Profile README setup failed (non-fatal, skipping)"
1658
- fi
1659
- fi
1660
-
1661
- # Profile README auto-update scheduled job.
1662
- # Installed whenever gh CLI is available — the update script self-heals
1663
- # (discovers/creates the profile repo on first run via _resolve_profile_repo).
1664
- # macOS: launchd plist (hourly) | Linux: cron (hourly)
1665
- if [[ -x "$pr_script" ]] && command -v gh &>/dev/null; then
1666
- mkdir -p "$HOME/.aidevops/.agent-workspace/logs"
1667
-
1668
- if [[ "$(uname -s)" == "Darwin" ]]; then
1669
- local pr_plist="$HOME/Library/LaunchAgents/${pr_label}.plist"
1670
-
1671
- # XML-escape paths for safe plist embedding
1672
- local _xml_pr_script _xml_pr_home
1673
- _xml_pr_script=$(_xml_escape "$pr_script")
1674
- _xml_pr_home=$(_xml_escape "$HOME")
1675
-
1676
- local pr_plist_content
1677
- pr_plist_content=$(
1678
- cat <<PR_PLIST
1679
- <?xml version="1.0" encoding="UTF-8"?>
1680
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1681
- <plist version="1.0">
1682
- <dict>
1683
- <key>Label</key>
1684
- <string>${pr_label}</string>
1685
- <key>ProgramArguments</key>
1686
- <array>
1687
- <string>/bin/bash</string>
1688
- <string>${_xml_pr_script}</string>
1689
- <string>update</string>
1690
- </array>
1691
- <key>StartInterval</key>
1692
- <integer>3600</integer>
1693
- <key>StandardOutPath</key>
1694
- <string>${_xml_pr_home}/.aidevops/.agent-workspace/logs/profile-readme-update.log</string>
1695
- <key>StandardErrorPath</key>
1696
- <string>${_xml_pr_home}/.aidevops/.agent-workspace/logs/profile-readme-update.log</string>
1697
- <key>EnvironmentVariables</key>
1698
- <dict>
1699
- <key>PATH</key>
1700
- <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
1701
- <key>HOME</key>
1702
- <string>${_xml_pr_home}</string>
1703
- </dict>
1704
- <key>RunAtLoad</key>
1705
- <false/>
1706
- <key>KeepAlive</key>
1707
- <false/>
1708
- <key>ProcessType</key>
1709
- <string>Background</string>
1710
- <key>LowPriorityBackgroundIO</key>
1711
- <true/>
1712
- <key>Nice</key>
1713
- <integer>10</integer>
1714
- </dict>
1715
- </plist>
1716
- PR_PLIST
1717
- )
1718
-
1719
- if _launchd_install_if_changed "$pr_label" "$pr_plist" "$pr_plist_content"; then
1720
- print_info "Profile README update enabled (launchd, hourly)"
1721
- else
1722
- print_warning "Failed to load profile README update LaunchAgent"
1723
- fi
1724
- else
1725
- # Linux: cron entry (hourly)
1726
- local _cron_pr_script
1727
- _cron_pr_script=$(_cron_escape "$pr_script")
1728
- (
1729
- crontab -l 2>/dev/null | grep -v 'aidevops: profile-readme-update'
1730
- echo "0 * * * * /bin/bash ${_cron_pr_script} update >> \"\$HOME/.aidevops/.agent-workspace/logs/profile-readme-update.log\" 2>&1 # aidevops: profile-readme-update"
1731
- ) | crontab - 2>/dev/null || true
1732
- if crontab -l 2>/dev/null | grep -qF "aidevops: profile-readme-update" 2>/dev/null; then
1733
- print_info "Profile README update enabled (cron, hourly)"
1734
- else
1735
- print_warning "Failed to install profile README update cron entry"
1736
- fi
1737
- fi
1738
- fi
1739
-
1740
- # OAuth token refresh scheduled job.
1741
- # Refreshes expired/expiring tokens every 30 min so sessions never hit
1742
- # "invalid x-api-key". Also runs at load to catch tokens that expired
1743
- # while the machine was off.
1744
- local tr_script="$HOME/.aidevops/agents/scripts/oauth-pool-helper.sh"
1745
- local tr_label="sh.aidevops.token-refresh"
1746
- if [[ -x "$tr_script" ]] && [[ -f "$HOME/.aidevops/oauth-pool.json" ]]; then
1747
- mkdir -p "$HOME/.aidevops/.agent-workspace/logs"
1748
-
1749
- if [[ "$(uname -s)" == "Darwin" ]]; then
1750
- local tr_plist="$HOME/Library/LaunchAgents/${tr_label}.plist"
1751
-
1752
- local _xml_tr_script _xml_tr_home
1753
- _xml_tr_script=$(_xml_escape "$tr_script")
1754
- _xml_tr_home=$(_xml_escape "$HOME")
1755
-
1756
- local tr_plist_content
1757
- tr_plist_content=$(
1758
- cat <<TR_PLIST
1759
- <?xml version="1.0" encoding="UTF-8"?>
1760
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1761
- <plist version="1.0">
1762
- <dict>
1763
- <key>Label</key>
1764
- <string>${tr_label}</string>
1765
- <key>ProgramArguments</key>
1766
- <array>
1767
- <string>/bin/bash</string>
1768
- <string>-c</string>
1769
- <string>${_xml_tr_script} refresh anthropic; ${_xml_tr_script} refresh openai</string>
1770
- </array>
1771
- <key>StartInterval</key>
1772
- <integer>1800</integer>
1773
- <key>StandardOutPath</key>
1774
- <string>${_xml_tr_home}/.aidevops/.agent-workspace/logs/token-refresh.log</string>
1775
- <key>StandardErrorPath</key>
1776
- <string>${_xml_tr_home}/.aidevops/.agent-workspace/logs/token-refresh.log</string>
1777
- <key>EnvironmentVariables</key>
1778
- <dict>
1779
- <key>PATH</key>
1780
- <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
1781
- <key>HOME</key>
1782
- <string>${_xml_tr_home}</string>
1783
- </dict>
1784
- <key>RunAtLoad</key>
1785
- <true/>
1786
- <key>KeepAlive</key>
1787
- <false/>
1788
- <key>ProcessType</key>
1789
- <string>Background</string>
1790
- <key>LowPriorityBackgroundIO</key>
1791
- <true/>
1792
- <key>Nice</key>
1793
- <integer>10</integer>
1794
- </dict>
1795
- </plist>
1796
- TR_PLIST
1797
- )
1798
-
1799
- if _launchd_install_if_changed "$tr_label" "$tr_plist" "$tr_plist_content"; then
1800
- print_info "OAuth token refresh enabled (launchd, every 30 min)"
1801
- else
1802
- print_warning "Failed to load token refresh LaunchAgent"
1803
- fi
1804
- else
1805
- # Linux: cron entry (every 30 min)
1806
- local _cron_tr_script
1807
- _cron_tr_script=$(_cron_escape "$tr_script")
1808
- (
1809
- crontab -l 2>/dev/null | grep -v 'aidevops: token-refresh'
1810
- echo "*/30 * * * * /bin/bash ${_cron_tr_script} refresh anthropic >> \"\$HOME/.aidevops/.agent-workspace/logs/token-refresh.log\" 2>&1; /bin/bash ${_cron_tr_script} refresh openai >> \"\$HOME/.aidevops/.agent-workspace/logs/token-refresh.log\" 2>&1 # aidevops: token-refresh"
1811
- ) | crontab - 2>/dev/null || true
1812
- if crontab -l 2>/dev/null | grep -qF "aidevops: token-refresh" 2>/dev/null; then
1813
- print_info "OAuth token refresh enabled (cron, every 30 min)"
1814
- else
1815
- print_warning "Failed to install token refresh cron entry"
1816
- fi
1817
- fi
1818
- fi
1819
-
1820
- echo ""
1821
- echo "CLI Command:"
1822
- echo " aidevops init - Initialize aidevops in a project"
1823
- echo " aidevops features - List available features"
1824
- echo " aidevops status - Check installation status"
1825
- echo " aidevops update - Update to latest version"
1826
- echo " aidevops update-tools - Check for and update installed tools"
1827
- echo " aidevops uninstall - Remove aidevops"
1828
- echo ""
1829
- echo "Deployed to:"
1830
- echo " ~/.aidevops/agents/ - Agent files (main agents, subagents, scripts)"
1831
- echo " ~/.aidevops/*-backups/ - Backups with rotation (keeps last $BACKUP_KEEP_COUNT)"
1832
- echo ""
1833
- echo "Next steps:"
1834
- echo "1. Edit configuration files in configs/ with your actual credentials"
1835
- echo "2. Setup Git CLI tools and authentication (shown during setup)"
1836
- echo "3. Setup API keys: bash .agents/scripts/setup-local-api-keys.sh setup"
1837
- echo "4. Test access: ./.agents/scripts/servers-helper.sh list"
1838
- echo "5. Enable orchestration: see runners.md 'Pulse Scheduler Setup' (autonomous task dispatch)"
1839
- echo "6. Read documentation: ~/.aidevops/agents/AGENTS.md"
1840
- echo ""
1841
- echo "For development on aidevops framework itself:"
1842
- echo " See ~/Git/aidevops/AGENTS.md"
1843
- echo ""
1844
- echo "OpenCode Primary Agents (12 total, Tab to switch):"
1845
- echo "• Plan+ - Enhanced planning with context tools (read-only)"
1846
- echo "• Build+ - Enhanced build with context tools (full access)"
1847
- echo "• Accounts, AI-DevOps, Content, Health, Legal, Marketing,"
1848
- echo " Research, Sales, SEO, WordPress"
1849
- echo ""
1850
- echo "Agent Skills (SKILL.md):"
1851
- echo "• 21 SKILL.md files generated in ~/.aidevops/agents/"
1852
- echo "• Skills include: wordpress, seo, aidevops, build-mcp, and more"
1853
- echo ""
1854
- echo "MCP Integrations (OpenCode):"
1855
- echo "• Augment Context Engine - Cloud semantic codebase retrieval"
1856
- echo "• Context7 - Real-time library documentation"
1857
- echo "• GSC - Google Search Console (MCP + OAuth2)"
1858
- echo "• Google Analytics - Analytics data (shared GSC credentials)"
1859
- echo ""
1860
- echo "SEO Integrations (curl subagents - no MCP overhead):"
1861
- echo "• DataForSEO - Comprehensive SEO data APIs"
1862
- echo "• Serper - Google Search API"
1863
- echo "• Ahrefs - Backlink and keyword data"
1864
- echo ""
1865
- echo "DSPy & DSPyGround Integration:"
1866
- echo "• ./.agents/scripts/dspy-helper.sh - DSPy prompt optimization toolkit"
1867
- echo "• ./.agents/scripts/dspyground-helper.sh - DSPyGround playground interface"
1868
- echo "• python-env/dspy-env/ - Python virtual environment for DSPy"
1869
- echo "• data/dspy/ - DSPy projects and datasets"
1870
- echo "• data/dspyground/ - DSPyGround projects and configurations"
1871
- echo ""
1872
- echo "Task Management:"
1873
- echo "• Beads CLI (bd) - Task graph visualization"
1874
- echo "• beads-sync-helper.sh - Sync TODO.md/PLANS.md with Beads"
1875
- echo "• todo-ready.sh - Show tasks with no open blockers"
1876
- echo "• Run: aidevops init beads - Initialize Beads in a project"
1877
- echo ""
1878
- echo "Autonomous Orchestration:"
1879
- echo "• Supervisor pulse - Dispatches workers, merges PRs, evaluates results"
1880
- echo "• Auto-pickup - Workers claim #auto-dispatch tasks from TODO.md"
1881
- echo "• Cross-repo visibility - Manages tasks across all repos in repos.json"
1882
- echo "• Strategic review (opus) - 4-hourly queue health, root cause analysis"
1883
- echo "• Model routing - Cost-aware: local>haiku>flash>sonnet>pro>opus"
1884
- echo "• Budget tracking - Per-provider spend limits, subscription-aware"
1885
- echo "• Session miner - Extracts learning from past sessions"
1886
- echo "• Circuit breaker - Pauses dispatch on consecutive failures"
1887
- echo ""
1888
- echo " Supervisor pulse (autonomous orchestration) requires explicit consent."
1889
- echo " Enable: aidevops config set orchestration.supervisor_pulse true && ./setup.sh"
1890
- echo ""
1891
- echo " Run /onboarding in your AI assistant to configure services interactively."
1892
- echo ""
1893
- echo "Security reminders:"
1894
- echo "- Never commit configuration files with real credentials"
1895
- echo "- Use strong passwords and enable MFA on all accounts"
1896
- echo "- Regularly rotate API tokens and SSH keys"
1897
- echo ""
1898
- echo "Happy server managing! 🚀"
1899
- echo ""
870
+ # Post-setup: auto-update, schedulers, final instructions (GH#5793)
871
+ setup_auto_update
872
+ setup_supervisor_pulse "$_os"
873
+ setup_stats_wrapper "${PULSE_ENABLED:-}"
874
+ setup_repo_sync
875
+ setup_process_guard
876
+ setup_memory_pressure_monitor
877
+ setup_screen_time_snapshot
878
+ setup_contribution_watch
879
+ setup_draft_responses
880
+ setup_profile_readme
881
+ setup_oauth_token_refresh
882
+ print_final_instructions
1900
883
 
1901
884
  # Check for tool updates if --update flag was passed
1902
885
  if [[ "$UPDATE_TOOLS_MODE" == "true" ]]; then
@@ -1904,32 +887,7 @@ TR_PLIST
1904
887
  check_tool_updates
1905
888
  fi
1906
889
 
1907
- # Offer to launch onboarding for new users (only if not running inside OpenCode and not non-interactive)
1908
- # Respects config: aidevops config set ui.onboarding_prompt false
1909
- if [[ "$NON_INTERACTIVE" != "true" ]] && [[ -z "${OPENCODE_SESSION:-}" ]] && is_feature_enabled onboarding_prompt 2>/dev/null && command -v opencode &>/dev/null; then
1910
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1911
- echo ""
1912
- echo "Ready to configure your services?"
1913
- echo ""
1914
- echo "Launch OpenCode with the onboarding wizard to:"
1915
- echo " - See which services are already configured"
1916
- echo " - Get personalized recommendations based on your work"
1917
- echo " - Set up API keys and credentials interactively"
1918
- echo ""
1919
- read -r -p "Launch OpenCode with /onboarding now? [Y/n]: " launch_onboarding
1920
- if [[ "$launch_onboarding" =~ ^[Yy]?$ || "$launch_onboarding" == "Y" ]]; then
1921
- echo ""
1922
- echo "Starting OpenCode with onboarding wizard..."
1923
- # Launch with /onboarding prompt only — don't use --agent flag because
1924
- # the "Onboarding" agent only exists after generate-opencode-agents.sh
1925
- # writes to opencode.json, which requires opencode.json to already exist.
1926
- # On first run it won't, so --agent "Onboarding" causes a fatal error.
1927
- opencode --prompt "/onboarding"
1928
- else
1929
- echo ""
1930
- echo "You can run /onboarding anytime in OpenCode to configure services."
1931
- fi
1932
- fi
890
+ setup_onboarding_prompt
1933
891
 
1934
892
  return 0
1935
893
  }