social-autoposter 1.1.0 → 1.1.2

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 CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Automated social posting pipeline for Reddit, X/Twitter, LinkedIn, and Moltbook. Install as an AI agent skill or use the standalone Python scripts.
4
4
 
5
+ The current storage backend is Neon Postgres via `DATABASE_URL` in `~/social-autoposter/.env`. Any legacy `database` key in old local configs or leftover `*.db` files are not used by the current scripts.
6
+
5
7
  ## Install as a skill
6
8
 
7
9
  ```bash
package/SKILL.md CHANGED
@@ -1,148 +1,120 @@
1
1
  ---
2
2
  name: social-autoposter
3
- description: "Automate social media posting across Reddit, X/Twitter, LinkedIn, and Moltbook. Find threads, post comments, track engagement stats, and handle reply engagement. Use when: 'post to social', 'social autoposter', 'find threads', 'audit social posts', 'update post stats', 'engage replies'. Run /social-autoposter to start."
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', or after completing any task (mandatory per CLAUDE.md)."
4
4
  user_invocable: true
5
5
  ---
6
6
 
7
7
  # Social Autoposter
8
8
 
9
- Automates finding, posting, and tracking social media comments. Works with any agent that has browser automation and shell access.
9
+ Automates finding, posting, and tracking social media comments and original posts across Reddit, X/Twitter, LinkedIn, and Moltbook.
10
10
 
11
11
  ## Quick Start
12
12
 
13
13
  | Command | What it does |
14
14
  |---------|-------------|
15
- | `/social-autoposter` | Full posting run (find threads + post + log) |
15
+ | `/social-autoposter` | Comment run find threads + post comment + log (cron-safe) |
16
+ | `/social-autoposter post` | Create an original post/thread (manual only, never cron) |
16
17
  | `/social-autoposter stats` | Update engagement stats via API |
17
18
  | `/social-autoposter engage` | Scan and reply to responses on our posts |
18
19
  | `/social-autoposter audit` | Full browser audit of all posts |
19
20
 
20
- ## Config
21
+ **View your posts live:** `https://s4l.ai/stats/[your_handle]`
22
+ — e.g. `https://s4l.ai/stats/m13v_` (Twitter handle without `@`), `https://s4l.ai/stats/Deep_Ad1959` (Reddit), `https://s4l.ai/stats/matthew-autoposter` (Moltbook).
23
+ The handles come from `config.json → accounts.*.handle/username`. Each platform account has its own URL.
21
24
 
22
- All personal settings live in `config.json` (copy from `config.example.json`). The skill reads this at runtime.
23
-
24
- Key fields:
25
- - `accounts` — platform usernames/handles
26
- - `subreddits` — target subreddits to monitor
27
- - `content_angle` — your unique perspective for authentic comments
28
- - `projects` — your products/repos to mention when relevant (with topic keywords)
29
- - `database` — unused (DB is Neon Postgres via `DATABASE_URL` in `.env`)
30
-
31
- ## Helper Scripts
25
+ ---
32
26
 
33
- Standalone Python scripts any agent can call directly. No LLM needed for these.
27
+ ## FIRST: Read config
34
28
 
35
- | Script | What it does |
36
- |--------|-------------|
37
- | `scripts/find_threads.py` | Find candidate threads via Reddit JSON + Moltbook API |
38
- | `scripts/scan_replies.py` | Scan for new replies to our posts via API |
39
- | `scripts/update_stats.py` | Fetch engagement stats via API, update DB |
29
+ Before doing anything, read `~/social-autoposter/config.json`. Everything — accounts, projects, subreddits, content angle — comes from there.
40
30
 
41
- Examples:
42
31
  ```bash
43
- python3 scripts/find_threads.py --topic "macOS automation" --limit 5
44
- python3 scripts/scan_replies.py
45
- python3 scripts/update_stats.py --quiet
32
+ cat ~/social-autoposter/config.json
46
33
  ```
47
34
 
48
- ---
49
-
50
- ## Critical: No Parallel Posting
35
+ Key fields you'll use throughout every workflow:
51
36
 
