@m13v/s4l 1.7.4-rc.4 → 1.7.4
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 +134 -101
- package/bin/cli.js +23 -0
- package/mcp/dist/index.js +24 -5
- package/mcp/dist/runtime.js +110 -0
- package/mcp/dist/version.js +163 -54
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +1 -1
- package/mcp/menubar/s4l_card.py +55 -19
- package/mcp/menubar/s4l_menubar.py +179 -56
- package/mcp/menubar/s4l_state.py +41 -1
- package/mcp/package.json +1 -1
- package/package.json +4 -2
- package/scripts/active_experiments.py +30 -6
- package/scripts/autopilot_stall_watch.py +185 -63
- package/scripts/cdp_ready_check.py +94 -0
- package/scripts/engage_twitter_helper.py +83 -17
- package/scripts/engagement_styles.py +221 -26
- package/scripts/enrich_reply_parents.py +68 -22
- package/scripts/feedback_digest.py +24 -1
- package/scripts/generate_daily_human_style.py +31 -7
- package/scripts/invent_styles.py +103 -26
- package/scripts/linkedin_cadence.py +234 -0
- package/scripts/linkedin_killswitch.py +43 -0
- package/scripts/memory_snapshot.py +57 -0
- package/scripts/patches/browser-harness-loopback-cdp-proxy.patch +118 -0
- package/scripts/recent_self_posts.py +6 -0
- package/scripts/reddit_browser.py +57 -3
- package/scripts/s4l_box_update.sh +20 -1
- package/scripts/s4l_mode.py +3 -4
- package/scripts/scan_x_profile.py +25 -7
- package/scripts/schedule_state.py +23 -0
- package/scripts/scheduled_task_selfheal.py +213 -16
- package/scripts/setup_twitter_auth.py +6 -1
- package/scripts/snapshot.py +179 -61
- package/scripts/stall_guard.py +70 -0
- package/scripts/test_scheduled_task_selfheal.py +121 -0
- package/scripts/top_performers.py +34 -0
- package/scripts/twitter_browser.py +146 -8
- package/scripts/twitter_post_plan.py +10 -1
- package/scripts/twitter_scan.py +26 -0
- package/scripts/watchdog_hung_runs.py +117 -3
- package/skill/amplitude-24h-signups.sh +1 -1
- package/skill/audit-dm-staleness.sh +1 -1
- package/skill/audit-linkedin.sh +1 -1
- package/skill/audit-reddit-resurrect.sh +1 -1
- package/skill/audit.sh +5 -5
- package/skill/check-web-chats.sh +1 -1
- package/skill/dm-outreach-linkedin.sh +4 -4
- package/skill/dm-outreach-reddit.sh +1 -1
- package/skill/dm-outreach-twitter.sh +1 -1
- package/skill/engage-dm-replies.sh +16 -3
- package/skill/engage-linkedin.sh +2 -2
- package/skill/engage-moltbook.sh +1 -1
- package/skill/engage-twitter.sh +4 -4
- package/skill/github-engage.sh +1 -1
- package/skill/lib/browser-launch.sh +54 -0
- package/skill/lib/linkedin-backend.sh +22 -15
- package/skill/lib/reddit-backend.sh +17 -11
- package/skill/lib/twitter-backend.sh +137 -16
- package/skill/link-edit-github.sh +1 -1
- package/skill/link-edit-moltbook.sh +1 -1
- package/skill/link-edit-reddit.sh +1 -1
- package/skill/linkedin-cadence.sh +20 -0
- package/skill/linkedin-presence.sh +1 -1
- package/skill/lock.sh +30 -6
- package/skill/precompute-stats.sh +1 -1
- package/skill/refresh-instagram-tokens.sh +1 -1
- package/skill/refresh-twitter-following.sh +1 -1
- package/skill/run-generate-daily-style.sh +1 -1
- package/skill/run-instagram-daily.sh +1 -1
- package/skill/run-instagram-render.sh +1 -1
- package/skill/run-linkedin-launchd.sh +1 -1
- package/skill/run-linkedin-unipile.sh +1 -1
- package/skill/run-linkedin.sh +1 -1
- package/skill/run-reddit-search.sh +2 -2
- package/skill/run-reddit-threads.sh +1 -1
- package/skill/run-twitter-cycle.sh +190 -55
- package/skill/scan-instagram-replies.sh +1 -1
- package/skill/sentry-digest.sh +1 -1
- package/skill/stats-instagram.sh +1 -1
- package/skill/stats-linkedin.sh +2 -2
- package/skill/stats.sh +1 -1
- package/skill/sweep-link-clicks.sh +1 -1
- package/scripts/_lock_preempt_test.py +0 -60
- package/scripts/saps_activity.py +0 -18
- package/scripts/saps_mode.py +0 -18
package/skill/sentry-digest.sh
CHANGED
|
@@ -37,7 +37,7 @@ LOG_FILE="$LOG_DIR/sentry-digest-$TS.log"
|
|
|
37
37
|
SCAN_FILE="$LOG_DIR/sentry-digest-scan-$TS.json"
|
|
38
38
|
OUTCOME_FILE="$LOG_DIR/outcome-sentry-digest-$TS.json"
|
|
39
39
|
|
|
40
|
-
log() { echo "[$(date +%H:%M:%
|
|
40
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
41
41
|
|
|
42
42
|
cd "$REPO_DIR"
|
|
43
43
|
|
package/skill/stats-instagram.sh
CHANGED
|
@@ -15,7 +15,7 @@ LOG_DIR="$REPO_DIR/skill/logs"
|
|
|
15
15
|
mkdir -p "$LOG_DIR"
|
|
16
16
|
LOG_FILE="$LOG_DIR/stats-instagram-$(date +%Y-%m-%d_%H%M%S).log"
|
|
17
17
|
|
|
18
|
-
log() { echo "[$(date +%H:%M:%
|
|
18
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
19
19
|
log "=== stats-instagram fire: $(date) ==="
|
|
20
20
|
|
|
21
21
|
RUN_START=$(date +%s)
|
package/skill/stats-linkedin.sh
CHANGED
|
@@ -55,7 +55,7 @@ set -euo pipefail
|
|
|
55
55
|
# State: ~/.claude/social-autoposter/linkedin.killswitch
|
|
56
56
|
# Clear: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear
|
|
57
57
|
if [ -f "$HOME/.claude/social-autoposter/linkedin.killswitch" ]; then
|
|
58
|
-
echo "[$(date +%H:%M:%
|
|
58
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] LINKEDIN_KILLSWITCH active. Aborting LinkedIn pipeline."
|
|
59
59
|
echo " Re-auth LinkedIn in harness Chrome, then: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear"
|
|
60
60
|
exit 0
|
|
61
61
|
fi
|
|
@@ -85,7 +85,7 @@ SCRAPER_TIMEOUT_SEC=900 # 15min outer gtimeout. Inner JS deadline now defaults
|
|
|
85
85
|
|
|
86
86
|
mkdir -p "$LOG_DIR"
|
|
87
87
|
LOG_FILE="$LOG_DIR/stats-linkedin-$(date +%Y-%m-%d_%H%M%S).log"
|
|
88
|
-
log() { echo "[$(date +%H:%M:%
|
|
88
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
89
89
|
|
|
90
90
|
RUN_START=$(date +%s)
|
|
91
91
|
log "=== LinkedIn Stats Run (unified): $(date) ==="
|
package/skill/stats.sh
CHANGED
|
@@ -87,7 +87,7 @@ mkdir -p "$LOG_DIR"
|
|
|
87
87
|
LOG_TAG="${PLATFORM:-all}"
|
|
88
88
|
LOGFILE="$LOG_DIR/stats-${LOG_TAG}-$(date +%Y-%m-%d_%H%M%S).log"
|
|
89
89
|
|
|
90
|
-
log() { echo "[$(date +%H:%M:%
|
|
90
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$LOGFILE"; echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"; }
|
|
91
91
|
|
|
92
92
|
RUN_START=$(date +%s)
|
|
93
93
|
STEP1_EXIT=0; STEP2_EXIT=0; STEP3_EXIT=0; STEP4_EXIT=0
|
|
@@ -36,5 +36,5 @@ RUN_START=$(date +%s)
|
|
|
36
36
|
EXIT_CODE=$?
|
|
37
37
|
RUN_ELAPSED=$(( $(date +%s) - RUN_START ))
|
|
38
38
|
|
|
39
|
-
echo "[$(date +%H:%M:%
|
|
39
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] === done in ${RUN_ELAPSED}s (exit=${EXIT_CODE}) ==="
|
|
40
40
|
exit "$EXIT_CODE"
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import json, os, sys, time, subprocess, signal
|
|
2
|
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
|
3
|
-
|
|
4
|
-
LOCK = os.path.expanduser("~/.claude/twitter-browser-lock.json")
|
|
5
|
-
os.makedirs(os.path.dirname(LOCK), exist_ok=True)
|
|
6
|
-
|
|
7
|
-
def write_lock(pid, role):
|
|
8
|
-
with open(LOCK, "w") as f:
|
|
9
|
-
json.dump({"session_id": f"python:{pid}", "timestamp": int(time.time()), "role": role}, f)
|
|
10
|
-
|
|
11
|
-
def clean():
|
|
12
|
-
try: os.remove(LOCK)
|
|
13
|
-
except OSError: pass
|
|
14
|
-
|
|
15
|
-
# ---- TEST 1: poster preempts a LIVE scan holder ----
|
|
16
|
-
clean()
|
|
17
|
-
victim = subprocess.Popen(["sleep", "120"])
|
|
18
|
-
write_lock(victim.pid, "scan")
|
|
19
|
-
os.environ["SAPS_LOCK_ROLE"] = "post"
|
|
20
|
-
import importlib, twitter_browser
|
|
21
|
-
importlib.reload(twitter_browser)
|
|
22
|
-
t0 = time.time()
|
|
23
|
-
twitter_browser._acquire_browser_lock()
|
|
24
|
-
dt = time.time() - t0
|
|
25
|
-
held = json.load(open(LOCK))
|
|
26
|
-
victim_alive = victim.poll() is None
|
|
27
|
-
print(f"TEST1 poster-preempts-scan: took={dt:.1f}s holder_now={held['session_id']} role={held.get('role')} victim_alive={victim_alive}")
|
|
28
|
-
assert held["session_id"] == f"python:{os.getpid()}", "poster should hold lock"
|
|
29
|
-
assert held.get("role") == "post"
|
|
30
|
-
assert not victim_alive, "scan victim should be killed"
|
|
31
|
-
assert dt < 10, "should preempt fast, not wait 45s"
|
|
32
|
-
if victim.poll() is None: victim.kill()
|
|
33
|
-
print("TEST1 PASS")
|
|
34
|
-
|
|
35
|
-
# ---- TEST 2: scanner does NOT preempt a live POST holder (waits then gives up) ----
|
|
36
|
-
clean()
|
|
37
|
-
poster = subprocess.Popen(["sleep", "120"])
|
|
38
|
-
write_lock(poster.pid, "post")
|
|
39
|
-
os.environ["SAPS_LOCK_ROLE"] = "scan"
|
|
40
|
-
twitter_browser.LOCK_WAIT_MAX = 4 # shorten the give-up window for the test
|
|
41
|
-
twitter_browser.LOCK_ROLE = "scan"
|
|
42
|
-
twitter_browser._LOCK_SESSION_ID = f"python:{os.getpid()}"
|
|
43
|
-
twitter_browser._LOCK_INHERITED = False
|
|
44
|
-
import io, contextlib
|
|
45
|
-
t0 = time.time()
|
|
46
|
-
rc = None
|
|
47
|
-
try:
|
|
48
|
-
twitter_browser._acquire_browser_lock()
|
|
49
|
-
rc = "acquired"
|
|
50
|
-
except SystemExit as e:
|
|
51
|
-
rc = f"exit:{e.code}"
|
|
52
|
-
dt = time.time() - t0
|
|
53
|
-
poster_alive = poster.poll() is None
|
|
54
|
-
print(f"TEST2 scanner-vs-post: result={rc} took={dt:.1f}s poster_alive={poster_alive}")
|
|
55
|
-
assert rc == "exit:1", "scanner must NOT take a live post holder; should give up"
|
|
56
|
-
assert poster_alive, "scanner must NOT kill the poster"
|
|
57
|
-
if poster.poll() is None: poster.kill()
|
|
58
|
-
print("TEST2 PASS")
|
|
59
|
-
clean()
|
|
60
|
-
print("ALL PASS")
|
package/scripts/saps_activity.py
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""LEGACY-NAME SHIM (brand rename SAPS -> S4L, completed 2026-07-06).
|
|
3
|
-
|
|
4
|
-
The real module is scripts/s4l_activity.py. This shim exists ONLY for callers
|
|
5
|
-
that cannot be edited (chflags-locked pipeline scripts) and for older installed
|
|
6
|
-
runtimes. Do NOT add new references to this filename; import or invoke
|
|
7
|
-
s4l_activity.py directly.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import os
|
|
11
|
-
import sys
|
|
12
|
-
|
|
13
|
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
14
|
-
from s4l_activity import * # noqa: F401,F403,E402
|
|
15
|
-
from s4l_activity import _main # noqa: E402
|
|
16
|
-
|
|
17
|
-
if __name__ == "__main__":
|
|
18
|
-
raise SystemExit(_main(sys.argv[1:]))
|
package/scripts/saps_mode.py
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""LEGACY-NAME SHIM (brand rename SAPS -> S4L, completed 2026-07-06).
|
|
3
|
-
|
|
4
|
-
The real module is scripts/s4l_mode.py. This shim exists ONLY for callers that
|
|
5
|
-
cannot be edited (the chflags-locked run-twitter-cycle.sh) and for older
|
|
6
|
-
installed runtimes. Do NOT add new references to this filename; import or
|
|
7
|
-
invoke s4l_mode.py directly.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import os
|
|
11
|
-
import sys
|
|
12
|
-
|
|
13
|
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
14
|
-
from s4l_mode import * # noqa: F401,F403,E402
|
|
15
|
-
from s4l_mode import main # noqa: E402
|
|
16
|
-
|
|
17
|
-
if __name__ == "__main__":
|
|
18
|
-
raise SystemExit(main(sys.argv[1:]))
|