@m13v/s4l 1.7.4-rc.29 → 1.7.4-rc.30

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.4-rc.29",
3
- "installedAt": "2026-07-13T22:06:08.099Z"
2
+ "version": "1.7.4-rc.30",
3
+ "installedAt": "2026-07-13T22:13:17.873Z"
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.4-rc.29",
5
+ "version": "1.7.4-rc.30",
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": {
@@ -555,6 +555,9 @@ class S4LMenuBar(rumps.App):
555
555
  # (0 = not snoozed). Loaded from disk so a menubar restart mid-snooze
556
556
  # doesn't re-pop the card the user just put away.
557
557
  self._review_snooze_until = self._read_snooze_until()
558
+ # Reveal cadence: when the last fresh card presentation happened (epoch,
559
+ # 0 = never). Loaded from disk so a restart mid-interval doesn't re-pop.
560
+ self._last_presented_at = self._read_last_presented()
558
561
  # Unattended-review watchdog state (_maybe_heal_review).
559
562
  self._review_heal_at = 0.0
560
563
  self._review_unattended_notified = False
@@ -2667,6 +2670,11 @@ class S4LMenuBar(rumps.App):
2667
2670
  int(self._review_snooze_until)
2668
2671
  if time.time() < self._review_snooze_until
2669
2672
  else 0,
2673
+ # Reveal cadence: rebuilds when the preset changes (checkmark +
2674
+ # submenu title) and when a hold starts or lapses (the "Next cards
2675
+ # around HH:MM" line in _state_c).
2676
+ round(st.read_reveal_cadence()),
2677
+ int(self._reveal_hold_until(pending_count)),
2670
2678
  )
2671
2679
  if sig != self._sig:
2672
2680
  self._sig = sig
@@ -2765,6 +2773,37 @@ class S4LMenuBar(rumps.App):
2765
2773
  except Exception:
2766
2774
  pass
2767
2775
 
2776
+ def _read_last_presented(self):
2777
+ try:
2778
+ with open(REVEAL_STAMP_FILE) as f:
2779
+ return float((json.load(f) or {}).get("at") or 0.0)
2780
+ except Exception:
2781
+ return 0.0
2782
+
2783
+ def _stamp_presented(self):
2784
+ """Record a fresh card presentation, mirrored to disk (restart-safe).
2785
+ Starts the next reveal-cadence interval."""
2786
+ self._last_presented_at = time.time()
2787
+ try:
2788
+ with open(REVEAL_STAMP_FILE, "w") as f:
2789
+ json.dump({"at": self._last_presented_at}, f)
2790
+ except Exception:
2791
+ pass
2792
+
2793
+ def _reveal_hold_until(self, pending_count):
2794
+ """Epoch until which the reveal cadence is actually holding fresh cards
2795
+ back (0.0 = no hold). A hold only exists while there is something to
2796
+ hold: pending drafts, no review in flight, and not snoozed (the snooze
2797
+ label and gate take precedence)."""
2798
+ if pending_count <= 0 or self._review_active:
2799
+ return 0.0
2800
+ now = time.time()
2801
+ if now < self._review_snooze_until:
2802
+ return 0.0
2803
+ cadence = st.read_reveal_cadence()
2804
+ until = self._last_presented_at + cadence
2805
+ return until if (cadence > 0 and now < until) else 0.0
2806
+
2768
2807
  def _review_now(self, _=None):
2769
2808
  """Menu: bring the pending draft cards up right now. Clears any snooze;
2770
2809
  this is the one presentation path that may take keyboard focus (the
@@ -2846,6 +2885,17 @@ class S4LMenuBar(rumps.App):
2846
2885
  sys.stderr.flush()
2847
2886
  self._last_review_sig = sig
2848
2887
  return
2888
+ # Reveal cadence: hold FRESH pop-ups until the configured interval since
2889
+ # the last presentation has passed (0 = show immediately). Drafting is
2890
+ # untouched; the backlog keeps accumulating and is presented whole once
2891
+ # due. Deliberately after the extend-open-card path above (an on-screen
2892
+ # card keeps growing live) and skipped for focus=True (the menu's
2893
+ # "Review N pending drafts" is an explicit ask). _last_review_sig is NOT
2894
+ # stamped on a hold, so the same set still presents fresh when due.
2895
+ if not focus:
2896
+ cadence = st.read_reveal_cadence()
2897
+ if cadence > 0 and time.time() - self._last_presented_at < cadence:
2898
+ return
2849
2899
  with self._review_lock:
2850
2900
  self._reset_posting_progress_locked()
2851
2901
  self._review_active = True
@@ -2869,6 +2919,7 @@ class S4LMenuBar(rumps.App):
2869
2919
  # Record as shown only AFTER the cards are actually up, so a transient
2870
2920
  # card-UI failure never permanently suppresses this pending set.
2871
2921
  self._last_review_sig = sig
2922
+ self._stamp_presented()
2872
2923
  # No macOS notification for fresh drafts, per explicit user
2873
2924
  # request (2026-07-03): the card itself is the surface. A missed
2874
2925
  # card is the unattended watchdog's job (it heals the window and
@@ -3457,6 +3508,22 @@ class S4LMenuBar(rumps.App):
3457
3508
  split_menu.add(it)
3458
3509
  items.append(split_menu)
3459
3510
 
3511
+ # Reveal cadence: how often fresh draft cards may pop (the drafting
3512
+ # pipeline is unchanged; this only paces the pop-up). Always offered:
3513
+ # it is the "don't interrupt me every few minutes" control.
3514
+ cadence = st.read_reveal_cadence()
3515
+ cadence_menu = rumps.MenuItem(
3516
+ f"Show draft cards: {self._cadence_label(cadence).lower()}"
3517
+ )
3518
+ for preset, label in self.REVEAL_CADENCE_PRESETS:
3519
+ it = rumps.MenuItem(
3520
+ label,
3521
+ callback=functools.partial(self._on_cadence_preset, preset),
3522
+ )
3523
+ it.state = 1 if round(cadence) == preset else 0
3524
+ cadence_menu.add(it)
3525
+ items.append(cadence_menu)
3526
+
3460
3527
  items.append(rumps.separator)
3461
3528
  items.append(rumps.MenuItem("Open dashboard", callback=self._open_dashboard))
3462
3529
  # The one entry point for overall feedback (the review card no longer
@@ -3595,6 +3662,18 @@ class S4LMenuBar(rumps.App):
3595
3662
  )
3596
3663
  )
3597
3664
  )
3665
+ else:
3666
+ # Reveal-cadence hold: the backlog exists but the pop-up is being
3667
+ # paced. Same courtesy line as the snooze; "Review N pending
3668
+ # drafts" above shows them right now regardless.
3669
+ hold_until = self._reveal_hold_until(pending_count)
3670
+ if hold_until:
3671
+ items.append(
3672
+ self._label(
3673
+ "Next cards around "
3674
+ + time.strftime("%H:%M", time.localtime(hold_until))
3675
+ )
3676
+ )
3598
3677
  # No bulk-discard item here anymore: "Discard all…" lives in the
3599
3678
  # review card's title bar (2026-07-10). Reaching it with no card open
3600
3679
  # is "Review N pending drafts" -> Discard all, two clicks.
package/mcp/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m13v/s4l-mcp",
3
- "version": "1.7.4-rc.29",
3
+ "version": "1.7.4-rc.30",
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.4-rc.29",
3
+ "version": "1.7.4-rc.30",
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",