social-autoposter 1.6.61 → 1.6.63

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
@@ -705,6 +705,22 @@ function generatePlists() {
705
705
  stdoutLog: `${DEST}/skill/logs/launchd-self-update-stdout.log`,
706
706
  stderrLog: `${DEST}/skill/logs/launchd-self-update-stderr.log`,
707
707
  },
708
+ {
709
+ // On-screen overlay watcher supervisor. The overlay (harness status banner
710
+ // + interactive draft sidebar) only renders WHILE harness_overlay.py watch
711
+ // runs. The supervisor is idempotent (pgrep guard), so a 60s StartInterval
712
+ // is a no-op while the watcher is up and re-spawns it within a minute if it
713
+ // ever dies. RunAtLoad starts it right after install. This is what makes the
714
+ // overlay appear on headless / remote installs (Lane A); the MCP covers the
715
+ // pure-.mcpb lane by calling the same script on draft_cycle / autopilot.
716
+ file: 'com.m13v.social-overlay-watch.plist',
717
+ label: 'com.m13v.social-overlay-watch',
718
+ script: `${DEST}/skill/run-overlay-watch.sh`,
719
+ interval: 60,
720
+ runAtLoad: true,
721
+ stdoutLog: `${DEST}/skill/logs/launchd-overlay-watch-stdout.log`,
722
+ stderrLog: `${DEST}/skill/logs/launchd-overlay-watch-stderr.log`,
723
+ },
708
724
  ];
709
725
 
710
726
  const driver = scheduler.driverFor();
package/mcp/dist/index.js CHANGED
@@ -222,6 +222,32 @@ function cycleProgressMessage(line) {
222
222
  return `Cycle stopped (${m[1]}).`;
223
223
  return null;
224
224
  }
225
+ // Start the twitter-harness on-screen overlay watcher if it isn't already up.
226
+ // The overlay (status banner + interactive draft sidebar) only renders WHILE
227
+ // `harness_overlay.py watch` runs. The supervisor script is idempotent (pgrep
228
+ // guard), so calling this on every draft_cycle / autopilot-enable / show-browser
229
+ // is safe: it spawns at most one detached watcher and is a fast no-op otherwise.
230
+ //
231
+ // We thread SAPS_PYTHON (the owned uv runtime, so the watcher resolves a
232
+ // playwright-capable interpreter on Lane B / .mcpb installs that have no system
233
+ // python) and SAPS_LOG_DIR (the materialized repo's skill/logs, so the watcher
234
+ // reads the SAME cycle logs this run writes to decide busy/idle). Fire-and-forget:
235
+ // a failure here must never break the cycle it's decorating.
236
+ async function ensureOverlayWatch() {
237
+ try {
238
+ await run("bash", ["skill/run-overlay-watch.sh"], {
239
+ timeoutMs: 20_000,
240
+ env: {
241
+ SAPS_PYTHON: resolvePython(),
242
+ SAPS_LOG_DIR: path.join(repoDir(), "skill", "logs"),
243
+ TWITTER_CDP_URL: process.env.TWITTER_CDP_URL || "http://127.0.0.1:9555",
244
+ },
245
+ });
246
+ }
247
+ catch {
248
+ /* best-effort: the overlay is a nicety, never a blocker */
249
+ }
250
+ }
225
251
  async function produceDrafts(project, onProgress) {
226
252
  // Run the real pipeline in DRAFT_ONLY mode: scan -> score -> draft -> link-gen,
227
253
  // then STOP before posting. The script prints `DRAFT_ONLY_PLAN=<path>` and
@@ -240,6 +266,9 @@ async function produceDrafts(project, onProgress) {
240
266
  };
241
267
  if (project)
242
268
  env.SAPS_FORCE_PROJECT = project;
269
+ // Bring the on-screen overlay up alongside the live harness window so the user
270
+ // watching the scan/scrape sees status + queued drafts. Idempotent + detached.
271
+ await ensureOverlayWatch();
243
272
  let step = 0;
244
273
  let lastMsg = "";
245
274
  // ONE predictable, host-independent place to watch a draft_cycle run, so any
@@ -427,6 +456,30 @@ server.registerPrompt("getting-started", {
427
456
  },
428
457
  ],
429
458
  }));
