@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 +1 -1
- package/mcp/dist/screencast.js +15 -1
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +1 -1
- package/mcp/menubar/s4l_card.py +54 -57
- package/mcp/package.json +2 -1
- package/package.json +1 -1
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
|
|
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
|
}
|
package/mcp/dist/screencast.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
package/mcp/dist/version.json
CHANGED
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.
|
|
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": {
|
package/mcp/menubar/s4l_card.py
CHANGED
|
@@ -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
|
|
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
|
-
#
|
|
508
|
-
#
|
|
509
|
-
|
|
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 +
|
|
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
|
|
533
|
-
#
|
|
534
|
-
#
|
|
535
|
-
#
|
|
536
|
-
#
|
|
537
|
-
#
|
|
538
|
-
|
|
539
|
-
fb.
|
|
540
|
-
|
|
541
|
-
|
|
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 -
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
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.
|
|
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",
|