@m13v/s4l 1.7.4-rc.30 → 1.7.4-rc.32
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/mcp/dist/index.js +4 -2
- package/mcp/dist/runtime.js +16 -3
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +1 -1
- package/mcp/menubar/s4l_menubar.py +33 -31
- package/mcp/package.json +1 -1
- package/package.json +1 -1
- package/scripts/autopilot_stall_watch.py +6 -9
- package/scripts/patches/browser-harness-loopback-cdp-proxy.patch +20 -28
- package/scripts/schedule_state.py +23 -0
- 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 +2 -2
- 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/linkedin-backend.sh +12 -12
- package/skill/lib/reddit-backend.sh +8 -8
- package/skill/lib/twitter-backend.sh +12 -12
- 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-presence.sh +1 -1
- package/skill/lock.sh +6 -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 +1 -1
- 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/mcp/dist/index.js
CHANGED
|
@@ -2988,8 +2988,10 @@ function queueDir() {
|
|
|
2988
2988
|
// routine is draining the queue — the worker would claim within a minute if it
|
|
2989
2989
|
// were firing. This is the liveness signal that survives a Claude account switch
|
|
2990
2990
|
// (which orphans the routines while their global SKILL.md files stay put, so the
|
|
2991
|
-
// SKILL.md-presence check in autopilotLoaded() reads a FALSE green). Mirrors
|
|
2992
|
-
//
|
|
2991
|
+
// SKILL.md-presence check in autopilotLoaded() reads a FALSE green). Mirrors
|
|
2992
|
+
// HEALTHY_DRAIN_MAX_SECONDS in scripts/schedule_state.py, the single source of
|
|
2993
|
+
// truth every Python stall detector derives from; this TS constant can't import
|
|
2994
|
+
// it, so keep it in sync by hand when retuning there.
|
|
2993
2995
|
const AUTOPILOT_STALL_MS = 1_200_000;
|
|
2994
2996
|
// True when no scheduled-task routine is draining the draft queue. Two signals,
|
|
2995
2997
|
// OR'd (keep in lockstep with mcp/menubar/s4l_menubar.py::_autopilot_stalled and
|
package/mcp/dist/runtime.js
CHANGED
|
@@ -599,15 +599,28 @@ export function ensureRuntimeProvisioned() {
|
|
|
599
599
|
// live without a reinstall). Best-effort; never blocks startup.
|
|
600
600
|
export async function ensureHarnessPatched() {
|
|
601
601
|
try {
|
|
602
|
-
|
|
603
|
-
|
|
602
|
+
// Version marker of the CURRENT vendored patch: a symbol that exists only
|
|
603
|
+
// in its newest revision (v2 added the daemon.py websocket-proxy
|
|
604
|
+
// neutralization after v1's cdp_urlopen alone proved insufficient — the
|
|
605
|
+
// WS dial still routed through the macOS system proxy and died with
|
|
606
|
+
// "proxy rejected connection: HTTP 503"). Bump this string whenever the
|
|
607
|
+
// patch gains a new hunk, or upgraded installs will silently keep the
|
|
608
|
+
// previous revision.
|
|
609
|
+
const daemonPy = path.join(HARNESS_DIR, "src", "browser_harness", "daemon.py");
|
|
610
|
+
if (!fs.existsSync(daemonPy))
|
|
604
611
|
return { ok: false, detail: "harness not installed yet" };
|
|
605
|
-
if (fs.readFileSync(
|
|
612
|
+
if (fs.readFileSync(daemonPy, "utf-8").includes("s4l_no_proxy_ws")) {
|
|
606
613
|
return { ok: true, detail: "already patched" };
|
|
607
614
|
}
|
|
608
615
|
const harnessPatch = path.join(MATERIALIZED_REPO, "scripts", "patches", "browser-harness-loopback-cdp-proxy.patch");
|
|
609
616
|
if (!fs.existsSync(harnessPatch))
|
|
610
617
|
return { ok: false, detail: "patch file missing from package" };
|
|
618
|
+
// Discard any PRIOR vendored-patch revision (e.g. v1) so the cumulative
|
|
619
|
+
// patch applies onto pristine pinned sources. Only src/ is touched; the
|
|
620
|
+
// checkout is a managed artifact, never a place for local edits.
|
|
621
|
+
await sh("git", ["-C", HARNESS_DIR, "checkout", "--", "src/browser_harness"], {
|
|
622
|
+
timeoutMs: 30000,
|
|
623
|
+
});
|
|
611
624
|
const chk = await sh("git", ["-C", HARNESS_DIR, "apply", "--check", harnessPatch], {
|
|
612
625
|
timeoutMs: 30000,
|
|
613
626
|
});
|
package/mcp/dist/version.json
CHANGED
package/mcp/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"dxt_version": "0.1",
|
|
3
3
|
"name": "social-autoposter",
|
|
4
4
|
"display_name": "S4L",
|
|
5
|
-
"version": "1.7.4-rc.
|
|
5
|
+
"version": "1.7.4-rc.32",
|
|
6
6
|
"description": "Draft, review, approve, and autopilot X/Twitter posts.",
|
|
7
7
|
"long_description": "## **⚠️ The disclaimer above is generic Claude boilerplate.** Anthropic shows the same warning on every plugin regardless of what it does; any plugin has the same level of access as any app you download from the internet.\n\nS4L is an open source product developed by Mediar.ai Incorporated, a VC-backed San Francisco-based startup.\n\nTo get started:\n\n1\\. Copy this prompt: **Set me up on S4L plugin end to end**\n\n2\\. Quit with CMD+Q, reopen Claude, paste into a new chat.\n\nWhat happens next:\n\n* About every 5 minutes S4L scans X for posts that match your topics and drafts replies in your voice.\n* Drafts show up as review cards, usually the first within a few minutes. Nothing is posted automatically; you approve each one.\n* Posting autopilot stays off until you explicitly turn it on.",
|
|
8
8
|
"author": {
|
|
@@ -398,32 +398,33 @@ DIAGNOSE_PROMPT_TEMPLATE = (
|
|
|
398
398
|
"<that file>` so the report reaches the S4L developers. Keep replies short."
|
|
399
399
|
)
|
|
400
400
|
|
|
401
|
-
#
|
|
402
|
-
#
|
|
403
|
-
#
|
|
404
|
-
#
|
|
405
|
-
#
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
#
|
|
415
|
-
#
|
|
416
|
-
#
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
401
|
+
# Stall thresholds live in the single source of truth, scripts/schedule_state.py
|
|
402
|
+
# (HEALTHY_DRAIN_MAX_SECONDS and the names derived from it) — retune there, not
|
|
403
|
+
# here. Same module that owns FIRING_WINDOW; _schedule_state already delegates to
|
|
404
|
+
# it. The three names cover three distinct failure modes:
|
|
405
|
+
# AUTOPILOT_STALL_SECONDS pending job unclaimed → no routine draining
|
|
406
|
+
# (fix: Re-arm). Idle queue has no pending job,
|
|
407
|
+
# so a quiet pipeline never trips it.
|
|
408
|
+
# AUTOPILOT_RUNNING_STALL_SECONDS job claimed but wedged in running/ → worker
|
|
409
|
+
# died mid-run (fix: Diagnose).
|
|
410
|
+
# DRAFT_STUCK_SECONDS the producer's "draft Nm" label alive this
|
|
411
|
+
# long → end-to-end clock; the only timer that
|
|
412
|
+
# spans claim→die→requeue churn loops, which
|
|
413
|
+
# reset the two per-state timers on every
|
|
414
|
+
# transition while nothing ever completes.
|
|
415
|
+
# The import is best-effort because the S4L_REPO_DIR sys.path insertion near the
|
|
416
|
+
# top of this file is best-effort; the fallback literal mirrors
|
|
417
|
+
# HEALTHY_DRAIN_MAX_SECONDS.
|
|
418
|
+
try:
|
|
419
|
+
from schedule_state import ( # noqa: E402
|
|
420
|
+
STALL_SECONDS as AUTOPILOT_STALL_SECONDS,
|
|
421
|
+
RUNNING_STALL_SECONDS as AUTOPILOT_RUNNING_STALL_SECONDS,
|
|
422
|
+
DRAFT_STUCK_SECONDS,
|
|
423
|
+
)
|
|
424
|
+
except Exception:
|
|
425
|
+
AUTOPILOT_STALL_SECONDS = 1200
|
|
426
|
+
AUTOPILOT_RUNNING_STALL_SECONDS = 1200
|
|
427
|
+
DRAFT_STUCK_SECONDS = 1200
|
|
427
428
|
|
|
428
429
|
# A poll can read a transient "not stuck" moment (e.g. the activity file
|
|
429
430
|
# between one dying worker attempt and the next claim on the same aged job)
|
|
@@ -2445,11 +2446,12 @@ class S4LMenuBar(rumps.App):
|
|
|
2445
2446
|
self._stall_reason_info = ("", "")
|
|
2446
2447
|
# Draft worker stuck/killed: the producer narrates "drafting replies (Nm)"
|
|
2447
2448
|
# the whole time it blocks waiting for a worker to return a result, with NO
|
|
2448
|
-
# idea the worker died. A healthy drain
|
|
2449
|
-
# has been "drafting" past
|
|
2450
|
-
# killed mid-run (or never
|
|
2451
|
-
#
|
|
2452
|
-
#
|
|
2449
|
+
# idea the worker died. A healthy drain can run 8-10 min end to end (slot
|
|
2450
|
+
# wait + the drafting turn); once that label has been "drafting" past
|
|
2451
|
+
# DRAFT_STUCK_SECONDS the worker keeps getting killed mid-run (or never
|
|
2452
|
+
# claims) and nothing is draining — flip to ⚠ instead of leaving the
|
|
2453
|
+
# reassuring "drafting (25m)" spinner up. Skip when a more specific cause
|
|
2454
|
+
# (rate limit) already owns the reason.
|
|
2453
2455
|
#
|
|
2454
2456
|
# Gated on schedule_state in ("ok", "stalled"), NOT missing/disabled: when
|
|
2455
2457
|
# the schedule is missing/disabled (e.g. orphaned by an account switch),
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m13v/s4l-mcp",
|
|
3
|
-
"version": "1.7.4-rc.
|
|
3
|
+
"version": "1.7.4-rc.32",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Desktop MCP client for social-autoposter (X/Twitter rail): manual draft/review/approve loop, autopilot control, and stats. Thin wrapper over the existing pipeline scripts.",
|
|
6
6
|
"license": "MIT",
|
package/package.json
CHANGED
|
@@ -14,7 +14,8 @@ autopilot" item). This watcher is the part the user can't see: a fleet-side aler
|
|
|
14
14
|
so a sustained stall pages us even when nobody is looking at the menu bar.
|
|
15
15
|
|
|
16
16
|
Design mirrors the stall signal in mcp/menubar/s4l_menubar.py (_autopilot_stalled)
|
|
17
|
-
and mcp/src/index.ts (autopilotStalled)
|
|
17
|
+
and mcp/src/index.ts (autopilotStalled); the thresholds are shared via
|
|
18
|
+
schedule_state.py (index.ts mirrors by hand):
|
|
18
19
|
stalled = the autopilot is configured (a complete worker set's SKILL.md
|
|
19
20
|
files present — see WORKER_TASK_SETS)
|
|
20
21
|
AND a draft job has sat unclaimed in pending/ past STALL_SECONDS.
|
|
@@ -46,14 +47,10 @@ import identity # noqa: E402 (lives next to this file in scripts/)
|
|
|
46
47
|
|
|
47
48
|
s4l_env.mirror()
|
|
48
49
|
|
|
49
|
-
#
|
|
50
|
-
STALL_SECONDS =
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
# or crashed. Must be generous enough to clear the longest real drafting turn so a
|
|
54
|
-
# healthy run never trips it. Keep in sync with AUTOPILOT_RUNNING_STALL_SECONDS
|
|
55
|
-
# (menubar). See _oldest_running_age.
|
|
56
|
-
RUNNING_STALL_SECONDS = 1200
|
|
50
|
+
# Stall thresholds live in schedule_state.py (HEALTHY_DRAIN_MAX_SECONDS and the
|
|
51
|
+
# names derived from it) — retune there, not here. STALL_SECONDS = pending job
|
|
52
|
+
# unclaimed; RUNNING_STALL_SECONDS = claimed but wedged (see _oldest_running_age).
|
|
53
|
+
from schedule_state import STALL_SECONDS, RUNNING_STALL_SECONDS # noqa: E402
|
|
57
54
|
# Require the stall to persist this many consecutive checks before paging, so a
|
|
58
55
|
# transient slow claim (e.g. right after a Claude restart) doesn't false-alarm.
|
|
59
56
|
# At StartInterval 120 that is ~6 min of continuous stall.
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
From a97b7cdae960813f13af39ef99ce878426008dcc Mon Sep 17 00:00:00 2001
|
|
2
|
-
From: m13v <i@m13v.com>
|
|
3
|
-
Date: Mon, 13 Jul 2026 08:48:50 -0700
|
|
4
|
-
Subject: [PATCH] Never route loopback CDP requests through a proxy
|
|
5
|
-
|
|
6
|
-
urllib's default opener honors proxy env vars and macOS system proxy
|
|
7
|
-
settings (scutil --proxy). On a machine with a system-wide proxy (e.g. a
|
|
8
|
-
local gost/ISP forwarder), every 127.0.0.1 CDP probe is sent to the proxy
|
|
9
|
-
and returns 403 Forbidden, so Chrome looks unreachable while curl (env
|
|
10
|
-
proxies only) and Playwright (own websocket stack) work fine. Route all
|
|
11
|
-
loopback CDP HTTP calls through a proxy-free opener; non-loopback CDP
|
|
12
|
-
URLs keep default proxy behavior.
|
|
13
|
-
|
|
14
|
-
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
15
|
-
---
|
|
16
|
-
src/browser_harness/admin.py | 24 +++++++++++++++++++++++-
|
|
17
|
-
src/browser_harness/daemon.py | 7 ++++---
|
|
18
|
-
src/browser_harness/run.py | 3 ++-
|
|
19
|
-
3 files changed, 29 insertions(+), 5 deletions(-)
|
|
20
|
-
|
|
21
1
|
diff --git a/src/browser_harness/admin.py b/src/browser_harness/admin.py
|
|
22
2
|
index c72a8fb..64addee 100644
|
|
23
3
|
--- a/src/browser_harness/admin.py
|
|
@@ -62,18 +42,33 @@ index c72a8fb..64addee 100644
|
|
|
62
42
|
|
|
63
43
|
def _has_local_gui():
|
|
64
44
|
diff --git a/src/browser_harness/daemon.py b/src/browser_harness/daemon.py
|
|
65
|
-
index 0f0f255..
|
|
45
|
+
index 0f0f255..de51934 100644
|
|
66
46
|
--- a/src/browser_harness/daemon.py
|
|
67
47
|
+++ b/src/browser_harness/daemon.py
|
|
68
|
-
@@ -5,
|
|
48
|
+
@@ -5,8 +5,22 @@ from collections import deque
|
|
69
49
|
from pathlib import Path
|
|
70
50
|
|
|
71
51
|
from . import _ipc as ipc
|
|
72
52
|
+from .admin import cdp_urlopen
|
|
73
53
|
from cdp_use.client import CDPClient
|
|
74
54
|
|
|
55
|
+
+# s4l_no_proxy_ws: the daemon's CDP traffic is loopback; never let ANY client
|
|
56
|
+
+# in this process route it through a proxy. websockets>=14 autodetects proxies
|
|
57
|
+
+# at dial time via urllib.request.proxy_bypass()/getproxies() (which on macOS
|
|
58
|
+
+# include SYSTEM proxy settings from scutil --proxy), so a box-wide forwarder
|
|
59
|
+
+# turns the CDP WS dial into a proxied CONNECT that fails ("proxy rejected
|
|
60
|
+
+# connection: HTTP 503") — same failure class as the urllib 403 that
|
|
61
|
+
+# cdp_urlopen fixes for the HTTP lookup. Neutralize resolution process-wide:
|
|
62
|
+
+# the daemon's only non-loopback traffic (browser-use cloud API) works direct.
|
|
63
|
+
+# Both lookups happen at call time via module attributes, so this holds
|
|
64
|
+
+# regardless of import order.
|
|
65
|
+
+urllib.request.getproxies = lambda: {} # type: ignore[assignment]
|
|
66
|
+
+urllib.request.proxy_bypass = lambda host: 1 # type: ignore[assignment]
|
|
67
|
+
+
|
|
75
68
|
|
|
76
|
-
|
|
69
|
+
def _load_env():
|
|
70
|
+
repo_root = Path(__file__).resolve().parents[2]
|
|
71
|
+
@@ -113,7 +127,7 @@ def get_ws_url():
|
|
77
72
|
base_url = url.rstrip("/")
|
|
78
73
|
while time.time() < deadline:
|
|
79
74
|
try:
|
|
@@ -82,7 +77,7 @@ index 0f0f255..ceb08b9 100644
|
|
|
82
77
|
except urllib.error.HTTPError as e:
|
|
83
78
|
last_err = e
|
|
84
79
|
if e.code == 404 and (ws := _ws_from_devtools_active_port(url)):
|
|
85
|
-
@@ -139,7 +
|
|
80
|
+
@@ -139,7 +153,7 @@ def get_ws_url():
|
|
86
81
|
deadline = time.time() + 30
|
|
87
82
|
while time.time() < deadline:
|
|
88
83
|
try:
|
|
@@ -91,7 +86,7 @@ index 0f0f255..ceb08b9 100644
|
|
|
91
86
|
except urllib.error.HTTPError as e:
|
|
92
87
|
# Chrome 147+ disables /json/* HTTP discovery on the default user-data-dir;
|
|
93
88
|
# the ws path Chrome wrote to DevToolsActivePort still works.
|
|
94
|
-
@@ -153,7 +
|
|
89
|
+
@@ -153,7 +167,7 @@ def get_ws_url():
|
|
95
90
|
)
|
|
96
91
|
for probe_port in (9222, 9223):
|
|
97
92
|
try:
|
|
@@ -121,6 +116,3 @@ index 8ab1f0f..cbd3835 100644
|
|
|
121
116
|
return True
|
|
122
117
|
except OSError: pass
|
|
123
118
|
return False
|
|
124
|
-
--
|
|
125
|
-
2.43.0
|
|
126
|
-
|
|
@@ -94,6 +94,29 @@ def _registry_worker_recs(by_id):
|
|
|
94
94
|
# false "not scheduled".
|
|
95
95
|
FIRING_WINDOW = 420
|
|
96
96
|
|
|
97
|
+
# --- Stall thresholds (single source of truth) --------------------------------
|
|
98
|
+
# The longest a fully healthy drain can take end to end: a draft job can wait
|
|
99
|
+
# minutes for a worker slot (per-minute scheduler cadence, workers busy on other
|
|
100
|
+
# jobs) and the drafting turn itself runs multiple minutes. Observed healthy
|
|
101
|
+
# episodes reach 8-10 min (2026-07-13); alarm at 20 min for margin. Every stall
|
|
102
|
+
# detector derives from this ONE number so they can't drift apart again (99e695bf
|
|
103
|
+
# retuned the two per-state timers to 1200s but left the end-to-end DRAFT_STUCK
|
|
104
|
+
# check at 300s, which then false-alarmed on healthy 8-minute drains). The Node
|
|
105
|
+
# server can't import this module — mcp/src/index.ts::AUTOPILOT_STALL_MS must be
|
|
106
|
+
# kept in sync by hand.
|
|
107
|
+
HEALTHY_DRAIN_MAX_SECONDS = 1200
|
|
108
|
+
|
|
109
|
+
# A pending draft job unclaimed this long: no routine is draining the queue
|
|
110
|
+
# (an idle queue has no pending job at all, so a quiet pipeline never trips it).
|
|
111
|
+
STALL_SECONDS = HEALTHY_DRAIN_MAX_SECONDS
|
|
112
|
+
# A job claimed but still sitting in running/ this long: the worker picked it up
|
|
113
|
+
# and then wedged mid-run (the claude -p drafting child died / never spawned).
|
|
114
|
+
RUNNING_STALL_SECONDS = HEALTHY_DRAIN_MAX_SECONDS
|
|
115
|
+
# The producer's "drafting Nm" label alive this long: the end-to-end clock. This
|
|
116
|
+
# is the only timer that spans claim→die→requeue churn loops, which reset the two
|
|
117
|
+
# per-state timers above on every transition while nothing ever completes.
|
|
118
|
+
DRAFT_STUCK_SECONDS = HEALTHY_DRAIN_MAX_SECONDS
|
|
119
|
+
|
|
97
120
|
# Grace for a JUST-scheduled task that hasn't fired yet. When the user runs
|
|
98
121
|
# "Set up draft schedule", create_scheduled_task registers both worker tasks
|
|
99
122
|
# (cron "* * * * *", enabled) but lastRunAt is null until the host fires them the
|
|
@@ -34,5 +34,5 @@ RUN_START=$(date +%s)
|
|
|
34
34
|
EXIT_CODE=$?
|
|
35
35
|
RUN_ELAPSED=$(( $(date +%s) - RUN_START ))
|
|
36
36
|
|
|
37
|
-
echo "[$(date +%H:%M:%
|
|
37
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] === done in ${RUN_ELAPSED}s (exit=${EXIT_CODE}) ==="
|
|
38
38
|
exit "$EXIT_CODE"
|
|
@@ -14,7 +14,7 @@ LOG_DIR="$REPO_DIR/skill/logs"
|
|
|
14
14
|
mkdir -p "$LOG_DIR"
|
|
15
15
|
LOG_FILE="$LOG_DIR/audit-dm-staleness-$(date +%Y-%m-%d_%H%M%S).log"
|
|
16
16
|
|
|
17
|
-
log() { echo "[$(date +%H:%M:%
|
|
17
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
18
18
|
|
|
19
19
|
# HTTP-only lane (2026-06-01): both staleness UPDATEs run server-side via the
|
|
20
20
|
# s4l.ai API (POST /api/v1/dms/staleness-sweep). No DATABASE_URL, no psql.
|
package/skill/audit-linkedin.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# State: ~/.claude/social-autoposter/linkedin.killswitch
|
|
7
7
|
# Clear: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear
|
|
8
8
|
if [ -f "$HOME/.claude/social-autoposter/linkedin.killswitch" ]; then
|
|
9
|
-
echo "[$(date +%H:%M:%
|
|
9
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] LINKEDIN_KILLSWITCH active. Aborting LinkedIn pipeline."
|
|
10
10
|
echo " Re-auth LinkedIn in harness Chrome, then: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear"
|
|
11
11
|
exit 0
|
|
12
12
|
fi
|
|
@@ -38,7 +38,7 @@ AUDIT_HELPER="$REPO_DIR/scripts/audit_helper.py"
|
|
|
38
38
|
mkdir -p "$LOG_DIR"
|
|
39
39
|
LOG_FILE="$LOG_DIR/audit-reddit-resurrect-$(date +%Y-%m-%d_%H%M%S).log"
|
|
40
40
|
|
|
41
|
-
log() { echo "[$(date +%H:%M:%
|
|
41
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$LOG_FILE"; echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"; }
|
|
42
42
|
|
|
43
43
|
RUN_START=$(date +%s)
|
|
44
44
|
log "=== Reddit resurrect audit: $(date) ==="
|
package/skill/audit.sh
CHANGED
|
@@ -62,10 +62,10 @@ case "${PLATFORM:-all}" in
|
|
|
62
62
|
_LI_BOOT_RC=0
|
|
63
63
|
( source "$(dirname "$0")/lib/linkedin-backend.sh"; ensure_linkedin_browser_for_backend ) || _LI_BOOT_RC=$?
|
|
64
64
|
if [ "$_LI_BOOT_RC" -eq 78 ]; then
|
|
65
|
-
echo "[$(date +%H:%M:%
|
|
65
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] linkedin-pipeline lock: peer pipeline is driving the 9556 Chrome; skipping this fire"
|
|
66
66
|
exit 0
|
|
67
67
|
elif [ "$_LI_BOOT_RC" -ne 0 ]; then
|
|
68
|
-
echo "[$(date +%H:%M:%
|
|
68
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: linkedin browser bootstrap failed (rc=$_LI_BOOT_RC)"
|
|
69
69
|
exit "$_LI_BOOT_RC"
|
|
70
70
|
fi
|
|
71
71
|
;;
|
|
@@ -84,10 +84,10 @@ case "${PLATFORM:-all}" in
|
|
|
84
84
|
_LI_BOOT_RC=0
|
|
85
85
|
( source "$(dirname "$0")/lib/linkedin-backend.sh"; ensure_linkedin_browser_for_backend ) || _LI_BOOT_RC=$?
|
|
86
86
|
if [ "$_LI_BOOT_RC" -eq 78 ]; then
|
|
87
|
-
echo "[$(date +%H:%M:%
|
|
87
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] linkedin-pipeline lock: peer pipeline is driving the 9556 Chrome; skipping this fire (all-platform audit)"
|
|
88
88
|
exit 0
|
|
89
89
|
elif [ "$_LI_BOOT_RC" -ne 0 ]; then
|
|
90
|
-
echo "[$(date +%H:%M:%
|
|
90
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: linkedin browser bootstrap failed (rc=$_LI_BOOT_RC)"
|
|
91
91
|
exit "$_LI_BOOT_RC"
|
|
92
92
|
fi
|
|
93
93
|
python3 "$REPO_DIR_FOR_LOCK/scripts/reddit_browser_lock.py" acquire --timeout 3600 --ttl 90 2>&1 || \
|
|
@@ -113,7 +113,7 @@ mkdir -p "$LOG_DIR"
|
|
|
113
113
|
LOG_TAG="${PLATFORM:-all}"
|
|
114
114
|
LOG_FILE="$LOG_DIR/audit-${LOG_TAG}-$(date +%Y-%m-%d_%H%M%S).log"
|
|
115
115
|
|
|
116
|
-
log() { echo "[$(date +%H:%M:%
|
|
116
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$LOG_FILE"; echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"; }
|
|
117
117
|
|
|
118
118
|
RUN_START=$(date +%s)
|
|
119
119
|
log "=== Audit Pipeline Run (${LOG_TAG}): $(date) ==="
|
package/skill/check-web-chats.sh
CHANGED
|
@@ -35,7 +35,7 @@ mkdir -p "$LOG_DIR"
|
|
|
35
35
|
PYTHON_BIN="${PYTHON_BIN:-/opt/homebrew/bin/python3.11}"
|
|
36
36
|
[ -x "$PYTHON_BIN" ] || PYTHON_BIN="/usr/bin/python3"
|
|
37
37
|
|
|
38
|
-
log() { echo "[$(date +%H:%M:%
|
|
38
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$LOG_DIR/web-chat.log"; }
|
|
39
39
|
|
|
40
40
|
# Step 1: query Postgres for unread threads.
|
|
41
41
|
CHATS=$("$PYTHON_BIN" "$SCRIPTS_DIR/check_unread_web_chats.py" 2>>"$LOG_DIR/web-chat.log")
|
|
@@ -12,7 +12,7 @@ set -euo pipefail
|
|
|
12
12
|
# State: ~/.claude/social-autoposter/linkedin.killswitch
|
|
13
13
|
# Clear: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear
|
|
14
14
|
if [ -f "$HOME/.claude/social-autoposter/linkedin.killswitch" ]; then
|
|
15
|
-
echo "[$(date +%H:%M:%
|
|
15
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] LINKEDIN_KILLSWITCH active. Aborting LinkedIn pipeline."
|
|
16
16
|
echo " Re-auth LinkedIn in harness Chrome, then: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear"
|
|
17
17
|
exit 0
|
|
18
18
|
fi
|
|
@@ -37,10 +37,10 @@ acquire_lock "linkedin-browser" 3600
|
|
|
37
37
|
_LI_BOOT_RC=0
|
|
38
38
|
ensure_linkedin_browser_for_backend || _LI_BOOT_RC=$?
|
|
39
39
|
if [ "$_LI_BOOT_RC" -eq 78 ]; then
|
|
40
|
-
echo "[$(date +%H:%M:%
|
|
40
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] linkedin-pipeline lock: peer pipeline is driving the 9556 Chrome; skipping this fire"
|
|
41
41
|
exit 0
|
|
42
42
|
elif [ "$_LI_BOOT_RC" -ne 0 ]; then
|
|
43
|
-
echo "[$(date +%H:%M:%
|
|
43
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: linkedin browser bootstrap failed (rc=$_LI_BOOT_RC)"
|
|
44
44
|
exit "$_LI_BOOT_RC"
|
|
45
45
|
fi
|
|
46
46
|
acquire_lock "dm-outreach-linkedin" 2700
|
|
@@ -56,7 +56,7 @@ LOG_DIR="$REPO_DIR/skill/logs"
|
|
|
56
56
|
mkdir -p "$LOG_DIR"
|
|
57
57
|
LOG_FILE="$LOG_DIR/dm-outreach-linkedin-$(date +%Y-%m-%d_%H%M%S).log"
|
|
58
58
|
|
|
59
|
-
log() { echo "[$(date +%H:%M:%
|
|
59
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
60
60
|
|
|
61
61
|
RUN_START=$(date +%s)
|
|
62
62
|
log "=== LinkedIn DM Outreach Run: $(date) ==="
|
|
@@ -50,7 +50,7 @@ LOG_DIR="$REPO_DIR/skill/logs"
|
|
|
50
50
|
mkdir -p "$LOG_DIR"
|
|
51
51
|
LOG_FILE="$LOG_DIR/dm-outreach-reddit-$(date +%Y-%m-%d_%H%M%S).log"
|
|
52
52
|
|
|
53
|
-
log() { echo "[$(date +%H:%M:%
|
|
53
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
54
54
|
|
|
55
55
|
RUN_START=$(date +%s)
|
|
56
56
|
log "=== Reddit DM Outreach Run: $(date) ==="
|
|
@@ -44,7 +44,7 @@ SKILL_FILE="$REPO_DIR/SKILL.md"
|
|
|
44
44
|
# dm-outreach-linkedin.sh (migrated 2026-05-12).
|
|
45
45
|
# (LOG_DIR/LOG_FILE bootstrapped at top of script.)
|
|
46
46
|
|
|
47
|
-
log() { echo "[$(date +%H:%M:%
|
|
47
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
48
48
|
|
|
49
49
|
RUN_START=$(date +%s)
|
|
50
50
|
log "=== Twitter DM Outreach Run: $(date) ==="
|
|
@@ -35,7 +35,7 @@ fi
|
|
|
35
35
|
# State: ~/.claude/social-autoposter/linkedin.killswitch
|
|
36
36
|
# Clear: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear
|
|
37
37
|
if [ "$PLATFORM" = "linkedin" ] && [ -f "$HOME/.claude/social-autoposter/linkedin.killswitch" ]; then
|
|
38
|
-
echo "[$(date +%H:%M:%
|
|
38
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] LINKEDIN_KILLSWITCH active. Aborting LinkedIn DM-replies pipeline."
|
|
39
39
|
echo " Re-auth LinkedIn in harness Chrome, then: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear"
|
|
40
40
|
exit 0
|
|
41
41
|
fi
|
|
@@ -116,7 +116,7 @@ LOG_SUFFIX=""
|
|
|
116
116
|
[ -n "$PLATFORM" ] && LOG_SUFFIX="-$PLATFORM"
|
|
117
117
|
LOG_FILE="$LOG_DIR/engage-dm-replies${LOG_SUFFIX}-$(date +%Y-%m-%d_%H%M%S).log"
|
|
118
118
|
|
|
119
|
-
log() { echo "[$(date +%H:%M:%
|
|
119
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
120
120
|
|
|
121
121
|
RUN_START=$(date +%s)
|
|
122
122
|
log "=== DM Reply Engagement Run: $(date) (platform: ${PLATFORM:-all}) ==="
|
package/skill/engage-linkedin.sh
CHANGED
|
@@ -17,7 +17,7 @@ set -euo pipefail
|
|
|
17
17
|
# State: ~/.claude/social-autoposter/linkedin.killswitch
|
|
18
18
|
# Clear: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear
|
|
19
19
|
if [ -f "$HOME/.claude/social-autoposter/linkedin.killswitch" ]; then
|
|
20
|
-
echo "[$(date +%H:%M:%
|
|
20
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] LINKEDIN_KILLSWITCH active. Aborting LinkedIn pipeline."
|
|
21
21
|
echo " Re-auth LinkedIn in harness Chrome, then: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear"
|
|
22
22
|
exit 0
|
|
23
23
|
fi
|
|
@@ -84,7 +84,7 @@ LOG_FILE="$LOG_DIR/engage-linkedin-$(date +%Y-%m-%d_%H%M%S).log"
|
|
|
84
84
|
BATCH_ID="enli-$(date +%Y%m%d_%H%M%S)-$$"
|
|
85
85
|
export SA_CYCLE_ID="$BATCH_ID"
|
|
86
86
|
|
|
87
|
-
log() { echo "[$(date +%H:%M:%
|
|
87
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
88
88
|
|
|
89
89
|
RUN_START=$(date +%s)
|
|
90
90
|
log "=== LinkedIn Engagement Run: $(date) ==="
|
package/skill/engage-moltbook.sh
CHANGED
|
@@ -23,7 +23,7 @@ LOG_DIR="$REPO_DIR/skill/logs"
|
|
|
23
23
|
mkdir -p "$LOG_DIR"
|
|
24
24
|
LOG_FILE="$LOG_DIR/engage-moltbook-$(date +%Y-%m-%d_%H%M%S).log"
|
|
25
25
|
|
|
26
|
-
log() { echo "[$(date +%H:%M:%
|
|
26
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
27
27
|
|
|
28
28
|
RUN_START=$(date +%s)
|
|
29
29
|
log "=== MoltBook Engage Run: $(date) ==="
|
package/skill/engage-twitter.sh
CHANGED
|
@@ -25,12 +25,12 @@ source "$(dirname "$0")/lock.sh"
|
|
|
25
25
|
# Sets MCP_CONFIG_FILE, BROWSER_INSTRUCTIONS, exports TWITTER_CDP_URL=9555.
|
|
26
26
|
source "$(dirname "$0")/lib/twitter-backend.sh"
|
|
27
27
|
|
|
28
|
-
echo "[$(date +%H:%M:%
|
|
28
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Acquiring twitter-browser lock (pid=$$)..." | tee -a "$LOG_FILE"
|
|
29
29
|
acquire_lock "twitter-browser" 3600 2>>"$LOG_FILE"
|
|
30
|
-
echo "[$(date +%H:%M:%
|
|
30
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] twitter-browser lock held (pid=$$)" | tee -a "$LOG_FILE"
|
|
31
31
|
# Probe + launch harness Chrome on port 9555 if needed, then sweep leftover tabs.
|
|
32
32
|
ensure_twitter_browser_for_backend 2>&1 | tee -a "$LOG_FILE"
|
|
33
|
-
echo "[$(date +%H:%M:%
|
|
33
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Acquiring twitter (pipeline) lock (pid=$$)..." | tee -a "$LOG_FILE"
|
|
34
34
|
acquire_lock "twitter" 3600 2>>"$LOG_FILE"
|
|
35
35
|
|
|
36
36
|
# Load secrets
|
|
@@ -47,7 +47,7 @@ BATCH_SIZE=500
|
|
|
47
47
|
ENGAGE_TWITTER_HELPER="$REPO_DIR/scripts/engage_twitter_helper.py"
|
|
48
48
|
# (LOG_DIR/LOG_FILE bootstrapped at top of script.)
|
|
49
49
|
|
|
50
|
-
log() { echo "[$(date +%H:%M:%
|
|
50
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
51
51
|
|
|
52
52
|
RUN_START=$(date +%s)
|
|
53
53
|
log "=== Twitter Engagement Run: $(date) ==="
|
package/skill/github-engage.sh
CHANGED
|
@@ -20,7 +20,7 @@ LOG_DIR="$REPO_DIR/skill/logs"
|
|
|
20
20
|
mkdir -p "$LOG_DIR"
|
|
21
21
|
LOG_FILE="$LOG_DIR/github-engage-$(date +%Y-%m-%d_%H%M%S).log"
|
|
22
22
|
|
|
23
|
-
log() { echo "[$(date +%H:%M:%
|
|
23
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
24
24
|
|
|
25
25
|
# Per-cycle batch id stamped onto every claude_sessions row spawned by this
|
|
26
26
|
# engagement run (via SA_CYCLE_ID env -> log_claude_session.py). 2026-05-10
|
|
@@ -154,7 +154,7 @@ cleanup_harness_tabs() {
|
|
|
154
154
|
if ! $_probe 2>/dev/null; then
|
|
155
155
|
sleep 1
|
|
156
156
|
if ! $_probe 2>/dev/null; then
|
|
157
|
-
echo "[$(date +%H:%M:%
|
|
157
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] cleanup_harness_tabs: SKIPPED (linkedin-harness CDP /json/version unreachable after 10s+retry)" >&2
|
|
158
158
|
return 0
|
|
159
159
|
fi
|
|
160
160
|
fi
|
|
@@ -221,7 +221,7 @@ _acquire_linkedin_pipeline_lock() {
|
|
|
221
221
|
echo "$$" > "$_LI_PIPELINE_LOCK_DIR/pid"
|
|
222
222
|
echo "$_who" > "$_LI_PIPELINE_LOCK_DIR/holder"
|
|
223
223
|
export _LI_PIPELINE_LOCK_HELD=1
|
|
224
|
-
echo "[$(date +%H:%M:%
|
|
224
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] linkedin-pipeline lock ACQUIRED by $_who (pid $$)" >&2
|
|
225
225
|
return 0
|
|
226
226
|
fi
|
|
227
227
|
local _h_pid _h_who
|
|
@@ -236,11 +236,11 @@ _acquire_linkedin_pipeline_lock() {
|
|
|
236
236
|
return 0
|
|
237
237
|
fi
|
|
238
238
|
if [ -z "$_h_pid" ] || ! kill -0 "$_h_pid" 2>/dev/null; then
|
|
239
|
-
echo "[$(date +%H:%M:%
|
|
239
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] linkedin-pipeline lock: reclaiming stale lock (dead holder ${_h_who} pid ${_h_pid:-unknown})" >&2
|
|
240
240
|
rm -rf "$_LI_PIPELINE_LOCK_DIR"
|
|
241
241
|
continue
|
|
242
242
|
fi
|
|
243
|
-
echo "[$(date +%H:%M:%
|
|
243
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] linkedin-pipeline lock: held by ${_h_who} (pid ${_h_pid}); ${_who} skipping this fire (rc=78) to avoid two drivers on the 9556 Chrome" >&2
|
|
244
244
|
return 78
|
|
245
245
|
done
|
|
246
246
|
}
|
|
@@ -271,10 +271,10 @@ ensure_linkedin_browser_for_backend() {
|
|
|
271
271
|
if [ "${LINKEDIN_CDP_URL:-$_BH_LINKEDIN_DEFAULT_URL}" != "$_BH_LINKEDIN_DEFAULT_URL" ]; then
|
|
272
272
|
local _ext_url="${LINKEDIN_CDP_URL}"
|
|
273
273
|
if curl -sf --max-time 2 -o /dev/null "${_ext_url}/json/version" 2>/dev/null; then
|
|
274
|
-
echo "[$(date +%H:%M:%
|
|
274
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Using externally-managed Chrome at ${_ext_url} (skipping harness launch + tab cleanup)" >&2
|
|
275
275
|
return 0
|
|
276
276
|
fi
|
|
277
|
-
echo "[$(date +%H:%M:%
|
|
277
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: LINKEDIN_CDP_URL=${_ext_url} not reachable. External Chrome must be managed by host." >&2
|
|
278
278
|
return 1
|
|
279
279
|
fi
|
|
280
280
|
# Cross-pipeline whole-run lock: only one LinkedIn browser pipeline drives
|
|
@@ -288,14 +288,14 @@ ensure_linkedin_browser_for_backend() {
|
|
|
288
288
|
_acquire_linkedin_pipeline_lock || return $?
|
|
289
289
|
# Probe + launch harness Chrome on port 9556 if needed.
|
|
290
290
|
if ! curl -sf --max-time 2 -o /dev/null http://127.0.0.1:9556/json/version 2>/dev/null; then
|
|
291
|
-
echo "[$(date +%H:%M:%
|
|
291
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] LinkedIn harness Chrome down on port 9556, launching..." >&2
|
|
292
292
|
# Dated relaunch stamp for central observability (see twitter-backend.sh).
|
|
293
293
|
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) port=9556" \
|
|
294
294
|
>> "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/logs/chrome-relaunch-events.log" 2>/dev/null || true
|
|
295
295
|
local _chrome_bin
|
|
296
296
|
_chrome_bin=$(_resolve_chrome_bin)
|
|
297
297
|
if [ -z "$_chrome_bin" ]; then
|
|
298
|
-
echo "[$(date +%H:%M:%
|
|
298
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: no Chrome/Chromium binary found. Set BH_CHROME_BIN." >&2
|
|
299
299
|
return 1
|
|
300
300
|
fi
|
|
301
301
|
# On Linux + no display, run headless. On root, add --no-sandbox.
|
|
@@ -323,7 +323,7 @@ ensure_linkedin_browser_for_backend() {
|
|
|
323
323
|
local _stale_pids
|
|
324
324
|
_stale_pids=$(pgrep -f -- "--user-data-dir=$_prof_dir " 2>/dev/null || true)
|
|
325
325
|
if [ -n "$_stale_pids" ] && ! curl -sf --max-time 2 -o /dev/null http://127.0.0.1:9556/json/version 2>/dev/null; then
|
|
326
|
-
echo "[$(date +%H:%M:%
|
|
326
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] CDP down but Chrome still holds $_prof_dir (pids: $(echo $_stale_pids | tr '\n' ' ')); reaping stale profile owner before relaunch" >&2
|
|
327
327
|
kill $_stale_pids 2>/dev/null || true
|
|
328
328
|
sleep 2
|
|
329
329
|
_stale_pids=$(pgrep -f -- "--user-data-dir=$_prof_dir " 2>/dev/null || true)
|
|
@@ -357,10 +357,10 @@ os.execv(sys.argv[1], sys.argv[1:])' \
|
|
|
357
357
|
sleep 1
|
|
358
358
|
done
|
|
359
359
|
if ! curl -sf --max-time 2 -o /dev/null http://127.0.0.1:9556/json/version 2>/dev/null; then
|
|
360
|
-
echo "[$(date +%H:%M:%
|
|
360
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: LinkedIn harness Chrome failed to start within 12s" >&2
|
|
361
361
|
return 1
|
|
362
362
|
fi
|
|
363
|
-
echo "[$(date +%H:%M:%
|
|
363
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] LinkedIn harness Chrome up on port 9556" >&2
|
|
364
364
|
fi
|
|
365
365
|
# Always close leftover tabs from prior runs. Safe under acquire_lock
|
|
366
366
|
# "linkedin-browser" serialization.
|
|
@@ -395,7 +395,7 @@ _linkedin_session_detect_gate() {
|
|
|
395
395
|
"$_py" "$HOME/social-autoposter/scripts/linkedin_killswitch.py" detect-gate \
|
|
396
396
|
--cdp-url "${LINKEDIN_CDP_URL:-$_BH_LINKEDIN_DEFAULT_URL}" >&2 || _rc=$?
|
|
397
397
|
if [ "$_rc" = "2" ]; then
|
|
398
|
-
echo "[$(date +%H:%M:%
|
|
398
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] detect-gate tripped the LinkedIn killswitch; aborting this fire" >&2
|
|
399
399
|
return 1
|
|
400
400
|
fi
|
|
401
401
|
return 0
|
|
@@ -123,7 +123,7 @@ cleanup_harness_tabs() {
|
|
|
123
123
|
if ! $_probe 2>/dev/null; then
|
|
124
124
|
sleep 1
|
|
125
125
|
if ! $_probe 2>/dev/null; then
|
|
126
|
-
echo "[$(date +%H:%M:%
|
|
126
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] cleanup_harness_tabs: SKIPPED (reddit-harness CDP /json/version unreachable after 10s+retry)" >&2
|
|
127
127
|
return 0
|
|
128
128
|
fi
|
|
129
129
|
fi
|
|
@@ -156,19 +156,19 @@ ensure_reddit_browser_for_backend() {
|
|
|
156
156
|
if [ "${REDDIT_CDP_URL:-$_BH_REDDIT_DEFAULT_URL}" != "$_BH_REDDIT_DEFAULT_URL" ]; then
|
|
157
157
|
local _ext_url="${REDDIT_CDP_URL}"
|
|
158
158
|
if curl -sf --max-time 2 -o /dev/null "${_ext_url}/json/version" 2>/dev/null; then
|
|
159
|
-
echo "[$(date +%H:%M:%
|
|
159
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Using externally-managed Chrome at ${_ext_url} (skipping harness launch + tab cleanup)" >&2
|
|
160
160
|
return 0
|
|
161
161
|
fi
|
|
162
|
-
echo "[$(date +%H:%M:%
|
|
162
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: REDDIT_CDP_URL=${_ext_url} not reachable. External Chrome must be managed by host." >&2
|
|
163
163
|
return 1
|
|
164
164
|
fi
|
|
165
165
|
# Probe + launch harness Chrome on port 9557 if needed.
|
|
166
166
|
if ! curl -sf --max-time 2 -o /dev/null http://127.0.0.1:9557/json/version 2>/dev/null; then
|
|
167
|
-
echo "[$(date +%H:%M:%
|
|
167
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Reddit harness Chrome down on port 9557, launching..." >&2
|
|
168
168
|
local _chrome_bin
|
|
169
169
|
_chrome_bin=$(_resolve_chrome_bin)
|
|
170
170
|
if [ -z "$_chrome_bin" ]; then
|
|
171
|
-
echo "[$(date +%H:%M:%
|
|
171
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: no Chrome/Chromium binary found. Set BH_CHROME_BIN." >&2
|
|
172
172
|
return 1
|
|
173
173
|
fi
|
|
174
174
|
# On Linux + no display, run headless. On root, add --no-sandbox.
|
|
@@ -196,7 +196,7 @@ ensure_reddit_browser_for_backend() {
|
|
|
196
196
|
local _stale_pids
|
|
197
197
|
_stale_pids=$(pgrep -f -- "--user-data-dir=$_prof_dir " 2>/dev/null || true)
|
|
198
198
|
if [ -n "$_stale_pids" ] && ! curl -sf --max-time 2 -o /dev/null http://127.0.0.1:9557/json/version 2>/dev/null; then
|
|
199
|
-
echo "[$(date +%H:%M:%
|
|
199
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] CDP down but Chrome still holds $_prof_dir (pids: $(echo $_stale_pids | tr '\n' ' ')); reaping stale profile owner before relaunch" >&2
|
|
200
200
|
kill $_stale_pids 2>/dev/null || true
|
|
201
201
|
sleep 2
|
|
202
202
|
_stale_pids=$(pgrep -f -- "--user-data-dir=$_prof_dir " 2>/dev/null || true)
|
|
@@ -216,10 +216,10 @@ ensure_reddit_browser_for_backend() {
|
|
|
216
216
|
sleep 1
|
|
217
217
|
done
|
|
218
218
|
if ! curl -sf --max-time 2 -o /dev/null http://127.0.0.1:9557/json/version 2>/dev/null; then
|
|
219
|
-
echo "[$(date +%H:%M:%
|
|
219
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: Reddit harness Chrome failed to start within 12s" >&2
|
|
220
220
|
return 1
|
|
221
221
|
fi
|
|
222
|
-
echo "[$(date +%H:%M:%
|
|
222
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Reddit harness Chrome up on port 9557" >&2
|
|
223
223
|
fi
|
|
224
224
|
# Always close leftover tabs from prior runs. Safe under acquire_lock
|
|
225
225
|
# "reddit-browser" serialization.
|
|
@@ -138,7 +138,7 @@ cleanup_harness_tabs() {
|
|
|
138
138
|
if ! $_probe 2>/dev/null; then
|
|
139
139
|
sleep 1
|
|
140
140
|
if ! $_probe 2>/dev/null; then
|
|
141
|
-
echo "[$(date +%H:%M:%
|
|
141
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] cleanup_harness_tabs: SKIPPED (harness CDP /json/version unreachable after 10s+retry)" >&2
|
|
142
142
|
return 0
|
|
143
143
|
fi
|
|
144
144
|
fi
|
|
@@ -202,13 +202,13 @@ ensure_twitter_browser_for_backend() {
|
|
|
202
202
|
# 180s per downstream attach against a wedged browser.
|
|
203
203
|
local _ext_verdict
|
|
204
204
|
if ! _ext_verdict=$(_bh_cdp_ready "$_ext_url"); then
|
|
205
|
-
echo "[$(date +%H:%M:%
|
|
205
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: external Chrome at ${_ext_url} is WEDGED (/json/version answers but the CDP handshake never completes). Host must restart it (AppMaker /opt/startup.sh, etc)." >&2
|
|
206
206
|
echo "twitter_cdp_wedge: detected url=${_ext_url} action=none-external" >&2
|
|
207
207
|
_bh_record_cdp_health external_wedged "$_ext_verdict"
|
|
208
208
|
return 1
|
|
209
209
|
fi
|
|
210
210
|
_bh_record_cdp_health ok-external "$_ext_verdict"
|
|
211
|
-
echo "[$(date +%H:%M:%
|
|
211
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Using externally-managed Chrome at ${_ext_url} (skipping harness launch + tab cleanup)" >&2
|
|
212
212
|
# Restore the Twitter login if the sandbox was substituted. AppMaker
|
|
213
213
|
# Hobby-tier sandboxes have a 1h TTL; on substitution /root is reseeded
|
|
214
214
|
# from /etc/skel-root and the harness profile (cookies) is wiped. This
|
|
@@ -217,7 +217,7 @@ ensure_twitter_browser_for_backend() {
|
|
|
217
217
|
python3 "$_BH_REPO_DIR/scripts/restore_twitter_session.py" 2>&1 | sed 's/^/[restore] /' >&2 || true
|
|
218
218
|
return 0
|
|
219
219
|
fi
|
|
220
|
-
echo "[$(date +%H:%M:%
|
|
220
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: TWITTER_CDP_URL=${_ext_url} not reachable. External Chrome must be managed by host (AppMaker /opt/startup.sh, etc)." >&2
|
|
221
221
|
return 1
|
|
222
222
|
fi
|
|
223
223
|
# Probe + launch harness Chrome on port 9555 if needed. Two-stage probe:
|
|
@@ -229,12 +229,12 @@ ensure_twitter_browser_for_backend() {
|
|
|
229
229
|
local _bh_prof_dir="$HOME/.claude/browser-profiles/browser-harness"
|
|
230
230
|
if ! curl -sf --max-time 2 -o /dev/null http://127.0.0.1:9555/json/version 2>/dev/null; then
|
|
231
231
|
_need_launch=1; _launch_reason="http_down"
|
|
232
|
-
echo "[$(date +%H:%M:%
|
|
232
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Harness Chrome down on port 9555, launching..." >&2
|
|
233
233
|
else
|
|
234
234
|
local _ready_verdict
|
|
235
235
|
if ! _ready_verdict=$(_bh_cdp_ready "$_BH_DEFAULT_URL"); then
|
|
236
236
|
_need_launch=1; _launch_reason="cdp_wedge"
|
|
237
|
-
echo "[$(date +%H:%M:%
|
|
237
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Harness Chrome WEDGED on port 9555 (/json/version answers but the CDP handshake never completes: ${_ready_verdict:-no verdict}); killing and relaunching..." >&2
|
|
238
238
|
# Machine-greppable marker (same stderr-marker convention as
|
|
239
239
|
# twitter_access_gate; bin/server.js parses these).
|
|
240
240
|
echo "twitter_cdp_wedge: detected url=$_BH_DEFAULT_URL action=restart" >&2
|
|
@@ -260,7 +260,7 @@ ensure_twitter_browser_for_backend() {
|
|
|
260
260
|
local _chrome_bin
|
|
261
261
|
_chrome_bin=$(_resolve_chrome_bin)
|
|
262
262
|
if [ -z "$_chrome_bin" ]; then
|
|
263
|
-
echo "[$(date +%H:%M:%
|
|
263
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: no Chrome/Chromium binary found. Set BH_CHROME_BIN." >&2
|
|
264
264
|
return 1
|
|
265
265
|
fi
|
|
266
266
|
# On Linux + no display, run headless. On root, add --no-sandbox.
|
|
@@ -302,7 +302,7 @@ ensure_twitter_browser_for_backend() {
|
|
|
302
302
|
local _stale_pids
|
|
303
303
|
_stale_pids=$(pgrep -f -- "--user-data-dir=$_prof_dir " 2>/dev/null || true)
|
|
304
304
|
if [ -n "$_stale_pids" ] && ! curl -sf --max-time 2 -o /dev/null http://127.0.0.1:9555/json/version 2>/dev/null; then
|
|
305
|
-
echo "[$(date +%H:%M:%
|
|
305
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] CDP down but Chrome still holds $_prof_dir (pids: $(echo $_stale_pids | tr '\n' ' ')); reaping stale profile owner before relaunch" >&2
|
|
306
306
|
kill $_stale_pids 2>/dev/null || true
|
|
307
307
|
sleep 2
|
|
308
308
|
_stale_pids=$(pgrep -f -- "--user-data-dir=$_prof_dir " 2>/dev/null || true)
|
|
@@ -335,7 +335,7 @@ os.execv(sys.argv[1], sys.argv[1:])' \
|
|
|
335
335
|
sleep 1
|
|
336
336
|
done
|
|
337
337
|
if ! curl -sf --max-time 2 -o /dev/null http://127.0.0.1:9555/json/version 2>/dev/null; then
|
|
338
|
-
echo "[$(date +%H:%M:%
|
|
338
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: harness Chrome failed to start within 12s" >&2
|
|
339
339
|
_bh_record_cdp_health launch_failed null
|
|
340
340
|
return 1
|
|
341
341
|
fi
|
|
@@ -344,13 +344,13 @@ os.execv(sys.argv[1], sys.argv[1:])' \
|
|
|
344
344
|
# the cycle loudly, not feed 180s hangs downstream.
|
|
345
345
|
local _post_verdict
|
|
346
346
|
if ! _post_verdict=$(_bh_cdp_ready "$_BH_DEFAULT_URL"); then
|
|
347
|
-
echo "[$(date +%H:%M:%
|
|
347
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: harness Chrome answers HTTP after relaunch but the CDP handshake is STILL failing: ${_post_verdict:-no verdict}" >&2
|
|
348
348
|
echo "twitter_cdp_wedge: detected url=$_BH_DEFAULT_URL action=relaunch_failed" >&2
|
|
349
349
|
_bh_record_cdp_health relaunch_failed "$_post_verdict"
|
|
350
350
|
return 1
|
|
351
351
|
fi
|
|
352
352
|
_bh_record_cdp_health relaunched "$_post_verdict"
|
|
353
|
-
echo "[$(date +%H:%M:%
|
|
353
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Harness Chrome up on port 9555" >&2
|
|
354
354
|
else
|
|
355
355
|
_bh_record_cdp_health ok "${_ready_verdict:-null}"
|
|
356
356
|
fi
|
|
@@ -392,7 +392,7 @@ defer_if_foreign_for_backend() {
|
|
|
392
392
|
_sa_harness_log() {
|
|
393
393
|
# Use the caller's log() FUNCTION when present; `declare -F` matches only a
|
|
394
394
|
# shell function, never the macOS /usr/bin/log binary (command -v would).
|
|
395
|
-
if declare -F log >/dev/null 2>&1; then log "$*"; else echo "[$(date +%H:%M:%
|
|
395
|
+
if declare -F log >/dev/null 2>&1; then log "$*"; else echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >&2; fi
|
|
396
396
|
}
|
|
397
397
|
_sa_resolve_uv() {
|
|
398
398
|
local c
|
|
@@ -30,7 +30,7 @@ LE_HELPER="$REPO_DIR/scripts/link_edit_helper.py"
|
|
|
30
30
|
mkdir -p "$LOG_DIR"
|
|
31
31
|
LOG_FILE="$LOG_DIR/link-edit-github-$(date +%Y-%m-%d_%H%M%S).log"
|
|
32
32
|
|
|
33
|
-
log() { echo "[$(date +%H:%M:%
|
|
33
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
34
34
|
|
|
35
35
|
RUN_START=$(date +%s)
|
|
36
36
|
log "=== GitHub Link Edit Run: $(date) ==="
|
|
@@ -29,7 +29,7 @@ LE_HELPER="$REPO_DIR/scripts/link_edit_helper.py"
|
|
|
29
29
|
mkdir -p "$LOG_DIR"
|
|
30
30
|
LOG_FILE="$LOG_DIR/link-edit-moltbook-$(date +%Y-%m-%d_%H%M%S).log"
|
|
31
31
|
|
|
32
|
-
log() { echo "[$(date +%H:%M:%
|
|
32
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
33
33
|
|
|
34
34
|
RUN_START=$(date +%s)
|
|
35
35
|
log "=== Moltbook Link Edit Run: $(date) ==="
|
|
@@ -38,7 +38,7 @@ LE_HELPER="$REPO_DIR/scripts/link_edit_helper.py"
|
|
|
38
38
|
mkdir -p "$LOG_DIR"
|
|
39
39
|
LOG_FILE="$LOG_DIR/link-edit-reddit-$(date +%Y-%m-%d_%H%M%S).log"
|
|
40
40
|
|
|
41
|
-
log() { echo "[$(date +%H:%M:%
|
|
41
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
42
42
|
|
|
43
43
|
RUN_START=$(date +%s)
|
|
44
44
|
log "=== Reddit Link Edit Run: $(date) ==="
|
|
@@ -25,7 +25,7 @@ BATCH_ID="lipres-$(date +%Y%m%d_%H%M%S)-$$"
|
|
|
25
25
|
export SA_CYCLE_ID="$BATCH_ID"
|
|
26
26
|
export S4L_PIPELINE_NAME="linkedin-presence"
|
|
27
27
|
|
|
28
|
-
log() { echo "[$(date +%H:%M:%
|
|
28
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
29
29
|
|
|
30
30
|
DRY_RUN="${LINKEDIN_PRESENCE_DRY_RUN:-0}"
|
|
31
31
|
|
package/skill/lock.sh
CHANGED
|
@@ -121,11 +121,11 @@ if [ -z "${_SA_LOCK_DIRS+x}" ]; then
|
|
|
121
121
|
# is defect "owner=OTHER" (wipes a live peer's lock -> double-hold).
|
|
122
122
|
if _sa_we_own_lock "$d"; then
|
|
123
123
|
_sa_lock_event trap_rm "$_lname" "$(_sa_lock_owner_tag "$d")"
|
|
124
|
-
echo "[lock] trap-released $_lname pid=$$ at $(date +%H:%M:%
|
|
124
|
+
echo "[lock] trap-released $_lname pid=$$ at $(date -u +%Y-%m-%dT%H:%M:%SZ)" >&2
|
|
125
125
|
rm -rf "$d"
|
|
126
126
|
else
|
|
127
127
|
_sa_lock_event trap_rm_skipped "$_lname" "$(_sa_lock_owner_tag "$d")"
|
|
128
|
-
echo "[lock] trap-release SKIPPED $_lname pid=$$ (not owner) at $(date +%H:%M:%
|
|
128
|
+
echo "[lock] trap-release SKIPPED $_lname pid=$$ (not owner) at $(date -u +%Y-%m-%dT%H:%M:%SZ)" >&2
|
|
129
129
|
fi
|
|
130
130
|
done
|
|
131
131
|
for t in ${_SA_LOCK_TICKETS[@]+"${_SA_LOCK_TICKETS[@]}"}; do
|
|
@@ -149,7 +149,7 @@ if [ -z "${_SA_LOCK_DIRS+x}" ]; then
|
|
|
149
149
|
_sa_exit_on_signal() {
|
|
150
150
|
local _sig="$1" _code="$2"
|
|
151
151
|
_sa_lock_event signal_exit "-" "signal=$_sig"
|
|
152
|
-
echo "[lock] caught SIG${_sig} pid=$$; releasing locks and exiting $_code at $(date +%H:%M:%
|
|
152
|
+
echo "[lock] caught SIG${_sig} pid=$$; releasing locks and exiting $_code at $(date -u +%Y-%m-%dT%H:%M:%SZ)" >&2
|
|
153
153
|
_sa_release_locks
|
|
154
154
|
trap - EXIT
|
|
155
155
|
exit "$_code"
|
|
@@ -247,7 +247,7 @@ acquire_lock() {
|
|
|
247
247
|
[ "$_existing" != "$ticket_file" ] && _new_t+=("$_existing")
|
|
248
248
|
done
|
|
249
249
|
_SA_LOCK_TICKETS=(${_new_t[@]+"${_new_t[@]}"})
|
|
250
|
-
echo "[lock] acquired $name pid=$$ at $(date +%H:%M:%
|
|
250
|
+
echo "[lock] acquired $name pid=$$ at $(date -u +%Y-%m-%dT%H:%M:%SZ) waited=${waited}s" >&2
|
|
251
251
|
_sa_lock_event acquired "$name" "waited=${waited}s"
|
|
252
252
|
break
|
|
253
253
|
fi
|
|
@@ -694,10 +694,10 @@ release_lock() {
|
|
|
694
694
|
if _sa_we_own_lock "$lock_dir"; then
|
|
695
695
|
_sa_lock_event release "$name" "$(_sa_lock_owner_tag "$lock_dir")"
|
|
696
696
|
rm -rf "$lock_dir"
|
|
697
|
-
echo "[lock] released $name pid=$$ at $(date +%H:%M:%
|
|
697
|
+
echo "[lock] released $name pid=$$ at $(date -u +%Y-%m-%dT%H:%M:%SZ)" >&2
|
|
698
698
|
else
|
|
699
699
|
_sa_lock_event release_skipped "$name" "$(_sa_lock_owner_tag "$lock_dir")"
|
|
700
|
-
echo "[lock] release SKIPPED $name pid=$$ (not owner) at $(date +%H:%M:%
|
|
700
|
+
echo "[lock] release SKIPPED $name pid=$$ (not owner) at $(date -u +%Y-%m-%dT%H:%M:%SZ)" >&2
|
|
701
701
|
fi
|
|
702
702
|
# Rebuild the lock stack without this entry so the EXIT trap doesn't try to
|
|
703
703
|
# rm it again (harmless, but keeps the stack honest if release_lock is paired
|
|
@@ -31,5 +31,5 @@ python3 "$REPO_DIR/scripts/precompute_dashboard_stats.py"
|
|
|
31
31
|
EXIT_CODE=$?
|
|
32
32
|
RUN_ELAPSED=$(( $(date +%s) - RUN_START ))
|
|
33
33
|
|
|
34
|
-
echo "[$(date +%H:%M:%
|
|
34
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] === done in ${RUN_ELAPSED}s (exit=${EXIT_CODE}) ==="
|
|
35
35
|
exit "$EXIT_CODE"
|
|
@@ -19,7 +19,7 @@ LOG_DIR="$REPO_DIR/skill/logs"
|
|
|
19
19
|
mkdir -p "$LOG_DIR"
|
|
20
20
|
LOG_FILE="$LOG_DIR/refresh-instagram-tokens-$(date +%Y-%m-%d_%H%M%S).log"
|
|
21
21
|
|
|
22
|
-
log() { echo "[$(date +%H:%M:%
|
|
22
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
23
23
|
log "=== refresh-instagram-tokens fire: $(date) ==="
|
|
24
24
|
|
|
25
25
|
RUN_START=$(date +%s)
|
|
@@ -16,7 +16,7 @@ set -uo pipefail
|
|
|
16
16
|
LOG_DIR="$HOME/social-autoposter/skill/logs"
|
|
17
17
|
mkdir -p "$LOG_DIR"
|
|
18
18
|
LOG_FILE="$LOG_DIR/refresh-twitter-following-$(date +%Y-%m-%d_%H%M%S).log"
|
|
19
|
-
log() { echo "[$(date +%H:%M:%
|
|
19
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
20
20
|
|
|
21
21
|
REPO_DIR="$HOME/social-autoposter"
|
|
22
22
|
|
|
@@ -36,7 +36,7 @@ if [ -f "$REPO_DIR/.env" ]; then
|
|
|
36
36
|
set +a
|
|
37
37
|
fi
|
|
38
38
|
|
|
39
|
-
log() { echo "[$(date +%H:%M:%
|
|
39
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
40
40
|
|
|
41
41
|
# Run accounting for dashboard Job History (Post Threads · Instagram).
|
|
42
42
|
# Each exit site updates POSTED_CT / SKIPPED_CT / FAILED_CT; the EXIT trap
|
|
@@ -43,7 +43,7 @@ if [ -f "$REPO_DIR/.env" ]; then
|
|
|
43
43
|
set +a
|
|
44
44
|
fi
|
|
45
45
|
|
|
46
|
-
log() { echo "[$(date +%H:%M:%
|
|
46
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
47
47
|
|
|
48
48
|
# Run accounting for dashboard Job History (Render · Instagram). Matches the
|
|
49
49
|
# pattern in run-instagram-daily.sh / run-twitter-threads.sh / run-reddit-threads.sh:
|
|
@@ -40,7 +40,7 @@ mkdir -p "$LOG_DIR"
|
|
|
40
40
|
# State: ~/.claude/social-autoposter/linkedin.killswitch
|
|
41
41
|
# Clear: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear
|
|
42
42
|
if [ -f "$HOME/.claude/social-autoposter/linkedin.killswitch" ]; then
|
|
43
|
-
echo "[$(date +%H:%M:%
|
|
43
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] LINKEDIN_KILLSWITCH active. Skipping run-linkedin." \
|
|
44
44
|
>> "$LOG_DIR/launchd-linkedin-stderr.log"
|
|
45
45
|
exit 0
|
|
46
46
|
fi
|
package/skill/run-linkedin.sh
CHANGED
|
@@ -85,7 +85,7 @@ LINKEDIN_BACKEND="${LINKEDIN_BACKEND:-browser}"
|
|
|
85
85
|
# Clear: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear
|
|
86
86
|
# Only gates the browser backend — UniPile has no headed session to compromise.
|
|
87
87
|
if [ "$LINKEDIN_BACKEND" = "browser" ] && [ -f "$HOME/.claude/social-autoposter/linkedin.killswitch" ]; then
|
|
88
|
-
echo "[$(date +%H:%M:%
|
|
88
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] LINKEDIN_KILLSWITCH active. Aborting LinkedIn pipeline."
|
|
89
89
|
echo " Re-auth LinkedIn in harness Chrome, then: python3 ~/social-autoposter/scripts/linkedin_killswitch.py clear"
|
|
90
90
|
exit 0
|
|
91
91
|
fi
|
|
@@ -43,7 +43,7 @@ LOG_DIR="$REPO_DIR/skill/logs"
|
|
|
43
43
|
mkdir -p "$LOG_DIR"
|
|
44
44
|
LOG_FILE="$LOG_DIR/run-reddit-search-$(date +%Y-%m-%d_%H%M%S).log"
|
|
45
45
|
|
|
46
|
-
log() { echo "[$(date +%H:%M:%
|
|
46
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
47
47
|
|
|
48
48
|
log "=== Reddit Search Post Run: $(date) ==="
|
|
49
49
|
|
|
@@ -88,7 +88,7 @@ _parse_post_results() {
|
|
|
88
88
|
# Bug observed 2026-05-08: a leaked "[14:57:04] Post phase: ..." line
|
|
89
89
|
# produced "TOTAL_POSTED + [14:57:04]: syntax error" and `set -e` aborted
|
|
90
90
|
# the script BEFORE the discover lane ran.
|
|
91
|
-
echo "[$(date +%H:%M:%
|
|
91
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Post phase: exit code $rc; counting as failed." >> "$LOG_FILE"
|
|
92
92
|
echo "0 1"
|
|
93
93
|
fi
|
|
94
94
|
}
|
|
@@ -33,7 +33,7 @@ RUN_START_EPOCH=$(date +%s)
|
|
|
33
33
|
# calls added in 5e41d96 (2026-05-10) fall through to macOS /usr/bin/log,
|
|
34
34
|
# which barfs `Unknown subcommand` and ERR-traps the run at exit=64 before
|
|
35
35
|
# the browser lease is ever acquired (silent kill since 2026-05-10).
|
|
36
|
-
log() { echo "[$(date +%H:%M:%
|
|
36
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
37
37
|
|
|
38
38
|
# Diagnostic: log the failing line and command before set -e kills the script.
|
|
39
39
|
# Without this, silent deaths (e.g., Claude exits non-zero inside the $() below)
|
|
@@ -174,7 +174,7 @@ if [ -f "$REPO_DIR/.env" ]; then
|
|
|
174
174
|
set +a
|
|
175
175
|
fi
|
|
176
176
|
|
|
177
|
-
log() { echo "[$(date +%H:%M:%
|
|
177
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
178
178
|
|
|
179
179
|
log "=== Twitter Cycle (batch=$BATCH_ID): $(date) ==="
|
|
180
180
|
log "Logic=D (no-ripen + 1h freshness + 2k_view_cap; experiment concluded 2026-05-31); discover_freshness=${FRESHNESS_HOURS_DISCOVER}h"
|
|
@@ -16,7 +16,7 @@ LOG_DIR="$REPO_DIR/skill/logs"
|
|
|
16
16
|
mkdir -p "$LOG_DIR"
|
|
17
17
|
LOG_FILE="$LOG_DIR/scan-instagram-replies-$(date +%Y-%m-%d_%H%M%S).log"
|
|
18
18
|
|
|
19
|
-
log() { echo "[$(date +%H:%M:%
|
|
19
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_FILE"; }
|
|
20
20
|
log "=== scan-instagram-replies fire: $(date) ==="
|
|
21
21
|
|
|
22
22
|
RUN_START=$(date +%s)
|
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"
|