@m13v/s4l 1.6.197-rc.10

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 (326) hide show
  1. package/README.md +143 -0
  2. package/SKILL.md +342 -0
  3. package/bin/cli.js +980 -0
  4. package/bin/cookie-helper.js +315 -0
  5. package/bin/platform.js +59 -0
  6. package/bin/scheduler/index.js +12 -0
  7. package/bin/scheduler/launchd.js +518 -0
  8. package/browser-agent-configs/all-agents-mcp.json +68 -0
  9. package/browser-agent-configs/linkedin-agent-mcp.json +16 -0
  10. package/browser-agent-configs/linkedin-agent.json +17 -0
  11. package/browser-agent-configs/linkedin-harness-mcp.json +21 -0
  12. package/browser-agent-configs/reddit-agent-mcp.json +16 -0
  13. package/browser-agent-configs/reddit-agent.json +17 -0
  14. package/browser-agent-configs/twitter-harness-mcp.json +18 -0
  15. package/config.example.json +45 -0
  16. package/mcp/dist/index.js +4212 -0
  17. package/mcp/dist/onboarding.js +200 -0
  18. package/mcp/dist/panel.html +176 -0
  19. package/mcp/dist/product-link.html +102 -0
  20. package/mcp/dist/repo.js +222 -0
  21. package/mcp/dist/runtime.js +1079 -0
  22. package/mcp/dist/screencast.js +323 -0
  23. package/mcp/dist/setup.js +545 -0
  24. package/mcp/dist/telemetry.js +306 -0
  25. package/mcp/dist/twitterAuth.js +138 -0
  26. package/mcp/dist/version.js +271 -0
  27. package/mcp/dist/version.json +4 -0
  28. package/mcp/install-runtime.mjs +70 -0
  29. package/mcp/install.mjs +169 -0
  30. package/mcp/manifest.json +80 -0
  31. package/mcp/menubar/dashboard_server.py +213 -0
  32. package/mcp/menubar/s4l_card.py +1336 -0
  33. package/mcp/menubar/s4l_log_relay.py +179 -0
  34. package/mcp/menubar/s4l_menubar.py +2439 -0
  35. package/mcp/menubar/s4l_state.py +891 -0
  36. package/mcp/package.json +34 -0
  37. package/mcp/shared/doctor.cjs +437 -0
  38. package/mcp/shared/onboarding-ledger.cjs +324 -0
  39. package/mcp-servers/browser-harness/server.py +968 -0
  40. package/package.json +160 -0
  41. package/requirements.txt +20 -0
  42. package/scripts/_compute_allowlist.py +58 -0
  43. package/scripts/_db_update.py +20 -0
  44. package/scripts/_filt.py +9 -0
  45. package/scripts/_li_notif_match.py +76 -0
  46. package/scripts/_li_notif_orchestrate.py +126 -0
  47. package/scripts/_lock_preempt_test.py +60 -0
  48. package/scripts/_run_icp_precheck.py +57 -0
  49. package/scripts/a16z_pearx_calendar_reminders.py +99 -0
  50. package/scripts/account_resolver.py +141 -0
  51. package/scripts/active_campaigns.py +114 -0
  52. package/scripts/active_users.py +190 -0
  53. package/scripts/amplitude_24h_signups.py +468 -0
  54. package/scripts/amplitude_signups.py +177 -0
  55. package/scripts/apply_onboarding_selections.py +131 -0
  56. package/scripts/audience_pages.py +243 -0
  57. package/scripts/audit_helper.py +120 -0
  58. package/scripts/author_history_block.py +353 -0
  59. package/scripts/autopilot_stall_watch.py +284 -0
  60. package/scripts/backfill_twitter_attempts_topic.py +81 -0
  61. package/scripts/backfill_twitter_log_post_no_id.py +322 -0
  62. package/scripts/bench_dashboard.sh +138 -0
  63. package/scripts/bh_send.py +39 -0
  64. package/scripts/build_persona.py +409 -0
  65. package/scripts/bulk_icp.py +18 -0
  66. package/scripts/campaign_bump.py +51 -0
  67. package/scripts/capture_thread_media.py +288 -0
  68. package/scripts/check_browser_lock_health.sh +81 -0
  69. package/scripts/check_external_pool_depth.py +253 -0
  70. package/scripts/check_unread_web_chats.py +28 -0
  71. package/scripts/claim_web_chat.py +47 -0
  72. package/scripts/classify_run_error.py +158 -0
  73. package/scripts/claude_job.py +988 -0
  74. package/scripts/clean_stale_singleton.sh +56 -0
  75. package/scripts/cleanup_harness_tabs.py +68 -0
  76. package/scripts/copy_browser_cookies.py +454 -0
  77. package/scripts/counterparty_history.py +350 -0
  78. package/scripts/db.py +57 -0
  79. package/scripts/discover_claude_profiles.py +120 -0
  80. package/scripts/discover_linkedin_candidates.py +984 -0
  81. package/scripts/dm_conversation.py +682 -0
  82. package/scripts/dm_db_update.py +69 -0
  83. package/scripts/dm_engage_helper.py +161 -0
  84. package/scripts/dm_outreach_helper.py +147 -0
  85. package/scripts/dm_outreach_twitter_helper.py +129 -0
  86. package/scripts/dm_send_log.py +106 -0
  87. package/scripts/dm_short_links.py +1084 -0
  88. package/scripts/dump_web_chat_history.py +47 -0
  89. package/scripts/engage_github.py +640 -0
  90. package/scripts/engage_reddit.py +1235 -0
  91. package/scripts/engage_twitter_helper.py +301 -0
  92. package/scripts/engagement_styles.py +1787 -0
  93. package/scripts/enrich_twitter_candidates.py +82 -0
  94. package/scripts/feedback_digest.py +448 -0
  95. package/scripts/fetch_prospect_profile.py +312 -0
  96. package/scripts/fetch_twitter_t1.py +134 -0
  97. package/scripts/find_threads.py +530 -0
  98. package/scripts/follow_gate_log.py +59 -0
  99. package/scripts/funnel_per_day.py +194 -0
  100. package/scripts/generate_daily_human_style.py +494 -0
  101. package/scripts/generation_trace.py +173 -0
  102. package/scripts/get_run_cost.py +107 -0
  103. package/scripts/github_engage_helper.py +93 -0
  104. package/scripts/github_tools.py +509 -0
  105. package/scripts/harness_overlay.py +556 -0
  106. package/scripts/harvest_twitter_following.py +243 -0
  107. package/scripts/heartbeat.sh +70 -0
  108. package/scripts/history_context.py +284 -0
  109. package/scripts/http_api.py +206 -0
  110. package/scripts/human_dm_replies_helper.py +169 -0
  111. package/scripts/identity.py +302 -0
  112. package/scripts/ig_batch_creator.sh +93 -0
  113. package/scripts/ig_post_type_picker.py +243 -0
  114. package/scripts/ig_scrape_transcribe.sh +91 -0
  115. package/scripts/ingest_human_dm_replies.py +271 -0
  116. package/scripts/ingest_web_chat_replies.py +229 -0
  117. package/scripts/install_fleet.py +187 -0
  118. package/scripts/invent_mcp_server.py +350 -0
  119. package/scripts/invent_topics.py +1462 -0
  120. package/scripts/learned_preferences.py +263 -0
  121. package/scripts/li_discovery.py +161 -0
  122. package/scripts/link_edit_helper.py +142 -0
  123. package/scripts/link_tail.py +592 -0
  124. package/scripts/linkedin_api.py +561 -0
  125. package/scripts/linkedin_browser.py +730 -0
  126. package/scripts/linkedin_cooldown.py +128 -0
  127. package/scripts/linkedin_exclusions.py +234 -0
  128. package/scripts/linkedin_killswitch.py +1333 -0
  129. package/scripts/linkedin_search_topic_schema.py +49 -0
  130. package/scripts/linkedin_unipile.py +658 -0
  131. package/scripts/linkedin_url.py +228 -0
  132. package/scripts/log_claude_session.py +636 -0
  133. package/scripts/log_draft.py +143 -0
  134. package/scripts/log_linkedin_search_attempts.py +126 -0
  135. package/scripts/log_post.py +651 -0
  136. package/scripts/log_run.py +364 -0
  137. package/scripts/log_thread_media.py +108 -0
  138. package/scripts/log_twitter_search_attempts.py +150 -0
  139. package/scripts/log_twitter_skips.py +211 -0
  140. package/scripts/lookup_post.py +78 -0
  141. package/scripts/mark_web_chat_processed.py +32 -0
  142. package/scripts/mcp_lock_proxy.py +370 -0
  143. package/scripts/memory_snapshot.py +972 -0
  144. package/scripts/merge_review_queue.py +215 -0
  145. package/scripts/mint_external_pool.py +182 -0
  146. package/scripts/mint_kent_pool.py +249 -0
  147. package/scripts/moltbook_post.py +320 -0
  148. package/scripts/moltbook_tools.py +159 -0
  149. package/scripts/pending_threads.py +188 -0
  150. package/scripts/pick_ig_account.py +177 -0
  151. package/scripts/pick_project.py +208 -0
  152. package/scripts/pick_search_topic.py +771 -0
  153. package/scripts/pick_thread_target.py +279 -0
  154. package/scripts/pick_twitter_thread_target.py +202 -0
  155. package/scripts/podlog_fetch_batch.sh +32 -0
  156. package/scripts/post_github.py +1311 -0
  157. package/scripts/post_reddit.py +2668 -0
  158. package/scripts/precompute_dashboard_stats.py +204 -0
  159. package/scripts/preflight.sh +297 -0
  160. package/scripts/progress.py +88 -0
  161. package/scripts/project_excludes.py +353 -0
  162. package/scripts/project_slugs.py +91 -0
  163. package/scripts/project_stats.py +241 -0
  164. package/scripts/project_stats_json.py +1563 -0
  165. package/scripts/project_topics.py +192 -0
  166. package/scripts/qualified_query_bank.py +436 -0
  167. package/scripts/reap_stale_claude_sessions.py +867 -0
  168. package/scripts/reddit_browser.py +2549 -0
  169. package/scripts/reddit_browser_fetch.py +141 -0
  170. package/scripts/reddit_browser_lock.py +593 -0
  171. package/scripts/reddit_chat_sync.py +710 -0
  172. package/scripts/reddit_query_bank.py +200 -0
  173. package/scripts/reddit_threads_helper.py +151 -0
  174. package/scripts/reddit_tools.py +956 -0
  175. package/scripts/refresh_instagram_tokens.py +280 -0
  176. package/scripts/release-mcpb.sh +513 -0
  177. package/scripts/reply_db.py +334 -0
  178. package/scripts/reply_insert.py +98 -0
  179. package/scripts/reply_risk_digest.py +761 -0
  180. package/scripts/reset-test-machine.sh +602 -0
  181. package/scripts/restore_twitter_session.py +177 -0
  182. package/scripts/ripen_reddit_plan.py +478 -0
  183. package/scripts/run_claude.sh +433 -0
  184. package/scripts/run_moltbook_cycle.py +555 -0
  185. package/scripts/s4l_box_update.sh +226 -0
  186. package/scripts/s4l_channel.py +103 -0
  187. package/scripts/s4l_ctl.sh +75 -0
  188. package/scripts/s4l_env.py +47 -0
  189. package/scripts/saps_activity.py +126 -0
  190. package/scripts/saps_mode.py +328 -0
  191. package/scripts/scan_dm_candidates.py +580 -0
  192. package/scripts/scan_github_replies.py +168 -0
  193. package/scripts/scan_instagram_comments.py +481 -0
  194. package/scripts/scan_moltbook_replies.py +252 -0
  195. package/scripts/scan_pii.py +190 -0
  196. package/scripts/scan_reddit_replies.py +377 -0
  197. package/scripts/scan_twitter_mentions_browser.py +327 -0
  198. package/scripts/scan_twitter_thread_followups.py +299 -0
  199. package/scripts/scan_x_profile.py +384 -0
  200. package/scripts/schedule_state.py +202 -0
  201. package/scripts/scheduled_tasks_snapshot.py +123 -0
  202. package/scripts/score_linkedin_candidates.py +419 -0
  203. package/scripts/score_twitter_candidates.py +718 -0
  204. package/scripts/scrape_linkedin_comment_stats.py +1755 -0
  205. package/scripts/scrape_linkedin_stats_browser.py +52 -0
  206. package/scripts/scrape_reddit_views.py +365 -0
  207. package/scripts/seed_search_queries.py +453 -0
  208. package/scripts/seed_search_topics.py +127 -0
  209. package/scripts/send_web_chat_reply.py +130 -0
  210. package/scripts/sentry_init.py +128 -0
  211. package/scripts/setup_twitter_auth.py +1320 -0
  212. package/scripts/snapshot.py +583 -0
  213. package/scripts/stats.py +2702 -0
  214. package/scripts/stats_helper.py +52 -0
  215. package/scripts/strike_alert.py +783 -0
  216. package/scripts/sweep_post_link_clicks.py +107 -0
  217. package/scripts/sync_ig_to_posts.py +147 -0
  218. package/scripts/test_browser_lock.py +189 -0
  219. package/scripts/test_installation_api.sh +52 -0
  220. package/scripts/test_percard_posting.py +142 -0
  221. package/scripts/top_dud_linkedin_queries.py +71 -0
  222. package/scripts/top_dud_reddit_queries.py +67 -0
  223. package/scripts/top_dud_twitter_queries.py +71 -0
  224. package/scripts/top_dud_twitter_topics.py +102 -0
  225. package/scripts/top_linkedin_queries.py +55 -0
  226. package/scripts/top_omitted_reddit_topics.py +91 -0
  227. package/scripts/top_performers.py +588 -0
  228. package/scripts/top_search_topics.py +180 -0
  229. package/scripts/top_twitter_queries.py +190 -0
  230. package/scripts/twitter_access_check.py +382 -0
  231. package/scripts/twitter_account.py +41 -0
  232. package/scripts/twitter_batch_phase.py +126 -0
  233. package/scripts/twitter_browser.py +2804 -0
  234. package/scripts/twitter_cookie_mirror.py +130 -0
  235. package/scripts/twitter_cycle_helper.py +310 -0
  236. package/scripts/twitter_gen_links.py +287 -0
  237. package/scripts/twitter_post_plan.py +1188 -0
  238. package/scripts/twitter_scan.py +324 -0
  239. package/scripts/twitter_supply_signal.py +57 -0
  240. package/scripts/twitter_threads_helper.py +152 -0
  241. package/scripts/unclaim_web_chat.py +29 -0
  242. package/scripts/update_instagram_stats.py +261 -0
  243. package/scripts/update_linkedin_stats_from_feed.py +328 -0
  244. package/scripts/version.py +72 -0
  245. package/scripts/watchdog_hung_runs.py +343 -0
  246. package/scripts/write_generation_trace.py +73 -0
  247. package/setup/SKILL.md +277 -0
  248. package/skill/amplitude-24h-signups.sh +38 -0
  249. package/skill/archive-old-logs.sh +40 -0
  250. package/skill/audit-dm-staleness.sh +42 -0
  251. package/skill/audit-linkedin.sh +14 -0
  252. package/skill/audit-moltbook.sh +4 -0
  253. package/skill/audit-reddit-resurrect.sh +67 -0
  254. package/skill/audit-reddit.sh +4 -0
  255. package/skill/audit-twitter.sh +4 -0
  256. package/skill/audit.sh +287 -0
  257. package/skill/backfill-twitter-attempts-topic.sh +19 -0
  258. package/skill/backfill-twitter-ghost-posts.sh +24 -0
  259. package/skill/check-external-pool-depth.sh +7 -0
  260. package/skill/check-web-chats.sh +203 -0
  261. package/skill/dm-outreach-linkedin.sh +250 -0
  262. package/skill/dm-outreach-reddit.sh +274 -0
  263. package/skill/dm-outreach-twitter.sh +265 -0
  264. package/skill/engage-dm-replies-linkedin.sh +4 -0
  265. package/skill/engage-dm-replies-reddit.sh +4 -0
  266. package/skill/engage-dm-replies-twitter.sh +4 -0
  267. package/skill/engage-dm-replies.sh +1597 -0
  268. package/skill/engage-linkedin.sh +581 -0
  269. package/skill/engage-moltbook.sh +36 -0
  270. package/skill/engage-reddit.sh +146 -0
  271. package/skill/engage-twitter.sh +467 -0
  272. package/skill/github-engage.sh +176 -0
  273. package/skill/ingest-web-chat-replies.sh +38 -0
  274. package/skill/invent-supply-test.sh +100 -0
  275. package/skill/invent-topics.sh +50 -0
  276. package/skill/lib/linkedin-backend.sh +364 -0
  277. package/skill/lib/platform.sh +48 -0
  278. package/skill/lib/reddit-backend.sh +234 -0
  279. package/skill/lib/twitter-backend.sh +314 -0
  280. package/skill/link-edit-github.sh +136 -0
  281. package/skill/link-edit-moltbook.sh +117 -0
  282. package/skill/link-edit-reddit.sh +201 -0
  283. package/skill/linkedin-presence.sh +182 -0
  284. package/skill/linkedin-recovery.sh +282 -0
  285. package/skill/lock.sh +647 -0
  286. package/skill/memory-snapshot.sh +39 -0
  287. package/skill/precompute-stats.sh +35 -0
  288. package/skill/prewarm-funnel.sh +104 -0
  289. package/skill/refresh-instagram-tokens.sh +57 -0
  290. package/skill/refresh-twitter-following.sh +52 -0
  291. package/skill/reply-risk-digest.sh +31 -0
  292. package/skill/run-cycle-update-guard.sh +44 -0
  293. package/skill/run-draft-and-publish.sh +123 -0
  294. package/skill/run-generate-daily-style.sh +50 -0
  295. package/skill/run-github-launchd.sh +62 -0
  296. package/skill/run-github.sh +102 -0
  297. package/skill/run-instagram-daily.sh +149 -0
  298. package/skill/run-instagram-render.sh +875 -0
  299. package/skill/run-linkedin-launchd.sh +81 -0
  300. package/skill/run-linkedin-unipile.sh +130 -0
  301. package/skill/run-linkedin.sh +1593 -0
  302. package/skill/run-moltbook-launchd.sh +61 -0
  303. package/skill/run-moltbook.sh +38 -0
  304. package/skill/run-overlay-watch.sh +100 -0
  305. package/skill/run-reddit-search-launchd.sh +64 -0
  306. package/skill/run-reddit-search.sh +505 -0
  307. package/skill/run-reddit-threads-double.sh +32 -0
  308. package/skill/run-reddit-threads.sh +847 -0
  309. package/skill/run-scan-moltbook-replies.sh +57 -0
  310. package/skill/run-twitter-cycle-launchd.sh +63 -0
  311. package/skill/run-twitter-cycle-singleton.sh +62 -0
  312. package/skill/run-twitter-cycle.sh +2408 -0
  313. package/skill/run-twitter-threads.sh +592 -0
  314. package/skill/scan-instagram-replies.sh +61 -0
  315. package/skill/scan-twitter-followups.sh +57 -0
  316. package/skill/social-autoposter-update.sh +66 -0
  317. package/skill/stats-instagram.sh +72 -0
  318. package/skill/stats-linkedin.sh +271 -0
  319. package/skill/stats-moltbook.sh +4 -0
  320. package/skill/stats-reddit.sh +4 -0
  321. package/skill/stats-twitter.sh +4 -0
  322. package/skill/stats.sh +521 -0
  323. package/skill/strike-alert.sh +18 -0
  324. package/skill/styles.sh +87 -0
  325. package/skill/sweep-link-clicks.sh +40 -0
  326. package/skill/topics.sh +51 -0
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Export and import browser cookies for social-autoposter profiles.
6
+ *
7
+ * Export: connects to running Playwright browser instances via CDP to extract
8
+ * cookies. Falls back to launching headless Chrome if no running browser found.
9
+ *
10
+ * Import: launches headless Chromium per platform profile, injects cookies
11
+ * via CDP (Network.setCookies) so they persist to the profile on disk.
12
+ */
13
+
14
+ const { spawn, spawnSync } = require('child_process');
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const os = require('os');
18
+ const http = require('http');
19
+
20
+ const HOME = os.homedir();
21
+ const DEST = path.join(HOME, 'social-autoposter');
22
+ const PROFILES_DIR = path.join(HOME, '.claude', 'browser-profiles');
23
+ const PLATFORMS = ['reddit', 'twitter', 'linkedin'];
24
+
25
+ // ── WebSocket ──
26
+
27
+ let WS;
28
+ try {
29
+ if (typeof globalThis.WebSocket !== 'undefined') {
30
+ WS = globalThis.WebSocket;
31
+ } else {
32
+ try { WS = require('ws'); } catch {
33
+ try { WS = require(path.join('/usr/lib/node_modules', 'ws')); } catch {}
34
+ }
35
+ }
36
+ } catch {}
37
+
38
+ // ── CDP helpers ──
39
+
40
+ function httpGet(url) {
41
+ return new Promise((resolve, reject) => {
42
+ const req = http.get(url, (res) => {
43
+ let d = '';
44
+ res.on('data', c => { d += c; });
45
+ res.on('end', () => { try { resolve(JSON.parse(d)); } catch (e) { reject(e); } });
46
+ });
47
+ req.on('error', reject);
48
+ req.setTimeout(3000, () => { req.destroy(); reject(new Error('timeout')); });
49
+ });
50
+ }
51
+
52
+ function cdpSend(wsUrl, method, params = {}) {
53
+ if (!WS) throw new Error('No WebSocket. Install ws: npm install -g ws');
54
+ return new Promise((resolve, reject) => {
55
+ const ws = new WS(wsUrl);
56
+ let done = false;
57
+ const timer = setTimeout(() => { if (!done) { done = true; ws.close(); reject(new Error(`CDP ${method} timeout`)); } }, 15000);
58
+ ws.on('open', () => ws.send(JSON.stringify({ id: 1, method, params })));
59
+ ws.on('message', (raw) => {
60
+ if (done) return;
61
+ const msg = JSON.parse(typeof raw === 'string' ? raw : raw.toString());
62
+ if (msg.id === 1) {
63
+ done = true; clearTimeout(timer); ws.close();
64
+ msg.error ? reject(new Error(`CDP: ${msg.error.message}`)) : resolve(msg.result);
65
+ }
66
+ });
67
+ ws.on('error', (e) => { if (!done) { done = true; clearTimeout(timer); reject(e); } });
68
+ });
69
+ }
70
+
71
+ // ── Find running browser CDP port ──
72
+
73
+ function findRunningBrowserPort(platform) {
74
+ const profileDir = path.join(PROFILES_DIR, platform);
75
+ // Parse ps output to find Chrome with this profile's user-data-dir
76
+ const ps = spawnSync('ps', ['aux'], { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 });
77
+ if (ps.status !== 0) return null;
78
+
79
+ for (const line of ps.stdout.split('\n')) {
80
+ if (!line.includes(`user-data-dir=${profileDir}`) || !line.includes('--remote-debugging-port=')) continue;
81
+ // Only match the main Chrome process (not renderer helpers)
82
+ if (line.includes('--type=')) continue;
83
+ const match = line.match(/--remote-debugging-port=(\d+)/);
84
+ if (match) return parseInt(match[1], 10);
85
+ }
86
+ return null;
87
+ }
88
+
89
+ // ── Cookie format conversion ──
90
+
91
+ function cdpToPlaywright(c) {
92
+ const out = {
93
+ name: c.name, value: c.value, domain: c.domain,
94
+ path: c.path || '/', expires: c.expires || -1,
95
+ httpOnly: Boolean(c.httpOnly), secure: Boolean(c.secure),
96
+ };
97
+ if (c.sameSite && ['Strict', 'Lax', 'None'].includes(c.sameSite)) out.sameSite = c.sameSite;
98
+ return out;
99
+ }
100
+
101
+ function playwrightToCdp(c) {
102
+ const out = {
103
+ name: c.name, value: c.value, domain: c.domain,
104
+ path: c.path || '/', httpOnly: Boolean(c.httpOnly), secure: Boolean(c.secure),
105
+ };
106
+ if (c.expires && c.expires > 0) out.expires = c.expires;
107
+ if (c.sameSite) out.sameSite = c.sameSite;
108
+ return out;
109
+ }
110
+
111
+ // ── Chrome launcher (for import or fallback export) ──
112
+
113
+ function findChromium() {
114
+ if (process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH) return process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;
115
+ const candidates = [
116
+ '/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/google-chrome',
117
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
118
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
119
+ ];
120
+ for (const c of candidates) { if (fs.existsSync(c)) return c; }
121
+ return null;
122
+ }
123
+
124
+ async function withFreshChrome(profileDir, callback) {
125
+ const chromium = findChromium();
126
+ if (!chromium) throw new Error('No Chromium/Chrome binary found');
127
+
128
+ const port = 19000 + Math.floor(Math.random() * 10000);
129
+ const proc = spawn(chromium, [
130
+ '--headless=new', '--no-sandbox', '--disable-gpu', '--disable-software-rasterizer',
131
+ `--user-data-dir=${profileDir}`, `--remote-debugging-port=${port}`,
132
+ '--window-size=800,600', '--no-first-run', '--no-default-browser-check',
133
+ '--disable-extensions', '--password-store=basic', '--use-mock-keychain',
134
+ ], { stdio: ['ignore', 'pipe', 'pipe'], detached: false });
135
+
136
+ let wsUrl = null;
137
+ for (let i = 0; i < 40; i++) {
138
+ await new Promise(r => setTimeout(r, 250));
139
+ try { wsUrl = (await httpGet(`http://127.0.0.1:${port}/json/version`)).webSocketDebuggerUrl; if (wsUrl) break; } catch {}
140
+ }
141
+ if (!wsUrl) { proc.kill('SIGKILL'); throw new Error(`Chrome CDP failed on port ${port}`); }
142
+
143
+ try { return await callback(wsUrl, port); }
144
+ finally {
145
+ proc.kill('SIGTERM');
146
+ await new Promise(r => setTimeout(r, 500));
147
+ try { proc.kill('SIGKILL'); } catch {}
148
+ }
149
+ }
150
+
151
+ // ── Export ──
152
+
153
+ async function exportCookiesFromCdp(port) {
154
+ // Find a page target to use Network.getAllCookies
155
+ const pages = await httpGet(`http://127.0.0.1:${port}/json/list`);
156
+ const page = pages.find(p => p.type === 'page');
157
+ if (!page) throw new Error('No page targets on CDP port ' + port);
158
+
159
+ await cdpSend(page.webSocketDebuggerUrl, 'Network.enable');
160
+ const { cookies } = await cdpSend(page.webSocketDebuggerUrl, 'Network.getAllCookies');
161
+ return cookies.map(cdpToPlaywright);
162
+ }
163
+
164
+ async function exportCookies(platforms, outputDir) {
165
+ if (!WS) {
166
+ console.error('Error: WebSocket not available. Install ws: npm install -g ws');
167
+ process.exit(1);
168
+ }
169
+ fs.mkdirSync(outputDir, { recursive: true });
170
+
171
+ for (const platform of platforms) {
172
+ const profileDir = path.join(PROFILES_DIR, platform);
173
+ if (!fs.existsSync(profileDir)) {
174
+ console.log(` ${platform}: no profile at ${profileDir}, skipping`);
175
+ continue;
176
+ }
177
+
178
+ // Try to find a running browser first (fast path)
179
+ const runningPort = findRunningBrowserPort(platform);
180
+ if (runningPort) {
181
+ process.stdout.write(` ${platform}: found running browser on port ${runningPort}...`);
182
+ try {
183
+ const cookies = await exportCookiesFromCdp(runningPort);
184
+ const outFile = path.join(outputDir, `cookies-${platform}.json`);
185
+ fs.writeFileSync(outFile, JSON.stringify({ cookies, origins: [] }, null, 2));
186
+ console.log(` ${cookies.length} cookies -> ${outFile}`);
187
+ continue;
188
+ } catch (err) {
189
+ console.log(` failed (${err.message}), trying headless...`);
190
+ }
191
+ }
192
+
193
+ // Fallback: launch headless Chrome with the profile
194
+ // This only works if the profile is not locked by another Chrome
195
+ const lockFile = path.join(profileDir, 'SingletonLock');
196
+ const hasLock = (() => { try { fs.lstatSync(lockFile); return true; } catch { return false; } })();
197
+ if (hasLock) {
198
+ console.log(` ${platform}: profile is locked but no running browser found; cannot export`);
199
+ console.log(` (the browser may have crashed; remove ${lockFile} and retry)`);
200
+ continue;
201
+ }
202
+
203
+ process.stdout.write(` ${platform}: launching headless browser...`);
204
+ try {
205
+ const cookies = await withFreshChrome(profileDir, async (wsUrl, port) => {
206
+ process.stdout.write(' extracting...');
207
+ return await exportCookiesFromCdp(port);
208
+ });
209
+ const outFile = path.join(outputDir, `cookies-${platform}.json`);
210
+ fs.writeFileSync(outFile, JSON.stringify({ cookies, origins: [] }, null, 2));
211
+ console.log(` ${cookies.length} cookies -> ${outFile}`);
212
+ } catch (err) {
213
+ console.log(` FAILED: ${err.message}`);
214
+ }
215
+ }
216
+ }
217
+
218
+ // ── Import ──
219
+
220
+ async function importCookies(platforms, inputDir) {
221
+ const chromium = findChromium();
222
+ if (!chromium) { console.error('Error: No Chromium/Chrome binary found.'); process.exit(1); }
223
+ if (!WS) { console.error('Error: WebSocket not available. Install ws: npm install -g ws'); process.exit(1); }
224
+ console.log(`Using browser: ${chromium}`);
225
+
226
+ for (const platform of platforms) {
227
+ const cookieFile = path.join(inputDir, `cookies-${platform}.json`);
228
+ if (!fs.existsSync(cookieFile)) {
229
+ console.log(` ${platform}: no ${cookieFile} found, skipping`);
230
+ continue;
231
+ }
232
+
233
+ const data = JSON.parse(fs.readFileSync(cookieFile, 'utf8'));
234
+ const cookies = (data.cookies || []).map(playwrightToCdp);
235
+ if (cookies.length === 0) {
236
+ console.log(` ${platform}: cookie file is empty, skipping`);
237
+ continue;
238
+ }
239
+
240
+ const profileDir = path.join(PROFILES_DIR, platform);
241
+ fs.mkdirSync(profileDir, { recursive: true });
242
+
243
+ // Check if profile is locked (browser already running)
244
+ const lockFile = path.join(profileDir, 'SingletonLock');
245
+ const hasLock = (() => { try { fs.lstatSync(lockFile); return true; } catch { return false; } })();
246
+ if (hasLock) {
247
+ // Try to inject into the running browser
248
+ const runningPort = findRunningBrowserPort(platform);
249
+ if (runningPort) {
250
+ process.stdout.write(` ${platform}: injecting ${cookies.length} cookies into running browser...`);
251
+ try {
252
+ const pages = await httpGet(`http://127.0.0.1:${runningPort}/json/list`);
253
+ const page = pages.find(p => p.type === 'page');
254
+ if (!page) throw new Error('No page targets');
255
+ await cdpSend(page.webSocketDebuggerUrl, 'Network.enable');
256
+ await cdpSend(page.webSocketDebuggerUrl, 'Network.setCookies', { cookies });
257
+ console.log(' done');
258
+ continue;
259
+ } catch (err) {
260
+ console.log(` failed (${err.message})`);
261
+ continue;
262
+ }
263
+ }
264
+ console.log(` ${platform}: profile is locked; cannot import`);
265
+ continue;
266
+ }
267
+
268
+ process.stdout.write(` ${platform}: launching browser...`);
269
+ try {
270
+ await withFreshChrome(profileDir, async (wsUrl, port) => {
271
+ process.stdout.write(` injecting ${cookies.length} cookies...`);
272
+ const pages = await httpGet(`http://127.0.0.1:${port}/json/list`);
273
+ const page = pages.find(p => p.type === 'page');
274
+ if (!page) throw new Error('No page targets');
275
+ await cdpSend(page.webSocketDebuggerUrl, 'Network.enable');
276
+ await cdpSend(page.webSocketDebuggerUrl, 'Network.setCookies', { cookies });
277
+ });
278
+ console.log(' done');
279
+ } catch (err) {
280
+ console.log(` FAILED: ${err.message}`);
281
+ }
282
+ }
283
+ }
284
+
285
+ // ── Main ──
286
+
287
+ async function main() {
288
+ const args = process.argv.slice(2);
289
+ const mode = args[0];
290
+
291
+ if (mode === 'export') {
292
+ const outputDir = args[1] || DEST;
293
+ console.log(`Exporting browser cookies to ${outputDir}/`);
294
+ await exportCookies(PLATFORMS, outputDir);
295
+ console.log('\nExport complete. Files ready for upload to VM.');
296
+ } else if (mode === 'import') {
297
+ const inputDir = args[1] || DEST;
298
+ console.log(`Importing browser cookies from ${inputDir}/`);
299
+ await importCookies(PLATFORMS, inputDir);
300
+ console.log('\nImport complete. Browser profiles updated.');
301
+ } else {
302
+ console.log('Usage:');
303
+ console.log(' npx social-autoposter export-cookies [output-dir]');
304
+ console.log(' npx social-autoposter import-cookies [input-dir]');
305
+ console.log('');
306
+ console.log('Export extracts cookies from ~/.claude/browser-profiles/{reddit,twitter,linkedin}');
307
+ console.log('Import injects cookies from cookies-{platform}.json into browser profiles');
308
+ process.exit(1);
309
+ }
310
+ }
311
+
312
+ main().catch((err) => {
313
+ console.error('Fatal:', err.message);
314
+ process.exit(1);
315
+ });
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+
7
+ function detect() {
8
+ const p = process.platform;
9
+ if (p === 'darwin') return 'darwin';
10
+ return p;
11
+ }
12
+
13
+ function scheduler(platform = detect()) {
14
+ if (platform === 'darwin') return 'launchd';
15
+ return null;
16
+ }
17
+
18
+ function agentsDir(platform = detect(), home = os.homedir()) {
19
+ if (platform === 'darwin') return path.join(home, 'Library', 'LaunchAgents');
20
+ return null;
21
+ }
22
+
23
+ function statMtimeCmd(platform = detect()) {
24
+ if (platform === 'darwin') return ['stat', '-f', '%m'];
25
+ return null;
26
+ }
27
+
28
+ function notifier(platform = detect()) {
29
+ if (platform === 'darwin') return 'osascript';
30
+ return null;
31
+ }
32
+
33
+ function brewPrefix() {
34
+ const candidates = ['/opt/homebrew/bin', '/usr/local/bin'];
35
+ for (const c of candidates) {
36
+ if (fs.existsSync(c)) return c;
37
+ }
38
+ return null;
39
+ }
40
+
41
+ function launchdPath(nodeBin) {
42
+ const dirs = new Set([nodeBin]);
43
+ const brew = brewPrefix();
44
+ if (brew) dirs.add(brew);
45
+ dirs.add('/usr/local/bin');
46
+ dirs.add('/usr/bin');
47
+ dirs.add('/bin');
48
+ return [...dirs].join(':');
49
+ }
50
+
51
+ module.exports = {
52
+ detect,
53
+ scheduler,
54
+ agentsDir,
55
+ statMtimeCmd,
56
+ notifier,
57
+ brewPrefix,
58
+ launchdPath,
59
+ };
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ const platform = require('../platform');
4
+ const launchd = require('./launchd');
5
+
6
+ function driverFor(name) {
7
+ const key = name || platform.scheduler();
8
+ if (key === 'launchd') return launchd;
9
+ throw new Error(`no scheduler driver for: ${key}`);
10
+ }
11
+
12
+ module.exports = { driverFor, launchd };