social-autoposter 1.6.184 → 1.6.185
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 +11 -2
- package/mcp/dist/runtime.js +42 -4
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +1 -1
- package/mcp/menubar/s4l_menubar.py +62 -13
- package/mcp/package.json +1 -1
- package/package.json +1 -1
package/mcp/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import fs from "node:fs";
|
|
|
22
22
|
import { repoDir, runPython, run, readPlan, writePlan, planPath, } from "./repo.js";
|
|
23
23
|
import { applySetup, resolveProject, personaReady, listManagedProjectStatus, ensureShortLinksDefault, ensurePersonaProject, findPersonaProject, REQUIRED_FIELDS, RECOMMENDED_FIELDS, configPath, normalizeStringList, } from "./setup.js";
|
|
24
24
|
import { xStatus, xConnect, xDetectSources, xScanProfile, summarizeXAuth } from "./twitterAuth.js";
|
|
25
|
-
import { startProvisioning, isProvisioning, readProgress, runtimeReady, readRuntime, resolvePython, resolveChrome, ensureMenubar, menubarRunning, ensurePipelineCurrent, ensureRuntimeProvisioned, } from "./runtime.js";
|
|
25
|
+
import { startProvisioning, isProvisioning, readProgress, runtimeReady, readRuntime, resolvePython, resolveChrome, ensureMenubar, menubarRunning, clearMenubarStop, ensurePipelineCurrent, ensureRuntimeProvisioned, } from "./runtime.js";
|
|
26
26
|
import { blockOnboardingMilestone, completeOnboardingMilestone, ensureDoctorPhase, onboardingLedger, onboardingSnapshot, recordOnboardingAttempt, runDoctorPhase, } from "./onboarding.js";
|
|
27
27
|
import { VERSION, versionStatus, latestPublishedVersion } from "./version.js";
|
|
28
28
|
import { initSentry, sendHeartbeat, captureError, flushSentry, startLogStreaming, flushLogs } from "./telemetry.js";
|
|
@@ -2101,10 +2101,14 @@ tool("restart_menubar", {
|
|
|
2101
2101
|
title: "Restart the S4L menu bar app",
|
|
2102
2102
|
description: "Relaunch the always-on S4L menu bar (tray) app after it was quit. Re-loads its " +
|
|
2103
2103
|
"LaunchAgent (installing the menu bar first if needed). Use when the dashboard reports the menu " +
|
|
2104
|
-
"bar is not running, or the user asks to
|
|
2104
|
+
"bar is not running, or the user asks to start S4L, restart S4L, or bring the S4L tray icon " +
|
|
2105
|
+
"back. Does NOT touch the draft " +
|
|
2105
2106
|
"schedule, X connection, or any posting — it only restarts the tray UI.",
|
|
2106
2107
|
inputSchema: {},
|
|
2107
2108
|
}, async () => {
|
|
2109
|
+
// Explicit user intent to start: lift the stop sentinel a tray Quit wrote,
|
|
2110
|
+
// otherwise ensureMenubar() would no-op forever.
|
|
2111
|
+
clearMenubarStop();
|
|
2108
2112
|
const res = await ensureMenubar();
|
|
2109
2113
|
const running = await menubarRunning();
|
|
2110
2114
|
return jsonContent({
|
|
@@ -2145,6 +2149,11 @@ tool("queue_setup", {
|
|
|
2145
2149
|
inputSchema: {},
|
|
2146
2150
|
}, async () => {
|
|
2147
2151
|
ensureQueueWorkerToolsAllowed();
|
|
2152
|
+
// Re-arming the autopilot is an explicit "start S4L" action: lift a prior
|
|
2153
|
+
// tray Quit so the review cards have a surface again. Best-effort and
|
|
2154
|
+
// async — task specs must return regardless of tray state.
|
|
2155
|
+
clearMenubarStop();
|
|
2156
|
+
void ensureMenubar();
|
|
2148
2157
|
// Write each worker's canonical SKILL.md to disk NOW, before the agent calls
|
|
2149
2158
|
// create_scheduled_task. The host's create_scheduled_task can report a task
|
|
2150
2159
|
// "already exists" (e.g. a stale Routines registration left after a reset) and
|
package/mcp/dist/runtime.js
CHANGED
|
@@ -84,6 +84,29 @@ const MENUBAR_DIR = path.join(STATE_DIR, "menubar");
|
|
|
84
84
|
const MENUBAR_ENTRY = path.join(MENUBAR_DIR, "s4l_menubar.py");
|
|
85
85
|
const MENUBAR_OUT_LOG = path.join(MENUBAR_DIR, "menubar.out.log");
|
|
86
86
|
const MENUBAR_ERR_LOG = path.join(MENUBAR_DIR, "menubar.err.log");
|
|
87
|
+
// Stop sentinel: the menu bar's Quit flow writes this file (and boots itself
|
|
88
|
+
// out) to record that the USER explicitly stopped S4L. Every auto-start path
|
|
89
|
+
// (boot-time ensureMenubar, runtime provision) must respect it, otherwise the
|
|
90
|
+
// tray is guaranteed back on the next Claude restart — the exact bug users hit
|
|
91
|
+
// after Quit. Only an explicit start action (restart_menubar tool, queue_setup
|
|
92
|
+
// re-arm) clears it. KEEP the filename in sync with menubar/s4l_menubar.py.
|
|
93
|
+
const MENUBAR_STOP_FLAG = path.join(STATE_DIR, "stopped.flag");
|
|
94
|
+
export function menubarStopped() {
|
|
95
|
+
try {
|
|
96
|
+
return fs.existsSync(MENUBAR_STOP_FLAG);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export function clearMenubarStop() {
|
|
103
|
+
try {
|
|
104
|
+
fs.rmSync(MENUBAR_STOP_FLAG, { force: true });
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* best-effort */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
87
110
|
// A directory is a usable pipeline clone only if it carries requirements.txt
|
|
88
111
|
// (the deps manifest) AND scripts/ (the pipeline). Guards against pointing at an
|
|
89
112
|
// empty extension dir or a half-deleted state dir.
|
|
@@ -656,7 +679,15 @@ async function provision(progress) {
|
|
|
656
679
|
// Non-fatal: a menu bar failure must never block a usable runtime, so on any
|
|
657
680
|
// problem we mark the step errored and still persist runtime.json below.
|
|
658
681
|
setStep("menubar", "running");
|
|
659
|
-
if (process.platform
|
|
682
|
+
if (process.platform !== "darwin") {
|
|
683
|
+
setStep("menubar", "done", "skipped (macOS only)");
|
|
684
|
+
}
|
|
685
|
+
else if (menubarStopped()) {
|
|
686
|
+
// A runtime repair/re-provision must not resurrect a tray the user
|
|
687
|
+
// explicitly quit; an explicit start clears the flag first.
|
|
688
|
+
setStep("menubar", "done", "skipped (user stopped the menu bar)");
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
660
691
|
const mb = await installMenubar(uv, uvEnv, VENV_PYTHON);
|
|
661
692
|
setStep("menubar", mb.ok ? "done" : "error", mb.detail);
|
|
662
693
|
// Non-fatal step, so the only prior signal of a menu bar install failure was
|
|
@@ -669,9 +700,6 @@ async function provision(progress) {
|
|
|
669
700
|
});
|
|
670
701
|
}
|
|
671
702
|
}
|
|
672
|
-
else {
|
|
673
|
-
setStep("menubar", "done", "skipped (macOS only)");
|
|
674
|
-
}
|
|
675
703
|
// --- Persist the result ---------------------------------------------------
|
|
676
704
|
const info = {
|
|
677
705
|
python: VENV_PYTHON,
|
|
@@ -790,6 +818,10 @@ export async function menubarRunning() {
|
|
|
790
818
|
return true;
|
|
791
819
|
if (!runtimeReady())
|
|
792
820
|
return true;
|
|
821
|
+
// User explicitly quit the tray: it is down ON PURPOSE, so report "fine" —
|
|
822
|
+
// the dashboard banner must not nag about a state the user chose.
|
|
823
|
+
if (menubarStopped())
|
|
824
|
+
return true;
|
|
793
825
|
try {
|
|
794
826
|
return await menubarLoaded();
|
|
795
827
|
}
|
|
@@ -846,6 +878,12 @@ export async function installMenubar(uv, uvEnv, python) {
|
|
|
846
878
|
export async function ensureMenubar() {
|
|
847
879
|
if (process.platform !== "darwin")
|
|
848
880
|
return { ok: true, skipped: true, detail: "non-macOS" };
|
|
881
|
+
// The user clicked Quit in the tray: stay stopped across Claude restarts,
|
|
882
|
+
// regardless of runtime state. Explicit start paths (restart_menubar tool,
|
|
883
|
+
// queue_setup) clear the flag before calling this.
|
|
884
|
+
if (menubarStopped()) {
|
|
885
|
+
return { ok: true, skipped: true, detail: "user stopped the menu bar (stopped.flag)" };
|
|
886
|
+
}
|
|
849
887
|
if (!runtimeReady())
|
|
850
888
|
return { ok: false, skipped: true, detail: "runtime not ready" };
|
|
851
889
|
if (fs.existsSync(MENUBAR_ENTRY) &&
|
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.
|
|
5
|
+
"version": "1.6.185",
|
|
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": {
|
|
@@ -118,6 +118,19 @@ def _activate_front():
|
|
|
118
118
|
CLAUDE_APP = "Claude"
|
|
119
119
|
POLL_SECONDS = 5
|
|
120
120
|
|
|
121
|
+
# Our own LaunchAgent. Quit boots it out and deletes the plist so the tray is
|
|
122
|
+
# genuinely gone (KeepAlive can't respawn it, RunAtLoad can't resurrect it at
|
|
123
|
+
# next login). Keep the label in sync with MENUBAR_LABEL in mcp/src/runtime.ts.
|
|
124
|
+
MENUBAR_LABEL = "com.m13v.social-autoposter.menubar"
|
|
125
|
+
MENUBAR_PLIST = os.path.join(
|
|
126
|
+
os.path.expanduser("~"), "Library", "LaunchAgents", f"{MENUBAR_LABEL}.plist"
|
|
127
|
+
)
|
|
128
|
+
# Stop sentinel read by the MCP server's ensureMenubar()/provision paths: while
|
|
129
|
+
# present, no auto-start path may reinstall the tray. Cleared only by explicit
|
|
130
|
+
# start actions (restart_menubar tool, queue_setup re-arm). Keep the filename in
|
|
131
|
+
# sync with MENUBAR_STOP_FLAG in mcp/src/runtime.ts.
|
|
132
|
+
STOP_FLAG = os.path.join(st.state_dir(), "stopped.flag")
|
|
133
|
+
|
|
121
134
|
# Autopilot scheduled tasks. The two queue workers must RUN in a dedicated folder
|
|
122
135
|
# (~/.s4l-worker) so their once-a-minute sessions don't flood the user's
|
|
123
136
|
# interactive Claude Code history (Claude buckets sessions by cwd). The single
|
|
@@ -704,22 +717,26 @@ class S4LMenuBar(rumps.App):
|
|
|
704
717
|
|
|
705
718
|
def _quit_app(self, _=None):
|
|
706
719
|
"""The single Quit path. Quitting stops the autopilot completely: the
|
|
707
|
-
draft/query scheduled tasks are removed so they no longer fire
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
720
|
+
draft/query scheduled tasks are removed so they no longer fire, AND the
|
|
721
|
+
tray itself goes away for good (stop flag + plist removal + self
|
|
722
|
+
bootout — see _quit_work). Claude Desktop OWNS the live schedule and
|
|
723
|
+
caches the registry in memory, clobbering any live edit on the next
|
|
724
|
+
fire — so the only reliable way to disable them is to quit Claude,
|
|
725
|
+
strip the tasks while it's down, then relaunch. We warn the user with a
|
|
726
|
+
modal FIRST that Claude Desktop will restart, since the app window will
|
|
727
|
+
close and reopen under them."""
|
|
713
728
|
_activate_front()
|
|
714
729
|
choice = rumps.alert(
|
|
715
730
|
title="Quit the S4L autoposter?",
|
|
716
731
|
message=(
|
|
717
732
|
"Quitting stops the autoposter completely: the draft + query "
|
|
718
|
-
"scheduled tasks are removed so nothing fires anymore
|
|
733
|
+
"scheduled tasks are removed so nothing fires anymore, and this "
|
|
734
|
+
"menu bar icon goes away and stays away.\n\n"
|
|
719
735
|
"Claude Desktop will quit and restart to apply this — its window "
|
|
720
736
|
"will close and reopen in a moment. Your X login, browser layer, "
|
|
721
|
-
"and config all stay
|
|
722
|
-
"
|
|
737
|
+
"and config all stay.\n\n"
|
|
738
|
+
"To start S4L again later, open Claude and say \"start S4L\" "
|
|
739
|
+
"(or re-run setup)."
|
|
723
740
|
),
|
|
724
741
|
ok="Quit & restart Claude", cancel="Cancel",
|
|
725
742
|
)
|
|
@@ -768,10 +785,23 @@ class S4LMenuBar(rumps.App):
|
|
|
768
785
|
pass
|
|
769
786
|
|
|
770
787
|
def _quit_work(self):
|
|
771
|
-
"""Quit/kill Claude, strip the scheduled tasks while it's down, relaunch
|
|
772
|
-
|
|
773
|
-
|
|
788
|
+
"""Quit/kill Claude, strip the scheduled tasks while it's down, relaunch
|
|
789
|
+
Claude, then take THIS tray down for good. Mirror of
|
|
790
|
+
_relocate_restart_work's restart block. The menu bar is a separate
|
|
791
|
+
launchd process, so killing Claude does not kill us.
|
|
792
|
+
|
|
793
|
+
The stop flag is written FIRST: the relaunched Claude boots the MCP
|
|
794
|
+
server, whose ensureMenubar() would otherwise reinstall the tray
|
|
795
|
+
unconditionally (the reappearing-icon bug). The plist is deleted so
|
|
796
|
+
RunAtLoad can't resurrect us at next login, and the final bootout
|
|
797
|
+
removes the KeepAlive job — which kills this process, so it must be
|
|
798
|
+
the last thing we do."""
|
|
774
799
|
try:
|
|
800
|
+
try:
|
|
801
|
+
with open(STOP_FLAG, "w") as fh:
|
|
802
|
+
fh.write(f"user quit via menu bar at {time.strftime('%Y-%m-%dT%H:%M:%S%z')}\n")
|
|
803
|
+
except Exception as e:
|
|
804
|
+
_capture(e, action="quit_stop_flag")
|
|
775
805
|
subprocess.run(["osascript", "-e", 'tell application "Claude" to quit'],
|
|
776
806
|
capture_output=True, timeout=20)
|
|
777
807
|
time.sleep(4)
|
|
@@ -780,11 +810,30 @@ class S4LMenuBar(rumps.App):
|
|
|
780
810
|
subprocess.run(["killall", "-9", "Claude"], capture_output=True)
|
|
781
811
|
time.sleep(1)
|
|
782
812
|
self._remove_scheduled_tasks()
|
|
813
|
+
try:
|
|
814
|
+
os.remove(MENUBAR_PLIST)
|
|
815
|
+
except FileNotFoundError:
|
|
816
|
+
pass
|
|
817
|
+
except Exception as e:
|
|
818
|
+
_capture(e, action="quit_remove_plist")
|
|
783
819
|
subprocess.run(["open", "-a", CLAUDE_APP], capture_output=True, timeout=20)
|
|
784
|
-
self._notify("S4L", "Autoposter stopped. Claude
|
|
820
|
+
self._notify("S4L", "Autoposter stopped. Say \"start S4L\" in Claude to bring it back.")
|
|
785
821
|
except Exception as e:
|
|
786
822
|
_capture(e, action="quit_app")
|
|
787
823
|
self._notify("S4L", "Couldn't fully stop the autoposter — see logs.")
|
|
824
|
+
finally:
|
|
825
|
+
# Boot out our own KeepAlive agent. launchd kills this process as
|
|
826
|
+
# part of the bootout, so nothing after this line is guaranteed to
|
|
827
|
+
# run. Runs even if the Claude restart above failed: the user asked
|
|
828
|
+
# for the tray to be gone.
|
|
829
|
+
subprocess.run(
|
|
830
|
+
["launchctl", "bootout", f"gui/{os.getuid()}/{MENUBAR_LABEL}"],
|
|
831
|
+
capture_output=True, timeout=15,
|
|
832
|
+
)
|
|
833
|
+
# Only reached if bootout didn't kill us (e.g. dev run outside
|
|
834
|
+
# launchd). Exit 0: KeepAlive {SuccessfulExit: false} treats a clean
|
|
835
|
+
# exit as final. os._exit because we're on a background thread.
|
|
836
|
+
os._exit(0)
|
|
788
837
|
|
|
789
838
|
def _update(self, _=None):
|
|
790
839
|
self._send_to_claude(UPDATE_PROMPT)
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m13v/social-autoposter-mcp",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.185",
|
|
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