social-autoposter 1.1.2 → 1.2.0
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/README.md +111 -55
- package/SKILL.md +56 -23
- package/bin/cli.js +53 -9
- package/bin/server.js +402 -32
- package/browser-agent-configs/linkedin-agent-mcp.json +16 -0
- package/browser-agent-configs/linkedin-agent.json +17 -0
- package/browser-agent-configs/reddit-agent-mcp.json +16 -0
- package/browser-agent-configs/reddit-agent.json +17 -0
- package/browser-agent-configs/twitter-agent-mcp.json +16 -0
- package/browser-agent-configs/twitter-agent.json +17 -0
- package/package.json +8 -4
- package/schema-postgres.sql +15 -0
- package/scripts/active_campaigns.py +110 -0
- package/scripts/backfill_twitter_urls.py +223 -0
- package/scripts/backfill_twitter_urls_v2.py +255 -0
- package/scripts/backfill_twitter_urls_v3.py +345 -0
- package/scripts/batch_send_dms.py +131 -0
- package/scripts/campaign_bump.py +50 -0
- package/scripts/check_link_rules.py +101 -0
- package/scripts/daily_stats_email.py +285 -0
- package/scripts/diagnose_linkedin_agent.py +196 -0
- package/scripts/engage_github.py +545 -0
- package/scripts/engage_reddit.py +476 -0
- package/scripts/engagement_styles.py +269 -0
- package/scripts/enrich_twitter_candidates.py +85 -0
- package/scripts/export_cdp_storage_state.py +190 -0
- package/scripts/find_threads.py +36 -7
- package/scripts/find_tweets.py +154 -0
- package/scripts/github_tools.py +259 -0
- package/scripts/linkedin_api.py +389 -0
- package/scripts/linkedin_auth_check.py +415 -0
- package/scripts/linkedin_browser.py +1538 -0
- package/scripts/linkedin_cooldown.py +128 -0
- package/scripts/log_run.py +46 -0
- package/scripts/octolens_twitter_batch.py +193 -0
- package/scripts/octolens_twitter_cdp.py +152 -0
- package/scripts/octolens_twitter_read.py +46 -0
- package/scripts/pick_project.py +155 -0
- package/scripts/pick_thread_target.py +186 -0
- package/scripts/post_github.py +547 -0
- package/scripts/post_reddit.py +502 -0
- package/scripts/project_stats.py +322 -0
- package/scripts/reconcile_reply_urls.py +114 -0
- package/scripts/recover_linkedin_urls.py +301 -0
- package/scripts/reddit_browser.py +1579 -0
- package/scripts/reddit_tools.py +369 -0
- package/scripts/run_pieline_linkedin_batch.py +165 -0
- package/scripts/scan_linkedin_notifications.py +273 -0
- package/scripts/scan_twitter_mentions.py +175 -0
- package/scripts/score_twitter_candidates.py +272 -0
- package/scripts/scrape_linkedin_stats.py +46 -17
- package/scripts/top_performers.py +409 -0
- package/scripts/twitter_api.py +258 -0
- package/scripts/twitter_browser.py +848 -0
- package/scripts/twitter_compose_dm.py +332 -0
- package/scripts/update_stats.py +15 -3
- package/skill/engage.sh +41 -20
- package/skill/run-github.sh +27 -0
- package/skill/run-linkedin.sh +103 -0
- package/skill/run-moltbook.sh +86 -0
- package/skill/run-reddit.sh +111 -0
- package/skill/run-twitter.sh +108 -0
- package/skill/stats.sh +97 -62
- package/skill/github.sh +0 -93
- package/skill/run.sh +0 -41
package/README.md
CHANGED
|
@@ -1,84 +1,140 @@
|
|
|
1
1
|
# social-autoposter
|
|
2
2
|
|
|
3
|
-
Automated social posting pipeline for Reddit, X/Twitter, LinkedIn, and Moltbook.
|
|
3
|
+
Automated social posting pipeline for Reddit, X/Twitter, LinkedIn, and Moltbook. Ships as a Claude Code skill plus a set of standalone Python helpers and macOS launchd jobs.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Posts are written to a shared Neon Postgres database via `DATABASE_URL` in `~/social-autoposter/.env`. Each platform drives its own persistent Playwright MCP browser profile, so logins survive across runs.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
A new machine needs all of these before the pipeline can run end to end:
|
|
10
|
+
|
|
11
|
+
- **macOS** (the launchd plists are mac-only; Linux users can crib the cron snippets from `setup/SKILL.md` Step 7)
|
|
12
|
+
- **Node.js 16+** (for `npx`, the installer, and `@playwright/mcp` at runtime)
|
|
13
|
+
- **Python 3.9+** with `pip3` (helper scripts; `psycopg2-binary` is auto-installed by the installer)
|
|
14
|
+
- **Claude Code CLI** on `PATH` (the cron scripts shell out to `claude -p` with a per-platform MCP config)
|
|
15
|
+
- **`psql`** on `PATH` (a few scripts query Neon directly)
|
|
16
|
+
- One Chromium install per platform (created on first run by `@playwright/mcp` against the persistent profile dirs)
|
|
17
|
+
|
|
18
|
+
Optional:
|
|
19
|
+
|
|
20
|
+
- `MOLTBOOK_API_KEY` in `.env` for Moltbook posting and scanning
|
|
21
|
+
- `RESEND_API_KEY` and `NOTIFICATION_EMAIL` in `.env` for DM-escalation emails
|
|
22
|
+
|
|
23
|
+
## Install
|
|
8
24
|
|
|
9
25
|
```bash
|
|
10
26
|
npx social-autoposter init
|
|
11
27
|
```
|
|
12
28
|
|
|
13
|
-
|
|
29
|
+
`bin/cli.js` does all of the wiring in one shot:
|
|
30
|
+
|
|
31
|
+
1. Copies `scripts/`, `skill/`, `setup/`, `SKILL.md`, `schema-postgres.sql`, and `browser-agent-configs/` into `~/social-autoposter/`
|
|
32
|
+
2. Creates `config.json` from `config.example.json` and `.env` from `.env.example` (the shared Neon `DATABASE_URL` is pre-filled)
|
|
33
|
+
3. Installs `psycopg2-binary` via `pip3` if missing
|
|
34
|
+
4. Generates launchd plists in `~/social-autoposter/launchd/` with the user's actual `HOME` and `PATH`
|
|
35
|
+
5. Installs the Playwright MCP configs to `~/.claude/browser-agent-configs/` (twitter, reddit, linkedin) with `__HOME__` and `__NODE_BIN__` placeholders substituted. Existing files are left alone, so any window-position tweaks survive `npx social-autoposter update`.
|
|
36
|
+
6. Creates empty persistent browser profile dirs at `~/.claude/browser-profiles/{twitter,reddit,linkedin}`
|
|
37
|
+
7. Symlinks `~/.claude/skills/social-autoposter` and `~/.claude/skills/social-autoposter-setup` to the install dir
|
|
38
|
+
|
|
39
|
+
To refresh code without touching user files (`config.json`, `.env`, `SKILL.md`, or any browser config you customized):
|
|
14
40
|
|
|
15
|
-
To update scripts without touching your config or database:
|
|
16
41
|
```bash
|
|
17
42
|
npx social-autoposter update
|
|
18
43
|
```
|
|
19
44
|
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
cp config.example.json config.json # edit with your accounts
|
|
23
|
-
psql "$DATABASE_URL" -f schema-postgres.sql # initialize the Neon DB
|
|
24
|
-
bash setup.sh # symlinks + launchd (macOS)
|
|
25
|
-
```
|
|
45
|
+
## Configure
|
|
26
46
|
|
|
27
|
-
|
|
47
|
+
Tell your Claude Code agent: **"set up social autoposter"**. The interactive wizard in `setup/SKILL.md` walks through:
|
|
28
48
|
|
|
49
|
+
1. Verifying the Neon connection
|
|
50
|
+
2. Filling in `~/social-autoposter/config.json` with handles for Reddit, Twitter, LinkedIn, optional Moltbook
|
|
51
|
+
3. A 5-question interview to draft your `content_angle`
|
|
52
|
+
4. Capturing `projects` with `topics` (used by the tiered reply strategy)
|
|
53
|
+
5. Verifying browser logins per platform via the dedicated MCP agent. The first time each platform runs you'll be asked to log in once; cookies persist into the userDataDir under `~/.claude/browser-profiles/`.
|
|
54
|
+
6. A dry-run of `find_threads.py --limit 3`
|
|
55
|
+
7. Optional: loading the launchd plists into `~/Library/LaunchAgents/`
|
|
56
|
+
|
|
57
|
+
## How the runtime is wired
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
launchd ──▶ skill/run-{platform}.sh ──▶ claude -p --strict-mcp-config --mcp-config ~/.claude/browser-agent-configs/{platform}-agent-mcp.json
|
|
61
|
+
│ │
|
|
62
|
+
│ └──▶ @playwright/mcp@latest
|
|
63
|
+
│ │
|
|
64
|
+
│ └──▶ ~/.claude/browser-profiles/{platform}/ (persistent userDataDir)
|
|
65
|
+
│
|
|
66
|
+
├──▶ scripts/find_{tweets,threads}.py (no browser, API + DB dedup)
|
|
67
|
+
├──▶ scripts/pick_project.py (weighted project rotation)
|
|
68
|
+
├──▶ scripts/top_performers.py (feedback report from past stats)
|
|
69
|
+
└──▶ Neon Postgres (DATABASE_URL in .env)
|
|
29
70
|
```
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
71
|
+
|
|
72
|
+
Each `skill/run-*.sh`:
|
|
73
|
+
|
|
74
|
+
1. Controlled by launchd (load/unload). Use the dashboard Pause All / Resume All button, or `launchctl unload/load` directly
|
|
75
|
+
2. Acquires a per-platform lock from `skill/lock.sh` (waits up to 60 min for any prior run)
|
|
76
|
+
3. Sources `~/social-autoposter/.env`
|
|
77
|
+
4. Picks a project, builds a feedback report, fetches `llms.txt` for product context
|
|
78
|
+
5. Calls `find_*.py` for API-side candidates already deduped against the DB
|
|
79
|
+
6. Spawns a child Claude process with `--strict-mcp-config` so it only sees the one platform's browser MCP
|
|
80
|
+
|
|
81
|
+
The launchd schedules from `bin/cli.js`:
|
|
82
|
+
|
|
83
|
+
| Job | Cadence |
|
|
84
|
+
|-----|---------|
|
|
85
|
+
| `com.m13v.social-autoposter` (`run.sh`) | every 3600 s (hourly) |
|
|
86
|
+
| `com.m13v.social-stats` (`stats.sh`) | every 21600 s (6 h) |
|
|
87
|
+
| `com.m13v.social-engage` (`engage.sh`) | every 21600 s (6 h) |
|
|
88
|
+
|
|
89
|
+
Per-platform plists in `launchd/` (twitter, reddit, linkedin, moltbook, github, octolens, audit, dm-replies, scan-replies, etc.) use either `StartInterval` or `StartCalendarInterval` for fixed wall-clock times. Activate them with:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ln -sf ~/social-autoposter/launchd/com.m13v.social-twitter.plist ~/Library/LaunchAgents/
|
|
93
|
+
launchctl load ~/Library/LaunchAgents/com.m13v.social-twitter.plist
|
|
44
94
|
```
|
|
45
95
|
|
|
46
|
-
##
|
|
96
|
+
## Skill commands
|
|
97
|
+
|
|
98
|
+
| Command | What it does |
|
|
99
|
+
|---------|-------------|
|
|
100
|
+
| `/social-autoposter` | Comment run: find threads, draft, post, log (cron-safe) |
|
|
101
|
+
| `/social-autoposter post` | Create an original post or thread (manual only) |
|
|
102
|
+
| `/social-autoposter stats` | Update engagement stats via API |
|
|
103
|
+
| `/social-autoposter engage` | Scan and reply to responses on our posts |
|
|
104
|
+
| `/social-autoposter audit` | Full browser audit of all posts |
|
|
105
|
+
|
|
106
|
+
View live stats at `https://s4l.ai/stats/<your-handle>` once posts start landing in Neon.
|
|
107
|
+
|
|
108
|
+
## Repo layout
|
|
47
109
|
|
|
48
110
|
```
|
|
49
111
|
social-autoposter/
|
|
50
|
-
├── SKILL.md
|
|
51
|
-
├──
|
|
52
|
-
├──
|
|
53
|
-
├──
|
|
54
|
-
├──
|
|
55
|
-
|
|
56
|
-
├── scripts/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
│ ├── scan_replies.py <- scan for new replies to our posts via API
|
|
60
|
-
│ └── update_stats.py <- fetch engagement stats via API
|
|
61
|
-
├── skill/
|
|
62
|
-
│ ├── SKILL.md <- personal skill (hardcoded accounts)
|
|
63
|
-
│ ├── run.sh <- hourly posting (launchd wrapper)
|
|
64
|
-
│ ├── stats.sh <- 6-hourly stats (launchd wrapper)
|
|
65
|
-
│ ├── engage.sh <- 2-hourly engagement (launchd wrapper)
|
|
66
|
-
│ └── logs/ <- runtime logs (gitignored)
|
|
67
|
-
└── launchd/ <- macOS LaunchAgent plists
|
|
112
|
+
├── SKILL.md the playbook (locked, immutable)
|
|
113
|
+
├── bin/cli.js installer + dashboard launcher
|
|
114
|
+
├── browser-agent-configs/ Playwright MCP templates (twitter/reddit/linkedin)
|
|
115
|
+
├── config.example.json config template
|
|
116
|
+
├── schema-postgres.sql Neon schema
|
|
117
|
+
├── setup/SKILL.md interactive setup wizard skill (locked)
|
|
118
|
+
├── scripts/ Python and JS helpers (no browser, no LLM)
|
|
119
|
+
├── skill/ shell wrappers invoked by launchd
|
|
120
|
+
└── launchd/ generated macOS LaunchAgent plists
|
|
68
121
|
```
|
|
69
122
|
|
|
70
123
|
## For other AI agents
|
|
71
124
|
|
|
72
|
-
The skill
|
|
73
|
-
- **Shell access** (to run Python scripts and psql)
|
|
74
|
-
- **Browser automation** (Playwright, Selenium, etc. for posting)
|
|
75
|
-
- **An LLM** (for drafting comments in the right tone)
|
|
125
|
+
The skill works with any agent that has shell access, browser automation, and an LLM. The Python and JS helpers in `scripts/` handle thread discovery, reply scanning, and stats updates without needing a browser. `SKILL.md` is the playbook; any agent can read it and execute the workflows with its own tools.
|
|
76
126
|
|
|
77
|
-
|
|
127
|
+
## Pause and resume
|
|
78
128
|
|
|
79
|
-
|
|
129
|
+
Use the dashboard at `localhost:3141` (Pause All / Resume All button), or manually:
|
|
80
130
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
131
|
+
```bash
|
|
132
|
+
# Pause: unload all jobs + kill running processes
|
|
133
|
+
for plist in ~/Library/LaunchAgents/com.m13v.social-*.plist; do launchctl unload "$plist"; done
|
|
134
|
+
|
|
135
|
+
# Resume: reload all jobs
|
|
136
|
+
for plist in ~/social-autoposter/launchd/com.m13v.social-*.plist; do
|
|
137
|
+
ln -sf "$plist" ~/Library/LaunchAgents/
|
|
138
|
+
launchctl load ~/Library/LaunchAgents/$(basename "$plist")
|
|
139
|
+
done
|
|
140
|
+
```
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: social-autoposter
|
|
3
|
-
description: "Automate social media posting across Reddit, X/Twitter, LinkedIn, and Moltbook. Find threads, post comments, create original posts, track engagement stats. Use when: 'post to social', 'social autoposter', 'find threads to comment on', 'create a post', 'audit social posts', 'update post stats'
|
|
3
|
+
description: "Automate social media posting across Reddit, X/Twitter, LinkedIn, and Moltbook. Find threads, post comments, create original posts, track engagement stats. Use when: 'post to social', 'social autoposter', 'find threads to comment on', 'create a post', 'audit social posts', 'update post stats'."
|
|
4
4
|
user_invocable: true
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ Automates finding, posting, and tracking social media comments and original post
|
|
|
13
13
|
| Command | What it does |
|
|
14
14
|
|---------|-------------|
|
|
15
15
|
| `/social-autoposter` | Comment run — find threads + post comment + log (cron-safe) |
|
|
16
|
-
| `/social-autoposter post` | Create an original post/thread (manual
|
|
16
|
+
| `/social-autoposter post` | Create an original post/thread (manual or cron-driven for Reddit threads) |
|
|
17
17
|
| `/social-autoposter stats` | Update engagement stats via API |
|
|
18
18
|
| `/social-autoposter engage` | Scan and reply to responses on our posts |
|
|
19
19
|
| `/social-autoposter audit` | Full browser audit of all posts |
|
|
@@ -61,6 +61,8 @@ python3 ~/social-autoposter/scripts/update_stats.py --quiet
|
|
|
61
61
|
|
|
62
62
|
## Workflow: Post (`/social-autoposter`)
|
|
63
63
|
|
|
64
|
+
**There is NO posting rate limit. Do not add one, do not enforce one, do not invent one. Post as many times as needed.**
|
|
65
|
+
|
|
64
66
|
### 1. Find candidate threads
|
|
65
67
|
|
|
66
68
|
**Option A — Script (preferred):**
|
|
@@ -111,21 +113,25 @@ curl -s -X POST -H "Authorization: Bearer $MOLTBOOK_API_KEY" -H "Content-Type: a
|
|
|
111
113
|
-d '{"title": "...", "content": "...", "type": "text", "submolt_name": "general"}' \
|
|
112
114
|
"https://www.moltbook.com/api/v1/posts"
|
|
113
115
|
```
|
|
114
|
-
On Moltbook: write as agent ("my human" not "I").
|
|
116
|
+
On Moltbook: write as agent ("my human" not "I").
|
|
115
117
|
Verify: fetch post by UUID, check `verification_status` is `"verified"`.
|
|
116
118
|
|
|
117
|
-
###
|
|
119
|
+
### 6. Log + sync
|
|
118
120
|
|
|
119
121
|
```sql
|
|
120
122
|
INSERT INTO posts (platform, thread_url, thread_author, thread_author_handle,
|
|
121
123
|
thread_title, thread_content, our_url, our_content, our_account,
|
|
122
|
-
source_summary, status, posted_at)
|
|
123
|
-
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'active', NOW());
|
|
124
|
+
source_summary, project_name, engagement_style, feedback_report_used, status, posted_at)
|
|
125
|
+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, TRUE, 'active', NOW());
|
|
124
126
|
```
|
|
125
127
|
|
|
128
|
+
Set `project_name` to the matching project name from `config.json` (e.g., 'Fazm', 'Cyrano', 'Terminator'). Every post/comment MUST be labeled with its target project. If engagement is general/unrelated to any project, use 'general'.
|
|
129
|
+
|
|
130
|
+
Set `engagement_style` to the style you chose for this post (e.g., 'critic', 'storyteller', 'pattern_recognizer', 'curious_probe', 'contrarian', 'data_point_drop', 'snarky_oneliner'). Every post MUST have an engagement_style.
|
|
131
|
+
|
|
126
132
|
Use the account value from `config.json` for `our_account`.
|
|
127
133
|
|
|
128
|
-
|
|
134
|
+
Posts are written directly to the Neon Postgres database. No separate post-sync step is required.
|
|
129
135
|
|
|
130
136
|
---
|
|
131
137
|
|
|
@@ -143,11 +149,11 @@ ORDER BY posted_at DESC;
|
|
|
143
149
|
|
|
144
150
|
**NEVER post the same or similar content to multiple subreddits.** This is the #1 AI detection red flag. Each post must be unique to its community.
|
|
145
151
|
|
|
146
|
-
###
|
|
152
|
+
### 2. Pick one target community
|
|
147
153
|
|
|
148
154
|
Choose the single best subreddit from `config.json → subreddits` for this topic. Tailor the post to that community's culture and tone.
|
|
149
155
|
|
|
150
|
-
###
|
|
156
|
+
### 3. Draft the post
|
|
151
157
|
|
|
152
158
|
**Anti-AI-detection checklist** (must pass ALL before posting):
|
|
153
159
|
|
|
@@ -163,22 +169,24 @@ Choose the single best subreddit from `config.json → subreddits` for this topi
|
|
|
163
169
|
|
|
164
170
|
**Read it out loud.** If it sounds like a blog post or a ChatGPT response, rewrite it.
|
|
165
171
|
|
|
166
|
-
###
|
|
172
|
+
### 4. Post it
|
|
167
173
|
|
|
168
174
|
**Reddit**: old.reddit.com → Submit new text post → paste title + body → submit → verify → capture permalink.
|
|
169
175
|
|
|
170
|
-
###
|
|
176
|
+
### 5. Log it
|
|
171
177
|
|
|
172
178
|
```sql
|
|
173
179
|
INSERT INTO posts (platform, thread_url, thread_author, thread_author_handle,
|
|
174
180
|
thread_title, thread_content, our_url, our_content, our_account,
|
|
175
|
-
source_summary, status, posted_at)
|
|
176
|
-
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'active', NOW());
|
|
181
|
+
source_summary, project_name, engagement_style, feedback_report_used, status, posted_at)
|
|
182
|
+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, TRUE, 'active', NOW());
|
|
177
183
|
```
|
|
178
184
|
|
|
179
|
-
|
|
185
|
+
Set `engagement_style` to the style you chose (e.g., 'critic', 'storyteller', 'pattern_recognizer'). Every post MUST have an engagement_style.
|
|
180
186
|
|
|
181
|
-
|
|
187
|
+
Set `project_name` to the matching project name from `config.json`. For original posts: `thread_url` = `our_url`, `thread_author` = our account from config.json.
|
|
188
|
+
|
|
189
|
+
### 6. Mandatory engagement plan
|
|
182
190
|
|
|
183
191
|
After posting, you MUST:
|
|
184
192
|
- Check for comments within 2-4 hours
|
|
@@ -188,13 +196,39 @@ After posting, you MUST:
|
|
|
188
196
|
|
|
189
197
|
---
|
|
190
198
|
|
|
199
|
+
## Workflow: Cron-driven Reddit Threads (`run-reddit-threads.sh`)
|
|
200
|
+
|
|
201
|
+
Daily-cadence original Reddit threads across all products, automated via launchd.
|
|
202
|
+
|
|
203
|
+
**Config lives per-project** under `projects[].threads`:
|
|
204
|
+
- `enabled`: true/false
|
|
205
|
+
- `own_community`: `{subreddit, cadence, floor_days}` (optional). Defaults to 1-day floor.
|
|
206
|
+
- `external_subreddits`: list of external subs (default 3-day floor, override via `external_floor_days`)
|
|
207
|
+
- `topic_angles`: discussion-starter ideas the agent picks from
|
|
208
|
+
- `voice_notes`: per-thread voice guidance on top of `projects[].voice`
|
|
209
|
+
- `content_sources.guide_dir` / `link_base`: optional source paths/URLs
|
|
210
|
+
- `dynamic_context.day_counter` / `static_facts`: live-calculated facts injected into the prompt
|
|
211
|
+
|
|
212
|
+
**Source research** uses `landing_pages.repo` + `landing_pages.product_source[]` (same schema as the SEO pipeline). The agent is told to read README + product source before drafting so posts ground in real details.
|
|
213
|
+
|
|
214
|
+
**Picker** (`scripts/pick_thread_target.py`): weighted project selection with:
|
|
215
|
+
- Per-sub floor-days filter (queries `posts` table for this account's last original thread)
|
|
216
|
+
- `banned_subreddits` filter (top-level config, all groups flattened)
|
|
217
|
+
- Own-community candidates always picked first when eligible
|
|
218
|
+
|
|
219
|
+
**Schedule**: `com.m13v.social-reddit-threads.plist` fires 4x/day at 00:15, 06:15, 12:15, 18:15.
|
|
220
|
+
|
|
221
|
+
**Lock**: the runner calls `acquire_lock reddit-threads` to serialize against the comment pipeline.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
191
225
|
## Workflow: Stats (`/social-autoposter stats`)
|
|
192
226
|
|
|
193
227
|
```bash
|
|
194
228
|
python3 ~/social-autoposter/scripts/update_stats.py
|
|
195
229
|
```
|
|
196
230
|
|
|
197
|
-
After running, view updated stats at `https://s4l.ai/stats/[handle]`.
|
|
231
|
+
After running, view updated stats at `https://s4l.ai/stats/[handle]`. Stats are read from the same Neon Postgres database used by the posting pipeline. Changes appear on the website within ~5 minutes.
|
|
198
232
|
|
|
199
233
|
---
|
|
200
234
|
|
|
@@ -214,7 +248,7 @@ FROM replies r JOIN posts p ON r.post_id = p.id
|
|
|
214
248
|
WHERE r.status='pending' ORDER BY r.discovered_at ASC LIMIT 10
|
|
215
249
|
```
|
|
216
250
|
|
|
217
|
-
Draft replies: 2-4 sentences, casual, expand the topic. Apply Tiered Reply Strategy.
|
|
251
|
+
Draft replies: 2-4 sentences, casual, expand the topic. Apply Tiered Reply Strategy.
|
|
218
252
|
|
|
219
253
|
Post via browser (Reddit/X) or API (Moltbook). Update:
|
|
220
254
|
```sql
|
|
@@ -224,7 +258,7 @@ UPDATE replies SET status='replied', our_reply_content=%s, our_reply_url=%s,
|
|
|
224
258
|
|
|
225
259
|
### Phase C: X/Twitter replies (browser required)
|
|
226
260
|
|
|
227
|
-
Navigate to `https://x.com/notifications/mentions`. Find replies to the handle in config.json. Respond to substantive ones
|
|
261
|
+
Navigate to `https://x.com/notifications/mentions`. Find replies to the handle in config.json. Respond to substantive ones. Log to `replies` table.
|
|
228
262
|
|
|
229
263
|
---
|
|
230
264
|
|
|
@@ -251,10 +285,9 @@ Visit each post URL via browser. Check status (active/deleted/removed/inactive).
|
|
|
251
285
|
8. **No em dashes (—).** Use commas, periods, or regular dashes (-) instead. Em dashes are the #1 "ChatGPT tell."
|
|
252
286
|
9. **No markdown formatting in Reddit.** No headers (##), no bold (**text**), no numbered lists. Write in plain paragraphs.
|
|
253
287
|
10. **Never cross-post.** One post per topic per community.
|
|
254
|
-
11. **
|
|
255
|
-
12. **
|
|
256
|
-
13. **
|
|
257
|
-
14. **Reply to comments on your posts.** Zero engagement on your own post = bot signal. Reply within 24h.
|
|
288
|
+
11. **Include imperfections.** Contractions, sentence fragments, casual asides, occasional lowercase.
|
|
289
|
+
12. **Vary your openings.** Don't always start with credentials. Sometimes just jump into the topic.
|
|
290
|
+
13. **Reply to comments on your posts.** Zero engagement on your own post = bot signal. Reply within 24h.
|
|
258
291
|
|
|
259
292
|
### Bad vs Good (Comments)
|
|
260
293
|
|
|
@@ -286,6 +319,6 @@ GOOD body: Paragraphs, incomplete thoughts, personal details, casual tone, ends
|
|
|
286
319
|
|
|
287
320
|
## Database Schema
|
|
288
321
|
|
|
289
|
-
`posts`: id, platform, thread_url, thread_title, our_url, our_content, our_account, posted_at, status, upvotes, comments_count, views, source_summary
|
|
322
|
+
`posts`: id, platform, thread_url, thread_title, our_url, our_content, our_account, project_name, posted_at, status, upvotes, comments_count, views, source_summary, link_edited_at, link_edit_content
|
|
290
323
|
|
|
291
324
|
`replies`: id, post_id, platform, their_author, their_content, our_reply_content, status (pending|replied|skipped|error), depth
|
package/bin/cli.js
CHANGED
|
@@ -19,11 +19,24 @@ const COPY_TARGETS = [
|
|
|
19
19
|
'SKILL.md',
|
|
20
20
|
'skill',
|
|
21
21
|
'setup',
|
|
22
|
+
'browser-agent-configs',
|
|
22
23
|
];
|
|
23
24
|
|
|
24
25
|
// Never overwrite these user files during update
|
|
25
26
|
const USER_FILES = new Set(['config.json', '.env', 'SKILL.md']);
|
|
26
27
|
|
|
28
|
+
// Browser agent config templates -> install path under ~/.claude/browser-agent-configs/
|
|
29
|
+
const BROWSER_AGENT_CONFIGS = [
|
|
30
|
+
'twitter-agent-mcp.json',
|
|
31
|
+
'twitter-agent.json',
|
|
32
|
+
'reddit-agent-mcp.json',
|
|
33
|
+
'reddit-agent.json',
|
|
34
|
+
'linkedin-agent-mcp.json',
|
|
35
|
+
'linkedin-agent.json',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const BROWSER_PROFILES = ['twitter', 'reddit', 'linkedin'];
|
|
39
|
+
|
|
27
40
|
function copyDir(src, dest) {
|
|
28
41
|
fs.mkdirSync(dest, { recursive: true });
|
|
29
42
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -42,6 +55,40 @@ function linkOrRelink(target, linkPath) {
|
|
|
42
55
|
fs.symlinkSync(target, linkPath);
|
|
43
56
|
}
|
|
44
57
|
|
|
58
|
+
function installBrowserAgentConfigs() {
|
|
59
|
+
const nodeBin = path.dirname(process.execPath);
|
|
60
|
+
const srcDir = path.join(PKG_ROOT, 'browser-agent-configs');
|
|
61
|
+
const destDir = path.join(HOME, '.claude', 'browser-agent-configs');
|
|
62
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
63
|
+
|
|
64
|
+
let installed = 0;
|
|
65
|
+
let skipped = 0;
|
|
66
|
+
for (const name of BROWSER_AGENT_CONFIGS) {
|
|
67
|
+
const src = path.join(srcDir, name);
|
|
68
|
+
const dest = path.join(destDir, name);
|
|
69
|
+
if (!fs.existsSync(src)) continue;
|
|
70
|
+
if (fs.existsSync(dest)) {
|
|
71
|
+
skipped++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const tpl = fs.readFileSync(src, 'utf8');
|
|
75
|
+
const out = tpl
|
|
76
|
+
.replace(/__HOME__/g, HOME)
|
|
77
|
+
.replace(/__NODE_BIN__/g, nodeBin);
|
|
78
|
+
fs.writeFileSync(dest, out);
|
|
79
|
+
installed++;
|
|
80
|
+
}
|
|
81
|
+
console.log(` browser agent configs -> ${destDir} (installed ${installed}, skipped ${skipped} existing)`);
|
|
82
|
+
|
|
83
|
+
// Create empty persistent profile dirs so Playwright has somewhere to land cookies
|
|
84
|
+
const profilesDir = path.join(HOME, '.claude', 'browser-profiles');
|
|
85
|
+
fs.mkdirSync(profilesDir, { recursive: true });
|
|
86
|
+
for (const p of BROWSER_PROFILES) {
|
|
87
|
+
fs.mkdirSync(path.join(profilesDir, p), { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
console.log(` browser profile dirs ready -> ${profilesDir}/{${BROWSER_PROFILES.join(',')}}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
45
92
|
function generatePlists() {
|
|
46
93
|
// Detect PATH for launchd (include node, homebrew, system)
|
|
47
94
|
const nodeBin = path.dirname(process.execPath);
|
|
@@ -49,15 +96,6 @@ function generatePlists() {
|
|
|
49
96
|
const launchdPath = [...pathDirs].join(':');
|
|
50
97
|
|
|
51
98
|
const plists = [
|
|
52
|
-
{
|
|
53
|
-
file: 'com.m13v.social-autoposter.plist',
|
|
54
|
-
label: 'com.m13v.social-autoposter',
|
|
55
|
-
script: `${DEST}/skill/run.sh`,
|
|
56
|
-
interval: 3600,
|
|
57
|
-
runAtLoad: true,
|
|
58
|
-
stdoutLog: `${DEST}/skill/logs/launchd-stdout.log`,
|
|
59
|
-
stderrLog: `${DEST}/skill/logs/launchd-stderr.log`,
|
|
60
|
-
},
|
|
61
99
|
{
|
|
62
100
|
file: 'com.m13v.social-stats.plist',
|
|
63
101
|
label: 'com.m13v.social-stats',
|
|
@@ -137,6 +175,9 @@ function init() {
|
|
|
137
175
|
// Generate launchd plists with user's actual HOME
|
|
138
176
|
generatePlists();
|
|
139
177
|
|
|
178
|
+
// Install browser agent MCP configs + profile dirs (skips existing files)
|
|
179
|
+
installBrowserAgentConfigs();
|
|
180
|
+
|
|
140
181
|
// config.json — only if it doesn't exist
|
|
141
182
|
const configDest = path.join(DEST, 'config.json');
|
|
142
183
|
if (!fs.existsSync(configDest)) {
|
|
@@ -218,6 +259,9 @@ function update() {
|
|
|
218
259
|
// Regenerate launchd plists with correct paths
|
|
219
260
|
generatePlists();
|
|
220
261
|
|
|
262
|
+
// Top up browser agent configs (won't overwrite user customizations)
|
|
263
|
+
installBrowserAgentConfigs();
|
|
264
|
+
|
|
221
265
|
// Remove stale skill/SKILL.md if it exists (SKILL.md lives at repo root only)
|
|
222
266
|
const skillMd = path.join(DEST, 'skill', 'SKILL.md');
|
|
223
267
|
try { fs.rmSync(skillMd, { force: true }); } catch {}
|