@m13v/s4l 1.6.197-rc.7

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 +1314 -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 +497 -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,61 @@
1
+ #!/bin/bash
2
+ # run-moltbook-launchd.sh — detach wrapper invoked by launchd.
3
+ #
4
+ # Why this exists:
5
+ # launchd's StartInterval silently SUPPRESSES a scheduled fire when the prior
6
+ # invocation of the same Label is still alive. The moltbook cycle has a 600s
7
+ # T0->T1 sleep plus phased posting, so a single run takes 12-25 min and
8
+ # regularly overlaps the next 15-min slot. Without this wrapper we lost
9
+ # 1-2 of every 4 fires.
10
+ #
11
+ # How it works:
12
+ # Python double-fork daemon idiom — first fork gives launchd a parent that
13
+ # exits immediately (so the job is marked complete in milliseconds), setsid
14
+ # detaches the session, second fork prevents reacquiring a controlling
15
+ # terminal, then we exec the real pipeline. macOS lacks `setsid(1)` and
16
+ # `nohup ... & disown` is not enough because launchd reaps the wrapper's
17
+ # pgid, taking the nohup child with it.
18
+ #
19
+ # Cross-cycle safety:
20
+ # run_moltbook_cycle.py uses already_posted_thread_ids() against the posts
21
+ # table at pick time, so two overlapping cycles will not double-post the
22
+ # same MoltBook thread. The 600s T0->T1 sleep further narrows the window.
23
+
24
+ REPO_DIR="$HOME/social-autoposter"
25
+ LOG_DIR="$REPO_DIR/skill/logs"
26
+ mkdir -p "$LOG_DIR"
27
+
28
+ SCRIPT="$REPO_DIR/skill/run-moltbook.sh"
29
+ OUT="$LOG_DIR/launchd-moltbook-stdout.log"
30
+ ERR="$LOG_DIR/launchd-moltbook-stderr.log"
31
+
32
+ # Preflight (added 2026-05-02): skip cleanly if Claude is blocked on a
33
+ # quota cap, or if the system is under memory pressure. See
34
+ # scripts/preflight.sh for full design.
35
+ SA_PREFLIGHT_SCRIPT="run-moltbook"
36
+ source "$REPO_DIR/scripts/preflight.sh"
37
+ preflight_skip_if_claude_blocked
38
+ preflight_skip_if_jetsam_pressure
39
+
40
+ exec /usr/bin/python3 -c "
41
+ import os, sys
42
+ script = '$SCRIPT'
43
+ out_log = '$OUT'
44
+ err_log = '$ERR'
45
+
46
+ if os.fork() != 0:
47
+ os._exit(0)
48
+ os.setsid()
49
+
50
+ if os.fork() != 0:
51
+ os._exit(0)
52
+
53
+ os.chdir('/')
54
+ out_fd = os.open(out_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
55
+ err_fd = os.open(err_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
56
+ nul_fd = os.open('/dev/null', os.O_RDONLY)
57
+ os.dup2(nul_fd, 0)
58
+ os.dup2(out_fd, 1)
59
+ os.dup2(err_fd, 2)
60
+ os.execv(script, [script])
61
+ "
@@ -0,0 +1,38 @@
1
+ #!/bin/bash
2
+ # Social Autoposter - MoltBook phased posting cycle.
3
+ #
4
+ # Delegates to scripts/run_moltbook_cycle.py which implements:
5
+ # - Phase 1: scan hot + new via MoltBook API, snapshot T0
6
+ # - Sleep 600s (T0 -> T1 momentum window)
7
+ # - Phase 2a: re-poll for T1, compute delta_score
8
+ # - Phase 2b: adaptive cap (default 2, bump to 5 when >=3 candidates
9
+ # show real-time momentum), Claude drafts, Python posts
10
+ #
11
+ # Three reduction levers baked in:
12
+ # (1) Historical (project, style) engagement block injected into the drafter prompt.
13
+ # (2) Adaptive cap gated by per-cycle momentum.
14
+ # (3) T0 -> T1 delta filter: dead threads drop out before Claude sees them.
15
+ #
16
+ # Called by launchd every 30 minutes.
17
+
18
+ set -euo pipefail
19
+
20
+ [ -f "$HOME/social-autoposter/.env" ] && source "$HOME/social-autoposter/.env"
21
+
22
+ REPO_DIR="$HOME/social-autoposter"
23
+ LOG_DIR="$REPO_DIR/skill/logs"
24
+ mkdir -p "$LOG_DIR"
25
+ LOG_FILE="$LOG_DIR/run-moltbook-$(date +%Y-%m-%d_%H%M%S).log"
26
+
27
+ # Per-cycle batch id stamped onto every claude_sessions row spawned by this
28
+ # run (via SA_CYCLE_ID env -> log_claude_session.py). 2026-05-10 cycle_id rollout.
29
+ BATCH_ID="mbcycle-$(date +%Y%m%d-%H%M%S)-$$"
30
+ export SA_CYCLE_ID="$BATCH_ID"
31
+
32
+ echo "=== MoltBook Post Run: $(date) (cycle=$BATCH_ID) ===" | tee "$LOG_FILE"
33
+
34
+ python3 "$REPO_DIR/scripts/run_moltbook_cycle.py" 2>&1 | tee -a "$LOG_FILE"
35
+
36
+ echo "=== Run complete: $(date) ===" | tee -a "$LOG_FILE"
37
+
38
+ find "$LOG_DIR" -name "run-moltbook-*.log" -mtime +7 -delete 2>/dev/null || true
@@ -0,0 +1,100 @@
1
+ #!/bin/bash
2
+ # Idempotent supervisor for the twitter-harness on-screen overlay watcher.
3
+ #
4
+ # WHAT: keeps exactly ONE `harness_overlay.py watch` process alive. That watcher
5
+ # injects the status overlay into the twitter-harness Chrome window so a human
6
+ # watching the harness sees what the pipeline is doing.
7
+ #
8
+ # WHY a supervisor: the overlay only renders WHILE the watch process runs. It was
9
+ # previously a manual, local-only process, so it never appeared on headless /
10
+ # remote installs. This script makes it self-starting from BOTH install lanes:
11
+ # - Lane A (npm/cli): launchd job `com.m13v.social-overlay-watch` (StartInterval
12
+ # 60 + RunAtLoad) re-invokes this script every minute; the pgrep guard makes
13
+ # re-invocation a no-op while the watcher is already up.
14
+ # - Lane B (.mcpb / pure MCP): the MCP calls this script on draft_cycle /
15
+ # autopilot-enable / show_browser_to_user, threading S4L_PYTHON + S4L_LOG_DIR.
16
+ #
17
+ # IDEMPOTENT: safe to call on a 60s timer. If a watcher is already running it
18
+ # exits 0 immediately. Otherwise it spawns one detached (nohup, own session) and
19
+ # returns; the spawned watcher then runs until the machine/MCP tears it down.
20
+ #
21
+ # This script is intentionally NOT locked: the overlay UX is expected to evolve.
22
+
23
+ set -u
24
+
25
+ # SAPS_->S4L_ env mirror (brand rename 2026-07-03): old plists/tasks still
26
+ # export SAPS_*; new code reads S4L_*. Copy names, never values via eval.
27
+ while IFS='=' read -r _k _; do
28
+ case "$_k" in SAPS_*) _n="S4L_${_k#SAPS_}"; eval "[ -n \"\${$_n+x}\" ] || export $_n=\"\${$_k}\"";; esac
29
+ done <<EOF_ENV
30
+ $(env | grep '^SAPS_' | cut -d= -f1 | sed 's/$/=/')
31
+ EOF_ENV
32
+
33
+ # --- resolve the repo this script lives in (works from launchd + MCP cwd) -----
34
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
35
+ REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
36
+ OVERLAY_PY="${REPO_DIR}/scripts/harness_overlay.py"
37
+
38
+ if [ ! -f "${OVERLAY_PY}" ]; then
39
+ echo "[overlay-watch] harness_overlay.py not found at ${OVERLAY_PY}; nothing to do" >&2
40
+ exit 0
41
+ fi
42
+
43
+ # --- idempotency guard: at most one watcher, ever ----------------------------
44
+ # Match the full `harness_overlay.py watch` invocation (NOT a bare `grep -r`, so
45
+ # this never trips the BSD-grep-on-FIFO hang noted in the repo CLAUDE.md).
46
+ if pgrep -f "harness_overlay.py watch" >/dev/null 2>&1; then
47
+ exit 0
48
+ fi
49
+
50
+ # --- resolve a python interpreter --------------------------------------------
51
+ # harness_overlay.py self-heals to a playwright-capable interpreter on its own
52
+ # (see _ensure_playwright_interpreter), so any python3 that exists is fine to
53
+ # launch with. Prefer the MCP-threaded S4L_PYTHON (owned uv runtime), then the
54
+ # usual suspects.
55
+ PYBIN=""
56
+ for _cand in \
57
+ "${S4L_PYTHON:-}" \
58
+ "/opt/homebrew/bin/python3.11" \
59
+ "/usr/bin/python3" \
60
+ "/opt/homebrew/bin/python3"
61
+ do
62
+ if [ -n "${_cand}" ] && [ -x "${_cand}" ]; then PYBIN="${_cand}"; break; fi
63
+ done
64
+ if [ -z "${PYBIN}" ]; then
65
+ PYBIN="$(command -v python3 2>/dev/null || true)"
66
+ fi
67
+ if [ -z "${PYBIN}" ]; then
68
+ echo "[overlay-watch] no python3 found; cannot start overlay watcher" >&2
69
+ exit 0
70
+ fi
71
+
72
+ # --- env the watcher needs ---------------------------------------------------
73
+ # S4L_LOG_DIR: where harness_overlay.py reads cycle logs to decide busy/idle.
74
+ # Default to this repo's skill/logs (MCP overrides it to the materialized repo).
75
+ export S4L_LOG_DIR="${S4L_LOG_DIR:-${REPO_DIR}/skill/logs}"
76
+ # CDP target for the twitter harness Chrome (honor BYO-Chrome installs).
77
+ export TWITTER_CDP_URL="${TWITTER_CDP_URL:-http://127.0.0.1:9555}"
78
+ mkdir -p "${S4L_LOG_DIR}" 2>/dev/null || true
79
+ WATCH_LOG="${S4L_LOG_DIR}/overlay-watch.log"
80
+
81
+ # --- spawn detached ----------------------------------------------------------
82
+ cd "${REPO_DIR}" || exit 0
83
+ echo "[overlay-watch] $(date '+%Y-%m-%d %H:%M:%S') starting watcher py=${PYBIN} cdp=${TWITTER_CDP_URL} log=${S4L_LOG_DIR}" >>"${WATCH_LOG}" 2>&1
84
+ # Spawn the watcher in a NEW SESSION so it outlives this supervisor.
85
+ # When launchd fires this script (StartInterval 60), the script is the job's
86
+ # main process; the moment it exits, launchd reaps the WHOLE job process group.
87
+ # `nohup` only blocks SIGHUP, not that group SIGKILL, so a plain `nohup ... &`
88
+ # child dies the instant we `exit 0` (it survives only when this runs from an
89
+ # interactive shell, which is NOT a launchd job). macOS ships no setsid(1), so
90
+ # use the python we already resolved to os.setsid() off the launchd group, then
91
+ # exec the real watcher. The watcher's own interpreter self-heal (os.execv)
92
+ # keeps the new session.
93
+ nohup "${PYBIN}" -c 'import os, sys
94
+ try:
95
+ os.setsid()
96
+ except OSError:
97
+ pass
98
+ os.execv(sys.argv[1], sys.argv[1:])' "${PYBIN}" "${OVERLAY_PY}" watch >>"${WATCH_LOG}" 2>&1 &
99
+ disown 2>/dev/null || true
100
+ exit 0
@@ -0,0 +1,64 @@
1
+ #!/bin/bash
2
+ # run-reddit-search-launchd.sh — detach wrapper invoked by launchd.
3
+ #
4
+ # Why this exists:
5
+ # launchd's StartInterval silently SUPPRESSES a scheduled fire when the prior
6
+ # invocation of the same Label is still alive. Reddit-search cycles vary
7
+ # (5 iterations of plan+post) and routinely run 15-25 min, so 1-3 of every
8
+ # 4 fires were getting dropped. Throughput collapsed to ~2 cycles/hr instead
9
+ # of 4.
10
+ #
11
+ # How it works:
12
+ # This wrapper is what launchd actually invokes. It uses Python's classic
13
+ # double-fork daemon idiom to spawn the real pipeline in a fresh session
14
+ # (os.setsid), then exits. macOS lacks the `setsid` binary and a plain
15
+ # `nohup ... &; disown` is NOT sufficient — launchd cleans up the wrapper's
16
+ # process group after the wrapper exits and SIGTERMs the nohup'd child too.
17
+ # The double-fork moves the cycle into its own session/pgid that launchd
18
+ # no longer tracks, so it survives wrapper exit.
19
+ #
20
+ # Net effect: launchd marks the job complete in milliseconds and fires the
21
+ # next 15-min slot on schedule. Multiple run-reddit-search.sh processes can
22
+ # now live in parallel; the reddit-browser lock (skill/lock.sh) serializes
23
+ # the brief post-phase windows, and post_reddit.py's already_posted filter
24
+ # prevents double-posting across overlapping cycles.
25
+
26
+ REPO_DIR="$HOME/social-autoposter"
27
+ LOG_DIR="$REPO_DIR/skill/logs"
28
+ mkdir -p "$LOG_DIR"
29
+
30
+ SCRIPT="$REPO_DIR/skill/run-reddit-search.sh"
31
+ OUT="$LOG_DIR/launchd-reddit-search-stdout.log"
32
+ ERR="$LOG_DIR/launchd-reddit-search-stderr.log"
33
+
34
+ # Preflight (added 2026-05-02): skip cleanly if Claude is blocked on a
35
+ # quota cap, or if the system is under memory pressure. Both paths exit 0
36
+ # with a stderr [skipped:] line so launchd treats the slot as consumed.
37
+ # See scripts/preflight.sh for full design.
38
+ SA_PREFLIGHT_SCRIPT="run-reddit-search"
39
+ source "$REPO_DIR/scripts/preflight.sh"
40
+ preflight_skip_if_claude_blocked
41
+ preflight_skip_if_jetsam_pressure
42
+
43
+ exec /usr/bin/python3 -c "
44
+ import os, sys
45
+ script = '$SCRIPT'
46
+ out_log = '$OUT'
47
+ err_log = '$ERR'
48
+
49
+ if os.fork() != 0:
50
+ os._exit(0)
51
+ os.setsid()
52
+
53
+ if os.fork() != 0:
54
+ os._exit(0)
55
+
56
+ os.chdir('/')
57
+ out_fd = os.open(out_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
58
+ err_fd = os.open(err_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
59
+ nul_fd = os.open('/dev/null', os.O_RDONLY)
60
+ os.dup2(nul_fd, 0)
61
+ os.dup2(out_fd, 1)
62
+ os.dup2(err_fd, 2)
63
+ os.execv(script, [script])
64
+ "