@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
package/setup/SKILL.md ADDED
@@ -0,0 +1,277 @@
1
+ ---
2
+ name: social-autoposter-setup
3
+ description: "Set up social-autoposter for a new user end to end. Installs/repairs the runtime, discovers product and voice context, connects X/Twitter, seeds search topics, schedules the draft autopilot, and verifies it produces a draft. Use when: 'set up social autoposter', 'install social autoposter', 'configure social posting'."
4
+ ---
5
+
6
+ # Social Autoposter Setup
7
+
8
+ Set up social-autoposter end to end. Treat the user's setup request as a terminal
9
+ goal, not as the beginning of an interview.
10
+
11
+ ## Operating contract
12
+
13
+ - Keep taking the next safe setup action until the system is working end to end.
14
+ - Do not ask whether to run setup, install a dependency, inspect status, connect
15
+ X, scan the profile, research the website, save inferred fields, seed topics,
16
+ or retry a recoverable failure. Do them.
17
+ - An explicit request to set up social-autoposter authorizes its owned local
18
+ runtime installation and importing only x.com/twitter.com session cookies
19
+ into its managed browser. Briefly warn that macOS keychain prompts may appear,
20
+ then proceed; do not wait for another yes/no reply.
21
+ - Use existing configuration, the user's message/context, their connected X
22
+ profile, public website pages, and reasonable factual inference before asking
23
+ for information.
24
+ - Ask at most one bundled blocking question, and only when required information
25
+ cannot be discovered or safely inferred. The usual legitimate blocker is:
26
+ there is no configured project, no clear product URL in context or the X
27
+ profile, and no way to identify what product to market.
28
+ - Never post a draft during setup unless the user explicitly asked for that.
29
+ - Never hand-run the X cycle. Do not run `run-twitter-cycle.sh`,
30
+ `claude_job.py`, or any cycle script directly, and never offer to "trigger a
31
+ cycle" or "run one now" so a draft appears faster. Only the launchd kicker may
32
+ fire the cycle, with its required environment. A manual kick produces an
33
+ empty-plan artifact and blocks the autopilot. Verification means scheduling
34
+ the autopilot and letting the kicker drive it; you wait and poll, you never
35
+ trigger.
36
+ - Do not edit the MCP server, plugin source, or an unrelated user workspace to
37
+ work around setup failures. Use the product's setup/install tools.
38
+
39
+ ## Definition of done
40
+
41
+ Do not report setup complete until all of these are true:
42
+
43
+ 1. The owned runtime is installed and ready.
44
+ 2. At least one project is ready with name, website, description, ICP, voice,
45
+ and search topics. For the personal-brand persona, the search topics and the
46
+ grounding corpus come from the user's dictation interview (below), not from
47
+ the profile scan alone.
48
+ 3. Search topics have been seeded into the backend. Seeding is POSTPONED until
49
+ after the dictation interview so the topics reflect what the user wants to be
50
+ in conversations about, not only what they already posted.
51
+ 4. X is connected and the real handle has been auto-detected.
52
+ 5. The draft autopilot is scheduled and has produced a draft card without
53
+ posting. A returned/pending review batch is the strongest success signal. If X
54
+ simply has no matching supply, report that precise result only after
55
+ configuration/auth/runtime checks pass.
56
+
57
+ ## Architecture
58
+
59
+ - **Config**: `~/social-autoposter/config.json` — `projects[]` (what to post
60
+ about) and `accounts` (where to post).
61
+ - **Data + stats**: backend HTTP API at `https://s4l.ai`, scoped by a stable
62
+ per-install identity in `identity.json`. There is no local Postgres or
63
+ `DATABASE_URL`.
64
+ - **Search topics**: the X cycle reads `project_search_topics`, seeded
65
+ automatically from each project's `search_topics`. No topics means nothing
66
+ to scan. `search_topics` is the ONLY field that changes what gets scanned;
67
+ every other persona field only shapes the draft after a thread is found. For
68
+ the persona, the profile scan is backward-looking (only what the user already
69
+ posted), so topics are sourced primarily from the dictation interview.
70
+
71
+ ## Choose the path
72
+
73
+ - If the social-autoposter MCP tools are connected (`project_config`, `runtime`,
74
+ `queue_setup`, `post_drafts`, `get_stats`, `dashboard`), use the MCP path.
75
+ Do not hand-edit `config.json`.
76
+ - If only the CLI/skill is installed, use the CLI fallback.
77
+
78
+ ## MCP path
79
+
80
+ ### 1. Inspect and repair the environment
81
+
82
+ Call `project_config` in status mode and `runtime` (action:'status') immediately.
83
+
84
+ If the runtime is not ready:
85
+
86
+ 1. Call `runtime` with action:'install'.
87
+ 2. Poll `runtime` (action:'status') until it succeeds or returns a concrete failure.
88
+ 3. For a recoverable/partial failure, call `runtime` action:'install' again and continue.
89
+ Do not send the user away to install Chrome, Python, uv, Chromium, or
90
+ browser-harness manually; the owned installer handles them.
91
+
92
+ Preserve any already-ready project or X connection. Resume from the first
93
+ incomplete milestone instead of restarting the interview.
94
+
95
+ ### 2. Connect X and learn the user's voice
96
+
97
+ If X is not connected:
98
+
99
+ 1. Call `project_config` with `action:'detect_x_sources'`.
100
+ 2. Choose `recommended`, preferring a source whose `x_session` is present.
101
+ Do not ask the user to choose a browser profile when the tool can choose.
102
+ 3. Tell the user in one short progress update that macOS may show browser Safe
103
+ Storage prompts and they should enter their Mac password and click **Allow**
104
+ or **Always Allow**.
105
+ 4. Immediately call `project_config` with `action:'connect_x', confirm:true` and the
106
+ selected `x_source`. The setup request itself is authorization; do not add a
107
+ separate consent round-trip.
108
+ 5. If the result is transient, retry. If it opens managed Chrome in
109
+ `needs_login`, tell the user to finish signing in to x.com in that window.
110
+ This is an unavoidable user action, not a product-choice question. Re-run
111
+ `connect_x` after sign-in and continue.
112
+
113
+ Once connected, call `project_config` with `action:'profile_scan'`. Treat the returned
114
+ bio, links, recent posts, and replies as grounding truth for the VOICE, not as the
115
+ primary source of topics (the scan only shows what they already posted):
116
+
117
+ - profession/identity;
118
+ - voice, casing, phrasing, and tone;
119
+ - ICP;
120
+ - wording or claims the user avoids;
121
+ - recurring themes (reinforcement for topics, not the origin).
122
+
123
+ Do not ask the user to approve the inferred voice during initial setup. Save a
124
+ specific, conservative best draft and mention afterward that it can be edited.
125
+
126
+ Then run the DICTATION INTERVIEW before extracting topics or seeding. This is
127
+ where the persona's `search_topics` and grounding corpus come from. Tell the user
128
+ to answer all of the following in ONE spoken dictation (the Claude input box
129
+ already supports dictation, so they talk once and you split the answers into
130
+ fields). Ask verbatim as a single numbered list:
131
+
132
+ 1. Who are you, and what do you want to be known for? (→ description)
133
+ 2. What subjects could you talk about for an hour, work and non-work? (→
134
+ `search_topics`; this is the load-bearing answer, it is the only thing that
135
+ decides what gets scanned on X)
136
+ 3. Your most contrarian takes — what does everyone in your field get wrong, and
137
+ what did you used to believe that you have reversed on? (→ content_angle +
138
+ corpus)
139
+ 4. What can you explain in 5 minutes that took you years, and what mistake do you
140
+ watch beginners make over and over? (→ content_angle + corpus)
141
+ 5. Best or worst thing that happened to you recently, and a failure you learned
142
+ the most from? (→ corpus, keeps drafts current)
143
+ 6. Who do you love or hate reading online, and any lines or phrases you say a
144
+ lot? (→ voice calibration)
145
+ 7. Anything off-limits (topics, companies, people), and how spicy can we get —
146
+ safe, opinionated, or provocative? (→ content_guardrails + voice.never)
147
+
148
+ From the dictation, synthesize the fields: `search_topics` primarily from answer
149
+ 2 (fold in recurring scan themes only as reinforcement), the rest from the other
150
+ answers. Keep the RAW transcript VERBATIM as the persona corpus (do not
151
+ paraphrase; the actual numbers, opinions, and phrasing are what make drafts sound
152
+ like them). Pass it as `content_corpus` when you call `engagement_mode` (or
153
+ `project_config`); it is stored in the `persona_corpus.txt` sidecar, not
154
+ config.json. If the user declines or gives nothing usable, fall back to
155
+ scan-derived topics.
156
+
157
+ Only after the dictation is captured do you set the engagement mode / save the
158
+ persona, which seeds the topics. Do not seed topics from the scan before the
159
+ dictation.
160
+
161
+ ### 3. Discover and research the product
162
+
163
+ Find the product URL in this order:
164
+
165
+ 1. an existing project/config;
166
+ 2. the user's setup request and conversation context;
167
+ 3. the connected X profile URL/bio/recent posts;
168
+ 4. a clearly associated public product discovered with web research.
169
+
170
+ When one product is clearly supported by the evidence, use it without asking.
171
+ If several are plausible and no primary product is evident, choose the one most
172
+ prominent in the current context/profile. Ask one bundled blocking question
173
+ only if no defensible product can be identified.
174
+
175
+ Visit the product site with your own browser/fetch tools and read at least five
176
+ pages when available: homepage, pricing, features/product, about, docs,
177
+ changelog/blog, FAQ, and customer/case-study pages. From what you actually read,
178
+ derive:
179
+
180
+ - `name`: short lowercase machine slug;
181
+ - `website`;
182
+ - `description`;
183
+ - `differentiator`;
184
+ - `icp`;
185
+ - `get_started_link`;
186
+ - `content_guardrails`;
187
+ - `voice` and `search_topics`, grounded in the profile scan.
188
+
189
+ Do not invent features, metrics, customers, or guarantees. If the site is thin,
190
+ save a conservative factual description rather than stopping for optional
191
+ details.
192
+
193
+ ### 4. Save and seed
194
+
195
+ Call `project_config` once with the complete inferred project whenever possible. It
196
+ merges fields, reports missing required fields, seeds `search_topics` into
197
+ `project_search_topics`, and expands them into search queries.
198
+
199
+ If required fields remain, first attempt to derive them from the sources already
200
+ collected. Ask the user only if a genuinely unknowable required field blocks
201
+ readiness. Optional/recommended fields never justify stopping setup.
202
+
203
+ ### 5. Schedule the autopilot + verify end to end
204
+
205
+ Schedule the draft autopilot: call `queue_setup`, then for EACH returned task
206
+ call the host tool `create_scheduled_task` (taskId, cronExpression, prompt
207
+ verbatim; "already exists" is fine). The autopilot then drafts on its own — the
208
+ launchd kicker fires a draft-only cycle and the queue worker drafts the replies;
209
+ nothing posts. Then wait: poll the `dashboard` tool until the pending-draft
210
+ count rises; a returned review batch is the strongest success signal.
211
+
212
+ Do not hand-run a cycle to make this happen faster, and do not offer the user to
213
+ trigger one. Scheduling the autopilot is the only sanctioned way to produce the
214
+ verification draft; the kicker drives it on its own minute-by-minute schedule.
215
+
216
+ If no card appears, diagnose the fixable reason, fix it, and let the autopilot
217
+ retry on its next scheduled cycle:
218
+
219
+ - missing topics: derive/add topics through `project_config`, then retry;
220
+ - runtime/browser-harness/Chrome issue: run/repair the owned runtime, then retry;
221
+ - stale X session: reconnect X, then retry;
222
+ - transient backend/network issue: retry once;
223
+ - Claude CLI login/usage limit or an interactive X login: report the exact
224
+ blocker and the single user action required.
225
+
226
+ Do not enable autopilot automatically. Offer it only after setup is verified,
227
+ or enable it when the user's original request explicitly included hands-free
228
+ posting.
229
+
230
+ Once verification passes (or you reach a precise blocker), call the `dashboard`
231
+ tool so the user sees the finished setup rendered visually, then give the
232
+ completion summary below.
233
+
234
+ ## CLI fallback
235
+
236
+ Use only when MCP tools are unavailable. Execute the flow yourself rather than
237
+ turning it into instructions for the user.
238
+
239
+ 1. Install/repair: `npx -y social-autoposter@latest init`
240
+ 2. Inspect `~/social-autoposter/config.json` and preserve existing projects.
241
+ 3. Discover the product and voice using the same evidence order above. For a
242
+ personal-brand persona, run the dictation interview (the 7 questions above)
243
+ and take `search_topics` + the raw corpus from it, scan themes as backup.
244
+ 4. Write a complete project with name, website, description, ICP, voice, and
245
+ `search_topics`.
246
+ 5. Seed topics:
247
+ `python3 ~/social-autoposter/scripts/seed_search_topics.py --project <name>`
248
+ 6. Connect X:
249
+ `python3 ~/social-autoposter/scripts/setup_twitter_auth.py connect`
250
+ The user may need to approve a macOS keychain prompt or sign in once in the
251
+ managed browser; continue automatically afterward.
252
+ 7. Do not load launchd/autopilot jobs unless explicitly requested, and never
253
+ hand-run `run-twitter-cycle.sh` or any cycle script to "verify." A manual
254
+ kick produces an empty-plan artifact and blocks the autopilot. The cycle runs
255
+ only when the launchd autopilot is scheduled and the kicker fires it. In CLI
256
+ fallback, setup is configured once the project is saved, topics are seeded,
257
+ and X is connected; the first draft appears after the autopilot is scheduled.
258
+
259
+ ## Completion summary
260
+
261
+ Render the `dashboard` tool once setup is verified, then report outcomes (not a
262
+ recap of every prompt):
263
+
264
+ ```text
265
+ Social Autoposter Setup Complete
266
+
267
+ Runtime: ready
268
+ Project: NAME — ready
269
+ Topics seeded: N
270
+ X/Twitter: @HANDLE
271
+ Verification: autopilot produced a draft (not posted)
272
+ Autopilot: off
273
+ Stats: https://s4l.ai/stats/HANDLE
274
+ ```
275
+
276
+ If setup is blocked, do not call it complete. State the exact completed
277
+ milestones, the blocker, and the one user action needed to resume.
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # amplitude-24h-signups.sh — launchd wrapper for scripts/amplitude_24h_signups.py.
3
+ #
4
+ # Fires every 5 min from com.m13v.social-amplitude-24h.plist.
5
+ # Writes ~/social-autoposter/skill/cache/amplitude_24h_signups.json.
6
+ #
7
+ # The script itself uses a real-time PostHog count for the headline number
8
+ # (cheap, ~1s) and refreshes the eventually-consistent Amplitude export
9
+ # only every ~25 min (heavy, ~30s + ~150 MB).
10
+ #
11
+ # Read by project_stats_json.py:_amplitude_signups when days==1.
12
+
13
+ set -uo pipefail
14
+
15
+ REPO_DIR="$HOME/social-autoposter"
16
+
17
+ # shellcheck source=/dev/null
18
+ [ -f "$REPO_DIR/.env" ] && source "$REPO_DIR/.env"
19
+
20
+ # Inject Amplitude + PostHog creds from keychain so the export half can run
21
+ # without env vars being baked into the launchd plist.
22
+ export AMPLITUDE_STUDYLY_API_KEY="${AMPLITUDE_STUDYLY_API_KEY:-$(security find-generic-password -s amplitude-studyly-api-key -w 2>/dev/null)}"
23
+ export AMPLITUDE_STUDYLY_SECRET_KEY="${AMPLITUDE_STUDYLY_SECRET_KEY:-$(security find-generic-password -s amplitude-studyly-secret-key -w 2>/dev/null)}"
24
+ export POSTHOG_PERSONAL_API_KEY="${POSTHOG_PERSONAL_API_KEY:-$(security find-generic-password -s PostHog-Personal-API-Key-m13v -w 2>/dev/null)}"
25
+
26
+ cd "$REPO_DIR" || exit 2
27
+
28
+ # shellcheck source=lock.sh
29
+ source "$REPO_DIR/skill/lock.sh"
30
+ acquire_lock amplitude-24h-signups 5
31
+
32
+ RUN_START=$(date +%s)
33
+ /opt/homebrew/bin/python3.11 "$REPO_DIR/scripts/amplitude_24h_signups.py"
34
+ EXIT_CODE=$?
35
+ RUN_ELAPSED=$(( $(date +%s) - RUN_START ))
36
+
37
+ echo "[$(date +%H:%M:%S)] === done in ${RUN_ELAPSED}s (exit=${EXIT_CODE}) ==="
38
+ exit "$EXIT_CODE"
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # Archive log files older than 7 days from skill/logs/ to skill/logs-archive/.
3
+ # The dashboard (bin/server.js) does many fs.readdirSync(LOG_DIR) calls per
4
+ # pulse. Letting that directory grow to 17k+ files starves the event loop
5
+ # and the dashboard stops responding. Pruning to a sibling dir keeps the
6
+ # files around for forensics without including them in the dashboard scan.
7
+ #
8
+ # Scheduled daily by ~/Library/LaunchAgents/com.m13v.social-archive-logs.plist
9
+
10
+ set -uo pipefail
11
+
12
+ LOG_DIR="/Users/matthewdi/social-autoposter/skill/logs"
13
+ ARCHIVE_DIR="/Users/matthewdi/social-autoposter/skill/logs-archive"
14
+ DAYS="${ARCHIVE_DAYS:-7}"
15
+
16
+ mkdir -p "$ARCHIVE_DIR" "$LOG_DIR"
17
+
18
+ # Per-run summary log so the dashboard's "Other" section can find this job.
19
+ # Filename matches the JOBS[].logPrefix value in bin/server.js.
20
+ RUN_LOG="$LOG_DIR/archive-logs-$(date +%Y-%m-%d_%H%M%S).log"
21
+ log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$RUN_LOG"; }
22
+
23
+ if [ ! -d "$LOG_DIR" ]; then
24
+ log "ERROR: LOG_DIR not found: $LOG_DIR"
25
+ exit 0
26
+ fi
27
+
28
+ log "=== archive-old-logs starting (DAYS=$DAYS) ==="
29
+
30
+ # Only top-level files; do not touch claude-sessions/ or other subdirs.
31
+ # Also exclude the per-run summary we just created so we don't archive
32
+ # ourselves on long-tail edge cases.
33
+ find "$LOG_DIR" -maxdepth 1 -type f -mtime +"$DAYS" ! -name "$(basename "$RUN_LOG")" -print0 \
34
+ | xargs -0 -I{} mv {} "$ARCHIVE_DIR/" 2>&1 | tee -a "$RUN_LOG" >/dev/null || true
35
+
36
+ remaining=$(find "$LOG_DIR" -maxdepth 1 -type f | wc -l | tr -d ' ')
37
+ archived=$(find "$ARCHIVE_DIR" -maxdepth 1 -type f | wc -l | tr -d ' ')
38
+
39
+ log "kept=$remaining archived_total=$archived"
40
+ log "=== done ==="
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ # audit-dm-staleness.sh — Age out no_response DMs after 14 days of silence
3
+ # and downgrade stale not_our_prospect escalations.
4
+ #
5
+ # Runs daily via launchd com.m13v.social-audit-dm-staleness.plist.
6
+
7
+ set -uo pipefail
8
+
9
+ # shellcheck source=/dev/null
10
+ [ -f "$HOME/social-autoposter/.env" ] && source "$HOME/social-autoposter/.env"
11
+
12
+ REPO_DIR="$HOME/social-autoposter"
13
+ LOG_DIR="$REPO_DIR/skill/logs"
14
+ mkdir -p "$LOG_DIR"
15
+ LOG_FILE="$LOG_DIR/audit-dm-staleness-$(date +%Y-%m-%d_%H%M%S).log"
16
+
17
+ log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_FILE"; }
18
+
19
+ # HTTP-only lane (2026-06-01): both staleness UPDATEs run server-side via the
20
+ # s4l.ai API (POST /api/v1/dms/staleness-sweep). No DATABASE_URL, no psql.
21
+ AUDIT_HELPER="$REPO_DIR/scripts/audit_helper.py"
22
+
23
+ RUN_START=$(date +%s)
24
+ log "=== DM staleness audit: $(date) ==="
25
+
26
+ # Both sweeps run in one POST and return {aged, downgraded}:
27
+ # 1. Ghosted outreach: no_response + active + older than 14 days -> stale.
28
+ # 2. Reverse-pitchers the reply bot escalated (needs_human + not_our_prospect)
29
+ # -> active; next inbound re-evaluates via classifier and re-escalates if needed.
30
+ SWEEP_JSON=$(python3 "$AUDIT_HELPER" dm-staleness-sweep 2>/dev/null || echo '{"aged":0,"downgraded":0}')
31
+ AGED=$(echo "$SWEEP_JSON" | python3 -c "import json,sys; print(int(json.load(sys.stdin).get('aged') or 0))" 2>/dev/null || echo "0")
32
+ DOWNGRADED=$(echo "$SWEEP_JSON" | python3 -c "import json,sys; print(int(json.load(sys.stdin).get('downgraded') or 0))" 2>/dev/null || echo "0")
33
+ log "Aged ghosted outreach to stale: $AGED"
34
+ log "Downgraded not_our_prospect escalations: $DOWNGRADED"
35
+
36
+ RUN_ELAPSED=$(( $(date +%s) - RUN_START ))
37
+ python3 "$REPO_DIR/scripts/log_run.py" --script "audit-dm-staleness" \
38
+ --posted "$AGED" --skipped "$DOWNGRADED" --failed 0 --cost 0 --elapsed "$RUN_ELAPSED" 2>/dev/null || true
39
+
40
+ log "=== Done in ${RUN_ELAPSED}s ==="
41
+
42
+ find "$LOG_DIR" -name "audit-dm-staleness-*.log" -mtime +30 -delete 2>/dev/null || true
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ # audit-linkedin.sh — LinkedIn-only audit (Python CDP + summary)
3
+
4
+ # LinkedIn killswitch (2026-05-27): refuse to run if a prior fire detected
5
+ # session compromise (http_999, authwall, throttle, li_at cleared).
6
+ # State: ~/.claude/social-autoposter/linkedin.killswitch
7
+ # Clear: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear
8
+ if [ -f "$HOME/.claude/social-autoposter/linkedin.killswitch" ]; then
9
+ echo "[$(date +%H:%M:%S)] LINKEDIN_KILLSWITCH active. Aborting LinkedIn pipeline."
10
+ echo " Re-auth LinkedIn in harness Chrome, then: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear"
11
+ exit 0
12
+ fi
13
+
14
+ exec "$(dirname "$0")/audit.sh" --platform linkedin
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ # audit-moltbook.sh — Moltbook-only audit (Moltbook API + summary)
3
+
4
+ exec "$(dirname "$0")/audit.sh" --platform moltbook
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env bash
2
+ # audit-reddit-resurrect.sh — Weekly check of Reddit posts marked deleted/removed
3
+ # in the last 60 days. If a post is now visible again, flips status back to active.
4
+
5
+ set -uo pipefail
6
+
7
+ source "$(dirname "$0")/lock.sh"
8
+ # reddit-harness backend (2026-05-29): exports REDDIT_CDP_URL=:9557 so the
9
+ # resurrect fetch (stats.py --reddit-resurrect -> reddit_tools.batch_fetch_info
10
+ # -> reddit_browser_fetch) attaches to the harness Chrome instead of 403ing on
11
+ # *.json from this residential IP. Source after lock.sh, before pre-flight.
12
+ source "$(dirname "$0")/lib/reddit-backend.sh"
13
+ # Unified reddit lock (2026-05-10): TTL-aware Python lease. The MCP proxy
14
+ # heartbeats expires_at on every reddit-agent call, so the lease stays held
15
+ # during real browser activity but auto-decays within 90s of idleness.
16
+ REPO_DIR_FOR_LOCK="$HOME/social-autoposter"
17
+ _release_reddit_lease() {
18
+ timeout 3 python3 "$REPO_DIR_FOR_LOCK/scripts/reddit_browser_lock.py" release 2>/dev/null || true
19
+ }
20
+ python3 "$REPO_DIR_FOR_LOCK/scripts/reddit_browser_lock.py" acquire --timeout 3600 --ttl 90 2>&1 || \
21
+ echo "WARNING: reddit_browser_lock.py acquire failed; proceeding without lease."
22
+ trap '_release_reddit_lease; _sa_release_locks' EXIT INT TERM HUP
23
+ if ! ensure_reddit_browser_for_backend 2>&1; then
24
+ echo "[audit-reddit-resurrect] WARNING: reddit-harness bootstrap failed; falling back to ensure_browser_healthy reddit"
25
+ ensure_browser_healthy "reddit"
26
+ fi
27
+ acquire_lock "audit-reddit-resurrect" 3600
28
+
29
+ # shellcheck source=/dev/null
30
+ [ -f "$HOME/social-autoposter/.env" ] && source "$HOME/social-autoposter/.env"
31
+
32
+ REPO_DIR="$HOME/social-autoposter"
33
+ LOG_DIR="$REPO_DIR/skill/logs"
34
+ # HTTP-only lane (2026-06-01): the candidate-count read goes through the s4l.ai
35
+ # API via scripts/audit_helper.py. No DATABASE_URL, no psql, no fallback.
36
+ AUDIT_HELPER="$REPO_DIR/scripts/audit_helper.py"
37
+
38
+ mkdir -p "$LOG_DIR"
39
+ LOG_FILE="$LOG_DIR/audit-reddit-resurrect-$(date +%Y-%m-%d_%H%M%S).log"
40
+
41
+ log() { echo "[$(date +%H:%M:%S)] $*" >> "$LOG_FILE"; echo "[$(date +%H:%M:%S)] $*"; }
42
+
43
+ RUN_START=$(date +%s)
44
+ log "=== Reddit resurrect audit: $(date) ==="
45
+
46
+ CANDIDATES=$(python3 "$AUDIT_HELPER" resurrect-candidates 2>/dev/null || echo "0")
47
+
48
+ log "Candidates: $CANDIDATES posts marked deleted/removed in last 60 days"
49
+
50
+ python3 "$REPO_DIR/scripts/stats.py" --reddit-resurrect --resurrect-days 60 >> "$LOG_FILE" 2>&1
51
+ EXIT_CODE=$?
52
+
53
+ if [ "$EXIT_CODE" -ne 0 ]; then
54
+ log "FAILED (exit $EXIT_CODE)"
55
+ else
56
+ log "Done"
57
+ fi
58
+
59
+ RESURRECTED=$(grep -c "^RESURRECTED " "$LOG_FILE" 2>/dev/null || echo "0")
60
+ log "Resurrected this run: $RESURRECTED"
61
+
62
+ RUN_ELAPSED=$(( $(date +%s) - RUN_START ))
63
+ python3 "$REPO_DIR/scripts/log_run.py" --script "audit-reddit-resurrect" --posted "$RESURRECTED" --skipped 0 --failed "$EXIT_CODE" --cost 0 --elapsed "$RUN_ELAPSED"
64
+
65
+ log "=== Reddit resurrect audit complete: $(date) ==="
66
+
67
+ find "$LOG_DIR" -name "audit-reddit-resurrect-*.log" -mtime +30 -delete 2>/dev/null || true
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ # audit-reddit.sh — Reddit-only audit (API deleted/removed check + summary)
3
+
4
+ exec "$(dirname "$0")/audit.sh" --platform reddit
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ # audit-twitter.sh — Twitter-only audit (fxtwitter API check + summary)
3
+
4
+ exec "$(dirname "$0")/audit.sh" --platform twitter