@m13v/s4l 1.6.197-rc.7

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 +1314 -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 +497 -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,34 @@
1
+ {
2
+ "name": "@m13v/s4l-mcp",
3
+ "version": "1.6.197-rc.7",
4
+ "private": true,
5
+ "description": "Desktop MCP client for social-autoposter (X/Twitter rail): manual draft/review/approve loop, autopilot control, and stats. Thin wrapper over the existing pipeline scripts.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "social-autoposter-mcp": "dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "npm run build:panel && tsc",
13
+ "build:panel": "vite build && SAPS_PANEL_ENTRY=product-link vite build",
14
+ "build:server": "tsc",
15
+ "bundle:pipeline": "node bundle-pipeline.mjs",
16
+ "build:bundle": "npm run build:panel && tsc && node bundle-pipeline.mjs",
17
+ "start": "node dist/index.js",
18
+ "dev": "tsc --watch",
19
+ "install-clients": "node install.mjs",
20
+ "uninstall-clients": "node install.mjs --uninstall"
21
+ },
22
+ "dependencies": {
23
+ "@modelcontextprotocol/ext-apps": "^1.7.3",
24
+ "@modelcontextprotocol/sdk": "^1.29.0",
25
+ "@sentry/node": "^10.58.0",
26
+ "zod": "^3.23.8"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^22.0.0",
30
+ "typescript": "^5.5.0",
31
+ "vite": "^6.0.0",
32
+ "vite-plugin-singlefile": "^2.0.0"
33
+ }
34
+ }
@@ -0,0 +1,437 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const os = require("node:os");
5
+ const path = require("node:path");
6
+ const { spawnSync } = require("node:child_process");
7
+
8
+ const PHASES = new Set(["pre_connect", "full"]);
9
+
10
+ function existsExecutable(file) {
11
+ if (!file || !fs.existsSync(file)) return false;
12
+ try {
13
+ fs.accessSync(file, fs.constants.X_OK);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ function findFirst(candidates) {
21
+ return candidates.find(existsExecutable) || "";
22
+ }
23
+
24
+ function findPython(home, preferred) {
25
+ return (
26
+ findFirst([
27
+ preferred,
28
+ process.env.SAPS_PYTHON,
29
+ path.join(home, ".social-autoposter-mcp", "runtime", ".venv", "bin", "python3"),
30
+ "/opt/homebrew/bin/python3",
31
+ "/usr/local/bin/python3",
32
+ "/usr/bin/python3",
33
+ ]) || "python3"
34
+ );
35
+ }
36
+
37
+ function findUv(home) {
38
+ return findFirst([
39
+ path.join(home, ".local", "bin", "uv"),
40
+ "/opt/homebrew/bin/uv",
41
+ "/usr/local/bin/uv",
42
+ "/usr/bin/uv",
43
+ ]);
44
+ }
45
+
46
+ function result(status, detail, fix) {
47
+ return { status, detail, ...(fix ? { fix } : {}) };
48
+ }
49
+
50
+ function runDoctorSync(options = {}) {
51
+ const phase = PHASES.has(options.phase) ? options.phase : "full";
52
+ const home = options.home || os.homedir();
53
+ const repoDir =
54
+ options.repoDir ||
55
+ process.env.SAPS_REPO_DIR ||
56
+ path.join(home, "social-autoposter");
57
+ const python = findPython(home, options.python);
58
+ const harness = path.join(home, ".local", "bin", "browser-harness");
59
+ const cookiesDb = path.join(
60
+ home,
61
+ ".claude",
62
+ "browser-profiles",
63
+ "browser-harness",
64
+ "Default",
65
+ "Cookies"
66
+ );
67
+ const mirrorPath = path.join(
68
+ home,
69
+ ".claude",
70
+ "browser-profiles",
71
+ "browser-harness.x-cookies.json"
72
+ );
73
+ const setupScript = path.join(repoDir, "scripts", "setup_twitter_auth.py");
74
+ const startedAt = new Date();
75
+ const checks = [];
76
+ const add = (id, name, runner) => checks.push({ id, name, runner });
77
+
78
+ const mirrorCount = () => {
79
+ try {
80
+ const data = JSON.parse(fs.readFileSync(mirrorPath, "utf8"));
81
+ return Array.isArray(data.cookies) ? data.cookies.length : 0;
82
+ } catch {
83
+ return -1;
84
+ }
85
+ };
86
+
87
+ add("node", "Node.js available", () =>
88
+ result("pass", process.version)
89
+ );
90
+
91
+ add("python", "Python 3 available", () => {
92
+ const r = spawnSync(python, ["--version"], {
93
+ encoding: "utf8",
94
+ timeout: 15000,
95
+ });
96
+ if (r.status === 0) {
97
+ return result("pass", `${(r.stdout || r.stderr).trim()} (${python})`);
98
+ }
99
+ return result(
100
+ "fail",
101
+ "python3 not found",
102
+ "run the owned runtime installer"
103
+ );
104
+ });
105
+
106
+ add("uv", "uv Python launcher installed", () => {
107
+ const uv = findUv(home);
108
+ return uv
109
+ ? result("pass", uv)
110
+ : result("fail", "uv not found", "run the owned runtime installer");
111
+ });
112
+
113
+ add("browser_harness", "browser-harness CLI installed", () =>
114
+ fs.existsSync(harness)
115
+ ? result("pass", harness)
116
+ : result(
117
+ "fail",
118
+ `not found at ${harness}`,
119
+ "run the owned runtime installer"
120
+ )
121
+ );
122
+
123
+ add("browser_harness_shape", "browser-harness CLI shape", () => {
124
+ if (!fs.existsSync(harness)) {
125
+ return result("fail", "binary missing", "run the owned runtime installer");
126
+ }
127
+ const probe = spawnSync(harness, [], {
128
+ encoding: "utf8",
129
+ timeout: 15000,
130
+ });
131
+ const usage = `${probe.stdout || ""}${probe.stderr || ""}`;
132
+ const dashC = /\b-c\b/.test(usage);
133
+ const stdin = /<<'PY'|<<"PY"|<<PY\b/.test(usage);
134
+ if (!dashC && !stdin) {
135
+ return result(
136
+ "fail",
137
+ "CLI advertises neither supported invocation shape",
138
+ "reinstall browser-harness with the owned runtime installer"
139
+ );
140
+ }
141
+ return result("pass", stdin ? "stdin heredoc" : "-c flag");
142
+ });
143
+
144
+ add("chrome_safe_storage", "Chrome Safe Storage readable", () => {
145
+ if (process.platform !== "darwin") {
146
+ return result("pass", "not applicable on this platform");
147
+ }
148
+ if (phase === "pre_connect") {
149
+ return result(
150
+ "expected",
151
+ "deferred until X connection to avoid an unexpected keychain prompt"
152
+ );
153
+ }
154
+ const r = spawnSync(
155
+ "security",
156
+ [
157
+ "find-generic-password",
158
+ "-s",
159
+ "Chrome Safe Storage",
160
+ "-a",
161
+ "Chrome",
162
+ "-w",
163
+ ],
164
+ { encoding: "utf8", timeout: 10000 }
165
+ );
166
+ if (r.status === 0) {
167
+ return result("pass", "accessible (cookie import can decrypt Chrome data)");
168
+ }
169
+ const tail =
170
+ (r.stderr || "").trim().split("\n").slice(-1)[0] || `exit ${r.status}`;
171
+ return result(
172
+ "fail",
173
+ tail,
174
+ "unlock the login keychain, then reconnect X"
175
+ );
176
+ });
177
+
178
+ add("chrome_cdp", "Managed Chrome CDP responding", () => {
179
+ const probe = spawnSync(
180
+ "curl",
181
+ [
182
+ "-sf",
183
+ "--max-time",
184
+ "2",
185
+ "-o",
186
+ "/dev/null",
187
+ "http://127.0.0.1:9555/json/version",
188
+ ],
189
+ { encoding: "utf8" }
190
+ );
191
+ if (probe.status === 0) return result("pass", "CDP responding on :9555");
192
+ if (phase === "pre_connect") {
193
+ return result(
194
+ "expected",
195
+ "managed Chrome is not running yet; connect_x launches it"
196
+ );
197
+ }
198
+ return result(
199
+ "fail",
200
+ "no CDP response on :9555",
201
+ "re-run connect_x to launch managed Chrome"
202
+ );
203
+ });
204
+
205
+ add("x_session", "X session valid in managed Chrome", () => {
206
+ if (!fs.existsSync(setupScript)) {
207
+ return phase === "pre_connect"
208
+ ? result("expected", "X status script becomes available after runtime installation")
209
+ : result("fail", `setup script missing at ${setupScript}`, "repair the runtime");
210
+ }
211
+ const r = spawnSync(python, [setupScript, "status"], {
212
+ encoding: "utf8",
213
+ timeout: 90000,
214
+ });
215
+ let out = null;
216
+ try {
217
+ out = JSON.parse((r.stdout || "").trim());
218
+ } catch {
219
+ out = null;
220
+ }
221
+ if (out && out.connected) {
222
+ return result(
223
+ "pass",
224
+ `state=${out.state}${out.handle ? ` handle=@${out.handle}` : ""}`
225
+ );
226
+ }
227
+ const state = out && out.state ? out.state : "unavailable";
228
+ return phase === "pre_connect"
229
+ ? result("expected", `state=${state}; X is connected later in onboarding`)
230
+ : result(
231
+ "fail",
232
+ `state=${state}`,
233
+ "run connect_x and finish any interactive X login"
234
+ );
235
+ });
236
+
237
+ add("x_cookie_sqlite", "X cookies persisted to Chrome SQLite", () => {
238
+ if (!fs.existsSync(cookiesDb)) {
239
+ return phase === "pre_connect"
240
+ ? result("expected", "Chrome Cookies database has not been created yet")
241
+ : result("fail", `${cookiesDb} missing`, "re-run connect_x");
242
+ }
243
+ const r = spawnSync(
244
+ python,
245
+ [
246
+ "-c",
247
+ `import sqlite3; c=sqlite3.connect(${JSON.stringify(
248
+ cookiesDb
249
+ )}); print(c.execute("SELECT COUNT(*) FROM cookies WHERE host_key LIKE '%x.com' OR host_key LIKE '%twitter.com'").fetchone()[0])`,
250
+ ],
251
+ { encoding: "utf8", timeout: 10000 }
252
+ );
253
+ const count = Number.parseInt((r.stdout || "0").trim(), 10);
254
+ if (count > 0) {
255
+ return result("pass", `${count} x.com/twitter.com rows persisted`);
256
+ }
257
+ return phase === "pre_connect"
258
+ ? result("expected", "no X cookie rows yet; connect_x imports them")
259
+ : result("fail", "0 x.com/twitter.com rows", "re-run connect_x");
260
+ });
261
+
262
+ add("x_cookie_mirror", "Durable X cookie mirror populated", () => {
263
+ const count = mirrorCount();
264
+ if (count > 0) return result("pass", `${count} cookies mirrored`);
265
+ if (phase === "pre_connect") {
266
+ return result(
267
+ "expected",
268
+ count === 0
269
+ ? "mirror exists but is empty until X is connected"
270
+ : "mirror is created during X connection"
271
+ );
272
+ }
273
+ return result(
274
+ "fail",
275
+ count === 0
276
+ ? "mirror file is empty"
277
+ : `no mirror at ${mirrorPath}`,
278
+ "re-run connect_x to populate the durable mirror"
279
+ );
280
+ });
281
+
282
+ add("keychain_autolock", "Login keychain auto-lock is covered", () => {
283
+ if (process.platform !== "darwin") {
284
+ return result("pass", "not applicable on this platform");
285
+ }
286
+ const keychain = path.join(
287
+ home,
288
+ "Library",
289
+ "Keychains",
290
+ "login.keychain-db"
291
+ );
292
+ const r = spawnSync("security", ["show-keychain-info", keychain], {
293
+ encoding: "utf8",
294
+ timeout: 10000,
295
+ });
296
+ const output = `${r.stdout || ""}${r.stderr || ""}`;
297
+ const match = output.match(/timeout=(\d+)s/);
298
+ if (!match) return result("pass", "no auto-lock timeout detected");
299
+ const seconds = Number.parseInt(match[1], 10);
300
+ if (mirrorCount() > 0) {
301
+ return result(
302
+ "pass",
303
+ `auto-locks after ${seconds}s; durable cookie mirror covers relaunches`
304
+ );
305
+ }
306
+ return phase === "pre_connect"
307
+ ? result(
308
+ "expected",
309
+ `auto-locks after ${seconds}s; connect_x will create the protective mirror`
310
+ )
311
+ : result(
312
+ "fail",
313
+ `auto-locks after ${seconds}s with no durable mirror`,
314
+ "re-run connect_x to create the mirror"
315
+ );
316
+ });
317
+
318
+ add("autopilot_kicker", "Autopilot draft kicker installed + loaded", () => {
319
+ if (process.platform !== "darwin") {
320
+ return result("pass", "not applicable on this platform");
321
+ }
322
+ const label = "com.m13v.social-twitter-cycle";
323
+ const plist = path.join(
324
+ home,
325
+ "Library",
326
+ "LaunchAgents",
327
+ `${label}.plist`
328
+ );
329
+ const onDisk = fs.existsSync(plist);
330
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
331
+ const printed = spawnSync(
332
+ "launchctl",
333
+ ["print", `gui/${uid}/${label}`],
334
+ { encoding: "utf8", timeout: 10000 }
335
+ );
336
+ const loaded = printed.status === 0;
337
+ if (loaded && onDisk) {
338
+ return result("pass", "kicker plist installed and loaded in launchd");
339
+ }
340
+ if (phase === "pre_connect") {
341
+ return result(
342
+ "expected",
343
+ "kicker installs after a project (or the personal-brand persona) is ready"
344
+ );
345
+ }
346
+ if (onDisk && !loaded) {
347
+ return result(
348
+ "fail",
349
+ "kicker plist exists but is NOT loaded in launchd (no drafts will be produced)",
350
+ "run runtime action:'install', or bootstrap the plist: " +
351
+ `launchctl bootstrap gui/${uid} ${plist}`
352
+ );
353
+ }
354
+ // plist missing: could be a legitimately-unconfigured box, so warn (non-blocking)
355
+ // rather than hard-fail — but make it visible so a stuck autopilot is caught.
356
+ return result(
357
+ "warn",
358
+ "autopilot kicker not installed (no drafts until it is)",
359
+ "finish setup: choose a mode (engagement_mode) and schedule the autopilot " +
360
+ "(queue_setup). The kicker auto-installs once a project or the persona is ready."
361
+ );
362
+ });
363
+
364
+ const completedChecks = checks.map((check) => {
365
+ const checkStarted = Date.now();
366
+ try {
367
+ return {
368
+ id: check.id,
369
+ name: check.name,
370
+ ...check.runner(),
371
+ duration_ms: Date.now() - checkStarted,
372
+ };
373
+ } catch (error) {
374
+ return {
375
+ id: check.id,
376
+ name: check.name,
377
+ status: "fail",
378
+ detail: error instanceof Error ? error.message : String(error),
379
+ duration_ms: Date.now() - checkStarted,
380
+ };
381
+ }
382
+ });
383
+ const summary = { pass: 0, fail: 0, expected: 0, warn: 0, total: checks.length };
384
+ for (const check of completedChecks) {
385
+ if (Object.prototype.hasOwnProperty.call(summary, check.status)) {
386
+ summary[check.status] += 1;
387
+ }
388
+ }
389
+ const completedAt = new Date();
390
+ return {
391
+ schema_version: 1,
392
+ phase,
393
+ ok: summary.fail === 0,
394
+ started_at: startedAt.toISOString(),
395
+ completed_at: completedAt.toISOString(),
396
+ duration_ms: completedAt.getTime() - startedAt.getTime(),
397
+ summary,
398
+ checks: completedChecks,
399
+ };
400
+ }
401
+
402
+ function formatDoctorReport(report) {
403
+ const glyph = {
404
+ pass: "OK",
405
+ fail: "FAIL",
406
+ expected: "WAIT",
407
+ warn: "WARN",
408
+ };
409
+ const lines = [
410
+ `social-autoposter doctor — ${report.phase.replace("_", " ")} phase`,
411
+ "",
412
+ ];
413
+ for (const check of report.checks) {
414
+ lines.push(
415
+ ` [${(glyph[check.status] || check.status.toUpperCase()).padEnd(4)}] ${
416
+ check.name
417
+ }: ${check.detail || ""}`
418
+ );
419
+ if (check.fix) lines.push(` fix: ${check.fix}`);
420
+ }
421
+ lines.push(
422
+ "",
423
+ `${report.summary.pass}/${report.summary.total} passed, ` +
424
+ `${report.summary.expected} expected, ${report.summary.fail} failed.`
425
+ );
426
+ if (!report.ok) {
427
+ lines.push(
428
+ "Address the failures above and re-run `npx social-autoposter doctor`."
429
+ );
430
+ }
431
+ return lines.join("\n");
432
+ }
433
+
434
+ module.exports = {
435
+ formatDoctorReport,
436
+ runDoctorSync,
437
+ };