@m13v/s4l 1.6.197-rc.13 → 1.6.197-rc.14

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/mcp/dist/index.js CHANGED
@@ -4004,7 +4004,7 @@ tool("show_browser_to_user", {
4004
4004
  const message = ensured.error === "no_browser"
4005
4005
  ? "No managed Chrome is running right now. Start a draft cycle or autopilot so there's a live browser session to show."
4006
4006
  : ensured.error === "no_websocket"
4007
- ? "This Node runtime has no WebSocket support (needs Node 21+), so a screencast can't be opened."
4007
+ ? "This runtime has no WebSocket support, so a live screencast can't be opened. Use 'Bring to front' to see the browser window instead."
4008
4008
  : "Couldn't attach to the browser: " + String(ensured.error);
4009
4009
  return jsonContent({ ok: false, running: false, frame: null, message });
4010
4010
  }
@@ -17,10 +17,24 @@
17
17
  * this module is the robust baseline that works regardless.
18
18
  */
19
19
  import { execFile } from "node:child_process";
20
+ import { createRequire } from "node:module";
20
21
  // Untyped indirection: Node ships a global WebSocket at runtime (>=21) but
21
22
  // @types/node doesn't always declare it as a value, and MessageEvent isn't typed
22
23
  // without the DOM lib. Reach for it dynamically and keep the event handlers `any`.
23
- const WS = globalThis.WebSocket;
24
+ //
25
+ // Claude Desktop launches this server with whatever `node` is on the user's
26
+ // PATH, which can be 18/20 with no global WebSocket. Fall back to the bundled
27
+ // `ws` package, whose browser-style API (onopen/onmessage/send/close/readyState)
28
+ // matches how this module drives the socket.
29
+ const WS = globalThis.WebSocket ??
30
+ (() => {
31
+ try {
32
+ return createRequire(import.meta.url)("ws").WebSocket;
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ })();
24
38
  // Ports we manage a Chrome on, most-likely-active first. TWITTER_CDP_URL (the
25
39
  // twitter harness) wins if set; the rest cover linkedin (9556) / reddit (9557) /
26
40
  // browser-harness / assrt.
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.6.197-rc.13",
3
- "installedAt": "2026-07-03T20:22:38.198Z"
2
+ "version": "1.6.197-rc.14",
3
+ "installedAt": "2026-07-03T20:38:34.506Z"
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.6.197-rc.13",
5
+ "version": "1.6.197-rc.14",
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": {
@@ -22,7 +22,7 @@ entries).
22
22
  Overall feedback (guidance not tied to any single thread) has two entry
23
23
  points that share one submit handler, registered by the menu bar via
24
24
  `set_feedback_handler` (it ships decision='feedback' review events): the
25
- card's bubble button swaps the card body for an in-window composer (a
25
+ card's Feedback button swaps the card body for an in-window composer (a
26
26
  separate floating panel was tried and opened where the user never saw it on
27
27
  a multi-monitor setup), and the menu bar's "Send feedback…" item opens the
28
28
  standalone `present_feedback(on_submit)` panel (no card window need exist).
