@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,102 @@
1
+ #!/bin/bash
2
+ # Social Autoposter - GitHub Issues momentum-gated posting.
3
+ #
4
+ # Delegates to scripts/post_github.py which implements:
5
+ # - Phase 1: gh search across project topics, snapshot T0 comment + reaction counts
6
+ # - Sleep (T0 -> T1 momentum window, default 600s, owned by post_github --sleep)
7
+ # - Phase 2a: re-fetch same issues, compute delta_score
8
+ # - Phase 2b: adaptive cap (default 1, bump to 3 when >=3 candidates show momentum),
9
+ # Claude drafts (one-shot, no Bash tools), Python posts via `gh issue comment`
10
+ # and persists search_topic, language, engagement_style to the posts table.
11
+ #
12
+ # Reduction levers baked in:
13
+ # (1) Historical (project, style) engagement block in drafter prompt.
14
+ # (2) top_search_topics feedback so high-scoring seeds get preferred.
15
+ # (3) Adaptive cap gated by per-cycle momentum.
16
+ # (4) T0 -> T1 delta filter: stale issues drop out before Claude sees them.
17
+ # (5) Pre-filter eliminates Claude's tool budget; one JSON in one shot.
18
+ #
19
+ # Called by launchd. Cadence is owned by the .plist, not this script.
20
+
21
+ set -euo pipefail
22
+
23
+ [ -f "$HOME/social-autoposter/.env" ] && source "$HOME/social-autoposter/.env"
24
+
25
+ REPO_DIR="$HOME/social-autoposter"
26
+ LOG_DIR="$REPO_DIR/skill/logs"
27
+ mkdir -p "$LOG_DIR"
28
+ LOG_FILE="$LOG_DIR/github-$(date +%Y-%m-%d_%H%M%S).log"
29
+ RUN_START=$(date +%s)
30
+
31
+ # Per-cycle batch id stamped onto every claude_sessions row spawned by this
32
+ # run (via SA_CYCLE_ID env -> log_claude_session.py). Lets the dashboard /
33
+ # get_run_cost.py --cycle-id report exact per-cycle cost instead of the
34
+ # legacy script+since query which bleeds costs across concurrent runs.
35
+ # 2026-05-10 cycle_id rollout.
36
+ BATCH_ID="ghcycle-$(date +%Y%m%d-%H%M%S)-$$"
37
+ export SA_CYCLE_ID="$BATCH_ID"
38
+
39
+ # Idempotent run_monitor.log emitter wired to EXIT/INT/TERM/HUP. Without this,
40
+ # a SIGTERM landing during the post_github.py loop (after `gh issue comment`
41
+ # committed a comment but before the script's own log_run.py call at the
42
+ # bottom of main()) silently drops the run from run_monitor.log. The
43
+ # dashboard reads run_monitor.log, so the operator-visible "last post_github
44
+ # cycle" stays stuck on a stale entry while real comments continue landing
45
+ # unrecorded. Mirrors the same fix shipped to run-reddit-search.sh,
46
+ # run-twitter-cycle.sh, and run-linkedin.sh.
47
+ #
48
+ # Detection mechanism: post_github.py writes a "=== SUMMARY: elapsed=" line
49
+ # to stdout (teed into $LOG_FILE) IMMEDIATELY before its own log_run.py
50
+ # call. Presence of that marker in $LOG_FILE means Python wrote its own
51
+ # summary, so the trap no-ops. Absence means SIGTERM landed before Python
52
+ # could finish, and the trap emits a "sigterm:1" placeholder so the cycle
53
+ # is at least visible on the dashboard.
54
+ #
55
+ # We can't re-derive accurate POSTED counts from the DB at trap-time the way
56
+ # linkedin/twitter do — github posts go through `gh issue comment` and only
57
+ # get a posts-table row from inside post_github.py's log_post call, so a
58
+ # SIGTERM mid-loop may leave 1-2 comments live on github.com with no posts
59
+ # row yet. The placeholder marks the cycle as failed=1 + sigterm:1 so the
60
+ # operator notices; manual reconciliation if the count matters.
61
+ _SA_RUN_SUMMARY_EMITTED=0
62
+ _sa_emit_run_summary_oneshot() {
63
+ [ "${_SA_RUN_SUMMARY_EMITTED:-0}" = "1" ] && return 0
64
+ _SA_RUN_SUMMARY_EMITTED=1
65
+ # Python wrote its own summary? Trust it.
66
+ if [ -n "${LOG_FILE:-}" ] && [ -f "${LOG_FILE:-}" ] \
67
+ && grep -q "=== SUMMARY: elapsed=" "$LOG_FILE" 2>/dev/null; then
68
+ return 0
69
+ fi
70
+ local elapsed cost
71
+ elapsed=$(( $(date +%s) - ${RUN_START:-$(date +%s)} ))
72
+ cost=$(timeout 10 python3 "$REPO_DIR/scripts/get_run_cost.py" \
73
+ --since "${RUN_START:-0}" \
74
+ --scripts "post_github" \
75
+ 2>/dev/null || echo "0.0000")
76
+ # Classify the most likely cause from the cycle log before falling back to
77
+ # the generic "sigterm:1" placeholder. Picks up Anthropic-side failures
78
+ # (stream_idle_timeout, monthly_limit, api_overloaded, context_overflow,
79
+ # credit_balance) that would otherwise read as a silent sigterm row on the
80
+ # dashboard. Falls back to sigterm:1 when no API marker is present (true
81
+ # external SIGTERM, e.g. cycle budget exceeded by watchdog_hung_runs).
82
+ local trap_reason
83
+ trap_reason=""
84
+ if [ -n "${LOG_FILE:-}" ] && [ -f "${LOG_FILE:-}" ]; then
85
+ trap_reason=$(python3 "$REPO_DIR/scripts/classify_run_error.py" "$LOG_FILE" 2>/dev/null)
86
+ fi
87
+ [ -z "$trap_reason" ] && trap_reason="sigterm"
88
+ python3 "$REPO_DIR/scripts/log_run.py" \
89
+ --script post_github \
90
+ --posted 0 --skipped 0 --failed 1 \
91
+ --cost "$cost" --elapsed "$elapsed" \
92
+ --failure-reasons "${trap_reason}:1" 2>/dev/null || true
93
+ }
94
+ trap _sa_emit_run_summary_oneshot EXIT INT TERM HUP
95
+
96
+ echo "=== GitHub Issues Run: $(date) ===" | tee "$LOG_FILE"
97
+
98
+ python3 "$REPO_DIR/scripts/post_github.py" 2>&1 | tee -a "$LOG_FILE"
99
+
100
+ echo "=== Run complete: $(date) ===" | tee -a "$LOG_FILE"
101
+
102
+ find "$LOG_DIR" -name "github-*.log" -mtime +7 -delete 2>/dev/null || true
@@ -0,0 +1,149 @@
1
+ #!/bin/bash
2
+ # run-instagram-daily.sh — Pick + post one Instagram Reel per fire.
3
+ #
4
+ # Cadence (set by launchd, com.m13v.social-instagram-daily.plist):
5
+ # 5 fires/day at 09:00, 12:00, 15:00, 18:00, 21:00 local time.
6
+ #
7
+ # Per-fire logic:
8
+ # 1. acquire_lock instagram-poster (file lock; IG is HTTP-only, no browser)
9
+ # 2. pick_ig_account.py -> chooses target IG account (inverse-recent-share
10
+ # over enabled accounts in config.json instagram.accounts).
11
+ # 3. ig_post_type_picker.py --account <name> -> JSON {post_type, video_path, ...}
12
+ # scoped to the chosen account's draft pool.
13
+ # 4. call mixer/post_to_ig.py --file <path> --post-type <type> --account <name>
14
+ # which: uploads to GCS, posts via IG Graph API, writes posted.json,
15
+ # updates media_posts (status=posted, post_type + target_account coalesced).
16
+ #
17
+ # Exit behavior:
18
+ # 0 - posted, OR queue exhausted (logged), OR another run holds the lock
19
+ # 1 - real failure (DB error, IG API error, picker crash, etc.)
20
+ #
21
+ # Logs: skill/logs/instagram-daily-YYYY-MM-DD_HHMMSS.log
22
+
23
+ set -uo pipefail
24
+
25
+ REPO_DIR="$HOME/social-autoposter"
26
+ LOG_DIR="$REPO_DIR/skill/logs"
27
+ mkdir -p "$LOG_DIR"
28
+
29
+ LOG_FILE="$LOG_DIR/instagram-daily-$(date +%Y-%m-%d_%H%M%S).log"
30
+ PICK_FILE="/tmp/ig_pick_$(date +%s)_$$.json"
31
+
32
+ if [ -f "$REPO_DIR/.env" ]; then
33
+ set -a
34
+ # shellcheck disable=SC1091
35
+ source "$REPO_DIR/.env"
36
+ set +a
37
+ fi
38
+
39
+ log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_FILE"; }
40
+
41
+ # Run accounting for dashboard Job History (Post Threads · Instagram).
42
+ # Each exit site updates POSTED_CT / SKIPPED_CT / FAILED_CT; the EXIT trap
43
+ # always emits one log_run.py line so the run shows up under
44
+ # thread_instagram, matching how thread_twitter / thread_reddit log.
45
+ RUN_START_EPOCH=$(date +%s)
46
+ POSTED_CT=0
47
+ SKIPPED_CT=0
48
+ FAILED_CT=0
49
+
50
+ cleanup() {
51
+ local rc=$?
52
+ rm -f "$PICK_FILE"
53
+ if [ "$POSTED_CT" -eq 0 ] && [ "$SKIPPED_CT" -eq 0 ] && [ "$FAILED_CT" -eq 0 ]; then
54
+ if [ "$rc" -eq 0 ]; then SKIPPED_CT=1; else FAILED_CT=1; fi
55
+ fi
56
+ local elapsed=$(( $(date +%s) - RUN_START_EPOCH ))
57
+ local cost
58
+ cost=$(/usr/bin/python3 "$REPO_DIR/scripts/get_run_cost.py" --since "$RUN_START_EPOCH" --scripts "run-instagram-daily" 2>/dev/null || echo "0.0000")
59
+ /usr/bin/python3 "$REPO_DIR/scripts/log_run.py" \
60
+ --script "thread_instagram" \
61
+ --posted "$POSTED_CT" --skipped "$SKIPPED_CT" --failed "$FAILED_CT" \
62
+ --cost "$cost" --elapsed "$elapsed" >/dev/null 2>&1 || true
63
+ }
64
+ trap cleanup EXIT INT TERM HUP
65
+
66
+ log "=== instagram-daily fire: $(date) ==="
67
+
68
+ # Lock against overlapping fires (pure HTTP pipeline, no browser, but the
69
+ # picker reads-then-posts so two concurrent fires could pick the same row).
70
+ # 30s timeout — if a prior fire is still uploading, skip cleanly.
71
+ # shellcheck source=lock.sh
72
+ source "$REPO_DIR/skill/lock.sh"
73
+ acquire_lock instagram-poster 30
74
+
75
+ # Step 1: pick target IG account. Per-account plists set FORCE_ACCOUNT in
76
+ # their EnvironmentVariables block so the slot is hard-pinned to one account;
77
+ # fall back to inverse-recent-share picker only when no FORCE_ACCOUNT is set
78
+ # (manual / legacy invocation).
79
+ if [ -n "${FORCE_ACCOUNT:-}" ]; then
80
+ log "step 1: FORCE_ACCOUNT=$FORCE_ACCOUNT honored from env"
81
+ TARGET_ACCOUNT=$(/opt/homebrew/bin/python3.11 "$REPO_DIR/scripts/pick_ig_account.py" --account "$FORCE_ACCOUNT" 2>>"$LOG_FILE")
82
+ else
83
+ log "step 1: pick_ig_account (no FORCE_ACCOUNT in env)"
84
+ TARGET_ACCOUNT=$(/opt/homebrew/bin/python3.11 "$REPO_DIR/scripts/pick_ig_account.py" 2>>"$LOG_FILE")
85
+ fi
86
+ if [ -z "$TARGET_ACCOUNT" ]; then
87
+ log "pick_ig_account.py produced no account — exiting non-zero"
88
+ FAILED_CT=1
89
+ exit 1
90
+ fi
91
+ log "picker chose account: $TARGET_ACCOUNT"
92
+
93
+ # Step 2: pick post type + video, scoped to that account
94
+ log "step 2: ig_post_type_picker --account $TARGET_ACCOUNT"
95
+ if ! /opt/homebrew/bin/python3.11 "$REPO_DIR/scripts/ig_post_type_picker.py" \
96
+ --account "$TARGET_ACCOUNT" > "$PICK_FILE" 2>>"$LOG_FILE"; then
97
+ rc=$?
98
+ if [ "$rc" -eq 2 ]; then
99
+ log "queue exhausted for account=$TARGET_ACCOUNT (no drafts of either type) — exiting cleanly"
100
+ SKIPPED_CT=1
101
+ exit 0
102
+ fi
103
+ log "picker failed rc=$rc — exiting non-zero"
104
+ FAILED_CT=1
105
+ exit 1
106
+ fi
107
+
108
+ POST_TYPE=$(/opt/homebrew/bin/python3.11 -c "import json; print(json.load(open('$PICK_FILE'))['post_type'])")
109
+ VIDEO_PATH=$(/opt/homebrew/bin/python3.11 -c "import json; print(json.load(open('$PICK_FILE'))['video_path'])")
110
+ POST_NUMBER=$(/opt/homebrew/bin/python3.11 -c "import json; print(json.load(open('$PICK_FILE'))['post_number'])")
111
+ REASON=$(/opt/homebrew/bin/python3.11 -c "import json; print(json.load(open('$PICK_FILE'))['reason'])")
112
+ FALLBACK=$(/opt/homebrew/bin/python3.11 -c "import json; print(json.load(open('$PICK_FILE'))['fallback'])")
113
+
114
+ log "picker chose: account=${TARGET_ACCOUNT} post-${POST_NUMBER} type=${POST_TYPE} fallback=${FALLBACK}"
115
+ log "picker reason: ${REASON}"
116
+
117
+ if [ ! -f "$VIDEO_PATH" ]; then
118
+ log "ERROR: picker pointed at $VIDEO_PATH but file missing on disk"
119
+ FAILED_CT=1
120
+ exit 1
121
+ fi
122
+
123
+ # Step 2: post
124
+ DRY_FLAG=""
125
+ if [ "${IG_DRY_RUN:-0}" = "1" ]; then
126
+ DRY_FLAG="--dry-run"
127
+ log "IG_DRY_RUN=1 — passing --dry-run to post_to_ig.py"
128
+ fi
129
+
130
+ log "step 3: post_to_ig.py --file $(basename "$VIDEO_PATH") --post-type $POST_TYPE --account $TARGET_ACCOUNT $DRY_FLAG"
131
+ if ! /opt/homebrew/bin/python3.11 "$REPO_DIR/mixer/post_to_ig.py" \
132
+ --file "$VIDEO_PATH" --post-type "$POST_TYPE" --account "$TARGET_ACCOUNT" $DRY_FLAG >>"$LOG_FILE" 2>&1; then
133
+ log "post_to_ig.py failed — exiting non-zero"
134
+ FAILED_CT=1
135
+ exit 1
136
+ fi
137
+
138
+ POSTED_CT=1
139
+ log "=== finished post-${POST_NUMBER} (${POST_TYPE}) on ${TARGET_ACCOUNT} successfully ==="
140
+
141
+ # Step 4: mirror the new media_posts row into the cross-platform `posts` table
142
+ # so it surfaces in the dashboard (Trends, Top, Activity, Cohort, Stats by
143
+ # Style) alongside Reddit/Twitter/LinkedIn rows. Idempotent.
144
+ log "step 4: sync_ig_to_posts"
145
+ if ! /opt/homebrew/bin/python3.11 "$REPO_DIR/scripts/sync_ig_to_posts.py" --quiet >>"$LOG_FILE" 2>&1; then
146
+ log "sync_ig_to_posts failed (post is already on IG; will retry on next stats fire)"
147
+ fi
148
+
149
+ exit 0