459
+ // Instruction (NOT a script) the agent follows to research the product website
460
+ // after the profile scan. The agent uses ITS OWN browser/fetch tools — the MCP
461
+ // ships no scraper. The goal is to fill the PRODUCT half of the config (what it
462
+ // does, how it's different, who it's for, the CTA link, claims to avoid) from the
463
+ // site itself, written in the user's voice captured by the profile scan.
464
+ const WEBSITE_RESEARCH_INSTRUCTIONS = "PRODUCT RESEARCH (do this before saving the product fields):\n" +
465
+ "1. Ask the user for the URL of the product/website they want to market on X.\n" +
466
+ "2. Visit it with your OWN browser/fetch tools (no scraper is provided) and read " +
467
+ "AT LEAST 5 pages if the site has them — follow the internal nav/footer links. " +
468
+ "Prioritize: homepage, pricing, features/product, about, docs or changelog or blog, " +
469
+ "FAQ, customers/testimonials/case-studies. Read as many as you can find (5+ is the " +
470
+ "floor, not the cap) to learn the product deeply.\n" +
471
+ "3. From what you actually read, extract the PRODUCT fields: `description` (what it " +
472
+ "does, concretely), `differentiator` (how it's genuinely different from alternatives), " +
473
+ "`icp` (who it's for — cross-check against who the user engages with on X), " +
474
+ "`get_started_link` (the primary signup/CTA URL), and `content_guardrails` (claims, " +
475
+ "competitors, or wording the site avoids — never overclaim beyond the site).\n" +
476
+ "4. WRITE these fields in the USER'S voice from the profile scan (their phrasing, " +
477
+ "register, vibe) while keeping every product CLAIM factual to the site. Don't invent " +
478
+ "features, metrics, or guarantees the site doesn't state.\n" +
479
+ "5. Show the user your draft of the product fields for confirmation, then call setup " +
480
+ "with name + the product fields (plus the voice/search_topics from the profile scan) " +
481
+ "to save. If the site is thin or unreachable, tell the user and ask them to fill the " +
482
+ "gaps rather than guessing.";
430
483
  // ---- setup: per-project config (the "brain": project, website, voice) -----
431
484
  // Run this FIRST. The action tools refuse until at least one project is ready.
432
485
  // You can set up MULTIPLE products and fill each project's fields INCREMENTALLY
@@ -437,9 +490,11 @@ tool("setup", {
437
490
  title: "Set up a project",
438
491
  description: "Run this FIRST, before any drafting or autopilot. Two jobs:\n" +
439
492
  "1) Configure a project this install posts for: its website, what it does (description), who " +
440
- "to target (icp), and brand voice. Set up MULTIPLE products (call once per product, identified " +
441
- "by name); fill a project's fields INCREMENTALLY across several calls pass whatever you have, " +
442
- "it merges and tells you what's still missing.\n" +
493
+ "to target (icp), and brand voice. To fill the PRODUCT fields, get the product URL and visit " +
494
+ "it with your own browser/fetch tools read 5+ pages (home, pricing, features, about, docs/" +
495
+ "blog, FAQ) to learn it deeply, rather than guessing from the name. Set up MULTIPLE products " +
496
+ "(call once per product, identified by name); fill a project's fields INCREMENTALLY across " +
497
+ "several calls — pass whatever you have, it merges and tells you what's still missing.\n" +
443
498
  "2) Connect X/Twitter (action:'connect_x'): the autoposter posts through its OWN managed Chrome, " +
444
499
  "which needs your logged-in x.com session. This imports x.com/twitter.com cookies from your " +
445
500
  "everyday browser (Chrome/Arc/Brave/Edge, auto-detected) into that browser — nothing else is " +
@@ -591,12 +646,17 @@ tool("setup", {
591
646
  posts: scan.posts,
592
647
  comments: scan.comments,
593
648
  grounding_instructions: scan.grounding_instructions,
594
- next_step: "Read the bio, posts, and comments as GROUND TRUTH. Following grounding_instructions, " +
595
- "extract: (1) their profession/identity, (2) their voice & vibe (tone, phrasing, casing, " +
596
- "tics), (3) 2-4 verbatim golden-rule example replies, (4) a phrase bank + things they " +
597
- "avoid, (5) their icp, (6) recurring themes -> search_topics. Show the user your read " +
598
- "('here's the voice/topics I picked up, sound like you?'), then call setup with the " +
599
- "confirmed name/voice/icp/differentiator/search_topics/content_guardrails to save.",
649
+ website_research_instructions: WEBSITE_RESEARCH_INSTRUCTIONS,
650
+ next_step: "TWO steps, in order. FIRST (voice, from this scan): read the bio, posts, and comments " +
651
+ "as GROUND TRUTH and, per grounding_instructions, extract their profession/identity, " +
652
+ "voice & vibe (tone, phrasing, casing, tics), 2-4 verbatim golden-rule example replies, " +
653
+ "a phrase bank + things they avoid, their icp, and recurring themes -> search_topics. " +
654
+ "SECOND (product, from their website): then follow website_research_instructions — ask " +
655
+ "the user for the product URL and read 5+ of its pages to fill description, " +
656
+ "differentiator, icp, get_started_link, and content_guardrails, written in the voice you " +
657
+ "just captured. Show the user your combined read ('here's the voice/topics from your " +
658
+ "profile and what I learned from your site — accurate?'), then call setup with the " +
659
+ "confirmed fields to save.",
600
660
  });
601
661
  }
602
662
  // Status / discovery mode: no project name supplied, or explicitly asked.
@@ -940,6 +1000,9 @@ tool("autopilot", {
940
1000
  });
941
1001
  }
