@m13v/s4l 1.6.197-rc.10

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.
Files changed (326) hide show
  1. package/README.md +143 -0
  2. package/SKILL.md +342 -0
  3. package/bin/cli.js +980 -0
  4. package/bin/cookie-helper.js +315 -0
  5. package/bin/platform.js +59 -0
  6. package/bin/scheduler/index.js +12 -0
  7. package/bin/scheduler/launchd.js +518 -0
  8. package/browser-agent-configs/all-agents-mcp.json +68 -0
  9. package/browser-agent-configs/linkedin-agent-mcp.json +16 -0
  10. package/browser-agent-configs/linkedin-agent.json +17 -0
  11. package/browser-agent-configs/linkedin-harness-mcp.json +21 -0
  12. package/browser-agent-configs/reddit-agent-mcp.json +16 -0
  13. package/browser-agent-configs/reddit-agent.json +17 -0
  14. package/browser-agent-configs/twitter-harness-mcp.json +18 -0
  15. package/config.example.json +45 -0
  16. package/mcp/dist/index.js +4212 -0
  17. package/mcp/dist/onboarding.js +200 -0
  18. package/mcp/dist/panel.html +176 -0
  19. package/mcp/dist/product-link.html +102 -0
  20. package/mcp/dist/repo.js +222 -0
  21. package/mcp/dist/runtime.js +1079 -0
  22. package/mcp/dist/screencast.js +323 -0
  23. package/mcp/dist/setup.js +545 -0
  24. package/mcp/dist/telemetry.js +306 -0
  25. package/mcp/dist/twitterAuth.js +138 -0
  26. package/mcp/dist/version.js +271 -0
  27. package/mcp/dist/version.json +4 -0
  28. package/mcp/install-runtime.mjs +70 -0
  29. package/mcp/install.mjs +169 -0
  30. package/mcp/manifest.json +80 -0
  31. package/mcp/menubar/dashboard_server.py +213 -0
  32. package/mcp/menubar/s4l_card.py +1336 -0
  33. package/mcp/menubar/s4l_log_relay.py +179 -0
  34. package/mcp/menubar/s4l_menubar.py +2439 -0
  35. package/mcp/menubar/s4l_state.py +891 -0
  36. package/mcp/package.json +34 -0
  37. package/mcp/shared/doctor.cjs +437 -0
  38. package/mcp/shared/onboarding-ledger.cjs +324 -0
  39. package/mcp-servers/browser-harness/server.py +968 -0
  40. package/package.json +160 -0
  41. package/requirements.txt +20 -0
  42. package/scripts/_compute_allowlist.py +58 -0
  43. package/scripts/_db_update.py +20 -0
  44. package/scripts/_filt.py +9 -0
  45. package/scripts/_li_notif_match.py +76 -0
  46. package/scripts/_li_notif_orchestrate.py +126 -0
  47. package/scripts/_lock_preempt_test.py +60 -0
  48. package/scripts/_run_icp_precheck.py +57 -0
  49. package/scripts/a16z_pearx_calendar_reminders.py +99 -0
  50. package/scripts/account_resolver.py +141 -0
  51. package/scripts/active_campaigns.py +114 -0
  52. package/scripts/active_users.py +190 -0
  53. package/scripts/amplitude_24h_signups.py +468 -0
  54. package/scripts/amplitude_signups.py +177 -0
  55. package/scripts/apply_onboarding_selections.py +131 -0
  56. package/scripts/audience_pages.py +243 -0
  57. package/scripts/audit_helper.py +120 -0
  58. package/scripts/author_history_block.py +353 -0
  59. package/scripts/autopilot_stall_watch.py +284 -0
  60. package/scripts/backfill_twitter_attempts_topic.py +81 -0
  61. package/scripts/backfill_twitter_log_post_no_id.py +322 -0
  62. package/scripts/bench_dashboard.sh +138 -0
  63. package/scripts/bh_send.py +39 -0
  64. package/scripts/build_persona.py +409 -0
  65. package/scripts/bulk_icp.py +18 -0
  66. package/scripts/campaign_bump.py +51 -0
  67. package/scripts/capture_thread_media.py +288 -0
  68. package/scripts/check_browser_lock_health.sh +81 -0
  69. package/scripts/check_external_pool_depth.py +253 -0
  70. package/scripts/check_unread_web_chats.py +28 -0
  71. package/scripts/claim_web_chat.py +47 -0
  72. package/scripts/classify_run_error.py +158 -0
  73. package/scripts/claude_job.py +988 -0
  74. package/scripts/clean_stale_singleton.sh +56 -0
  75. package/scripts/cleanup_harness_tabs.py +68 -0
  76. package/scripts/copy_browser_cookies.py +454 -0
  77. package/scripts/counterparty_history.py +350 -0
  78. package/scripts/db.py +57 -0
  79. package/scripts/discover_claude_profiles.py +120 -0
  80. package/scripts/discover_linkedin_candidates.py +984 -0
  81. package/scripts/dm_conversation.py +682 -0
  82. package/scripts/dm_db_update.py +69 -0
  83. package/scripts/dm_engage_helper.py +161 -0
  84. package/scripts/dm_outreach_helper.py +147 -0
  85. package/scripts/dm_outreach_twitter_helper.py +129 -0
  86. package/scripts/dm_send_log.py +106 -0
  87. package/scripts/dm_short_links.py +1084 -0
  88. package/scripts/dump_web_chat_history.py +47 -0
  89. package/scripts/engage_github.py +640 -0
  90. package/scripts/engage_reddit.py +1235 -0
  91. package/scripts/engage_twitter_helper.py +301 -0
  92. package/scripts/engagement_styles.py +1787 -0
  93. package/scripts/enrich_twitter_candidates.py +82 -0
  94. package/scripts/feedback_digest.py +448 -0
  95. package/scripts/fetch_prospect_profile.py +312 -0
  96. package/scripts/fetch_twitter_t1.py +134 -0
  97. package/scripts/find_threads.py +530 -0
  98. package/scripts/follow_gate_log.py +59 -0
  99. package/scripts/funnel_per_day.py +194 -0
  100. package/scripts/generate_daily_human_style.py +494 -0
  101. package/scripts/generation_trace.py +173 -0
  102. package/scripts/get_run_cost.py +107 -0
  103. package/scripts/github_engage_helper.py +93 -0
  104. package/scripts/github_tools.py +509 -0
  105. package/scripts/harness_overlay.py +556 -0
  106. package/scripts/harvest_twitter_following.py +243 -0
  107. package/scripts/heartbeat.sh +70 -0
  108. package/scripts/history_context.py +284 -0
  109. package/scripts/http_api.py +206 -0
  110. package/scripts/human_dm_replies_helper.py +169 -0
  111. package/scripts/identity.py +302 -0
  112. package/scripts/ig_batch_creator.sh +93 -0
  113. package/scripts/ig_post_type_picker.py +243 -0
  114. package/scripts/ig_scrape_transcribe.sh +91 -0
  115. package/scripts/ingest_human_dm_replies.py +271 -0
  116. package/scripts/ingest_web_chat_replies.py +229 -0
  117. package/scripts/install_fleet.py +187 -0
  118. package/scripts/invent_mcp_server.py +350 -0
  119. package/scripts/invent_topics.py +1462 -0
  120. package/scripts/learned_preferences.py +263 -0
  121. package/scripts/li_discovery.py +161 -0
  122. package/scripts/link_edit_helper.py +142 -0
  123. package/scripts/link_tail.py +592 -0
  124. package/scripts/linkedin_api.py +561 -0
  125. package/scripts/linkedin_browser.py +730 -0
  126. package/scripts/linkedin_cooldown.py +128 -0
  127. package/scripts/linkedin_exclusions.py +234 -0
  128. package/scripts/linkedin_killswitch.py +1333 -0
  129. package/scripts/linkedin_search_topic_schema.py +49 -0
  130. package/scripts/linkedin_unipile.py +658 -0
  131. package/scripts/linkedin_url.py +228 -0
  132. package/scripts/log_claude_session.py +636 -0
  133. package/scripts/log_draft.py +143 -0
  134. package/scripts/log_linkedin_search_attempts.py +126 -0
  135. package/scripts/log_post.py +651 -0
  136. package/scripts/log_run.py +364 -0
  137. package/scripts/log_thread_media.py +108 -0
  138. package/scripts/log_twitter_search_attempts.py +150 -0
  139. package/scripts/log_twitter_skips.py +211 -0
  140. package/scripts/lookup_post.py +78 -0
  141. package/scripts/mark_web_chat_processed.py +32 -0
  142. package/scripts/mcp_lock_proxy.py +370 -0
  143. package/scripts/memory_snapshot.py +972 -0
  144. package/scripts/merge_review_queue.py +215 -0
  145. package/scripts/mint_external_pool.py +182 -0
  146. package/scripts/mint_kent_pool.py +249 -0
  147. package/scripts/moltbook_post.py +320 -0
  148. package/scripts/moltbook_tools.py +159 -0
  149. package/scripts/pending_threads.py +188 -0
  150. package/scripts/pick_ig_account.py +177 -0
  151. package/scripts/pick_project.py +208 -0
  152. package/scripts/pick_search_topic.py +771 -0
  153. package/scripts/pick_thread_target.py +279 -0
  154. package/scripts/pick_twitter_thread_target.py +202 -0
  155. package/scripts/podlog_fetch_batch.sh +32 -0
  156. package/scripts/post_github.py +1311 -0
  157. package/scripts/post_reddit.py +2668 -0
  158. package/scripts/precompute_dashboard_stats.py +204 -0
  159. package/scripts/preflight.sh +297 -0
  160. package/scripts/progress.py +88 -0
  161. package/scripts/project_excludes.py +353 -0
  162. package/scripts/project_slugs.py +91 -0
  163. package/scripts/project_stats.py +241 -0
  164. package/scripts/project_stats_json.py +1563 -0
  165. package/scripts/project_topics.py +192 -0
  166. package/scripts/qualified_query_bank.py +436 -0
  167. package/scripts/reap_stale_claude_sessions.py +867 -0
  168. package/scripts/reddit_browser.py +2549 -0
  169. package/scripts/reddit_browser_fetch.py +141 -0
  170. package/scripts/reddit_browser_lock.py +593 -0
  171. package/scripts/reddit_chat_sync.py +710 -0
  172. package/scripts/reddit_query_bank.py +200 -0
  173. package/scripts/reddit_threads_helper.py +151 -0
  174. package/scripts/reddit_tools.py +956 -0
  175. package/scripts/refresh_instagram_tokens.py +280 -0
  176. package/scripts/release-mcpb.sh +513 -0
  177. package/scripts/reply_db.py +334 -0
  178. package/scripts/reply_insert.py +98 -0
  179. package/scripts/reply_risk_digest.py +761 -0
  180. package/scripts/reset-test-machine.sh +602 -0
  181. package/scripts/restore_twitter_session.py +177 -0
  182. package/scripts/ripen_reddit_plan.py +478 -0
  183. package/scripts/run_claude.sh +433 -0
  184. package/scripts/run_moltbook_cycle.py +555 -0
  185. package/scripts/s4l_box_update.sh +226 -0
  186. package/scripts/s4l_channel.py +103 -0
  187. package/scripts/s4l_ctl.sh +75 -0
  188. package/scripts/s4l_env.py +47 -0
  189. package/scripts/saps_activity.py +126 -0
  190. package/scripts/saps_mode.py +328 -0
  191. package/scripts/scan_dm_candidates.py +580 -0
  192. package/scripts/scan_github_replies.py +168 -0
  193. package/scripts/scan_instagram_comments.py +481 -0
  194. package/scripts/scan_moltbook_replies.py +252 -0
  195. package/scripts/scan_pii.py +190 -0
  196. package/scripts/scan_reddit_replies.py +377 -0
  197. package/scripts/scan_twitter_mentions_browser.py +327 -0
  198. package/scripts/scan_twitter_thread_followups.py +299 -0
  199. package/scripts/scan_x_profile.py +384 -0
  200. package/scripts/schedule_state.py +202 -0
  201. package/scripts/scheduled_tasks_snapshot.py +123 -0
  202. package/scripts/score_linkedin_candidates.py +419 -0
  203. package/scripts/score_twitter_candidates.py +718 -0
  204. package/scripts/scrape_linkedin_comment_stats.py +1755 -0
  205. package/scripts/scrape_linkedin_stats_browser.py +52 -0
  206. package/scripts/scrape_reddit_views.py +365 -0
  207. package/scripts/seed_search_queries.py +453 -0
  208. package/scripts/seed_search_topics.py +127 -0
  209. package/scripts/send_web_chat_reply.py +130 -0
  210. package/scripts/sentry_init.py +128 -0
  211. package/scripts/setup_twitter_auth.py +1320 -0
  212. package/scripts/snapshot.py +583 -0
  213. package/scripts/stats.py +2702 -0
  214. package/scripts/stats_helper.py +52 -0
  215. package/scripts/strike_alert.py +783 -0
  216. package/scripts/sweep_post_link_clicks.py +107 -0
  217. package/scripts/sync_ig_to_posts.py +147 -0
  218. package/scripts/test_browser_lock.py +189 -0
  219. package/scripts/test_installation_api.sh +52 -0
  220. package/scripts/test_percard_posting.py +142 -0
  221. package/scripts/top_dud_linkedin_queries.py +71 -0
  222. package/scripts/top_dud_reddit_queries.py +67 -0
  223. package/scripts/top_dud_twitter_queries.py +71 -0
  224. package/scripts/top_dud_twitter_topics.py +102 -0
  225. package/scripts/top_linkedin_queries.py +55 -0
  226. package/scripts/top_omitted_reddit_topics.py +91 -0
  227. package/scripts/top_performers.py +588 -0
  228. package/scripts/top_search_topics.py +180 -0
  229. package/scripts/top_twitter_queries.py +190 -0
  230. package/scripts/twitter_access_check.py +382 -0
  231. package/scripts/twitter_account.py +41 -0
  232. package/scripts/twitter_batch_phase.py +126 -0
  233. package/scripts/twitter_browser.py +2804 -0
  234. package/scripts/twitter_cookie_mirror.py +130 -0
  235. package/scripts/twitter_cycle_helper.py +310 -0
  236. package/scripts/twitter_gen_links.py +287 -0
  237. package/scripts/twitter_post_plan.py +1188 -0
  238. package/scripts/twitter_scan.py +324 -0
  239. package/scripts/twitter_supply_signal.py +57 -0
  240. package/scripts/twitter_threads_helper.py +152 -0
  241. package/scripts/unclaim_web_chat.py +29 -0
  242. package/scripts/update_instagram_stats.py +261 -0
  243. package/scripts/update_linkedin_stats_from_feed.py +328 -0
  244. package/scripts/version.py +72 -0
  245. package/scripts/watchdog_hung_runs.py +343 -0
  246. package/scripts/write_generation_trace.py +73 -0
  247. package/setup/SKILL.md +277 -0
  248. package/skill/amplitude-24h-signups.sh +38 -0
  249. package/skill/archive-old-logs.sh +40 -0
  250. package/skill/audit-dm-staleness.sh +42 -0
  251. package/skill/audit-linkedin.sh +14 -0
  252. package/skill/audit-moltbook.sh +4 -0
  253. package/skill/audit-reddit-resurrect.sh +67 -0
  254. package/skill/audit-reddit.sh +4 -0
  255. package/skill/audit-twitter.sh +4 -0
  256. package/skill/audit.sh +287 -0
  257. package/skill/backfill-twitter-attempts-topic.sh +19 -0
  258. package/skill/backfill-twitter-ghost-posts.sh +24 -0
  259. package/skill/check-external-pool-depth.sh +7 -0
  260. package/skill/check-web-chats.sh +203 -0
  261. package/skill/dm-outreach-linkedin.sh +250 -0
  262. package/skill/dm-outreach-reddit.sh +274 -0
  263. package/skill/dm-outreach-twitter.sh +265 -0
  264. package/skill/engage-dm-replies-linkedin.sh +4 -0
  265. package/skill/engage-dm-replies-reddit.sh +4 -0
  266. package/skill/engage-dm-replies-twitter.sh +4 -0
  267. package/skill/engage-dm-replies.sh +1597 -0
  268. package/skill/engage-linkedin.sh +581 -0
  269. package/skill/engage-moltbook.sh +36 -0
  270. package/skill/engage-reddit.sh +146 -0
  271. package/skill/engage-twitter.sh +467 -0
  272. package/skill/github-engage.sh +176 -0
  273. package/skill/ingest-web-chat-replies.sh +38 -0
  274. package/skill/invent-supply-test.sh +100 -0
  275. package/skill/invent-topics.sh +50 -0
  276. package/skill/lib/linkedin-backend.sh +364 -0
  277. package/skill/lib/platform.sh +48 -0
  278. package/skill/lib/reddit-backend.sh +234 -0
  279. package/skill/lib/twitter-backend.sh +314 -0
  280. package/skill/link-edit-github.sh +136 -0
  281. package/skill/link-edit-moltbook.sh +117 -0
  282. package/skill/link-edit-reddit.sh +201 -0
  283. package/skill/linkedin-presence.sh +182 -0
  284. package/skill/linkedin-recovery.sh +282 -0
  285. package/skill/lock.sh +647 -0
  286. package/skill/memory-snapshot.sh +39 -0
  287. package/skill/precompute-stats.sh +35 -0
  288. package/skill/prewarm-funnel.sh +104 -0
  289. package/skill/refresh-instagram-tokens.sh +57 -0
  290. package/skill/refresh-twitter-following.sh +52 -0
  291. package/skill/reply-risk-digest.sh +31 -0
  292. package/skill/run-cycle-update-guard.sh +44 -0
  293. package/skill/run-draft-and-publish.sh +123 -0
  294. package/skill/run-generate-daily-style.sh +50 -0
  295. package/skill/run-github-launchd.sh +62 -0
  296. package/skill/run-github.sh +102 -0
  297. package/skill/run-instagram-daily.sh +149 -0
  298. package/skill/run-instagram-render.sh +875 -0
  299. package/skill/run-linkedin-launchd.sh +81 -0
  300. package/skill/run-linkedin-unipile.sh +130 -0
  301. package/skill/run-linkedin.sh +1593 -0
  302. package/skill/run-moltbook-launchd.sh +61 -0
  303. package/skill/run-moltbook.sh +38 -0
  304. package/skill/run-overlay-watch.sh +100 -0
  305. package/skill/run-reddit-search-launchd.sh +64 -0
  306. package/skill/run-reddit-search.sh +505 -0
  307. package/skill/run-reddit-threads-double.sh +32 -0
  308. package/skill/run-reddit-threads.sh +847 -0
  309. package/skill/run-scan-moltbook-replies.sh +57 -0
  310. package/skill/run-twitter-cycle-launchd.sh +63 -0
  311. package/skill/run-twitter-cycle-singleton.sh +62 -0
  312. package/skill/run-twitter-cycle.sh +2408 -0
  313. package/skill/run-twitter-threads.sh +592 -0
  314. package/skill/scan-instagram-replies.sh +61 -0
  315. package/skill/scan-twitter-followups.sh +57 -0
  316. package/skill/social-autoposter-update.sh +66 -0
  317. package/skill/stats-instagram.sh +72 -0
  318. package/skill/stats-linkedin.sh +271 -0
  319. package/skill/stats-moltbook.sh +4 -0
  320. package/skill/stats-reddit.sh +4 -0
  321. package/skill/stats-twitter.sh +4 -0
  322. package/skill/stats.sh +521 -0
  323. package/skill/strike-alert.sh +18 -0
  324. package/skill/styles.sh +87 -0
  325. package/skill/sweep-link-clicks.sh +40 -0
  326. package/skill/topics.sh +51 -0
