@m13v/s4l 1.6.197-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/README.md +143 -0
  2. package/SKILL.md +342 -0
  3. package/bin/cli.js +980 -0
  4. package/bin/cookie-helper.js +315 -0
  5. package/bin/platform.js +59 -0
  6. package/bin/scheduler/index.js +12 -0
  7. package/bin/scheduler/launchd.js +518 -0
  8. package/browser-agent-configs/all-agents-mcp.json +68 -0
  9. package/browser-agent-configs/linkedin-agent-mcp.json +16 -0
  10. package/browser-agent-configs/linkedin-agent.json +17 -0
  11. package/browser-agent-configs/linkedin-harness-mcp.json +21 -0
  12. package/browser-agent-configs/reddit-agent-mcp.json +16 -0
  13. package/browser-agent-configs/reddit-agent.json +17 -0
  14. package/browser-agent-configs/twitter-harness-mcp.json +18 -0
  15. package/config.example.json +45 -0
  16. package/mcp/dist/index.js +4212 -0
  17. package/mcp/dist/onboarding.js +200 -0
  18. package/mcp/dist/panel.html +176 -0
  19. package/mcp/dist/product-link.html +102 -0
  20. package/mcp/dist/repo.js +222 -0
  21. package/mcp/dist/runtime.js +1079 -0
  22. package/mcp/dist/screencast.js +323 -0
  23. package/mcp/dist/setup.js +545 -0
  24. package/mcp/dist/telemetry.js +306 -0
  25. package/mcp/dist/twitterAuth.js +138 -0
  26. package/mcp/dist/version.js +271 -0
  27. package/mcp/dist/version.json +4 -0
  28. package/mcp/install-runtime.mjs +70 -0
  29. package/mcp/install.mjs +169 -0
  30. package/mcp/manifest.json +80 -0
  31. package/mcp/menubar/dashboard_server.py +213 -0
  32. package/mcp/menubar/s4l_card.py +1336 -0
  33. package/mcp/menubar/s4l_log_relay.py +179 -0
  34. package/mcp/menubar/s4l_menubar.py +2439 -0
  35. package/mcp/menubar/s4l_state.py +891 -0
  36. package/mcp/package.json +34 -0
  37. package/mcp/shared/doctor.cjs +437 -0
  38. package/mcp/shared/onboarding-ledger.cjs +324 -0
  39. package/mcp-servers/browser-harness/server.py +968 -0
  40. package/package.json +160 -0
  41. package/requirements.txt +20 -0
  42. package/scripts/_compute_allowlist.py +58 -0
  43. package/scripts/_db_update.py +20 -0
  44. package/scripts/_filt.py +9 -0
  45. package/scripts/_li_notif_match.py +76 -0
  46. package/scripts/_li_notif_orchestrate.py +126 -0
  47. package/scripts/_lock_preempt_test.py +60 -0
  48. package/scripts/_run_icp_precheck.py +57 -0
  49. package/scripts/a16z_pearx_calendar_reminders.py +99 -0
  50. package/scripts/account_resolver.py +141 -0
  51. package/scripts/active_campaigns.py +114 -0
  52. package/scripts/active_users.py +190 -0
  53. package/scripts/amplitude_24h_signups.py +468 -0
  54. package/scripts/amplitude_signups.py +177 -0
  55. package/scripts/apply_onboarding_selections.py +131 -0
  56. package/scripts/audience_pages.py +243 -0
  57. package/scripts/audit_helper.py +120 -0
  58. package/scripts/author_history_block.py +353 -0
  59. package/scripts/autopilot_stall_watch.py +284 -0
  60. package/scripts/backfill_twitter_attempts_topic.py +81 -0
  61. package/scripts/backfill_twitter_log_post_no_id.py +322 -0
  62. package/scripts/bench_dashboard.sh +138 -0
  63. package/scripts/bh_send.py +39 -0
  64. package/scripts/build_persona.py +409 -0
  65. package/scripts/bulk_icp.py +18 -0
  66. package/scripts/campaign_bump.py +51 -0
  67. package/scripts/capture_thread_media.py +288 -0
  68. package/scripts/check_browser_lock_health.sh +81 -0
  69. package/scripts/check_external_pool_depth.py +253 -0
  70. package/scripts/check_unread_web_chats.py +28 -0
  71. package/scripts/claim_web_chat.py +47 -0
  72. package/scripts/classify_run_error.py +158 -0
  73. package/scripts/claude_job.py +988 -0
  74. package/scripts/clean_stale_singleton.sh +56 -0
  75. package/scripts/cleanup_harness_tabs.py +68 -0
  76. package/scripts/copy_browser_cookies.py +454 -0
  77. package/scripts/counterparty_history.py +350 -0
  78. package/scripts/db.py +57 -0
  79. package/scripts/discover_claude_profiles.py +120 -0
  80. package/scripts/discover_linkedin_candidates.py +984 -0
  81. package/scripts/dm_conversation.py +682 -0
  82. package/scripts/dm_db_update.py +69 -0
  83. package/scripts/dm_engage_helper.py +161 -0
  84. package/scripts/dm_outreach_helper.py +147 -0
  85. package/scripts/dm_outreach_twitter_helper.py +129 -0
  86. package/scripts/dm_send_log.py +106 -0
  87. package/scripts/dm_short_links.py +1084 -0
  88. package/scripts/dump_web_chat_history.py +47 -0
  89. package/scripts/engage_github.py +640 -0
  90. package/scripts/engage_reddit.py +1235 -0
  91. package/scripts/engage_twitter_helper.py +301 -0
  92. package/scripts/engagement_styles.py +1787 -0
  93. package/scripts/enrich_twitter_candidates.py +82 -0
  94. package/scripts/feedback_digest.py +448 -0
  95. package/scripts/fetch_prospect_profile.py +312 -0
  96. package/scripts/fetch_twitter_t1.py +134 -0
  97. package/scripts/find_threads.py +530 -0
  98. package/scripts/follow_gate_log.py +59 -0
  99. package/scripts/funnel_per_day.py +194 -0
  100. package/scripts/generate_daily_human_style.py +494 -0
  101. package/scripts/generation_trace.py +173 -0
  102. package/scripts/get_run_cost.py +107 -0
  103. package/scripts/github_engage_helper.py +93 -0
  104. package/scripts/github_tools.py +509 -0
  105. package/scripts/harness_overlay.py +556 -0
  106. package/scripts/harvest_twitter_following.py +243 -0
  107. package/scripts/heartbeat.sh +70 -0
  108. package/scripts/history_context.py +284 -0
  109. package/scripts/http_api.py +206 -0
  110. package/scripts/human_dm_replies_helper.py +169 -0
  111. package/scripts/identity.py +302 -0
  112. package/scripts/ig_batch_creator.sh +93 -0
  113. package/scripts/ig_post_type_picker.py +243 -0
  114. package/scripts/ig_scrape_transcribe.sh +91 -0
  115. package/scripts/ingest_human_dm_replies.py +271 -0
  116. package/scripts/ingest_web_chat_replies.py +229 -0
  117. package/scripts/install_fleet.py +187 -0
  118. package/scripts/invent_mcp_server.py +350 -0
  119. package/scripts/invent_topics.py +1462 -0
  120. package/scripts/learned_preferences.py +263 -0
  121. package/scripts/li_discovery.py +161 -0
  122. package/scripts/link_edit_helper.py +142 -0
  123. package/scripts/link_tail.py +592 -0
  124. package/scripts/linkedin_api.py +561 -0
  125. package/scripts/linkedin_browser.py +730 -0
  126. package/scripts/linkedin_cooldown.py +128 -0
  127. package/scripts/linkedin_exclusions.py +234 -0
  128. package/scripts/linkedin_killswitch.py +1333 -0
  129. package/scripts/linkedin_search_topic_schema.py +49 -0
  130. package/scripts/linkedin_unipile.py +658 -0
  131. package/scripts/linkedin_url.py +228 -0
  132. package/scripts/log_claude_session.py +636 -0
  133. package/scripts/log_draft.py +143 -0
  134. package/scripts/log_linkedin_search_attempts.py +126 -0
  135. package/scripts/log_post.py +651 -0
  136. package/scripts/log_run.py +364 -0
  137. package/scripts/log_thread_media.py +108 -0
  138. package/scripts/log_twitter_search_attempts.py +150 -0
  139. package/scripts/log_twitter_skips.py +211 -0
  140. package/scripts/lookup_post.py +78 -0
  141. package/scripts/mark_web_chat_processed.py +32 -0
  142. package/scripts/mcp_lock_proxy.py +370 -0
  143. package/scripts/memory_snapshot.py +972 -0
  144. package/scripts/merge_review_queue.py +215 -0
  145. package/scripts/mint_external_pool.py +182 -0
  146. package/scripts/mint_kent_pool.py +249 -0
  147. package/scripts/moltbook_post.py +320 -0
  148. package/scripts/moltbook_tools.py +159 -0
  149. package/scripts/pending_threads.py +188 -0
  150. package/scripts/pick_ig_account.py +177 -0
  151. package/scripts/pick_project.py +208 -0
  152. package/scripts/pick_search_topic.py +771 -0
  153. package/scripts/pick_thread_target.py +279 -0
  154. package/scripts/pick_twitter_thread_target.py +202 -0
  155. package/scripts/podlog_fetch_batch.sh +32 -0
  156. package/scripts/post_github.py +1311 -0
  157. package/scripts/post_reddit.py +2668 -0
  158. package/scripts/precompute_dashboard_stats.py +204 -0
  159. package/scripts/preflight.sh +297 -0
  160. package/scripts/progress.py +88 -0
  161. package/scripts/project_excludes.py +353 -0
  162. package/scripts/project_slugs.py +91 -0
  163. package/scripts/project_stats.py +241 -0
  164. package/scripts/project_stats_json.py +1563 -0
  165. package/scripts/project_topics.py +192 -0
  166. package/scripts/qualified_query_bank.py +436 -0
  167. package/scripts/reap_stale_claude_sessions.py +867 -0
  168. package/scripts/reddit_browser.py +2549 -0
  169. package/scripts/reddit_browser_fetch.py +141 -0
  170. package/scripts/reddit_browser_lock.py +593 -0
  171. package/scripts/reddit_chat_sync.py +710 -0
  172. package/scripts/reddit_query_bank.py +200 -0
  173. package/scripts/reddit_threads_helper.py +151 -0
  174. package/scripts/reddit_tools.py +956 -0
  175. package/scripts/refresh_instagram_tokens.py +280 -0
  176. package/scripts/release-mcpb.sh +513 -0
  177. package/scripts/reply_db.py +334 -0
  178. package/scripts/reply_insert.py +98 -0
  179. package/scripts/reply_risk_digest.py +761 -0
  180. package/scripts/reset-test-machine.sh +602 -0
  181. package/scripts/restore_twitter_session.py +177 -0
  182. package/scripts/ripen_reddit_plan.py +478 -0
  183. package/scripts/run_claude.sh +433 -0
  184. package/scripts/run_moltbook_cycle.py +555 -0
  185. package/scripts/s4l_box_update.sh +226 -0
  186. package/scripts/s4l_channel.py +103 -0
  187. package/scripts/s4l_ctl.sh +75 -0
  188. package/scripts/s4l_env.py +47 -0
  189. package/scripts/saps_activity.py +126 -0
  190. package/scripts/saps_mode.py +328 -0
  191. package/scripts/scan_dm_candidates.py +580 -0
  192. package/scripts/scan_github_replies.py +168 -0
  193. package/scripts/scan_instagram_comments.py +481 -0
  194. package/scripts/scan_moltbook_replies.py +252 -0
  195. package/scripts/scan_pii.py +190 -0
  196. package/scripts/scan_reddit_replies.py +377 -0
  197. package/scripts/scan_twitter_mentions_browser.py +327 -0
  198. package/scripts/scan_twitter_thread_followups.py +299 -0
  199. package/scripts/scan_x_profile.py +384 -0
  200. package/scripts/schedule_state.py +202 -0
  201. package/scripts/scheduled_tasks_snapshot.py +123 -0
  202. package/scripts/score_linkedin_candidates.py +419 -0
  203. package/scripts/score_twitter_candidates.py +718 -0
  204. package/scripts/scrape_linkedin_comment_stats.py +1755 -0
  205. package/scripts/scrape_linkedin_stats_browser.py +52 -0
  206. package/scripts/scrape_reddit_views.py +365 -0
  207. package/scripts/seed_search_queries.py +453 -0
  208. package/scripts/seed_search_topics.py +127 -0
  209. package/scripts/send_web_chat_reply.py +130 -0
  210. package/scripts/sentry_init.py +128 -0
  211. package/scripts/setup_twitter_auth.py +1320 -0
  212. package/scripts/snapshot.py +583 -0
  213. package/scripts/stats.py +2702 -0
  214. package/scripts/stats_helper.py +52 -0
  215. package/scripts/strike_alert.py +783 -0
  216. package/scripts/sweep_post_link_clicks.py +107 -0
  217. package/scripts/sync_ig_to_posts.py +147 -0
  218. package/scripts/test_browser_lock.py +189 -0
  219. package/scripts/test_installation_api.sh +52 -0
  220. package/scripts/test_percard_posting.py +142 -0
  221. package/scripts/top_dud_linkedin_queries.py +71 -0
  222. package/scripts/top_dud_reddit_queries.py +67 -0
  223. package/scripts/top_dud_twitter_queries.py +71 -0
  224. package/scripts/top_dud_twitter_topics.py +102 -0
  225. package/scripts/top_linkedin_queries.py +55 -0
  226. package/scripts/top_omitted_reddit_topics.py +91 -0
  227. package/scripts/top_performers.py +588 -0
  228. package/scripts/top_search_topics.py +180 -0
  229. package/scripts/top_twitter_queries.py +190 -0
  230. package/scripts/twitter_access_check.py +382 -0
  231. package/scripts/twitter_account.py +41 -0
  232. package/scripts/twitter_batch_phase.py +126 -0
  233. package/scripts/twitter_browser.py +2804 -0
  234. package/scripts/twitter_cookie_mirror.py +130 -0
  235. package/scripts/twitter_cycle_helper.py +310 -0
  236. package/scripts/twitter_gen_links.py +287 -0
  237. package/scripts/twitter_post_plan.py +1188 -0
  238. package/scripts/twitter_scan.py +324 -0
  239. package/scripts/twitter_supply_signal.py +57 -0
  240. package/scripts/twitter_threads_helper.py +152 -0
  241. package/scripts/unclaim_web_chat.py +29 -0
  242. package/scripts/update_instagram_stats.py +261 -0
  243. package/scripts/update_linkedin_stats_from_feed.py +328 -0
  244. package/scripts/version.py +72 -0
  245. package/scripts/watchdog_hung_runs.py +343 -0
  246. package/scripts/write_generation_trace.py +73 -0
  247. package/setup/SKILL.md +277 -0
  248. package/skill/amplitude-24h-signups.sh +38 -0
  249. package/skill/archive-old-logs.sh +40 -0
  250. package/skill/audit-dm-staleness.sh +42 -0
  251. package/skill/audit-linkedin.sh +14 -0
  252. package/skill/audit-moltbook.sh +4 -0
  253. package/skill/audit-reddit-resurrect.sh +67 -0
  254. package/skill/audit-reddit.sh +4 -0
  255. package/skill/audit-twitter.sh +4 -0
  256. package/skill/audit.sh +287 -0
  257. package/skill/backfill-twitter-attempts-topic.sh +19 -0
  258. package/skill/backfill-twitter-ghost-posts.sh +24 -0
  259. package/skill/check-external-pool-depth.sh +7 -0
  260. package/skill/check-web-chats.sh +203 -0
  261. package/skill/dm-outreach-linkedin.sh +250 -0
  262. package/skill/dm-outreach-reddit.sh +274 -0
  263. package/skill/dm-outreach-twitter.sh +265 -0
  264. package/skill/engage-dm-replies-linkedin.sh +4 -0
  265. package/skill/engage-dm-replies-reddit.sh +4 -0
  266. package/skill/engage-dm-replies-twitter.sh +4 -0
  267. package/skill/engage-dm-replies.sh +1597 -0
  268. package/skill/engage-linkedin.sh +581 -0
  269. package/skill/engage-moltbook.sh +36 -0
  270. package/skill/engage-reddit.sh +146 -0
  271. package/skill/engage-twitter.sh +467 -0
  272. package/skill/github-engage.sh +176 -0
  273. package/skill/ingest-web-chat-replies.sh +38 -0
  274. package/skill/invent-supply-test.sh +100 -0
  275. package/skill/invent-topics.sh +50 -0
  276. package/skill/lib/linkedin-backend.sh +364 -0
  277. package/skill/lib/platform.sh +48 -0
  278. package/skill/lib/reddit-backend.sh +234 -0
  279. package/skill/lib/twitter-backend.sh +314 -0
  280. package/skill/link-edit-github.sh +136 -0
  281. package/skill/link-edit-moltbook.sh +117 -0
  282. package/skill/link-edit-reddit.sh +201 -0
  283. package/skill/linkedin-presence.sh +182 -0
  284. package/skill/linkedin-recovery.sh +282 -0
  285. package/skill/lock.sh +647 -0
  286. package/skill/memory-snapshot.sh +39 -0
  287. package/skill/precompute-stats.sh +35 -0
  288. package/skill/prewarm-funnel.sh +104 -0
  289. package/skill/refresh-instagram-tokens.sh +57 -0
  290. package/skill/refresh-twitter-following.sh +52 -0
  291. package/skill/reply-risk-digest.sh +31 -0
  292. package/skill/run-cycle-update-guard.sh +44 -0
  293. package/skill/run-draft-and-publish.sh +123 -0
  294. package/skill/run-generate-daily-style.sh +50 -0
  295. package/skill/run-github-launchd.sh +62 -0
  296. package/skill/run-github.sh +102 -0
  297. package/skill/run-instagram-daily.sh +149 -0
  298. package/skill/run-instagram-render.sh +875 -0
  299. package/skill/run-linkedin-launchd.sh +81 -0
  300. package/skill/run-linkedin-unipile.sh +130 -0
  301. package/skill/run-linkedin.sh +1593 -0
  302. package/skill/run-moltbook-launchd.sh +61 -0
  303. package/skill/run-moltbook.sh +38 -0
  304. package/skill/run-overlay-watch.sh +100 -0
  305. package/skill/run-reddit-search-launchd.sh +64 -0
  306. package/skill/run-reddit-search.sh +505 -0
  307. package/skill/run-reddit-threads-double.sh +32 -0
  308. package/skill/run-reddit-threads.sh +847 -0
  309. package/skill/run-scan-moltbook-replies.sh +57 -0
  310. package/skill/run-twitter-cycle-launchd.sh +63 -0
  311. package/skill/run-twitter-cycle-singleton.sh +62 -0
  312. package/skill/run-twitter-cycle.sh +2408 -0
  313. package/skill/run-twitter-threads.sh +592 -0
  314. package/skill/scan-instagram-replies.sh +61 -0
  315. package/skill/scan-twitter-followups.sh +57 -0
  316. package/skill/social-autoposter-update.sh +66 -0
  317. package/skill/stats-instagram.sh +72 -0
  318. package/skill/stats-linkedin.sh +271 -0
  319. package/skill/stats-moltbook.sh +4 -0
  320. package/skill/stats-reddit.sh +4 -0
  321. package/skill/stats-twitter.sh +4 -0
  322. package/skill/stats.sh +521 -0
  323. package/skill/strike-alert.sh +18 -0
  324. package/skill/styles.sh +87 -0
  325. package/skill/sweep-link-clicks.sh +40 -0
  326. package/skill/topics.sh +51 -0
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env python3
2
+ """Send a founder reply on a web-chat thread (HTTP-only DB layer).
3
+
4
+ Mirror of ~/fazm/inbox/scripts/send-chat-reply.js. The DB work (dedup guard,
5
+ insert the agent/founder message, mark visitor messages read, bump thread
6
+ metadata) runs through POST /api/v1/web-chat/threads/<id>/reply. The Resend
7
+ email forward to the visitor stays local (send-email.js), and the resulting
8
+ Resend id is stamped back onto the message via PATCH
9
+ /api/v1/web-chat/messages/<id>.
10
+
11
+ Dedup guard (server-side): skip if a founder/agent message was already sent in
12
+ the last 60 seconds (matches Fazm behaviour).
13
+
14
+ Usage:
15
+ python3 send_web_chat_reply.py --thread <thread_id> --text "reply" [--name "matt"]
16
+ [--sender agent|founder] [--no-email]
17
+ [--ingested-gmail-id <gmail_id>]
18
+ """
19
+
20
+ import argparse
21
+ import os
22
+ import subprocess
23
+ import sys
24
+
25
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
26
+ from http_api import api_post, api_patch
27
+
28
+ SEND_EMAIL_SCRIPT = os.path.expanduser("~/analytics/scripts/send-email.js")
29
+ NODE_BIN = os.path.expanduser("~/.nvm/versions/node/v20.19.4/bin/node")
30
+ NODE_PATH = os.path.expanduser("~/analytics/node_modules")
31
+
32
+
33
+ def project_config(project_name):
34
+ """Read the project entry from ~/social-autoposter/config.json."""
35
+ import json
36
+
37
+ cfg_path = os.path.expanduser("~/social-autoposter/config.json")
38
+ with open(cfg_path) as f:
39
+ cfg = json.load(f)
40
+ for p in cfg.get("projects", []):
41
+ if p.get("name") == project_name:
42
+ return p
43
+ return {}
44
+
45
+
46
+ def email_visitor(visitor_email, project_name, text, project_cfg):
47
+ """Forward founder reply to visitor's email via Resend (send-email.js)."""
48
+ if not visitor_email or not SEND_EMAIL_SCRIPT or not os.path.exists(SEND_EMAIL_SCRIPT):
49
+ return None
50
+
51
+ web_chat_cfg = (project_cfg or {}).get("web_chat", {}) or {}
52
+ from_email = web_chat_cfg.get("from_email") or "Matt <matt@mail.omi.me>"
53
+ site_pretty = (project_cfg.get("website") or "").replace("https://", "").replace("http://", "").rstrip("/")
54
+ subject = f"re: your message on {site_pretty}" if site_pretty else "re: your message"
55
+
56
+ env = os.environ.copy()
57
+ env.setdefault("NODE_PATH", NODE_PATH)
58
+
59
+ args = [
60
+ NODE_BIN, SEND_EMAIL_SCRIPT,
61
+ "--to", visitor_email,
62
+ "--subject", subject,
63
+ "--body", text,
64
+ "--from", from_email,
65
+ "--no-db", # web_chat has its own DB rail
66
+ ]
67
+ try:
68
+ result = subprocess.run(args, env=env, capture_output=True, text=True, timeout=30)
69
+ # send-email.js prints "Sent! Resend ID: <id>"
70
+ for line in (result.stdout or "").splitlines():
71
+ if "Resend ID:" in line:
72
+ return line.split("Resend ID:")[-1].strip()
73
+ except Exception as e:
74
+ print(f" WARN: visitor email send failed: {e}", file=sys.stderr)
75
+ return None
76
+
77
+
78
+ def main():
79
+ parser = argparse.ArgumentParser()
80
+ parser.add_argument("--thread", required=True)
81
+ parser.add_argument("--text", required=True)
82
+ parser.add_argument("--name", default="matt")
83
+ parser.add_argument("--sender", default="agent", choices=["agent", "founder"])
84
+ parser.add_argument("--no-email", action="store_true",
85
+ help="Skip Resend forward to visitor (used when ingest already emailed)")
86
+ parser.add_argument("--ingested-gmail-id", default=None,
87
+ help="Stamp this Gmail id on the inserted message (Gmail-ingest rail dedup)")
88
+ args = parser.parse_args()
89
+
90
+ # 1-3 (dedup + insert + mark-read + bump) all happen server-side.
91
+ body = {
92
+ "text": args.text,
93
+ "name": args.name,
94
+ "sender": args.sender,
95
+ }
96
+ if args.ingested_gmail_id:
97
+ body["ingested_gmail_id"] = args.ingested_gmail_id
98
+
99
+ resp = api_post(
100
+ f"/api/v1/web-chat/threads/{args.thread}/reply", body, ok_on_404=True,
101
+ )
102
+ if resp.get("_not_found"):
103
+ print(f"ERROR: thread {args.thread} not found", file=sys.stderr)
104
+ sys.exit(1)
105
+
106
+ data = resp.get("data") or {}
107
+ if data.get("deduped"):
108
+ print(f"dedup: founder/agent message sent {int(data.get('age_s', 0))}s ago, skipping")
109
+ return
110
+
111
+ msg_id = data.get("message_id")
112
+ visitor_email = data.get("visitor_email") or ""
113
+ project_name = data.get("project_name")
114
+
115
+ # 4. Forward to visitor email so they get the reply when widget is closed.
116
+ visitor_resend_id = None
117
+ if not args.no_email and visitor_email:
118
+ cfg = project_config(project_name)
119
+ visitor_resend_id = email_visitor(visitor_email, project_name, args.text, cfg)
120
+ if visitor_resend_id and msg_id:
121
+ api_patch(
122
+ f"/api/v1/web-chat/messages/{msg_id}",
123
+ {"visitor_email_id": visitor_resend_id},
124
+ )
125
+
126
+ print(f"sent reply (msg #{msg_id}, visitor_email_id={visitor_resend_id or '-'})")
127
+
128
+
129
+ if __name__ == "__main__":
130
+ main()
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env python3
2
+ """Best-effort Sentry init for the social-autoposter Python posting pipeline.
3
+
4
+ Imported once from scripts/http_api.py (the central HTTP-lane client, pulled in
5
+ by ~100 pipeline scripts), so any pipeline process that talks to the s4l.ai API
6
+ gets Sentry error capture. Mirrors the Node .mcpb telemetry (mcp/src/telemetry.ts)
7
+ and the Fazm app. Sentry org: `mediar-n5`.
8
+
9
+ HARD RULE: this must NEVER raise into the caller. If sentry-sdk is missing or
10
+ the DSN is unset, init() is a silent no-op so the pipeline runs unchanged.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import os
15
+
16
+ # Client-side DSN: safe to embed, same posture as the Node telemetry and Fazm's
17
+ # hardcoded Swift DSN. Overridable via env for dev. Empty -> Sentry disabled.
18
+ _EMBEDDED_DSN = "https://4d44ac907262c6545cf8681703528d04@o4507617161314304.ingest.us.sentry.io/4511598804336640"
19
+
20
+ _initialized = False
21
+
22
+
23
+ def init() -> None:
24
+ """Initialize Sentry once per process. Idempotent and exception-safe."""
25
+ global _initialized
26
+ if _initialized:
27
+ return
28
+ # Set early so a failure below never re-attempts on every http_api import.
29
+ _initialized = True
30
+
31
+ dsn = os.environ.get("S4L_SENTRY_DSN") or _EMBEDDED_DSN
32
+ if not dsn:
33
+ return
34
+ try:
35
+ import sentry_sdk
36
+ except Exception:
37
+ return # sentry-sdk not installed in this runtime; stay silent
38
+ try:
39
+ env = "development" if os.environ.get("S4L_ENV") == "development" else "production"
40
+ sentry_sdk.init(
41
+ dsn=dsn,
42
+ environment=env,
43
+ release=os.environ.get("S4L_VERSION") or None,
44
+ traces_sample_rate=0.0, # errors only, no performance tracing
45
+ send_default_pii=False,
46
+ )
47
+ _tag_install(sentry_sdk)
48
+ except Exception:
49
+ return
50
+
51
+
52
+ def _tag_install(sentry_sdk) -> None:
53
+ """Attach the stable install_id so events are attributable + cross-ref the
54
+ install-lane digest. Best-effort; identity.py is standalone (no http_api
55
+ import) so there is no import cycle."""
56
+ try:
57
+ from identity import get_identity
58
+
59
+ ident = get_identity() or {}
60
+ iid = ident.get("install_id")
61
+ host = ident.get("hostname")
62
+ if iid:
63
+ sentry_sdk.set_tag("install_id", str(iid))
64
+ if host:
65
+ sentry_sdk.set_tag("hostname", str(host))
66
+ except Exception:
67
+ pass
68
+
69
+
70
+ def capture_exception(err, tags=None) -> None:
71
+ """Explicitly report an exception to Sentry with optional tags. Safe to call
72
+ even if init() was never run or sentry-sdk is missing (silent no-op). Use for
73
+ swallowed/handled errors that would otherwise never reach Sentry (the global
74
+ excepthook only catches UNHANDLED ones)."""
75
+ if not _initialized:
76
+ return
77
+ try:
78
+ import sentry_sdk
79
+ except Exception:
80
+ return
81
+ try:
82
+ if tags:
83
+ with sentry_sdk.push_scope() as scope:
84
+ for k, v in tags.items():
85
+ scope.set_tag(str(k), str(v))
86
+ sentry_sdk.capture_exception(err)
87
+ else:
88
+ sentry_sdk.capture_exception(err)
89
+ except Exception:
90
+ return
91
+
92
+
93
+ def capture_message(message, level="error", tags=None) -> None:
94
+ """Report a handled, non-exception condition to Sentry (e.g. a post that
95
+ failed gracefully and returned a reason instead of raising). The global
96
+ excepthook only catches UNHANDLED exceptions, so operational failures that
97
+ are caught + reported as a result count would otherwise never reach Sentry.
98
+ Safe to call even if init() never ran or sentry-sdk is missing (no-op)."""
99
+ if not _initialized:
100
+ return
101
+ try:
102
+ import sentry_sdk
103
+ except Exception:
104
+ return
105
+ try:
106
+ if tags:
107
+ with sentry_sdk.push_scope() as scope:
108
+ for k, v in tags.items():
109
+ scope.set_tag(str(k), str(v))
110
+ sentry_sdk.capture_message(str(message), level=level)
111
+ else:
112
+ sentry_sdk.capture_message(str(message), level=level)
113
+ except Exception:
114
+ return
115
+
116
+
117
+ def flush(timeout: float = 2.0) -> None:
118
+ """Block until queued events are sent (best-effort). Call before a short-lived
119
+ or about-to-crash process exits so a just-captured event isn't dropped on
120
+ teardown."""
121
+ if not _initialized:
122
+ return
123
+ try:
124
+ import sentry_sdk
125
+
126
+ sentry_sdk.flush(timeout)
127
+ except Exception:
128
+ return