@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,602 @@
1
+ #!/usr/bin/env bash
2
+ # reset-test-machine.sh — wipe a social-autoposter install back to factory-fresh.
3
+ #
4
+ # For TEST MACHINES. Removes what a social-autoposter install scatters so you can
5
+ # re-run a clean first-install and reproduce new-user bugs.
6
+ #
7
+ # DEFAULT IS A DRY RUN. Nothing is deleted until you pass --yes.
8
+ #
9
+ # Two scopes:
10
+ # (default) PLUGIN RESET — just the plugin: the menu-bar app, the .mcpb/npm
11
+ # plugin itself, the scheduled tasks, the user's state+config
12
+ # (~/.social-autoposter-mcp, including config.json + mode.json), the
13
+ # /tmp scratch, autopilot transcripts, and the social-autoposter MCP
14
+ # registration. PRESERVES the imported X login (browser-harness
15
+ # profile), all shared browser profiles, the browser-harness backend,
16
+ # and the packaged toolchain. Uninstall + forget the plugin without
17
+ # disturbing the rest of the environment.
18
+ # --deep FULL NUKE — the plugin reset above PLUS the shared layer it touches:
19
+ # per-agent Chrome profiles + cookies (reddit/linkedin/twitter), the
20
+ # browser-harness backend, and the packaged toolchain (uv binary, uv
21
+ # cache, Chromium ~1.7G). Other tools that rely on uv/Chromium
22
+ # re-provision afterward. Use to reproduce a true bare-metal box.
23
+ #
24
+ # Usage:
25
+ # scripts/reset-test-machine.sh # dry run: print what the plugin reset WOULD remove
26
+ # scripts/reset-test-machine.sh --yes # plugin reset (light) — quits Claude, wipes, relaunches Claude fresh
27
+ # scripts/reset-test-machine.sh --yes --deep # full nuke incl. shared browser layer + toolchain
28
+ # scripts/reset-test-machine.sh --yes --keep-claude # don't quit Claude (run from inside a live session)
29
+ #
30
+ # THE ONE STANDARD PATH: --yes QUITS Claude Desktop, wipes, settles, then
31
+ # RELAUNCHES Claude Desktop fresh. No flag skips or adds the relaunch; every
32
+ # caller (menubar Uninstall, `npx social-autoposter uninstall`, manual SSH runs)
33
+ # gets the same sequence. The .mcpb registry ([1b]), scheduled-task ([1c]) and
34
+ # mcpServers ([3]) edits only STICK if Claude is not running, because the host
35
+ # rewrites those files on quit and re-materializes the state dir on launch. A
36
+ # settle step ([7]) re-quits Claude and re-wipes if it auto-relaunches mid-wipe
37
+ # (e.g. an auto-update), so the box is genuinely first-run, and only then does
38
+ # [8] bring Claude Desktop back up (fresh, without S4L). If you invoke this from
39
+ # INSIDE a live Claude session and don't want the app taken down, pass
40
+ # --keep-claude (the registry/task/mcp edits then won't persist, and the
41
+ # settle + relaunch steps are skipped, until you quit Claude yourself).
42
+ #
43
+ set -u
44
+
45
+ HOME_DIR="${HOME}"
46
+ DRY=1
47
+ DEEP=0
48
+ KEEP_CLAUDE=0
49
+ for a in "$@"; do
50
+ case "$a" in
51
+ --yes|-y) DRY=0 ;;
52
+ --deep) DEEP=1 ;;
53
+ --keep-claude) KEEP_CLAUDE=1 ;;
54
+ --relaunch) ;; # legacy no-op: relaunch is part of the standard path now
55
+ -h|--help)
56
+ grep '^#' "$0" | sed 's/^# \{0,1\}//' | sed '/^!/d'
57
+ exit 0 ;;
58
+ *) echo "unknown arg: $a (use --yes, --deep, --keep-claude, --help)"; exit 2 ;;
59
+ esac
60
+ done
61
+
62
+ # The plugin reset is the default; --deep widens it to the shared browser layer +
63
+ # toolchain. PLUGIN_ONLY (the inverse of DEEP) gates the shared-layer steps below.
64
+ if [ "$DEEP" -eq 1 ]; then PLUGIN_ONLY=0; else PLUGIN_ONLY=1; fi
65
+
66
+ if [ "$DRY" -eq 1 ]; then
67
+ echo "=== DRY RUN — nothing will be deleted. Re-run with --yes to apply. ==="
68
+ else
69
+ echo "=== APPLYING — removing social-autoposter install ==="
70
+ fi
71
+ if [ "$PLUGIN_ONLY" -eq 1 ]; then
72
+ echo "=== SCOPE: plugin reset (app + plugin + tasks + state/config; keeping X login, browser layer, toolchain). Pass --deep for the full nuke. ==="
73
+ else
74
+ echo "=== SCOPE: --deep full nuke (plugin + shared browser profiles + harness backend + toolchain) ==="
75
+ fi
76
+ echo
77
+
78
+ # ---- helpers ---------------------------------------------------------------
79
+ rm_path() { # rm_path <path> <label>
80
+ local p="$1" label="${2:-}"
81
+ if [ -e "$p" ] || [ -L "$p" ]; then
82
+ local size; size="$(du -sh "$p" 2>/dev/null | cut -f1)"
83
+ echo " remove ${label:+[$label] }$p (${size:-?})"
84
+ [ "$DRY" -eq 0 ] && rm -rf "$p"
85
+ fi
86
+ }
87
+ run() { # run <description> <cmd...>
88
+ echo " run $1"
89
+ [ "$DRY" -eq 0 ] && shift && "$@" >/dev/null 2>&1 || true
90
+ }
91
+ json_edit() { # json_edit <description> <cmd...>
92
+ echo " edit $1"
93
+ [ "$DRY" -eq 0 ] && shift && "$@" || true
94
+ }
95
+
96
+ # ---- 0. quit Claude Desktop (so [1b]/[1c] registry+task edits stick) -------
97
+ # Claude Desktop OWNS the .mcpb installations registry and the scheduled-task
98
+ # schedule, and REWRITES both files on quit. If it is running while we delete the
99
+ # extension entry / task dirs, it resurrects them on its next exit — exactly the
100
+ # "plugin still installed + scheduled tasks back" failure. So quit it FIRST
101
+ # (graceful AppleScript quit, wait, then SIGKILL fallback) unless --keep-claude.
102
+ echo "[0] quit Claude Desktop"
103
+ if [ "$KEEP_CLAUDE" -eq 1 ]; then
104
+ echo " (--keep-claude: leaving Claude Desktop running — [1b]/[1c] edits will NOT persist until you quit it)"
105
+ elif [ "$DRY" -eq 0 ]; then
106
+ if pgrep -x "Claude" >/dev/null 2>&1; then
107
+ echo " quit Claude Desktop (graceful)"
108
+ osascript -e 'tell application "Claude" to quit' >/dev/null 2>&1 || true
109
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
110
+ pgrep -x "Claude" >/dev/null 2>&1 || break
111
+ sleep 1
112
+ done
113
+ if pgrep -x "Claude" >/dev/null 2>&1; then
114
+ echo " kill Claude Desktop still up — SIGKILL"
115
+ pkill -9 -x "Claude" 2>/dev/null || true
116
+ sleep 1
117
+ fi
118
+ pgrep -x "Claude" >/dev/null 2>&1 && echo " WARN Claude Desktop still running after kill" || echo " ok Claude Desktop is down"
119
+ else
120
+ echo " (Claude Desktop not running)"
121
+ fi
122
+ else
123
+ echo " (would quit Claude Desktop: AppleScript quit, then SIGKILL fallback)"
124
+ fi
125
+ echo
126
+
127
+ # ---- 0a. stop anything still running ---------------------------------------
128
+ echo "[0a] stop running processes"
129
+ if [ "$DRY" -eq 0 ]; then
130
+ pkill -f 'social-autoposter-mcp' 2>/dev/null || true
131
+ pkill -f 'browser-harness' 2>/dev/null || true
132
+ pkill -f 's4l_menubar.py' 2>/dev/null || true
133
+ # packaged Chromium launched from the owned playwright / harness profiles
134
+ pkill -f '\.claude/browser-profiles/browser-harness' 2>/dev/null || true
135
+ sleep 1
136
+ else
137
+ echo " (would pkill: social-autoposter-mcp, browser-harness, menu bar app, packaged Chrome)"
138
+ fi
139
+ echo
140
+
141
+ # ---- 0b. menu bar LaunchAgent ---------------------------------------------
142
+ # The menu bar app runs as a KeepAlive LaunchAgent; its plist lives outside the
143
+ # state dir, so unload + remove it explicitly (its python files under the state
144
+ # dir are removed in [1]). KEEP MENUBAR_LABEL in sync with src/runtime.ts.
145
+ echo "[0b] menu bar LaunchAgent"
146
+ MENUBAR_LABEL="com.m13v.social-autoposter.menubar"
147
+ MENUBAR_PLIST="$HOME_DIR/Library/LaunchAgents/$MENUBAR_LABEL.plist"
148
+ if [ "$DRY" -eq 0 ]; then
149
+ launchctl bootout "gui/$(id -u)/$MENUBAR_LABEL" 2>/dev/null \
150
+ || launchctl unload "$MENUBAR_PLIST" 2>/dev/null || true
151
+ else
152
+ echo " (would bootout $MENUBAR_LABEL)"
153
+ fi
154
+ rm_path "$MENUBAR_PLIST" "menubar-plist"
155
+ echo
156
+
157
+ # ---- 0c. ALL other social-autoposter LaunchAgents (dynamic sweep) ----------
158
+ # Beyond the menu bar ([0b]), the install has dropped a GROWING set of
159
+ # com.m13v.social-* LaunchAgents into ~/Library/LaunchAgents: the twitter-cycle
160
+ # kicker, the daily updater, plus autopilot-stall-watch, claude-reaper,
161
+ # memory-snapshot, and overlay-watch. All live OUTSIDE the state dir removed in
162
+ # [1], so any we miss keep firing against a half-wiped install (or resurrect it).
163
+ # Hardcoding the list meant every NEW agent survived the next reset (that bug bit
164
+ # us twice). So sweep DYNAMICALLY: the union of every com.m13v.social-* plist on
165
+ # disk and every com.m13v.social-* label loaded in launchd, minus the menu bar
166
+ # already handled in [0b]. Bootout + remove each (and any .disabled-* variant).
167
+ echo "[0c] social-autoposter LaunchAgents (dynamic sweep of all com.m13v.social-*)"
168
+ LA_DIR="$HOME_DIR/Library/LaunchAgents"
169
+ AGENT_LABELS=()
170
+ # labels from plist files on disk
171
+ for pl in "$LA_DIR"/com.m13v.social-*.plist; do
172
+ [ -e "$pl" ] || continue
173
+ AGENT_LABELS+=("$(basename "$pl" .plist)")
174
+ done
175
+ # labels currently loaded in launchd (catches loaded-but-plist-already-gone)
176
+ while IFS= read -r lbl; do
177
+ [ -n "$lbl" ] && AGENT_LABELS+=("$lbl")
178
+ done < <(launchctl list 2>/dev/null | awk '/com\.m13v\.social-/ {print $3}')
179
+ # dedupe + drop the menu bar (handled in [0b]); keep set -u safe with an empty set
180
+ FILTERED=()
181
+ if [ "${#AGENT_LABELS[@]}" -gt 0 ]; then
182
+ while IFS= read -r lbl; do
183
+ [ -n "$lbl" ] && FILTERED+=("$lbl")
184
+ done < <(printf '%s\n' "${AGENT_LABELS[@]}" | awk -v mb="$MENUBAR_LABEL" 'NF && $0!=mb && !seen[$0]++')
185
+ fi
186
+ if [ "${#FILTERED[@]}" -eq 0 ]; then
187
+ echo " (no other com.m13v.social-* LaunchAgents found)"
188
+ else
189
+ for LBL in "${FILTERED[@]}"; do
190
+ PL="$LA_DIR/$LBL.plist"
191
+ if [ "$DRY" -eq 0 ]; then
192
+ launchctl bootout "gui/$(id -u)/$LBL" 2>/dev/null \
193
+ || launchctl unload "$PL" 2>/dev/null || true
194
+ else
195
+ echo " (would bootout $LBL)"
196
+ fi
197
+ rm_path "$PL" "launchagent"
198
+ for d in "$PL".disabled*; do [ -e "$d" ] && rm_path "$d" "launchagent-disabled"; done
199
+ done
200
+ fi
201
+ echo
202
+
203
+ # ---- 1. MCP state dir (owned python + venv + materialized repo) ------------
204
+ echo "[1] MCP state dir (uv-owned python, venv, materialized repo, runtime.json, setup-state)"
205
+ rm_path "$HOME_DIR/.social-autoposter-mcp" "state"
206
+ echo
207
+
208
+ # ---- 1b. Claude Desktop .mcpb extension -----------------------------------
209
+ # A .mcpb (Desktop) install scatters THREE artifacts under ~/Library/Application
210
+ # Support/Claude/, NONE of which live in the state dir removed in [1]: the
211
+ # unpacked extension, its per-extension settings, and an entry in the
212
+ # installations registry. Remove all three so a reinstall is genuinely first-run.
213
+ # Scoped to social-autoposter only — other extensions (lede, s4l-plugin) are left
214
+ # untouched.
215
+ echo "[1b] Claude Desktop .mcpb extension (install dir + settings + registry)"
216
+ CLAUDE_SUPPORT="$HOME_DIR/Library/Application Support/Claude"
217
+ INSTALL_REG="$CLAUDE_SUPPORT/extensions-installations.json"
218
+ # The registry edit only sticks if Claude Desktop is NOT running (it rewrites
219
+ # these files on quit). Step [0] already quit it; this warns only if it came back
220
+ # (e.g. --keep-claude, or a relaunch mid-run).
221
+ if pgrep -x "Claude" >/dev/null 2>&1; then
222
+ echo " WARN Claude Desktop is running — quit it (Cmd+Q) so the registry edit"
223
+ echo " isn't rewritten on exit, then re-run this step if needed."
224
+ fi
225
+ EXT_IDS=()
226
+ for eid in \
227
+ local.mcpb.m13v.social-autoposter \
228
+ local.mcpb.s4l.ai.social-autoposter; do
229
+ EXT_IDS+=("$eid")
230
+ done
231
+ for d in "$CLAUDE_SUPPORT/Claude Extensions"/local.mcpb.*social-autoposter; do
232
+ [ -e "$d" ] && EXT_IDS+=("$(basename "$d")")
233
+ done
234
+ for f in "$CLAUDE_SUPPORT/Claude Extensions Settings"/local.mcpb.*social-autoposter.json; do
235
+ [ -e "$f" ] && EXT_IDS+=("$(basename "$f" .json)")
236
+ done
237
+ if [ -f "$INSTALL_REG" ] && command -v python3 >/dev/null 2>&1; then
238
+ while IFS= read -r eid; do [ -n "$eid" ] && EXT_IDS+=("$eid"); done < <(python3 - "$INSTALL_REG" <<'PY' 2>/dev/null || true
239
+ import json, sys
240
+ d = json.load(open(sys.argv[1]))
241
+ for eid in sorted((d.get("extensions") or {}).keys()):
242
+ if "social-autoposter" in eid:
243
+ print(eid)
244
+ PY
245
+ )
246
+ fi
247
+ if [ "${#EXT_IDS[@]}" -gt 0 ]; then
248
+ EXT_IDS=($(printf '%s\n' "${EXT_IDS[@]}" | awk 'NF && !seen[$0]++'))
249
+ fi
250
+ for EXT_ID in "${EXT_IDS[@]}"; do
251
+ rm_path "$CLAUDE_SUPPORT/Claude Extensions/$EXT_ID" "ext-install"
252
+ rm_path "$CLAUDE_SUPPORT/Claude Extensions Settings/$EXT_ID.json" "ext-settings"
253
+ done
254
+ # Surgically drop only OUR entries from the installations registry (keep others).
255
+ if [ -f "$INSTALL_REG" ]; then
256
+ if command -v python3 >/dev/null 2>&1; then
257
+ if python3 - "$INSTALL_REG" "${EXT_IDS[@]}" <<'PY' >/dev/null 2>&1; then
258
+ import json, sys
259
+ p, ids = sys.argv[1], set(sys.argv[2:])
260
+ d = json.load(open(p))
261
+ sys.exit(0 if ids.intersection((d.get("extensions") or {}).keys()) else 1)
262
+ PY
263
+ json_edit "remove social-autoposter extension entries from $INSTALL_REG" \
264
+ sh -c 'cp "$1" "$1.bak" 2>/dev/null || true; shift; python3 - "$@"' sh "$INSTALL_REG" "$INSTALL_REG" "${EXT_IDS[@]}" <<'PY'
265
+ import json, sys
266
+ p, ids = sys.argv[1], set(sys.argv[2:])
267
+ d = json.load(open(p))
268
+ for eid in ids:
269
+ (d.get("extensions") or {}).pop(eid, None)
270
+ json.dump(d, open(p, "w"), indent=2)
271
+ open(p, "a").write("\n")
272
+ PY
273
+ else
274
+ echo " (registry has no social-autoposter extension entry)"
275
+ fi
276
+ else
277
+ echo " NOTE python3 not on PATH — remove social-autoposter entries from $INSTALL_REG by hand"
278
+ fi
279
+ fi
280
+ echo
281
+
282
+ # ---- 1c. Claude Desktop scheduled tasks -----------------------------------
283
+ # Onboarding creates the queue-worker tasks (and historically the deprecated
284
+ # single autopilot) under ~/.claude/scheduled-tasks/. They live OUTSIDE the
285
+ # state dir, so a reset that skips them leaves idle workers firing every minute
286
+ # against a wiped install. Remove the task dirs. Like the .mcpb registry, the
287
+ # host (Claude Desktop) owns the live schedule and rewrites it on quit. Step [0]
288
+ # already quit it; this warns only if it came back (--keep-claude / relaunch).
289
+ echo "[1c] Claude Desktop scheduled tasks (queue workers + deprecated autopilot)"
290
+ SCHED_DIR="$HOME_DIR/.claude/scheduled-tasks"
291
+ if pgrep -x "Claude" >/dev/null 2>&1; then
292
+ echo " WARN Claude Desktop is running — it owns the task schedule; quit it (Cmd+Q)"
293
+ echo " before reset so the removed tasks aren't rewritten on exit."
294
+ fi
295
+ TASK_IDS=()
296
+ if [ -d "$SCHED_DIR" ]; then
297
+ for d in "$SCHED_DIR"/saps-* "$SCHED_DIR"/social-autoposter*; do
298
+ [ -e "$d" ] && TASK_IDS+=("$(basename "$d")")
299
+ done
300
+ fi
301
+ for t in saps-phase1-query saps-phase2b-draft social-autoposter-autopilot; do
302
+ TASK_IDS+=("$t")
303
+ done
304
+ if [ "${#TASK_IDS[@]}" -gt 0 ]; then
305
+ TASK_IDS=($(printf '%s\n' "${TASK_IDS[@]}" | awk 'NF && !seen[$0]++'))
306
+ fi
307
+ for t in "${TASK_IDS[@]}"; do
308
+ rm_path "$SCHED_DIR/$t" "scheduled-task"
309
+ done
310
+ if command -v python3 >/dev/null 2>&1; then
311
+ while IFS= read -r reg; do
312
+ if python3 - "$reg" "$SCHED_DIR" "${TASK_IDS[@]}" <<'PY' >/dev/null 2>&1; then
313
+ import json, os, sys
314
+ p, sched_dir, ids = sys.argv[1], os.path.realpath(sys.argv[2]), set(sys.argv[3:])
315
+ d = json.load(open(p))
316
+ tasks = d.get("scheduledTasks") or []
317
+ def owned(t):
318
+ tid = str(t.get("id") or "")
319
+ fp = str(t.get("filePath") or "")
320
+ return (
321
+ tid in ids
322
+ or tid.startswith("saps-")
323
+ or tid.startswith("social-autoposter")
324
+ or os.path.realpath(fp).startswith(sched_dir + os.sep + "saps-")
325
+ or os.path.realpath(fp).startswith(sched_dir + os.sep + "social-autoposter")
326
+ )
327
+ sys.exit(0 if any(owned(t) for t in tasks) else 1)
328
+ PY
329
+ json_edit "remove social-autoposter scheduled-task entries from $reg" \
330
+ sh -c 'cp "$1" "$1.bak" 2>/dev/null || true; shift; python3 - "$@"' sh "$reg" "$reg" "$SCHED_DIR" "${TASK_IDS[@]}" <<'PY'
331
+ import json, os, sys
332
+ p, sched_dir, ids = sys.argv[1], os.path.realpath(sys.argv[2]), set(sys.argv[3:])
333
+ d = json.load(open(p))
334
+ tasks = d.get("scheduledTasks") or []
335
+ def owned(t):
336
+ tid = str(t.get("id") or "")
337
+ fp = str(t.get("filePath") or "")
338
+ return (
339
+ tid in ids
340
+ or tid.startswith("saps-")
341
+ or tid.startswith("social-autoposter")
342
+ or os.path.realpath(fp).startswith(sched_dir + os.sep + "saps-")
343
+ or os.path.realpath(fp).startswith(sched_dir + os.sep + "social-autoposter")
344
+ )
345
+ d["scheduledTasks"] = [t for t in tasks if not owned(t)]
346
+ json.dump(d, open(p, "w"), indent=2)
347
+ open(p, "a").write("\n")
348
+ PY
349
+ fi
350
+ done < <(find "$CLAUDE_SUPPORT/claude-code-sessions" -path '*/scheduled-tasks.json' -type f 2>/dev/null || true)
351
+ else
352
+ echo " NOTE python3 not on PATH — could not scrub Claude scheduled-tasks.json registries"
353
+ fi
354
+ echo
355
+
356
+ # ---- 1d. /tmp scratch (draft plans, queue, browser locks, run_claude) ------
357
+ # The draft pipeline scatters state across /tmp that the state-dir wipe in [1]
358
+ # does NOT cover: the review-queue + per-batch plan files, mkdir-based browser
359
+ # locks, and run_claude.sh's quota stamp / active-session sidecars. Left behind,
360
+ # stale plans resurface as phantom drafts and a stale lock blocks the first new
361
+ # cycle. (The queue dir itself, ~/.social-autoposter-mcp/claude-queue, is removed
362
+ # with the state dir in [1].)
363
+ echo "[1d] /tmp scratch (draft plans, browser locks, run_claude artifacts)"
364
+ if [ "$DRY" -eq 0 ]; then
365
+ rm -f /tmp/twitter_cycle_plan_*.json 2>/dev/null || true
366
+ rm -rf /tmp/social-autoposter-*.lock 2>/dev/null || true
367
+ rm -rf /tmp/sa-active-claude /tmp/sa-claude-blocked.json 2>/dev/null || true
368
+ rm -f /tmp/sa_run_claude_stdout.* 2>/dev/null || true
369
+ else
370
+ echo " (would remove /tmp/twitter_cycle_plan_*.json, /tmp/social-autoposter-*.lock,"
371
+ echo " /tmp/sa-active-claude, /tmp/sa-claude-blocked.json, /tmp/sa_run_claude_stdout.*)"
372
+ fi
373
+ echo
374
+
375
+ # ---- 1e. scheduled-task session transcripts (autopilot run history) --------
376
+ # Each autopilot fire writes a Claude Code session transcript under
377
+ # ~/.claude/projects/<encoded-cwd>/<uuid>.jsonl. Those are what flood the
378
+ # interactive `claude --resume` / history picker. Step [1c] removes the TASKS but
379
+ # NOT their accumulated run transcripts. Clear only OUR automated runs, identified
380
+ # by the injected <scheduled-task name="saps-…"/"social-autoposter…"> marker in
381
+ # the first user message — never the user's interactive sessions. Only reads the
382
+ # head of each .jsonl (the marker is in the opening message) and is scoped to
383
+ # *.jsonl files, so it can't block on a stray FIFO.
384
+ echo "[1e] scheduled-task session transcripts (autopilot run history)"
385
+ PROJDIR="$HOME_DIR/.claude/projects"
386
+ if [ -d "$PROJDIR" ]; then
387
+ found=0
388
+ while IFS= read -r jf; do
389
+ [ -n "$jf" ] || continue
390
+ if head -c 8000 "$jf" 2>/dev/null | grep -qE 'scheduled-task name=\\"(saps-|social-autoposter)'; then
391
+ found=$((found+1))
392
+ [ "$DRY" -eq 0 ] && rm -f "$jf"
393
+ fi
394
+ done < <(find "$PROJDIR" -type f -name '*.jsonl' 2>/dev/null)
395
+ if [ "$DRY" -eq 1 ]; then
396
+ echo " would remove $found scheduled-task transcript(s) under $PROJDIR"
397
+ else
398
+ echo " removed $found scheduled-task transcript(s)"
399
+ fi
400
+ else
401
+ echo " (~/.claude/projects not present)"
402
+ fi
403
+ echo
404
+
405
+ # ---- 2. global npm library -------------------------------------------------
406
+ echo "[2] global npm library"
407
+ if command -v npm >/dev/null 2>&1; then
408
+ if npm ls -g social-autoposter >/dev/null 2>&1; then
409
+ echo " run npm rm -g social-autoposter"
410
+ [ "$DRY" -eq 0 ] && npm rm -g social-autoposter >/dev/null 2>&1 || true
411
+ else
412
+ echo " (social-autoposter not installed as a global npm package)"
413
+ fi
414
+ else
415
+ echo " (npm not on PATH — skipping)"
416
+ fi
417
+ echo
418
+
419
+ # ---- 3. MCP registration ---------------------------------------------------
420
+ # In the default plugin reset, deregister ONLY social-autoposter; the browser-agent
421
+ # MCPs (twitter-harness/reddit-agent/linkedin-agent) are shared infra we preserve
422
+ # (their profiles + backend survive in steps 4/5). --deep deregisters them too.
423
+ echo "[3] MCP registration (claude CLI + config files)"
424
+ if [ "$PLUGIN_ONLY" -eq 1 ]; then
425
+ MCP_NAMES="social-autoposter"
426
+ else
427
+ MCP_NAMES="social-autoposter twitter-harness reddit-agent linkedin-agent"
428
+ fi
429
+ if command -v claude >/dev/null 2>&1; then
430
+ for name in $MCP_NAMES; do
431
+ echo " run claude mcp remove $name"
432
+ [ "$DRY" -eq 0 ] && claude mcp remove "$name" >/dev/null 2>&1 || true
433
+ done
434
+ else
435
+ echo " (claude CLI not on PATH — scrub config files manually if needed)"
436
+ fi
437
+ # Surgically remove ONLY the in-scope MCP stanzas from the config files. The
438
+ # claude CLI (above) isn't on PATH on the box, and even when it is it leaves the
439
+ # raw mcpServers stanza in ~/.claude.json — which the Cowork/Code boot self-heal
440
+ # (ensureCoworkMcpRegistered) reads back and RE-materializes the state dir on the
441
+ # next Claude launch. So drop the keys ourselves (backup first). Plugin reset
442
+ # removes only social-autoposter; --deep also removes the shared browser MCPs.
443
+ if command -v python3 >/dev/null 2>&1; then
444
+ for cfg in \
445
+ "$HOME_DIR/.claude.json" \
446
+ "$HOME_DIR/Library/Application Support/Claude/claude_desktop_config.json"; do
447
+ [ -f "$cfg" ] || continue
448
+ if python3 - "$cfg" $MCP_NAMES <<'PY' >/dev/null 2>&1; then
449
+ import json, sys
450
+ p, names = sys.argv[1], set(sys.argv[2:])
451
+ try:
452
+ d = json.load(open(p))
453
+ except Exception:
454
+ sys.exit(1)
455
+ srv = d.get("mcpServers") or {}
456
+ sys.exit(0 if names.intersection(srv.keys()) else 1)
457
+ PY
458
+ json_edit "remove MCP stanza(s) [$MCP_NAMES] from $cfg" \
459
+ sh -c 'cp "$1" "$1.bak" 2>/dev/null || true; shift; python3 - "$@"' sh "$cfg" "$cfg" $MCP_NAMES <<'PY'
460
+ import json, sys
461
+ p, names = sys.argv[1], set(sys.argv[2:])
462
+ d = json.load(open(p))
463
+ srv = d.get("mcpServers") or {}
464
+ for n in names:
465
+ srv.pop(n, None)
466
+ d["mcpServers"] = srv
467
+ json.dump(d, open(p, "w"), indent=2)
468
+ open(p, "a").write("\n")
469
+ PY
470
+ else
471
+ [ -f "$cfg" ] && echo " (no in-scope MCP stanza in $cfg)"
472
+ fi
473
+ done
474
+ else
475
+ for cfg in \
476
+ "$HOME_DIR/.claude.json" \
477
+ "$HOME_DIR/Library/Application Support/Claude/claude_desktop_config.json"; do
478
+ if [ -f "$cfg" ] && grep -q 'social-autoposter\|twitter-harness' "$cfg" 2>/dev/null; then
479
+ echo " NOTE python3 not on PATH — remove the MCP stanza from $cfg by hand"
480
+ fi
481
+ done
482
+ fi
483
+ echo
484
+
485
+ # ---- 4. packaged Chrome profiles + imported cookies ------------------------
486
+ # Skipped in the default plugin reset: the per-agent profiles (reddit/linkedin/
487
+ # twitter) belong to the OTHER agents, and the browser-harness profile holds the
488
+ # imported X login we preserve. Only --deep removes these.
489
+ if [ "$PLUGIN_ONLY" -eq 1 ]; then
490
+ echo "[4] packaged Chrome profiles + imported cookies — skipped (plugin reset: X login + shared profiles preserved; pass --deep to remove)"
491
+ else
492
+ echo "[4] packaged Chrome profiles + imported cookies"
493
+ PROF="$HOME_DIR/.claude/browser-profiles"
494
+ for d in browser-harness browser-harness-linkedin browser-harness-reddit reddit linkedin twitter; do
495
+ rm_path "$PROF/$d" "profile"
496
+ done
497
+ # cookie mirrors + harness logs/pids
498
+ if [ -d "$PROF" ]; then
499
+ for f in "$PROF"/*.x-cookies.json "$PROF"/*.chrome.log "$PROF"/*.chrome.pid \
500
+ "$PROF"/*.mcp.log "$PROF"/browser-activity.log*; do
501
+ [ -e "$f" ] && rm_path "$f" "cookie/log"
502
+ done
503
+ fi
504
+ fi
505
+ echo
506
+
507
+ # ---- 5. browser-harness backend -------------------------------------------
508
+ # Skipped in the default plugin reset: the harness backend is shared infra
509
+ # (twitter-harness MCP + per-platform agents drive it), not the plugin itself.
510
+ # Only --deep removes it.
511
+ if [ "$PLUGIN_ONLY" -eq 1 ]; then
512
+ echo "[5] browser-harness backend — skipped (plugin reset: shared harness backend preserved; pass --deep to remove)"
513
+ else
514
+ echo "[5] browser-harness backend (clone + uv-tool CLI + server.py)"
515
+ rm_path "$HOME_DIR/Developer/browser-harness" "harness-clone"
516
+ rm_path "$HOME_DIR/.claude/mcp-servers/browser-harness" "harness-server"
517
+ rm_path "$HOME_DIR/.local/bin/browser-harness" "harness-cli"
518
+ if command -v uv >/dev/null 2>&1; then
519
+ echo " run uv tool uninstall browser-harness"
520
+ [ "$DRY" -eq 0 ] && uv tool uninstall browser-harness >/dev/null 2>&1 || true
521
+ fi
522
+ fi
523
+ echo
524
+
525
+ # ---- 6. DEEP: shared toolchain we package (uv + chromium) ------------------
526
+ if [ "$PLUGIN_ONLY" -eq 1 ]; then
527
+ echo "[6] shared toolchain (uv, uv cache, packaged Chromium) — skipped (plugin reset; pass --deep to remove)"
528
+ else
529
+ echo "[6] shared toolchain (uv, uv cache, packaged Chromium) — ${DEEP:+}$([ "$DEEP" -eq 1 ] && echo ENABLED || echo 'skipped (pass --deep)')"
530
+ if [ "$DEEP" -eq 1 ]; then
531
+ rm_path "$HOME_DIR/Library/Caches/ms-playwright" "chromium"
532
+ rm_path "$HOME_DIR/.local/bin/uv" "uv-bin"
533
+ rm_path "$HOME_DIR/.local/bin/uvx" "uvx-bin"
534
+ rm_path "$HOME_DIR/.cache/uv" "uv-cache"
535
+ echo " WARN --deep removed uv + ms-playwright; other tools on this machine that rely on them will re-provision."
536
+ else
537
+ echo " (left uv + ~/Library/Caches/ms-playwright + ~/.cache/uv in place)"
538
+ fi
539
+ fi
540
+ echo
541
+
542
+ # ---- 7. settle & verify (defeat Claude auto-relaunch resurrection) ---------
543
+ # Claude Desktop can RELAUNCH after step [0] — on auto-update (ShipIt), a macOS
544
+ # re-open, or a login item — and a relaunched host re-materializes a skeleton
545
+ # ~/.social-autoposter-mcp/repo within seconds of the wipe, so the box is NOT
546
+ # truly first-run. Unless --keep-claude, settle it: keep Claude down and re-remove
547
+ # the state dir until it stops coming back (cap 3 rounds), then report. Leaves
548
+ # Claude Desktop DOWN on purpose — relaunch it by hand to do the fresh install.
549
+ STATE_DIR="$HOME_DIR/.social-autoposter-mcp"
550
+ if [ "$DRY" -eq 0 ] && [ "$KEEP_CLAUDE" -eq 0 ]; then
551
+ echo "[7] settle & verify (guard against Claude auto-relaunch resurrection)"
552
+ for round in 1 2 3; do
553
+ if pgrep -x "Claude" >/dev/null 2>&1; then
554
+ echo " round $round: Claude Desktop is up (relaunched) — quitting again"
555
+ osascript -e 'tell application "Claude" to quit' >/dev/null 2>&1 || true
556
+ for _ in 1 2 3 4 5; do pgrep -x "Claude" >/dev/null 2>&1 || break; sleep 1; done
557
+ pgrep -x "Claude" >/dev/null 2>&1 && pkill -9 -x "Claude" 2>/dev/null || true
558
+ sleep 1
559
+ fi
560
+ if [ -e "$STATE_DIR" ]; then
561
+ echo " round $round: removing recreated $STATE_DIR"
562
+ rm -rf "$STATE_DIR"
563
+ fi
564
+ sleep 3
565
+ if [ ! -e "$STATE_DIR" ] && ! pgrep -x "Claude" >/dev/null 2>&1; then
566
+ echo " ok state dir gone and Claude Desktop down (settled on round $round)"
567
+ break
568
+ fi
569
+ done
570
+ if [ -e "$STATE_DIR" ]; then
571
+ echo " WARN $STATE_DIR keeps reappearing after 3 rounds — something beyond"
572
+ echo " Claude is recreating it; investigate before treating as first-run."
573
+ fi
574
+ echo
575
+ fi
576
+
577
+ # ---- 8. relaunch Claude Desktop (standard path) -----------------------------
578
+ # The standard path always ends here: only after the settle step confirmed the
579
+ # wipe stuck. The extension registry entry is gone by now, so the fresh Claude
580
+ # launch comes up WITHOUT S4L and does not re-materialize the state dir.
581
+ # Skipped only with --keep-claude (Claude never went down) and on dry runs.
582
+ if [ "$DRY" -eq 0 ] && [ "$KEEP_CLAUDE" -eq 0 ]; then
583
+ echo "[8] relaunch Claude Desktop"
584
+ if open -a "Claude" >/dev/null 2>&1; then
585
+ echo " ok Claude Desktop relaunched (fresh, S4L removed)"
586
+ else
587
+ echo " WARN 'open -a Claude' failed; start Claude Desktop by hand"
588
+ fi
589
+ echo
590
+ fi
591
+
592
+ if [ "$DRY" -eq 1 ]; then
593
+ echo "=== DRY RUN complete. Re-run with --yes to actually remove. ==="
594
+ elif [ "$KEEP_CLAUDE" -eq 1 ]; then
595
+ echo "=== Reset complete. --keep-claude: Claude Desktop was left running, so"
596
+ echo "=== the registry/task/mcp edits will NOT persist until you quit it"
597
+ echo "=== yourself. ==="
598
+ else
599
+ echo "=== Reset complete. Machine is ready for a clean first-install test."
600
+ echo "=== Claude Desktop was relaunched fresh (no S4L). Re-download the"
601
+ echo "=== .mcpb for a genuine first-run install. ==="
602
+ fi