@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.
- package/README.md +143 -0
- package/SKILL.md +342 -0
- package/bin/cli.js +980 -0
- package/bin/cookie-helper.js +315 -0
- package/bin/platform.js +59 -0
- package/bin/scheduler/index.js +12 -0
- package/bin/scheduler/launchd.js +518 -0
- package/browser-agent-configs/all-agents-mcp.json +68 -0
- package/browser-agent-configs/linkedin-agent-mcp.json +16 -0
- package/browser-agent-configs/linkedin-agent.json +17 -0
- package/browser-agent-configs/linkedin-harness-mcp.json +21 -0
- package/browser-agent-configs/reddit-agent-mcp.json +16 -0
- package/browser-agent-configs/reddit-agent.json +17 -0
- package/browser-agent-configs/twitter-harness-mcp.json +18 -0
- package/config.example.json +45 -0
- package/mcp/dist/index.js +4212 -0
- package/mcp/dist/onboarding.js +200 -0
- package/mcp/dist/panel.html +176 -0
- package/mcp/dist/product-link.html +102 -0
- package/mcp/dist/repo.js +222 -0
- package/mcp/dist/runtime.js +1079 -0
- package/mcp/dist/screencast.js +323 -0
- package/mcp/dist/setup.js +545 -0
- package/mcp/dist/telemetry.js +306 -0
- package/mcp/dist/twitterAuth.js +138 -0
- package/mcp/dist/version.js +271 -0
- package/mcp/dist/version.json +4 -0
- package/mcp/install-runtime.mjs +70 -0
- package/mcp/install.mjs +169 -0
- package/mcp/manifest.json +80 -0
- package/mcp/menubar/dashboard_server.py +213 -0
- package/mcp/menubar/s4l_card.py +1314 -0
- package/mcp/menubar/s4l_log_relay.py +179 -0
- package/mcp/menubar/s4l_menubar.py +2439 -0
- package/mcp/menubar/s4l_state.py +891 -0
- package/mcp/package.json +34 -0
- package/mcp/shared/doctor.cjs +437 -0
- package/mcp/shared/onboarding-ledger.cjs +324 -0
- package/mcp-servers/browser-harness/server.py +968 -0
- package/package.json +160 -0
- package/requirements.txt +20 -0
- package/scripts/_compute_allowlist.py +58 -0
- package/scripts/_db_update.py +20 -0
- package/scripts/_filt.py +9 -0
- package/scripts/_li_notif_match.py +76 -0
- package/scripts/_li_notif_orchestrate.py +126 -0
- package/scripts/_lock_preempt_test.py +60 -0
- package/scripts/_run_icp_precheck.py +57 -0
- package/scripts/a16z_pearx_calendar_reminders.py +99 -0
- package/scripts/account_resolver.py +141 -0
- package/scripts/active_campaigns.py +114 -0
- package/scripts/active_users.py +190 -0
- package/scripts/amplitude_24h_signups.py +468 -0
- package/scripts/amplitude_signups.py +177 -0
- package/scripts/apply_onboarding_selections.py +131 -0
- package/scripts/audience_pages.py +243 -0
- package/scripts/audit_helper.py +120 -0
- package/scripts/author_history_block.py +353 -0
- package/scripts/autopilot_stall_watch.py +284 -0
- package/scripts/backfill_twitter_attempts_topic.py +81 -0
- package/scripts/backfill_twitter_log_post_no_id.py +322 -0
- package/scripts/bench_dashboard.sh +138 -0
- package/scripts/bh_send.py +39 -0
- package/scripts/build_persona.py +409 -0
- package/scripts/bulk_icp.py +18 -0
- package/scripts/campaign_bump.py +51 -0
- package/scripts/capture_thread_media.py +288 -0
- package/scripts/check_browser_lock_health.sh +81 -0
- package/scripts/check_external_pool_depth.py +253 -0
- package/scripts/check_unread_web_chats.py +28 -0
- package/scripts/claim_web_chat.py +47 -0
- package/scripts/classify_run_error.py +158 -0
- package/scripts/claude_job.py +988 -0
- package/scripts/clean_stale_singleton.sh +56 -0
- package/scripts/cleanup_harness_tabs.py +68 -0
- package/scripts/copy_browser_cookies.py +454 -0
- package/scripts/counterparty_history.py +350 -0
- package/scripts/db.py +57 -0
- package/scripts/discover_claude_profiles.py +120 -0
- package/scripts/discover_linkedin_candidates.py +984 -0
- package/scripts/dm_conversation.py +682 -0
- package/scripts/dm_db_update.py +69 -0
- package/scripts/dm_engage_helper.py +161 -0
- package/scripts/dm_outreach_helper.py +147 -0
- package/scripts/dm_outreach_twitter_helper.py +129 -0
- package/scripts/dm_send_log.py +106 -0
- package/scripts/dm_short_links.py +1084 -0
- package/scripts/dump_web_chat_history.py +47 -0
- package/scripts/engage_github.py +640 -0
- package/scripts/engage_reddit.py +1235 -0
- package/scripts/engage_twitter_helper.py +301 -0
- package/scripts/engagement_styles.py +1787 -0
- package/scripts/enrich_twitter_candidates.py +82 -0
- package/scripts/feedback_digest.py +448 -0
- package/scripts/fetch_prospect_profile.py +312 -0
- package/scripts/fetch_twitter_t1.py +134 -0
- package/scripts/find_threads.py +530 -0
- package/scripts/follow_gate_log.py +59 -0
- package/scripts/funnel_per_day.py +194 -0
- package/scripts/generate_daily_human_style.py +494 -0
- package/scripts/generation_trace.py +173 -0
- package/scripts/get_run_cost.py +107 -0
- package/scripts/github_engage_helper.py +93 -0
- package/scripts/github_tools.py +509 -0
- package/scripts/harness_overlay.py +556 -0
- package/scripts/harvest_twitter_following.py +243 -0
- package/scripts/heartbeat.sh +70 -0
- package/scripts/history_context.py +284 -0
- package/scripts/http_api.py +206 -0
- package/scripts/human_dm_replies_helper.py +169 -0
- package/scripts/identity.py +302 -0
- package/scripts/ig_batch_creator.sh +93 -0
- package/scripts/ig_post_type_picker.py +243 -0
- package/scripts/ig_scrape_transcribe.sh +91 -0
- package/scripts/ingest_human_dm_replies.py +271 -0
- package/scripts/ingest_web_chat_replies.py +229 -0
- package/scripts/install_fleet.py +187 -0
- package/scripts/invent_mcp_server.py +350 -0
- package/scripts/invent_topics.py +1462 -0
- package/scripts/learned_preferences.py +263 -0
- package/scripts/li_discovery.py +161 -0
- package/scripts/link_edit_helper.py +142 -0
- package/scripts/link_tail.py +592 -0
- package/scripts/linkedin_api.py +561 -0
- package/scripts/linkedin_browser.py +730 -0
- package/scripts/linkedin_cooldown.py +128 -0
- package/scripts/linkedin_exclusions.py +234 -0
- package/scripts/linkedin_killswitch.py +1333 -0
- package/scripts/linkedin_search_topic_schema.py +49 -0
- package/scripts/linkedin_unipile.py +658 -0
- package/scripts/linkedin_url.py +228 -0
- package/scripts/log_claude_session.py +636 -0
- package/scripts/log_draft.py +143 -0
- package/scripts/log_linkedin_search_attempts.py +126 -0
- package/scripts/log_post.py +651 -0
- package/scripts/log_run.py +364 -0
- package/scripts/log_thread_media.py +108 -0
- package/scripts/log_twitter_search_attempts.py +150 -0
- package/scripts/log_twitter_skips.py +211 -0
- package/scripts/lookup_post.py +78 -0
- package/scripts/mark_web_chat_processed.py +32 -0
- package/scripts/mcp_lock_proxy.py +370 -0
- package/scripts/memory_snapshot.py +972 -0
- package/scripts/merge_review_queue.py +215 -0
- package/scripts/mint_external_pool.py +182 -0
- package/scripts/mint_kent_pool.py +249 -0
- package/scripts/moltbook_post.py +320 -0
- package/scripts/moltbook_tools.py +159 -0
- package/scripts/pending_threads.py +188 -0
- package/scripts/pick_ig_account.py +177 -0
- package/scripts/pick_project.py +208 -0
- package/scripts/pick_search_topic.py +771 -0
- package/scripts/pick_thread_target.py +279 -0
- package/scripts/pick_twitter_thread_target.py +202 -0
- package/scripts/podlog_fetch_batch.sh +32 -0
- package/scripts/post_github.py +1311 -0
- package/scripts/post_reddit.py +2668 -0
- package/scripts/precompute_dashboard_stats.py +204 -0
- package/scripts/preflight.sh +297 -0
- package/scripts/progress.py +88 -0
- package/scripts/project_excludes.py +353 -0
- package/scripts/project_slugs.py +91 -0
- package/scripts/project_stats.py +241 -0
- package/scripts/project_stats_json.py +1563 -0
- package/scripts/project_topics.py +192 -0
- package/scripts/qualified_query_bank.py +436 -0
- package/scripts/reap_stale_claude_sessions.py +867 -0
- package/scripts/reddit_browser.py +2549 -0
- package/scripts/reddit_browser_fetch.py +141 -0
- package/scripts/reddit_browser_lock.py +593 -0
- package/scripts/reddit_chat_sync.py +710 -0
- package/scripts/reddit_query_bank.py +200 -0
- package/scripts/reddit_threads_helper.py +151 -0
- package/scripts/reddit_tools.py +956 -0
- package/scripts/refresh_instagram_tokens.py +280 -0
- package/scripts/release-mcpb.sh +497 -0
- package/scripts/reply_db.py +334 -0
- package/scripts/reply_insert.py +98 -0
- package/scripts/reply_risk_digest.py +761 -0
- package/scripts/reset-test-machine.sh +602 -0
- package/scripts/restore_twitter_session.py +177 -0
- package/scripts/ripen_reddit_plan.py +478 -0
- package/scripts/run_claude.sh +433 -0
- package/scripts/run_moltbook_cycle.py +555 -0
- package/scripts/s4l_box_update.sh +226 -0
- package/scripts/s4l_channel.py +103 -0
- package/scripts/s4l_ctl.sh +75 -0
- package/scripts/s4l_env.py +47 -0
- package/scripts/saps_activity.py +126 -0
- package/scripts/saps_mode.py +328 -0
- package/scripts/scan_dm_candidates.py +580 -0
- package/scripts/scan_github_replies.py +168 -0
- package/scripts/scan_instagram_comments.py +481 -0
- package/scripts/scan_moltbook_replies.py +252 -0
- package/scripts/scan_pii.py +190 -0
- package/scripts/scan_reddit_replies.py +377 -0
- package/scripts/scan_twitter_mentions_browser.py +327 -0
- package/scripts/scan_twitter_thread_followups.py +299 -0
- package/scripts/scan_x_profile.py +384 -0
- package/scripts/schedule_state.py +202 -0
- package/scripts/scheduled_tasks_snapshot.py +123 -0
- package/scripts/score_linkedin_candidates.py +419 -0
- package/scripts/score_twitter_candidates.py +718 -0
- package/scripts/scrape_linkedin_comment_stats.py +1755 -0
- package/scripts/scrape_linkedin_stats_browser.py +52 -0
- package/scripts/scrape_reddit_views.py +365 -0
- package/scripts/seed_search_queries.py +453 -0
- package/scripts/seed_search_topics.py +127 -0
- package/scripts/send_web_chat_reply.py +130 -0
- package/scripts/sentry_init.py +128 -0
- package/scripts/setup_twitter_auth.py +1320 -0
- package/scripts/snapshot.py +583 -0
- package/scripts/stats.py +2702 -0
- package/scripts/stats_helper.py +52 -0
- package/scripts/strike_alert.py +783 -0
- package/scripts/sweep_post_link_clicks.py +107 -0
- package/scripts/sync_ig_to_posts.py +147 -0
- package/scripts/test_browser_lock.py +189 -0
- package/scripts/test_installation_api.sh +52 -0
- package/scripts/test_percard_posting.py +142 -0
- package/scripts/top_dud_linkedin_queries.py +71 -0
- package/scripts/top_dud_reddit_queries.py +67 -0
- package/scripts/top_dud_twitter_queries.py +71 -0
- package/scripts/top_dud_twitter_topics.py +102 -0
- package/scripts/top_linkedin_queries.py +55 -0
- package/scripts/top_omitted_reddit_topics.py +91 -0
- package/scripts/top_performers.py +588 -0
- package/scripts/top_search_topics.py +180 -0
- package/scripts/top_twitter_queries.py +190 -0
- package/scripts/twitter_access_check.py +382 -0
- package/scripts/twitter_account.py +41 -0
- package/scripts/twitter_batch_phase.py +126 -0
- package/scripts/twitter_browser.py +2804 -0
- package/scripts/twitter_cookie_mirror.py +130 -0
- package/scripts/twitter_cycle_helper.py +310 -0
- package/scripts/twitter_gen_links.py +287 -0
- package/scripts/twitter_post_plan.py +1188 -0
- package/scripts/twitter_scan.py +324 -0
- package/scripts/twitter_supply_signal.py +57 -0
- package/scripts/twitter_threads_helper.py +152 -0
- package/scripts/unclaim_web_chat.py +29 -0
- package/scripts/update_instagram_stats.py +261 -0
- package/scripts/update_linkedin_stats_from_feed.py +328 -0
- package/scripts/version.py +72 -0
- package/scripts/watchdog_hung_runs.py +343 -0
- package/scripts/write_generation_trace.py +73 -0
- package/setup/SKILL.md +277 -0
- package/skill/amplitude-24h-signups.sh +38 -0
- package/skill/archive-old-logs.sh +40 -0
- package/skill/audit-dm-staleness.sh +42 -0
- package/skill/audit-linkedin.sh +14 -0
- package/skill/audit-moltbook.sh +4 -0
- package/skill/audit-reddit-resurrect.sh +67 -0
- package/skill/audit-reddit.sh +4 -0
- package/skill/audit-twitter.sh +4 -0
- package/skill/audit.sh +287 -0
- package/skill/backfill-twitter-attempts-topic.sh +19 -0
- package/skill/backfill-twitter-ghost-posts.sh +24 -0
- package/skill/check-external-pool-depth.sh +7 -0
- package/skill/check-web-chats.sh +203 -0
- package/skill/dm-outreach-linkedin.sh +250 -0
- package/skill/dm-outreach-reddit.sh +274 -0
- package/skill/dm-outreach-twitter.sh +265 -0
- package/skill/engage-dm-replies-linkedin.sh +4 -0
- package/skill/engage-dm-replies-reddit.sh +4 -0
- package/skill/engage-dm-replies-twitter.sh +4 -0
- package/skill/engage-dm-replies.sh +1597 -0
- package/skill/engage-linkedin.sh +581 -0
- package/skill/engage-moltbook.sh +36 -0
- package/skill/engage-reddit.sh +146 -0
- package/skill/engage-twitter.sh +467 -0
- package/skill/github-engage.sh +176 -0
- package/skill/ingest-web-chat-replies.sh +38 -0
- package/skill/invent-supply-test.sh +100 -0
- package/skill/invent-topics.sh +50 -0
- package/skill/lib/linkedin-backend.sh +364 -0
- package/skill/lib/platform.sh +48 -0
- package/skill/lib/reddit-backend.sh +234 -0
- package/skill/lib/twitter-backend.sh +314 -0
- package/skill/link-edit-github.sh +136 -0
- package/skill/link-edit-moltbook.sh +117 -0
- package/skill/link-edit-reddit.sh +201 -0
- package/skill/linkedin-presence.sh +182 -0
- package/skill/linkedin-recovery.sh +282 -0
- package/skill/lock.sh +647 -0
- package/skill/memory-snapshot.sh +39 -0
- package/skill/precompute-stats.sh +35 -0
- package/skill/prewarm-funnel.sh +104 -0
- package/skill/refresh-instagram-tokens.sh +57 -0
- package/skill/refresh-twitter-following.sh +52 -0
- package/skill/reply-risk-digest.sh +31 -0
- package/skill/run-cycle-update-guard.sh +44 -0
- package/skill/run-draft-and-publish.sh +123 -0
- package/skill/run-generate-daily-style.sh +50 -0
- package/skill/run-github-launchd.sh +62 -0
- package/skill/run-github.sh +102 -0
- package/skill/run-instagram-daily.sh +149 -0
- package/skill/run-instagram-render.sh +875 -0
- package/skill/run-linkedin-launchd.sh +81 -0
- package/skill/run-linkedin-unipile.sh +130 -0
- package/skill/run-linkedin.sh +1593 -0
- package/skill/run-moltbook-launchd.sh +61 -0
- package/skill/run-moltbook.sh +38 -0
- package/skill/run-overlay-watch.sh +100 -0
- package/skill/run-reddit-search-launchd.sh +64 -0
- package/skill/run-reddit-search.sh +505 -0
- package/skill/run-reddit-threads-double.sh +32 -0
- package/skill/run-reddit-threads.sh +847 -0
- package/skill/run-scan-moltbook-replies.sh +57 -0
- package/skill/run-twitter-cycle-launchd.sh +63 -0
- package/skill/run-twitter-cycle-singleton.sh +62 -0
- package/skill/run-twitter-cycle.sh +2408 -0
- package/skill/run-twitter-threads.sh +592 -0
- package/skill/scan-instagram-replies.sh +61 -0
- package/skill/scan-twitter-followups.sh +57 -0
- package/skill/social-autoposter-update.sh +66 -0
- package/skill/stats-instagram.sh +72 -0
- package/skill/stats-linkedin.sh +271 -0
- package/skill/stats-moltbook.sh +4 -0
- package/skill/stats-reddit.sh +4 -0
- package/skill/stats-twitter.sh +4 -0
- package/skill/stats.sh +521 -0
- package/skill/strike-alert.sh +18 -0
- package/skill/styles.sh +87 -0
- package/skill/sweep-link-clicks.sh +40 -0
- 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
|