52
- **NEVER launch multiple agents or parallel tasks for posting.** All posting operations (comments, replies, thread creation) MUST be done sequentially in a single agent. Reasons:
53
- - There is only one shared browser parallel agents fight over it and cause timeouts
54
- - Parallel agents cause duplicate posts (same comment posted twice on the same thread)
55
- - Browser lock conflicts lead to unpredictable failures
56
-
57
- This applies to ALL posting workflows: comments on existing threads, self-replies with links, new thread creation, and reply engagement. Even if you have 5 threads to post on, do them one at a time in the same agent.
37
+ - `accounts.reddit.username` Reddit handle to post as
38
+ - `accounts.twitter.handle`X/Twitter handle
39
+ - `accounts.linkedin.name` LinkedIn display name
40
+ - `accounts.moltbook.username` Moltbook username
41
+ - `subreddits` — list of subreddits to monitor and post in
42
+ - `content_angle` the user's unique perspective for writing authentic comments
43
+ - `projects` — products/repos to mention naturally when relevant (each has `name`, `description`, `website`, `github`, `topics`)
44
+ - `database` — unused (DB is Neon Postgres via `DATABASE_URL` in `.env`)
58
45
 
59
- **After each post, always verify** by reloading the page and confirming the comment appears exactly once before moving to the next post.
46
+ Use these values everywhere below instead of any hardcoded names or links.
60
47
 
61
48
  ---
62
49
 
63
- ## Workflow: Post (`/social-autoposter`)
64
-
65
- Find a thread, draft a comment, post it, log it.
50
+ ## Helper Scripts
66
51
 
67
- ### 1. Rate limit check
52
+ Standalone Python scripts no LLM needed.
68
53
 
69
- ```sql
70
- SELECT COUNT(*) FROM posts WHERE posted_at >= NOW() - INTERVAL '24 hours'
54
+ ```bash
55
+ python3 ~/social-autoposter/scripts/find_threads.py --include-moltbook
56
+ python3 ~/social-autoposter/scripts/scan_replies.py
57
+ python3 ~/social-autoposter/scripts/update_stats.py --quiet
71
58
  ```
72
- If 40+ posts in the last 24 hours, stop. Max 40/day.
73
59
 
74
- ### 2. Find candidate threads
60
+ ---
61
+
62
+ ## Workflow: Post (`/social-autoposter`)
63
+
64
+ ### 1. Find candidate threads
75
65
 
76
- **Option A — Use the helper script (preferred, no browser needed):**
66
+ **Option A — Script (preferred):**
77
67
  ```bash
78
- python3 scripts/find_threads.py --include-moltbook
68
+ python3 ~/social-autoposter/scripts/find_threads.py --include-moltbook
79
69
  ```
80
- This returns a JSON list of candidate threads with dedup already applied.
81
70
 
82
- **Option B — Browse manually with browser automation:**
83
- Browse `/new` and `/hot` across target subreddits (from `config.json`). Also check Moltbook via API.
71
+ **Option B — Browse manually:**
72
+ Browse `/new` and `/hot` on the subreddits from `config.json`. Also check Moltbook via API.
84
73
 
85
- ### 3. Pick the best thread
74
+ ### 2. Pick the best thread
86
75
 
87
- Requirements:
88
- - You have a genuine angle from your `content_angle` in `config.json`
89
- - The thread hasn't been posted in before (check `thread_url` in DB)
90
- - Your last 5 comments don't repeat the same talking points:
76
+ - You have a genuine angle from `content_angle` in config.json
77
+ - Not already posted in: `SELECT thread_url FROM posts`
78
+ - Last 5 comments don't repeat the same talking points:
91
79
  ```sql
92
80
  SELECT our_content FROM posts ORDER BY id DESC LIMIT 5
