@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,102 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Social Autoposter - GitHub Issues momentum-gated posting.
|
|
3
|
+
#
|
|
4
|
+
# Delegates to scripts/post_github.py which implements:
|
|
5
|
+
# - Phase 1: gh search across project topics, snapshot T0 comment + reaction counts
|
|
6
|
+
# - Sleep (T0 -> T1 momentum window, default 600s, owned by post_github --sleep)
|
|
7
|
+
# - Phase 2a: re-fetch same issues, compute delta_score
|
|
8
|
+
# - Phase 2b: adaptive cap (default 1, bump to 3 when >=3 candidates show momentum),
|
|
9
|
+
# Claude drafts (one-shot, no Bash tools), Python posts via `gh issue comment`
|
|
10
|
+
# and persists search_topic, language, engagement_style to the posts table.
|
|
11
|
+
#
|
|
12
|
+
# Reduction levers baked in:
|
|
13
|
+
# (1) Historical (project, style) engagement block in drafter prompt.
|
|
14
|
+
# (2) top_search_topics feedback so high-scoring seeds get preferred.
|
|
15
|
+
# (3) Adaptive cap gated by per-cycle momentum.
|
|
16
|
+
# (4) T0 -> T1 delta filter: stale issues drop out before Claude sees them.
|
|
17
|
+
# (5) Pre-filter eliminates Claude's tool budget; one JSON in one shot.
|
|
18
|
+
#
|
|
19
|
+
# Called by launchd. Cadence is owned by the .plist, not this script.
|
|
20
|
+
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
[ -f "$HOME/social-autoposter/.env" ] && source "$HOME/social-autoposter/.env"
|
|
24
|
+
|
|
25
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
26
|
+
LOG_DIR="$REPO_DIR/skill/logs"
|
|
27
|
+
mkdir -p "$LOG_DIR"
|
|
28
|
+
LOG_FILE="$LOG_DIR/github-$(date +%Y-%m-%d_%H%M%S).log"
|
|
29
|
+
RUN_START=$(date +%s)
|
|
30
|
+
|
|
31
|
+
# Per-cycle batch id stamped onto every claude_sessions row spawned by this
|
|
32
|
+
# run (via SA_CYCLE_ID env -> log_claude_session.py). Lets the dashboard /
|
|
33
|
+
# get_run_cost.py --cycle-id report exact per-cycle cost instead of the
|
|
34
|
+
# legacy script+since query which bleeds costs across concurrent runs.
|
|
35
|
+
# 2026-05-10 cycle_id rollout.
|
|
36
|
+
BATCH_ID="ghcycle-$(date +%Y%m%d-%H%M%S)-$$"
|
|
37
|
+
export SA_CYCLE_ID="$BATCH_ID"
|
|
38
|
+
|
|
39
|
+
# Idempotent run_monitor.log emitter wired to EXIT/INT/TERM/HUP. Without this,
|
|
40
|
+
# a SIGTERM landing during the post_github.py loop (after `gh issue comment`
|
|
41
|
+
# committed a comment but before the script's own log_run.py call at the
|
|
42
|
+
# bottom of main()) silently drops the run from run_monitor.log. The
|
|
43
|
+
# dashboard reads run_monitor.log, so the operator-visible "last post_github
|
|
44
|
+
# cycle" stays stuck on a stale entry while real comments continue landing
|
|
45
|
+
# unrecorded. Mirrors the same fix shipped to run-reddit-search.sh,
|
|
46
|
+
# run-twitter-cycle.sh, and run-linkedin.sh.
|
|
47
|
+
#
|
|
48
|
+
# Detection mechanism: post_github.py writes a "=== SUMMARY: elapsed=" line
|
|
49
|
+
# to stdout (teed into $LOG_FILE) IMMEDIATELY before its own log_run.py
|
|
50
|
+
# call. Presence of that marker in $LOG_FILE means Python wrote its own
|
|
51
|
+
# summary, so the trap no-ops. Absence means SIGTERM landed before Python
|
|
52
|
+
# could finish, and the trap emits a "sigterm:1" placeholder so the cycle
|
|
53
|
+
# is at least visible on the dashboard.
|
|
54
|
+
#
|
|
55
|
+
# We can't re-derive accurate POSTED counts from the DB at trap-time the way
|
|
56
|
+
# linkedin/twitter do — github posts go through `gh issue comment` and only
|
|
57
|
+
# get a posts-table row from inside post_github.py's log_post call, so a
|
|
58
|
+
# SIGTERM mid-loop may leave 1-2 comments live on github.com with no posts
|
|
59
|
+
# row yet. The placeholder marks the cycle as failed=1 + sigterm:1 so the
|
|
60
|
+
# operator notices; manual reconciliation if the count matters.
|
|
61
|
+
_SA_RUN_SUMMARY_EMITTED=0
|
|
62
|
+
_sa_emit_run_summary_oneshot() {
|
|
63
|
+
[ "${_SA_RUN_SUMMARY_EMITTED:-0}" = "1" ] && return 0
|
|
64
|
+
_SA_RUN_SUMMARY_EMITTED=1
|
|
65
|
+
# Python wrote its own summary? Trust it.
|
|
66
|
+
if [ -n "${LOG_FILE:-}" ] && [ -f "${LOG_FILE:-}" ] \
|
|
67
|
+
&& grep -q "=== SUMMARY: elapsed=" "$LOG_FILE" 2>/dev/null; then
|
|
68
|
+
return 0
|
|
69
|
+
fi
|
|
70
|
+
local elapsed cost
|
|
71
|
+
elapsed=$(( $(date +%s) - ${RUN_START:-$(date +%s)} ))
|
|
72
|
+
cost=$(timeout 10 python3 "$REPO_DIR/scripts/get_run_cost.py" \
|
|
73
|
+
--since "${RUN_START:-0}" \
|
|
74
|
+
--scripts "post_github" \
|
|
75
|
+
2>/dev/null || echo "0.0000")
|
|
76
|
+
# Classify the most likely cause from the cycle log before falling back to
|
|
77
|
+
# the generic "sigterm:1" placeholder. Picks up Anthropic-side failures
|
|
78
|
+
# (stream_idle_timeout, monthly_limit, api_overloaded, context_overflow,
|
|
79
|
+
# credit_balance) that would otherwise read as a silent sigterm row on the
|
|
80
|
+
# dashboard. Falls back to sigterm:1 when no API marker is present (true
|
|
81
|
+
# external SIGTERM, e.g. cycle budget exceeded by watchdog_hung_runs).
|
|
82
|
+
local trap_reason
|
|
83
|
+
trap_reason=""
|
|
84
|
+
if [ -n "${LOG_FILE:-}" ] && [ -f "${LOG_FILE:-}" ]; then
|
|
85
|
+
trap_reason=$(python3 "$REPO_DIR/scripts/classify_run_error.py" "$LOG_FILE" 2>/dev/null)
|
|
86
|
+
fi
|
|
87
|
+
[ -z "$trap_reason" ] && trap_reason="sigterm"
|
|
88
|
+
python3 "$REPO_DIR/scripts/log_run.py" \
|
|
89
|
+
--script post_github \
|
|
90
|
+
--posted 0 --skipped 0 --failed 1 \
|
|
91
|
+
--cost "$cost" --elapsed "$elapsed" \
|
|
92
|
+
--failure-reasons "${trap_reason}:1" 2>/dev/null || true
|
|
93
|
+
}
|
|
94
|
+
trap _sa_emit_run_summary_oneshot EXIT INT TERM HUP
|
|
95
|
+
|
|
96
|
+
echo "=== GitHub Issues Run: $(date) ===" | tee "$LOG_FILE"
|
|
97
|
+
|
|
98
|
+
python3 "$REPO_DIR/scripts/post_github.py" 2>&1 | tee -a "$LOG_FILE"
|
|
99
|
+
|
|
100
|
+
echo "=== Run complete: $(date) ===" | tee -a "$LOG_FILE"
|
|
101
|
+
|
|
102
|
+
find "$LOG_DIR" -name "github-*.log" -mtime +7 -delete 2>/dev/null || true
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# run-instagram-daily.sh — Pick + post one Instagram Reel per fire.
|
|
3
|
+
#
|
|
4
|
+
# Cadence (set by launchd, com.m13v.social-instagram-daily.plist):
|
|
5
|
+
# 5 fires/day at 09:00, 12:00, 15:00, 18:00, 21:00 local time.
|
|
6
|
+
#
|
|
7
|
+
# Per-fire logic:
|
|
8
|
+
# 1. acquire_lock instagram-poster (file lock; IG is HTTP-only, no browser)
|
|
9
|
+
# 2. pick_ig_account.py -> chooses target IG account (inverse-recent-share
|
|
10
|
+
# over enabled accounts in config.json instagram.accounts).
|
|
11
|
+
# 3. ig_post_type_picker.py --account <name> -> JSON {post_type, video_path, ...}
|
|
12
|
+
# scoped to the chosen account's draft pool.
|
|
13
|
+
# 4. call mixer/post_to_ig.py --file <path> --post-type <type> --account <name>
|
|
14
|
+
# which: uploads to GCS, posts via IG Graph API, writes posted.json,
|
|
15
|
+
# updates media_posts (status=posted, post_type + target_account coalesced).
|
|
16
|
+
#
|
|
17
|
+
# Exit behavior:
|
|
18
|
+
# 0 - posted, OR queue exhausted (logged), OR another run holds the lock
|
|
19
|
+
# 1 - real failure (DB error, IG API error, picker crash, etc.)
|
|
20
|
+
#
|
|
21
|
+
# Logs: skill/logs/instagram-daily-YYYY-MM-DD_HHMMSS.log
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
26
|
+
LOG_DIR="$REPO_DIR/skill/logs"
|
|
27
|
+
mkdir -p "$LOG_DIR"
|
|
28
|
+
|
|
29
|
+
LOG_FILE="$LOG_DIR/instagram-daily-$(date +%Y-%m-%d_%H%M%S).log"
|
|
30
|
+
PICK_FILE="/tmp/ig_pick_$(date +%s)_$$.json"
|
|
31
|
+
|
|
32
|
+
if [ -f "$REPO_DIR/.env" ]; then
|
|
33
|
+
set -a
|
|
34
|
+
# shellcheck disable=SC1091
|
|
35
|
+
source "$REPO_DIR/.env"
|
|
36
|
+
set +a
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_FILE"; }
|
|
40
|
+
|
|
41
|
+
# Run accounting for dashboard Job History (Post Threads · Instagram).
|
|
42
|
+
# Each exit site updates POSTED_CT / SKIPPED_CT / FAILED_CT; the EXIT trap
|
|
43
|
+
# always emits one log_run.py line so the run shows up under
|
|
44
|
+
# thread_instagram, matching how thread_twitter / thread_reddit log.
|
|
45
|
+
RUN_START_EPOCH=$(date +%s)
|
|
46
|
+
POSTED_CT=0
|
|
47
|
+
SKIPPED_CT=0
|
|
48
|
+
FAILED_CT=0
|
|
49
|
+
|
|
50
|
+
cleanup() {
|
|
51
|
+
local rc=$?
|
|
52
|
+
rm -f "$PICK_FILE"
|
|
53
|
+
if [ "$POSTED_CT" -eq 0 ] && [ "$SKIPPED_CT" -eq 0 ] && [ "$FAILED_CT" -eq 0 ]; then
|
|
54
|
+
if [ "$rc" -eq 0 ]; then SKIPPED_CT=1; else FAILED_CT=1; fi
|
|
55
|
+
fi
|
|
56
|
+
local elapsed=$(( $(date +%s) - RUN_START_EPOCH ))
|
|
57
|
+
local cost
|
|
58
|
+
cost=$(/usr/bin/python3 "$REPO_DIR/scripts/get_run_cost.py" --since "$RUN_START_EPOCH" --scripts "run-instagram-daily" 2>/dev/null || echo "0.0000")
|
|
59
|
+
/usr/bin/python3 "$REPO_DIR/scripts/log_run.py" \
|
|
60
|
+
--script "thread_instagram" \
|
|
61
|
+
--posted "$POSTED_CT" --skipped "$SKIPPED_CT" --failed "$FAILED_CT" \
|
|
62
|
+
--cost "$cost" --elapsed "$elapsed" >/dev/null 2>&1 || true
|
|
63
|
+
}
|
|
64
|
+
trap cleanup EXIT INT TERM HUP
|
|
65
|
+
|
|
66
|
+
log "=== instagram-daily fire: $(date) ==="
|
|
67
|
+
|
|
68
|
+
# Lock against overlapping fires (pure HTTP pipeline, no browser, but the
|
|
69
|
+
# picker reads-then-posts so two concurrent fires could pick the same row).
|
|
70
|
+
# 30s timeout — if a prior fire is still uploading, skip cleanly.
|
|
71
|
+
# shellcheck source=lock.sh
|
|
72
|
+
source "$REPO_DIR/skill/lock.sh"
|
|
73
|
+
acquire_lock instagram-poster 30
|
|
74
|
+
|
|
75
|
+
# Step 1: pick target IG account. Per-account plists set FORCE_ACCOUNT in
|
|
76
|
+
# their EnvironmentVariables block so the slot is hard-pinned to one account;
|
|
77
|
+
# fall back to inverse-recent-share picker only when no FORCE_ACCOUNT is set
|
|
78
|
+
# (manual / legacy invocation).
|
|
79
|
+
if [ -n "${FORCE_ACCOUNT:-}" ]; then
|
|
80
|
+
log "step 1: FORCE_ACCOUNT=$FORCE_ACCOUNT honored from env"
|
|
81
|
+
TARGET_ACCOUNT=$(/opt/homebrew/bin/python3.11 "$REPO_DIR/scripts/pick_ig_account.py" --account "$FORCE_ACCOUNT" 2>>"$LOG_FILE")
|
|
82
|
+
else
|
|
83
|
+
log "step 1: pick_ig_account (no FORCE_ACCOUNT in env)"
|
|
84
|
+
TARGET_ACCOUNT=$(/opt/homebrew/bin/python3.11 "$REPO_DIR/scripts/pick_ig_account.py" 2>>"$LOG_FILE")
|
|
85
|
+
fi
|
|
86
|
+
if [ -z "$TARGET_ACCOUNT" ]; then
|
|
87
|
+
log "pick_ig_account.py produced no account — exiting non-zero"
|
|
88
|
+
FAILED_CT=1
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
log "picker chose account: $TARGET_ACCOUNT"
|
|
92
|
+
|
|
93
|
+
# Step 2: pick post type + video, scoped to that account
|
|
94
|
+
log "step 2: ig_post_type_picker --account $TARGET_ACCOUNT"
|
|
95
|
+
if ! /opt/homebrew/bin/python3.11 "$REPO_DIR/scripts/ig_post_type_picker.py" \
|
|
96
|
+
--account "$TARGET_ACCOUNT" > "$PICK_FILE" 2>>"$LOG_FILE"; then
|
|
97
|
+
rc=$?
|
|
98
|
+
if [ "$rc" -eq 2 ]; then
|
|
99
|
+
log "queue exhausted for account=$TARGET_ACCOUNT (no drafts of either type) — exiting cleanly"
|
|
100
|
+
SKIPPED_CT=1
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
log "picker failed rc=$rc — exiting non-zero"
|
|
104
|
+
FAILED_CT=1
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
POST_TYPE=$(/opt/homebrew/bin/python3.11 -c "import json; print(json.load(open('$PICK_FILE'))['post_type'])")
|
|
109
|
+
VIDEO_PATH=$(/opt/homebrew/bin/python3.11 -c "import json; print(json.load(open('$PICK_FILE'))['video_path'])")
|
|
110
|
+
POST_NUMBER=$(/opt/homebrew/bin/python3.11 -c "import json; print(json.load(open('$PICK_FILE'))['post_number'])")
|
|
111
|
+
REASON=$(/opt/homebrew/bin/python3.11 -c "import json; print(json.load(open('$PICK_FILE'))['reason'])")
|
|
112
|
+
FALLBACK=$(/opt/homebrew/bin/python3.11 -c "import json; print(json.load(open('$PICK_FILE'))['fallback'])")
|
|
113
|
+
|
|
114
|
+
log "picker chose: account=${TARGET_ACCOUNT} post-${POST_NUMBER} type=${POST_TYPE} fallback=${FALLBACK}"
|
|
115
|
+
log "picker reason: ${REASON}"
|
|
116
|
+
|
|
117
|
+
if [ ! -f "$VIDEO_PATH" ]; then
|
|
118
|
+
log "ERROR: picker pointed at $VIDEO_PATH but file missing on disk"
|
|
119
|
+
FAILED_CT=1
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# Step 2: post
|
|
124
|
+
DRY_FLAG=""
|
|
125
|
+
if [ "${IG_DRY_RUN:-0}" = "1" ]; then
|
|
126
|
+
DRY_FLAG="--dry-run"
|
|
127
|
+
log "IG_DRY_RUN=1 — passing --dry-run to post_to_ig.py"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
log "step 3: post_to_ig.py --file $(basename "$VIDEO_PATH") --post-type $POST_TYPE --account $TARGET_ACCOUNT $DRY_FLAG"
|
|
131
|
+
if ! /opt/homebrew/bin/python3.11 "$REPO_DIR/mixer/post_to_ig.py" \
|
|
132
|
+
--file "$VIDEO_PATH" --post-type "$POST_TYPE" --account "$TARGET_ACCOUNT" $DRY_FLAG >>"$LOG_FILE" 2>&1; then
|
|
133
|
+
log "post_to_ig.py failed — exiting non-zero"
|
|
134
|
+
FAILED_CT=1
|
|
135
|
+
exit 1
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
POSTED_CT=1
|
|
139
|
+
log "=== finished post-${POST_NUMBER} (${POST_TYPE}) on ${TARGET_ACCOUNT} successfully ==="
|
|
140
|
+
|
|
141
|
+
# Step 4: mirror the new media_posts row into the cross-platform `posts` table
|
|
142
|
+
# so it surfaces in the dashboard (Trends, Top, Activity, Cohort, Stats by
|
|
143
|
+
# Style) alongside Reddit/Twitter/LinkedIn rows. Idempotent.
|
|
144
|
+
log "step 4: sync_ig_to_posts"
|
|
145
|
+
if ! /opt/homebrew/bin/python3.11 "$REPO_DIR/scripts/sync_ig_to_posts.py" --quiet >>"$LOG_FILE" 2>&1; then
|
|
146
|
+
log "sync_ig_to_posts failed (post is already on IG; will retry on next stats fire)"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
exit 0
|