social-autoposter 1.6.148 → 1.6.150

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/mcp/dist/index.js CHANGED
@@ -464,6 +464,14 @@ async function produceDrafts(project, onProgress) {
464
464
  await ensureOverlayWatch();
465
465
  let step = 0;
466
466
  let lastMsg = "";
467
+ // Granular scan progress for the menu-bar label. Phase 1 logs one
468
+ // `executing N queries` line (the total), then one `ok/err project=… kept=K`
469
+ // line per query. We count those to paint `scanning X · N/M · kept K` instead
470
+ // of a static "scanning X". Best-effort: missing total falls back to a plain
471
+ // count, and any parse miss just leaves the prior label up.
472
+ let scanTotal = 0;
473
+ let scanDone = 0;
474
+ let scanKept = 0;
467
475
  // ONE predictable, host-independent place to watch a draft_cycle run, so any
468
476
  // agent (or human) debugging "the cycle looks stuck" has an obvious path:
469
477
  // ~/social-autoposter/skill/logs/draft_cycle-mcp.log
@@ -505,6 +513,21 @@ async function produceDrafts(project, onProgress) {
505
513
  appendLog(`${t}\n`);
506
514
  console.error(`[draft_cycle] ${t}`);
507
515
  }
516
+ // Per-query scan progress -> granular menu-bar label. These lines only
517
+ // appear during Phase 1 (before 2b-prep), so they never fight the
518
+ // "drafting" label below.
519
+ let sm;
520
+ if ((sm = /executing (\d+) quer/.exec(t))) {
521
+ scanTotal = parseInt(sm[1], 10) || 0;
522
+ }
523
+ else if ((sm = /^\s*(?:ok|err)\s+project=/.exec(t))) {
524
+ scanDone += 1;
525
+ const km = /kept=(\d+)/.exec(t);
526
+ if (km)
527
+ scanKept += parseInt(km[1], 10) || 0;
528
+ const prog = scanTotal ? `${scanDone}/${scanTotal}` : `${scanDone}`;
529
+ writeActivity("scanning", `scanning X · ${prog} · kept ${scanKept}`);
530
+ }
508
531
  if (/Phase 2b-prep/.test(t))
509
532
  writeActivity("drafting", "drafting replies");
510
533
  if (!onProgress)
@@ -1014,9 +1037,9 @@ tool("engagement_mode", {
1014
1037
  "toggle.",
1015
1038
  inputSchema: {
1016
1039
  action: z
1017
- .enum(["get", "set"])
1040
+ .enum(["get", "set", "toggle"])
1018
1041
  .optional()
1019
- .describe("get = read current mode + persona status. set = record the user's chosen mode."),
1042
+ .describe("get = read current mode + persona status. set = record the user's chosen mode (provisions the persona). toggle = lightweight flip promotion<->personal_brand (mode.json only, no persona work) — the dashboard/menu-bar quick toggle."),
1020
1043
  mode: z
1021
1044
  .enum(["personal_brand", "promotion"])
1022
1045
  .optional()
@@ -1047,6 +1070,20 @@ tool("engagement_mode", {
1047
1070
  const persona = findPersonaProject();
1048
1071
  return jsonContent({ mode, persona: persona ? persona.name : null });
1049
1072
  }
1073
+ // Lightweight flip (the dashboard/menu-bar quick toggle): just rewrite
1074
+ // mode.json via saps_mode.py — NO persona provisioning. Mirrors the menu
1075
+ // bar's pure-local _toggle_mode so flipping from either surface is cheap.
1076
+ if (action === "toggle") {
1077
+ const cur = await runPython("scripts/saps_mode.py", ["get"], { timeoutMs: 15_000 });
1078
+ const now = (cur.stdout || "").trim() || "promotion";
1079
+ const next = now === "personal_brand" ? "promotion" : "personal_brand";
1080
+ const res = await runPython("scripts/saps_mode.py", ["set", next], { timeoutMs: 15_000 });
1081
+ if (res.code !== 0) {
1082
+ const tail = (res.stderr || res.stdout).trim().split("\n").slice(-1)[0] || "unknown error";
1083
+ return textContent(`Could not switch mode: ${tail}`);
1084
+ }
1085
+ return jsonContent({ mode: next });
1086
+ }
1050
1087
  const mode = args.mode;
1051
1088
  if (mode !== "personal_brand" && mode !== "promotion") {
1052
1089
  return textContent("Ask the user which they want, then call again with mode:'personal_brand' (organic brand " +
@@ -1412,6 +1449,7 @@ tool("project_config", {
1412
1449
  mcp_version: ver.installed,
1413
1450
  latest_version: ver.latest,
1414
1451
  update_available: ver.update_available,
1452
+ mode: currentMode(),
1415
1453
  update_hint: ver.update_available
1416
1454
  ? `A newer version (${ver.latest}) is available — you're on ${ver.installed}. ` +
1417
1455
  `Tell the user and offer to run the \`runtime\` tool with action:'update' ` +
@@ -2730,6 +2768,8 @@ async function buildSnapshot() {
2730
2768
  runtime_ready: rtReady,
2731
2769
  runtime_provisioning: isProvisioning(),
2732
2770
  setup_complete: setupComplete,
2771
+ // Engagement mode for display in BOTH surfaces (single source: mode.json).
2772
+ mode: currentMode(),
2733
2773
  onboarding: onboardingLive,
2734
2774
  };
2735
2775
  // Persist this snapshot so the menu bar can answer "set up?" the SAME way when
@@ -2892,6 +2932,18 @@ function modeChosen() {
2892
2932
  return false;
2893
2933
  }
2894
2934
  }
2935
+ // The current engagement mode ('promotion' | 'personal_brand'), surfaced in the
2936
+ // snapshot so the dashboard AND menu bar read it from ONE place (mode.json, the
2937
+ // same file saps_mode.py writes). Defaults to promotion when unset.
2938
+ function currentMode() {
2939
+ try {
2940
+ const m = (JSON.parse(fs.readFileSync(path.join(sapsStateDir(), "mode.json"), "utf-8")).mode || "").trim();
2941
+ return m === "personal_brand" ? "personal_brand" : "promotion";
2942
+ }
2943
+ catch {
2944
+ return "promotion";
2945
+ }
2946
+ }
2895
2947
  // ---- Cross-instance "posting active" flag ----------------------------------
2896
2948
  // posting-active.json in the shared state dir is the CROSS-MCP-INSTANCE version
2897
2949
  // of the in-process `postingActive` flag. The autopilot scan and the post