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 CHANGED
@@ -45,15 +45,19 @@ npx social-autoposter update
45
45
 
46
46
  ## Configure
47
47
 
48
- Tell your Claude Code agent: **"set up social autoposter"**. The interactive wizard in `setup/SKILL.md` walks through:
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. Verifying the S4L API connection
51
- 2. Filling in `~/social-autoposter/config.json` with handles for Reddit, Twitter, LinkedIn, optional Moltbook
52
- 3. A 5-question interview to draft your `content_angle`
53
- 4. Capturing `projects` with `topics` (used by the tiered reply strategy)
54
- 5. Verifying browser logins per platform via the dedicated MCP agent. The first time each platform runs you'll be asked to log in once; cookies persist into the userDataDir under `~/.claude/browser-profiles/`.
55
- 6. A dry-run of `find_threads.py --limit 3`
56
- 7. Optional: loading the launchd plists into `~/Library/LaunchAgents/`
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 interactive setup wizard skill (locked)
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. Edit ~/social-autoposter/config.json with your accounts');
886
- console.log(' 2. Tell your Claude agent: "set up social autoposter"');
887
- console.log(' (uses the setup/SKILL.md wizard for browser login verification)');
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` (#6, added 2026-06-02) single command that probes every known
1064
- // failure mode of the install so the user can SEE what's broken instead of
1065
- // learning about it via "Phase 1 returned 0 tweets" or "needs_login" with a
1066
- // silent keychain failure underneath. Each check returns either ok=true or a
1067
- // {ok:false, detail, fix} record. We print a green/red checklist and exit
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
- console.log('social-autoposter doctor probing install health\n');
1071
-
1072
- const checks = [];
1073
- const add = (name, runner) => checks.push({ name, runner });
1074
-
1075
- add('Node.js on PATH', () => ({ ok: true, detail: process.version }));
1076
-
1077
- add('python3 on PATH', () => {
1078
- const r = spawnSync('python3', ['--version'], { encoding: 'utf8' });
1079
- if (r.status === 0) return { ok: true, detail: (r.stdout || r.stderr).trim() };
1080
- return { ok: false, detail: 'python3 not found', fix: 'install Python 3 (brew install python3 / xcode-select --install)' };
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 probe install health (#6, 1.6.34+)');
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');