social-autoposter 1.6.28 → 1.6.30
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/bin/cli.js +15 -10
- package/mcp/package.json +8 -3
- package/mcp-servers/browser-harness/__pycache__/server.cpython-314.pyc +0 -0
- package/mcp-servers/browser-harness/server.py +15 -2
- package/package.json +1 -1
- package/scripts/backfill_twitter_attempts_topic.py +21 -58
- package/skill/lib/twitter-backend.sh +9 -33
package/bin/cli.js
CHANGED
|
@@ -549,23 +549,28 @@ function installBrowserHarness() {
|
|
|
549
549
|
spawnSync(harnessBin, ['--reload'], { stdio: 'inherit' });
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
-
// Contract check: server.py
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
//
|
|
557
|
-
//
|
|
552
|
+
// Contract check: server.py pipes the script to browser-harness via stdin.
|
|
553
|
+
// Upstream supports two banner shapes — older builds advertise `-c <script>`
|
|
554
|
+
// and newer builds advertise the `<<'PY' ... PY` heredoc form. Either is
|
|
555
|
+
// fine for our use case (we pass the script via stdin, which both accept).
|
|
556
|
+
// Fail loudly if the installed binary advertises NEITHER, which usually
|
|
557
|
+
// means an offline/partial clone left a broken CLI that will silently make
|
|
558
|
+
// every bh_run look like "CDP not connected".
|
|
558
559
|
if (fs.existsSync(harnessBin)) {
|
|
559
560
|
const probe = spawnSync(harnessBin, [], { stdio: 'pipe', encoding: 'utf8', timeout: 15000 });
|
|
560
561
|
const usage = `${probe.stdout || ''}${probe.stderr || ''}`;
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
562
|
+
const supportsDashC = /\b-c\b/.test(usage);
|
|
563
|
+
const supportsStdin = /<<'PY'|<<"PY"|<<PY\b/.test(usage);
|
|
564
|
+
if (!supportsDashC && !supportsStdin) {
|
|
565
|
+
console.error(' ERROR: installed browser-harness CLI advertises neither `-c` nor a stdin heredoc.');
|
|
566
|
+
console.error(' This usually means a partial/corrupted install. The twitter-harness MCP will');
|
|
567
|
+
console.error(' return a usage banner / "CDP not connected" on every call.');
|
|
564
568
|
console.error(` Fix: rm -rf ${harnessDir} && re-run \`social-autoposter init\` while online,`);
|
|
565
569
|
console.error(' or manually: git clone https://github.com/browser-use/browser-harness ' + harnessDir +
|
|
566
570
|
' && ' + uvBin + ' tool install --force -e ' + harnessDir);
|
|
567
571
|
} else {
|
|
568
|
-
|
|
572
|
+
const shape = supportsStdin ? 'stdin heredoc' : '-c flag';
|
|
573
|
+
console.log(` browser-harness CLI verified (${shape}).`);
|
|
569
574
|
}
|
|
570
575
|
}
|
|
571
576
|
}
|
package/mcp/package.json
CHANGED
|
@@ -8,18 +8,23 @@
|
|
|
8
8
|
"social-autoposter-mcp": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
11
|
+
"build": "vite build && tsc",
|
|
12
|
+
"build:panel": "vite build",
|
|
13
|
+
"build:server": "tsc",
|
|
12
14
|
"start": "node dist/index.js",
|
|
13
15
|
"dev": "tsc --watch",
|
|
14
16
|
"install-clients": "node install.mjs",
|
|
15
17
|
"uninstall-clients": "node install.mjs --uninstall"
|
|
16
18
|
},
|
|
17
19
|
"dependencies": {
|
|
18
|
-
"@modelcontextprotocol/
|
|
20
|
+
"@modelcontextprotocol/ext-apps": "^1.7.3",
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
19
22
|
"zod": "^3.23.8"
|
|
20
23
|
},
|
|
21
24
|
"devDependencies": {
|
|
22
25
|
"@types/node": "^22.0.0",
|
|
23
|
-
"typescript": "^5.5.0"
|
|
26
|
+
"typescript": "^5.5.0",
|
|
27
|
+
"vite": "^6.0.0",
|
|
28
|
+
"vite-plugin-singlefile": "^2.0.0"
|
|
24
29
|
}
|
|
25
30
|
}
|
|
Binary file
|
|
@@ -407,9 +407,14 @@ def _run_harness(script: str, timeout: int = EXEC_TIMEOUT_SEC) -> dict:
|
|
|
407
407
|
# Make sure ~/.local/bin is on PATH (uv tools live there).
|
|
408
408
|
env["PATH"] = f"{Path.home()}/.local/bin:" + env.get("PATH", "")
|
|
409
409
|
|
|
410
|
+
# Upstream browser-harness dropped the `-c <script>` flag and now reads the
|
|
411
|
+
# script from stdin only (heredoc style). Pass via stdin so we work against
|
|
412
|
+
# current upstream; the old `-c` form returns the usage banner and exits 1,
|
|
413
|
+
# which used to surface as "CDP not connected" on every fresh install.
|
|
410
414
|
try:
|
|
411
415
|
proc = subprocess.run(
|
|
412
|
-
[BROWSER_HARNESS_BIN
|
|
416
|
+
[BROWSER_HARNESS_BIN],
|
|
417
|
+
input=script,
|
|
413
418
|
env=env,
|
|
414
419
|
capture_output=True,
|
|
415
420
|
text=True,
|
|
@@ -677,12 +682,20 @@ def bh_screenshot(quality: int = 50) -> str:
|
|
|
677
682
|
|
|
678
683
|
Returns JSON with the file path and basic page info. Use bh_run for any
|
|
679
684
|
workflow that needs to keep state across multiple steps.
|
|
685
|
+
|
|
686
|
+
The `quality` parameter is accepted for back-compat but is ignored — the
|
|
687
|
+
current upstream `capture_screenshot()` signature is (path, full, max_dim)
|
|
688
|
+
and does not expose a JPEG-quality knob. Older callers (and the MCP tool
|
|
689
|
+
schema) still pass it; we just don't forward it.
|
|
680
690
|
"""
|
|
691
|
+
# Avoid the unused-variable lint and keep `quality` part of the MCP
|
|
692
|
+
# contract: a sanity-cap so a bad caller can't pass arbitrary types.
|
|
693
|
+
_ = int(quality)
|
|
681
694
|
script = (
|
|
682
695
|
"import json, time, os\n"
|
|
683
696
|
"ensure_real_tab()\n"
|
|
684
697
|
"info = page_info()\n"
|
|
685
|
-
|
|
698
|
+
"path = capture_screenshot()\n"
|
|
686
699
|
"print(json.dumps({\"screenshot\": str(path), \"page\": info}))\n"
|
|
687
700
|
)
|
|
688
701
|
result = _run_harness(script)
|
package/package.json
CHANGED
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
Periodic UPDATE that fills `twitter_search_attempts.search_topic` from the
|
|
5
5
|
adjacent `twitter_candidates` rows once a scoring cycle finishes writing them.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
HTTP-only (no DATABASE_URL): the two backfill passes run server-side behind
|
|
8
|
+
`POST /api/v1/twitter-search-attempts/backfill-topic`. This script is now a
|
|
9
|
+
thin trigger that POSTs the window and prints the rows-updated counts. The
|
|
10
|
+
published package carries no direct-DB dependency.
|
|
11
|
+
|
|
12
|
+
Why this backfill exists at all: `score_twitter_candidates.py` and the parent
|
|
8
13
|
`skill/run-twitter-cycle.sh` are both `chflags uchg` locked, and the canonical
|
|
9
14
|
SCAN_SCHEMA in the shell does not yet carry `search_topic` on each entry of
|
|
10
15
|
`queries_used` (so `log_twitter_search_attempts.py` cannot stamp it at INSERT
|
|
@@ -12,7 +17,7 @@ time). Until those locked files are extended, we backfill from the candidate
|
|
|
12
17
|
side, which DOES know the topic (set by `pick_search_topic.py` -> stamped onto
|
|
13
18
|
twitter_candidates.search_topic + search_attempt_id).
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
The endpoint runs two passes, both safe to rerun:
|
|
16
21
|
|
|
17
22
|
A) Direct join via search_attempt_id (covers non-dud attempts that produced
|
|
18
23
|
at least one candidate).
|
|
@@ -21,9 +26,8 @@ Two passes, both safe to rerun:
|
|
|
21
26
|
in the same cycle DID return candidates and therefore know the topic.
|
|
22
27
|
Skips ambiguous batches (more than one distinct topic) to avoid noise.
|
|
23
28
|
|
|
24
|
-
Fully-dud cycles
|
|
25
|
-
|
|
26
|
-
dashboard as a single "(no topic)" bucket per project.
|
|
29
|
+
Fully-dud cycles stay NULL until the locked shell is extended; rare, and they
|
|
30
|
+
surface in the dashboard as a single "(no topic)" bucket per project.
|
|
27
31
|
|
|
28
32
|
Run from cron (launchd `com.m13v.social-twitter-attempt-topic-backfill`,
|
|
29
33
|
every 5 min) or directly:
|
|
@@ -38,79 +42,38 @@ import sys
|
|
|
38
42
|
import time
|
|
39
43
|
|
|
40
44
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
41
|
-
import
|
|
45
|
+
from http_api import api_post, load_env # noqa: E402
|
|
42
46
|
|
|
43
47
|
|
|
44
48
|
def main():
|
|
45
49
|
p = argparse.ArgumentParser()
|
|
46
50
|
p.add_argument("--days", type=int, default=7,
|
|
47
51
|
help="Only backfill rows where ran_at >= NOW() - INTERVAL "
|
|
48
|
-
"'N days' (default 7). Cron path uses
|
|
52
|
+
"'N days' (default 7). Cron path uses 14; ad-hoc "
|
|
49
53
|
"operators can widen.")
|
|
50
54
|
p.add_argument("--all", action="store_true",
|
|
51
55
|
help="Backfill the entire table; ignores --days.")
|
|
52
56
|
args = p.parse_args()
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
where_recent = (
|
|
56
|
-
""
|
|
57
|
-
if args.all
|
|
58
|
-
else f"AND a.ran_at >= NOW() - INTERVAL '{int(args.days)} days'"
|
|
59
|
-
)
|
|
60
|
-
|
|
58
|
+
load_env()
|
|
61
59
|
t0 = time.time()
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
AND search_topic IS NOT NULL
|
|
72
|
-
AND search_topic <> ''
|
|
73
|
-
GROUP BY search_attempt_id
|
|
74
|
-
) sub
|
|
75
|
-
WHERE a.id = sub.search_attempt_id
|
|
76
|
-
AND a.search_topic IS NULL
|
|
77
|
-
{where_recent}
|
|
78
|
-
""")
|
|
79
|
-
a_rows = cur.rowcount
|
|
80
|
-
conn.commit()
|
|
81
|
-
|
|
82
|
-
# Pass B: batch-fanout. Covers dud attempts whose sibling non-dud attempts
|
|
83
|
-
# agree on a single topic. Ambiguous batches stay NULL.
|
|
84
|
-
cur = conn.execute(f"""
|
|
85
|
-
UPDATE twitter_search_attempts a
|
|
86
|
-
SET search_topic = sub.topic
|
|
87
|
-
FROM (
|
|
88
|
-
SELECT batch_id, project_name,
|
|
89
|
-
MIN(search_topic) AS topic,
|
|
90
|
-
COUNT(DISTINCT search_topic) AS topic_n
|
|
91
|
-
FROM twitter_search_attempts
|
|
92
|
-
WHERE batch_id IS NOT NULL
|
|
93
|
-
AND project_name IS NOT NULL
|
|
94
|
-
AND search_topic IS NOT NULL
|
|
95
|
-
GROUP BY batch_id, project_name
|
|
96
|
-
) sub
|
|
97
|
-
WHERE a.batch_id = sub.batch_id
|
|
98
|
-
AND a.project_name = sub.project_name
|
|
99
|
-
AND a.search_topic IS NULL
|
|
100
|
-
AND sub.topic_n = 1
|
|
101
|
-
{where_recent}
|
|
102
|
-
""")
|
|
103
|
-
b_rows = cur.rowcount
|
|
104
|
-
conn.commit()
|
|
61
|
+
resp = api_post(
|
|
62
|
+
"/api/v1/twitter-search-attempts/backfill-topic",
|
|
63
|
+
{"days": int(args.days), "all": bool(args.all)},
|
|
64
|
+
)
|
|
65
|
+
data = resp.get("data") or {}
|
|
66
|
+
a_rows = data.get("pass_a", 0)
|
|
67
|
+
b_rows = data.get("pass_b", 0)
|
|
68
|
+
window = data.get("window", "all" if args.all else f"{args.days}d")
|
|
105
69
|
|
|
106
70
|
elapsed = time.time() - t0
|
|
107
71
|
print(
|
|
108
72
|
f"backfill_twitter_attempts_topic: "
|
|
109
|
-
f"pass_a={a_rows} pass_b={b_rows} window={
|
|
73
|
+
f"pass_a={a_rows} pass_b={b_rows} window={window} "
|
|
110
74
|
f"elapsed={elapsed:.2f}s",
|
|
111
75
|
file=sys.stderr,
|
|
112
76
|
)
|
|
113
|
-
conn.close()
|
|
114
77
|
return 0
|
|
115
78
|
|
|
116
79
|
|
|
@@ -254,38 +254,14 @@ _sa_resolve_uv() {
|
|
|
254
254
|
return 1
|
|
255
255
|
}
|
|
256
256
|
ensure_harness_c_support() {
|
|
257
|
-
#
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
local
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
# (`uv tool install -e`) install of $src, so its run.py is the source of
|
|
267
|
-
# truth for whether `-c` is recognized.
|
|
268
|
-
if [ -f "$run_py" ] && grep -q '"-c"' "$run_py"; then
|
|
269
|
-
return 0
|
|
270
|
-
fi
|
|
271
|
-
|
|
272
|
-
_sa_harness_log "[harness] browser-harness checkout missing/stale (no -c support) -> self-healing via git + uv..."
|
|
273
|
-
if [ -d "$src/.git" ]; then
|
|
274
|
-
git -C "$src" fetch --depth 1 origin HEAD >/dev/null 2>&1 \
|
|
275
|
-
&& git -C "$src" reset --hard FETCH_HEAD >/dev/null 2>&1
|
|
276
|
-
fi
|
|
277
|
-
local uv; uv="$(_sa_resolve_uv || true)"
|
|
278
|
-
if [ -n "$uv" ] && [ -d "$src" ]; then
|
|
279
|
-
"$uv" tool install --force -e "$src" >/dev/null 2>&1 || true
|
|
280
|
-
fi
|
|
281
|
-
[ -x "$bh" ] && "$bh" --reload >/dev/null 2>&1 || true
|
|
282
|
-
|
|
283
|
-
if [ -f "$run_py" ] && grep -q '"-c"' "$run_py"; then
|
|
284
|
-
_sa_harness_log "[harness] self-heal OK -> browser-harness -c is supported now"
|
|
285
|
-
return 0
|
|
286
|
-
fi
|
|
287
|
-
_sa_harness_log "[harness] ERROR: browser-harness still lacks -c after self-heal."
|
|
288
|
-
_sa_harness_log "[harness] FIX: run 'social-autoposter update' (re-clones/refreshes $src + reinstalls the CLI). The -c flag is CORRECT; browser-harness has NO stdin mode, so do NOT rewrite the cycle to a stdin form."
|
|
289
|
-
return 1
|
|
257
|
+
# Retired 2026-06-02. Upstream browser-harness removed `-c` in favor of
|
|
258
|
+
# stdin-heredoc (commits after merge-base 0e679e2); our server.py wrapper
|
|
259
|
+
# now passes scripts via stdin (input=script) so the CLI shape doesn't
|
|
260
|
+
# need any pre-flight probing. The old gate grepped run.py for `"-c"`
|
|
261
|
+
# which always fails against current upstream, and its "self-heal" was a
|
|
262
|
+
# `git reset --hard FETCH_HEAD` on ~/Developer/browser-harness that
|
|
263
|
+
# would clobber local commits AND not actually re-add `-c`. Keep the
|
|
264
|
+
# name + no-op return so older sourced contexts that call it don't break.
|
|
265
|
+
return 0
|
|
290
266
|
}
|
|
291
267
|
ensure_harness_c_support || true
|