@@ -49,6 +49,7 @@ import objc
49
49
  from Foundation import (
50
50
  NSObject,
51
51
  NSMakeRect,
52
+ NSMakeSize,
52
53
  NSAttributedString,
53
54
  NSMutableAttributedString,
54
55
  NSURL,
@@ -92,6 +93,7 @@ from AppKit import (
92
93
  NSEventModifierFlagCommand,
93
94
  NSEventModifierFlagShift,
94
95
  NSEventModifierFlagDeviceIndependentFlagsMask,
96
+ NSViewWidthSizable,
95
97
  )
96
98
 
97
99
  # Strong reference to the live controller so pyobjc doesn't GC it mid-review
@@ -306,6 +308,35 @@ def _label(frame, text, *, size=12, bold=False, muted=False):
306
308
  return f
307
309
 
308
310
 
311
+ def _editable_scroll(frame, text=""):
312
+ """Bezel-bordered scrollable text editor. The document view must be sized
313
+ to the scroll view's contentSize (NOT the outer frame) and track its width;
314
+ sized to the outer frame the text runs underneath the scroller. The
315
+ scroller itself auto-hides so it only appears when the text overflows.
316
+ Returns (scroll, textview)."""
317
+ scroll = NSScrollView.alloc().initWithFrame_(frame)
318
+ scroll.setHasVerticalScroller_(True)
319
+ scroll.setAutohidesScrollers_(True)
320
+ scroll.setBorderType_(NS_BEZEL_BORDER)
321
+ cs = scroll.contentSize()
322
+ tv = NSTextView.alloc().initWithFrame_(NSMakeRect(0, 0, cs.width, cs.height))
323
+ tv.setFont_(NSFont.systemFontOfSize_(12))
324
+ tv.setRichText_(False)
325
+ tv.setEditable_(True)
326
+ tv.setSelectable_(True)
327
+ tv.setVerticallyResizable_(True)
328
+ tv.setHorizontallyResizable_(False)
329
+ tv.setMinSize_(NSMakeSize(0, cs.height))
330
+ tv.setMaxSize_(NSMakeSize(1e7, 1e7))
331
+ tv.setAutoresizingMask_(NSViewWidthSizable)
332
+ tv.textContainer().setWidthTracksTextView_(True)
333
+ tv.textContainer().setContainerSize_(NSMakeSize(cs.width, 1e7))
334
+ if text:
335
+ tv.setString_(text)
336
+ scroll.setDocumentView_(tv)
337
+ return scroll, tv
338
+
339
+
309
340
  class _ReviewController(NSObject):
310
341
  def initWithDrafts_onDecision_onComplete_(self, drafts, on_decision, on_complete):
311
342
  self = objc.super(_ReviewController, self).init()
@@ -504,9 +535,10 @@ class _ReviewController(NSObject):
504
535
  content = NSView.alloc().initWithFrame_(NSMakeRect(0, 0, W, H))
505
536
 
506
537
  # Buttons at the TOP, one line: Approve, Approve 😄 (approve + loved),
507
- # a small feedback-bubble icon (overall feedback, decides nothing),
508
- # Reject at the right.
509
- approve = NSButton.alloc().initWithFrame_(NSMakeRect(M, H - 42, 84, 30))
538
+ # Feedback (overall feedback, decides nothing), Reject at the right.
539
+ # Widths are hand-fit so all four bezeled buttons share the 348pt row
540
+ # without truncating their titles.
541
+ approve = NSButton.alloc().initWithFrame_(NSMakeRect(M, H - 42, 78, 30))
510
542
  approve.setTitle_("Approve")
511
543
  approve.setBezelStyle_(NSBezelStyleRounded)
512
544
  approve.setTarget_(self)
@@ -518,7 +550,7 @@ class _ReviewController(NSObject):
518
550
  # rail sees the difference. Labeled as an approve variant, not a bare
519
551
  # emoji: a lone 😄 read as decoration and users doubted the click
520
552
  # registered (2026-07-03 feedback).
521
- smile = NSButton.alloc().initWithFrame_(NSMakeRect(M + 88, H - 42, 116, 30))
553
+ smile = NSButton.alloc().initWithFrame_(NSMakeRect(M + 82, H - 42, 100, 30))
522
554
  smile.setTitle_("Approve 😄")
523
555
  smile.setBezelStyle_(NSBezelStyleRounded)
524
556
  smile.setTarget_(self)
@@ -529,22 +561,16 @@ class _ReviewController(NSObject):
529
561
  pass
530
562
  content.addSubview_(smile)
531
563
 
532
- # Feedback bubble = overall feedback (about the pipeline, not this
533
- # draft). Swaps the card body for the composer IN THIS WINDOW; a
534
- # separate floating panel was tried first and opened somewhere the
535
- # user never saw on a multi-monitor setup (2026-07-03: 7 clicks,
536
- # zero sightings). SF Symbol, like the stats eye; emoji 💬 rendered
537
- # as a grey three-dots glyph nobody could identify.
538
- fb = NSButton.alloc().initWithFrame_(NSMakeRect(M + 212, H - 40, 28, 26))
539
- fb.setBordered_(False)
540
- fb_img = NSImage.imageWithSystemSymbolName_accessibilityDescription_(
541
- "bubble.left", "overall feedback"
542
- )
543
- if fb_img is not None:
544
- fb.setImage_(fb_img)
545
- fb.setTitle_("")
546
- else: # pre-Big Sur fallback: no SF Symbols
547
- fb.setTitle_("💬")
564
+ # Feedback = overall feedback (about the pipeline, not this draft).
565
+ # Swaps the card body for the composer IN THIS WINDOW; a separate
566
+ # floating panel was tried first and opened somewhere the user never
567
+ # saw on a multi-monitor setup (2026-07-03: 7 clicks, zero sightings).
568
+ # A borderless bubble.left icon was tried next and read as decoration,
569
+ # not a button; a labeled bezel button matching its row mates won
570
+ # (2026-07-03 feedback).
571
+ fb = NSButton.alloc().initWithFrame_(NSMakeRect(M + 186, H - 42, 90, 30))
572
+ fb.setTitle_("Feedback")
573
+ fb.setBezelStyle_(NSBezelStyleRounded)
548
574
  fb.setTarget_(self)
549
575
  fb.setAction_("feedbackOpen:")
550
576
  try:
@@ -553,7 +579,7 @@ class _ReviewController(NSObject):
553
579
  pass
554
580
  content.addSubview_(fb)
555
581
 
556
- reject = NSButton.alloc().initWithFrame_(NSMakeRect(W - M - 84, H - 42, 84, 30))
582
+ reject = NSButton.alloc().initWithFrame_(NSMakeRect(W - M - 66, H - 42, 66, 30))
557
583
  reject.setTitle_("Reject")
558
584
  reject.setBezelStyle_(NSBezelStyleRounded)
559
585
  reject.setTarget_(self)
@@ -703,18 +729,9 @@ class _ReviewController(NSObject):
703
729
  reply = d.get("reply_text") or ""
704
730
  link = d.get("link_url")
705
731
  composed = f"{reply} {link}" if link else reply
706
- scroll = NSScrollView.alloc().initWithFrame_(
707
- NSMakeRect(M, M, W - 2 * M, H - 172 - M - 6)
732
+ scroll, tv = _editable_scroll(
733
+ NSMakeRect(M, M, W - 2 * M, H - 172 - M - 6), composed
708
734
  )
709
- scroll.setHasVerticalScroller_(True)
710
- scroll.setBorderType_(NS_BEZEL_BORDER)
711
- tv = NSTextView.alloc().initWithFrame_(NSMakeRect(0, 0, W - 2 * M, 100))
712
- tv.setFont_(NSFont.systemFontOfSize_(12))
713
- tv.setRichText_(False)
714
- tv.setEditable_(True)
715
- tv.setSelectable_(True)
716
- tv.setString_(composed)
717
- scroll.setDocumentView_(tv)
718
735
  content.addSubview_(scroll)
719
736
  self._textview = tv
720
737
 
@@ -988,17 +1005,7 @@ class _ReviewController(NSObject):
988
1005
  muted=True,
989
1006
  )
990
1007
  )
