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 CHANGED
@@ -549,23 +549,28 @@ function installBrowserHarness() {
549
549
  spawnSync(harnessBin, ['--reload'], { stdio: 'inherit' });
550
550
  }
551
551
 
552
- // Contract check: server.py invokes `browser-harness -c <script>`. If an
553
- // offline/rate-limited fetch left a stale checkout that predates the `-c`
554
- // interface, the CLI still "installs" fine but every bh_run returns the
555
- // usage banner and CDP looks "not connected". Verify the installed binary
556
- // actually speaks `-c` (its no-arg usage string mentions it) and fail
557
- // LOUDLY here instead of shipping a silently-broken twitter-harness.
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
- if (!/\b-c\b/.test(usage)) {
562
- console.error(' ERROR: installed browser-harness CLI does not accept `-c` (stale clone).');
563
- console.error(' The twitter-harness MCP will return a usage banner / "CDP not connected".');
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
- console.log(' browser-harness CLI verified (accepts -c).');
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/sdk": "^1.12.0",
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
  }
@@ -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, "-c", script],
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
- f"path = capture_screenshot(quality={int(quality)})\n"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "social-autoposter",
3
- "version": "1.6.28",
3
+ "version": "1.6.30",
4
4
  "description": "Automated social posting pipeline for Reddit, X/Twitter, LinkedIn, and Moltbook. Install as a Claude Code agent skill.",
5
5
  "bin": {
6
6
  "social-autoposter": "bin/cli.js"
@@ -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
- Why this lives outside the cycle: `score_twitter_candidates.py` and the parent
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
- Two passes, both safe to rerun:
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 (every query for a project in the batch was a dud) stay NULL
25
- until the locked shell is extended; those are rare and surface in the
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 db # noqa: E402
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 7; ad-hoc "
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
- conn = db.get_conn()
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
- # Pass A: candidate-join. Covers any attempt that produced >=1 scored tweet.
64
- cur = conn.execute(f"""
65
- UPDATE twitter_search_attempts a
66
- SET search_topic = sub.topic
67
- FROM (
68
- SELECT search_attempt_id, MIN(search_topic) AS topic
69
- FROM twitter_candidates
70
- WHERE search_attempt_id IS NOT NULL
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={'all' if args.all else f'{args.days}d'} "
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
- # Run once per process even if several libs source this file.
258
- [ -n "${_SA_HARNESS_C_CHECKED:-}" ] && return 0
259
- export _SA_HARNESS_C_CHECKED=1
260
-
261
- local src="$HOME/Developer/browser-harness"
262
- local run_py="$src/src/browser_harness/run.py"
263
- local bh="$HOME/.local/bin/browser-harness"
264
-
265
- # Static capability probe — no daemon/Chrome needed. The CLI is an editable
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