@m13v/s4l 1.7.2-rc.2 → 1.7.2-rc.4

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.
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.7.2-rc.2",
3
- "installedAt": "2026-07-09T01:12:07.771Z"
2
+ "version": "1.7.2-rc.4",
3
+ "installedAt": "2026-07-09T01:35:07.203Z"
4
4
  }
package/mcp/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "dxt_version": "0.1",
3
3
  "name": "social-autoposter",
4
4
  "display_name": "S4L",
5
- "version": "1.7.2-rc.2",
5
+ "version": "1.7.2-rc.4",
6
6
  "description": "Draft, review, approve, and autopilot X/Twitter posts.",
7
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
8
  "author": {
@@ -785,19 +785,12 @@ class S4LMenuBar(rumps.App):
785
785
  relaunch. Primary action for this case now instead of re-arm: no
786
786
  clipboard paste, no chat turn required. Re-arm remains the fallback
787
787
  for the rarer case where no session directory exists yet for the
788
- active account (see _build_menu) — fix 5 never fabricates one."""
789
- _activate_front()
790
- choice = _show_alert(
791
- title="Finish setting up drafts?",
792
- message=(
793
- "Claude needs to restart once to finish registering your draft "
794
- "schedule for this account. Its window will close and reopen "
795
- "in a moment — drafting starts within a few minutes after."
796
- ),
797
- ok="Restart & Finish Setup", cancel="Cancel",
798
- )
799
- if choice != 1:
800
- return
788
+ active account (see _build_menu) — fix 5 never fabricates one.
789
+
790
+ No confirm dialog: the restart is already disclosed in the menu item
791
+ label itself ("...(restarts Claude)", _build_menu) — a modal repeating
792
+ that would just be a second click for information the user already
793
+ has (2026-07-09). The notify toast below still tells them it started."""
801
794
  _capture_msg(
802
795
  "S4L finish-schedule-setup clicked",
803
796
  phase="draft_schedule",
@@ -1147,7 +1140,7 @@ class S4LMenuBar(rumps.App):
1147
1140
  "Uninstall: keep your X login + browser layer (quick uninstall).\n"
1148
1141
  "Deep wipe: also remove the shared browser profiles + toolchain."
1149
1142
  ),
1150
- ok="Uninstall & Restart Claude", cancel="Cancel", other="Deep wipe",
1143
+ ok="Uninstall & Restart Claude", cancel="Cancel", other="Deep wipe & Restart Claude",
1151
1144
  )
1152
1145
  if choice == 0: # cancel
1153
1146
  return
@@ -3008,7 +3001,6 @@ class S4LMenuBar(rumps.App):
3008
3001
  items.append(rumps.MenuItem("Resume S4L", callback=self._pause_toggle))
3009
3002
  else:
3010
3003
  items.append(rumps.MenuItem("Pause S4L", callback=self._pause_toggle))
3011
- items.append(self._label(" stop drafting/posting, keep Claude + tray running"))
3012
3004
  items.append(rumps.separator)
3013
3005
 
3014
3006
  # Attention = the draft schedule isn't running for THIS account (missing or
@@ -3112,19 +3104,15 @@ class S4LMenuBar(rumps.App):
3112
3104
  personal_on = bool(flags.get("personal_brand"))
3113
3105
  promo_on = bool(flags.get("promotion"))
3114
3106
  items.append(rumps.separator)
3115
- items.append(self._label("Engagement lanes"))
3116
3107
  pb_item = rumps.MenuItem("Personal brand", callback=self._toggle_personal)
3117
3108
  pb_item.state = 1 if personal_on else 0
3118
3109
  items.append(pb_item)
3119
- items.append(self._label(" organic, link-free engagement"))
3120
3110
  pr_item = rumps.MenuItem("Product promotion", callback=self._toggle_promotion)
3121
3111
  pr_item.state = 1 if promo_on else 0
3122
3112
  items.append(pr_item)
3123
- items.append(self._label(" promoting your products (link replies)"))
3124
3113
  if personal_on and promo_on:
3125
3114
  # Both lanes on: the split becomes meaningful, so offer the presets.
3126
3115
  share = st.read_split()
