@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.
- package/README.md +143 -0
- package/SKILL.md +342 -0
- package/bin/cli.js +980 -0
- package/bin/cookie-helper.js +315 -0
- package/bin/platform.js +59 -0
- package/bin/scheduler/index.js +12 -0
- package/bin/scheduler/launchd.js +518 -0
- package/browser-agent-configs/all-agents-mcp.json +68 -0
- package/browser-agent-configs/linkedin-agent-mcp.json +16 -0
- package/browser-agent-configs/linkedin-agent.json +17 -0
- package/browser-agent-configs/linkedin-harness-mcp.json +21 -0
- package/browser-agent-configs/reddit-agent-mcp.json +16 -0
- package/browser-agent-configs/reddit-agent.json +17 -0
- package/browser-agent-configs/twitter-harness-mcp.json +18 -0
- package/config.example.json +45 -0
- package/mcp/dist/index.js +4212 -0
- package/mcp/dist/onboarding.js +200 -0
- package/mcp/dist/panel.html +176 -0
- package/mcp/dist/product-link.html +102 -0
- package/mcp/dist/repo.js +222 -0
- package/mcp/dist/runtime.js +1079 -0
- package/mcp/dist/screencast.js +323 -0
- package/mcp/dist/setup.js +545 -0
- package/mcp/dist/telemetry.js +306 -0
- package/mcp/dist/twitterAuth.js +138 -0
- package/mcp/dist/version.js +271 -0
- package/mcp/dist/version.json +4 -0
- package/mcp/install-runtime.mjs +70 -0
- package/mcp/install.mjs +169 -0
- package/mcp/manifest.json +80 -0
- package/mcp/menubar/dashboard_server.py +213 -0
- package/mcp/menubar/s4l_card.py +1314 -0
- package/mcp/menubar/s4l_log_relay.py +179 -0
- package/mcp/menubar/s4l_menubar.py +2439 -0
- package/mcp/menubar/s4l_state.py +891 -0
- package/mcp/package.json +34 -0
- package/mcp/shared/doctor.cjs +437 -0
- package/mcp/shared/onboarding-ledger.cjs +324 -0
- package/mcp-servers/browser-harness/server.py +968 -0
- package/package.json +160 -0
- package/requirements.txt +20 -0
- package/scripts/_compute_allowlist.py +58 -0
- package/scripts/_db_update.py +20 -0
- package/scripts/_filt.py +9 -0
- package/scripts/_li_notif_match.py +76 -0
- package/scripts/_li_notif_orchestrate.py +126 -0
- package/scripts/_lock_preempt_test.py +60 -0
- package/scripts/_run_icp_precheck.py +57 -0
- package/scripts/a16z_pearx_calendar_reminders.py +99 -0
- package/scripts/account_resolver.py +141 -0
- package/scripts/active_campaigns.py +114 -0
- package/scripts/active_users.py +190 -0
- package/scripts/amplitude_24h_signups.py +468 -0
- package/scripts/amplitude_signups.py +177 -0
- package/scripts/apply_onboarding_selections.py +131 -0
- package/scripts/audience_pages.py +243 -0
- package/scripts/audit_helper.py +120 -0
- package/scripts/author_history_block.py +353 -0
- package/scripts/autopilot_stall_watch.py +284 -0
- package/scripts/backfill_twitter_attempts_topic.py +81 -0
- package/scripts/backfill_twitter_log_post_no_id.py +322 -0
- package/scripts/bench_dashboard.sh +138 -0
- package/scripts/bh_send.py +39 -0
- package/scripts/build_persona.py +409 -0
- package/scripts/bulk_icp.py +18 -0
- package/scripts/campaign_bump.py +51 -0
- package/scripts/capture_thread_media.py +288 -0
- package/scripts/check_browser_lock_health.sh +81 -0
- package/scripts/check_external_pool_depth.py +253 -0
- package/scripts/check_unread_web_chats.py +28 -0
- package/scripts/claim_web_chat.py +47 -0
- package/scripts/classify_run_error.py +158 -0
- package/scripts/claude_job.py +988 -0
- package/scripts/clean_stale_singleton.sh +56 -0
- package/scripts/cleanup_harness_tabs.py +68 -0
- package/scripts/copy_browser_cookies.py +454 -0
- package/scripts/counterparty_history.py +350 -0
- package/scripts/db.py +57 -0
- package/scripts/discover_claude_profiles.py +120 -0
- package/scripts/discover_linkedin_candidates.py +984 -0
- package/scripts/dm_conversation.py +682 -0
- package/scripts/dm_db_update.py +69 -0
- package/scripts/dm_engage_helper.py +161 -0
- package/scripts/dm_outreach_helper.py +147 -0
- package/scripts/dm_outreach_twitter_helper.py +129 -0
- package/scripts/dm_send_log.py +106 -0
- package/scripts/dm_short_links.py +1084 -0
- package/scripts/dump_web_chat_history.py +47 -0
- package/scripts/engage_github.py +640 -0
- package/scripts/engage_reddit.py +1235 -0
- package/scripts/engage_twitter_helper.py +301 -0
- package/scripts/engagement_styles.py +1787 -0
- package/scripts/enrich_twitter_candidates.py +82 -0
- package/scripts/feedback_digest.py +448 -0
- package/scripts/fetch_prospect_profile.py +312 -0
- package/scripts/fetch_twitter_t1.py +134 -0
- package/scripts/find_threads.py +530 -0
- package/scripts/follow_gate_log.py +59 -0
- package/scripts/funnel_per_day.py +194 -0
- package/scripts/generate_daily_human_style.py +494 -0
- package/scripts/generation_trace.py +173 -0
- package/scripts/get_run_cost.py +107 -0
- package/scripts/github_engage_helper.py +93 -0
- package/scripts/github_tools.py +509 -0
- package/scripts/harness_overlay.py +556 -0
- package/scripts/harvest_twitter_following.py +243 -0
- package/scripts/heartbeat.sh +70 -0
- package/scripts/history_context.py +284 -0
- package/scripts/http_api.py +206 -0
- package/scripts/human_dm_replies_helper.py +169 -0
- package/scripts/identity.py +302 -0
- package/scripts/ig_batch_creator.sh +93 -0
- package/scripts/ig_post_type_picker.py +243 -0
- package/scripts/ig_scrape_transcribe.sh +91 -0
- package/scripts/ingest_human_dm_replies.py +271 -0
- package/scripts/ingest_web_chat_replies.py +229 -0
- package/scripts/install_fleet.py +187 -0
- package/scripts/invent_mcp_server.py +350 -0
- package/scripts/invent_topics.py +1462 -0
- package/scripts/learned_preferences.py +263 -0
- package/scripts/li_discovery.py +161 -0
- package/scripts/link_edit_helper.py +142 -0
- package/scripts/link_tail.py +592 -0
- package/scripts/linkedin_api.py +561 -0
- package/scripts/linkedin_browser.py +730 -0
- package/scripts/linkedin_cooldown.py +128 -0
- package/scripts/linkedin_exclusions.py +234 -0
- package/scripts/linkedin_killswitch.py +1333 -0
- package/scripts/linkedin_search_topic_schema.py +49 -0
- package/scripts/linkedin_unipile.py +658 -0
- package/scripts/linkedin_url.py +228 -0
- package/scripts/log_claude_session.py +636 -0
- package/scripts/log_draft.py +143 -0
- package/scripts/log_linkedin_search_attempts.py +126 -0
- package/scripts/log_post.py +651 -0
- package/scripts/log_run.py +364 -0
- package/scripts/log_thread_media.py +108 -0
- package/scripts/log_twitter_search_attempts.py +150 -0
- package/scripts/log_twitter_skips.py +211 -0
- package/scripts/lookup_post.py +78 -0
- package/scripts/mark_web_chat_processed.py +32 -0
- package/scripts/mcp_lock_proxy.py +370 -0
- package/scripts/memory_snapshot.py +972 -0
- package/scripts/merge_review_queue.py +215 -0
- package/scripts/mint_external_pool.py +182 -0
- package/scripts/mint_kent_pool.py +249 -0
- package/scripts/moltbook_post.py +320 -0
- package/scripts/moltbook_tools.py +159 -0
- package/scripts/pending_threads.py +188 -0
- package/scripts/pick_ig_account.py +177 -0
- package/scripts/pick_project.py +208 -0
- package/scripts/pick_search_topic.py +771 -0
- package/scripts/pick_thread_target.py +279 -0
- package/scripts/pick_twitter_thread_target.py +202 -0
- package/scripts/podlog_fetch_batch.sh +32 -0
- package/scripts/post_github.py +1311 -0
- package/scripts/post_reddit.py +2668 -0
- package/scripts/precompute_dashboard_stats.py +204 -0
- package/scripts/preflight.sh +297 -0
- package/scripts/progress.py +88 -0
- package/scripts/project_excludes.py +353 -0
- package/scripts/project_slugs.py +91 -0
- package/scripts/project_stats.py +241 -0
- package/scripts/project_stats_json.py +1563 -0
- package/scripts/project_topics.py +192 -0
- package/scripts/qualified_query_bank.py +436 -0
- package/scripts/reap_stale_claude_sessions.py +867 -0
- package/scripts/reddit_browser.py +2549 -0
- package/scripts/reddit_browser_fetch.py +141 -0
- package/scripts/reddit_browser_lock.py +593 -0
- package/scripts/reddit_chat_sync.py +710 -0
- package/scripts/reddit_query_bank.py +200 -0
- package/scripts/reddit_threads_helper.py +151 -0
- package/scripts/reddit_tools.py +956 -0
- package/scripts/refresh_instagram_tokens.py +280 -0
- package/scripts/release-mcpb.sh +497 -0
- package/scripts/reply_db.py +334 -0
- package/scripts/reply_insert.py +98 -0
- package/scripts/reply_risk_digest.py +761 -0
- package/scripts/reset-test-machine.sh +602 -0
- package/scripts/restore_twitter_session.py +177 -0
- package/scripts/ripen_reddit_plan.py +478 -0
- package/scripts/run_claude.sh +433 -0
- package/scripts/run_moltbook_cycle.py +555 -0
- package/scripts/s4l_box_update.sh +226 -0
- package/scripts/s4l_channel.py +103 -0
- package/scripts/s4l_ctl.sh +75 -0
- package/scripts/s4l_env.py +47 -0
- package/scripts/saps_activity.py +126 -0
- package/scripts/saps_mode.py +328 -0
- package/scripts/scan_dm_candidates.py +580 -0
- package/scripts/scan_github_replies.py +168 -0
- package/scripts/scan_instagram_comments.py +481 -0
- package/scripts/scan_moltbook_replies.py +252 -0
- package/scripts/scan_pii.py +190 -0
- package/scripts/scan_reddit_replies.py +377 -0
- package/scripts/scan_twitter_mentions_browser.py +327 -0
- package/scripts/scan_twitter_thread_followups.py +299 -0
- package/scripts/scan_x_profile.py +384 -0
- package/scripts/schedule_state.py +202 -0
- package/scripts/scheduled_tasks_snapshot.py +123 -0
- package/scripts/score_linkedin_candidates.py +419 -0
- package/scripts/score_twitter_candidates.py +718 -0
- package/scripts/scrape_linkedin_comment_stats.py +1755 -0
- package/scripts/scrape_linkedin_stats_browser.py +52 -0
- package/scripts/scrape_reddit_views.py +365 -0
- package/scripts/seed_search_queries.py +453 -0
- package/scripts/seed_search_topics.py +127 -0
- package/scripts/send_web_chat_reply.py +130 -0
- package/scripts/sentry_init.py +128 -0
- package/scripts/setup_twitter_auth.py +1320 -0
- package/scripts/snapshot.py +583 -0
- package/scripts/stats.py +2702 -0
- package/scripts/stats_helper.py +52 -0
- package/scripts/strike_alert.py +783 -0
- package/scripts/sweep_post_link_clicks.py +107 -0
- package/scripts/sync_ig_to_posts.py +147 -0
- package/scripts/test_browser_lock.py +189 -0
- package/scripts/test_installation_api.sh +52 -0
- package/scripts/test_percard_posting.py +142 -0
- package/scripts/top_dud_linkedin_queries.py +71 -0
- package/scripts/top_dud_reddit_queries.py +67 -0
- package/scripts/top_dud_twitter_queries.py +71 -0
- package/scripts/top_dud_twitter_topics.py +102 -0
- package/scripts/top_linkedin_queries.py +55 -0
- package/scripts/top_omitted_reddit_topics.py +91 -0
- package/scripts/top_performers.py +588 -0
- package/scripts/top_search_topics.py +180 -0
- package/scripts/top_twitter_queries.py +190 -0
- package/scripts/twitter_access_check.py +382 -0
- package/scripts/twitter_account.py +41 -0
- package/scripts/twitter_batch_phase.py +126 -0
- package/scripts/twitter_browser.py +2804 -0
- package/scripts/twitter_cookie_mirror.py +130 -0
- package/scripts/twitter_cycle_helper.py +310 -0
- package/scripts/twitter_gen_links.py +287 -0
- package/scripts/twitter_post_plan.py +1188 -0
- package/scripts/twitter_scan.py +324 -0
- package/scripts/twitter_supply_signal.py +57 -0
- package/scripts/twitter_threads_helper.py +152 -0
- package/scripts/unclaim_web_chat.py +29 -0
- package/scripts/update_instagram_stats.py +261 -0
- package/scripts/update_linkedin_stats_from_feed.py +328 -0
- package/scripts/version.py +72 -0
- package/scripts/watchdog_hung_runs.py +343 -0
- package/scripts/write_generation_trace.py +73 -0
- package/setup/SKILL.md +277 -0
- package/skill/amplitude-24h-signups.sh +38 -0
- package/skill/archive-old-logs.sh +40 -0
- package/skill/audit-dm-staleness.sh +42 -0
- package/skill/audit-linkedin.sh +14 -0
- package/skill/audit-moltbook.sh +4 -0
- package/skill/audit-reddit-resurrect.sh +67 -0
- package/skill/audit-reddit.sh +4 -0
- package/skill/audit-twitter.sh +4 -0
- package/skill/audit.sh +287 -0
- package/skill/backfill-twitter-attempts-topic.sh +19 -0
- package/skill/backfill-twitter-ghost-posts.sh +24 -0
- package/skill/check-external-pool-depth.sh +7 -0
- package/skill/check-web-chats.sh +203 -0
- package/skill/dm-outreach-linkedin.sh +250 -0
- package/skill/dm-outreach-reddit.sh +274 -0
- package/skill/dm-outreach-twitter.sh +265 -0
- package/skill/engage-dm-replies-linkedin.sh +4 -0
- package/skill/engage-dm-replies-reddit.sh +4 -0
- package/skill/engage-dm-replies-twitter.sh +4 -0
- package/skill/engage-dm-replies.sh +1597 -0
- package/skill/engage-linkedin.sh +581 -0
- package/skill/engage-moltbook.sh +36 -0
- package/skill/engage-reddit.sh +146 -0
- package/skill/engage-twitter.sh +467 -0
- package/skill/github-engage.sh +176 -0
- package/skill/ingest-web-chat-replies.sh +38 -0
- package/skill/invent-supply-test.sh +100 -0
- package/skill/invent-topics.sh +50 -0
- package/skill/lib/linkedin-backend.sh +364 -0
- package/skill/lib/platform.sh +48 -0
- package/skill/lib/reddit-backend.sh +234 -0
- package/skill/lib/twitter-backend.sh +314 -0
- package/skill/link-edit-github.sh +136 -0
- package/skill/link-edit-moltbook.sh +117 -0
- package/skill/link-edit-reddit.sh +201 -0
- package/skill/linkedin-presence.sh +182 -0
- package/skill/linkedin-recovery.sh +282 -0
- package/skill/lock.sh +647 -0
- package/skill/memory-snapshot.sh +39 -0
- package/skill/precompute-stats.sh +35 -0
- package/skill/prewarm-funnel.sh +104 -0
- package/skill/refresh-instagram-tokens.sh +57 -0
- package/skill/refresh-twitter-following.sh +52 -0
- package/skill/reply-risk-digest.sh +31 -0
- package/skill/run-cycle-update-guard.sh +44 -0
- package/skill/run-draft-and-publish.sh +123 -0
- package/skill/run-generate-daily-style.sh +50 -0
- package/skill/run-github-launchd.sh +62 -0
- package/skill/run-github.sh +102 -0
- package/skill/run-instagram-daily.sh +149 -0
- package/skill/run-instagram-render.sh +875 -0
- package/skill/run-linkedin-launchd.sh +81 -0
- package/skill/run-linkedin-unipile.sh +130 -0
- package/skill/run-linkedin.sh +1593 -0
- package/skill/run-moltbook-launchd.sh +61 -0
- package/skill/run-moltbook.sh +38 -0
- package/skill/run-overlay-watch.sh +100 -0
- package/skill/run-reddit-search-launchd.sh +64 -0
- package/skill/run-reddit-search.sh +505 -0
- package/skill/run-reddit-threads-double.sh +32 -0
- package/skill/run-reddit-threads.sh +847 -0
- package/skill/run-scan-moltbook-replies.sh +57 -0
- package/skill/run-twitter-cycle-launchd.sh +63 -0
- package/skill/run-twitter-cycle-singleton.sh +62 -0
- package/skill/run-twitter-cycle.sh +2408 -0
- package/skill/run-twitter-threads.sh +592 -0
- package/skill/scan-instagram-replies.sh +61 -0
- package/skill/scan-twitter-followups.sh +57 -0
- package/skill/social-autoposter-update.sh +66 -0
- package/skill/stats-instagram.sh +72 -0
- package/skill/stats-linkedin.sh +271 -0
- package/skill/stats-moltbook.sh +4 -0
- package/skill/stats-reddit.sh +4 -0
- package/skill/stats-twitter.sh +4 -0
- package/skill/stats.sh +521 -0
- package/skill/strike-alert.sh +18 -0
- package/skill/styles.sh +87 -0
- package/skill/sweep-link-clicks.sh +40 -0
- 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
|