@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,271 @@
|
|
|
1
|
+
// Version resolution + update checks for the social-autoposter MCP.
|
|
2
|
+
//
|
|
3
|
+
// The "real" version is the top-level `social-autoposter` npm package version
|
|
4
|
+
// (e.g. 1.6.x) — that is what actually bundles this MCP's prebuilt dist/. The
|
|
5
|
+
// MCP's own package.json and manifest are stamped to the same version at release
|
|
6
|
+
// time (scripts/release-mcpb.sh step 3b), but historically they were frozen at
|
|
7
|
+
// 0.0.1, so this module still resolves the true version from the most
|
|
8
|
+
// authoritative source available at runtime (and tolerates a stale co-located
|
|
9
|
+
// package.json on an old bundle). It can also check npm for a newer published
|
|
10
|
+
// release so we can deliver updates on demand.
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { repoDir, run } from "./repo.js";
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
function readJsonVersion(p) {
|
|
19
|
+
try {
|
|
20
|
+
const v = JSON.parse(fs.readFileSync(p, "utf-8")).version;
|
|
21
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Resolve the REAL shipped version. Priority, most authoritative first:
|
|
28
|
+
// 1. dist/version.json — stamped by the CLI installer (bin/cli.js installMcp)
|
|
29
|
+
// from the npm package version at every init/update. Authoritative on a
|
|
30
|
+
// real user install, where the top-level package.json is NOT copied.
|
|
31
|
+
// 2. <repo>/package.json — git checkout / dev machine: the meaningful 1.6.x.
|
|
32
|
+
// 3. mcp/package.json — co-located last resort (release-stamped to match,
|
|
33
|
+
// but may be stale on an older bundle).
|
|
34
|
+
export function resolveVersion() {
|
|
35
|
+
return (readJsonVersion(path.join(__dirname, "version.json")) ||
|
|
36
|
+
readJsonVersion(path.join(repoDir(), "package.json")) ||
|
|
37
|
+
readJsonVersion(path.join(__dirname, "..", "package.json")) ||
|
|
38
|
+
"0.0.0-unknown");
|
|
39
|
+
}
|
|
40
|
+
export const VERSION = resolveVersion();
|
|
41
|
+
// Best-effort latest released version, cached per-process with a TTL. Never throws.
|
|
42
|
+
//
|
|
43
|
+
// SOURCE = GitHub releases/latest, NOT npm. This is deliberate and load-bearing:
|
|
44
|
+
// • The .mcpb boxes that run the menu-bar have NO npm/npx on PATH (PATH is just
|
|
45
|
+
// /usr/bin:/bin:/usr/sbin:/sbin). The old `npm view` probe threw there, so
|
|
46
|
+
// `latest` was always null, `update_available` was always false, and the
|
|
47
|
+
// "⬆ Update available" banner could NEVER fire on a box — even when a new
|
|
48
|
+
// release was live. (That is exactly the bug this replaced: box stuck on
|
|
49
|
+
// 1.6.177 while 1.6.181 was out, banner silent.)
|
|
50
|
+
// • GitHub releases/latest is the SAME source the box updater installs from
|
|
51
|
+
// (scripts/s4l_box_update.sh + menu-bar `_mcpb_update_work` pull the .mcpb
|
|
52
|
+
// from releases/latest/download). Detecting from the same place the update
|
|
53
|
+
// comes from means "update available" and "what an update installs" can
|
|
54
|
+
// never disagree (npm publish and the GitHub release step could otherwise
|
|
55
|
+
// drift). curl lives at /usr/bin/curl, present in every PATH, so this works
|
|
56
|
+
// with zero npm dependency.
|
|
57
|
+
// npm stays as a fallback only for the rare case GitHub is unreachable (and for
|
|
58
|
+
// dev machines checking a version that is on npm but not yet released).
|
|
59
|
+
//
|
|
60
|
+
// TTL is ~1 minute (a new release must surface within a minute — mirrored in
|
|
61
|
+
// scripts/snapshot.py, the copy the menu bar actually renders from; keep the two
|
|
62
|
+
// in lockstep). Probe order (measured 2026-07-01 releasing v1.6.188):
|
|
63
|
+
// 1. api.github.com releases/latest with a CONDITIONAL request (If-None-Match).
|
|
64
|
+
// The API reflects a new release near-instantly, and GitHub does NOT count
|
|
65
|
+
// 304 responses against the unauthenticated 60/h-per-IP quota, so a 1-min
|
|
66
|
+
// cadence is quota-free between releases (each release costs one 200). A
|
|
67
|
+
// plain (unconditional) 1-min API poll would burn the whole quota.
|
|
68
|
+
// 2. The website redirect (302 to /releases/tag/vX.Y.Z): un-rate-limited
|
|
69
|
+
// fallback, but GitHub's web tier lagged the API by ~2 minutes, so not primary.
|
|
70
|
+
// 3. npm (dev machines only; boxes have no npm).
|
|
71
|
+
let cache = null;
|
|
72
|
+
const TTL_MS = 55 * 1000;
|
|
73
|
+
const RELEASES_LATEST_URL = "https://github.com/m13v/s4l/releases/latest";
|
|
74
|
+
const RELEASES_LATEST_API = "https://api.github.com/repos/m13v/s4l/releases/latest";
|
|
75
|
+
// Staging channel resolves the newest release OVERALL (prereleases included)
|
|
76
|
+
// from the releases LIST, since releases/latest excludes prereleases. Keep this
|
|
77
|
+
// and verKey/isNewer in lockstep with scripts/snapshot.py.
|
|
78
|
+
const RELEASES_LIST_API = "https://api.github.com/repos/m13v/s4l/releases?per_page=30";
|
|
79
|
+
// Per-box release channel. `staging` boxes track the newest release overall
|
|
80
|
+
// (RCs first); everything else is `stable` (releases/latest, the historical
|
|
81
|
+
// default). Single source of truth shared with snapshot.py / s4l_channel.py:
|
|
82
|
+
// <state dir>/channel.json. Read fresh each call (cheap file read) so a channel
|
|
83
|
+
// flip takes effect on the next probe with no restart.
|
|
84
|
+
const STATE_DIR = process.env.S4L_STATE_DIR || process.env.SAPS_STATE_DIR || path.join(os.homedir(), ".social-autoposter-mcp");
|
|
85
|
+
export function releaseChannel() {
|
|
86
|
+
try {
|
|
87
|
+
const raw = fs.readFileSync(path.join(STATE_DIR, "channel.json"), "utf-8");
|
|
88
|
+
const v = (JSON.parse(raw) || {}).channel;
|
|
89
|
+
if (v === "staging" || v === "stable")
|
|
90
|
+
return v;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
/* absent/corrupt -> stable (fail-safe: never silently push a box to prerelease) */
|
|
94
|
+
}
|
|
95
|
+
return "stable";
|
|
96
|
+
}
|
|
97
|
+
function parseSemverish(v) {
|
|
98
|
+
return /^\d+\.\d+\.\d+/.test(v) ? v : null;
|
|
99
|
+
}
|
|
100
|
+
// Precedence key for an rc-aware compare: a full release outranks any prerelease
|
|
101
|
+
// of the SAME core version (1.6.193 > 1.6.193-rc.2 > 1.6.193-rc.1). For stable
|
|
102
|
+
// (no prereleases compared) this reduces to a plain numeric core compare, so
|
|
103
|
+
// behavior there is unchanged. Mirrors snapshot.py::_ver_key.
|
|
104
|
+
function verKey(v) {
|
|
105
|
+
const s = String(v).trim().replace(/^v/, "");
|
|
106
|
+
const [coreRaw, pre = ""] = s.split("-", 2);
|
|
107
|
+
const core = coreRaw.split("+")[0];
|
|
108
|
+
const nums = core.split(".").map((n) => parseInt(n, 10) || 0);
|
|
109
|
+
while (nums.length < 3)
|
|
110
|
+
nums.push(0);
|
|
111
|
+
if (!pre)
|
|
112
|
+
return [nums[0], nums[1], nums[2], 1, 0];
|
|
113
|
+
const m = pre.match(/\d+/g);
|
|
114
|
+
return [nums[0], nums[1], nums[2], 0, m ? parseInt(m[m.length - 1], 10) : 0];
|
|
115
|
+
}
|
|
116
|
+
async function latestFromGithubRedirect() {
|
|
117
|
+
try {
|
|
118
|
+
// releases/latest already excludes drafts and prereleases, so a `--draft`
|
|
119
|
+
// release correctly does NOT trigger the update banner. No -L: read the
|
|
120
|
+
// first response's Location via %{redirect_url} and stop.
|
|
121
|
+
const res = await run("curl", ["-fsS", "-m", "10", "-o", "/dev/null", "-w", "%{redirect_url}", RELEASES_LATEST_URL], { timeoutMs: 12000, noTee: true });
|
|
122
|
+
const loc = (res.stdout || "").trim();
|
|
123
|
+
if (!loc.includes("/releases/tag/"))
|
|
124
|
+
return null;
|
|
125
|
+
return parseSemverish(loc.split("/").pop().replace(/^v/, "").trim());
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// In-process conditional-request state: long-lived processes send If-None-Match
|
|
132
|
+
// on every probe and get free 304s; each new release costs a single 200.
|
|
133
|
+
const apiState = { etag: null, latest: null };
|
|
134
|
+
async function latestFromGithubApi() {
|
|
135
|
+
try {
|
|
136
|
+
const args = ["-sS", "-m", "10", "-H", "Accept: application/vnd.github+json"];
|
|
137
|
+
if (apiState.etag)
|
|
138
|
+
args.push("-H", `If-None-Match: ${apiState.etag}`);
|
|
139
|
+
args.push("-w", "\n__CURL_STATUS__:%{http_code}\n__CURL_ETAG__:%header{etag}", RELEASES_LATEST_API);
|
|
140
|
+
const res = await run("curl", args, { timeoutMs: 12000, noTee: true });
|
|
141
|
+
let status = 0;
|
|
142
|
+
let etag = null;
|
|
143
|
+
const body = [];
|
|
144
|
+
for (const line of (res.stdout || "").split("\n")) {
|
|
145
|
+
if (line.startsWith("__CURL_STATUS__:"))
|
|
146
|
+
status = parseInt(line.slice(16).trim(), 10) || 0;
|
|
147
|
+
else if (line.startsWith("__CURL_ETAG__:"))
|
|
148
|
+
etag = line.slice(14).trim() || null;
|
|
149
|
+
else
|
|
150
|
+
body.push(line);
|
|
151
|
+
}
|
|
152
|
+
if (status === 304)
|
|
153
|
+
return apiState.latest;
|
|
154
|
+
if (status !== 200)
|
|
155
|
+
return null;
|
|
156
|
+
const tag = JSON.parse(body.join("\n")).tag_name;
|
|
157
|
+
const v = typeof tag === "string" ? parseSemverish(tag.replace(/^v/, "").trim()) : null;
|
|
158
|
+
if (v) {
|
|
159
|
+
apiState.etag = etag;
|
|
160
|
+
apiState.latest = v;
|
|
161
|
+
}
|
|
162
|
+
return v;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function latestFromNpm() {
|
|
169
|
+
try {
|
|
170
|
+
const res = await run("npm", ["view", "social-autoposter", "version"], { timeoutMs: 8000 });
|
|
171
|
+
const line = res.stdout.trim().split("\n").pop()?.trim() ?? "";
|
|
172
|
+
return /^\d+\.\d+\.\d+/.test(line) ? line : null;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Staging channel: newest release OVERALL (prereleases included) from the
|
|
179
|
+
// releases LIST, since releases/latest excludes prereleases. Drafts are skipped;
|
|
180
|
+
// "newest" is by the rc-aware verKey. Returns {version, tag} or null.
|
|
181
|
+
async function latestFromGithubListStaging() {
|
|
182
|
+
try {
|
|
183
|
+
const res = await run("curl", ["-sS", "-m", "10", "-H", "Accept: application/vnd.github+json", RELEASES_LIST_API], { timeoutMs: 12000, noTee: true });
|
|
184
|
+
const rels = JSON.parse(res.stdout || "[]");
|
|
185
|
+
if (!Array.isArray(rels))
|
|
186
|
+
return null;
|
|
187
|
+
let best = null;
|
|
188
|
+
for (const r of rels) {
|
|
189
|
+
if (!r || typeof r !== "object" || r.draft)
|
|
190
|
+
continue;
|
|
191
|
+
const tag = r.tag_name;
|
|
192
|
+
if (typeof tag !== "string")
|
|
193
|
+
continue;
|
|
194
|
+
const v = parseSemverish(tag.replace(/^v/, "").trim());
|
|
195
|
+
if (!v)
|
|
196
|
+
continue;
|
|
197
|
+
const key = verKey(v);
|
|
198
|
+
if (best == null || cmpKey(key, best.key) > 0)
|
|
199
|
+
best = { version: v, tag, key };
|
|
200
|
+
}
|
|
201
|
+
return best ? { version: best.version, tag: best.tag } : null;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function cmpKey(a, b) {
|
|
208
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
209
|
+
const x = a[i] ?? 0;
|
|
210
|
+
const y = b[i] ?? 0;
|
|
211
|
+
if (x !== y)
|
|
212
|
+
return x > y ? 1 : -1;
|
|
213
|
+
}
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
// Resolve the newest release for this box's channel, cached (with the channel,
|
|
217
|
+
// so a mid-process flip re-probes). Returns the version AND the release tag: the
|
|
218
|
+
// staging download URL is built from the tag, stable uses releases/latest.
|
|
219
|
+
async function resolveLatest() {
|
|
220
|
+
const channel = releaseChannel();
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
if (cache && cache.channel === channel && now - cache.at < TTL_MS)
|
|
223
|
+
return { version: cache.latest, tag: cache.tag, channel };
|
|
224
|
+
let version = null;
|
|
225
|
+
let tag = null;
|
|
226
|
+
if (channel === "staging") {
|
|
227
|
+
const s = await latestFromGithubListStaging();
|
|
228
|
+
if (s) {
|
|
229
|
+
version = s.version;
|
|
230
|
+
tag = s.tag;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// Degrade to the stable probes so a staging box tracks at least stable
|
|
234
|
+
// rather than going blind when the list endpoint fails.
|
|
235
|
+
version = (await latestFromGithubApi()) ?? (await latestFromGithubRedirect());
|
|
236
|
+
tag = version ? `v${version}` : null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
version = await latestFromGithubApi();
|
|
241
|
+
if (version == null)
|
|
242
|
+
version = await latestFromGithubRedirect();
|
|
243
|
+
if (version == null)
|
|
244
|
+
version = await latestFromNpm();
|
|
245
|
+
tag = version ? `v${version}` : null;
|
|
246
|
+
}
|
|
247
|
+
cache = { at: now, latest: version, tag, channel };
|
|
248
|
+
return { version, tag, channel };
|
|
249
|
+
}
|
|
250
|
+
export async function latestPublishedVersion() {
|
|
251
|
+
return (await resolveLatest()).version;
|
|
252
|
+
}
|
|
253
|
+
// rc-aware compare: true when `latest` is strictly newer than `current`. A full
|
|
254
|
+
// release outranks any prerelease of the same core version, so on the staging
|
|
255
|
+
// channel one RC correctly supersedes another (1.6.193-rc.2 > 1.6.193-rc.1) and
|
|
256
|
+
// the promoted full release supersedes its RCs. Mirrors snapshot.py::_is_newer.
|
|
257
|
+
export function isNewer(latest, current) {
|
|
258
|
+
return cmpKey(verKey(latest), verKey(current)) > 0;
|
|
259
|
+
}
|
|
260
|
+
// One-shot convenience: installed + latest + whether an update is available,
|
|
261
|
+
// plus the resolved channel and release tag (the staging updater needs the tag).
|
|
262
|
+
export async function versionStatus() {
|
|
263
|
+
const { version: latest, tag, channel } = await resolveLatest();
|
|
264
|
+
return {
|
|
265
|
+
installed: VERSION,
|
|
266
|
+
latest,
|
|
267
|
+
latest_tag: tag,
|
|
268
|
+
channel,
|
|
269
|
+
update_available: !!latest && isNewer(latest, VERSION),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI entry for provisioning the owned Python/Chromium runtime.
|
|
3
|
+
//
|
|
4
|
+
// This is the terminal-side twin of the panel's "Install runtime" button and
|
|
5
|
+
// the `install_runtime` MCP tool: all three call the SAME provisioning logic in
|
|
6
|
+
// dist/runtime.js, so there is one implementation and one source of truth. The
|
|
7
|
+
// panel polls install_status; this script polls readProgress() and prints each
|
|
8
|
+
// step transition to stdout so an agent (or a human) can install head-less when
|
|
9
|
+
// the UI panel isn't available (Claude Code/Cowork, CI, a bare VM).
|
|
10
|
+
//
|
|
11
|
+
// Exit code: 0 when the runtime is ready, 1 on any step failure.
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
startProvisioning,
|
|
15
|
+
readProgress,
|
|
16
|
+
runtimeReady,
|
|
17
|
+
readRuntime,
|
|
18
|
+
} from "./dist/runtime.js";
|
|
19
|
+
|
|
20
|
+
const GLYPH = { pending: "·", running: "…", done: "✓", error: "×" };
|
|
21
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
if (runtimeReady()) {
|
|
25
|
+
const rt = readRuntime();
|
|
26
|
+
console.log(`Runtime already installed; nothing to do.`);
|
|
27
|
+
console.log(` python: ${rt?.python}`);
|
|
28
|
+
console.log(` uv: ${rt?.uv}`);
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log("Installing the social-autoposter runtime (uv, Python, Chromium).");
|
|
33
|
+
console.log("This is a one-time download; nothing touches your system Python.\n");
|
|
34
|
+
|
|
35
|
+
startProvisioning();
|
|
36
|
+
|
|
37
|
+
// Print each step the first time it leaves "pending", and again when it
|
|
38
|
+
// finishes, so the terminal shows live forward motion instead of going dark.
|
|
39
|
+
const printed = new Map(); // step id -> last status printed
|
|
40
|
+
for (;;) {
|
|
41
|
+
const p = readProgress();
|
|
42
|
+
if (p) {
|
|
43
|
+
for (const s of p.steps) {
|
|
44
|
+
if (printed.get(s.id) !== s.status && s.status !== "pending") {
|
|
45
|
+
const detail = s.status === "error" && s.detail ? ` ${s.detail}` : "";
|
|
46
|
+
console.log(` ${GLYPH[s.status] || "·"} ${s.label}${detail}`);
|
|
47
|
+
printed.set(s.id, s.status);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (p.done) {
|
|
51
|
+
if (p.ok) {
|
|
52
|
+
const rt = readRuntime();
|
|
53
|
+
console.log(`\nRuntime ready.`);
|
|
54
|
+
console.log(` python: ${rt?.python}`);
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
console.log(`\nInstall failed: ${p.error || "see the step marked × above."}`);
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
await sleep(1000);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
main()
|
|
66
|
+
.then((code) => process.exit(code))
|
|
67
|
+
.catch((err) => {
|
|
68
|
+
console.error(`install-runtime crashed: ${err?.stack || err}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
package/mcp/install.mjs
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Installs the social-autoposter MCP server into BOTH Claude Desktop and Claude Code.
|
|
3
|
+
//
|
|
4
|
+
// node install.mjs # install into both clients
|
|
5
|
+
// node install.mjs --uninstall # remove from both clients
|
|
6
|
+
//
|
|
7
|
+
// Idempotent: re-running overwrites the existing entry. Each config file is
|
|
8
|
+
// backed up (timestamped) before it is touched, and missing files/dirs are created.
|
|
9
|
+
|
|
10
|
+
import { execSync } from "node:child_process";
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
const SERVER_KEY = "social-autoposter";
|
|
16
|
+
const UNINSTALL = process.argv.includes("--uninstall");
|
|
17
|
+
|
|
18
|
+
// ---- resolve the absolute paths we want pinned into the spawn env ----------
|
|
19
|
+
const here = path.dirname(new URL(import.meta.url).pathname);
|
|
20
|
+
const repoDir = path.resolve(here, "..");
|
|
21
|
+
const distEntry = path.join(here, "dist", "index.js");
|
|
22
|
+
|
|
23
|
+
function whichNode() {
|
|
24
|
+
// Prefer the stable symlink over a versioned Cellar path so a `brew upgrade
|
|
25
|
+
// node` doesn't break the pinned command.
|
|
26
|
+
for (const p of ["/opt/homebrew/bin/node", "/usr/local/bin/node"]) {
|
|
27
|
+
if (fs.existsSync(p)) return p;
|
|
28
|
+
}
|
|
29
|
+
return process.execPath; // the node running this installer
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const nodeBin = whichNode();
|
|
33
|
+
|
|
34
|
+
function whichPython() {
|
|
35
|
+
// Prefer a real Homebrew python (has the pipeline's deps) over the macOS
|
|
36
|
+
// /usr/bin/python3 stub, which can trigger an Xcode CLT install prompt and
|
|
37
|
+
// often lacks the pipeline deps.
|
|
38
|
+
const candidates = ["/opt/homebrew/bin/python3", "/usr/local/bin/python3"];
|
|
39
|
+
try {
|
|
40
|
+
const found = execSync("command -v python3 2>/dev/null", { shell: "/bin/bash" })
|
|
41
|
+
.toString()
|
|
42
|
+
.trim();
|
|
43
|
+
if (found) candidates.push(found);
|
|
44
|
+
} catch {}
|
|
45
|
+
candidates.push("/usr/bin/python3");
|
|
46
|
+
for (const p of candidates) {
|
|
47
|
+
if (p && fs.existsSync(p)) return p;
|
|
48
|
+
}
|
|
49
|
+
return "python3"; // last resort: rely on PATH
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pythonBin = whichPython();
|
|
53
|
+
|
|
54
|
+
const serverEntry = {
|
|
55
|
+
command: nodeBin,
|
|
56
|
+
args: [distEntry],
|
|
57
|
+
env: {
|
|
58
|
+
SAPS_PYTHON: pythonBin,
|
|
59
|
+
SAPS_REPO_DIR: repoDir,
|
|
60
|
+
PATH: `${path.dirname(nodeBin)}:${path.dirname(pythonBin)}:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin`,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// ---- the two client config targets ----------------------------------------
|
|
65
|
+
const home = os.homedir();
|
|
66
|
+
const targets = [
|
|
67
|
+
{
|
|
68
|
+
name: "Claude Desktop",
|
|
69
|
+
file: path.join(
|
|
70
|
+
home,
|
|
71
|
+
"Library",
|
|
72
|
+
"Application Support",
|
|
73
|
+
"Claude",
|
|
74
|
+
"claude_desktop_config.json",
|
|
75
|
+
),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "Claude Code",
|
|
79
|
+
file: path.join(home, ".claude.json"),
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
function readJson(file) {
|
|
84
|
+
if (!fs.existsSync(file)) return {};
|
|
85
|
+
const raw = fs.readFileSync(file, "utf8").trim();
|
|
86
|
+
if (!raw) return {};
|
|
87
|
+
return JSON.parse(raw);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function backup(file) {
|
|
91
|
+
if (!fs.existsSync(file)) return null;
|
|
92
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
93
|
+
const bak = `${file}.bak-${stamp}`;
|
|
94
|
+
fs.copyFileSync(file, bak);
|
|
95
|
+
return bak;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function writeJson(file, obj) {
|
|
99
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
100
|
+
fs.writeFileSync(file, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let ok = 0;
|
|
104
|
+
for (const t of targets) {
|
|
105
|
+
try {
|
|
106
|
+
const config = readJson(t.file);
|
|
107
|
+
config.mcpServers = config.mcpServers || {};
|
|
108
|
+
|
|
109
|
+
if (UNINSTALL) {
|
|
110
|
+
if (!(SERVER_KEY in config.mcpServers)) {
|
|
111
|
+
console.log(`• ${t.name}: not present, nothing to remove`);
|
|
112
|
+
ok++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const bak = backup(t.file);
|
|
116
|
+
delete config.mcpServers[SERVER_KEY];
|
|
117
|
+
writeJson(t.file, config);
|
|
118
|
+
console.log(`✓ ${t.name}: removed "${SERVER_KEY}" (backup: ${bak})`);
|
|
119
|
+
ok++;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const existed = SERVER_KEY in config.mcpServers;
|
|
124
|
+
const bak = backup(t.file);
|
|
125
|
+
config.mcpServers[SERVER_KEY] = serverEntry;
|
|
126
|
+
writeJson(t.file, config);
|
|
127
|
+
console.log(
|
|
128
|
+
`✓ ${t.name}: ${existed ? "updated" : "added"} "${SERVER_KEY}"` +
|
|
129
|
+
(bak ? ` (backup: ${bak})` : " (created new file)"),
|
|
130
|
+
);
|
|
131
|
+
console.log(` -> ${t.file}`);
|
|
132
|
+
ok++;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error(`✗ ${t.name}: ${err.message}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log("");
|
|
139
|
+
if (ok === targets.length) {
|
|
140
|
+
if (UNINSTALL) {
|
|
141
|
+
console.log("Done. Removed from both clients.");
|
|
142
|
+
console.log("");
|
|
143
|
+
console.log(
|
|
144
|
+
"Fully QUIT and relaunch each client (Cmd+Q for Claude Desktop; restart any open",
|
|
145
|
+
);
|
|
146
|
+
console.log(
|
|
147
|
+
"Claude Code session) so the removal takes effect. MCP servers load at launch.",
|
|
148
|
+
);
|
|
149
|
+
} else {
|
|
150
|
+
console.log("Done. Registered in both clients.");
|
|
151
|
+
console.log("node: " + nodeBin);
|
|
152
|
+
console.log("python: " + pythonBin);
|
|
153
|
+
console.log("server: " + distEntry);
|
|
154
|
+
console.log("");
|
|
155
|
+
console.log(
|
|
156
|
+
"The MCP server is registered but not yet loaded (MCP servers load at launch,",
|
|
157
|
+
);
|
|
158
|
+
console.log("not per-tab). To finish setup:");
|
|
159
|
+
console.log("");
|
|
160
|
+
console.log(" 1. Fully quit Claude (Cmd+Q; closing the window is not enough).");
|
|
161
|
+
console.log(" 2. Reopen Claude.");
|
|
162
|
+
console.log(' 3. Send: "Set me up on social-autoposter."');
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
console.error(
|
|
166
|
+
`Partial: ${ok}/${targets.length} clients updated. See errors above.`,
|
|
167
|
+
);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dxt_version": "0.1",
|
|
3
|
+
"name": "social-autoposter",
|
|
4
|
+
"display_name": "S4L",
|
|
5
|
+
"version": "1.6.197-rc.7",
|
|
6
|
+
"description": "Draft, review, approve, and autopilot X/Twitter posts.",
|
|
7
|
+
"long_description": "## **⚠️ The disclaimer above is generic Claude boilerplate.** Anthropic shows the same warning on every plugin regardless of what it does; any plugin has the same level of access as any app you download from the internet.\n\nS4L is an open source product developed by Mediar.ai Incorporated, a VC-backed San Francisco-based startup.\n\nTo get started:\n\n1\\. Copy this prompt: **Set me up on S4L plugin end to end**\n\n2\\. Quit with CMD+Q, reopen Claude, paste into a new chat.\n\nWhat happens next:\n\n* About every 5 minutes S4L scans X for posts that match your topics and drafts replies in your voice.\n* Drafts show up as review cards, usually the first within a few minutes. Nothing is posted automatically; you approve each one.\n* Posting autopilot stays off until you explicitly turn it on.",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "S4L.ai",
|
|
10
|
+
"email": "i@m13v.com",
|
|
11
|
+
"url": "https://s4l.ai"
|
|
12
|
+
},
|
|
13
|
+
"server": {
|
|
14
|
+
"type": "node",
|
|
15
|
+
"entry_point": "dist/index.js",
|
|
16
|
+
"mcp_config": {
|
|
17
|
+
"command": "node",
|
|
18
|
+
"args": [
|
|
19
|
+
"${__dirname}/dist/index.js"
|
|
20
|
+
],
|
|
21
|
+
"env": {
|
|
22
|
+
"PATH": "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"tools": [
|
|
27
|
+
{
|
|
28
|
+
"name": "engagement_mode",
|
|
29
|
+
"description": "Choose engagement lanes (personal brand + optional product promotion)"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "project_config",
|
|
33
|
+
"description": "Configure or edit a project"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "post_drafts",
|
|
37
|
+
"description": "Post chosen drafts"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "get_stats",
|
|
41
|
+
"description": "Get X/Twitter stats"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "runtime",
|
|
45
|
+
"description": "Runtime: status, update & diagnostics"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "restart_menubar",
|
|
49
|
+
"description": "Restart the S4L menu bar app"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "queue_setup",
|
|
53
|
+
"description": "Get autopilot scheduled-task specs"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "dashboard",
|
|
57
|
+
"description": "S4L dashboard"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "connect_product",
|
|
61
|
+
"description": "Add your product"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "show_browser_to_user",
|
|
65
|
+
"description": "Show browser to user"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"keywords": [
|
|
69
|
+
"twitter",
|
|
70
|
+
"x",
|
|
71
|
+
"social",
|
|
72
|
+
"autoposter",
|
|
73
|
+
"marketing"
|
|
74
|
+
],
|
|
75
|
+
"license": "MIT",
|
|
76
|
+
"repository": {
|
|
77
|
+
"type": "git",
|
|
78
|
+
"url": "https://github.com/m13v/s4l"
|
|
79
|
+
}
|
|
80
|
+
}
|