991
- scroll = NSScrollView.alloc().initWithFrame_(
992
- NSMakeRect(M, 54, W - 2 * M, H - 86 - 54)
993
- )
994
- scroll.setHasVerticalScroller_(True)
995
- scroll.setBorderType_(NS_BEZEL_BORDER)
996
- tv = NSTextView.alloc().initWithFrame_(NSMakeRect(0, 0, W - 2 * M, 100))
997
- tv.setFont_(NSFont.systemFontOfSize_(12))
998
- tv.setRichText_(False)
999
- tv.setEditable_(True)
1000
- tv.setSelectable_(True)
1001
- scroll.setDocumentView_(tv)
1008
+ scroll, tv = _editable_scroll(NSMakeRect(M, 54, W - 2 * M, H - 86 - 54))
1002
1009
  content.addSubview_(scroll)
1003
1010
  self._feedback_tv = tv
1004
1011
 
@@ -1256,7 +1263,7 @@ def heal_active():
1256
1263
  # ---- overall-feedback composer ----------------------------------------------
1257
1264
  # One small floating panel with a free-text field, for guidance that is about
1258
1265
  # the PIPELINE rather than any single draft ("less shilling", "more dev
1259
- # threads", ...). Reachable from the card's 💬 button and the menu bar's
1266
+ # threads", ...). Reachable from the card's Feedback button and the menu bar's
1260
1267
  # "Send feedback…" item; both call present_feedback(), which falls back to the
1261
1268
  # handler the menu bar registered at boot via set_feedback_handler() (that
1262
1269
  # handler ships a decision='feedback' review event down the same outbox rail
@@ -1265,7 +1272,7 @@ def heal_active():
1265
1272
  FB_W = 380
1266
1273
  FB_H = 200
1267
1274
 
1268
- # Default submit handler (menu bar's shipper). Module-level so the card's 💬
1275
+ # Default submit handler (menu bar's shipper). Module-level so the card's
1269
1276
  # button can open the composer without threading a callback through
1270
1277
  # present_review's signature.
1271
1278
  _feedback_handler = None
@@ -1341,19 +1348,9 @@ class _FeedbackController(NSObject):
1341
1348
  muted=True,
1342
1349
  )
1343
1350
  )
1344
- scroll = NSScrollView.alloc().initWithFrame_(
1351
+ scroll, tv = _editable_scroll(
1345
1352
  NSMakeRect(M, 54, FB_W - 2 * M, FB_H - 48 - 8 - 54)
1346
1353
  )
1347
- scroll.setHasVerticalScroller_(True)
1348
- scroll.setBorderType_(NS_BEZEL_BORDER)
1349
- tv = NSTextView.alloc().initWithFrame_(
1350
- NSMakeRect(0, 0, FB_W - 2 * M, 80)
1351
- )
1352
- tv.setFont_(NSFont.systemFontOfSize_(12))
1353
- tv.setRichText_(False)
1354
- tv.setEditable_(True)
1355
- tv.setSelectable_(True)
1356
- scroll.setDocumentView_(tv)
1357
1354
  content.addSubview_(scroll)
1358
1355
  self._tv = tv
1359
1356
 
package/mcp/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m13v/s4l-mcp",
3
- "version": "1.6.197-rc.13",
3
+ "version": "1.6.197-rc.14",
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",
@@ -23,6 +23,7 @@
23
23
  "@modelcontextprotocol/ext-apps": "^1.7.3",
24
24
  "@modelcontextprotocol/sdk": "^1.29.0",
25
25
  "@sentry/node": "^10.58.0",
26
+ "ws": "^8.21.0",
26
27
  "zod": "^3.23.8"
27
28
  },
28
29
  "devDependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m13v/s4l",
3
- "version": "1.6.197-rc.13",
3
+ "version": "1.6.197-rc.14",
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",