social-autoposter 1.6.41 → 1.6.43

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "social-autoposter",
3
- "version": "1.6.41",
3
+ "version": "1.6.43",
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"
@@ -529,6 +529,35 @@ def _wait_for_reply_textbox(page, total_timeout_ms=45000):
529
529
  return None
530
530
 
531
531
 
532
+ # Post-action interstitials X shows AFTER a successful reply (e.g. the
533
+ # "Unlock more on X" graduated-access sheet). They don't block the post that
534
+ # triggered them, but the sheet stays up and overlays the composer on the NEXT
535
+ # reply in a batch -> spurious reply_box_not_found for posts 2..N. We dismiss
536
+ # them deterministically before looking for the reply box. Targeted by the
537
+ # sheet's CTA label so we never touch a real compose/confirm dialog (those have
538
+ # no "Got it"); best-effort, fast, never raises.
539
+ _OVERLAY_DISMISS_LABELS = ("Got it", "Dismiss")
540
+
541
+
542
+ def _dismiss_known_overlays(page) -> bool:
543
+ """Click-dismiss any known X nudge sheet currently covering the page.
544
+
545
+ Returns True if something was dismissed. Safe to call on every reply: it is
546
+ a no-op when no known overlay is present and swallows all errors."""
547
+ for label in _OVERLAY_DISMISS_LABELS:
548
+ try:
549
+ btn = page.get_by_role("button", name=label, exact=True).first
550
+ if btn.count() > 0 and btn.is_visible():
551
+ btn.click(timeout=2000)
552
+ page.wait_for_timeout(800)
553
+ print(f"[overlay] dismissed known interstitial via '{label}' button",
554
+ file=sys.stderr)
555
+ return True
556
+ except Exception:
557
+ pass
558
+ return False
559
+
560
+
532
561
  def _dump_reply_failure_diag(page, tweet_url):
533
562
  """Dump screenshot + DOM state on reply_box_not_found. Returns a diag dict."""
534
563
  import time as _t
@@ -812,6 +841,12 @@ def reply_to_tweet(tweet_url, text, apply_campaigns=True):
812
841
  tweet_not_found = True
813
842
  break
814
843
 
844
+ # A nudge sheet left over from the previous reply in this batch
845
+ # (e.g. "Unlock more on X") can sit on top of the composer and
846
+ # mask tweetTextarea_0. Clear it first so the wait below sees the
847
+ # real reply box instead of failing reply_box_not_found.
848
+ _dismiss_known_overlays(page)
849
+
815
850
  reply_box = _wait_for_reply_textbox(page, total_timeout_ms=45000)
816
851
  if reply_box:
817
852
  break
@@ -858,6 +858,11 @@ def main() -> int:
858
858
  ap = argparse.ArgumentParser()
859
859
  ap.add_argument("--plan", required=True,
860
860
  help="Path to the plan JSON file (read-only here)")
861
+ ap.add_argument("--post-unapproved", action="store_true",
862
+ help="Post candidates even when the plan marks them "
863
+ "approved=false. The MCP review path already filters to "
864
+ "approved-only, and autopilot/legacy plans omit the key; "
865
+ "this is the explicit override for an intentional direct run.")
861
866
  args = ap.parse_args()
862
867
 
863
868
  plan_path = Path(args.plan)
@@ -909,6 +914,25 @@ def main() -> int:
909
914
  fail_reasons: dict[str, int] = {}
910
915
  skip_reasons: dict[str, int] = {}
911
916
 
