social-autoposter 1.6.65 → 1.6.67
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 +13 -9
- package/bin/cli.js +33 -161
- package/mcp/dist/index.js +315 -61
- package/mcp/dist/onboarding.js +194 -0
- package/mcp/dist/panel.html +74 -29
- package/mcp/dist/runtime.js +185 -0
- package/mcp/dist/telemetry.js +104 -0
- package/mcp/dist/twitterAuth.js +16 -3
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +14 -2
- package/mcp/package.json +1 -0
- package/mcp-servers/browser-harness/server.py +102 -9
- package/package.json +2 -2
- package/scripts/setup_twitter_auth.py +215 -58
- package/scripts/twitter_cookie_mirror.py +9 -0
- package/setup/SKILL.md +197 -78
- package/skill/lock.sh +36 -6
- package/skill/run-twitter-cycle.sh +7 -1
package/README.md
CHANGED
|
@@ -45,15 +45,19 @@ npx social-autoposter update
|
|
|
45
45
|
|
|
46
46
|
## Configure
|
|
47
47
|
|
|
48
|
-
Tell your Claude
|
|
48
|
+
Tell your Claude agent: **"set me up on social-autoposter end to end"**. The
|
|
49
|
+
setup skill treats that as a terminal goal:
|
|
49
50
|
|
|
50
|
-
1.
|
|
51
|
-
2.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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.
|
|
58
|
+
|
|
59
|
+
The agent pauses only for an unavoidable login or when no product can be
|
|
60
|
+
identified. Autopilot remains off until explicitly requested.
|
|
57
61
|
|
|
58
62
|
## How the runtime is wired
|
|
59
63
|
|
|
@@ -113,7 +117,7 @@ social-autoposter/
|
|
|
113
117
|
├── bin/cli.js installer + dashboard launcher
|
|
114
118
|
├── browser-agent-configs/ Playwright MCP templates (twitter/reddit/linkedin)
|
|
115
119
|
├── config.example.json config template
|
|
116
|
-
├── setup/SKILL.md
|
|
120
|
+
├── setup/SKILL.md autonomous end-to-end setup skill (locked)
|
|
117
121
|
├── scripts/ Python and JS helpers (no browser, no LLM)
|
|
118
122
|
├── skill/ shell wrappers invoked by launchd
|
|
119
123
|
└── launchd/ generated macOS LaunchAgent plists
|
package/bin/cli.js
CHANGED
|
@@ -8,6 +8,8 @@ const { spawnSync } = require('child_process');
|
|
|
8
8
|
|
|
9
9
|
const platform = require('./platform');
|
|
10
10
|
const scheduler = require('./scheduler');
|
|
11
|
+
const { formatDoctorReport, runDoctorSync } = require('../mcp/shared/doctor.cjs');
|
|
12
|
+
const { recordDoctorReport } = require('../mcp/shared/onboarding-ledger.cjs');
|
|
11
13
|
|
|
12
14
|
const DEST = path.join(os.homedir(), 'social-autoposter');
|
|
13
15
|
const PKG_ROOT = path.join(__dirname, '..');
|
|
@@ -882,9 +884,9 @@ function init() {
|
|
|
882
884
|
|
|
883
885
|
console.log('');
|
|
884
886
|
console.log('Done! Next steps:');
|
|
885
|
-
console.log(' 1.
|
|
886
|
-
console.log(' 2. Tell your Claude agent: "set up social
|
|
887
|
-
console.log('
|
|
887
|
+
console.log(' 1. Fully quit and relaunch Claude so the MCP loads');
|
|
888
|
+
console.log(' 2. Tell your Claude agent: "set me up on social-autoposter end to end"');
|
|
889
|
+
console.log(' The agent will configure the product, connect X, seed topics, and verify a draft cycle');
|
|
888
890
|
console.log(' 3. Posts and all pipeline state sync via the s4l.ai HTTP API (no Postgres required)');
|
|
889
891
|
}
|
|
890
892
|
|
|
@@ -1060,165 +1062,35 @@ function removeLegacyEngagementStylesSidecar() {
|
|
|
1060
1062
|
}
|
|
1061
1063
|
}
|
|
1062
1064
|
|
|
1063
|
-
// `doctor`
|
|
1064
|
-
//
|
|
1065
|
-
//
|
|
1066
|
-
//
|
|
1067
|
-
//
|
|
1068
|
-
// non-zero if anything failed, so CI / setup wizards can gate on it.
|
|
1065
|
+
// `doctor` is a structured diagnostic engine shared with MCP onboarding.
|
|
1066
|
+
// Human-readable output remains the default; --json gives setup tools and CI a
|
|
1067
|
+
// stable machine-readable report. --phase pre_connect treats the not-yet-created
|
|
1068
|
+
// X session/cookie artifacts as expected, while full verifies the completed
|
|
1069
|
+
// environment after connect_x.
|
|
1069
1070
|
function doctor() {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
const
|
|
1073
|
-
const
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
add('uv tool on PATH', () => {
|
|
1084
|
-
const uv = findUvBin();
|
|
1085
|
-
if (!uv) return { ok: false, detail: 'uv not found', fix: 'curl -LsSf https://astral.sh/uv/install.sh | sh' };
|
|
1086
|
-
return { ok: true, detail: uv };
|
|
1087
|
-
});
|
|
1088
|
-
|
|
1089
|
-
add('browser-harness CLI installed', () => {
|
|
1090
|
-
const bh = path.join(HOME, '.local', 'bin', 'browser-harness');
|
|
1091
|
-
if (!fs.existsSync(bh)) return { ok: false, detail: `not found at ${bh}`, fix: 'npx social-autoposter init' };
|
|
1092
|
-
return { ok: true, detail: bh };
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
add('browser-harness CLI shape (stdin / -c)', () => {
|
|
1096
|
-
const bh = path.join(HOME, '.local', 'bin', 'browser-harness');
|
|
1097
|
-
if (!fs.existsSync(bh)) return { ok: false, detail: 'binary missing' };
|
|
1098
|
-
const probe = spawnSync(bh, [], { encoding: 'utf8', timeout: 15000 });
|
|
1099
|
-
const usage = `${probe.stdout || ''}${probe.stderr || ''}`;
|
|
1100
|
-
const dashC = /\b-c\b/.test(usage);
|
|
1101
|
-
const stdin = /<<'PY'|<<"PY"|<<PY\b/.test(usage);
|
|
1102
|
-
if (!dashC && !stdin) return { ok: false, detail: 'CLI advertises neither shape', fix: 'reinstall via npx social-autoposter init' };
|
|
1103
|
-
return { ok: true, detail: stdin ? 'stdin heredoc' : '-c flag' };
|
|
1104
|
-
});
|
|
1105
|
-
|
|
1106
|
-
add('macOS Keychain: Chrome Safe Storage readable', () => {
|
|
1107
|
-
if (process.platform !== 'darwin') return { ok: true, detail: 'skipped (non-macOS)' };
|
|
1108
|
-
const r = spawnSync('security', ['find-generic-password', '-s', 'Chrome Safe Storage', '-a', 'Chrome', '-w'], {
|
|
1109
|
-
encoding: 'utf8', timeout: 10000,
|
|
1110
|
-
});
|
|
1111
|
-
if (r.status === 0) return { ok: true, detail: 'accessible (cookie import will work)' };
|
|
1112
|
-
const tail = (r.stderr || '').trim().split('\n').slice(-1)[0] || `exit ${r.status}`;
|
|
1113
|
-
return {
|
|
1114
|
-
ok: false,
|
|
1115
|
-
detail: tail,
|
|
1116
|
-
fix: 'security unlock-keychain ~/Library/Keychains/login.keychain-db (then retry)',
|
|
1117
|
-
};
|
|
1118
|
-
});
|
|
1119
|
-
|
|
1120
|
-
add('harness Chrome on :9555', () => {
|
|
1121
|
-
try {
|
|
1122
|
-
const probe = spawnSync('curl', ['-sf', '--max-time', '2', '-o', '/dev/null', 'http://127.0.0.1:9555/json/version'], {
|
|
1123
|
-
encoding: 'utf8',
|
|
1124
|
-
});
|
|
1125
|
-
if (probe.status === 0) return { ok: true, detail: 'CDP responding' };
|
|
1126
|
-
return { ok: false, detail: 'no CDP on 9555', fix: 'will auto-launch on next cycle / connect_x call' };
|
|
1127
|
-
} catch (e) {
|
|
1128
|
-
return { ok: false, detail: e.message };
|
|
1129
|
-
}
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
add('X session in harness Chrome', () => {
|
|
1133
|
-
const setup = path.join(HOME, 'social-autoposter', 'scripts', 'setup_twitter_auth.py');
|
|
1134
|
-
if (!fs.existsSync(setup)) return { ok: false, detail: 'setup script missing' };
|
|
1135
|
-
const py = findPythonBin();
|
|
1136
|
-
const r = spawnSync(py, [setup, 'status'], { encoding: 'utf8', timeout: 60000 });
|
|
1137
|
-
let out;
|
|
1138
|
-
try { out = JSON.parse((r.stdout || '').trim()); } catch { out = null; }
|
|
1139
|
-
if (!out) return { ok: false, detail: 'status probe did not return JSON' };
|
|
1140
|
-
if (out.connected) return { ok: true, detail: `state=${out.state}` };
|
|
1141
|
-
return {
|
|
1142
|
-
ok: false,
|
|
1143
|
-
detail: `state=${out.state}`,
|
|
1144
|
-
fix: 'python3 ~/social-autoposter/scripts/setup_twitter_auth.py connect',
|
|
1145
|
-
};
|
|
1146
|
-
});
|
|
1147
|
-
|
|
1148
|
-
add('x.com cookies persisted to SQLite', () => {
|
|
1149
|
-
const cookiesDb = path.join(HOME, '.claude', 'browser-profiles', 'browser-harness', 'Default', 'Cookies');
|
|
1150
|
-
if (!fs.existsSync(cookiesDb)) return { ok: false, detail: `${cookiesDb} missing`, fix: 'connect_x will create it' };
|
|
1151
|
-
const py = findPythonBin();
|
|
1152
|
-
const r = spawnSync(py, ['-c',
|
|
1153
|
-
`import sqlite3; c=sqlite3.connect(${JSON.stringify(cookiesDb)}); ` +
|
|
1154
|
-
`print(c.execute("SELECT COUNT(*) FROM cookies WHERE host_key LIKE '%x.com' OR host_key LIKE '%twitter.com'").fetchone()[0])`,
|
|
1155
|
-
], { encoding: 'utf8', timeout: 10000 });
|
|
1156
|
-
const n = parseInt((r.stdout || '0').trim(), 10);
|
|
1157
|
-
if (n > 0) return { ok: true, detail: `${n} rows persisted (Chrome's encrypted store)` };
|
|
1158
|
-
return {
|
|
1159
|
-
ok: false,
|
|
1160
|
-
detail: '0 x.com rows in SQLite',
|
|
1161
|
-
fix: 'run setup_twitter_auth.py connect to import (durability is backed by the cookie mirror below)',
|
|
1162
|
-
};
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
// Gap B durability layer (1.6.35+): the keychain-independent local cookie
|
|
1166
|
-
// mirror is what survives a re-locked keychain wiping Chrome's encrypted
|
|
1167
|
-
// Cookies DB on relaunch. Read it directly (plaintext 0600 JSON).
|
|
1168
|
-
const mirrorPath = path.join(HOME, '.claude', 'browser-profiles', 'browser-harness.x-cookies.json');
|
|
1169
|
-
const mirrorCount = () => {
|
|
1170
|
-
try {
|
|
1171
|
-
const data = JSON.parse(fs.readFileSync(mirrorPath, 'utf8'));
|
|
1172
|
-
return Array.isArray(data.cookies) ? data.cookies.length : 0;
|
|
1173
|
-
} catch { return -1; }
|
|
1174
|
-
};
|
|
1175
|
-
|
|
1176
|
-
add('X cookie mirror (durable across keychain re-lock)', () => {
|
|
1177
|
-
const n = mirrorCount();
|
|
1178
|
-
if (n > 0) return { ok: true, detail: `${n} cookies mirrored — cycle preflight auto-restores after a wipe` };
|
|
1179
|
-
if (n === 0) return { ok: false, detail: 'mirror file present but empty', fix: 'run setup_twitter_auth.py connect to (re)populate the mirror' };
|
|
1180
|
-
return { ok: false, detail: `no mirror at ${mirrorPath}`, fix: 'run setup_twitter_auth.py connect (1.6.35+) to create the durable cookie mirror' };
|
|
1181
|
-
});
|
|
1182
|
-
|
|
1183
|
-
add('macOS Keychain: login keychain auto-lock', () => {
|
|
1184
|
-
if (process.platform !== 'darwin') return { ok: true, detail: 'skipped (non-macOS)' };
|
|
1185
|
-
const kc = path.join(HOME, 'Library', 'Keychains', 'login.keychain-db');
|
|
1186
|
-
const r = spawnSync('security', ['show-keychain-info', kc], { encoding: 'utf8', timeout: 10000 });
|
|
1187
|
-
const out = `${r.stdout || ''}${r.stderr || ''}`;
|
|
1188
|
-
const m = out.match(/timeout=(\d+)s/);
|
|
1189
|
-
if (!m) return { ok: true, detail: 'no auto-lock timeout (encrypted cookie store stays decryptable)' };
|
|
1190
|
-
const secs = parseInt(m[1], 10);
|
|
1191
|
-
// Only a real problem if the keychain re-locks AND the mirror isn't there to
|
|
1192
|
-
// cover the resulting Cookies-DB wipe. With a populated mirror this is benign.
|
|
1193
|
-
if (mirrorCount() > 0) {
|
|
1194
|
-
return { ok: true, detail: `auto-locks after ${secs}s, but the cookie mirror covers the relaunch-wipe case` };
|
|
1195
|
-
}
|
|
1196
|
-
return {
|
|
1197
|
-
ok: false,
|
|
1198
|
-
detail: `auto-locks after ${secs}s — Chrome's encrypted cookie store can wipe on relaunch with no mirror to restore from`,
|
|
1199
|
-
fix: `run connect_x to create the cookie mirror, or disable auto-lock: security set-keychain-settings "${kc}"`,
|
|
1200
|
-
};
|
|
1201
|
-
});
|
|
1202
|
-
|
|
1203
|
-
let pass = 0, fail = 0;
|
|
1204
|
-
for (const c of checks) {
|
|
1205
|
-
let res;
|
|
1206
|
-
try { res = c.runner(); } catch (e) { res = { ok: false, detail: e.message }; }
|
|
1207
|
-
if (res.ok) {
|
|
1208
|
-
console.log(` [OK] ${c.name}: ${res.detail || ''}`);
|
|
1209
|
-
pass++;
|
|
1210
|
-
} else {
|
|
1211
|
-
console.log(` [FAIL] ${c.name}: ${res.detail || ''}`);
|
|
1212
|
-
if (res.fix) console.log(` fix: ${res.fix}`);
|
|
1213
|
-
fail++;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
console.log(`\n${pass}/${checks.length} checks passed.`);
|
|
1218
|
-
if (fail > 0) {
|
|
1219
|
-
console.log('Address the failures above and re-run `npx social-autoposter doctor`.');
|
|
1220
|
-
process.exit(1);
|
|
1071
|
+
const args = process.argv.slice(3);
|
|
1072
|
+
const json = args.includes('--json');
|
|
1073
|
+
const phaseArg = args.find((arg) => arg.startsWith('--phase='));
|
|
1074
|
+
const phaseIndex = args.indexOf('--phase');
|
|
1075
|
+
const phase =
|
|
1076
|
+
(phaseArg && phaseArg.slice('--phase='.length)) ||
|
|
1077
|
+
(phaseIndex >= 0 ? args[phaseIndex + 1] : null) ||
|
|
1078
|
+
'full';
|
|
1079
|
+
if (!['pre_connect', 'full'].includes(phase)) {
|
|
1080
|
+
console.error("doctor: --phase must be 'pre_connect' or 'full'");
|
|
1081
|
+
process.exit(2);
|
|
1221
1082
|
}
|
|
1083
|
+
const report = runDoctorSync({
|
|
1084
|
+
phase,
|
|
1085
|
+
home: HOME,
|
|
1086
|
+
repoDir: fs.existsSync(DEST) ? DEST : PKG_ROOT,
|
|
1087
|
+
python: findPythonBin(),
|
|
1088
|
+
});
|
|
1089
|
+
// Doctor runs are durable even when invoked directly from the CLI. MCP uses
|
|
1090
|
+
// this same ledger, so a later onboarding session can show the historical run.
|
|
1091
|
+
recordDoctorReport(report);
|
|
1092
|
+
console.log(json ? JSON.stringify(report, null, 2) : formatDoctorReport(report));
|
|
1093
|
+
if (!report.ok) process.exitCode = 1;
|
|
1222
1094
|
}
|
|
1223
1095
|
|
|
1224
1096
|
// Provision the owned Python/Chromium runtime from the terminal. This is the
|
|
@@ -1298,7 +1170,7 @@ if (cmd === 'init') {
|
|
|
1298
1170
|
console.log(' npx social-autoposter open the dashboard');
|
|
1299
1171
|
console.log(' npx social-autoposter init first-time setup');
|
|
1300
1172
|
console.log(' npx social-autoposter update update scripts, preserve config');
|
|
1301
|
-
console.log(' npx social-autoposter doctor
|
|
1173
|
+
console.log(' npx social-autoposter doctor [--json] [--phase pre_connect|full]');
|
|
1302
1174
|
console.log(' npx social-autoposter bootstrap-vm AppMaker VM self-bootstrap (DB-driven)');
|
|
1303
1175
|
console.log(' npx social-autoposter install-runtime provision owned Python + Chromium (panel-free)');
|
|
1304
1176
|
console.log(' npx social-autoposter export-cookies [dir] export browser cookies');
|