@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,176 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# github-engage.sh — GitHub Issues engagement loop
|
|
3
|
+
# Scan our GitHub issue comments for replies, respond to substantive ones.
|
|
4
|
+
# Called by launchd every 6 hours.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
# GitHub engage lock: wait up to 60min for previous run to finish, then skip
|
|
10
|
+
source "$(dirname "$0")/lock.sh"
|
|
11
|
+
acquire_lock "github" 3600
|
|
12
|
+
|
|
13
|
+
# Load secrets
|
|
14
|
+
# shellcheck source=/dev/null
|
|
15
|
+
[ -f "$HOME/social-autoposter/.env" ] && source "$HOME/social-autoposter/.env"
|
|
16
|
+
|
|
17
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
18
|
+
SKILL_FILE="$REPO_DIR/SKILL.md"
|
|
19
|
+
LOG_DIR="$REPO_DIR/skill/logs"
|
|
20
|
+
mkdir -p "$LOG_DIR"
|
|
21
|
+
LOG_FILE="$LOG_DIR/github-engage-$(date +%Y-%m-%d_%H%M%S).log"
|
|
22
|
+
|
|
23
|
+
log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_FILE"; }
|
|
24
|
+
|
|
25
|
+
# Per-cycle batch id stamped onto every claude_sessions row spawned by this
|
|
26
|
+
# engagement run (via SA_CYCLE_ID env -> log_claude_session.py). 2026-05-10
|
|
27
|
+
# cycle_id rollout.
|
|
28
|
+
BATCH_ID="engh-$(date +%Y%m%d-%H%M%S)-$$"
|
|
29
|
+
export SA_CYCLE_ID="$BATCH_ID"
|
|
30
|
+
|
|
31
|
+
RUN_START=$(date +%s)
|
|
32
|
+
log "=== GitHub Engagement Run: $(date) (cycle=$BATCH_ID) ==="
|
|
33
|
+
|
|
34
|
+
# HTTP-only lane (2026-06-01): all read-side counts route through the s4l.ai
|
|
35
|
+
# HTTP API via scripts/github_engage_helper.py. The direct-Postgres lane was
|
|
36
|
+
# removed; DATABASE_URL, if present in .env, is deliberately ignored. No psql,
|
|
37
|
+
# no DB fallback.
|
|
38
|
+
GH_HELPER="$REPO_DIR/scripts/github_engage_helper.py"
|
|
39
|
+
|
|
40
|
+
# ═══════════════════════════════════════════════════════
|
|
41
|
+
# PHASE A: Scan for replies to our GitHub comments
|
|
42
|
+
# ═══════════════════════════════════════════════════════
|
|
43
|
+
log "Phase A: Scanning GitHub issues for replies..."
|
|
44
|
+
python3 "$REPO_DIR/scripts/scan_github_replies.py" 2>&1 | tee -a "$LOG_FILE"
|
|
45
|
+
|
|
46
|
+
# ═══════════════════════════════════════════════════════
|
|
47
|
+
# PHASE A.5: Refresh engagement stats on our GitHub comments
|
|
48
|
+
# Reactions pulled via gh api; reply counts tallied from the replies
|
|
49
|
+
# table that Phase A just refreshed. Stored on posts.upvotes +
|
|
50
|
+
# posts.comments_count. Per-reply stats also refreshed (same call), and
|
|
51
|
+
# the count is forwarded to a stats_github row in the dashboard Jobs table.
|
|
52
|
+
# ═══════════════════════════════════════════════════════
|
|
53
|
+
log "Phase A.5: Updating github engagement stats (reactions + reply counts)..."
|
|
54
|
+
# Best-effort: stats failures (Postgres disconnects, gh rate limits) must not block
|
|
55
|
+
# Phase B reply handling. Subshell scopes the set-flags, `|| true` absorbs rc.
|
|
56
|
+
PHASE_A5_START=$(date +%s)
|
|
57
|
+
GH_REPLY_SUMMARY=$(mktemp -t fazm-gh-reply-summary.XXXXXX)
|
|
58
|
+
# Chain lock cleanup into our cleanup. A plain `trap '...' EXIT` here would
|
|
59
|
+
# REPLACE lock.sh's `trap _sa_release_locks EXIT INT TERM HUP`, orphaning
|
|
60
|
+
# /tmp/social-autoposter-github.lock across runs (root cause of the stale
|
|
61
|
+
# github-lock orphans seen 2026-04-29). All four signals must be covered so
|
|
62
|
+
# watchdog SIGTERM also frees the lock.
|
|
63
|
+
trap 'rm -f "$GH_REPLY_SUMMARY"; _sa_release_locks' EXIT INT TERM HUP
|
|
64
|
+
( set +e +o pipefail
|
|
65
|
+
python3 "$REPO_DIR/scripts/stats.py" --github-only --reply-summary "$GH_REPLY_SUMMARY" 2>&1 | tee -a "$LOG_FILE"
|
|
66
|
+
) || true
|
|
67
|
+
PHASE_A5_ELAPSED=$(( $(date +%s) - PHASE_A5_START ))
|
|
68
|
+
|
|
69
|
+
GH_REPLIES_REFRESHED=0
|
|
70
|
+
if [ -s "$GH_REPLY_SUMMARY" ]; then
|
|
71
|
+
GH_REPLIES_REFRESHED=$(python3 -c "import json; print(json.load(open('$GH_REPLY_SUMMARY')).get('github', 0))" 2>/dev/null || echo 0)
|
|
72
|
+
fi
|
|
73
|
+
GH_ACTIVE=$(python3 "$GH_HELPER" posts-active-count 2>/dev/null | tr -d '[:space:]')
|
|
74
|
+
[ -z "$GH_ACTIVE" ] && GH_ACTIVE=0
|
|
75
|
+
# Emit a stats_github row so the dashboard Jobs table shows the github stats run
|
|
76
|
+
# the same way it shows stats_reddit / stats_twitter.
|
|
77
|
+
python3 "$REPO_DIR/scripts/log_run.py" --script "stats_github" --posted "$GH_ACTIVE" --skipped 0 --failed 0 --replies-refreshed "$GH_REPLIES_REFRESHED" --cost 0 --elapsed "$PHASE_A5_ELAPSED" || true
|
|
78
|
+
log "Phase A.5: done (replies_refreshed=$GH_REPLIES_REFRESHED)"
|
|
79
|
+
|
|
80
|
+
# ═══════════════════════════════════════════════════════
|
|
81
|
+
# PHASE B: Respond to pending GitHub replies
|
|
82
|
+
# ═══════════════════════════════════════════════════════
|
|
83
|
+
PENDING_COUNT=$(python3 "$GH_HELPER" pending-count 2>/dev/null | tr -d '[:space:]')
|
|
84
|
+
[ -z "$PENDING_COUNT" ] && PENDING_COUNT=0
|
|
85
|
+
|
|
86
|
+
if [ "$PENDING_COUNT" -eq 0 ]; then
|
|
87
|
+
log "Phase B: No pending GitHub replies. Done!"
|
|
88
|
+
RUN_ELAPSED=$(( $(date +%s) - RUN_START ))
|
|
89
|
+
# Pull scan-stage counters from Phase A so the empty-engage row still shows
|
|
90
|
+
# "scanned N / 0 new" instead of all-zeros. scan_github_replies.py prints:
|
|
91
|
+
# Scanning N GitHub issues for replies...
|
|
92
|
+
# GitHub scan complete: N new pending, N skipped, N errors
|
|
93
|
+
GH_SCAN_PROC_LINE=$(grep -m1 -E "^Scanning [0-9]+ GitHub issues" "$LOG_FILE" 2>/dev/null || true)
|
|
94
|
+
GH_SCAN_DONE_LINE=$(grep -m1 "^GitHub scan complete:" "$LOG_FILE" 2>/dev/null || true)
|
|
95
|
+
GH_SCAN_ARG=""
|
|
96
|
+
if [ -n "$GH_SCAN_PROC_LINE" ] || [ -n "$GH_SCAN_DONE_LINE" ]; then
|
|
97
|
+
gh_scanned=$(echo "$GH_SCAN_PROC_LINE" | grep -oE "[0-9]+" | head -1)
|
|
98
|
+
gh_new=$(echo "$GH_SCAN_DONE_LINE" | grep -oE "[0-9]+ new pending" | grep -oE "[0-9]+" | head -1)
|
|
99
|
+
gh_skip=$(echo "$GH_SCAN_DONE_LINE" | grep -oE "[0-9]+ skipped" | grep -oE "[0-9]+" | head -1)
|
|
100
|
+
gh_err=$(echo "$GH_SCAN_DONE_LINE" | grep -oE "[0-9]+ errors" | grep -oE "[0-9]+" | head -1)
|
|
101
|
+
parts=""
|
|
102
|
+
[ -n "$gh_scanned" ] && parts="${parts}scanned=${gh_scanned},"
|
|
103
|
+
[ -n "$gh_new" ] && parts="${parts}new=${gh_new},"
|
|
104
|
+
[ -n "$gh_skip" ] && [ "$gh_skip" -gt 0 ] && parts="${parts}backfill=${gh_skip},"
|
|
105
|
+
[ -n "$gh_err" ] && [ "$gh_err" -gt 0 ] && parts="${parts}unmatched=${gh_err},"
|
|
106
|
+
GH_SCAN_ARG="${parts%,}"
|
|
107
|
+
fi
|
|
108
|
+
if [ -n "$GH_SCAN_ARG" ]; then
|
|
109
|
+
python3 "$REPO_DIR/scripts/log_run.py" --script "engage_github" --posted 0 --skipped 0 --failed 0 --cost 0 --elapsed "$RUN_ELAPSED" --scan "$GH_SCAN_ARG"
|
|
110
|
+
else
|
|
111
|
+
python3 "$REPO_DIR/scripts/log_run.py" --script "engage_github" --posted 0 --skipped 0 --failed 0 --cost 0 --elapsed "$RUN_ELAPSED"
|
|
112
|
+
fi
|
|
113
|
+
find "$LOG_DIR" -name "github-engage-*.log" -mtime +7 -delete 2>/dev/null || true
|
|
114
|
+
exit 0
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
log "Phase B: $PENDING_COUNT pending GitHub replies to process"
|
|
118
|
+
|
|
119
|
+
# One-at-a-time thread-aware orchestrator. Each reply gets its own Claude session
|
|
120
|
+
# with the full issue thread fetched via gh CLI, so Claude can see our prior
|
|
121
|
+
# comments and decide reply-or-skip with a JSON escape hatch. See
|
|
122
|
+
# scripts/engage_github.py for the prompt and skip-reason contract.
|
|
123
|
+
python3 "$REPO_DIR/scripts/engage_github.py" --timeout 3000 2>&1 | tee -a "$LOG_FILE"
|
|
124
|
+
|
|
125
|
+
# ═══════════════════════════════════════════════════════
|
|
126
|
+
# PHASE C: Summary
|
|
127
|
+
# ═══════════════════════════════════════════════════════
|
|
128
|
+
# engage_github.py prints a canonical LOG_RUN_SUMMARY line; we parse it and
|
|
129
|
+
# write ONE log_run.py row that also carries Phase A scan counters. Previously
|
|
130
|
+
# engage_github.py wrote its own row with no scan info and the shell wrote
|
|
131
|
+
# nothing on the has-work branch -- so productive cycles lost scan visibility
|
|
132
|
+
# and empty cycles wrote two rows (one with scan, one without).
|
|
133
|
+
RUN_ELAPSED=$(( $(date +%s) - RUN_START ))
|
|
134
|
+
GH_SCAN_PROC_LINE=$(grep -m1 -E "^Scanning [0-9]+ GitHub issues" "$LOG_FILE" 2>/dev/null || true)
|
|
135
|
+
GH_SCAN_DONE_LINE=$(grep -m1 "^GitHub scan complete:" "$LOG_FILE" 2>/dev/null || true)
|
|
136
|
+
GH_SCAN_ARG=""
|
|
137
|
+
if [ -n "$GH_SCAN_PROC_LINE" ] || [ -n "$GH_SCAN_DONE_LINE" ]; then
|
|
138
|
+
gh_scanned=$(echo "$GH_SCAN_PROC_LINE" | grep -oE "[0-9]+" | head -1)
|
|
139
|
+
gh_new=$(echo "$GH_SCAN_DONE_LINE" | grep -oE "[0-9]+ new pending" | grep -oE "[0-9]+" | head -1)
|
|
140
|
+
gh_skip=$(echo "$GH_SCAN_DONE_LINE" | grep -oE "[0-9]+ skipped" | grep -oE "[0-9]+" | head -1)
|
|
141
|
+
gh_err=$(echo "$GH_SCAN_DONE_LINE" | grep -oE "[0-9]+ errors" | grep -oE "[0-9]+" | head -1)
|
|
142
|
+
parts=""
|
|
143
|
+
[ -n "$gh_scanned" ] && parts="${parts}scanned=${gh_scanned},"
|
|
144
|
+
[ -n "$gh_new" ] && parts="${parts}new=${gh_new},"
|
|
145
|
+
[ -n "$gh_skip" ] && [ "$gh_skip" -gt 0 ] && parts="${parts}backfill=${gh_skip},"
|
|
146
|
+
[ -n "$gh_err" ] && [ "$gh_err" -gt 0 ] && parts="${parts}unmatched=${gh_err},"
|
|
147
|
+
GH_SCAN_ARG="${parts%,}"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
GH_SUMMARY_LINE=$(grep -m1 "^\[engage_github\] LOG_RUN_SUMMARY" "$LOG_FILE" 2>/dev/null || true)
|
|
151
|
+
GH_POSTED=0; GH_SKIPPED=0; GH_FAILED=0; GH_COST="0.0000"
|
|
152
|
+
if [ -n "$GH_SUMMARY_LINE" ]; then
|
|
153
|
+
GH_POSTED=$(echo "$GH_SUMMARY_LINE" | grep -oE "posted=[0-9]+" | head -1 | cut -d= -f2)
|
|
154
|
+
GH_SKIPPED=$(echo "$GH_SUMMARY_LINE" | grep -oE "skipped=[0-9]+" | head -1 | cut -d= -f2)
|
|
155
|
+
GH_FAILED=$(echo "$GH_SUMMARY_LINE" | grep -oE "failed=[0-9]+" | head -1 | cut -d= -f2)
|
|
156
|
+
GH_COST=$(echo "$GH_SUMMARY_LINE" | grep -oE "cost=[0-9.]+" | head -1 | cut -d= -f2)
|
|
157
|
+
: "${GH_POSTED:=0}" "${GH_SKIPPED:=0}" "${GH_FAILED:=0}" "${GH_COST:=0.0000}"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
GH_LOG_RUN_ARGS=(--script "engage_github" --posted "$GH_POSTED" --skipped "$GH_SKIPPED" --failed "$GH_FAILED" --cost "$GH_COST" --elapsed "$RUN_ELAPSED")
|
|
161
|
+
[ -n "$GH_SCAN_ARG" ] && GH_LOG_RUN_ARGS+=(--scan "$GH_SCAN_ARG")
|
|
162
|
+
python3 "$REPO_DIR/scripts/log_run.py" "${GH_LOG_RUN_ARGS[@]}" || true
|
|
163
|
+
|
|
164
|
+
# Print cumulative status for visibility in the log file. One HTTP roundtrip
|
|
165
|
+
# for all three counts instead of three psql one-liners.
|
|
166
|
+
GH_COUNTS_JSON=$(python3 "$GH_HELPER" reply-counts 2>/dev/null || echo '{"pending":0,"replied":0,"skipped":0}')
|
|
167
|
+
TOTAL_PENDING=$(echo "$GH_COUNTS_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('pending',0))" 2>/dev/null || echo "0")
|
|
168
|
+
TOTAL_REPLIED=$(echo "$GH_COUNTS_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('replied',0))" 2>/dev/null || echo "0")
|
|
169
|
+
TOTAL_SKIPPED=$(echo "$GH_COUNTS_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('skipped',0))" 2>/dev/null || echo "0")
|
|
170
|
+
|
|
171
|
+
log "GitHub replies cumulative: pending=$TOTAL_PENDING replied=$TOTAL_REPLIED skipped=$TOTAL_SKIPPED"
|
|
172
|
+
|
|
173
|
+
log "=== GitHub Engagement complete: $(date) ==="
|
|
174
|
+
|
|
175
|
+
# Clean up old logs
|
|
176
|
+
find "$LOG_DIR" -name "github-engage-*.log" -mtime +7 -delete 2>/dev/null || true
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ingest-web-chat-replies.sh — Poll Gmail for [WEB-CHAT #N] replies and forward
|
|
3
|
+
# them to visitors via Resend. Called by launchd every 5 minutes.
|
|
4
|
+
#
|
|
5
|
+
# Mirror of ~/social-autoposter/skill/dm-replies-ingest pattern (the
|
|
6
|
+
# ingest_human_dm_replies.py launchd rail), specialised for the web-chat thread.
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
source "$(dirname "$0")/lock.sh"
|
|
11
|
+
acquire_lock "ingest-web-chat-replies" 60
|
|
12
|
+
|
|
13
|
+
# DB access is HTTP-only via scripts/http_api.py -> s4l.ai /api/v1/web-chat/*.
|
|
14
|
+
# No DATABASE_URL needed here any more.
|
|
15
|
+
|
|
16
|
+
ANALYTICS_ENV="$HOME/analytics/.env.production.local"
|
|
17
|
+
if [ -f "$ANALYTICS_ENV" ]; then
|
|
18
|
+
export RESEND_API_KEY=$(grep '^RESEND_API_KEY=' "$ANALYTICS_ENV" | sed 's/^RESEND_API_KEY=//' | tr -d '"' | tr -d '\\n')
|
|
19
|
+
fi
|
|
20
|
+
export NODE_PATH="$HOME/analytics/node_modules"
|
|
21
|
+
|
|
22
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
23
|
+
LOG_DIR="$REPO_DIR/skill/logs"
|
|
24
|
+
mkdir -p "$LOG_DIR"
|
|
25
|
+
|
|
26
|
+
PYTHON_BIN="${PYTHON_BIN:-/opt/homebrew/bin/python3.11}"
|
|
27
|
+
[ -x "$PYTHON_BIN" ] || PYTHON_BIN="/usr/bin/python3"
|
|
28
|
+
|
|
29
|
+
LOG_FILE="$LOG_DIR/web-chat-ingest.log"
|
|
30
|
+
echo "[$(date)] starting ingest" >> "$LOG_FILE"
|
|
31
|
+
|
|
32
|
+
"$PYTHON_BIN" "$REPO_DIR/scripts/ingest_web_chat_replies.py" >> "$LOG_FILE" 2>&1 || \
|
|
33
|
+
echo "[$(date)] ERROR: ingest_web_chat_replies.py failed" >> "$LOG_FILE"
|
|
34
|
+
|
|
35
|
+
# Trim log.
|
|
36
|
+
if [ -f "$LOG_FILE" ]; then
|
|
37
|
+
tail -2000 "$LOG_FILE" > "$LOG_FILE.tmp" 2>/dev/null && mv "$LOG_FILE.tmp" "$LOG_FILE" || true
|
|
38
|
+
fi
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# invent-supply-test.sh — supply-test a batch of drafted Twitter queries for the
|
|
3
|
+
# topic-invention job (scripts/invent_topics.py).
|
|
4
|
+
#
|
|
5
|
+
# Mirrors run-twitter-cycle.sh's Phase 1 lean scan loop, but stands alone so the
|
|
6
|
+
# hourly invent job can measure how much FRESH (6h) supply each freshly-drafted
|
|
7
|
+
# query returns BEFORE committing its parent topic. It:
|
|
8
|
+
#
|
|
9
|
+
# 1. Acquires the same "twitter-browser" mkdir-lock the cycle uses, so an
|
|
10
|
+
# invent run and a 15-min cycle never fight over the managed Chrome on
|
|
11
|
+
# port 9555. Short timeout (default 600s): if the cycle is mid-scan we'd
|
|
12
|
+
# rather skip this hour than block. acquire_lock exits 0 on timeout, which
|
|
13
|
+
# leaves SCAN_OUT empty and signals "untested" back to Python.
|
|
14
|
+
# 2. Runs ONE browser-harness -c invocation that loops twitter_scan.scan()
|
|
15
|
+
# over every query, writing one JSONL record per query to SCAN_OUT (via the
|
|
16
|
+
# SCAN_TWEETS_FILE env the scan module already honors). scan() writes a
|
|
17
|
+
# record even on a zero-tweet result, so a present-but-empty tweets array
|
|
18
|
+
# is a real "tested, 0 supply" signal, distinct from a missing record.
|
|
19
|
+
# 3. Releases the lock.
|
|
20
|
+
#
|
|
21
|
+
# Usage:
|
|
22
|
+
# invent-supply-test.sh <queries_json> <scan_out> [freshness_hours] [lock_timeout_s]
|
|
23
|
+
#
|
|
24
|
+
# queries_json : path to a JSON array of {project, query, search_topic}
|
|
25
|
+
# scan_out : path the per-query JSONL results are written to (truncated first)
|
|
26
|
+
# freshness_hours (default 6)
|
|
27
|
+
# lock_timeout_s (default 600)
|
|
28
|
+
#
|
|
29
|
+
# Exit status is always 0 on a clean run (tested or skipped); Python decides
|
|
30
|
+
# tested-vs-untested by whether SCAN_OUT has records. Mirrors the cycle's
|
|
31
|
+
# fail-open posture so a transient browser hiccup never crashes the invent job.
|
|
32
|
+
set -uo pipefail
|
|
33
|
+
|
|
34
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
35
|
+
HARNESS_BIN="$HOME/.local/bin/browser-harness"
|
|
36
|
+
|
|
37
|
+
QUERIES_JSON="${1:?queries_json path required}"
|
|
38
|
+
SCAN_OUT="${2:?scan_out path required}"
|
|
39
|
+
FRESHNESS_HOURS="${3:-6}"
|
|
40
|
+
LOCK_TIMEOUT="${4:-600}"
|
|
41
|
+
|
|
42
|
+
if [ ! -s "$QUERIES_JSON" ]; then
|
|
43
|
+
echo "[invent-supply-test] queries json missing/empty: $QUERIES_JSON" >&2
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
if [ ! -x "$HARNESS_BIN" ]; then
|
|
47
|
+
echo "[invent-supply-test] browser-harness not found at $HARNESS_BIN" >&2
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Truncate the output so a stale file from a prior run can't masquerade as
|
|
52
|
+
# fresh results. Python treats an empty SCAN_OUT as "untested this run".
|
|
53
|
+
: > "$SCAN_OUT"
|
|
54
|
+
|
|
55
|
+
# Source the shared lock helpers (functions only; no lock acquired on source).
|
|
56
|
+
# shellcheck disable=SC1091
|
|
57
|
+
source "$REPO_DIR/skill/lock.sh"
|
|
58
|
+
|
|
59
|
+
# acquire_lock exits 0 if the lock can't be taken within LOCK_TIMEOUT, which
|
|
60
|
+
# unwinds this whole script — leaving SCAN_OUT empty. That's the intended
|
|
61
|
+
# "skip this hour" path: the cycle owns the browser right now.
|
|
62
|
+
echo "[invent-supply-test] acquiring twitter-browser lock (timeout=${LOCK_TIMEOUT}s)..." >&2
|
|
63
|
+
acquire_lock "twitter-browser" "$LOCK_TIMEOUT"
|
|
64
|
+
echo "[invent-supply-test] twitter-browser lock held (pid=$$)" >&2
|
|
65
|
+
|
|
66
|
+
# One harness invocation handles every query so we pay the CLI startup once.
|
|
67
|
+
# Each scan() call appends a JSONL record to SCAN_TWEETS_FILE=$SCAN_OUT.
|
|
68
|
+
# browser-harness upstream main reads the script from STDIN (the `-c` flag was
|
|
69
|
+
# removed). Feed the body via a quoted heredoc and pass $REPO_DIR / $QUERIES_JSON
|
|
70
|
+
# through the environment so the Python reads them from os.environ.
|
|
71
|
+
BU_NAME=twitter-harness BU_CDP_URL=http://127.0.0.1:9555 \
|
|
72
|
+
SCAN_TWEETS_FILE="$SCAN_OUT" \
|
|
73
|
+
BATCH_ID="${BATCH_ID:-}" \
|
|
74
|
+
FRESHNESS_HOURS_DISCOVER="$FRESHNESS_HOURS" \
|
|
75
|
+
REPO_DIR="$REPO_DIR" \
|
|
76
|
+
QUERIES_JSON="$QUERIES_JSON" \
|
|
77
|
+
"$HARNESS_BIN" <<'PY' 2>&1
|
|
78
|
+
import sys, json, os, time
|
|
79
|
+
sys.path.insert(0, os.environ['REPO_DIR'] + '/scripts')
|
|
80
|
+
from twitter_scan import scan
|
|
81
|
+
queries = json.load(open(os.environ['QUERIES_JSON']))
|
|
82
|
+
freshness = int(os.environ.get('FRESHNESS_HOURS_DISCOVER', '6'))
|
|
83
|
+
for q in queries:
|
|
84
|
+
project = q.get('project', '')
|
|
85
|
+
query = q.get('query', '')
|
|
86
|
+
topic = q.get('search_topic', '')
|
|
87
|
+
t0 = time.time()
|
|
88
|
+
try:
|
|
89
|
+
kept = scan(query=query, project=project, search_topic=topic,
|
|
90
|
+
freshness_hours=freshness)
|
|
91
|
+
dt = time.time() - t0
|
|
92
|
+
print(f' ok project={project!r} q={query[:50]!r} kept={len(kept)} in {dt:.1f}s', flush=True)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
dt = time.time() - t0
|
|
95
|
+
print(f' err project={project!r} q={query[:50]!r} in {dt:.1f}s {type(e).__name__}: {e}', flush=True)
|
|
96
|
+
PY
|
|
97
|
+
|
|
98
|
+
release_lock "twitter-browser"
|
|
99
|
+
echo "[invent-supply-test] done; results in $SCAN_OUT" >&2
|
|
100
|
+
exit 0
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# invent-topics.sh — hourly topic invention job (replaces in-cycle EXPLORE_INVENT).
|
|
3
|
+
#
|
|
4
|
+
# Picks ONE project via pick_projects() (same inverse-recent-share weighting
|
|
5
|
+
# the post-comments cycle uses), then calls Claude to propose N new
|
|
6
|
+
# search_topic candidates given that project's ledger. Validates each
|
|
7
|
+
# proposal against the universe (exact-match + Jaccard similarity), commits
|
|
8
|
+
# survivors to project_search_topics with source='invented', status='active',
|
|
9
|
+
# and appends an audit row to state/invented_topics_audit.jsonl.
|
|
10
|
+
#
|
|
11
|
+
# Fires hourly via com.m13v.social-invent-topics.plist. Deliberately runs
|
|
12
|
+
# outside the 15-min cycle cadence because invention doesn't need realtime;
|
|
13
|
+
# new topics added at minute :00 vs :30 make no difference to engagement.
|
|
14
|
+
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
|
|
17
|
+
# shellcheck source=/dev/null
|
|
18
|
+
[ -f "$HOME/social-autoposter/.env" ] && source "$HOME/social-autoposter/.env"
|
|
19
|
+
|
|
20
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
21
|
+
LOG_DIR="$REPO_DIR/skill/logs"
|
|
22
|
+
mkdir -p "$LOG_DIR"
|
|
23
|
+
LOG_FILE="$LOG_DIR/invent-topics-$(date +%Y-%m-%d_%H%M%S).log"
|
|
24
|
+
|
|
25
|
+
# Number of candidate topics to ask Claude for per attempt. One topic per loop
|
|
26
|
+
# matches the new supply-test rhythm: invent ONE topic, draft its queries,
|
|
27
|
+
# supply-test, gate on supply, decide whether to loop again. With the post-
|
|
28
|
+
# 2026-05-29 dupe-retry-doesn't-burn-attempts behavior, asking for more than
|
|
29
|
+
# one is wasteful (a dupe-only Claude call retries cost-free anyway).
|
|
30
|
+
# Override via INVENT_PROPOSALS_PER_RUN.
|
|
31
|
+
PROPOSALS="${INVENT_PROPOSALS_PER_RUN:-1}"
|
|
32
|
+
|
|
33
|
+
# Stop the run as soon as ONE topic clears the supply floor — the qualifying
|
|
34
|
+
# tweet count IS the real target, not "how many topics qualified." A single
|
|
35
|
+
# topic with supply >= SUPPLY_FLOOR fresh tweets is enough; no need to keep
|
|
36
|
+
# burning Claude calls on additional topics that hour. MAX_ATTEMPTS caps the
|
|
37
|
+
# loop only if the project is genuinely dry (no qualifier in N tries).
|
|
38
|
+
TARGET="${INVENT_TARGET:-1}"
|
|
39
|
+
MAX_ATTEMPTS="${INVENT_MAX_ATTEMPTS:-5}"
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
echo "[$(date +%Y-%m-%dT%H:%M:%S%z)] invent-topics start (proposals=$PROPOSALS target=$TARGET max_attempts=$MAX_ATTEMPTS)"
|
|
43
|
+
/usr/bin/python3 "$REPO_DIR/scripts/invent_topics.py" \
|
|
44
|
+
--proposals "$PROPOSALS" \
|
|
45
|
+
--target "$TARGET" \
|
|
46
|
+
--max-attempts "$MAX_ATTEMPTS"
|
|
47
|
+
rc=$?
|
|
48
|
+
echo "[$(date +%Y-%m-%dT%H:%M:%S%z)] invent-topics done rc=$rc"
|
|
49
|
+
exit $rc
|
|
50
|
+
} 2>&1 | tee -a "$LOG_FILE"
|