@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,81 @@
1
+ #!/bin/bash
2
+ # run-linkedin-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. Before 2026-05-01 the
7
+ # linkedin pipeline held the linkedin-browser lock for the full 25-45min
8
+ # run, so wrapping it would just queue blocked-on-lock processes with no
9
+ # throughput gain. On 2026-05-01 run-linkedin.sh was refactored to acquire
10
+ # linkedin-browser only around its two Claude phases (~5min each), freeing
11
+ # the lock during DB ingest / candidate pick / Phase B prep windows.
12
+ # With that change the wrapper becomes useful: parallel cycles can run
13
+ # their non-browser phases concurrently, and the lock + lock.sh's FIFO
14
+ # ticket queue serialize the brief browser windows fairly.
15
+ #
16
+ # How it works:
17
+ # Python double-fork daemon idiom — first fork gives launchd a parent that
18
+ # exits immediately (so the job is marked complete in milliseconds), setsid
19
+ # detaches the session, second fork prevents reacquiring a controlling
20
+ # terminal, then we exec the real pipeline. macOS lacks `setsid(1)` and
21
+ # `nohup ... & disown` is not enough because launchd reaps the wrapper's
22
+ # pgid, taking the nohup child with it.
23
+ #
24
+ # LinkedIn-specific risk:
25
+ # Per CLAUDE.md memory feedback_linkedin_session_fingerprint, anti-bot can
26
+ # fire on sequential agent re-auths even when the lock prevents parallel
27
+ # Chromes. Pre-wrapper steady state was ~2 cycles/hr × 2 fresh Chrome
28
+ # sessions/cycle = 4 sessions/hr. Post-wrapper it can rise to ~4 cycles/hr
29
+ # × 2 = 8 sessions/hr (peak). If LinkedIn restrictions return, REVERT this
30
+ # wrapper first (point the plist back at run-linkedin.sh directly) before
31
+ # touching the OAuth API or browser profile.
32
+
33
+ REPO_DIR="$HOME/social-autoposter"
34
+ LOG_DIR="$REPO_DIR/skill/logs"
35
+ mkdir -p "$LOG_DIR"
36
+
37
+ # LinkedIn killswitch (2026-05-27): refuse to even fork the daemon if a
38
+ # prior fire detected session compromise (http_999, authwall, throttle,
39
+ # li_at cleared). Cheaper than letting the inner script discover it.
40
+ # State: ~/.claude/social-autoposter/linkedin.killswitch
41
+ # Clear: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear
42
+ if [ -f "$HOME/.claude/social-autoposter/linkedin.killswitch" ]; then
43
+ echo "[$(date +%H:%M:%S)] LINKEDIN_KILLSWITCH active. Skipping run-linkedin." \
44
+ >> "$LOG_DIR/launchd-linkedin-stderr.log"
45
+ exit 0
46
+ fi
47
+
48
+ SCRIPT="$REPO_DIR/skill/run-linkedin.sh"
49
+ OUT="$LOG_DIR/launchd-linkedin-stdout.log"
50
+ ERR="$LOG_DIR/launchd-linkedin-stderr.log"
51
+
52
+ # Preflight (added 2026-05-02): skip cleanly if Claude is blocked on a
53
+ # quota cap, or if the system is under memory pressure. See
54
+ # scripts/preflight.sh for full design.
55
+ SA_PREFLIGHT_SCRIPT="run-linkedin"
56
+ source "$REPO_DIR/scripts/preflight.sh"
57
+ preflight_skip_if_claude_blocked
58
+ preflight_skip_if_jetsam_pressure
59
+
60
+ exec /usr/bin/python3 -c "
61
+ import os, sys
62
+ script = '$SCRIPT'
63
+ out_log = '$OUT'
64
+ err_log = '$ERR'
65
+
66
+ if os.fork() != 0:
67
+ os._exit(0)
68
+ os.setsid()
69
+
70
+ if os.fork() != 0:
71
+ os._exit(0)
72
+
73
+ os.chdir('/')
74
+ out_fd = os.open(out_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
75
+ err_fd = os.open(err_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
76
+ nul_fd = os.open('/dev/null', os.O_RDONLY)
77
+ os.dup2(nul_fd, 0)
78
+ os.dup2(out_fd, 1)
79
+ os.dup2(err_fd, 2)
80
+ os.execv(script, [script])
81
+ "
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env bash
2
+ # run-linkedin-unipile.sh: LinkedIn "post comments" pipeline via the UniPile API
3
+ # (search posts, then post a comment). This is the UniPile-backed counterpart to
4
+ # the browser-driven run-linkedin.sh. It talks ONLY to the UniPile REST API
5
+ # through scripts/linkedin_unipile.py: no browser, no linkedin-agent, no browser
6
+ # lock, no killswitch gate (there is no browser session to compromise here).
7
+ #
8
+ # "post comments" means searching for posts and commenting on them (outbound).
9
+ # This is NOT the "engage" pipeline (engage-linkedin.sh, which replies to replies
10
+ # left on our own comments).
11
+ #
12
+ # Default behavior is DRY RUN: it probes credentials, searches, and prints the
13
+ # comment it *would* post. A posted comment is visible to real people, so it only
14
+ # publishes when you pass --post AND --text.
15
+ #
16
+ # Usage:
17
+ # ./run-linkedin-unipile.sh --keywords "ai agents" --date-posted past_week --limit 5
18
+ # ./run-linkedin-unipile.sh --keywords "..." --text "nice breakdown" --post
19
+ # ./run-linkedin-unipile.sh --social-id "urn:li:activity:NNN" --text "..." --post
20
+ #
21
+ # Creds: the live UniPile account is under matt@mediar.ai (keychain entries scoped
22
+ # to that account; DSN api45.unipile.com:17570, account_id wHDpysUnRbm7Q0lvyv9pQQ).
23
+ # An older i@m13v.com trial shares the same service names but is dead.
24
+ # linkedin_unipile.py reads the mediar-scoped entry; we also pin UNIPILE_DSN here
25
+ # as a default (override by exporting it).
26
+
27
+ set -euo pipefail
28
+
29
+ : "${UNIPILE_DSN:=api45.unipile.com:17570}"
30
+ export UNIPILE_DSN
31
+
32
+ REPO_DIR="$HOME/social-autoposter"
33
+ PY="$REPO_DIR/scripts/linkedin_unipile.py"
34
+ LOG_DIR="$REPO_DIR/skill/logs"
35
+ mkdir -p "$LOG_DIR"
36
+ LOG_FILE="$LOG_DIR/engage-linkedin-unipile-$(date +%Y-%m-%d_%H%M%S).log"
37
+
38
+ KEYWORDS=""
39
+ SEARCH_URL=""
40
+ DATE_POSTED="past_week"
41
+ SORT_BY="date"
42
+ LIMIT=5
43
+ TEXT=""
44
+ SOCIAL_ID=""
45
+ REPLY_TO=""
46
+ DO_POST=0
47
+
48
+ log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_FILE"; }
49
+
50
+ while [ $# -gt 0 ]; do
51
+ case "$1" in
52
+ --keywords) KEYWORDS="$2"; shift 2 ;;
53
+ --url) SEARCH_URL="$2"; shift 2 ;;
54
+ --date-posted) DATE_POSTED="$2"; shift 2 ;;
55
+ --sort-by) SORT_BY="$2"; shift 2 ;;
56
+ --limit) LIMIT="$2"; shift 2 ;;
57
+ --text) TEXT="$2"; shift 2 ;;
58
+ --social-id) SOCIAL_ID="$2"; shift 2 ;;
59
+ --reply-to) REPLY_TO="$2"; shift 2 ;;
60
+ --post) DO_POST=1; shift ;;
61
+ -h|--help)
62
+ grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
63
+ *) echo "unknown arg: $1" >&2; exit 2 ;;
64
+ esac
65
+ done
66
+
67
+ log "=== UniPile LinkedIn post comments (search, then comment) ==="
68
+ log "DSN=$UNIPILE_DSN dry_run=$([ "$DO_POST" -eq 1 ] && echo no || echo YES)"
69
+
70
+ # ── Step 1: validate credentials ──────────────────────────────────────────
71
+ log "Step 1: probing credentials (/accounts)..."
72
+ if ! python3 "$PY" probe 2>>"$LOG_FILE" | tee -a "$LOG_FILE"; then
73
+ log "CREDENTIAL PROBE FAILED. UniPile rejected the API key (see above)."
74
+ log "Fix: the live account is matt@mediar.ai (the i@m13v.com trial is dead). Refresh the"
75
+ log " subscription if needed, then store the key under the mediar-scoped entry:"
76
+ log " security add-generic-password -U -s unipile-api-key -a matt@mediar.ai -w '<KEY>'"
77
+ log " and DSN: security add-generic-password -U -s unipile-dsn -a matt@mediar.ai -w 'apiNN.unipile.com:PORT'"
78
+ exit 1
79
+ fi
80
+
81
+ # ── Step 2: resolve a target post ─────────────────────────────────────────
82
+ # Either the caller named a --social-id directly, or we search and take the top
83
+ # result. We always print the search results so the run is auditable.
84
+ if [ -z "$SOCIAL_ID" ]; then
85
+ if [ -z "$KEYWORDS" ] && [ -z "$SEARCH_URL" ]; then
86
+ log "Nothing to do: pass --keywords/--url to search, or --social-id to target a post."
87
+ exit 2
88
+ fi
89
+ log "Step 2: searching posts (keywords='${KEYWORDS}' url='${SEARCH_URL}' date=$DATE_POSTED limit=$LIMIT)..."
90
+ SEARCH_ARGS=(search --limit "$LIMIT" --sort-by "$SORT_BY")
91
+ [ -n "$KEYWORDS" ] && SEARCH_ARGS+=(--keywords "$KEYWORDS" --date-posted "$DATE_POSTED")
92
+ [ -n "$SEARCH_URL" ] && SEARCH_ARGS+=(--url "$SEARCH_URL")
93
+ SEARCH_JSON=$(python3 "$PY" "${SEARCH_ARGS[@]}" 2>>"$LOG_FILE") || {
94
+ log "SEARCH FAILED (see log)."; exit 1; }
95
+ echo "$SEARCH_JSON" | tee -a "$LOG_FILE"
96
+ SOCIAL_ID=$(echo "$SEARCH_JSON" | python3 -c "import json,sys; items=json.load(sys.stdin).get('items',[]); print(items[0]['social_id'] if items and items[0].get('social_id') else '')")
97
+ if [ -z "$SOCIAL_ID" ]; then
98
+ log "No usable post (no social_id in top result). Stopping before comment."
99
+ exit 0
100
+ fi
101
+ log "Top result social_id: $SOCIAL_ID"
102
+ else
103
+ log "Step 2: skipping search, using provided --social-id $SOCIAL_ID"
104
+ fi
105
+
106
+ # ── Step 3: comment (or dry-run) ──────────────────────────────────────────
107
+ if [ -z "$TEXT" ]; then
108
+ log "No --text provided; nothing to post. Target post: https://www.linkedin.com/feed/update/$SOCIAL_ID/"
109
+ log "Done (search-only)."
110
+ exit 0
111
+ fi
112
+
113
+ if [ "$DO_POST" -ne 1 ]; then
114
+ log "DRY RUN — would comment on $SOCIAL_ID:"
115
+ log " \"$TEXT\""
116
+ log "Re-run with --post to actually publish. (No API write performed.)"
117
+ exit 0
118
+ fi
119
+
120
+ log "Step 3: posting comment on $SOCIAL_ID ..."
121
+ COMMENT_ARGS=(comment --social-id "$SOCIAL_ID" --text "$TEXT")
122
+ [ -n "$REPLY_TO" ] && COMMENT_ARGS+=(--reply-to "$REPLY_TO")
123
+ if python3 "$PY" "${COMMENT_ARGS[@]}" 2>>"$LOG_FILE" | tee -a "$LOG_FILE"; then
124
+ log "Comment posted. Watch the m13v LinkedIn account for any flag/challenge over the next 24-48h."
125
+ else
126
+ log "COMMENT FAILED (see log)."
127
+ exit 1
128
+ fi
129
+
130
+ log "=== Done ==="