@m13v/s4l 1.7.4-rc.23 → 1.7.4-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.
- package/README.md +101 -134
- package/mcp/dist/index.js +2 -8
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +1 -1
- package/mcp/menubar/s4l_card.py +3 -35
- package/mcp/menubar/s4l_menubar.py +3 -3
- package/mcp/package.json +1 -1
- package/package.json +2 -2
- package/scripts/active_experiments.py +6 -30
- package/scripts/autopilot_stall_watch.py +2 -2
- package/scripts/engage_twitter_helper.py +17 -83
- package/scripts/engagement_styles.py +26 -221
- package/scripts/enrich_reply_parents.py +22 -68
- package/scripts/feedback_digest.py +1 -24
- package/scripts/generate_daily_human_style.py +7 -31
- package/scripts/invent_styles.py +26 -103
- package/scripts/linkedin_killswitch.py +0 -43
- package/scripts/memory_snapshot.py +0 -57
- package/scripts/recent_self_posts.py +0 -6
- package/scripts/scan_x_profile.py +7 -25
- package/scripts/setup_twitter_auth.py +1 -6
- package/scripts/snapshot.py +1 -1
- package/scripts/top_performers.py +0 -34
- package/scripts/twitter_browser.py +5 -64
- package/scripts/twitter_post_plan.py +1 -10
- package/skill/lib/linkedin-backend.sh +1 -12
- package/skill/lib/twitter-backend.sh +5 -107
- package/skill/run-twitter-cycle.sh +49 -181
- package/scripts/cdp_ready_check.py +0 -64
- package/scripts/linkedin_cadence.py +0 -234
- package/skill/linkedin-cadence.sh +0 -20
package/README.md
CHANGED
|
@@ -1,176 +1,143 @@
|
|
|
1
|
-
#
|
|
1
|
+
# social-autoposter
|
|
2
2
|
|
|
3
|
-
S4L
|
|
4
|
-
your own machine.
|
|
3
|
+
Open-source repo behind **[S4L (s4lai)](https://s4l.ai)**: an automated social posting pipeline for Reddit, X/Twitter, LinkedIn, and Moltbook. Ships as a Claude Code skill plus a set of standalone Python helpers and macOS launchd jobs.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
momentum and fit, drafts contribution-first replies in your voice, and gives you
|
|
8
|
-
a review queue before anything goes live. The product is positioned as a
|
|
9
|
-
self-serve desktop plugin: subscribe, install, connect your accounts, review the
|
|
10
|
-
drafts, and track the results.
|
|
5
|
+
> The hosted managed version is **S4L** (written `s4lai`, domain `s4l.ai`): done-for-you Reddit and Twitter brand-awareness, $1/1K impressions, $50/1K site visits. See https://s4l.ai.
|
|
11
6
|
|
|
12
|
-
-
|
|
13
|
-
- Pricing: https://s4l.ai/pricing
|
|
14
|
-
- Source: https://github.com/m13v/social-autoposter
|
|
7
|
+
State (posts, replies, candidates, stats) is read and written through the hosted S4L HTTP API (`AUTOPOSTER_API_BASE` + an install key in `~/social-autoposter/.env`); no database to provision. Each platform drives its own persistent Playwright MCP browser profile, so logins survive across runs.
|
|
15
8
|
|
|
16
|
-
##
|
|
9
|
+
## Prerequisites
|
|
17
10
|
|
|
18
|
-
|
|
19
|
-
it looks for conversations worth entering.
|
|
11
|
+
A new machine needs all of these before the pipeline can run end to end:
|
|
20
12
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
and hard lines in the drafting context.
|
|
27
|
-
- Presents drafts in a review workflow so you can approve, reject, or edit before
|
|
28
|
-
posting.
|
|
29
|
-
- Tracks views, upvotes, clicks, deletions, and misses so each run learns from
|
|
30
|
-
the previous one.
|
|
13
|
+
- **macOS** (the launchd plists are mac-only; Linux users can crib the cron snippets from `setup/SKILL.md` Step 7)
|
|
14
|
+
- **Node.js 16+** (for `npx`, the installer, and `@playwright/mcp` at runtime)
|
|
15
|
+
- **Python 3.9+** with `pip3` (helper scripts; deps auto-installed by the installer)
|
|
16
|
+
- **Claude Code CLI** on `PATH` (the cron scripts shell out to `claude -p` with a per-platform MCP config)
|
|
17
|
+
- One Chromium install per platform (created on first run by `@playwright/mcp` against the persistent profile dirs)
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
explicitly turn it on.
|
|
19
|
+
Optional:
|
|
34
20
|
|
|
35
|
-
|
|
21
|
+
- `MOLTBOOK_API_KEY` in `.env` for Moltbook posting and scanning
|
|
22
|
+
- `RESEND_API_KEY` and `NOTIFICATION_EMAIL` in `.env` for DM-escalation emails
|
|
36
23
|
|
|
37
|
-
|
|
38
|
-
without handing their accounts to an agency or living inside mention alerts all
|
|
39
|
-
day.
|
|
24
|
+
## Install
|
|
40
25
|
|
|
41
|
-
|
|
42
|
-
|
|
26
|
+
```bash
|
|
27
|
+
npx social-autoposter init
|
|
28
|
+
```
|
|
43
29
|
|
|
44
|
-
|
|
30
|
+
`bin/cli.js` does all of the wiring in one shot:
|
|
45
31
|
|
|
46
|
-
|
|
32
|
+
1. Copies `scripts/`, `skill/`, `setup/`, `SKILL.md`, and `browser-agent-configs/` into `~/social-autoposter/`
|
|
33
|
+
2. Creates `config.json` from `config.example.json` and writes a blank `.env` template (fill in your S4L API key and optional `MOLTBOOK_API_KEY`)
|
|
34
|
+
3. Installs the Python helper deps via `pip3` if missing
|
|
35
|
+
4. Generates launchd plists in `~/social-autoposter/launchd/` with the user's actual `HOME` and `PATH`
|
|
36
|
+
5. Installs the Playwright MCP configs to `~/.claude/browser-agent-configs/` (twitter, reddit, linkedin) with `__HOME__` and `__NODE_BIN__` placeholders substituted. Existing files are left alone, so any window-position tweaks survive `npx social-autoposter update`.
|
|
37
|
+
6. Creates empty persistent browser profile dirs at `~/.claude/browser-profiles/{twitter,reddit,linkedin}`
|
|
38
|
+
7. Symlinks `~/.claude/skills/social-autoposter` and `~/.claude/skills/social-autoposter-setup` to the install dir
|
|
47
39
|
|
|
48
|
-
|
|
49
|
-
2. Download the plugin link emailed after checkout.
|
|
50
|
-
3. Install the plugin in Claude Desktop.
|
|
51
|
-
4. Start a new Claude chat and send:
|
|
40
|
+
To refresh code without touching user files (`config.json`, `.env`, `SKILL.md`, or any browser config you customized):
|
|
52
41
|
|
|
53
|
-
```
|
|
54
|
-
|
|
42
|
+
```bash
|
|
43
|
+
npx social-autoposter update
|
|
55
44
|
```
|
|
56
45
|
|
|
57
|
-
|
|
58
|
-
discovers product and voice context, seeds search topics, schedules draft
|
|
59
|
-
generation, and verifies that draft cards appear without posting.
|
|
46
|
+
## Configure
|
|
60
47
|
|
|
61
|
-
|
|
48
|
+
Tell your Claude agent: **"set me up on social-autoposter plugin end to end"**. The
|
|
49
|
+
setup skill treats that as a terminal goal:
|
|
62
50
|
|
|
63
|
-
|
|
51
|
+
1. Inspect and repair the owned runtime.
|
|
52
|
+
2. Auto-detect the best browser profile and connect X/Twitter. macOS may require
|
|
53
|
+
a Safe Storage approval; a logged-out account may require one manual sign-in.
|
|
54
|
+
3. Scan the X profile, discover and research the user's product, and infer a
|
|
55
|
+
conservative project, ICP, voice, and search topics without an interview.
|
|
56
|
+
4. Save the project and seed its topics into the backend.
|
|
57
|
+
5. Run a draft-only cycle to verify the pipeline without posting.
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|-- mcp/ Desktop plugin / MCP server, panel UI, release bundle
|
|
68
|
-
|-- scripts/ Discovery, drafting, stats, telemetry, and queue helpers
|
|
69
|
-
|-- skill/ Shell entrypoints used by scheduled jobs
|
|
70
|
-
|-- setup/ End-to-end setup skill used by Claude
|
|
71
|
-
|-- browser-agent-configs/ Browser automation profile templates
|
|
72
|
-
|-- launchd/ macOS LaunchAgent templates
|
|
73
|
-
|-- mcp-servers/ Local MCP helpers used by the runtime
|
|
74
|
-
|-- config.example.json Example project/account configuration
|
|
75
|
-
`-- SKILL.md Legacy social-autoposter agent playbook
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
The `mcp/` package is the plugin users interact with. The rest of the repo is the
|
|
79
|
-
pipeline it bundles, installs, and drives.
|
|
59
|
+
The agent pauses only for an unavoidable login or when no product can be
|
|
60
|
+
identified. Autopilot remains off until explicitly requested.
|
|
80
61
|
|
|
81
|
-
##
|
|
62
|
+
## How the runtime is wired
|
|
82
63
|
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
64
|
+
```
|
|
65
|
+
launchd ──▶ skill/run-{platform}.sh ──▶ claude -p --strict-mcp-config --mcp-config ~/.claude/browser-agent-configs/{platform}-agent-mcp.json
|
|
66
|
+
│ │
|
|
67
|
+
│ └──▶ @playwright/mcp@latest
|
|
68
|
+
│ │
|
|
69
|
+
│ └──▶ ~/.claude/browser-profiles/{platform}/ (persistent userDataDir)
|
|
70
|
+
│
|
|
71
|
+
├──▶ scripts/find_threads.py, top_twitter_queries.py (no browser, API dedup)
|
|
72
|
+
├──▶ scripts/pick_project.py (weighted project rotation)
|
|
73
|
+
├──▶ scripts/top_performers.py (feedback report from past stats)
|
|
74
|
+
└──▶ S4L HTTP API (AUTOPOSTER_API_BASE in .env)
|
|
90
75
|
```
|
|
91
76
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
- Runs locally on macOS.
|
|
95
|
-
- Uses the user's own logged-in browser profiles.
|
|
96
|
-
- Stores install state locally and scopes API calls by install identity.
|
|
97
|
-
- Keeps secrets such as `.env`, `config.json`, browser profiles, logs, and local
|
|
98
|
-
databases out of Git.
|
|
99
|
-
- Uses approval-first drafting as the default operating mode.
|
|
100
|
-
|
|
101
|
-
## Develop from source
|
|
77
|
+
Each `skill/run-*.sh`:
|
|
102
78
|
|
|
103
|
-
|
|
104
|
-
|
|
79
|
+
1. Controlled by launchd (load/unload). Use the dashboard Pause All / Resume All button, or `launchctl unload/load` directly
|
|
80
|
+
2. Acquires a per-platform lock from `skill/lock.sh` (waits up to 60 min for any prior run)
|
|
81
|
+
3. Sources `~/social-autoposter/.env`
|
|
82
|
+
4. Picks a project, builds a feedback report, fetches `llms.txt` for product context
|
|
83
|
+
5. Calls `find_*.py` for API-side candidates already deduped against the DB
|
|
84
|
+
6. Spawns a child Claude process with `--strict-mcp-config` so it only sees the one platform's browser MCP
|
|
105
85
|
|
|
106
|
-
|
|
86
|
+
The launchd schedules generated by `bin/cli.js` on install:
|
|
107
87
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
88
|
+
| Job | Cadence |
|
|
89
|
+
|-----|---------|
|
|
90
|
+
| `com.m13v.social-stats` (`stats.sh`) | every 21600 s (6 h) |
|
|
91
|
+
| `com.m13v.social-engage` (`engage.sh`) | every 21600 s (6 h) |
|
|
112
92
|
|
|
113
|
-
|
|
93
|
+
All per-platform plists live in `launchd/` (reddit-search, reddit-threads, twitter-cycle, linkedin, moltbook, github, octolens, audit, dm-replies-*, link-edit-*, scan-reddit-replies, scan-moltbook-replies, etc.) and use either `StartInterval` or `StartCalendarInterval` for fixed wall-clock times. Activate any of them with:
|
|
114
94
|
|
|
115
95
|
```bash
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
npm install
|
|
96
|
+
ln -sf ~/social-autoposter/launchd/com.m13v.social-twitter-cycle.plist ~/Library/LaunchAgents/
|
|
97
|
+
launchctl load ~/Library/LaunchAgents/com.m13v.social-twitter-cycle.plist
|
|
119
98
|
```
|
|
120
99
|
|
|
121
|
-
|
|
100
|
+
## Skill commands
|
|
122
101
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
102
|
+
| Command | What it does |
|
|
103
|
+
|---------|-------------|
|
|
104
|
+
| `/social-autoposter` | Comment run: find threads, draft, post, log (cron-safe) |
|
|
105
|
+
| `/social-autoposter post` | Create an original post or thread (manual only) |
|
|
106
|
+
| `/social-autoposter stats` | Update engagement stats via API |
|
|
107
|
+
| `/social-autoposter engage` | Scan and reply to responses on our posts |
|
|
108
|
+
| `/social-autoposter audit` | Full browser audit of all posts |
|
|
127
109
|
|
|
128
|
-
|
|
110
|
+
View live stats at `https://s4l.ai/stats/<your-handle>` once posts start landing.
|
|
129
111
|
|
|
130
|
-
|
|
131
|
-
cd mcp
|
|
132
|
-
node install.mjs
|
|
133
|
-
```
|
|
112
|
+
## Repo layout
|
|
134
113
|
|
|
135
|
-
Then fully quit and reopen Claude so MCP servers reload.
|
|
136
|
-
|
|
137
|
-
Run the test suite:
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
npm test
|
|
141
114
|
```
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
115
|
+
social-autoposter/
|
|
116
|
+
├── SKILL.md the playbook (locked, immutable)
|
|
117
|
+
├── bin/cli.js installer + dashboard launcher
|
|
118
|
+
├── browser-agent-configs/ Playwright MCP templates (twitter/reddit/linkedin)
|
|
119
|
+
├── config.example.json config template
|
|
120
|
+
├── setup/SKILL.md autonomous end-to-end setup skill (locked)
|
|
121
|
+
├── scripts/ Python and JS helpers (no browser, no LLM)
|
|
122
|
+
├── skill/ shell wrappers invoked by launchd
|
|
123
|
+
└── launchd/ generated macOS LaunchAgent plists
|
|
147
124
|
```
|
|
148
125
|
|
|
149
|
-
|
|
150
|
-
plugin, creates `mcp/social-autoposter.mcpb`, and runs the release checks without
|
|
151
|
-
touching npm or GitHub releases.
|
|
152
|
-
|
|
153
|
-
## Runtime commands
|
|
154
|
-
|
|
155
|
-
The plugin exposes MCP tools for the user-facing workflow:
|
|
126
|
+
## For other AI agents
|
|
156
127
|
|
|
157
|
-
|
|
158
|
-
- `engagement_mode` chooses personal-brand and product-promotion lanes.
|
|
159
|
-
- `dashboard` opens the review dashboard.
|
|
160
|
-
- `post_drafts` posts only the drafts the user selected.
|
|
161
|
-
- `get_stats` reads X/Twitter stats.
|
|
162
|
-
- `pause_s4l` pauses or resumes scheduled S4L jobs.
|
|
163
|
-
- `runtime` installs, updates, and diagnoses the local runtime.
|
|
164
|
-
- `report_diagnosis` sends a support report to the S4L team.
|
|
128
|
+
The skill works with any agent that has shell access, browser automation, and an LLM. The Python and JS helpers in `scripts/` handle thread discovery, reply scanning, and stats updates without needing a browser. `SKILL.md` is the playbook; any agent can read it and execute the workflows with its own tools.
|
|
165
129
|
|
|
166
|
-
|
|
167
|
-
the plugin bundles and reuses the same pipeline scripts.
|
|
130
|
+
## Pause and resume
|
|
168
131
|
|
|
169
|
-
|
|
132
|
+
Use the dashboard at `localhost:3141` (Pause All / Resume All button), or manually:
|
|
170
133
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
134
|
+
```bash
|
|
135
|
+
# Pause: unload all jobs + kill running processes
|
|
136
|
+
for plist in ~/Library/LaunchAgents/com.m13v.social-*.plist; do launchctl unload "$plist"; done
|
|
137
|
+
|
|
138
|
+
# Resume: reload all jobs
|
|
139
|
+
for plist in ~/social-autoposter/launchd/com.m13v.social-*.plist; do
|
|
140
|
+
ln -sf "$plist" ~/Library/LaunchAgents/
|
|
141
|
+
launchctl load ~/Library/LaunchAgents/$(basename "$plist")
|
|
142
|
+
done
|
|
143
|
+
```
|
package/mcp/dist/index.js
CHANGED
|
@@ -163,9 +163,6 @@ function plistXml(opts) {
|
|
|
163
163
|
const schedule = opts.keepAlive
|
|
164
164
|
? `\t<key>KeepAlive</key>\n\t<true/>`
|
|
165
165
|
: `\t<key>StartInterval</key>\n\t<integer>${opts.intervalSecs}</integer>`;
|
|
166
|
-
const abandon = opts.abandonProcessGroup
|
|
167
|
-
? `\n\t<key>AbandonProcessGroup</key>\n\t<true/>`
|
|
168
|
-
: "";
|
|
169
166
|
// Background (cron/autopilot) runs get the same Chrome the interactive cycle
|
|
170
167
|
// uses, so a no-sudo ~/Applications install (which the shell's own resolver
|
|
171
168
|
// doesn't scan) is still found off-screen. Omitted when Chrome resolves via
|
|
@@ -191,7 +188,7 @@ function plistXml(opts) {
|
|
|
191
188
|
\t<array>
|
|
192
189
|
${args}
|
|
193
190
|
\t</array>
|
|
194
|
-
${schedule}
|
|
191
|
+
${schedule}
|
|
195
192
|
\t<key>StandardOutPath</key>
|
|
196
193
|
\t<string>${opts.stdoutLog}</string>
|
|
197
194
|
\t<key>StandardErrorPath</key>
|
|
@@ -2990,7 +2987,7 @@ function queueDir() {
|
|
|
2990
2987
|
// (which orphans the routines while their global SKILL.md files stay put, so the
|
|
2991
2988
|
// SKILL.md-presence check in autopilotLoaded() reads a FALSE green). Mirrors the
|
|
2992
2989
|
// menu bar's AUTOPILOT_STALL_SECONDS in mcp/menubar/s4l_menubar.py — keep in sync.
|
|
2993
|
-
const AUTOPILOT_STALL_MS =
|
|
2990
|
+
const AUTOPILOT_STALL_MS = 180_000;
|
|
2994
2991
|
// True when no scheduled-task routine is draining the draft queue. Two signals,
|
|
2995
2992
|
// OR'd (keep in lockstep with mcp/menubar/s4l_menubar.py::_autopilot_stalled and
|
|
2996
2993
|
// scripts/autopilot_stall_watch.py):
|
|
@@ -3503,9 +3500,6 @@ async function ensureQueueKickerInstalled() {
|
|
|
3503
3500
|
stdoutLog: path.join(logDir, "launchd-twitter-cycle-stdout.log"),
|
|
3504
3501
|
stderrLog: path.join(logDir, "launchd-twitter-cycle-stderr.log"),
|
|
3505
3502
|
extraEnv: kickerEnv(),
|
|
3506
|
-
// Don't let launchd reap the harness Chrome the cycle launches when the
|
|
3507
|
-
// kicker shell exits (2026-07-12 foreground-steal loop).
|
|
3508
|
-
abandonProcessGroup: true,
|
|
3509
3503
|
});
|
|
3510
3504
|
// Content-aware install: an existing box has the OLD kicker plist pointing at
|
|
3511
3505
|
// run-twitter-cycle.sh (no merge step). ensurePlist won't overwrite, so detect
|
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.7.4-rc.
|
|
5
|
+
"version": "1.7.4-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": {
|
package/mcp/menubar/s4l_card.py
CHANGED
|
@@ -431,21 +431,7 @@ def _details_lines(d):
|
|
|
431
431
|
d = d or {}
|
|
432
432
|
lines = []
|
|
433
433
|
style = (d.get("engagement_style") or "").strip()
|
|
434
|
-
|
|
435
|
-
# can tell WHICH style produced the draft they are picking (Draft B is
|
|
436
|
-
# the exploration slot; its source arm renders separately below via the
|
|
437
|
-
# generic experiments lines). Single-draft cards keep the old one-liner.
|
|
438
|
-
_dual = d.get("drafts")
|
|
439
|
-
if isinstance(_dual, list) and len(_dual) == 2:
|
|
440
|
-
_slot_labels = {"a": "Draft A", "b": "Draft B"}
|
|
441
|
-
for _draft in _dual:
|
|
442
|
-
_s = (_draft.get("style") or "").strip()
|
|
443
|
-
if _s:
|
|
444
|
-
_label = _slot_labels.get(
|
|
445
|
-
(_draft.get("variant") or "").strip().lower(), "Draft"
|
|
446
|
-
)
|
|
447
|
-
lines.append(f"{_label} style: {_s}")
|
|
448
|
-
elif style:
|
|
434
|
+
if style:
|
|
449
435
|
lines.append(f"Style: {style}")
|
|
450
436
|
desc = (d.get("style_description") or "").strip()
|
|
451
437
|
if desc:
|
|
@@ -755,12 +741,6 @@ class _ReviewController(NSObject):
|
|
|
755
741
|
# timestamp of any hover still in progress (flushed on decision).
|
|
756
742
|
self._draft_hover_ms = {0: 0, 1: 0}
|
|
757
743
|
self._draft_hover_open = {}
|
|
758
|
-
# Slots the caret has actually been in this card (2026-07-10 follow-up):
|
|
759
|
-
# lets the decision distinguish "clicked into B, then came BACK to A"
|
|
760
|
-
# (an explicit head-to-head choice of A, per user) from "never touched
|
|
761
|
-
# B at all". Only the UNCHOSEN slot's membership matters at decision
|
|
762
|
-
# time; the selected slot is trivially visited.
|
|
763
|
-
self._draft_visited = set()
|
|
764
744
|
# Attention anchors for the unattended-review watchdog: the stack counts
|
|
765
745
|
# as "touched" on present, on any tracked interaction, and on any
|
|
766
746
|
# decision. No touch past the watchdog threshold = the user is not
|
|
@@ -1090,7 +1070,6 @@ class _ReviewController(NSObject):
|
|
|
1090
1070
|
self._selected_draft = None
|
|
1091
1071
|
self._draft_hover_ms = {0: 0, 1: 0}
|
|
1092
1072
|
self._draft_hover_open = {}
|
|
1093
|
-
self._draft_visited = set()
|
|
1094
1073
|
self._reason_field = None
|
|
1095
1074
|
content = NSView.alloc().initWithFrame_(NSMakeRect(0, 0, W, H))
|
|
1096
1075
|
|
|
@@ -1709,18 +1688,11 @@ class _ReviewController(NSObject):
|
|
|
1709
1688
|
except Exception:
|
|
1710
1689
|
return
|
|
1711
1690
|
for slot, cand_tv in (self._draft_textviews or {}).items():
|
|
1712
|
-
if cand_tv is
|
|
1713
|
-
continue
|
|
1714
|
-
# Visited even when it's already the selected slot: membership of
|
|
1715
|
-
# the eventually-UNCHOSEN slot is what _record reads, and that
|
|
1716
|
-
# slot only ever gets the caret via a deliberate user click (the
|
|
1717
|
-
# auto-focus seat in _render targets the selected slot only).
|
|
1718
|
-
self._draft_visited.add(slot)
|
|
1719
|
-
if slot != self._selected_draft:
|
|
1691
|
+
if cand_tv is tv and slot != self._selected_draft:
|
|
1720
1692
|
self._selected_draft = slot
|
|
1721
1693
|
self._textview = cand_tv
|
|
1722
1694
|
self._update_draft_borders()
|
|
1723
|
-
|
|
1695
|
+
break
|
|
1724
1696
|
|
|
1725
1697
|
@objc.python_method
|
|
1726
1698
|
def _update_draft_borders(self):
|
|
@@ -1807,10 +1779,6 @@ class _ReviewController(NSObject):
|
|
|
1807
1779
|
"unchosen_style": other.get("style") or None,
|
|
1808
1780
|
"hover_a_ms": int(self._draft_hover_ms.get(0, 0)),
|
|
1809
1781
|
"hover_b_ms": int(self._draft_hover_ms.get(1, 0)),
|
|
1810
|
-
# True = the caret was in the unchosen box at some point, i.e.
|
|
1811
|
-
# they tried the other draft and came back: an explicit choice
|
|
1812
|
-
# even when the winner is the preselected default.
|
|
1813
|
-
"visited_other": bool((1 - sel_idx) in self._draft_visited),
|
|
1814
1782
|
}
|
|
1815
1783
|
else:
|
|
1816
1784
|
orig = (d.get("reply_text") or "").strip()
|
|
@@ -403,13 +403,13 @@ DIAGNOSE_PROMPT_TEMPLATE = (
|
|
|
403
403
|
# were firing. False-positive-free: an idle queue has no pending job at all, so a
|
|
404
404
|
# quiet pipeline (no candidates) never trips this. Comfortably above the host
|
|
405
405
|
# scheduler's per-minute cadence + a slow claim.
|
|
406
|
-
AUTOPILOT_STALL_SECONDS =
|
|
406
|
+
AUTOPILOT_STALL_SECONDS = 180
|
|
407
407
|
|
|
408
408
|
# A job CLAIMED but never finished (sits in running/ this long) means a worker
|
|
409
409
|
# picked it up and then wedged mid-run (the claude -p drafting child died / never
|
|
410
410
|
# spawned). Generous enough that the longest real drafting turn never trips it.
|
|
411
411
|
# Keep in sync with RUNNING_STALL_SECONDS (scripts/autopilot_stall_watch.py).
|
|
412
|
-
AUTOPILOT_RUNNING_STALL_SECONDS =
|
|
412
|
+
AUTOPILOT_RUNNING_STALL_SECONDS = 900
|
|
413
413
|
|
|
414
414
|
# The "firing" window (how fresh lastRunAt must be) lives in the single source of
|
|
415
415
|
# truth, scripts/schedule_state.py (FIRING_WINDOW there). _schedule_state delegates
|
|
@@ -898,7 +898,7 @@ class S4LMenuBar(rumps.App):
|
|
|
898
898
|
to see between cycles) -> the ⚠ stays on continuously instead of
|
|
899
899
|
flickering off. This is the durable signal.
|
|
900
900
|
(2) FAST: a draft job has sat unclaimed in pending/ past
|
|
901
|
-
AUTOPILOT_STALL_SECONDS -> catches a fresh stall ~
|
|
901
|
+
AUTOPILOT_STALL_SECONDS -> catches a fresh stall ~3 min in, before
|
|
902
902
|
the first full producer timeout has even latched (1).
|
|
903
903
|
(3) IN-FLIGHT: a draft job was claimed (moved to running/) but never
|
|
904
904
|
finished within AUTOPILOT_RUNNING_STALL_SECONDS -> the worker picked
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m13v/s4l-mcp",
|
|
3
|
-
"version": "1.7.4-rc.
|
|
3
|
+
"version": "1.7.4-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.4-rc.
|
|
3
|
+
"version": "1.7.4-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",
|
|
@@ -160,4 +160,4 @@
|
|
|
160
160
|
"pg": "^8.20.0",
|
|
161
161
|
"ws": "^8.0.0"
|
|
162
162
|
}
|
|
163
|
-
}
|
|
163
|
+
}
|
|
@@ -40,39 +40,16 @@ ENV_PREFIX = "S4L_EXP_"
|
|
|
40
40
|
# Keep draft_prompt entries in sync with bin/server.js
|
|
41
41
|
# DRAFT_PROMPT_VARIANT_DEFS and the arm strings in run-twitter-cycle.sh.
|
|
42
42
|
DESCRIPTIONS = {
|
|
43
|
-
"draft_b_source": {
|
|
44
|
-
"human_derived": (
|
|
45
|
-
"Draft B explore slot: style distilled from real top-performing "
|
|
46
|
-
"human replies (daily synthesizer), least-used first"
|
|
47
|
-
),
|
|
48
|
-
"model_invented": (
|
|
49
|
-
"Draft B explore slot: freshly invented style from the "
|
|
50
|
-
"standalone invention job (post 2026-07-10), least-used first"
|
|
51
|
-
),
|
|
52
|
-
"scored_fallback": (
|
|
53
|
-
"Draft B explore pool was empty; fell back to a second scored "
|
|
54
|
-
"pick from the proven-style pool"
|
|
55
|
-
),
|
|
56
|
-
},
|
|
57
43
|
"draft_prompt": {
|
|
58
|
-
"treatment_v3": (
|
|
59
|
-
"style-as-form: the assigned style is the binding FORM (defining "
|
|
60
|
-
"move + per-style length + self-check), learned preferences apply "
|
|
61
|
-
"inside it; keeps the v2 skeleton ban"
|
|
62
|
-
),
|
|
63
|
-
"control_v3": (
|
|
64
|
-
"plain draft directive, uniform length clamp, no structure ban"
|
|
65
|
-
),
|
|
66
|
-
# v2 arms (skeleton-ban) retired 2026-07-10; v1 arms
|
|
67
|
-
# (decoupled-product-pivot) retired 2026-07-06; kept so any
|
|
68
|
-
# straggler card from an old plan still explains itself.
|
|
69
44
|
"treatment_v2": (
|
|
70
|
-
"
|
|
45
|
+
"skeleton ban: forbids the concede-then-reverse "
|
|
71
46
|
'"easy X / hard Y" structure and forces varied entry points'
|
|
72
47
|
),
|
|
73
48
|
"control_v2": (
|
|
74
|
-
"
|
|
49
|
+
"current draft directive (style + product pivot), no structure ban"
|
|
75
50
|
),
|
|
51
|
+
# v1 arms (decoupled-product-pivot) retired 2026-07-06; kept so any
|
|
52
|
+
# straggler card from an old plan still explains itself.
|
|
76
53
|
"treatment": "v1, retired: product pivot decoupled from the reply",
|
|
77
54
|
"control": "v1, retired: original draft directive",
|
|
78
55
|
},
|
|
@@ -122,9 +99,8 @@ def collect(env=None):
|
|
|
122
99
|
if var.startswith(ENV_PREFIX) and (v or "").strip():
|
|
123
100
|
out[var[len(ENV_PREFIX):].lower()] = v.strip()
|
|
124
101
|
# 2026-07-06: the personal_brand persona directive is now ARM-AWARE in
|
|
125
|
-
# run-twitter-cycle.sh (
|
|
126
|
-
#
|
|
127
|
-
# draft_prompt arm DOES touch persona
|
|
102
|
+
# run-twitter-cycle.sh (treatment_v2 adds the concede-then-reverse skeleton ban,
|
|
103
|
+
# control_v2 does not), so the assigned draft_prompt arm DOES touch persona
|
|
128
104
|
# drafts. Keep it stamped so the arm surfaces on persona cards and the per-arm
|
|
129
105
|
# readout covers both lanes. (Previously dropped here because the persona
|
|
130
106
|
# directive overrode both arms wholesale; that is no longer the case.)
|
|
@@ -47,13 +47,13 @@ import identity # noqa: E402 (lives next to this file in scripts/)
|
|
|
47
47
|
s4l_env.mirror()
|
|
48
48
|
|
|
49
49
|
# Keep in sync with AUTOPILOT_STALL_SECONDS (menubar) / AUTOPILOT_STALL_MS (index.ts).
|
|
50
|
-
STALL_SECONDS =
|
|
50
|
+
STALL_SECONDS = 180
|
|
51
51
|
# A job CLAIMED but never finished (sits in running/ this long) means a worker
|
|
52
52
|
# picked it up and then died mid-run — the claude -p drafting child never came up
|
|
53
53
|
# or crashed. Must be generous enough to clear the longest real drafting turn so a
|
|
54
54
|
# healthy run never trips it. Keep in sync with AUTOPILOT_RUNNING_STALL_SECONDS
|
|
55
55
|
# (menubar). See _oldest_running_age.
|
|
56
|
-
RUNNING_STALL_SECONDS =
|
|
56
|
+
RUNNING_STALL_SECONDS = 900
|
|
57
57
|
# Require the stall to persist this many consecutive checks before paging, so a
|
|
58
58
|
# transient slow claim (e.g. right after a Claude restart) doesn't false-alarm.
|
|
59
59
|
# At StartInterval 120 that is ~6 min of continuous stall.
|