942
1002
  if (action === "enable") {
1003
+ // Bring up the on-screen overlay watcher so background autopilot cycles
1004
+ // still paint the harness status/sidebar. Idempotent + detached.
1005
+ await ensureOverlayWatch();
943
1006
  // 1) Cycle plist. Write one pointing at the self-update guard ONLY if no
944
1007
  // plist exists yet; never overwrite a hand-tuned/dev plist.
945
1008
  const createdCycle = ensurePlist(TWITTER_AUTOPILOT_PLIST, plistXml({
@@ -1449,6 +1512,10 @@ tool("show_browser_to_user", {
1449
1512
  }
1450
1513
  return jsonContent({ ok: true, brought_to_front: true, port: res.port });
1451
1514
  }
1515
+ // If the user is about to watch the live browser, make sure the on-screen
1516
+ // overlay watcher is up too so the harness window carries status + drafts.
1517
+ if (action === "start")
1518
+ await ensureOverlayWatch();
1452
1519
  const ensured = await screencast.ensure(typeof args?.port === "number" ? args.port : undefined);
1453
1520
  if (!ensured.ok) {
1454
1521
  const message = ensured.error === "no_browser"
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.6.60",
3
- "installedAt": "2026-06-05T02:22:33.297Z"
2
+ "version": "1.6.63",
3
+ "installedAt": "2026-06-05T03:14:19.604Z"
4
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "social-autoposter",
3
- "version": "1.6.61",
3
+ "version": "1.6.63",
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"
@@ -30,7 +30,15 @@ PER_SCRIPT_CAP_SEC = {
30
30
  # 2026-04-27: extend 120 min cap to remaining stats / audit / link-edit jobs.
31
31
  # 45 min was killing audit-twitter mid-run and starving link-edit-* of time
32
32
  # to actually post replies + verify SEO deploys.
33
- ("stats.sh", "twitter"): 120 * 60,
33
+ # 2026-06-04: raised 120 -> 180 min. stats_twitter's total fxtwitter
34
+ # working set (posts + thread_top_replies + twitter replies + parent
35
+ # threads, ~1600-1900 polls @ ~1 req/s) crept past 7200s (~7400-7500s)
36
+ # as the post corpus grew, so every 6h run was SIGKILLed at the cap
37
+ # before stamping the final lanes -> the unstamped tail stayed stale ->
38
+ # the next run re-polled the same backlog and died again (death spiral).
39
+ # The job completes cleanly in <2.1h when not killed; 180 min gives
40
+ # durable headroom and the cron fires every 6h so there is no overlap.
41
+ ("stats.sh", "twitter"): 180 * 60,
34
42
  ("stats.sh", "linkedin"): 120 * 60,
35
43
  ("stats.sh", "moltbook"): 120 * 60,
36
44
  ("audit.sh", None): 120 * 60,
@@ -0,0 +1,79 @@
1
+ #!/bin/bash
2
+ # Idempotent supervisor for the twitter-harness on-screen overlay watcher.
3
+ #
4
+ # WHAT: keeps exactly ONE `harness_overlay.py watch` process alive. That watcher
5
+ # injects the status overlay + interactive draft sidebar into the twitter-harness
6
+ # Chrome window so a human watching the harness sees what the pipeline is doing
7
+ # and can preview queued drafts.
8
+ #
9
+ # WHY a supervisor: the overlay only renders WHILE the watch process runs. It was
10
+ # previously a manual, local-only process, so it never appeared on headless /
11
+ # remote installs. This script makes it self-starting from BOTH install lanes:
12
+ # - Lane A (npm/cli): launchd job `com.m13v.social-overlay-watch` (StartInterval
13
+ # 60 + RunAtLoad) re-invokes this script every minute; the pgrep guard makes
14
+ # re-invocation a no-op while the watcher is already up.
15
+ # - Lane B (.mcpb / pure MCP): the MCP calls this script on draft_cycle /
16
+ # autopilot-enable / show_browser_to_user, threading SAPS_PYTHON + SAPS_LOG_DIR.
17
+ #
18
+ # IDEMPOTENT: safe to call on a 60s timer. If a watcher is already running it
19
+ # exits 0 immediately. Otherwise it spawns one detached (nohup, own session) and
20
+ # returns; the spawned watcher then runs until the machine/MCP tears it down.
21
+ #
22
+ # This script is intentionally NOT locked: the overlay UX is expected to evolve.
23
+
24
+ set -u
25
+
26
+ # --- resolve the repo this script lives in (works from launchd + MCP cwd) -----
27
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
29
+ OVERLAY_PY="${REPO_DIR}/scripts/harness_overlay.py"
30
+
31
+ if [ ! -f "${OVERLAY_PY}" ]; then
32
+ echo "[overlay-watch] harness_overlay.py not found at ${OVERLAY_PY}; nothing to do" >&2
33
+ exit 0
34
+ fi
35
+
36
+ # --- idempotency guard: at most one watcher, ever ----------------------------
37
+ # Match the full `harness_overlay.py watch` invocation (NOT a bare `grep -r`, so
38
+ # this never trips the BSD-grep-on-FIFO hang noted in the repo CLAUDE.md).
39
+ if pgrep -f "harness_overlay.py watch" >/dev/null 2>&1; then
40
+ exit 0
41
+ fi
42
+
43
+ # --- resolve a python interpreter --------------------------------------------
44
+ # harness_overlay.py self-heals to a playwright-capable interpreter on its own
45
+ # (see _ensure_playwright_interpreter), so any python3 that exists is fine to
46
+ # launch with. Prefer the MCP-threaded SAPS_PYTHON (owned uv runtime), then the
47
+ # usual suspects.
48
+ PYBIN=""
49
+ for _cand in \
50
+ "${SAPS_PYTHON:-}" \
51
+ "/opt/homebrew/bin/python3.11" \
52
+ "/usr/bin/python3" \
53
+ "/opt/homebrew/bin/python3"
54
+ do
55
+ if [ -n "${_cand}" ] && [ -x "${_cand}" ]; then PYBIN="${_cand}"; break; fi
56
+ done
57
+ if [ -z "${PYBIN}" ]; then
58
+ PYBIN="$(command -v python3 2>/dev/null || true)"
59
+ fi
60
+ if [ -z "${PYBIN}" ]; then
61
+ echo "[overlay-watch] no python3 found; cannot start overlay watcher" >&2
62
+ exit 0
63
+ fi
64
+
65
+ # --- env the watcher needs ---------------------------------------------------
66
+ # SAPS_LOG_DIR: where harness_overlay.py reads cycle logs to decide busy/idle.
67
+ # Default to this repo's skill/logs (MCP overrides it to the materialized repo).
68
+ export SAPS_LOG_DIR="${SAPS_LOG_DIR:-${REPO_DIR}/skill/logs}"
69
+ # CDP target for the twitter harness Chrome (honor BYO-Chrome installs).
70
+ export TWITTER_CDP_URL="${TWITTER_CDP_URL:-http://127.0.0.1:9555}"
71
+ mkdir -p "${SAPS_LOG_DIR}" 2>/dev/null || true
72
+ WATCH_LOG="${SAPS_LOG_DIR}/overlay-watch.log"
73
+
74
+ # --- spawn detached ----------------------------------------------------------
75
+ cd "${REPO_DIR}" || exit 0
76
+ echo "[overlay-watch] $(date '+%Y-%m-%d %H:%M:%S') starting watcher py=${PYBIN} cdp=${TWITTER_CDP_URL} log=${SAPS_LOG_DIR}" >>"${WATCH_LOG}" 2>&1
77
+ nohup "${PYBIN}" "${OVERLAY_PY}" watch >>"${WATCH_LOG}" 2>&1 &
78
+ disown 2>/dev/null || true
79
+ exit 0