social-autoposter 1.1.3 → 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.
Files changed (58) hide show
  1. package/README.md +111 -55
  2. package/SKILL.md +38 -8
  3. package/bin/cli.js +53 -0
  4. package/bin/server.js +401 -31
  5. package/browser-agent-configs/linkedin-agent-mcp.json +16 -0
  6. package/browser-agent-configs/linkedin-agent.json +17 -0
  7. package/browser-agent-configs/reddit-agent-mcp.json +16 -0
  8. package/browser-agent-configs/reddit-agent.json +17 -0
  9. package/browser-agent-configs/twitter-agent-mcp.json +16 -0
  10. package/browser-agent-configs/twitter-agent.json +17 -0
  11. package/package.json +3 -2
  12. package/schema-postgres.sql +13 -0
  13. package/scripts/active_campaigns.py +110 -0
  14. package/scripts/backfill_twitter_urls.py +223 -0
  15. package/scripts/backfill_twitter_urls_v2.py +255 -0
  16. package/scripts/backfill_twitter_urls_v3.py +345 -0
  17. package/scripts/batch_send_dms.py +131 -0
  18. package/scripts/campaign_bump.py +50 -0
  19. package/scripts/check_link_rules.py +101 -0
  20. package/scripts/daily_stats_email.py +285 -0
  21. package/scripts/diagnose_linkedin_agent.py +196 -0
  22. package/scripts/engage_github.py +545 -0
  23. package/scripts/engage_reddit.py +476 -0
  24. package/scripts/engagement_styles.py +269 -0
  25. package/scripts/enrich_twitter_candidates.py +85 -0
  26. package/scripts/export_cdp_storage_state.py +190 -0
  27. package/scripts/find_tweets.py +154 -0
  28. package/scripts/github_tools.py +259 -0
  29. package/scripts/linkedin_api.py +389 -0
  30. package/scripts/linkedin_auth_check.py +415 -0
  31. package/scripts/linkedin_browser.py +1538 -0
  32. package/scripts/linkedin_cooldown.py +128 -0
  33. package/scripts/log_run.py +46 -0
  34. package/scripts/octolens_twitter_batch.py +193 -0
  35. package/scripts/octolens_twitter_cdp.py +152 -0
  36. package/scripts/octolens_twitter_read.py +46 -0
  37. package/scripts/pick_project.py +27 -2
  38. package/scripts/pick_thread_target.py +186 -0
  39. package/scripts/post_github.py +547 -0
  40. package/scripts/post_reddit.py +502 -0
  41. package/scripts/reconcile_reply_urls.py +114 -0
  42. package/scripts/reddit_browser.py +1579 -0
  43. package/scripts/reddit_tools.py +369 -0
  44. package/scripts/run_pieline_linkedin_batch.py +165 -0
  45. package/scripts/scan_linkedin_notifications.py +273 -0
  46. package/scripts/scan_twitter_mentions.py +175 -0
  47. package/scripts/score_twitter_candidates.py +272 -0
  48. package/scripts/top_performers.py +125 -7
  49. package/scripts/twitter_api.py +258 -0
  50. package/scripts/twitter_browser.py +848 -0
  51. package/scripts/twitter_compose_dm.py +332 -0
  52. package/setup/SKILL.md +0 -3
  53. package/skill/engage.sh +17 -16
  54. package/skill/run-github.sh +5 -83
  55. package/skill/run-linkedin.sh +34 -14
  56. package/skill/run-moltbook.sh +29 -14
  57. package/skill/run-reddit.sh +76 -21
  58. package/skill/run-twitter.sh +78 -27
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. Install as an AI agent skill or use the standalone Python scripts.
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
- 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.
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
- ## Install as a skill
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
- Then tell your agent: **"set up social autoposter"** — the setup skill walks you through config, DB creation, browser logins, and a test run.
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
- Or set up manually:
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
- ## How it works
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
- SKILL.md (the playbook)
31
-
32
- ├── /social-autoposter → find thread, draft, post, log
33
- ├── /social-autoposter stats → update engagement via API
34
- ├── /social-autoposter engage scan replies, respond
35
- └── /social-autoposter audit → browser-based full audit
36
-
37
- ├── scripts/find_threads.py thread discovery (no browser)
38
- ├── scripts/scan_replies.py → reply scanning (no browser)
39
- └── scripts/update_stats.py → stats fetching (no browser)
40
-
41
- ├── skill/run.sh → launchd wrapper (hourly)
42
- ├── skill/stats.sh → launchd wrapper (6-hourly)
43
- └── skill/engage.sh → launchd wrapper (2-hourly)
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
- ## Structure
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 <- skill playbook (generic, publishable)
51
- ├── config.example.json <- config template (accounts, subreddits, content angle)
52
- ├── schema-postgres.sql <- Neon Postgres DB schema
53
- ├── setup.sh <- creates symlinks, loads launchd agents
54
- ├── setup/
55
- │ └── SKILL.md <- interactive setup wizard skill
56
- ├── scripts/
57
- ├── db.py <- Neon Postgres connection wrapper
58
- │ ├── find_threads.py <- find candidate threads via Reddit/Moltbook API
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 is designed to work with any agent that has:
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
- The Python scripts handle thread discovery, reply scanning, and stats updates without needing a browser or LLM. The SKILL.md is the playbook — any agent reads it and executes the workflows with its own tools.
127
+ ## Pause and resume
78
128
 
