@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,497 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# THE single release flow for social-autoposter. One command does EVERYTHING and
|
|
3
|
+
# keeps the npm path (Story A) and the .mcpb double-click path (Story B) on one
|
|
4
|
+
# version, because both derive from one bumped repo-root package.json.
|
|
5
|
+
#
|
|
6
|
+
# What it does, end to end:
|
|
7
|
+
# 1. Bump the repo-root package.json (the SINGLE source of truth) and lockfile.
|
|
8
|
+
# Default: patch bump. --bump minor|major, or pin with --version / --tag,
|
|
9
|
+
# or --no-bump to re-release the current version as-is.
|
|
10
|
+
# 2. Stamp EVERY version satellite from that one source, THEN build, so the
|
|
11
|
+
# embedded pipeline.tgz (an `npm pack` of the repo) captures an all-current
|
|
12
|
+
# mcp/ subtree. Order is load-bearing: satellites (manifest.json,
|
|
13
|
+
# mcp/package.json+lock, dist/version.json) are stamped BEFORE the pack, not
|
|
14
|
+
# after, or the tarball ships a stale mcp/ subtree (the 1.6.181-menu bug).
|
|
15
|
+
# Sub-steps: 2a stamp source satellites -> 2b build panel+server -> 2c stamp
|
|
16
|
+
# dist/version.json -> 2d pack pipeline.tgz.
|
|
17
|
+
# 3. (Regenerate manifest.json `tools` from the server's registrations.)
|
|
18
|
+
# 4. Pack mcp/ into mcp/social-autoposter.mcpb via the mcpb CLI.
|
|
19
|
+
# 5. Verify: size cap, embedded pipeline.tgz present, version.json + manifest +
|
|
20
|
+
# the pipeline.tgz's OWN internal version AND its mcp/ subtree (dist/version.json,
|
|
21
|
+
# mcp/package.json) all == VERSION (guards the 1.6.84 stale-pipeline and the
|
|
22
|
+
# 1.6.181 stale-menu bugs), install tools present.
|
|
23
|
+
# 6. npm publish social-autoposter@VERSION (idempotent; skipped if already live).
|
|
24
|
+
# 7. Create/update GitHub release vX.Y.Z and upload the .mcpb (--clobber).
|
|
25
|
+
#
|
|
26
|
+
# Boxes self-update from the GitHub release: the menu-bar updater polls
|
|
27
|
+
# releases/latest, pulls the new .mcpb, and on next server boot
|
|
28
|
+
# ensurePipelineCurrent() re-extracts pipeline.tgz. No manual box step needed.
|
|
29
|
+
#
|
|
30
|
+
# WHERE THE "⬆ Update available" BANNER COMES FROM (read this before touching the
|
|
31
|
+
# release/detection path): the menu-bar banner is driven by versionStatus() in
|
|
32
|
+
# mcp/src/version.ts, which resolves "latest" from GitHub releases/latest (via
|
|
33
|
+
# curl), NOT from npm. .mcpb boxes have no npm/npx on PATH, so an npm-based probe
|
|
34
|
+
# is permanently blind there. Consequence for releasing: the banner fires only
|
|
35
|
+
# after the GitHub release step (7) lands and releases/latest serves the new tag,
|
|
36
|
+
# NOT after npm publish (6). Step 8 below verifies that so a release can't
|
|
37
|
+
# "succeed" while every box stays silent on the old version.
|
|
38
|
+
#
|
|
39
|
+
# CHANNELS (2026-07-02): a box can opt into pre-release builds by setting its
|
|
40
|
+
# channel to `staging` (scripts/s4l_channel.py). Staging releases are GitHub
|
|
41
|
+
# PRE-releases with an -rc.N version, so releases/latest and npm `latest` do NOT
|
|
42
|
+
# move — only staging boxes pull them (they resolve the newest release from the
|
|
43
|
+
# releases LIST endpoint). `--promote <tag>` flips a tested pre-release to stable
|
|
44
|
+
# IN PLACE (no rebuild): it clears the prerelease flag + moves npm `latest`, so
|
|
45
|
+
# the EXACT tested artifact ships to everyone. Nothing can drift between test and
|
|
46
|
+
# ship because there is no repack.
|
|
47
|
+
#
|
|
48
|
+
# Usage:
|
|
49
|
+
# bash scripts/release-mcpb.sh # patch bump, npm + .mcpb + GitHub (STABLE)
|
|
50
|
+
# bash scripts/release-mcpb.sh --bump minor
|
|
51
|
+
# bash scripts/release-mcpb.sh --version 1.7.0 # pin an exact version
|
|
52
|
+
# bash scripts/release-mcpb.sh --no-bump # re-release current package.json version
|
|
53
|
+
# bash scripts/release-mcpb.sh --no-npm # skip npm publish (only .mcpb + GitHub)
|
|
54
|
+
# bash scripts/release-mcpb.sh --no-release # build + pack + verify only (no npm, no GitHub)
|
|
55
|
+
# bash scripts/release-mcpb.sh --draft # GitHub release as a draft
|
|
56
|
+
# bash scripts/release-mcpb.sh --staging # PRE-release -rc.N (staging channel only)
|
|
57
|
+
# bash scripts/release-mcpb.sh --promote v1.6.193-rc.2 # ship a tested pre-release to stable
|
|
58
|
+
|
|
59
|
+
set -euo pipefail
|
|
60
|
+
|
|
61
|
+
# Homebrew node/gh/mcpb are not on the default Fazm/launchd PATH.
|
|
62
|
+
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
63
|
+
|
|
64
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
65
|
+
MCP_DIR="$REPO_ROOT/mcp"
|
|
66
|
+
BUNDLE="$MCP_DIR/social-autoposter.mcpb"
|
|
67
|
+
GH_REPO="m13v/s4l"
|
|
68
|
+
SIZE_CAP_MB=180
|
|
69
|
+
|
|
70
|
+
TAG_OVERRIDE=""
|
|
71
|
+
VERSION_OVERRIDE=""
|
|
72
|
+
DO_RELEASE=1
|
|
73
|
+
DO_NPM=1
|
|
74
|
+
DO_BUMP=1
|
|
75
|
+
BUMP_LEVEL="patch"
|
|
76
|
+
DRAFT_FLAG=""
|
|
77
|
+
# CHANNEL (2026-07-02): staging releases are GitHub PRE-releases carrying an
|
|
78
|
+
# -rc.N version, so they are invisible to releases/latest (stable boxes) and to
|
|
79
|
+
# npm's `latest` dist-tag — only a box on the `staging` channel pulls them. See
|
|
80
|
+
# scripts/s4l_channel.py. `--promote <tag>` flips a tested pre-release to stable
|
|
81
|
+
# IN PLACE (no rebuild, the exact tested artifact) by clearing its prerelease
|
|
82
|
+
# flag + moving npm `latest`.
|
|
83
|
+
DO_STAGING=0
|
|
84
|
+
PROMOTE_TAG=""
|
|
85
|
+
|
|
86
|
+
while [[ $# -gt 0 ]]; do
|
|
87
|
+
case "$1" in
|
|
88
|
+
--tag) TAG_OVERRIDE="$2"; shift 2 ;;
|
|
89
|
+
--tag=*) TAG_OVERRIDE="${1#*=}"; shift ;;
|
|
90
|
+
--version) VERSION_OVERRIDE="$2"; shift 2 ;;
|
|
91
|
+
--version=*) VERSION_OVERRIDE="${1#*=}"; shift ;;
|
|
92
|
+
--bump) BUMP_LEVEL="$2"; shift 2 ;;
|
|
93
|
+
--bump=*) BUMP_LEVEL="${1#*=}"; shift ;;
|
|
94
|
+
--no-bump) DO_BUMP=0; shift ;;
|
|
95
|
+
--no-npm) DO_NPM=0; shift ;;
|
|
96
|
+
--no-release) DO_RELEASE=0; shift ;;
|
|
97
|
+
--draft) DRAFT_FLAG="--draft"; shift ;;
|
|
98
|
+
--staging) DO_STAGING=1; shift ;;
|
|
99
|
+
--promote) PROMOTE_TAG="$2"; shift 2 ;;
|
|
100
|
+
--promote=*) PROMOTE_TAG="${1#*=}"; shift ;;
|
|
101
|
+
-h|--help) sed -n '2,46p' "$0"; exit 0 ;;
|
|
102
|
+
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
103
|
+
esac
|
|
104
|
+
done
|
|
105
|
+
|
|
106
|
+
case "$BUMP_LEVEL" in
|
|
107
|
+
patch|minor|major) ;;
|
|
108
|
+
*) echo "invalid --bump level: $BUMP_LEVEL (want patch|minor|major)" >&2; exit 2 ;;
|
|
109
|
+
esac
|
|
110
|
+
|
|
111
|
+
say() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
|
|
112
|
+
die() { printf '\033[1mERROR:\033[0m %s\n' "$*" >&2; exit 1; }
|
|
113
|
+
|
|
114
|
+
command -v node >/dev/null || die "node not found on PATH"
|
|
115
|
+
|
|
116
|
+
# ---- 0. Promote a tested pre-release to stable (no rebuild) ------------------
|
|
117
|
+
# Flip the SAME artifact the staging box tested: clear GitHub's prerelease flag
|
|
118
|
+
# and mark it latest (so releases/latest + the stable boxes pick it up), and move
|
|
119
|
+
# npm's `latest` dist-tag onto it. Byte-for-byte identical to what was tested;
|
|
120
|
+
# there is no repack, so nothing can drift between test and ship. The version
|
|
121
|
+
# keeps its -rc.N label on purpose (that IS the tested build); cut a fresh stable
|
|
122
|
+
# patch later if you want a clean number.
|
|
123
|
+
if [[ -n "$PROMOTE_TAG" ]]; then
|
|
124
|
+
command -v gh >/dev/null || die "gh CLI not found"
|
|
125
|
+
gh auth status >/dev/null 2>&1 || die "gh not authenticated (run: gh auth login)"
|
|
126
|
+
PTAG="$PROMOTE_TAG"; [[ "$PTAG" == v* ]] || PTAG="v$PTAG"
|
|
127
|
+
PVER="${PTAG#v}"
|
|
128
|
+
gh release view "$PTAG" -R "$GH_REPO" >/dev/null 2>&1 || die "no release $PTAG to promote"
|
|
129
|
+
say "Promoting $PTAG to stable (in place; same tested artifact, no rebuild)"
|
|
130
|
+
gh release edit "$PTAG" -R "$GH_REPO" --prerelease=false --latest
|
|
131
|
+
if [[ "$DO_NPM" == "1" ]]; then
|
|
132
|
+
command -v npm >/dev/null || die "npm not found on PATH"
|
|
133
|
+
say "Moving npm 'latest' dist-tag -> $PVER"
|
|
134
|
+
npm dist-tag add "social-autoposter@$PVER" latest || die "npm dist-tag add failed"
|
|
135
|
+
# Keep the dual-published "s4l" package's dist-tag in lockstep. Best-effort:
|
|
136
|
+
# releases cut before the 2026-07-03 dual-publish have no s4l@$PVER, so a
|
|
137
|
+
# miss here warns instead of failing the promote.
|
|
138
|
+
npm dist-tag add "@m13v/s4l@$PVER" latest \
|
|
139
|
+
|| echo " WARNING: npm dist-tag add @m13v/s4l@$PVER latest failed (version may predate the dual-publish)" >&2
|
|
140
|
+
fi
|
|
141
|
+
say "Verifying releases/latest serves $PTAG (drives stable boxes' update banner)"
|
|
142
|
+
LATEST_SEEN=""
|
|
143
|
+
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
144
|
+
LATEST_SEEN="$(curl -fsSL -m 15 "https://api.github.com/repos/$GH_REPO/releases/latest" \
|
|
145
|
+
| node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).tag_name || ''" 2>/dev/null || echo "")"
|
|
146
|
+
[[ "$LATEST_SEEN" == "$PTAG" ]] && break
|
|
147
|
+
sleep 6
|
|
148
|
+
done
|
|
149
|
+
[[ "$LATEST_SEEN" == "$PTAG" ]] \
|
|
150
|
+
&& echo " releases/latest -> $LATEST_SEEN; stable boxes detect within ~1 min" \
|
|
151
|
+
|| echo " WARNING: releases/latest still reports '${LATEST_SEEN:-<none>}', not $PTAG (GitHub may still be propagating)." >&2
|
|
152
|
+
say "Promoted $PTAG"
|
|
153
|
+
exit 0
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
command -v mcpb >/dev/null || die "mcpb CLI not found (npm i -g @anthropic-ai/mcpb)"
|
|
157
|
+
|
|
158
|
+
# ---- 1. Resolve + WRITE version into the repo-root package.json -------------
|
|
159
|
+
# The repo-root package.json is the SINGLE source of truth: `npm pack` reads it
|
|
160
|
+
# to build the embedded pipeline.tgz, so the bundle shell and the bundled
|
|
161
|
+
# pipeline cannot diverge as long as we bump it BEFORE building (step 2). This
|
|
162
|
+
# closes the 1.6.84-class bug where the shell said X but pipeline.tgz carried
|
|
163
|
+
# the prior version because only the satellites were stamped.
|
|
164
|
+
PKG_VERSION="$(node -p "require('$REPO_ROOT/package.json').version")"
|
|
165
|
+
if [[ -n "$VERSION_OVERRIDE" ]]; then
|
|
166
|
+
VERSION="${VERSION_OVERRIDE#v}"
|
|
167
|
+
elif [[ -n "$TAG_OVERRIDE" ]]; then
|
|
168
|
+
VERSION="${TAG_OVERRIDE#v}"
|
|
169
|
+
elif [[ "$DO_STAGING" == "1" ]]; then
|
|
170
|
+
# Staging = the next -rc.N. If package.json already carries an -rc.N for an
|
|
171
|
+
# unreleased patch, bump the rc; otherwise start rc.1 on the next patch of the
|
|
172
|
+
# current full release. The -rc.N rides through EVERY satellite (manifest,
|
|
173
|
+
# pipeline.tgz, dist/version.json) so a staging box's installed version string
|
|
174
|
+
# distinguishes rc.1 from rc.2 (the rc-aware compare in version.ts/snapshot.py
|
|
175
|
+
# depends on that).
|
|
176
|
+
VERSION="$(node -e "
|
|
177
|
+
const v='$PKG_VERSION';
|
|
178
|
+
const m=v.match(/^(\d+)\.(\d+)\.(\d+)(?:-rc\.(\d+))?/);
|
|
179
|
+
let [_,a,b,c,rc]=m.map((x)=>x);
|
|
180
|
+
a=+a;b=+b;c=+c;
|
|
181
|
+
if(rc!==undefined){ rc=+rc+1; } else { c=c+1; rc=1; }
|
|
182
|
+
console.log(a+'.'+b+'.'+c+'-rc.'+rc);
|
|
183
|
+
")"
|
|
184
|
+
elif [[ "$DO_BUMP" == "1" ]]; then
|
|
185
|
+
# Compute the next version without writing a git tag; we own the write below.
|
|
186
|
+
VERSION="$(node -e "
|
|
187
|
+
let [a,b,c]='$PKG_VERSION'.split('.').map(Number);
|
|
188
|
+
const lvl='$BUMP_LEVEL';
|
|
189
|
+
if(lvl==='major'){a++;b=0;c=0;} else if(lvl==='minor'){b++;c=0;} else {c++;}
|
|
190
|
+
console.log(a+'.'+b+'.'+c);
|
|
191
|
+
")"
|
|
192
|
+
else
|
|
193
|
+
VERSION="$PKG_VERSION"
|
|
194
|
+
fi
|
|
195
|
+
TAG="v$VERSION"
|
|
196
|
+
|
|
197
|
+
# Staging publishes as a GitHub pre-release + npm `next` dist-tag, so neither
|
|
198
|
+
# releases/latest nor npm `latest` moves — only staging-channel boxes see it.
|
|
199
|
+
GH_PRERELEASE_FLAG=""
|
|
200
|
+
NPM_TAG_ARGS=()
|
|
201
|
+
if [[ "$DO_STAGING" == "1" ]]; then
|
|
202
|
+
GH_PRERELEASE_FLAG="--prerelease"
|
|
203
|
+
NPM_TAG_ARGS=(--tag next)
|
|
204
|
+
say "STAGING pre-release $TAG (invisible to stable boxes; promote later with --promote $TAG)"
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# Write the resolved version into the repo-root package.json + lockfile so the
|
|
208
|
+
# pipeline tarball, npm publish, and all satellites share one number.
|
|
209
|
+
if [[ "$VERSION" != "$PKG_VERSION" ]]; then
|
|
210
|
+
say "Bumping repo-root package.json $PKG_VERSION -> $VERSION"
|
|
211
|
+
node -e "
|
|
212
|
+
const fs=require('fs');
|
|
213
|
+
for (const p of ['$REPO_ROOT/package.json','$REPO_ROOT/package-lock.json']) {
|
|
214
|
+
if (!fs.existsSync(p)) continue;
|
|
215
|
+
const j=JSON.parse(fs.readFileSync(p,'utf8'));
|
|
216
|
+
j.version='$VERSION';
|
|
217
|
+
if (j.packages && j.packages['']) j.packages[''].version='$VERSION';
|
|
218
|
+
fs.writeFileSync(p, JSON.stringify(j,null,2)+'\n');
|
|
219
|
+
console.log(' '+p.replace('$REPO_ROOT/','')+' -> '+j.version);
|
|
220
|
+
}
|
|
221
|
+
"
|
|
222
|
+
else
|
|
223
|
+
say "Releasing $TAG (repo-root package.json already $VERSION)"
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
# ---- 2. Stamp EVERY version satellite BEFORE packing, then build ------------
|
|
227
|
+
# ONE source of truth (repo-root package.json, bumped in step 1); every other
|
|
228
|
+
# file that carries a version is a SATELLITE stamped from it here. The ordering
|
|
229
|
+
# is load-bearing: the embedded pipeline.tgz is `npm pack` of the repo root
|
|
230
|
+
# (bundle-pipeline.mjs), so it captures mcp/package.json AND mcp/dist/version.json
|
|
231
|
+
# AS THEY ARE ON DISK at pack time. If a satellite is stamped AFTER the pack, the
|
|
232
|
+
# tarball ships a stale mcp/ subtree while its top-level package.json is current.
|
|
233
|
+
# The menu bar resolves its version from mcp/dist/version.json FIRST
|
|
234
|
+
# (scripts/snapshot.py::_resolve_version), so a late stamp shows the OLD version
|
|
235
|
+
# in the menu bar even though the install is current (the 1.6.181-menu-on-a-
|
|
236
|
+
# 1.6.182-box bug, 2026-07-01). So: stamp source-tree satellites (2a) -> build
|
|
237
|
+
# panel + server (2b) -> stamp dist/version.json now that tsc emitted dist/ (2c)
|
|
238
|
+
# -> pack pipeline.tgz, now capturing an all-$VERSION mcp/ subtree (2d).
|
|
239
|
+
|
|
240
|
+
# ---- 2a. Stamp manifest.json + mcp/package.json + lockfile (PRE-pack) --------
|
|
241
|
+
# manifest.json feeds Claude Desktop's extension "Details" panel; mcp/package.json
|
|
242
|
+
# + its lockfile are stamped in lockstep (npm errors if they disagree). All three
|
|
243
|
+
# are inside the repo `files` allowlist, so they land in pipeline.tgz — they MUST
|
|
244
|
+
# be current before 2d packs them.
|
|
245
|
+
say "Stamping mcp/manifest.json + mcp/package.json + mcp/package-lock.json -> $VERSION"
|
|
246
|
+
node -e "
|
|
247
|
+
const fs=require('fs');
|
|
248
|
+
const V='$VERSION';
|
|
249
|
+
for (const p of ['$MCP_DIR/manifest.json','$MCP_DIR/package.json']) {
|
|
250
|
+
const j=JSON.parse(fs.readFileSync(p,'utf8'));
|
|
251
|
+
j.version=V;
|
|
252
|
+
fs.writeFileSync(p, JSON.stringify(j,null,2)+'\n');
|
|
253
|
+
console.log(' '+p.replace('$MCP_DIR/','mcp/')+' -> '+j.version);
|
|
254
|
+
}
|
|
255
|
+
const lp='$MCP_DIR/package-lock.json';
|
|
256
|
+
if (fs.existsSync(lp)) {
|
|
257
|
+
const l=JSON.parse(fs.readFileSync(lp,'utf8'));
|
|
258
|
+
l.version=V;
|
|
259
|
+
if (l.packages && l.packages['']) l.packages[''].version=V;
|
|
260
|
+
fs.writeFileSync(lp, JSON.stringify(l,null,2)+'\n');
|
|
261
|
+
console.log(' mcp/package-lock.json -> '+l.version);
|
|
262
|
+
}
|
|
263
|
+
"
|
|
264
|
+
|
|
265
|
+
# ---- 2b. Build panel + server (emits dist/) ---------------------------------
|
|
266
|
+
# Split out of `build:bundle` so we can stamp dist/version.json (2c) AFTER tsc
|
|
267
|
+
# emits dist/ but BEFORE the pipeline pack (2d). tsc does not touch dist/version.json
|
|
268
|
+
# (it is JSON we author, not a TS output), so a 2c stamp survives to the pack.
|
|
269
|
+
say "Building MCP panel + server"
|
|
270
|
+
( cd "$MCP_DIR" && npm run build:panel && npm run build:server )
|
|
271
|
+
|
|
272
|
+
# ---- 2c. Stamp mcp/dist/version.json (after tsc, before the pipeline pack) ---
|
|
273
|
+
say "Stamping mcp/dist/version.json -> $VERSION"
|
|
274
|
+
node -e "
|
|
275
|
+
const fs=require('fs'),p='$MCP_DIR/dist/version.json';
|
|
276
|
+
fs.writeFileSync(p, JSON.stringify({version:'$VERSION',installedAt:new Date().toISOString()},null,2)+'\n');
|
|
277
|
+
console.log(' '+fs.readFileSync(p,'utf8').trim());
|
|
278
|
+
"
|
|
279
|
+
|
|
280
|
+
# ---- 2d. Pack embedded pipeline.tgz (now captures an all-$VERSION mcp/) ------
|
|
281
|
+
say "Packing embedded pipeline.tgz"
|
|
282
|
+
( cd "$MCP_DIR" && npm run bundle:pipeline )
|
|
283
|
+
|
|
284
|
+
# ---- 3c. Regenerate manifest.json `tools` from the SERVER's registrations ---
|
|
285
|
+
# Claude Desktop exposes a .mcpb extension's tools to agent chats from the
|
|
286
|
+
# manifest's `tools` array. It was hand-written and drifted: it listed 5 old
|
|
287
|
+
# tools while the server registers ~10 (queue_setup, run_draft_cycle, …), so the
|
|
288
|
+
# newer ones were INVISIBLE to the in-chat agent — which silently broke onboarding
|
|
289
|
+
# / re-arm on every .mcpb install (the agent couldn't call queue_setup). Derive
|
|
290
|
+
# the list from the source's tool()/appTool() registrations so it can never drift
|
|
291
|
+
# again. (name + title; the title is the human description Desktop shows.)
|
|
292
|
+
say "Regenerating mcp/manifest.json tools from src/index.ts registrations"
|
|
293
|
+
node -e "
|
|
294
|
+
const fs=require('fs');
|
|
295
|
+
const src=fs.readFileSync('$MCP_DIR/src/index.ts','utf8');
|
|
296
|
+
const re=/(?:^|\n)\s*(?:tool|appTool)\(\s*\n\s*\"([a-z0-9_]+)\"\s*,\s*\{\s*\n\s*title:\s*\"((?:[^\"\\\\]|\\\\.)*)\"/g;
|
|
297
|
+
const tools=[]; let m;
|
|
298
|
+
while((m=re.exec(src))!==null) tools.push({name:m[1], description:m[2]});
|
|
299
|
+
if(tools.length < 6) { console.error(' refusing: only '+tools.length+' tools parsed (regex drift?)'); process.exit(1); }
|
|
300
|
+
const p='$MCP_DIR/manifest.json';
|
|
301
|
+
const j=JSON.parse(fs.readFileSync(p,'utf8'));
|
|
302
|
+
j.tools=tools;
|
|
303
|
+
fs.writeFileSync(p, JSON.stringify(j,null,2)+'\n');
|
|
304
|
+
console.log(' manifest tools ('+tools.length+'): '+tools.map(t=>t.name).join(', '));
|
|
305
|
+
"
|
|
306
|
+
|
|
307
|
+
# ---- 4. Pack the .mcpb ------------------------------------------------------
|
|
308
|
+
say "Packing $BUNDLE"
|
|
309
|
+
rm -f "$BUNDLE"
|
|
310
|
+
mcpb pack "$MCP_DIR" "$BUNDLE"
|
|
311
|
+
|
|
312
|
+
# ---- 5. Verify --------------------------------------------------------------
|
|
313
|
+
say "Verifying bundle"
|
|
314
|
+
[[ -f "$BUNDLE" ]] || die "bundle was not produced"
|
|
315
|
+
BYTES=$(stat -f%z "$BUNDLE" 2>/dev/null || stat -c%s "$BUNDLE")
|
|
316
|
+
MB=$(( BYTES / 1024 / 1024 ))
|
|
317
|
+
echo " size: ${MB}MB (cap ${SIZE_CAP_MB}MB)"
|
|
318
|
+
(( MB <= SIZE_CAP_MB )) || die "bundle ${MB}MB exceeds ${SIZE_CAP_MB}MB cap"
|
|
319
|
+
|
|
320
|
+
# Capture the listing once (grep -q on a live pipe trips SIGPIPE under pipefail).
|
|
321
|
+
LISTING="$(unzip -l "$BUNDLE" 2>/dev/null || true)"
|
|
322
|
+
|
|
323
|
+
PIPELINE_COUNT=$(printf '%s\n' "$LISTING" | grep -c 'dist/pipeline.tgz' || true)
|
|
324
|
+
[[ "$PIPELINE_COUNT" == "1" ]] || die "expected exactly 1 embedded dist/pipeline.tgz, found $PIPELINE_COUNT"
|
|
325
|
+
echo " embedded pipeline.tgz: ok"
|
|
326
|
+
|
|
327
|
+
BUNDLE_VER=$(unzip -p "$BUNDLE" dist/version.json 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "?")
|
|
328
|
+
[[ "$BUNDLE_VER" == "$VERSION" ]] || die "bundle version.json=$BUNDLE_VER != $VERSION"
|
|
329
|
+
echo " version.json: $BUNDLE_VER ok"
|
|
330
|
+
|
|
331
|
+
MANIFEST_VER=$(unzip -p "$BUNDLE" manifest.json 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "?")
|
|
332
|
+
[[ "$MANIFEST_VER" == "$VERSION" ]] || die "bundle manifest.json=$MANIFEST_VER != $VERSION (Desktop Details panel would show the wrong version)"
|
|
333
|
+
echo " manifest.json: $MANIFEST_VER ok"
|
|
334
|
+
|
|
335
|
+
# THE guard that was missing when 1.6.84 shipped a 1.6.83 pipeline: assert the
|
|
336
|
+
# version INSIDE the embedded pipeline.tgz matches the bundle. ensurePipelineCurrent()
|
|
337
|
+
# on the box trusts version.json to decide whether to re-extract; if the tarball's
|
|
338
|
+
# own package.json lags, the box materializes stale Python and never knows.
|
|
339
|
+
PIPELINE_VER=$(unzip -p "$BUNDLE" dist/pipeline.tgz 2>/dev/null | tar -xzO package/package.json 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "?")
|
|
340
|
+
[[ "$PIPELINE_VER" == "$VERSION" ]] || die "embedded pipeline.tgz version=$PIPELINE_VER != $VERSION (box would materialize a STALE pipeline; bump repo-root package.json BEFORE build)"
|
|
341
|
+
echo " pipeline.tgz internal version: $PIPELINE_VER ok"
|
|
342
|
+
|
|
343
|
+
# The menu bar reads package/mcp/dist/version.json FIRST, then package/mcp/package.json
|
|
344
|
+
# (scripts/snapshot.py::_resolve_version), both from the SAME pipeline.tgz the box
|
|
345
|
+
# extracts into S4L_REPO_DIR. Assert the whole mcp/ subtree matches so a satellite
|
|
346
|
+
# stamped after the pack can't ship a menu that shows the wrong version on an
|
|
347
|
+
# otherwise-current box (the 1.6.181-menu-on-a-1.6.182-box bug). Empty/missing =
|
|
348
|
+
# fail: _resolve_version would fall through and could read a stale file.
|
|
349
|
+
for sub in mcp/dist/version.json mcp/package.json; do
|
|
350
|
+
SUBV=$(unzip -p "$BUNDLE" dist/pipeline.tgz 2>/dev/null | tar -xzO "package/$sub" 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "?")
|
|
351
|
+
[[ "$SUBV" == "$VERSION" ]] || die "embedded pipeline.tgz package/$sub=$SUBV != $VERSION (menu bar would show the wrong version; stamp satellites BEFORE the pipeline pack)"
|
|
352
|
+
echo " pipeline.tgz $sub: $SUBV ok"
|
|
353
|
+
done
|
|
354
|
+
|
|
355
|
+
for f in "dist/index.js" "dist/runtime.js" "manifest.json"; do
|
|
356
|
+
# grep -c reads all input (no SIGPIPE); anchor on the time column + 3-space
|
|
357
|
+
# gutter so node_modules/.../dist/index.js does not false-match the top-level.
|
|
358
|
+
n=$(printf '%s\n' "$LISTING" | grep -c "[0-9:] $f\$" || true)
|
|
359
|
+
[[ "$n" -ge 1 ]] || die "bundle missing $f"
|
|
360
|
+
done
|
|
361
|
+
echo " runtime + server + manifest: ok"
|
|
362
|
+
|
|
363
|
+
if [[ "$DO_RELEASE" == "0" ]]; then
|
|
364
|
+
say "Done (--no-release). Bundle ready at: $BUNDLE"
|
|
365
|
+
exit 0
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
# ---- 6. npm publish (Story A: `npx social-autoposter@<v> init`) -------------
|
|
369
|
+
# Same VERSION as the bundle, from the SAME bumped repo-root package.json, so the
|
|
370
|
+
# npm install path and the .mcpb path can never disagree. Idempotent: a version
|
|
371
|
+
# already on the registry is skipped, not failed.
|
|
372
|
+
if [[ "$DO_NPM" == "1" ]]; then
|
|
373
|
+
command -v npm >/dev/null || die "npm not found on PATH"
|
|
374
|
+
NPM_HTTP=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/social-autoposter/$VERSION" || echo "000")
|
|
375
|
+
if [[ "$NPM_HTTP" == "200" ]]; then
|
|
376
|
+
say "npm: social-autoposter@$VERSION already published — skipping"
|
|
377
|
+
else
|
|
378
|
+
say "Publishing social-autoposter@$VERSION to npm${NPM_TAG_ARGS:+ (tag: ${NPM_TAG_ARGS[*]})}"
|
|
379
|
+
# Guarded array expansion: an EMPTY array under `set -u` trips "unbound
|
|
380
|
+
# variable" on macOS bash 3.2, so expand to nothing when no --tag is set.
|
|
381
|
+
( cd "$REPO_ROOT" && npm publish ${NPM_TAG_ARGS[@]+"${NPM_TAG_ARGS[@]}"} ) || die "npm publish failed"
|
|
382
|
+
# Confirm it actually landed (granular-token whoami lies; a version fetch doesn't).
|
|
383
|
+
for _ in 1 2 3 4 5; do
|
|
384
|
+
sleep 2
|
|
385
|
+
[[ "$(curl -s -o /dev/null -w '%{http_code}' "https://registry.npmjs.org/social-autoposter/$VERSION")" == "200" ]] && break
|
|
386
|
+
done
|
|
387
|
+
echo " npm: social-autoposter@$VERSION live"
|
|
388
|
+
fi
|
|
389
|
+
|
|
390
|
+
# ---- 6b. Dual-publish the SAME content as "@m13v/s4l" (brand rename) -------
|
|
391
|
+
# `npx @m13v/s4l init` == `npx social-autoposter init`. Same version, same
|
|
392
|
+
# dist-tag logic (stable -> default `latest`; --staging -> `next`).
|
|
393
|
+
# npm REJECTS the bare name "s4l" (403: too similar to st/swr/sax/...), so the
|
|
394
|
+
# alias lives under the m13v scope, published --access=public.
|
|
395
|
+
# The repo package.json is NEVER mutated: we `npm pack` the repo root (the
|
|
396
|
+
# exact content step 6 shipped), extract into a temp dir, rewrite ONLY the
|
|
397
|
+
# temp copy's name field, and publish that copy. Idempotent like step 6.
|
|
398
|
+
# BEST-EFFORT BY DESIGN: every failure in 6b warns and falls through — it must
|
|
399
|
+
# NEVER die. On 2026-07-03 a die here aborted the run between the npm publish
|
|
400
|
+
# (step 6, done) and the GitHub release (step 7, never ran), leaving npm `next`
|
|
401
|
+
# on rc.6 with no matching GH release: exactly the diverged-lanes state this
|
|
402
|
+
# script exists to prevent. The alias package is a convenience; the
|
|
403
|
+
# social-autoposter npm lane + GH release lockstep is the contract.
|
|
404
|
+
S4L_ALIAS_PKG="@m13v/s4l"
|
|
405
|
+
S4L_ALIAS_URL="https://registry.npmjs.org/@m13v%2fs4l/$VERSION"
|
|
406
|
+
S4L_NPM_HTTP=$(curl -s -o /dev/null -w "%{http_code}" "$S4L_ALIAS_URL" || echo "000")
|
|
407
|
+
if [[ "$S4L_NPM_HTTP" == "200" ]]; then
|
|
408
|
+
say "npm: $S4L_ALIAS_PKG@$VERSION already published — skipping dual-publish"
|
|
409
|
+
else
|
|
410
|
+
say "Dual-publishing $S4L_ALIAS_PKG@$VERSION to npm${NPM_TAG_ARGS:+ (tag: ${NPM_TAG_ARGS[*]})}"
|
|
411
|
+
S4L_PUB_DIR="$(mktemp -d "${TMPDIR:-/tmp}/s4l-dual-publish.XXXXXX")"
|
|
412
|
+
if ( cd "$REPO_ROOT" && npm pack --pack-destination "$S4L_PUB_DIR" >/dev/null ) \
|
|
413
|
+
&& S4L_TGZ="$(ls "$S4L_PUB_DIR"/social-autoposter-*.tgz 2>/dev/null | head -1)" \
|
|
414
|
+
&& [[ -n "$S4L_TGZ" ]] \
|
|
415
|
+
&& tar -xzf "$S4L_TGZ" -C "$S4L_PUB_DIR" \
|
|
416
|
+
&& node -e "
|
|
417
|
+
const fs=require('fs');
|
|
418
|
+
const p='$S4L_PUB_DIR/package/package.json';
|
|
419
|
+
const j=JSON.parse(fs.readFileSync(p,'utf8'));
|
|
420
|
+
if (j.version!=='$VERSION') { console.error(' temp copy version '+j.version+' != $VERSION'); process.exit(1); }
|
|
421
|
+
j.name='$S4L_ALIAS_PKG';
|
|
422
|
+
fs.writeFileSync(p, JSON.stringify(j,null,2)+'\n');
|
|
423
|
+
console.log(' temp copy renamed to $S4L_ALIAS_PKG@'+j.version+' (repo package.json untouched)');
|
|
424
|
+
" \
|
|
425
|
+
&& ( cd "$S4L_PUB_DIR/package" && npm publish --access=public ${NPM_TAG_ARGS[@]+"${NPM_TAG_ARGS[@]}"} ); then
|
|
426
|
+
for _ in 1 2 3 4 5; do
|
|
427
|
+
sleep 2
|
|
428
|
+
[[ "$(curl -s -o /dev/null -w '%{http_code}' "$S4L_ALIAS_URL")" == "200" ]] && break
|
|
429
|
+
done
|
|
430
|
+
echo " npm: $S4L_ALIAS_PKG@$VERSION live"
|
|
431
|
+
else
|
|
432
|
+
echo " WARNING: $S4L_ALIAS_PKG dual-publish failed (alias lane only; social-autoposter + GH release proceed)" >&2
|
|
433
|
+
fi
|
|
434
|
+
rm -rf "$S4L_PUB_DIR"
|
|
435
|
+
fi
|
|
436
|
+
else
|
|
437
|
+
say "npm publish skipped (--no-npm)"
|
|
438
|
+
fi
|
|
439
|
+
|
|
440
|
+
# ---- 7. GitHub release ------------------------------------------------------
|
|
441
|
+
command -v gh >/dev/null || die "gh CLI not found"
|
|
442
|
+
gh auth status >/dev/null 2>&1 || die "gh not authenticated (run: gh auth login)"
|
|
443
|
+
|
|
444
|
+
NOTES="social-autoposter ${TAG}
|
|
445
|
+
|
|
446
|
+
Double-click install for Claude Desktop. Drag \`social-autoposter.mcpb\` into Settings > Extensions, enable it, then open the panel in a Chat tab and click Install runtime (provisions uv + Python 3.12 + Chromium on first run). The pipeline source is bundled, so no separate clone or config is needed.
|
|
447
|
+
|
|
448
|
+
Power-user / CLI install: \`npx social-autoposter@${VERSION} init\`."
|
|
449
|
+
|
|
450
|
+
if gh release view "$TAG" -R "$GH_REPO" >/dev/null 2>&1; then
|
|
451
|
+
say "Release $TAG exists -> uploading asset (clobber)"
|
|
452
|
+
gh release upload "$TAG" "$BUNDLE" -R "$GH_REPO" --clobber
|
|
453
|
+
else
|
|
454
|
+
say "Creating release $TAG${GH_PRERELEASE_FLAG:+ (pre-release)}"
|
|
455
|
+
gh release create "$TAG" "$BUNDLE" \
|
|
456
|
+
-R "$GH_REPO" \
|
|
457
|
+
--title "social-autoposter $TAG" \
|
|
458
|
+
--notes "$NOTES" \
|
|
459
|
+
$DRAFT_FLAG $GH_PRERELEASE_FLAG
|
|
460
|
+
fi
|
|
461
|
+
|
|
462
|
+
URL=$(gh release view "$TAG" -R "$GH_REPO" --json url -q .url 2>/dev/null || echo "")
|
|
463
|
+
say "Released $TAG"
|
|
464
|
+
echo " asset: social-autoposter.mcpb (${MB}MB)"
|
|
465
|
+
[[ -n "$URL" ]] && echo " $URL"
|
|
466
|
+
|
|
467
|
+
# ---- 8. Verify the update banner will fire ---------------------------------
|
|
468
|
+
# The menu-bar "⬆ Update available" banner (mcp/src/version.ts::versionStatus)
|
|
469
|
+
# resolves "latest" from GitHub releases/latest, which is what .mcpb boxes (no
|
|
470
|
+
# npm) can actually read. A draft release is deliberately excluded by GitHub's
|
|
471
|
+
# releases/latest, so it also won't (and shouldn't) trigger the banner — skip
|
|
472
|
+
# the check then. For a normal release, poll releases/latest until it serves the
|
|
473
|
+
# new tag so we don't declare success while every box stays silent on the old
|
|
474
|
+
# version (the 1.6.177-vs-1.6.181 blind-banner bug this guards against).
|
|
475
|
+
if [[ -n "$DRAFT_FLAG" ]]; then
|
|
476
|
+
say "Draft release — skipping banner verification (releases/latest excludes drafts by design)"
|
|
477
|
+
elif [[ "$DO_STAGING" == "1" ]]; then
|
|
478
|
+
say "Staging pre-release — releases/latest deliberately EXCLUDES it, so stable boxes stay put."
|
|
479
|
+
echo " Only boxes on the staging channel pull $TAG (via the releases LIST endpoint)."
|
|
480
|
+
echo " To ship it to everyone once tested: bash scripts/release-mcpb.sh --promote $TAG"
|
|
481
|
+
else
|
|
482
|
+
say "Verifying releases/latest serves $TAG (drives the menu-bar update banner)"
|
|
483
|
+
LATEST_SEEN=""
|
|
484
|
+
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
485
|
+
LATEST_SEEN="$(curl -fsSL -m 15 "https://api.github.com/repos/$GH_REPO/releases/latest" \
|
|
486
|
+
| node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).tag_name || ''" 2>/dev/null || echo "")"
|
|
487
|
+
[[ "$LATEST_SEEN" == "$TAG" ]] && break
|
|
488
|
+
sleep 6
|
|
489
|
+
done
|
|
490
|
+
if [[ "$LATEST_SEEN" == "$TAG" ]]; then
|
|
491
|
+
echo " releases/latest -> $LATEST_SEEN; boxes detect within version.ts's ~1-min TTL (55s, ETag-conditional; boxes older than 1.6.188 poll every 10 min)"
|
|
492
|
+
else
|
|
493
|
+
echo " WARNING: releases/latest still reports '${LATEST_SEEN:-<none>}', not $TAG." >&2
|
|
494
|
+
echo " The menu-bar update banner will NOT fire until this resolves. If it stays" >&2
|
|
495
|
+
echo " wrong, the release is likely a draft/prerelease or GitHub hasn't propagated." >&2
|
|
496
|
+
fi
|
|
497
|
+
fi
|