93
81
  ```
94
- - If no thread fits naturally, **stop**. Better to skip than force a bad comment.
82
+ - If nothing fits naturally, **stop**. Better to skip than force a bad comment.
95
83
 
96
- ### 4. Read the thread
84
+ ### 3. Read the thread + top comments
97
85
 
98
- Before commenting, read the full thread:
99
- - Check tone (casual/technical/professional)
100
- - Read top comments for length and style cues
101
- - Note thread age (skip stale threads)
102
- - Identify the best comment to reply to (high-upvote comments get more visibility)
86
+ Check tone, length cues, thread age. Find best comment to reply to (high-upvote comments get more visibility).
103
87
 
104
- ### 5. Draft the comment
88
+ ### 4. Draft the comment
105
89
 
106
- Follow the **Content Rules** section below. Key points:
107
- - 2-3 sentences max, match thread energy
108
- - First person, specific details from your experience
109
- - No product links in top-level comments (use Tiered Reply Strategy for that)
110
- - If it sounds like a blog post, rewrite it
90
+ Follow Content Rules below. 2-3 sentences, first person, specific details from `content_angle`. No product links in top-level comments.
111
91
 
112
- ### 6. Post it
92
+ ### 5. Post it
113
93
 
114
94
  **Reddit** (browser automation):
115
95
  - Navigate to `old.reddit.com` thread URL
116
- - Find the reply box (textarea with class `usertext-edit`)
117
- - Type the comment, click submit
118
- - Wait 2-3 seconds, verify the comment appeared
119
- - Capture the permalink of the new comment
120
- - Close the tab
96
+ - Reply box type comment submit → wait 2-3s → verify comment appeared → capture permalink → close tab
97
+ - Post as the username in `config.json → accounts.reddit.username`
121
98
 
122
99
  **X/Twitter** (browser automation):
123
- - Navigate to the tweet
124
- - Type reply in the reply box, click Reply
125
- - Verify the reply posted
126
- - Capture the URL
100
+ - Navigate to tweet → reply box → type → Reply → verify → capture URL
101
+ - Post as the handle in `config.json → accounts.twitter.handle`
127
102
 
128
103
  **LinkedIn** (browser automation):
129
- - Navigate to the post
130
- - Type comment, click Post
131
- - No stable URL available, note as posted
104
+ - Navigate to post → comment box → type → Post → close tab
105
+ - Post as the name in `config.json → accounts.linkedin.name`
132
106
 
133
107
  **Moltbook** (API — no browser needed):
134
108
  ```bash
135
109
  source ~/social-autoposter/.env
136
- curl -s -X POST -H "Authorization: Bearer $MOLTBOOK_API_KEY" \
137
- -H "Content-Type: application/json" \
110
+ curl -s -X POST -H "Authorization: Bearer $MOLTBOOK_API_KEY" -H "Content-Type: application/json" \
138
111
  -d '{"title": "...", "content": "...", "type": "text", "submolt_name": "general"}' \
139
112
  "https://www.moltbook.com/api/v1/posts"
140
113
  ```
114
+ On Moltbook: write as agent ("my human" not "I"). Max 1 post per 30 min.
141
115
  Verify: fetch post by UUID, check `verification_status` is `"verified"`.
142
- On Moltbook, write as an agent: "my human" not "I".
143
- Rate limit: max 1 post per 30 minutes.
144
116
 
145
- ### 7. Log to database
117
+ ### 7. Log + sync
146
118
 
147
119
  ```sql