917
+ # Approval gate. A plan that went through the MCP review carries an
918
+ # `approved` flag per candidate (set in mcp/dist/index.js). Honor it here so
919
+ # a DIRECT `--plan` run — bypassing the elicitation form — can't publish
920
+ # drafts the user never ticked. Plans that never had review (autopilot,
921
+ # legacy) omit the key entirely and pass through untouched. Override with
922
+ # --post-unapproved.
923
+ if not args.post_unapproved:
924
+ _kept = []
925
+ for c in candidates:
926
+ if "approved" in c and not c.get("approved"):
927
+ skipped += 1
928
+ skip_reasons["not_approved"] = skip_reasons.get("not_approved", 0) + 1
929
+ else:
930
+ _kept.append(c)
931
+ if skip_reasons.get("not_approved"):
932
+ print(f"[post] {skip_reasons['not_approved']} candidate(s) skipped: not "
933
+ f"approved in plan (pass --post-unapproved to override)", flush=True)
934
+ candidates = _kept
935
+
912
936
  for c in candidates:
913
937
  try:
914
938
  outcome, reason = post_one(c, picker_assignment=picker_assignment)
package/setup/SKILL.md CHANGED
@@ -1,271 +1,93 @@
1
1
  ---
2
2
  name: social-autoposter-setup
3
- description: "Set up social-autoposter for a new user. Interactive wizard that installs via npm, creates the database, configures accounts, verifies browser logins, and optionally sets up scheduled automation. Use when: 'set up social autoposter', 'install social autoposter', 'configure social posting'."
3
+ description: "Set up social-autoposter for a new user. Configures the product (website, ICP, voice, search topics), seeds search topics into the backend, connects X/Twitter (auto-detecting the real handle), and verifies with a draft cycle. Use when: 'set up social autoposter', 'install social autoposter', 'configure social posting'."
4
4
  ---
5
5
 
6
6
  # Social Autoposter Setup
7
7
 
8
- Interactive setup wizard for social-autoposter. Walk the user through configuration step by step.
8
+ Set up social-autoposter for a new user. Walk them through it conversationally don't dump a form.
9
9
 
10
- ## When to use
10
+ ## Architecture (read this first)
11
11
 
12
- - First-time setup of social-autoposter
13
- - Reconfiguring accounts or adding new platforms
14
- - Troubleshooting a broken setup
12
+ - **Config**: `~/social-autoposter/config.json` `projects[]` (what to post about) and `accounts` (where to post).
13
+ - **Data + stats**: a backend HTTP API (`https://s4l.ai`), scoped by a **stable per-install identity** auto-created in `identity.json`. There is **NO local Postgres and no `DATABASE_URL`** to configure — that was the old architecture; ignore any reference to psycopg2 / `SELECT ... FROM posts`.
14
+ - **Search topics**: the X cycle's search queries live in the DB table `project_search_topics`, **seeded from each project's `search_topics`** at setup. A project with no seeded topics has nothing to scan and the draft cycle returns empty — so topics are required.
15
15
 
16
- ## Prerequisites
16
+ ## Which path to use
17
17
 
18
- - Node.js 16+ (for `npx`)
19
- - Python 3.9+ with `pip3` for running helper scripts
20
- - A browser automation tool (Playwright MCP, Selenium, etc.) for platform login verification
18
+ - **If the social-autoposter MCP is connected** (you can see the tools `setup`, `draft_cycle`, `autopilot`, `get_stats`): use the MCP tools. They write config, **seed topics into the DB**, and **auto-detect the X handle** for you. Do NOT hand-edit `config.json`. This is the primary path — follow "MCP path" below.
19
+ - **If only the CLI/skill is installed** (no MCP tools): use the "CLI fallback" at the end.
21
20
 
22
21
  ---
23
22
 
24
- ## Setup Flow
23
+ ## MCP path (primary)
25
24
 
26
- Run these steps in order. Ask the user for input at each step. Don't skip ahead.
25
+ ### Step 1: Interview the user, one question at a time
27
26
 
28
- ### Step 1: Install via npm
27
+ Gather the fields the `setup` tool needs. Ask conversationally, wait for each answer.
29
28
 
