@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.
- 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 +1336 -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 +513 -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,57 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Social Autoposter - Moltbook reply scanner
|
|
3
|
+
# Runs scan_moltbook_replies.py on its own launchd schedule.
|
|
4
|
+
# Pure API; typically finishes in <1min, so uses a short 15min lock wait.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
source "$(dirname "$0")/lock.sh"
|
|
10
|
+
acquire_lock "scan-moltbook-replies" 900
|
|
11
|
+
|
|
12
|
+
[ -f "$HOME/social-autoposter/.env" ] && source "$HOME/social-autoposter/.env"
|
|
13
|
+
|
|
14
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
15
|
+
LOG_DIR="$REPO_DIR/skill/logs"
|
|
16
|
+
mkdir -p "$LOG_DIR"
|
|
17
|
+
LOG_FILE="$LOG_DIR/run-scan-moltbook-replies-$(date +%Y-%m-%d_%H%M%S).log"
|
|
18
|
+
|
|
19
|
+
echo "=== Scan Moltbook Replies Run: $(date) ===" | tee "$LOG_FILE"
|
|
20
|
+
START_TS=$(date +%s)
|
|
21
|
+
|
|
22
|
+
PYTHONUNBUFFERED=1 python3 "$REPO_DIR/scripts/scan_moltbook_replies.py" 2>&1 | tee -a "$LOG_FILE" || true
|
|
23
|
+
|
|
24
|
+
ELAPSED=$(( $(date +%s) - START_TS ))
|
|
25
|
+
# grep -c prints "0" AND exits 1 on zero matches, so `|| echo 0` was
|
|
26
|
+
# appending a second "0" and making FOUND multiline, which silently broke
|
|
27
|
+
# log_run.py. Use `|| FOUND=0` so the fallback only fires when the file is
|
|
28
|
+
# unreadable.
|
|
29
|
+
FOUND=$(grep -ci "new repl" "$LOG_FILE" 2>/dev/null) || FOUND=0
|
|
30
|
+
# Pull scan-stage counters out of the "Notification scan complete:" line that
|
|
31
|
+
# scan_moltbook_replies.py prints. Same key format as Reddit's scanner. Renders
|
|
32
|
+
# as scanned/new/excluded pills on the dashboard scan-replies row so empty
|
|
33
|
+
# cycles read as "scanned N / 0 new" instead of all-zeros.
|
|
34
|
+
SCAN_LINE=$(grep -m1 "^Notification scan complete:" "$LOG_FILE" 2>/dev/null || true)
|
|
35
|
+
SCAN_ARG=""
|
|
36
|
+
if [ -n "$SCAN_LINE" ]; then
|
|
37
|
+
scan_seen=$(echo "$SCAN_LINE" | grep -oE "seen=[0-9]+" | head -1 | cut -d= -f2)
|
|
38
|
+
scan_new=$(echo "$SCAN_LINE" | grep -oE "new_pending=[0-9]+" | head -1 | cut -d= -f2)
|
|
39
|
+
scan_excl=$(echo "$SCAN_LINE" | grep -oE "excluded_author=[0-9]+" | head -1 | cut -d= -f2)
|
|
40
|
+
scan_self=$(echo "$SCAN_LINE" | grep -oE "self_filtered=[0-9]+" | head -1 | cut -d= -f2)
|
|
41
|
+
scan_back=$(echo "$SCAN_LINE" | grep -oE "backfill_skipped=[0-9]+" | head -1 | cut -d= -f2)
|
|
42
|
+
scan_excl_total=$(( ${scan_excl:-0} + ${scan_self:-0} ))
|
|
43
|
+
parts=""
|
|
44
|
+
[ -n "$scan_seen" ] && parts="${parts}scanned=${scan_seen},"
|
|
45
|
+
[ -n "$scan_new" ] && parts="${parts}new=${scan_new},"
|
|
46
|
+
[ "$scan_excl_total" -gt 0 ] && parts="${parts}excluded=${scan_excl_total},"
|
|
47
|
+
[ -n "$scan_back" ] && parts="${parts}backfill=${scan_back},"
|
|
48
|
+
SCAN_ARG="${parts%,}"
|
|
49
|
+
fi
|
|
50
|
+
if [ -n "$SCAN_ARG" ]; then
|
|
51
|
+
python3 "$REPO_DIR/scripts/log_run.py" --script "scan_moltbook_replies" --posted "$FOUND" --skipped 0 --failed 0 --cost 0 --elapsed "$ELAPSED" --scan "$SCAN_ARG" || true
|
|
52
|
+
else
|
|
53
|
+
python3 "$REPO_DIR/scripts/log_run.py" --script "scan_moltbook_replies" --posted "$FOUND" --skipped 0 --failed 0 --cost 0 --elapsed "$ELAPSED" || true
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
echo "=== Scan Moltbook Replies complete: $(date) (elapsed ${ELAPSED}s) ===" | tee -a "$LOG_FILE"
|
|
57
|
+
find "$LOG_DIR" -name "run-scan-moltbook-replies-*.log" -mtime +7 -delete 2>/dev/null || true
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# run-twitter-cycle-launchd.sh — detach wrapper invoked by launchd.
|
|
3
|
+
#
|
|
4
|
+
# Why this exists:
|
|
5
|
+
# launchd's StartCalendarInterval silently SUPPRESSES a scheduled fire when
|
|
6
|
+
# the prior invocation of the same Label is still alive. The Twitter cycle
|
|
7
|
+
# takes 25-45 min wall-clock (Phase 2b-gen runs SEO landing-page generation
|
|
8
|
+
# per candidate, ~5-8 min each, lock-free). At a 15-min cadence that means
|
|
9
|
+
# 2-3 of every 4 fires get dropped on the floor; throughput collapses to
|
|
10
|
+
# 1 cycle/45min instead of 4 cycles/hr.
|
|
11
|
+
#
|
|
12
|
+
# How it works:
|
|
13
|
+
# This wrapper is what launchd actually invokes. It uses Python's classic
|
|
14
|
+
# double-fork daemon idiom to spawn the real pipeline in a fresh session
|
|
15
|
+
# (os.setsid), then exits. macOS lacks the `setsid` binary and a plain
|
|
16
|
+
# `nohup ... &; disown` is NOT sufficient — launchd cleans up the wrapper's
|
|
17
|
+
# process group after the wrapper exits and SIGTERMs the nohup'd child too.
|
|
18
|
+
# The double-fork moves the cycle into its own session/pgid that launchd
|
|
19
|
+
# no longer tracks, so it survives wrapper exit.
|
|
20
|
+
#
|
|
21
|
+
# Net effect: launchd marks the job complete in milliseconds and fires the
|
|
22
|
+
# next :00/:15/:30/:45 slot on schedule. Multiple run-twitter-cycle.sh
|
|
23
|
+
# processes can now live in parallel; the twitter-browser lock
|
|
24
|
+
# (skill/lock.sh, 3600s timeout) handles the brief browser-using windows
|
|
25
|
+
# (Phase 1 scrape, Phase 2b-prep draft, Phase 2c post — minutes each), and
|
|
26
|
+
# Phase 0 is guarded by a Postgres advisory lock to prevent salvage-UPDATE
|
|
27
|
+
# races between overlapping cycles.
|
|
28
|
+
|
|
29
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
30
|
+
LOG_DIR="$REPO_DIR/skill/logs"
|
|
31
|
+
mkdir -p "$LOG_DIR"
|
|
32
|
+
|
|
33
|
+
SCRIPT="$REPO_DIR/skill/run-twitter-cycle.sh"
|
|
34
|
+
OUT="$LOG_DIR/launchd-twitter-cycle-stdout.log"
|
|
35
|
+
ERR="$LOG_DIR/launchd-twitter-cycle-stderr.log"
|
|
36
|
+
|
|
37
|
+
exec /usr/bin/python3 -c "
|
|
38
|
+
import os, sys
|
|
39
|
+
script = '$SCRIPT'
|
|
40
|
+
out_log = '$OUT'
|
|
41
|
+
err_log = '$ERR'
|
|
42
|
+
|
|
43
|
+
# First fork — parent (launchd's tracked process) exits immediately so
|
|
44
|
+
# launchd marks the job complete. Child continues as session leader.
|
|
45
|
+
if os.fork() != 0:
|
|
46
|
+
os._exit(0)
|
|
47
|
+
os.setsid()
|
|
48
|
+
|
|
49
|
+
# Second fork — guarantees the daemon cannot reacquire a controlling
|
|
50
|
+
# terminal and detaches one level further from any process-group cleanup.
|
|
51
|
+
if os.fork() != 0:
|
|
52
|
+
os._exit(0)
|
|
53
|
+
|
|
54
|
+
# Grandchild: redirect stdio, exec the real cycle script.
|
|
55
|
+
os.chdir('/')
|
|
56
|
+
out_fd = os.open(out_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
|
|
57
|
+
err_fd = os.open(err_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
|
|
58
|
+
nul_fd = os.open('/dev/null', os.O_RDONLY)
|
|
59
|
+
os.dup2(nul_fd, 0)
|
|
60
|
+
os.dup2(out_fd, 1)
|
|
61
|
+
os.dup2(err_fd, 2)
|
|
62
|
+
os.execv(script, [script])
|
|
63
|
+
"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# run-twitter-cycle-singleton.sh — launchd entry wrapper for the twitter
|
|
3
|
+
# post-comments cycle.
|
|
4
|
+
#
|
|
5
|
+
# CONCURRENCY: this wrapper NO LONGER enforces one-at-a-time. As of 2026-06-01
|
|
6
|
+
# that is handled inside run-twitter-cycle.sh itself via:
|
|
7
|
+
# preflight_acquire_slot_or_skip "twitter-cycle" 1
|
|
8
|
+
# which EVERY launch path hits (launchd, MCP draft_cycle/autopilot, manual
|
|
9
|
+
# `bash skill/run-twitter-cycle.sh`). The old guard that used to live here
|
|
10
|
+
# (Phase 0 external-cycle detect + the /tmp/sa-twitter-cycle-singleton.lock
|
|
11
|
+
# mkdir claim) only governed the launchd path, never the MCP/manual paths, so
|
|
12
|
+
# it let out-of-band cycles slip through and then conflicted with the in-script
|
|
13
|
+
# gate. It was redundant belt-and-suspenders and is removed. Single source of
|
|
14
|
+
# truth for concurrency is now the preflight slot gate.
|
|
15
|
+
#
|
|
16
|
+
# SNAPSHOT (the one load-bearing job left here): copy the cycle script to a temp
|
|
17
|
+
# file and run THAT, so an in-place edit of run-twitter-cycle.sh mid-run (e.g.
|
|
18
|
+
# the auto-commit agent committing a rewrite) cannot corrupt bash's byte-offset
|
|
19
|
+
# execution of a live cycle. run-twitter-cycle.sh hardcodes
|
|
20
|
+
# REPO_DIR="$HOME/social-autoposter" (no $0/BASH_SOURCE), so the /tmp copy
|
|
21
|
+
# resolves the repo identically.
|
|
22
|
+
# 2026-05-28 incident: commit 0ac29141 landed mid-run, byte-offset misaligned,
|
|
23
|
+
# the cycle skipped its unconditional release_lock + Variant-A sleep, then
|
|
24
|
+
# re-acquired its own still-held twitter-browser lock and self-deadlocked.
|
|
25
|
+
#
|
|
26
|
+
# NEVER KILL: per user instruction 2026-05-22, this wrapper does not
|
|
27
|
+
# SIGTERM/SIGKILL anything. With concurrency now enforced by the slot gate,
|
|
28
|
+
# there is nothing to preempt anyway.
|
|
29
|
+
#
|
|
30
|
+
# Logs:
|
|
31
|
+
# - skill/logs/twitter-cycle-singleton.log -> wrapper start/done + snapshot events
|
|
32
|
+
# - launchd stdout/stderr (configured in the plist) receive the cycle's output
|
|
33
|
+
|
|
34
|
+
set -u
|
|
35
|
+
|
|
36
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
37
|
+
LOG_DIR="$REPO_DIR/skill/logs"
|
|
38
|
+
SINGLETON_LOG="$LOG_DIR/twitter-cycle-singleton.log"
|
|
39
|
+
CYCLE_SCRIPT="$REPO_DIR/skill/run-twitter-cycle.sh"
|
|
40
|
+
SNAPSHOT=""
|
|
41
|
+
|
|
42
|
+
mkdir -p "$LOG_DIR"
|
|
43
|
+
|
|
44
|
+
ts() { date '+%Y-%m-%d %H:%M:%S'; }
|
|
45
|
+
log() { echo "[$(ts)] $*" >> "$SINGLETON_LOG"; }
|
|
46
|
+
|
|
47
|
+
# Release the snapshot temp file on any exit path.
|
|
48
|
+
trap 'rm -f "$SNAPSHOT"; log "[wrapper] done pid=$$ rc=${EXIT_CODE:-?}"' EXIT
|
|
49
|
+
|
|
50
|
+
log "[wrapper] start pid=$$"
|
|
51
|
+
|
|
52
|
+
SNAPSHOT="/tmp/sa-twitter-cycle-snapshot-$$.sh"
|
|
53
|
+
cp "$CYCLE_SCRIPT" "$SNAPSHOT"
|
|
54
|
+
if ! /bin/bash -n "$SNAPSHOT" 2>/dev/null; then
|
|
55
|
+
log "[wrapper] snapshot syntax check failed (caught mid-edit?); aborting, launchd retries next fire"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Foreground run of the snapshot. Do NOT exec (so the trap fires on completion).
|
|
60
|
+
/bin/bash "$SNAPSHOT"
|
|
61
|
+
EXIT_CODE=$?
|
|
62
|
+
exit "$EXIT_CODE"
|