148
120
  INSERT INTO posts (platform, thread_url, thread_author, thread_author_handle,
@@ -151,163 +123,169 @@ INSERT INTO posts (platform, thread_url, thread_author, thread_author_handle,
151
123
  VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'active', NOW());
152
124
  ```
153
125
 
126
+ Use the account value from `config.json` for `our_account`.
127
+
128
+ If `sync_script` is set in config.json, run it after logging.
129
+
154
130
  ---
155
131
 
156
- ## Workflow: Stats (`/social-autoposter stats`)
132
+ ## Workflow: Create Post (`/social-autoposter post`)
157
133
 
158
- Update engagement metrics for all posts. No browser needed.
134
+ **Manual only never run from cron.** Original posts are high-stakes and need human review.
159
135
 
160
- **Preferred: use the helper script:**
161
- ```bash
162
- python3 scripts/update_stats.py
163
- python3 scripts/update_stats.py --quiet # summary only
164
- python3 scripts/update_stats.py --json # machine-readable output
136
+ ### 1. Cross-posting check
137
+
138
+ ```sql
139
+ SELECT platform, thread_title, posted_at FROM posts
140
+ WHERE source_summary LIKE '%' || %s || '%' AND posted_at >= NOW() - INTERVAL '30 days'
141
+ ORDER BY posted_at DESC;
165
142
  ```
166
143
 
167
- This fetches:
168
- - **Reddit**: comment scores and thread stats via public JSON API. Detects deleted/removed.
169
- - **Moltbook**: upvotes and comment counts via REST API. Detects deleted posts.
144
+ **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.
170
145
 
171
- Updates `upvotes`, `comments_count`, `thread_engagement`, `engagement_updated_at` in the DB.
146
+ ### 3. Pick one target community
172
147
 
173
- For **X/Twitter** stats (requires browser): use `/social-autoposter audit` instead.
148
+ Choose the single best subreddit from `config.json → subreddits` for this topic. Tailor the post to that community's culture and tone.
174
149
 
175
- ---
150
+ ### 4. Draft the post
176
151
 
177
- ## Workflow: Engage (`/social-autoposter engage`)
152
+ **Anti-AI-detection checklist** (must pass ALL before posting):
153
+
154
+ - [ ] No em dashes (—). Use regular dashes (-) or commas instead
155
+ - [ ] No markdown headers (##) or bold (**) in Reddit posts
156
+ - [ ] No numbered/bulleted lists — write in paragraphs
157
+ - [ ] No "Hi everyone" or "Hey r/subreddit" openings
158
+ - [ ] Title doesn't use clickbait patterns ("What I wish I'd known", "A guide to")
159
+ - [ ] Contains at least one imperfection: incomplete thought, casual aside, informality
160
+ - [ ] Reads like a real person writing on their phone, not an essay
161
+ - [ ] Does NOT link to any project in the post body — earn attention first
162
+ - [ ] Not too long — 2-4 short paragraphs max for Reddit
163
+
164
+ **Read it out loud.** If it sounds like a blog post or a ChatGPT response, rewrite it.
165
+
166
+ ### 5. Post it
167
+
168
+ **Reddit**: old.reddit.com → Submit new text post → paste title + body → submit → verify → capture permalink.
169
+
170
+ ### 6. Log it
171
+
172
+ ```sql
173
+ INSERT INTO posts (platform, thread_url, thread_author, thread_author_handle,
174
+ 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());
177
+ ```
178
+
179
+ For original posts: `thread_url` = `our_url`, `thread_author` = our account from config.json.
178
180
 
179
- Discover replies to our posts and respond to them.
181
+ ### 7. Mandatory engagement plan
180
182
 
181
- ### Phase A: Scan for replies (no browser needed)
183
+ After posting, you MUST:
184
+ - Check for comments within 2-4 hours
185
+ - Reply to every substantive comment within 24 hours
186
+ - Replies should be casual, conversational, expand the topic — NOT polished paragraphs
187
+ - If someone accuses the post of being AI: respond genuinely, mention a specific personal detail
188
+
189
+ ---
190
+
191
+ ## Workflow: Stats (`/social-autoposter stats`)
182
192
 
183
193
  ```bash
184
- python3 scripts/scan_replies.py
194
+ python3 ~/social-autoposter/scripts/update_stats.py
185
195
  ```
186
196
 
187
- This scans Reddit JSON API + Moltbook API for new replies. Inserts into `replies` table as `pending` or `skipped`.
197
+ After running, view updated stats at `https://s4l.ai/stats/[handle]`. The DB syncs to Neon Postgres via `syncfield.sh` (called automatically by `stats.sh`). Changes appear on the website within ~5 minutes.
198
+
199
+ ---
200
+
201
+ ## Workflow: Engage (`/social-autoposter engage`)
188
202
 
189
- Skip reasons: `too_short` (<5 words), `filtered_author` (AutoModerator, [deleted], self), `too_old` (>7 days), `deleted`.
203
+ ### Phase A: Scan for replies (no browser)
204
+ ```bash
205
+ python3 ~/social-autoposter/scripts/scan_replies.py
206
+ ```
190
207
 
191
- ### Phase B: Respond to pending replies (needs browser/API)
208
+ ### Phase B: Respond to pending replies
192
209
 
193
- Query pending replies:
194
210
  ```sql
195
211
  SELECT r.id, r.platform, r.their_author, r.their_content, r.their_comment_url,
196
212
  r.depth, p.thread_title, p.our_content
197
- FROM replies r
198
- JOIN posts p ON r.post_id = p.id
199
- WHERE r.status='pending'
200
- ORDER BY r.discovered_at ASC LIMIT 10
213
+ FROM replies r JOIN posts p ON r.post_id = p.id
214
+ WHERE r.status='pending' ORDER BY r.discovered_at ASC LIMIT 10
201
215
  ```
202
216
 
203
- For each pending reply:
204
- 1. Draft a response (2-4 sentences, casual, expand the topic, ask follow-ups)
205
- 2. Apply the **Tiered Reply Strategy** for project mentions
206
- 3. Post via browser (Reddit/X) or API (Moltbook)
207
- 4. Update the reply record:
208
- ```sql
209
- UPDATE replies SET status='replied', our_reply_content=%s, our_reply_url=%s,
210
- replied_at=NOW() WHERE id=%s
211
- ```
217
+ Draft replies: 2-4 sentences, casual, expand the topic. Apply Tiered Reply Strategy. Max 5 replies per run.
212
218
 
213
- Max 5 replies per run.
219
+ Post via browser (Reddit/X) or API (Moltbook). Update:
220
+ ```sql
221
+ UPDATE replies SET status='replied', our_reply_content=%s, our_reply_url=%s,
222
+ replied_at=NOW() WHERE id=%s
223
+ ```
214
224
 
215
225
  ### Phase C: X/Twitter replies (browser required)
216
226
 
217
- X has no public API for notifications. To discover X replies:
218
- 1. Navigate to `https://x.com/notifications/mentions`
219
- 2. Extract mentions replying to your account
220
- 3. Filter out already-tracked reply IDs, light acknowledgments, and your own replies
221
- 4. Respond to substantive replies (max 5)
222
- 5. Log everything to `replies` table
227
+ Navigate to `https://x.com/notifications/mentions`. Find replies to the handle in config.json. Respond to substantive ones (max 5). Log to `replies` table.
223
228
 
224
229
  ---
225
230
 
226
231
  ## Workflow: Audit (`/social-autoposter audit`)
227
232
 
228
- Full browser-based audit of all posts. Use for X/Twitter stats or visual verification.
229
-
230
- 1. Query all posts with URLs:
231
- ```sql
232
- SELECT id, platform, our_url, status, upvotes, views, comments_count
233
- FROM posts WHERE our_url IS NOT NULL ORDER BY posted_at DESC
234
- ```
235
-
236
- 2. Visit each URL via browser automation. Check:
237
- - `active`: visible and accessible
238
- - `deleted`: 404 or "this tweet has been deleted"
239
- - `removed`: marked as removed by moderator
240
- - `inactive`: thread locked or archived
241
-
242
- 3. Capture engagement metrics (upvotes, views, comments) and update DB.
243
-
244
- 4. Report summary: total checked, by status, top performers.
233
+ Visit each post URL via browser. Check status (active/deleted/removed/inactive). Update engagement metrics. Report summary.
245
234
 
246
235
  ---
247
236
 
248
237
  ## Content Rules
249
238
 
250
- 1. **Write like you're texting a coworker.** Lowercase fine. Sentence fragments fine. Never start with "Makes sense" or "The nuance here is." If it sounds like a blog post, rewrite it.
251
-
252
- 2. **First person, specific.** Use concrete numbers and real experiences, not generalizations. Say "I" not "you" or "one."
253
-
254
- 3. **Reply to top comments, not just OP.** High-upvote comments get more eyeballs.
255
-
256
- 4. **Only comment when you have a real angle from your work.** Use `content_angle` from `config.json`. If the thread doesn't connect to your experience, skip it.
239
+ ### Tone & Voice
257
240
 
258
- 5. **No product links in top-level comments.** Earn attention first. Links come later via the Tiered Reply Strategy.
241
+ 1. **Write like you're texting a coworker.** Lowercase fine. Sentence fragments fine. Never start with "Makes sense" or "The nuance here is." If it sounds like a blog post, rewrite it.
242
+ 2. **First person, specific.** Use concrete details from `content_angle` in config.json. Real numbers, real experiences, not generalizations.
243
+ 3. **Reply to top comments, not just OP.**
244
+ 4. **Only comment when there's a genuine angle from the user's work.** Use `content_angle` from config.json. If the thread doesn't connect, skip it.
245
+ 5. **No product links in top-level comments or original posts.** Earn attention first. Only link in deeper replies when someone asks.
246
+ 6. **On Moltbook, write as an agent.** "my human" not "I".
247
+ 7. **Log everything.**
259
248
 
260
- 6. **On Moltbook, write as an agent.** Use "my human" not "I". First-person agent perspective.
249
+ ### Anti-AI-Detection (applies to ALL content)
261
250
 
262
- 7. **Log everything.** Every thread and comment goes in the database.
251
+ 8. **No em dashes (—).** Use commas, periods, or regular dashes (-) instead. Em dashes are the #1 "ChatGPT tell."
252
+ 9. **No markdown formatting in Reddit.** No headers (##), no bold (**text**), no numbered lists. Write in plain paragraphs.
253
+ 10. **Never cross-post.** One post per topic per community.
254
+ 11. **Space posts out.** Max 1 original post per day, max 3 per week. Don't spam.
255
+ 12. **Include imperfections.** Contractions, sentence fragments, casual asides, occasional lowercase.
256
+ 13. **Vary your openings.** Don't always start with credentials. Sometimes just jump into the topic.
257
+ 14. **Reply to comments on your posts.** Zero engagement on your own post = bot signal. Reply within 24h.
263
258
 
264
- ### Bad vs Good
259
+ ### Bad vs Good (Comments)
265
260
 
266
- BAD: "Makes sense — Claude already tries to `| tail -n 50` on its own but by then the tokens are already in context."
267
- GOOD: "gonna try this I run 5 agents in parallel and my API bill is becoming a second rent payment"
261
+ BAD: "Makes sense — Claude already tries to tail the output on its own but by then the tokens are already in context."
262
+ GOOD: "gonna try this, I run 5 agents in parallel and my API bill is becoming a second rent payment"
268
263
 
269
264
  BAD: "What everyone here is describing is basically specification-driven development."
270
265
  GOOD: "I spend more time writing CLAUDE.md specs than I ever spent writing code. the irony is I'm basically doing waterfall now and shipping faster than ever."
271
266
 
272
- ---
273
-
274
- ## Tiered Reply Strategy
275
-
276
- When replying to comments on our posts, escalate project mentions based on conversation context.
277
-
278
- **Tier 1 — Default (no link):** Genuine engagement. Expand the topic, ask follow-ups. Most replies.
267
+ ### Bad vs Good (Original Posts)
279
268
 
280
- **Tier 2 Natural mention:** Conversation touches something you're building. Mention the project casually. Include link only if it adds value. Triggers:
281
- - "what are you working on" / "what tool do you use"
282
- - They describe a problem matching a project's `topics` from `config.json`
283
- - Conversation is 2+ replies deep (genuine interest)
269
+ BAD title: "What I Wish I'd Known Before My First Vipassana Retreat: A Complete Guide"
270
+ GOOD title: "just did my 7th course, some things that surprised me"
284
271
 
285
- **Tier 3 Direct ask:** They explicitly ask for a link, to try it, if it's open source. Give it immediately.
272
+ BAD body: Structured with headers, bold, numbered lists, "As a tech founder..."
273
+ GOOD body: Paragraphs, incomplete thoughts, personal details, casual tone, ends with a genuine question
286
274
 
287
275
  ---
288
276
 
289
- ## Database Schema
277
+ ## Tiered Reply Strategy
290
278
 
291
- ```sql
292
- -- Core tables (see schema.sql for full DDL)
293
- posts -- everything we post (platform, urls, content, engagement, status)
294
- threads -- threads we've discovered
295
- our_posts -- backward-compat post tracking
296
- replies -- replies to our posts and our responses
297
- ```
279
+ **Tier 1 — Default (no link):** Genuine engagement. Expand topic, ask follow-ups. Most replies.
298
280
 
299
- Key fields in `posts`: `id, platform, thread_url, thread_title, our_url, our_content, our_account, posted_at, status, upvotes, comments_count, views, source_summary`
281
+ **Tier 2 Natural mention:** Conversation touches a topic matching one of the user's projects (from `config.json → projects[].topics`). Mention casually, link only if it adds value. Triggers: "what tool do you use", problem matches a project topic, 2+ replies deep.
300
282
 
301
- Key fields in `replies`: `id, post_id, platform, their_author, their_content, our_reply_content, status (pending|replied|skipped|error), depth`
283
+ **Tier 3 Direct ask:** They ask for link/try/source. Give it immediately using `projects[].website` or `projects[].github` from config.json.
302
284
 
303
285
  ---
304
286
 
305
- ## Platform Reference
306
-
307
- **Reddit:** Use `old.reddit.com` for reliable automation. Comment box: textarea with class `usertext-edit`. No posting API — browser only.
308
-
309
- **X/Twitter:** Reply to existing tweets. 1-2 sentences ideal. No public API for notifications — browser only.
287
+ ## Database Schema
310
288
 
311
- **LinkedIn:** Professional tone, brief. Comments don't have stable URLs. Browser only.
289
+ `posts`: id, platform, thread_url, thread_title, our_url, our_content, our_account, posted_at, status, upvotes, comments_count, views, source_summary
312
290
 
313
- **Moltbook:** Full REST API, no browser needed. Base: `https://www.moltbook.com/api/v1`. Auth: `Bearer $MOLTBOOK_API_KEY`. Agent-first platform — write as an agent.
291
+ `replies`: id, post_id, platform, their_author, their_content, our_reply_content, status (pending|replied|skipped|error), depth
package/bin/cli.js CHANGED
@@ -22,7 +22,7 @@ const COPY_TARGETS = [
22
22
  ];
23
23
 
24
24
  // Never overwrite these user files during update
25
- const USER_FILES = new Set(['config.json', '.env']);
25
+ const USER_FILES = new Set(['config.json', '.env', 'SKILL.md']);
26
26
 
27
27
  function copyDir(src, dest) {
28
28
  fs.mkdirSync(dest, { recursive: true });
@@ -170,11 +170,15 @@ function init() {
170
170
  console.log(' psycopg2-binary already installed');
171
171
  }
172
172
 
173
- // Skill symlinks
173
+ // Remove stale skill/SKILL.md if it exists (SKILL.md lives at repo root only)
174
+ const skillMd = path.join(DEST, 'skill', 'SKILL.md');
175
+ try { fs.rmSync(skillMd, { force: true }); } catch {}
176
+
177
+ // Skill symlinks — point to repo root so Claude loads SKILL.md directly
174
178
  const skillsDir = path.join(os.homedir(), '.claude', 'skills');
175
179
  fs.mkdirSync(skillsDir, { recursive: true });
176
- linkOrRelink(path.join(DEST, 'skill'), path.join(skillsDir, 'social-autoposter'));
177
- console.log(' ~/.claude/skills/social-autoposter ->', path.join(DEST, 'skill'));
180
+ linkOrRelink(DEST, path.join(skillsDir, 'social-autoposter'));
181
+ console.log(' ~/.claude/skills/social-autoposter ->', DEST);
178
182
  linkOrRelink(path.join(DEST, 'setup'), path.join(skillsDir, 'social-autoposter-setup'));
179
183
  console.log(' ~/.claude/skills/social-autoposter-setup ->', path.join(DEST, 'setup'));
180
184
 
@@ -214,10 +218,14 @@ function update() {
214
218
  // Regenerate launchd plists with correct paths
215
219
  generatePlists();
216
220
 
217
- // Re-symlink skill and setup skill in case they broke
221
+ // Remove stale skill/SKILL.md if it exists (SKILL.md lives at repo root only)
222
+ const skillMd = path.join(DEST, 'skill', 'SKILL.md');
223
+ try { fs.rmSync(skillMd, { force: true }); } catch {}
224
+
225
+ // Re-symlink skills — point to repo root so Claude loads SKILL.md directly
218
226
  const skillsDir = path.join(os.homedir(), '.claude', 'skills');
219
227
  try {
220
- linkOrRelink(path.join(DEST, 'skill'), path.join(skillsDir, 'social-autoposter'));
228
+ linkOrRelink(DEST, path.join(skillsDir, 'social-autoposter'));
221
229
  console.log(' re-linked ~/.claude/skills/social-autoposter');
222
230
  } catch {}
223
231
  try {
@@ -234,10 +242,13 @@ if (cmd === 'init') {
234
242
  init();
235
243
  } else if (cmd === 'update') {
236
244
  update();
245
+ } else if (!cmd) {
246
+ require('./server.js');
237
247
  } else {
238
248
  console.log('social-autoposter — automated social posting for Claude agents');
239
249
  console.log('');
240
250
  console.log('Usage:');
241
- console.log(' npx social-autoposter init first-time setup');
242
- console.log(' npx social-autoposter update update scripts, preserve config');
251
+ console.log(' npx social-autoposter open the dashboard');
252
+ console.log(' npx social-autoposter init first-time setup');
253
+ console.log(' npx social-autoposter update update scripts, preserve config');
243
254
  }