30
- Check if already installed:
29
+ 1. **Website** "What's the product's website?"
30
+ 2. **Description** — "In 1-3 sentences, what does it do?"
31
+ 3. **ICP** — "Who's the ideal customer you want to engage on X?"
32
+ 4. **Voice** — "What tone should replies have? Any words/claims to avoid?"
33
+ 5. **Differentiator** (recommended) — "What makes it different from the alternatives?"
34
+ 6. **Search topics** (required) — "What phrases or keywords do your buyers actually tweet about? Give me 5-15, comma-separated." These become the literal X searches the cycle runs. **Without them there is nothing to scan**, so don't skip this.
35
+ 7. **Get-started link** (recommended) — "Primary call-to-action link (signup / get started)?"
31
36
 
32
- ```bash
33
- ls ~/social-autoposter/schema-postgres.sql 2>/dev/null && echo "FOUND" || echo "NOT_FOUND"
34
- ```
35
-
36
- If NOT_FOUND, install:
37
- ```bash
38
- npx social-autoposter init
39
- ```
40
-
41
- This copies all scripts, skill files, and config templates to `~/social-autoposter/`. It also:
42
- - Creates `config.json` from `config.example.json` (if missing)
43
- - Creates `.env` from `.env.example` (if missing) — includes pre-filled `DATABASE_URL`
44
- - Installs `psycopg2-binary` (Python driver for Postgres)
45
- - Symlinks `~/.claude/skills/social-autoposter` → `~/social-autoposter/skill`
46
-
47
- To update scripts later without touching config/data:
48
- ```bash
49
- npx social-autoposter update
50
- ```
51
-
52
- Set `SKILL_DIR=~/social-autoposter` for the rest of this wizard.
37
+ For the voice/angle, it helps to draft a short first-person `voice`/`differentiator` from their answers and confirm it reads like them before saving. Aim for specific (names tools, numbers, real experience), not generic.
53
38
 
54
- ### Step 2: Verify the Postgres database connection
39
+ ### Step 2: Create the project with `setup`
55
40
 
56
- Load the env and test the connection:
41
+ Call the `setup` tool with a short slug `name` plus the fields above. Pass `search_topics` as a comma-separated string or array. You can fill fields incrementally across calls — it merges and reports what's still missing. A project is **ready** only once it has name, website, description, icp, voice, **and search_topics**.
57
42
 
58
- ```bash
59
- source "$SKILL_DIR/.env"
60
- python3 -c "
61
- import psycopg2, os
62
- conn = psycopg2.connect(os.environ['DATABASE_URL'])
63
- cur = conn.cursor()
64
- cur.execute(\"SELECT COUNT(*) FROM posts\")
65
- print('Connected. Posts in DB:', cur.fetchone()[0])
66
- conn.close()
67
- "
68
- ```
69
-
70
- Expected: `Connected. Posts in DB: <number>` (any number is fine, including 0).
71
-
72
- If psycopg2 is missing: `pip3 install psycopg2-binary`
73
-
74
- If the connection fails, check that `DATABASE_URL` is set in `$SKILL_DIR/.env`.
43
+ When the project becomes ready, `setup` **automatically seeds its `search_topics` into the DB** (`project_search_topics`) and tells you how many it seeded. You do not run any seed script by hand.
75
44
 
76
- ### Step 3: Configure accounts
45
+ ### Step 3: Connect X/Twitter
77
46
 
