@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.
Files changed (86) hide show
  1. package/README.md +134 -101
  2. package/bin/cli.js +23 -0
  3. package/mcp/dist/index.js +24 -5
  4. package/mcp/dist/runtime.js +110 -0
  5. package/mcp/dist/version.js +163 -54
  6. package/mcp/dist/version.json +2 -2
  7. package/mcp/manifest.json +1 -1
  8. package/mcp/menubar/s4l_card.py +55 -19
  9. package/mcp/menubar/s4l_menubar.py +179 -56
  10. package/mcp/menubar/s4l_state.py +41 -1
  11. package/mcp/package.json +1 -1
  12. package/package.json +4 -2
  13. package/scripts/active_experiments.py +30 -6
  14. package/scripts/autopilot_stall_watch.py +185 -63
  15. package/scripts/cdp_ready_check.py +94 -0
  16. package/scripts/engage_twitter_helper.py +83 -17
  17. package/scripts/engagement_styles.py +221 -26
  18. package/scripts/enrich_reply_parents.py +68 -22
  19. package/scripts/feedback_digest.py +24 -1
  20. package/scripts/generate_daily_human_style.py +31 -7
  21. package/scripts/invent_styles.py +103 -26
  22. package/scripts/linkedin_cadence.py +234 -0
  23. package/scripts/linkedin_killswitch.py +43 -0
  24. package/scripts/memory_snapshot.py +57 -0
  25. package/scripts/patches/browser-harness-loopback-cdp-proxy.patch +118 -0
  26. package/scripts/recent_self_posts.py +6 -0
  27. package/scripts/reddit_browser.py +57 -3
  28. package/scripts/s4l_box_update.sh +20 -1
  29. package/scripts/s4l_mode.py +3 -4
  30. package/scripts/scan_x_profile.py +25 -7
  31. package/scripts/schedule_state.py +23 -0
  32. package/scripts/scheduled_task_selfheal.py +213 -16
  33. package/scripts/setup_twitter_auth.py +6 -1
  34. package/scripts/snapshot.py +179 -61
  35. package/scripts/stall_guard.py +70 -0
  36. package/scripts/test_scheduled_task_selfheal.py +121 -0
  37. package/scripts/top_performers.py +34 -0
  38. package/scripts/twitter_browser.py +146 -8
  39. package/scripts/twitter_post_plan.py +10 -1
  40. package/scripts/twitter_scan.py +26 -0
  41. package/scripts/watchdog_hung_runs.py +117 -3
  42. package/skill/amplitude-24h-signups.sh +1 -1
  43. package/skill/audit-dm-staleness.sh +1 -1
  44. package/skill/audit-linkedin.sh +1 -1
  45. package/skill/audit-reddit-resurrect.sh +1 -1
  46. package/skill/audit.sh +5 -5
  47. package/skill/check-web-chats.sh +1 -1
  48. package/skill/dm-outreach-linkedin.sh +4 -4
  49. package/skill/dm-outreach-reddit.sh +1 -1
  50. package/skill/dm-outreach-twitter.sh +1 -1
  51. package/skill/engage-dm-replies.sh +16 -3
  52. package/skill/engage-linkedin.sh +2 -2
  53. package/skill/engage-moltbook.sh +1 -1
  54. package/skill/engage-twitter.sh +4 -4
  55. package/skill/github-engage.sh +1 -1
  56. package/skill/lib/browser-launch.sh +54 -0
  57. package/skill/lib/linkedin-backend.sh +22 -15
  58. package/skill/lib/reddit-backend.sh +17 -11
  59. package/skill/lib/twitter-backend.sh +137 -16
  60. package/skill/link-edit-github.sh +1 -1
  61. package/skill/link-edit-moltbook.sh +1 -1
  62. package/skill/link-edit-reddit.sh +1 -1
  63. package/skill/linkedin-cadence.sh +20 -0
  64. package/skill/linkedin-presence.sh +1 -1
  65. package/skill/lock.sh +30 -6
  66. package/skill/precompute-stats.sh +1 -1
  67. package/skill/refresh-instagram-tokens.sh +1 -1
  68. package/skill/refresh-twitter-following.sh +1 -1
  69. package/skill/run-generate-daily-style.sh +1 -1
  70. package/skill/run-instagram-daily.sh +1 -1
  71. package/skill/run-instagram-render.sh +1 -1
  72. package/skill/run-linkedin-launchd.sh +1 -1
  73. package/skill/run-linkedin-unipile.sh +1 -1
  74. package/skill/run-linkedin.sh +1 -1
  75. package/skill/run-reddit-search.sh +2 -2
  76. package/skill/run-reddit-threads.sh +1 -1
  77. package/skill/run-twitter-cycle.sh +190 -55
  78. package/skill/scan-instagram-replies.sh +1 -1
  79. package/skill/sentry-digest.sh +1 -1
  80. package/skill/stats-instagram.sh +1 -1
  81. package/skill/stats-linkedin.sh +2 -2
  82. package/skill/stats.sh +1 -1
  83. package/skill/sweep-link-clicks.sh +1 -1
  84. package/scripts/_lock_preempt_test.py +0 -60
  85. package/scripts/saps_activity.py +0 -18
  86. package/scripts/saps_mode.py +0 -18
@@ -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:%S)] $*" | tee -a "$LOG_FILE"; }
40
+ log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
41
41
 
42
42
  cd "$REPO_DIR"
43
43
 
@@ -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:%S)] $*" | tee -a "$LOG_FILE"; }
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)
@@ -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:%S)] LINKEDIN_KILLSWITCH active. Aborting LinkedIn pipeline."
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:%S)] $*" | tee -a "$LOG_FILE"; }
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:%S)] $*" >> "$LOGFILE"; echo "[$(date +%H:%M:%S)] $*"; }
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:%S)] === done in ${RUN_ELAPSED}s (exit=${EXIT_CODE}) ==="
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")
@@ -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:]))
@@ -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:]))