@@ -0,0 +1,57 @@
1
+ #!/bin/bash
2
+ # Social Autoposter - Moltbook reply scanner
3
+ # Runs scan_moltbook_replies.py on its own launchd schedule.
4
+ # Pure API; typically finishes in <1min, so uses a short 15min lock wait.
5
+
6
+
7
+ set -euo pipefail
8
+
9
+ source "$(dirname "$0")/lock.sh"
10
+ acquire_lock "scan-moltbook-replies" 900
11
+
12
+ [ -f "$HOME/social-autoposter/.env" ] && source "$HOME/social-autoposter/.env"
13
+
14
+ REPO_DIR="$HOME/social-autoposter"
15
+ LOG_DIR="$REPO_DIR/skill/logs"
16
+ mkdir -p "$LOG_DIR"
17
+ LOG_FILE="$LOG_DIR/run-scan-moltbook-replies-$(date +%Y-%m-%d_%H%M%S).log"
18
+
19
+ echo "=== Scan Moltbook Replies Run: $(date) ===" | tee "$LOG_FILE"
20
+ START_TS=$(date +%s)
21
+
22
+ PYTHONUNBUFFERED=1 python3 "$REPO_DIR/scripts/scan_moltbook_replies.py" 2>&1 | tee -a "$LOG_FILE" || true
23
+
24
+ ELAPSED=$(( $(date +%s) - START_TS ))
25
+ # grep -c prints "0" AND exits 1 on zero matches, so `|| echo 0` was
26
+ # appending a second "0" and making FOUND multiline, which silently broke
27
+ # log_run.py. Use `|| FOUND=0` so the fallback only fires when the file is
28
+ # unreadable.
29
+ FOUND=$(grep -ci "new repl" "$LOG_FILE" 2>/dev/null) || FOUND=0
30
+ # Pull scan-stage counters out of the "Notification scan complete:" line that
31
+ # scan_moltbook_replies.py prints. Same key format as Reddit's scanner. Renders
32
+ # as scanned/new/excluded pills on the dashboard scan-replies row so empty
33
+ # cycles read as "scanned N / 0 new" instead of all-zeros.
34
+ SCAN_LINE=$(grep -m1 "^Notification scan complete:" "$LOG_FILE" 2>/dev/null || true)
35
+ SCAN_ARG=""
36
+ if [ -n "$SCAN_LINE" ]; then
37
+ scan_seen=$(echo "$SCAN_LINE" | grep -oE "seen=[0-9]+" | head -1 | cut -d= -f2)
38
+ scan_new=$(echo "$SCAN_LINE" | grep -oE "new_pending=[0-9]+" | head -1 | cut -d= -f2)
39
+ scan_excl=$(echo "$SCAN_LINE" | grep -oE "excluded_author=[0-9]+" | head -1 | cut -d= -f2)
40
+ scan_self=$(echo "$SCAN_LINE" | grep -oE "self_filtered=[0-9]+" | head -1 | cut -d= -f2)
41
+ scan_back=$(echo "$SCAN_LINE" | grep -oE "backfill_skipped=[0-9]+" | head -1 | cut -d= -f2)
42
+ scan_excl_total=$(( ${scan_excl:-0} + ${scan_self:-0} ))
43
+ parts=""
44
+ [ -n "$scan_seen" ] && parts="${parts}scanned=${scan_seen},"
45
+ [ -n "$scan_new" ] && parts="${parts}new=${scan_new},"
46
+ [ "$scan_excl_total" -gt 0 ] && parts="${parts}excluded=${scan_excl_total},"
47
+ [ -n "$scan_back" ] && parts="${parts}backfill=${scan_back},"
48
+ SCAN_ARG="${parts%,}"
49
+ fi
50
+ if [ -n "$SCAN_ARG" ]; then
51
+ python3 "$REPO_DIR/scripts/log_run.py" --script "scan_moltbook_replies" --posted "$FOUND" --skipped 0 --failed 0 --cost 0 --elapsed "$ELAPSED" --scan "$SCAN_ARG" || true
52
+ else
53
+ python3 "$REPO_DIR/scripts/log_run.py" --script "scan_moltbook_replies" --posted "$FOUND" --skipped 0 --failed 0 --cost 0 --elapsed "$ELAPSED" || true
54
+ fi
55
+
56
+ echo "=== Scan Moltbook Replies complete: $(date) (elapsed ${ELAPSED}s) ===" | tee -a "$LOG_FILE"
57
+ find "$LOG_DIR" -name "run-scan-moltbook-replies-*.log" -mtime +7 -delete 2>/dev/null || true
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+ # run-twitter-cycle-launchd.sh — detach wrapper invoked by launchd.
3
+ #
4
+ # Why this exists:
5
+ # launchd's StartCalendarInterval silently SUPPRESSES a scheduled fire when
6
+ # the prior invocation of the same Label is still alive. The Twitter cycle
7
+ # takes 25-45 min wall-clock (Phase 2b-gen runs SEO landing-page generation
8
+ # per candidate, ~5-8 min each, lock-free). At a 15-min cadence that means
9
+ # 2-3 of every 4 fires get dropped on the floor; throughput collapses to
10
+ # 1 cycle/45min instead of 4 cycles/hr.
11
+ #
12
+ # How it works:
13
+ # This wrapper is what launchd actually invokes. It uses Python's classic
14
+ # double-fork daemon idiom to spawn the real pipeline in a fresh session
15
+ # (os.setsid), then exits. macOS lacks the `setsid` binary and a plain
16
+ # `nohup ... &; disown` is NOT sufficient — launchd cleans up the wrapper's
17
+ # process group after the wrapper exits and SIGTERMs the nohup'd child too.
18
+ # The double-fork moves the cycle into its own session/pgid that launchd
19
+ # no longer tracks, so it survives wrapper exit.
20
+ #
21
+ # Net effect: launchd marks the job complete in milliseconds and fires the
22
+ # next :00/:15/:30/:45 slot on schedule. Multiple run-twitter-cycle.sh
23
+ # processes can now live in parallel; the twitter-browser lock
24
+ # (skill/lock.sh, 3600s timeout) handles the brief browser-using windows
25
+ # (Phase 1 scrape, Phase 2b-prep draft, Phase 2c post — minutes each), and
26
+ # Phase 0 is guarded by a Postgres advisory lock to prevent salvage-UPDATE
27
+ # races between overlapping cycles.
28
+
29
+ REPO_DIR="$HOME/social-autoposter"
30
+ LOG_DIR="$REPO_DIR/skill/logs"
31
+ mkdir -p "$LOG_DIR"
32
+
33
+ SCRIPT="$REPO_DIR/skill/run-twitter-cycle.sh"
34
+ OUT="$LOG_DIR/launchd-twitter-cycle-stdout.log"
35
+ ERR="$LOG_DIR/launchd-twitter-cycle-stderr.log"
36
+
37
+ exec /usr/bin/python3 -c "
38
+ import os, sys
39
+ script = '$SCRIPT'
40
+ out_log = '$OUT'
41
+ err_log = '$ERR'
42
+
43
+ # First fork — parent (launchd's tracked process) exits immediately so
44
+ # launchd marks the job complete. Child continues as session leader.
45
+ if os.fork() != 0:
46
+ os._exit(0)
47
+ os.setsid()
48
+
49
+ # Second fork — guarantees the daemon cannot reacquire a controlling
50
+ # terminal and detaches one level further from any process-group cleanup.
51
+ if os.fork() != 0:
52
+ os._exit(0)
53
+
54
+ # Grandchild: redirect stdio, exec the real cycle script.
55
+ os.chdir('/')
56
+ out_fd = os.open(out_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
57
+ err_fd = os.open(err_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
58
+ nul_fd = os.open('/dev/null', os.O_RDONLY)
59
+ os.dup2(nul_fd, 0)
60
+ os.dup2(out_fd, 1)
61
+ os.dup2(err_fd, 2)
62
+ os.execv(script, [script])
63
+ "
@@ -0,0 +1,62 @@
1
+ #!/bin/bash
2
+ # run-twitter-cycle-singleton.sh — launchd entry wrapper for the twitter
3
+ # post-comments cycle.
4
+ #
5
+ # CONCURRENCY: this wrapper NO LONGER enforces one-at-a-time. As of 2026-06-01
6
+ # that is handled inside run-twitter-cycle.sh itself via:
7
+ # preflight_acquire_slot_or_skip "twitter-cycle" 1
8
+ # which EVERY launch path hits (launchd, MCP draft_cycle/autopilot, manual
9
+ # `bash skill/run-twitter-cycle.sh`). The old guard that used to live here
10
+ # (Phase 0 external-cycle detect + the /tmp/sa-twitter-cycle-singleton.lock
11
+ # mkdir claim) only governed the launchd path, never the MCP/manual paths, so
12
+ # it let out-of-band cycles slip through and then conflicted with the in-script
13
+ # gate. It was redundant belt-and-suspenders and is removed. Single source of
14
+ # truth for concurrency is now the preflight slot gate.
15
+ #
16
+ # SNAPSHOT (the one load-bearing job left here): copy the cycle script to a temp
17
+ # file and run THAT, so an in-place edit of run-twitter-cycle.sh mid-run (e.g.
18
+ # the auto-commit agent committing a rewrite) cannot corrupt bash's byte-offset
19
+ # execution of a live cycle. run-twitter-cycle.sh hardcodes
20
+ # REPO_DIR="$HOME/social-autoposter" (no $0/BASH_SOURCE), so the /tmp copy
21
+ # resolves the repo identically.
22
+ # 2026-05-28 incident: commit 0ac29141 landed mid-run, byte-offset misaligned,
23
+ # the cycle skipped its unconditional release_lock + Variant-A sleep, then
24
+ # re-acquired its own still-held twitter-browser lock and self-deadlocked.
25
+ #
26
+ # NEVER KILL: per user instruction 2026-05-22, this wrapper does not
27
+ # SIGTERM/SIGKILL anything. With concurrency now enforced by the slot gate,
28
+ # there is nothing to preempt anyway.
29
+ #
30
+ # Logs:
31
+ # - skill/logs/twitter-cycle-singleton.log -> wrapper start/done + snapshot events
32
+ # - launchd stdout/stderr (configured in the plist) receive the cycle's output
33
+
34
+ set -u
35
+
36
+ REPO_DIR="$HOME/social-autoposter"
37
+ LOG_DIR="$REPO_DIR/skill/logs"
38
+ SINGLETON_LOG="$LOG_DIR/twitter-cycle-singleton.log"
39
+ CYCLE_SCRIPT="$REPO_DIR/skill/run-twitter-cycle.sh"
40
+ SNAPSHOT=""
41
+
42
+ mkdir -p "$LOG_DIR"
43
+
44
+ ts() { date '+%Y-%m-%d %H:%M:%S'; }
45
+ log() { echo "[$(ts)] $*" >> "$SINGLETON_LOG"; }
46
+
47
+ # Release the snapshot temp file on any exit path.
48
+ trap 'rm -f "$SNAPSHOT"; log "[wrapper] done pid=$$ rc=${EXIT_CODE:-?}"' EXIT
49
+
50
+ log "[wrapper] start pid=$$"
51
+
52
+ SNAPSHOT="/tmp/sa-twitter-cycle-snapshot-$$.sh"
53
+ cp "$CYCLE_SCRIPT" "$SNAPSHOT"
54
+ if ! /bin/bash -n "$SNAPSHOT" 2>/dev/null; then
55
+ log "[wrapper] snapshot syntax check failed (caught mid-edit?); aborting, launchd retries next fire"
56
+ exit 1
57
+ fi
58
+
59
+ # Foreground run of the snapshot. Do NOT exec (so the trap fires on completion).
60
+ /bin/bash "$SNAPSHOT"
61
+ EXIT_CODE=$?
62
+ exit "$EXIT_CODE"