3127
- items.append(self._label(f" both on · cycles split {self._split_pct(share)}"))
3128
3116
  split_menu = rumps.MenuItem(f"Lane split: {self._split_pct(share)}")
3129
3117
  for preset in self.SPLIT_PRESETS:
3130
3118
  it = rumps.MenuItem(
@@ -3141,7 +3129,6 @@ class S4LMenuBar(rumps.App):
3141
3129
  # carries a Feedback button); named for what it does to the pipeline,
3142
3130
  # not the mechanism.
3143
3131
  items.append(rumps.MenuItem("Give overall feedback to AI…", callback=self._menu_feedback))
3144
- items.append(self._label(" overall guidance, steers future drafts"))
3145
3132
  # While the update-verify marker is pending, the pipeline copy still
3146
3133
  # resolves the OLD version (it only advances once the restarted server
3147
3134
  # re-provisions repo/package, ~2 min), so the snapshot honestly reports
@@ -3168,8 +3155,10 @@ class S4LMenuBar(rumps.App):
3168
3155
  items.append(rumps.separator)
3169
3156
  items.append(rumps.MenuItem("Tidy autopilot history…", callback=self._prompt_relocate_tasks))
3170
3157
  items.append(rumps.separator)
3171
- items.append(rumps.MenuItem("Uninstall S4L…", callback=self._reset_machine))
3172
- items.append(rumps.MenuItem("Quit", callback=self._quit_app))
3158
+ quit_menu = rumps.MenuItem("Quit or Uninstall")
3159
+ quit_menu.add(rumps.MenuItem("Uninstall S4L…", callback=self._reset_machine))
3160
+ quit_menu.add(rumps.MenuItem("Quit", callback=self._quit_app))
3161
+ items.append(quit_menu)
3173
3162
 
3174
3163
  # Collapse consecutive/edge separators so an empty section (e.g. State C
3175
3164
  # now renders no status labels) can't leave a doubled or dangling divider.
package/mcp/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m13v/s4l-mcp",
3
- "version": "1.7.2-rc.2",
3
+ "version": "1.7.2-rc.4",
4
4
  "private": true,
5
5
  "description": "Desktop MCP client for social-autoposter (X/Twitter rail): manual draft/review/approve loop, autopilot control, and stats. Thin wrapper over the existing pipeline scripts.",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m13v/s4l",
3
- "version": "1.7.2-rc.2",
3
+ "version": "1.7.2-rc.4",
4
4
  "description": "Automated social posting pipeline for Reddit, X/Twitter, LinkedIn, and Moltbook. Install as a Claude Code agent skill.",
5
5
  "bin": {
6
6
  "social-autoposter": "bin/cli.js",
@@ -60,14 +60,26 @@ except Exception: # standalone fallbacks
60
60
 
61
61
 
62
62
  def _is_prep_result(obj):
63
- """True iff obj looks like a twitter-prep result (candidates with reply_text)."""
63
+ """True iff obj looks like a twitter-prep result (drafted candidates).
64
+
65
+ "reply_text" was the single-draft field before the 2026-07-07/08 two-draft
66
+ redesign (draft_a_text/draft_b_text per candidate, no single recommended
67
+ reply). Checking only "reply_text" made every post-redesign orphaned
68
+ result silently misclassified as non-prep and marked .skipped instead of
69
+ recovered — the exact "worker drafted but no card" bug this script exists
70
+ to prevent. Accept either field so both old and current schema results
71
+ are recognized.
72
+ """
64
73
  if not isinstance(obj, dict):
65
74
  return False
66
75
  cands = obj.get("candidates")
67
76
  if not isinstance(cands, list) or not cands:
68
77
  return False
69
78
  c0 = cands[0]
70
- return isinstance(c0, dict) and "reply_text" in c0 and ("candidate_url" in c0 or "candidate_id" in c0)
79
+ if not isinstance(c0, dict):
80
+ return False
81
+ has_text = "reply_text" in c0 or "draft_a_text" in c0
82
+ return has_text and ("candidate_url" in c0 or "candidate_id" in c0)
71
83
 
72
84
 
73
85
  def main():