social-autoposter 1.6.52 → 1.6.54
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 +49 -16
- package/mcp/dist/index.js +113 -0
- package/mcp/dist/panel.html +50 -14
- package/mcp/dist/repo.js +7 -3
- package/mcp/dist/runtime.js +271 -0
- package/mcp/dist/version.json +4 -1
- package/mcp/install-runtime.mjs +70 -0
- package/package.json +2 -1
- package/scripts/a16z_pearx_calendar_reminders.py +99 -0
- package/scripts/discover_linkedin_candidates.py +32 -0
- package/scripts/engagement_styles.py +9 -41
- package/scripts/github_tools.py +5 -3
- package/scripts/http_api.py +43 -1
- package/scripts/linkedin_exclusions.py +234 -0
- package/scripts/linkedin_killswitch.py +19 -1
- package/scripts/log_post.py +5 -9
- package/scripts/preflight.sh +6 -13
- package/scripts/scan_dm_candidates.py +16 -0
- package/scripts/score_linkedin_candidates.py +17 -0
- package/scripts/scrape_linkedin_comment_stats.py +16 -3
- package/scripts/seed_search_queries.py +303 -0
- package/scripts/setup_twitter_auth.py +99 -1
- package/scripts/twitter_post_plan.py +3 -66
- package/skill/run-instagram-render.sh +2 -2
- package/skill/run-twitter-cycle.sh +6 -38
package/bin/cli.js
CHANGED
|
@@ -520,31 +520,43 @@ function installBrowserHarness() {
|
|
|
520
520
|
}
|
|
521
521
|
|
|
522
522
|
// Step 2 + 3: clone + `uv tool install -e .` browser-harness.
|
|
523
|
+
//
|
|
524
|
+
// PINNED to a known-good upstream commit instead of tracking origin/HEAD.
|
|
525
|
+
// The installer used to fetch+reset --hard to HEAD on every run, so any
|
|
526
|
+
// upstream change shipped to users untested (this is how the two-blank-tab
|
|
527
|
+
// regression in upstream daemon.py attach behavior could reach users). Our
|
|
528
|
+
// launch-at-real-URL fix in server.py/twitter-backend.sh neutralizes that
|
|
529
|
+
// class of bug regardless, but pinning stops surprise upstream drift. Bump
|
|
530
|
+
// BROWSER_HARNESS_PIN deliberately after validating a newer upstream against
|
|
531
|
+
// the shipped server.py contract.
|
|
532
|
+
const BROWSER_HARNESS_PIN = '6d20866664ea3d9691b27bbf64f42ae097437dc3';
|
|
523
533
|
const harnessDir = path.join(HOME, 'Developer', 'browser-harness');
|
|
534
|
+
const pinHarness = () => {
|
|
535
|
+
// Fetch the exact pinned commit (GitHub serves arbitrary SHAs) and hard-
|
|
536
|
+
// reset onto it. Works for a fresh clone and an existing checkout alike.
|
|
537
|
+
const fetch = spawnSync('git', ['-C', harnessDir, 'fetch', '--depth', '1', 'origin', BROWSER_HARNESS_PIN], { stdio: 'inherit' });
|
|
538
|
+
if (fetch.status !== 0) {
|
|
539
|
+
console.warn(` WARNING: could not fetch pinned browser-harness commit ${BROWSER_HARNESS_PIN.slice(0, 9)}; using existing checkout.`);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const reset = spawnSync('git', ['-C', harnessDir, 'reset', '--hard', 'FETCH_HEAD'], { stdio: 'inherit' });
|
|
543
|
+
if (reset.status !== 0) {
|
|
544
|
+
console.warn(' WARNING: could not reset browser-harness clone to pinned commit; using existing checkout.');
|
|
545
|
+
}
|
|
546
|
+
};
|
|
524
547
|
if (!fs.existsSync(harnessDir)) {
|
|
525
548
|
fs.mkdirSync(path.dirname(harnessDir), { recursive: true });
|
|
526
549
|
console.log(' cloning browser-harness from GitHub...');
|
|
527
550
|
const clone = spawnSync('git', ['clone', '--depth', '1', 'https://github.com/browser-use/browser-harness', harnessDir], { stdio: 'inherit' });
|
|
528
551
|
if (clone.status !== 0) {
|
|
529
552
|
console.warn(' WARNING: git clone failed; twitter-harness will not work until you clone manually.');
|
|
530
|
-
}
|
|
531
|
-
} else {
|
|
532
|
-
// Refresh the existing clone instead of silently reusing it. server.py
|
|
533
|
-
// invokes `browser-harness -c <script>`; a stale checkout that predates the
|
|
534
|
-
// `-c` interface (or otherwise drifted from upstream) makes every bh_run
|
|
535
|
-
// return the CLI usage string while looking "installed". fetch+reset --hard
|
|
536
|
-
// to current upstream so the installed CLI always matches the shipped
|
|
537
|
-
// server.py contract.
|
|
538
|
-
console.log(` browser-harness clone exists -> ${harnessDir}; updating to latest...`);
|
|
539
|
-
const fetch = spawnSync('git', ['-C', harnessDir, 'fetch', '--depth', '1', 'origin', 'HEAD'], { stdio: 'inherit' });
|
|
540
|
-
if (fetch.status === 0) {
|
|
541
|
-
const reset = spawnSync('git', ['-C', harnessDir, 'reset', '--hard', 'FETCH_HEAD'], { stdio: 'inherit' });
|
|
542
|
-
if (reset.status !== 0) {
|
|
543
|
-
console.warn(' WARNING: could not reset browser-harness clone to latest; using existing checkout.');
|
|
544
|
-
}
|
|
545
553
|
} else {
|
|
546
|
-
console.
|
|
554
|
+
console.log(` pinning browser-harness to ${BROWSER_HARNESS_PIN.slice(0, 9)}...`);
|
|
555
|
+
pinHarness();
|
|
547
556
|
}
|
|
557
|
+
} else {
|
|
558
|
+
console.log(` browser-harness clone exists -> ${harnessDir}; pinning to ${BROWSER_HARNESS_PIN.slice(0, 9)}...`);
|
|
559
|
+
pinHarness();
|
|
548
560
|
}
|
|
549
561
|
|
|
550
562
|
if (uvBin && fs.existsSync(harnessDir)) {
|
|
@@ -1193,6 +1205,24 @@ function doctor() {
|
|
|
1193
1205
|
}
|
|
1194
1206
|
}
|
|
1195
1207
|
|
|
1208
|
+
// Provision the owned Python/Chromium runtime from the terminal. This is the
|
|
1209
|
+
// panel-free path: it runs the EXACT same provisioning logic the panel's
|
|
1210
|
+
// "Install runtime" button and the install_runtime MCP tool use (mcp/src/
|
|
1211
|
+
// runtime.ts -> dist/runtime.js), via the thin ESM wrapper mcp/install-runtime.mjs.
|
|
1212
|
+
// Use it when the UI panel can't render (Claude Code/Cowork), on a bare VM, or
|
|
1213
|
+
// when an agent wants to install head-less. Idempotent: re-running repairs.
|
|
1214
|
+
function installRuntime() {
|
|
1215
|
+
const wrapper = path.join(__dirname, '..', 'mcp', 'install-runtime.mjs');
|
|
1216
|
+
if (!fs.existsSync(wrapper)) {
|
|
1217
|
+
console.error(`Cannot find ${wrapper}. Re-run \`npx social-autoposter update\` to repair the install.`);
|
|
1218
|
+
process.exit(1);
|
|
1219
|
+
}
|
|
1220
|
+
// process.execPath is the Node already running this CLI, so we reuse it
|
|
1221
|
+
// rather than hunting for a node on PATH.
|
|
1222
|
+
const res = spawnSync(process.execPath, [wrapper], { stdio: 'inherit' });
|
|
1223
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1196
1226
|
const cmd = process.argv[2];
|
|
1197
1227
|
if (cmd === 'init') {
|
|
1198
1228
|
init();
|
|
@@ -1202,6 +1232,8 @@ if (cmd === 'init') {
|
|
|
1202
1232
|
doctor();
|
|
1203
1233
|
} else if (cmd === 'bootstrap-vm') {
|
|
1204
1234
|
bootstrapVm();
|
|
1235
|
+
} else if (cmd === 'install-runtime') {
|
|
1236
|
+
installRuntime();
|
|
1205
1237
|
} else if (cmd === 'export-cookies') {
|
|
1206
1238
|
// Forward to cookie-helper with 'export' + remaining args
|
|
1207
1239
|
process.argv = [process.argv[0], process.argv[1], 'export', ...process.argv.slice(3)];
|
|
@@ -1231,6 +1263,7 @@ if (cmd === 'init') {
|
|
|
1231
1263
|
console.log(' npx social-autoposter update update scripts, preserve config');
|
|
1232
1264
|
console.log(' npx social-autoposter doctor probe install health (#6, 1.6.34+)');
|
|
1233
1265
|
console.log(' npx social-autoposter bootstrap-vm AppMaker VM self-bootstrap (DB-driven)');
|
|
1266
|
+
console.log(' npx social-autoposter install-runtime provision owned Python + Chromium (panel-free)');
|
|
1234
1267
|
console.log(' npx social-autoposter export-cookies [dir] export browser cookies');
|
|
1235
1268
|
console.log(' npx social-autoposter import-cookies [dir] import browser cookies');
|
|
1236
1269
|
}
|
package/mcp/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import fs from "node:fs";
|
|
|
19
19
|
import { REPO_DIR, runPython, run, readPlan, writePlan, planPath, } from "./repo.js";
|
|
20
20
|
import { applySetup, resolveProject, hasReadyProject, listManagedProjectStatus, REQUIRED_FIELDS, RECOMMENDED_FIELDS, CONFIG_PATH, } from "./setup.js";
|
|
21
21
|
import { xStatus, xConnect, summarizeXAuth } from "./twitterAuth.js";
|
|
22
|
+
import { startProvisioning, isProvisioning, readProgress, runtimeReady, readRuntime, } from "./runtime.js";
|
|
22
23
|
import { VERSION, versionStatus, latestPublishedVersion } from "./version.js";
|
|
23
24
|
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server";
|
|
24
25
|
import { fileURLToPath } from "node:url";
|
|
@@ -526,6 +527,7 @@ server.registerTool("setup", {
|
|
|
526
527
|
projects,
|
|
527
528
|
x_connected: x.connected,
|
|
528
529
|
x_state: x.state,
|
|
530
|
+
x_handle: x.handle ?? null,
|
|
529
531
|
mcp_version: ver.installed,
|
|
530
532
|
latest_version: ver.latest,
|
|
531
533
|
update_available: ver.update_available,
|
|
@@ -950,6 +952,112 @@ server.registerTool("version", {
|
|
|
950
952
|
: "You are on the latest published version.",
|
|
951
953
|
});
|
|
952
954
|
});
|
|
955
|
+
// ---- runtime installer ----------------------------------------------------
|
|
956
|
+
// The pipeline runs Python locally. Rather than depend on the user's system
|
|
957
|
+
// Python (the #1 source of install failures), the first run provisions a fully
|
|
958
|
+
// OWNED uv runtime: standalone CPython + owned venv + deps + Chromium. These two
|
|
959
|
+
// tools drive it. They are plain (non-UI) tools so EVERY host can install — the
|
|
960
|
+
// panel's Install card is just a skin that calls install_runtime then polls
|
|
961
|
+
// install_status. See runtime.ts for the provisioning + progress contract.
|
|
962
|
+
function runtimeSnapshot() {
|
|
963
|
+
const rt = readRuntime();
|
|
964
|
+
const progress = readProgress();
|
|
965
|
+
return {
|
|
966
|
+
runtime_ready: runtimeReady(),
|
|
967
|
+
provisioning: isProvisioning(),
|
|
968
|
+
python: rt?.python ?? null,
|
|
969
|
+
python_version: rt?.python_version ?? null,
|
|
970
|
+
progress: progress ?? null,
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
server.registerTool("install_runtime", {
|
|
974
|
+
title: "Install the Python runtime",
|
|
975
|
+
description: "One-time setup that provisions the self-contained runtime the autoposter needs: a private " +
|
|
976
|
+
"Python (via uv, not your system Python), its dependencies, and the Chromium browser. Runs in " +
|
|
977
|
+
"the background and returns immediately; poll `install_status` for progress. Safe to call " +
|
|
978
|
+
"repeatedly; it resumes/repairs and is a no-op once everything is installed. Use this the " +
|
|
979
|
+
"first time the user sets up, or if other tools report the runtime isn't ready.",
|
|
980
|
+
inputSchema: {},
|
|
981
|
+
}, async () => {
|
|
982
|
+
if (runtimeReady()) {
|
|
983
|
+
return jsonContent({ already_installed: true, ...runtimeSnapshot() });
|
|
984
|
+
}
|
|
985
|
+
const progress = startProvisioning();
|
|
986
|
+
return jsonContent({
|
|
987
|
+
started: true,
|
|
988
|
+
runtime_ready: false,
|
|
989
|
+
note: "Runtime install started. Poll install_status every ~1.5s for progress.",
|
|
990
|
+
progress,
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
server.registerTool("install_status", {
|
|
994
|
+
title: "Runtime install status",
|
|
995
|
+
description: "Report whether the self-contained Python/Chromium runtime is installed and, if an install is " +
|
|
996
|
+
"in progress, the per-step progress (uv, Python, venv, dependencies, Chromium). Poll this after " +
|
|
997
|
+
"install_runtime to follow the install to completion.",
|
|
998
|
+
inputSchema: {},
|
|
999
|
+
}, async () => jsonContent(runtimeSnapshot()));
|
|
1000
|
+
// ---- config: read / edit the raw config.json ------------------------------
|
|
1001
|
+
// The panel renders the full config and lets the user edit it. Writing is
|
|
1002
|
+
// guarded: the new content must parse as JSON, and we always drop a timestamped
|
|
1003
|
+
// backup next to config.json before overwriting, so a bad paste is recoverable.
|
|
1004
|
+
server.registerTool("config", {
|
|
1005
|
+
title: "View or edit config.json",
|
|
1006
|
+
description: "Read or update the autoposter's config.json (the source of truth for every project, the X/" +
|
|
1007
|
+
"Reddit/LinkedIn account handles, topics, and exclusions). action:'get' (default) returns the " +
|
|
1008
|
+
"full raw JSON; action:'save' validates the supplied `content` as JSON, writes a timestamped " +
|
|
1009
|
+
"backup, then overwrites config.json. Use when the user asks to see, edit, or fix their config.",
|
|
1010
|
+
inputSchema: {
|
|
1011
|
+
action: z.enum(["get", "save"]).optional(),
|
|
1012
|
+
content: z.string().optional(),
|
|
1013
|
+
},
|
|
1014
|
+
}, async (args) => {
|
|
1015
|
+
const action = args.action || "get";
|
|
1016
|
+
if (action === "get") {
|
|
1017
|
+
try {
|
|
1018
|
+
const content = fs.readFileSync(CONFIG_PATH, "utf-8");
|
|
1019
|
+
return jsonContent({ ok: true, path: CONFIG_PATH, bytes: content.length, content });
|
|
1020
|
+
}
|
|
1021
|
+
catch (e) {
|
|
1022
|
+
return jsonContent({ ok: false, path: CONFIG_PATH, error: String(e?.message || e) });
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
// save
|
|
1026
|
+
const content = args.content;
|
|
1027
|
+
if (typeof content !== "string" || content.trim() === "") {
|
|
1028
|
+
return jsonContent({ ok: false, error: "Nothing to save: `content` was empty." });
|
|
1029
|
+
}
|
|
1030
|
+
let parsed;
|
|
1031
|
+
try {
|
|
1032
|
+
parsed = JSON.parse(content);
|
|
1033
|
+
}
|
|
1034
|
+
catch (e) {
|
|
1035
|
+
// Don't write a config that won't parse — every pipeline reads this file.
|
|
1036
|
+
return jsonContent({ ok: false, error: "Invalid JSON, not saved: " + String(e?.message || e) });
|
|
1037
|
+
}
|
|
1038
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1039
|
+
return jsonContent({ ok: false, error: "Top level of config.json must be a JSON object." });
|
|
1040
|
+
}
|
|
1041
|
+
try {
|
|
1042
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
1043
|
+
const backup = `${CONFIG_PATH}.bak-panel-${stamp}`;
|
|
1044
|
+
try {
|
|
1045
|
+
fs.copyFileSync(CONFIG_PATH, backup);
|
|
1046
|
+
}
|
|
1047
|
+
catch {
|
|
1048
|
+
/* first-write / missing original is non-fatal */
|
|
1049
|
+
}
|
|
1050
|
+
// Re-serialize the parsed object so what lands on disk is canonical,
|
|
1051
|
+
// 2-space-indented JSON with a trailing newline (matches the Python
|
|
1052
|
+
// writers), regardless of how the user formatted their paste.
|
|
1053
|
+
const out = JSON.stringify(parsed, null, 2) + "\n";
|
|
1054
|
+
fs.writeFileSync(CONFIG_PATH, out, "utf-8");
|
|
1055
|
+
return jsonContent({ ok: true, path: CONFIG_PATH, bytes: out.length, backup });
|
|
1056
|
+
}
|
|
1057
|
+
catch (e) {
|
|
1058
|
+
return jsonContent({ ok: false, error: "Write failed: " + String(e?.message || e) });
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
953
1061
|
// ---- panel: MCP Apps control surface --------------------------------------
|
|
954
1062
|
// A self-contained HTML view rendered by hosts that support MCP Apps (Claude
|
|
955
1063
|
// desktop/web, etc.). It duplicates NO pipeline logic: each button calls one of
|
|
@@ -990,11 +1098,16 @@ async function buildSnapshot() {
|
|
|
990
1098
|
projects_ready: projects.filter((p) => p.ready).length,
|
|
991
1099
|
x_connected: !!x.connected,
|
|
992
1100
|
x_state: x.state || "",
|
|
1101
|
+
x_handle: x.handle ?? null,
|
|
993
1102
|
autopilot_on: ap.autopilot_on,
|
|
994
1103
|
auto_update_on: ap.auto_update_on,
|
|
995
1104
|
version: ver.installed || VERSION,
|
|
996
1105
|
latest_version: ver.latest ?? null,
|
|
997
1106
|
update_available: !!ver.update_available,
|
|
1107
|
+
// Runtime install gate: the panel shows the Install card (and disables the
|
|
1108
|
+
// action buttons) until the owned Python/Chromium runtime is provisioned.
|
|
1109
|
+
runtime_ready: runtimeReady(),
|
|
1110
|
+
runtime_provisioning: isProvisioning(),
|
|
998
1111
|
};
|
|
999
1112
|
}
|
|
1000
1113
|
registerAppTool(server, "panel", {
|