social-autoposter 1.6.35 → 1.6.36
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/bin/cli.js +30 -3
- package/mcp/dist/index.js +35 -0
- package/mcp/dist/panel.html +12 -11
- package/package.json +1 -1
- package/scripts/classify_run_error.py +13 -0
- package/scripts/restore_twitter_session.py +10 -9
- package/skill/run-twitter-cycle.sh +32 -5
package/bin/cli.js
CHANGED
|
@@ -214,6 +214,30 @@ function isAppMakerVm() {
|
|
|
214
214
|
return probe.status === 0;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
// VM / AppMaker support is strictly opt-in. A normal `init`/`update` (the
|
|
218
|
+
// macOS user path) installs none of it — no apt-get, no :9222 CDP env file, no
|
|
219
|
+
// AppMaker MCP port overrides. It activates only when explicitly requested:
|
|
220
|
+
// - env SA_VM=1 (or SOCIAL_AUTOPOSTER_VM=1)
|
|
221
|
+
// - flag --vm on the command line
|
|
222
|
+
// - a persisted marker written by `bootstrap-vm` (so later `update`s on the
|
|
223
|
+
// same VM stay in VM mode without re-passing the flag)
|
|
224
|
+
// - a genuine AppMaker VM (linux + /opt/startup.sh + live :9222) — kept as a
|
|
225
|
+
// fallback so the existing mk0r bootstrap keeps working untouched. This can
|
|
226
|
+
// never be true on a user's Mac.
|
|
227
|
+
const VM_MARKER = path.join(HOME, '.social-autoposter', 'vm-mode');
|
|
228
|
+
function vmModeEnabled() {
|
|
229
|
+
if (process.env.SA_VM === '1' || process.env.SOCIAL_AUTOPOSTER_VM === '1') return true;
|
|
230
|
+
if (process.argv.includes('--vm')) return true;
|
|
231
|
+
try { if (fs.existsSync(VM_MARKER)) return true; } catch { /* ignore */ }
|
|
232
|
+
return isAppMakerVm();
|
|
233
|
+
}
|
|
234
|
+
function enableVmMode() {
|
|
235
|
+
try {
|
|
236
|
+
fs.mkdirSync(path.dirname(VM_MARKER), { recursive: true });
|
|
237
|
+
fs.writeFileSync(VM_MARKER, 'enabled\n');
|
|
238
|
+
} catch { /* best-effort; vmModeEnabled() still honors env/flag/probe */ }
|
|
239
|
+
}
|
|
240
|
+
|
|
217
241
|
// Write ~/.social-autoposter-env so skill/lib/twitter-backend.sh picks up the
|
|
218
242
|
// AppMaker-specific TWITTER_CDP_URL before its `${VAR:-default}` fallback hits.
|
|
219
243
|
// Idempotent: rewrites the file every invocation so a config edit on the VM
|
|
@@ -375,6 +399,9 @@ function bootstrapVm() {
|
|
|
375
399
|
console.error('bootstrap-vm: not an AppMaker VM (no /opt/startup.sh + CDP :9222). Use `init` or `update` on dev boxes.');
|
|
376
400
|
process.exit(2);
|
|
377
401
|
}
|
|
402
|
+
// Persist VM mode so subsequent `update` runs on this sandbox keep the
|
|
403
|
+
// AppMaker tweaks without re-passing --vm/SA_VM.
|
|
404
|
+
enableVmMode();
|
|
378
405
|
console.log(' AppMaker VM bootstrap: resolving identity from DB by sessionKey...');
|
|
379
406
|
|
|
380
407
|
let sessionKey;
|
|
@@ -471,7 +498,7 @@ function bootstrapVm() {
|
|
|
471
498
|
}
|
|
472
499
|
|
|
473
500
|
function installBrowserHarness() {
|
|
474
|
-
const onAppMaker =
|
|
501
|
+
const onAppMaker = vmModeEnabled();
|
|
475
502
|
if (onAppMaker) {
|
|
476
503
|
console.log(' AppMaker VM detected -> installing harness toolchain (deps); MCP will be pointed at port 9222');
|
|
477
504
|
writeAppMakerEnvFile();
|
|
@@ -803,7 +830,7 @@ function init() {
|
|
|
803
830
|
installBrowserAgentConfigs();
|
|
804
831
|
// On AppMaker VMs, patch the twitter-harness MCP config so its server.py
|
|
805
832
|
// drives port 9222 (AppMaker Chromium) instead of the default 9555.
|
|
806
|
-
if (
|
|
833
|
+
if (vmModeEnabled()) applyAppMakerMcpConfigOverrides();
|
|
807
834
|
// Register those MCP servers with Claude so they show up in `claude mcp list`.
|
|
808
835
|
registerBrowserAgentMcpServers();
|
|
809
836
|
|
|
@@ -891,7 +918,7 @@ function update() {
|
|
|
891
918
|
installBrowserAgentConfigs();
|
|
892
919
|
// On AppMaker VMs, patch the twitter-harness MCP config so its server.py
|
|
893
920
|
// drives port 9222 (AppMaker Chromium) instead of the default 9555.
|
|
894
|
-
if (
|
|
921
|
+
if (vmModeEnabled()) applyAppMakerMcpConfigOverrides();
|
|
895
922
|
// Register any newly added MCP servers with Claude (idempotent).
|
|
896
923
|
registerBrowserAgentMcpServers();
|
|
897
924
|
|
package/mcp/dist/index.js
CHANGED
|
@@ -131,6 +131,34 @@ function jsonContent(obj) {
|
|
|
131
131
|
function textContent(text) {
|
|
132
132
|
return { content: [{ type: "text", text }] };
|
|
133
133
|
}
|
|
134
|
+
// Map a pipeline failure-reason key (from scripts/classify_run_error.py, emitted
|
|
135
|
+
// by run-twitter-cycle.sh as `DRAFT_ONLY_BLOCKED=<reason>`) to a clear,
|
|
136
|
+
// actionable message. The most common one on a fresh machine is
|
|
137
|
+
// claude_not_logged_in: the background `claude` CLI the pipeline shells out to
|
|
138
|
+
// has its OWN login, separate from Claude Desktop, so it can be logged out even
|
|
139
|
+
// though this MCP host is signed in. Without this, an auth failure was silently
|
|
140
|
+
// reported as a benign empty cycle ("all threads already engaged").
|
|
141
|
+
function blockedReasonMessage(reason) {
|
|
142
|
+
switch (reason) {
|
|
143
|
+
case "claude_not_logged_in":
|
|
144
|
+
return ("The background Claude CLI on this machine isn't logged in, so the drafting step " +
|
|
145
|
+
"couldn't run. (It DID find and rank threads, it just couldn't draft replies.) This " +
|
|
146
|
+
"CLI uses its own login, separate from Claude Desktop. To fix it, open a terminal and run:\n\n" +
|
|
147
|
+
" claude\n\n" +
|
|
148
|
+
"then `/login` inside it (or run `claude setup-token`). Once it's logged in, run draft_cycle again.");
|
|
149
|
+
case "monthly_limit":
|
|
150
|
+
case "daily_limit":
|
|
151
|
+
case "rate_limit_5h":
|
|
152
|
+
return (`The drafting step hit an Anthropic usage limit (${reason}), so no replies were drafted. ` +
|
|
153
|
+
"Wait for the limit to reset, then run draft_cycle again.");
|
|
154
|
+
case "credit_balance":
|
|
155
|
+
return ("The drafting step failed because the Anthropic account is out of credits. " +
|
|
156
|
+
"Add credits, then run draft_cycle again.");
|
|
157
|
+
default:
|
|
158
|
+
return (`The drafting step failed (${reason}) and produced no drafts. ` +
|
|
159
|
+
"Check skill/logs/twitter-cycle-*.log on this machine for details, then run draft_cycle again.");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
134
162
|
async function produceDrafts(project) {
|
|
135
163
|
// Run the real pipeline in DRAFT_ONLY mode: scan -> score -> draft -> link-gen,
|
|
136
164
|
// then STOP before posting. The script prints `DRAFT_ONLY_PLAN=<path>` and
|
|
@@ -150,6 +178,13 @@ async function produceDrafts(project) {
|
|
|
150
178
|
const marker = /DRAFT_ONLY_PLAN=\/tmp\/twitter_cycle_plan_(.+)\.json/.exec(res.stdout + "\n" + res.stderr);
|
|
151
179
|
if (marker && marker[1])
|
|
152
180
|
return { batchId: marker[1] };
|
|
181
|
+
// A real prep-step failure (e.g. the background claude CLI isn't logged in)
|
|
182
|
+
// emits DRAFT_ONLY_BLOCKED=<reason>. Surface that instead of silently falling
|
|
183
|
+
// back to a stale/empty batch and mis-reporting "no fresh candidates".
|
|
184
|
+
const blockedMarker = /DRAFT_ONLY_BLOCKED=([a-z0-9_]+)/.exec(res.stdout + "\n" + res.stderr);
|
|
185
|
+
if (blockedMarker && blockedMarker[1]) {
|
|
186
|
+
return { batchId: null, blocked: blockedReasonMessage(blockedMarker[1]) };
|
|
187
|
+
}
|
|
153
188
|
const existing = latestBatchId();
|
|
154
189
|
if (existing)
|
|
155
190
|
return { batchId: existing };
|