@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,81 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# run-linkedin-launchd.sh — detach wrapper invoked by launchd.
|
|
3
|
+
#
|
|
4
|
+
# Why this exists:
|
|
5
|
+
# launchd's StartInterval silently SUPPRESSES a scheduled fire when the prior
|
|
6
|
+
# invocation of the same Label is still alive. Before 2026-05-01 the
|
|
7
|
+
# linkedin pipeline held the linkedin-browser lock for the full 25-45min
|
|
8
|
+
# run, so wrapping it would just queue blocked-on-lock processes with no
|
|
9
|
+
# throughput gain. On 2026-05-01 run-linkedin.sh was refactored to acquire
|
|
10
|
+
# linkedin-browser only around its two Claude phases (~5min each), freeing
|
|
11
|
+
# the lock during DB ingest / candidate pick / Phase B prep windows.
|
|
12
|
+
# With that change the wrapper becomes useful: parallel cycles can run
|
|
13
|
+
# their non-browser phases concurrently, and the lock + lock.sh's FIFO
|
|
14
|
+
# ticket queue serialize the brief browser windows fairly.
|
|
15
|
+
#
|
|
16
|
+
# How it works:
|
|
17
|
+
# Python double-fork daemon idiom — first fork gives launchd a parent that
|
|
18
|
+
# exits immediately (so the job is marked complete in milliseconds), setsid
|
|
19
|
+
# detaches the session, second fork prevents reacquiring a controlling
|
|
20
|
+
# terminal, then we exec the real pipeline. macOS lacks `setsid(1)` and
|
|
21
|
+
# `nohup ... & disown` is not enough because launchd reaps the wrapper's
|
|
22
|
+
# pgid, taking the nohup child with it.
|
|
23
|
+
#
|
|
24
|
+
# LinkedIn-specific risk:
|
|
25
|
+
# Per CLAUDE.md memory feedback_linkedin_session_fingerprint, anti-bot can
|
|
26
|
+
# fire on sequential agent re-auths even when the lock prevents parallel
|
|
27
|
+
# Chromes. Pre-wrapper steady state was ~2 cycles/hr × 2 fresh Chrome
|
|
28
|
+
# sessions/cycle = 4 sessions/hr. Post-wrapper it can rise to ~4 cycles/hr
|
|
29
|
+
# × 2 = 8 sessions/hr (peak). If LinkedIn restrictions return, REVERT this
|
|
30
|
+
# wrapper first (point the plist back at run-linkedin.sh directly) before
|
|
31
|
+
# touching the OAuth API or browser profile.
|
|
32
|
+
|
|
33
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
34
|
+
LOG_DIR="$REPO_DIR/skill/logs"
|
|
35
|
+
mkdir -p "$LOG_DIR"
|
|
36
|
+
|
|
37
|
+
# LinkedIn killswitch (2026-05-27): refuse to even fork the daemon if a
|
|
38
|
+
# prior fire detected session compromise (http_999, authwall, throttle,
|
|
39
|
+
# li_at cleared). Cheaper than letting the inner script discover it.
|
|
40
|
+
# State: ~/.claude/social-autoposter/linkedin.killswitch
|
|
41
|
+
# Clear: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear
|
|
42
|
+
if [ -f "$HOME/.claude/social-autoposter/linkedin.killswitch" ]; then
|
|
43
|
+
echo "[$(date +%H:%M:%S)] LINKEDIN_KILLSWITCH active. Skipping run-linkedin." \
|
|
44
|
+
>> "$LOG_DIR/launchd-linkedin-stderr.log"
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
SCRIPT="$REPO_DIR/skill/run-linkedin.sh"
|
|
49
|
+
OUT="$LOG_DIR/launchd-linkedin-stdout.log"
|
|
50
|
+
ERR="$LOG_DIR/launchd-linkedin-stderr.log"
|
|
51
|
+
|
|
52
|
+
# Preflight (added 2026-05-02): skip cleanly if Claude is blocked on a
|
|
53
|
+
# quota cap, or if the system is under memory pressure. See
|
|
54
|
+
# scripts/preflight.sh for full design.
|
|
55
|
+
SA_PREFLIGHT_SCRIPT="run-linkedin"
|
|
56
|
+
source "$REPO_DIR/scripts/preflight.sh"
|
|
57
|
+
preflight_skip_if_claude_blocked
|
|
58
|
+
preflight_skip_if_jetsam_pressure
|
|
59
|
+
|
|
60
|
+
exec /usr/bin/python3 -c "
|
|
61
|
+
import os, sys
|
|
62
|
+
script = '$SCRIPT'
|
|
63
|
+
out_log = '$OUT'
|
|
64
|
+
err_log = '$ERR'
|
|
65
|
+
|
|
66
|
+
if os.fork() != 0:
|
|
67
|
+
os._exit(0)
|
|
68
|
+
os.setsid()
|
|
69
|
+
|
|
70
|
+
if os.fork() != 0:
|
|
71
|
+
os._exit(0)
|
|
72
|
+
|
|
73
|
+
os.chdir('/')
|
|
74
|
+
out_fd = os.open(out_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
|
|
75
|
+
err_fd = os.open(err_log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
|
|
76
|
+
nul_fd = os.open('/dev/null', os.O_RDONLY)
|
|
77
|
+
os.dup2(nul_fd, 0)
|
|
78
|
+
os.dup2(out_fd, 1)
|
|
79
|
+
os.dup2(err_fd, 2)
|
|
80
|
+
os.execv(script, [script])
|
|
81
|
+
"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# run-linkedin-unipile.sh: LinkedIn "post comments" pipeline via the UniPile API
|
|
3
|
+
# (search posts, then post a comment). This is the UniPile-backed counterpart to
|
|
4
|
+
# the browser-driven run-linkedin.sh. It talks ONLY to the UniPile REST API
|
|
5
|
+
# through scripts/linkedin_unipile.py: no browser, no linkedin-agent, no browser
|
|
6
|
+
# lock, no killswitch gate (there is no browser session to compromise here).
|
|
7
|
+
#
|
|
8
|
+
# "post comments" means searching for posts and commenting on them (outbound).
|
|
9
|
+
# This is NOT the "engage" pipeline (engage-linkedin.sh, which replies to replies
|
|
10
|
+
# left on our own comments).
|
|
11
|
+
#
|
|
12
|
+
# Default behavior is DRY RUN: it probes credentials, searches, and prints the
|
|
13
|
+
# comment it *would* post. A posted comment is visible to real people, so it only
|
|
14
|
+
# publishes when you pass --post AND --text.
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# ./run-linkedin-unipile.sh --keywords "ai agents" --date-posted past_week --limit 5
|
|
18
|
+
# ./run-linkedin-unipile.sh --keywords "..." --text "nice breakdown" --post
|
|
19
|
+
# ./run-linkedin-unipile.sh --social-id "urn:li:activity:NNN" --text "..." --post
|
|
20
|
+
#
|
|
21
|
+
# Creds: the live UniPile account is under matt@mediar.ai (keychain entries scoped
|
|
22
|
+
# to that account; DSN api45.unipile.com:17570, account_id wHDpysUnRbm7Q0lvyv9pQQ).
|
|
23
|
+
# An older i@m13v.com trial shares the same service names but is dead.
|
|
24
|
+
# linkedin_unipile.py reads the mediar-scoped entry; we also pin UNIPILE_DSN here
|
|
25
|
+
# as a default (override by exporting it).
|
|
26
|
+
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
|
|
29
|
+
: "${UNIPILE_DSN:=api45.unipile.com:17570}"
|
|
30
|
+
export UNIPILE_DSN
|
|
31
|
+
|
|
32
|
+
REPO_DIR="$HOME/social-autoposter"
|
|
33
|
+
PY="$REPO_DIR/scripts/linkedin_unipile.py"
|
|
34
|
+
LOG_DIR="$REPO_DIR/skill/logs"
|
|
35
|
+
mkdir -p "$LOG_DIR"
|
|
36
|
+
LOG_FILE="$LOG_DIR/engage-linkedin-unipile-$(date +%Y-%m-%d_%H%M%S).log"
|
|
37
|
+
|
|
38
|
+
KEYWORDS=""
|
|
39
|
+
SEARCH_URL=""
|
|
40
|
+
DATE_POSTED="past_week"
|
|
41
|
+
SORT_BY="date"
|
|
42
|
+
LIMIT=5
|
|
43
|
+
TEXT=""
|
|
44
|
+
SOCIAL_ID=""
|
|
45
|
+
REPLY_TO=""
|
|
46
|
+
DO_POST=0
|
|
47
|
+
|
|
48
|
+
log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_FILE"; }
|
|
49
|
+
|
|
50
|
+
while [ $# -gt 0 ]; do
|
|
51
|
+
case "$1" in
|
|
52
|
+
--keywords) KEYWORDS="$2"; shift 2 ;;
|
|
53
|
+
--url) SEARCH_URL="$2"; shift 2 ;;
|
|
54
|
+
--date-posted) DATE_POSTED="$2"; shift 2 ;;
|
|
55
|
+
--sort-by) SORT_BY="$2"; shift 2 ;;
|
|
56
|
+
--limit) LIMIT="$2"; shift 2 ;;
|
|
57
|
+
--text) TEXT="$2"; shift 2 ;;
|
|
58
|
+
--social-id) SOCIAL_ID="$2"; shift 2 ;;
|
|
59
|
+
--reply-to) REPLY_TO="$2"; shift 2 ;;
|
|
60
|
+
--post) DO_POST=1; shift ;;
|
|
61
|
+
-h|--help)
|
|
62
|
+
grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
63
|
+
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
64
|
+
esac
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
log "=== UniPile LinkedIn post comments (search, then comment) ==="
|
|
68
|
+
log "DSN=$UNIPILE_DSN dry_run=$([ "$DO_POST" -eq 1 ] && echo no || echo YES)"
|
|
69
|
+
|
|
70
|
+
# ── Step 1: validate credentials ──────────────────────────────────────────
|
|
71
|
+
log "Step 1: probing credentials (/accounts)..."
|
|
72
|
+
if ! python3 "$PY" probe 2>>"$LOG_FILE" | tee -a "$LOG_FILE"; then
|
|
73
|
+
log "CREDENTIAL PROBE FAILED. UniPile rejected the API key (see above)."
|
|
74
|
+
log "Fix: the live account is matt@mediar.ai (the i@m13v.com trial is dead). Refresh the"
|
|
75
|
+
log " subscription if needed, then store the key under the mediar-scoped entry:"
|
|
76
|
+
log " security add-generic-password -U -s unipile-api-key -a matt@mediar.ai -w '<KEY>'"
|
|
77
|
+
log " and DSN: security add-generic-password -U -s unipile-dsn -a matt@mediar.ai -w 'apiNN.unipile.com:PORT'"
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# ── Step 2: resolve a target post ─────────────────────────────────────────
|
|
82
|
+
# Either the caller named a --social-id directly, or we search and take the top
|
|
83
|
+
# result. We always print the search results so the run is auditable.
|
|
84
|
+
if [ -z "$SOCIAL_ID" ]; then
|
|
85
|
+
if [ -z "$KEYWORDS" ] && [ -z "$SEARCH_URL" ]; then
|
|
86
|
+
log "Nothing to do: pass --keywords/--url to search, or --social-id to target a post."
|
|
87
|
+
exit 2
|
|
88
|
+
fi
|
|
89
|
+
log "Step 2: searching posts (keywords='${KEYWORDS}' url='${SEARCH_URL}' date=$DATE_POSTED limit=$LIMIT)..."
|
|
90
|
+
SEARCH_ARGS=(search --limit "$LIMIT" --sort-by "$SORT_BY")
|
|
91
|
+
[ -n "$KEYWORDS" ] && SEARCH_ARGS+=(--keywords "$KEYWORDS" --date-posted "$DATE_POSTED")
|
|
92
|
+
[ -n "$SEARCH_URL" ] && SEARCH_ARGS+=(--url "$SEARCH_URL")
|
|
93
|
+
SEARCH_JSON=$(python3 "$PY" "${SEARCH_ARGS[@]}" 2>>"$LOG_FILE") || {
|
|
94
|
+
log "SEARCH FAILED (see log)."; exit 1; }
|
|
95
|
+
echo "$SEARCH_JSON" | tee -a "$LOG_FILE"
|
|
96
|
+
SOCIAL_ID=$(echo "$SEARCH_JSON" | python3 -c "import json,sys; items=json.load(sys.stdin).get('items',[]); print(items[0]['social_id'] if items and items[0].get('social_id') else '')")
|
|
97
|
+
if [ -z "$SOCIAL_ID" ]; then
|
|
98
|
+
log "No usable post (no social_id in top result). Stopping before comment."
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
log "Top result social_id: $SOCIAL_ID"
|
|
102
|
+
else
|
|
103
|
+
log "Step 2: skipping search, using provided --social-id $SOCIAL_ID"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# ── Step 3: comment (or dry-run) ──────────────────────────────────────────
|
|
107
|
+
if [ -z "$TEXT" ]; then
|
|
108
|
+
log "No --text provided; nothing to post. Target post: https://www.linkedin.com/feed/update/$SOCIAL_ID/"
|
|
109
|
+
log "Done (search-only)."
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
if [ "$DO_POST" -ne 1 ]; then
|
|
114
|
+
log "DRY RUN — would comment on $SOCIAL_ID:"
|
|
115
|
+
log " \"$TEXT\""
|
|
116
|
+
log "Re-run with --post to actually publish. (No API write performed.)"
|
|
117
|
+
exit 0
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
log "Step 3: posting comment on $SOCIAL_ID ..."
|
|
121
|
+
COMMENT_ARGS=(comment --social-id "$SOCIAL_ID" --text "$TEXT")
|
|
122
|
+
[ -n "$REPLY_TO" ] && COMMENT_ARGS+=(--reply-to "$REPLY_TO")
|
|
123
|
+
if python3 "$PY" "${COMMENT_ARGS[@]}" 2>>"$LOG_FILE" | tee -a "$LOG_FILE"; then
|
|
124
|
+
log "Comment posted. Watch the m13v LinkedIn account for any flag/challenge over the next 24-48h."
|
|
125
|
+
else
|
|
126
|
+
log "COMMENT FAILED (see log)."
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
log "=== Done ==="
|