79
- ## Accounts
129
+ Use the dashboard at `localhost:3141` (Pause All / Resume All button), or manually:
80
130
 
81
- - **Reddit**: u/Deep_Ad1959
82
- - **X/Twitter**: @m13v_
83
- - **LinkedIn**: Matthew Diakonov
84
- - **Moltbook**: matthew-autoposter
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', or after completing any task (mandatory per CLAUDE.md)."
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 only, never cron) |
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 |
@@ -121,15 +121,17 @@ Verify: fetch post by UUID, check `verification_status` is `"verified"`.
121
121
  ```sql
122
122
  INSERT INTO posts (platform, thread_url, thread_author, thread_author_handle,
123
123
  thread_title, thread_content, our_url, our_content, our_account,
124
- source_summary, project_name, status, posted_at)
125
- VALUES (%s, %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());
126
126
  ```
127
127
 
128
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
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
+
130
132
  Use the account value from `config.json` for `our_account`.
131
133
 
132
- If `sync_script` is set in config.json, run it after logging.
134
+ Posts are written directly to the Neon Postgres database. No separate post-sync step is required.
133
135
 
134
136
  ---
135
137
 
@@ -176,10 +178,12 @@ Choose the single best subreddit from `config.json → subreddits` for this topi
176
178
  ```sql
177
179
  INSERT INTO posts (platform, thread_url, thread_author, thread_author_handle,
178
180
  thread_title, thread_content, our_url, our_content, our_account,
179
- source_summary, project_name, status, posted_at)
180
- VALUES (%s, %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());
181
183
  ```
182
184
 
185
+ Set `engagement_style` to the style you chose (e.g., 'critic', 'storyteller', 'pattern_recognizer'). Every post MUST have an engagement_style.
186
+
183
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.
184
188
 
185
189
  ### 6. Mandatory engagement plan
@@ -192,13 +196,39 @@ After posting, you MUST:
192
196
 
193
197
  ---
194
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
+
195
225
  ## Workflow: Stats (`/social-autoposter stats`)
196
226
 
197
227
  ```bash
198
228
  python3 ~/social-autoposter/scripts/update_stats.py
199
229
  ```
200
230
 
201
- 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.
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.
202
232
 
203
233
  ---
204
234
 
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);
@@ -128,6 +175,9 @@ function init() {
128
175
  // Generate launchd plists with user's actual HOME
129
176
  generatePlists();
130
177
 
178
+ // Install browser agent MCP configs + profile dirs (skips existing files)
179
+ installBrowserAgentConfigs();
180
+
131
181
  // config.json — only if it doesn't exist
132
182
  const configDest = path.join(DEST, 'config.json');
133
183
  if (!fs.existsSync(configDest)) {
@@ -209,6 +259,9 @@ function update() {
209
259
  // Regenerate launchd plists with correct paths
210
260
  generatePlists();
211
261
 
262
+ // Top up browser agent configs (won't overwrite user customizations)
263
+ installBrowserAgentConfigs();
264
+
212
265
  // Remove stale skill/SKILL.md if it exists (SKILL.md lives at repo root only)
213
266
  const skillMd = path.join(DEST, 'skill', 'SKILL.md');
214
267
  try { fs.rmSync(skillMd, { force: true }); } catch {}