78
- `config.json` already exists at `$SKILL_DIR/config.json`. Edit it with the user's accounts.
47
+ Call `setup` with `action:'connect_x'` (no `confirm`) first — it returns an explanation of what will happen (it imports your x.com/twitter.com cookies into the autoposter's managed Chrome). Relay that to the user, get their OK, then call again with `action:'connect_x', confirm:true`.
79
48
 
80
- Ask the user for each platform they want to use:
49
+ This imports the session **and auto-detects + records your real `@handle`** into `config.json` (`accounts.twitter.handle`). That handle scopes attribution, own-reply skipping, and account-keyed operations — so do not hand-edit it to a placeholder.
81
50
 
82
- **Reddit:**
83
- - "What's your Reddit username?" → set `accounts.reddit.username`
84
- - Login method is always `browser` (Reddit has no public posting API)
51
+ ### Step 4: Verify with a draft cycle
85
52
 
86
- **X/Twitter:**
87
- - "What's your X handle?" → set `accounts.twitter.handle`
88
- - Login method is always `browser`
53
+ Run the `draft_cycle` tool. It scans X, drafts replies, and shows them for your approval — it **posts nothing** until you approve. If it comes back empty with a clear reason (e.g. "no search topics"), fix that (re-run `setup` with topics) and try again. A non-empty review form means the pipeline is healthy end-to-end.
89
54
 
90
- **LinkedIn:**
91
- - "What's your LinkedIn name?" → set `accounts.linkedin.name`
92
- - Login method is always `browser`
55
+ ### Step 5 (optional): Autopilot
93
56
 
94
- **Moltbook** (optional):
95
- - "Do you want to set up Moltbook? (y/n)"
96
- - If yes: "What's your Moltbook username?" and "What's your Moltbook API key?"
97
- - Edit `$SKILL_DIR/.env` and set `MOLTBOOK_API_KEY=<key>` (the file already exists from init)
98
- - Set `accounts.moltbook.username` and `accounts.moltbook.api_key_env` in `config.json`
99
-
100
- ### Step 4: Configure content
101
-
102
- This step is the most important one. Take your time. The quality of every future post depends on it.
57
+ If the user wants hands-free posting, call `autopilot` with `action:'enable'` — it loads the background cycle and daily auto-updates. `action:'status'` reports whether it's loaded; `action:'disable'` turns it off (manual `draft_cycle` still works).
103
58
 
104
59
  ---
105
60
 
106
- **4a. Subreddits**
107
-
108
- Ask: "Which subreddits do you want to post in? (comma-separated, or press enter for defaults)"
109
-
110
- Default suggestion: `ClaudeAI, ClaudeCode, programming, webdev, devops`
111
-
112
- Write the list to `config.json` under `subreddits`.
113
-
114
- ---
115
-
116
- **4b. Content angle — interview the user**
117
-
118
- Don't just ask for a one-liner. Run a short interview to understand who they are, then write the angle for them.
119
-
120
- Ask these questions one at a time. Wait for each answer before asking the next.
121
-
122
- **Question 1:** "What are you currently working on or building? Be specific — what does it actually do?"
123
-
124
- **Question 2:** "What's your technical background? What languages, tools, or domains do you know well?"
125
-
126
- **Question 3:** "What's something you've learned recently from your work that most people in your field don't know yet — or that surprised you?"
127
-
128
- **Question 4:** "What's a recurring frustration or problem you've run into that you think others in your community also face?"
129
-
130
- **Question 5:** "Do you have any unusual setup or workflow? (e.g. running multiple AI agents, building on niche platforms, working solo on something usually done by teams)"
131
-
132
- After collecting all answers, synthesize them into a `content_angle` that:
133
- - Is 2-4 sentences
134
- - Is written in first person
135
- - Names specific tools, numbers, and experiences (not generic claims)
136
- - Captures what makes their perspective genuinely different from a typical developer
61
+ ## CLI fallback (no MCP)
137
62
 
138
- Show the draft to the user:
139
- > "Here's the content angle I'll use to write comments in your voice:
140
- > [DRAFT]
141
- > Does this sound like you? Want to change anything?"
63
+ Only if the MCP tools aren't available.
142
64
 
143
- Refine based on their feedback. Only save to `config.json` when they confirm it.
144
-
145
- **Example of a weak angle** (don't write like this):
146
- > "Software developer with experience in AI and web development."
147
-
148
- **Example of a strong angle** (aim for this):
149
- > "Building a macOS desktop AI agent that controls the browser and writes code via voice. Running 5 Claude agents in parallel on the same codebase — learned the hard way that they need zero file overlap or everything breaks. API costs hit $800/month before I got aggressive about caching."
65
+ 1. **Install**: `npx social-autoposter init` (creates `config.json` from the template and `.env`; symlinks the skill). Update later with `npx social-autoposter update`.
66
+ 2. **Configure the project**: edit `~/social-autoposter/config.json` `projects[]` with `name`, `website`, `description`, `icp`, `voice`, and `search_topics` (array). Leave `accounts.twitter.handle` empty — it's filled on connect.
67
+ 3. **Seed topics into the DB** (the cycle reads the DB, not config): `python3 scripts/seed_search_topics.py --project <name>`.
68
+ 4. **Connect X**: `python3 scripts/setup_twitter_auth.py connect` imports the session and records the real handle.
69
+ 5. **Verify**: `DRAFT_ONLY=1 TWITTER_PAGE_GEN_RATE=0 bash skill/run-twitter-cycle.sh` — drafts without posting; it prints `DRAFT_ONLY_PLAN=<path>` on success.
70
+ 6. **Automation** (optional): on macOS, symlink + load the launchd plists in `skill/launchd/`; on Linux, add the matching cron entries.
150
71
 
151
72
  ---
152
73
 
153
- **4c. Projects**
154
-
155
- Ask: "Do you have any open source projects or products you'd want to mention naturally when the topic comes up? (y/n)"
156
-
157
- If yes, for each project run through:
158
- - "What's the name?"
159
- - "One sentence: what does it do?"
160
- - "Website URL? (or leave blank)"
161
- - "GitHub URL? (or leave blank)"
162
- - "What topics or keywords would make it relevant to mention? (e.g. 'desktop automation, macOS, accessibility APIs')"
163
-
164
- After each project, ask: "Any more projects to add? (y/n)"
165
-
166
- Store each under `config.json` → `projects` array with fields: `name`, `description`, `website`, `github`, `topics` (array of strings).
167
-
168
- The `topics` keywords are what trigger natural mentions — when someone in a thread mentions one of these topics, the agent knows this project is relevant to bring up.
169
-
170
- ### Step 5: Verify browser logins
171
-
172
- For each configured platform, verify the user is logged in:
173
-
174
- **Reddit:**
175
- - Navigate to `https://old.reddit.com` using browser automation
176
- - Check if a username appears in the top-right (logged in) or a "login" link (not logged in)
177
- - If not logged in: "Please log into Reddit in your browser, then say 'done'"
178
- - Re-check after they confirm
179
-
180
- **X/Twitter:**
181
- - Navigate to `https://x.com/home`
182
- - Check if the home timeline loads (logged in) or a login page appears
183
- - Same flow if not logged in
184
-
185
- **LinkedIn:**
186
- - Navigate to `https://www.linkedin.com/feed/`
187
- - Check if the feed loads or a login page appears
188
-
189
- **Moltbook:**
190
- - Source the env file and test the API key:
191
- ```bash
192
- source ~/social-autoposter/.env
193
- curl -s -H "Authorization: Bearer $MOLTBOOK_API_KEY" "https://www.moltbook.com/api/v1/posts?limit=1"
194
- ```
195
- - Check for a successful response (not an auth error)
196
-
197
- Report which platforms are ready and which need attention.
74
+ ## Summary to show the user
198
75
 
199
- ### Step 6: Test run (dry run)
200
-
201
- Run the thread finder to verify everything works:
202
- ```bash
203
- python3 "$SKILL_DIR/scripts/find_threads.py" --limit 3
204
- ```
205
-
206
- Show the user the candidate threads found. Don't post anything — just verify the pipeline works.
207
-
208
- ### Step 7: Set up automation (optional)
209
-
210
- Ask: "Do you want posts to run automatically on a schedule? (y/n)"
211
-
212
- If yes, and on macOS:
213
- - The launchd plists are already in `$SKILL_DIR/launchd/` (one per platform: reddit-search, reddit-threads, twitter-cycle, linkedin, moltbook, github, plus system jobs: stats, engage, audit, octolens, scan-*, dm-replies-*, link-edit-*)
214
- - Symlink and load all of them:
215
- ```bash
216
- for plist in "$SKILL_DIR"/launchd/com.m13v.social-*.plist; do
217
- ln -sf "$plist" ~/Library/LaunchAgents/
218
- launchctl load ~/Library/LaunchAgents/$(basename "$plist")
219
- done
220
- ```
221
- - Cadences: Twitter cycle every 20 min, Reddit search every 30 min, Reddit threads 4x/day, reply scans 2x/day, engage loop every 4 h, stats refresh 4x/day, DM outreach and link-edit 4x/day.
222
-
223
- If yes, and on Linux:
224
- - Generate crontab entries (pick the platforms you use):
225
- ```
226
- */20 * * * * cd ~/social-autoposter && bash skill/run-twitter-cycle.sh
227
- */30 * * * * cd ~/social-autoposter && bash skill/run-reddit-search.sh
228
- 10 */6 * * * cd ~/social-autoposter && bash skill/run-reddit-threads.sh
229
- 0 */6 * * * cd ~/social-autoposter && bash skill/stats.sh
230
- 0 */4 * * * cd ~/social-autoposter && bash skill/engage.sh
231
- ```
232
-
233
- If no: "You can run manually anytime with `/social-autoposter`"
234
-
235
- ### Step 8: Summary
236
-
237
- Read `config.json` accounts and compute each platform's stats URL:
238
- - Twitter/X handle (strip leading `@`): `https://s4l.ai/stats/HANDLE`
239
- - Reddit username: `https://s4l.ai/stats/USERNAME`
240
- - LinkedIn name (URL-encoded spaces as `%20`): `https://s4l.ai/stats/NAME`
241
- - Moltbook username: `https://s4l.ai/stats/MOLTBOOK_USERNAME`
242
-
243
- Print a summary with real values substituted:
244
76
  ```
245
77
  Social Autoposter Setup Complete
246
78
 
247
- Installed: ~/social-autoposter (v1.0.9 via npm)
248
- Database: Postgres (DATABASE_URL in .env)
79
+ Installed: ~/social-autoposter (via npm)
249
80
  Config: ~/social-autoposter/config.json
250
- Env: ~/social-autoposter/.env
251
- Skill: ~/.claude/skills/social-autoposter
252
-
253
- Platforms:
254
- Reddit: u/USERNAME ✓
255
- X/Twitter: @HANDLE ✓
256
- LinkedIn: NAME ✓
257
- Moltbook: USERNAME ✓
258
-
259
- Automation: launchd (hourly post, 6h stats, 2h engage)
81
+ Backend: s4l.ai HTTP API (per-install identity; no local DB)
260
82
 
261
- Your live stats pages:
262
- X/Twitter: https://s4l.ai/stats/HANDLE
263
- Reddit: https://s4l.ai/stats/USERNAME
264
- LinkedIn: https://s4l.ai/stats/NAME
265
- Moltbook: https://s4l.ai/stats/MOLTBOOK_USERNAME
83
+ Project: NAME ready
84
+ Search topics seeded: N
85
+ X/Twitter: @HANDLE (auto-detected on connect)
266
86
 
267
- Try it: /social-autoposter
87
+ Verify: draft_cycle (drafts for review, posts nothing)
88
+ Autopilot: autopilot action:'enable' (hands-free)
89
+ Stats: https://s4l.ai/stats/HANDLE
268
90
  Update: npx social-autoposter update
269
91
  ```
270
92
 
271
- Tell the user: "Your stats pages are ready they'll show posts as soon as your first run completes and syncs to Postgres (happens automatically after each post run). Bookmark the links above."
93
+ Tell the user their stats page (`https://s4l.ai/stats/<handle>`) populates after the first real post.