social-autoposter 1.6.72 → 1.6.73
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 +70 -29
- package/mcp/dist/version.js +8 -5
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +11 -3
- package/mcp/package.json +1 -1
- package/package.json +1 -1
- package/scripts/release-mcpb.sh +29 -0
package/mcp/dist/index.js
CHANGED
|
@@ -30,6 +30,11 @@ import http from "node:http";
|
|
|
30
30
|
// (vite-plugin-singlefile) into dist/panel.html alongside this compiled file.
|
|
31
31
|
const DIST_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
32
32
|
const PANEL_URI = "ui://social-autoposter/panel.html";
|
|
33
|
+
// Stable id for the accumulating draft review queue. submit_drafts appends each
|
|
34
|
+
// run's drafts here (dedup by tweet URL) so the menu-bar cards PILE UP across a
|
|
35
|
+
// continuous autopilot instead of each run overwriting the last; post_drafts posts
|
|
36
|
+
// the approved subset and marks them posted (filtered out of the cards thereafter).
|
|
37
|
+
const REVIEW_QUEUE_ID = "review-queue";
|
|
33
38
|
const TWITTER_AUTOPILOT_LABEL = "com.m13v.social-twitter-cycle";
|
|
34
39
|
const TWITTER_AUTOPILOT_PLIST = path.join(os.homedir(), "Library", "LaunchAgents", `${TWITTER_AUTOPILOT_LABEL}.plist`);
|
|
35
40
|
// Daily self-updater. Enabled alongside autopilot so a hands-free (headless)
|
|
@@ -152,8 +157,9 @@ const server = new McpServer({
|
|
|
152
157
|
"ONBOARDING IS A TERMINAL GOAL. When the user asks to set up, install, configure, or onboard " +
|
|
153
158
|
"social-autoposter, keep taking the next safe action until the owned runtime is ready, a " +
|
|
154
159
|
"project is fully configured with seeded search topics, X is connected with its real handle, " +
|
|
155
|
-
"
|
|
156
|
-
"without posting
|
|
160
|
+
"the draft path (`scan_candidates` -> draft -> `submit_drafts`) has verified the pipeline " +
|
|
161
|
+
"without posting, AND the recurring autopilot scheduled task has been created and run once " +
|
|
162
|
+
"(see SCHEDULE THE AUTOPILOT below). Do not ask whether to inspect " +
|
|
157
163
|
"status, install or repair owned dependencies, choose an auto-detected browser profile, connect " +
|
|
158
164
|
"X, scan the profile, research the product website, save conservative inferred fields, seed " +
|
|
159
165
|
"topics, retry a recoverable failure, or run draft-only verification. Do those things. The " +
|
|
@@ -161,12 +167,24 @@ const server = new McpServer({
|
|
|
161
167
|
"twitter.com session cookies into the managed browser: warn that macOS keychain prompts may " +
|
|
162
168
|
"appear, then proceed without a separate yes/no turn. Ask at most one bundled question, only " +
|
|
163
169
|
"when no product can be identified from config, context, the X profile, or public research, " +
|
|
164
|
-
"or pause for an unavoidable interactive login or OS prompt. Never
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
"
|
|
170
|
+
"or pause for an unavoidable interactive login or OS prompt. Never auto-POST during setup, but " +
|
|
171
|
+
"DO schedule the draft-only autopilot as the final step (it only queues drafts for approval, it " +
|
|
172
|
+
"never posts on its own). When setup reaches done (" +
|
|
173
|
+
"runtime ready, a project configured with seeded topics, X connected, the draft path verified, " +
|
|
174
|
+
"and the autopilot scheduled task created and run once), call the `dashboard` tool so the user " +
|
|
168
175
|
"sees the finished setup rendered, then give " +
|
|
169
176
|
"the completion summary.\n\n" +
|
|
177
|
+
"SCHEDULE THE AUTOPILOT (final onboarding step). Once the draft path is verified, start the " +
|
|
178
|
+
"autopilot by calling the HOST tool `create_scheduled_task` with: taskId " +
|
|
179
|
+
"'social-autoposter-autopilot'; cronExpression '* * * * *' (continuous, like the pipeline; the " +
|
|
180
|
+
"host runs one at a time and skips overlapping fires, so cycles run back-to-back); and a " +
|
|
181
|
+
"self-contained prompt telling the run to call scan_candidates, draft ONE on-brand reply " +
|
|
182
|
+
"(<=250 chars, same language as the thread, adds real value) for each candidate worth engaging, " +
|
|
183
|
+
"then call submit_drafts with those drafts, and NOT post (drafts pile up in the menu-bar " +
|
|
184
|
+
"approval cards for the user to approve). Then run ONE cycle yourself now (scan_candidates -> " +
|
|
185
|
+
"draft -> submit_drafts) to seed the cards and pre-approve the tools, so unattended fires do not " +
|
|
186
|
+
"stall on a permission prompt. If the host exposes no create_scheduled_task tool, tell the user " +
|
|
187
|
+
"the autopilot could not be scheduled here and to run a draft cycle manually instead.\n\n" +
|
|
170
188
|
"BE PROACTIVE ABOUT MARKETING MOMENTS. Whenever the user ships, finishes, merges, or releases " +
|
|
171
189
|
"something worth talking about in this session (a new feature, a launch, a long-awaited fix, a " +
|
|
172
190
|
"milestone), don't wait to be asked: offer to tell the world by running a draft cycle " +
|
|
@@ -469,8 +487,11 @@ async function produceDrafts(project, onProgress) {
|
|
|
469
487
|
function renderDraftsTable(plan) {
|
|
470
488
|
const candidates = plan.candidates || [];
|
|
471
489
|
return candidates
|
|
472
|
-
|
|
473
|
-
|
|
490
|
+
// Number by FULL-array index (matches post_drafts + the menu bar), then drop
|
|
491
|
+
// already-posted entries so the cards only show what's still pending.
|
|
492
|
+
.map((c, i) => ({ c, n: i + 1 }))
|
|
493
|
+
.filter((e) => e.c.posted !== true)
|
|
494
|
+
.map(({ c, n }) => {
|
|
474
495
|
const author = c.thread_author ? `@${c.thread_author}` : "(unknown thread)";
|
|
475
496
|
const style = c.engagement_style ?? "?";
|
|
476
497
|
const reply = c.reply_text ?? "(empty)";
|
|
@@ -922,7 +943,7 @@ tool("project_config", {
|
|
|
922
943
|
(x.connected ? "" : " X is not connected yet either — detect_x_sources, warn about keychain prompts, then run connect_x with confirm:true without a separate permission turn.")
|
|
923
944
|
: projects.every((p) => p.ready)
|
|
924
945
|
? (x.connected
|
|
925
|
-
? "All configured projects are ready and X is connected. Run scan_candidates, draft a reply or two, and submit_drafts now to verify end to end without posting.
|
|
946
|
+
? "All configured projects are ready and X is connected. Run scan_candidates, draft a reply or two, and submit_drafts now to verify end to end without posting. Then SCHEDULE THE AUTOPILOT: call create_scheduled_task (taskId 'social-autoposter-autopilot', cron '* * * * *', prompt = scan_candidates -> draft -> submit_drafts, draft-only) and run one cycle now. Then call the `dashboard` tool so the user sees the finished setup."
|
|
926
947
|
: "All configured projects are ready, but X is NOT connected — posting needs a logged-in " +
|
|
927
948
|
"x.com session. Detect sources and run project_config action:'connect_x', confirm:true; do not ask whether to proceed.")
|
|
928
949
|
: "Some projects are missing required fields (see each project's missing_required). Derive them from config, context, profile_scan, and website research, then call project_config again. Ask only if a required field is genuinely unknowable." +
|
|
@@ -1034,8 +1055,9 @@ tool("project_config", {
|
|
|
1034
1055
|
note: (result.ready
|
|
1035
1056
|
? `Project '${result.project}' is fully configured.${seedNote} Next: if X is not connected, ` +
|
|
1036
1057
|
`detect sources, warn about keychain prompts, and call project_config with ` +
|
|
1037
|
-
`action:'connect_x', confirm:true immediately. Once X is connected, run scan_candidates -> submit_drafts
|
|
1038
|
-
`verify without posting
|
|
1058
|
+
`action:'connect_x', confirm:true immediately. Once X is connected, run scan_candidates -> submit_drafts ` +
|
|
1059
|
+
`to verify without posting, then schedule the draft-only autopilot (create_scheduled_task, cron '* * * * *', ` +
|
|
1060
|
+
`prompt = scan_candidates -> draft -> submit_drafts) and run one cycle now.`
|
|
1039
1061
|
: `Saved what you provided for '${result.project}'. Still need: ${result.missing_required.join(", ")}. ` +
|
|
1040
1062
|
`First derive those fields from existing context, profile_scan, and website research, then ` +
|
|
1041
1063
|
`call project_config again with name='${result.project}'. Ask only if a required field is genuinely unknowable.`) +
|
|
@@ -1719,11 +1741,10 @@ tool("submit_drafts", {
|
|
|
1719
1741
|
const project = (candidates.map((c) => c.matched_project).find((p) => !!p) ||
|
|
1720
1742
|
firstSc?.matched_project ||
|
|
1721
1743
|
"default");
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
//
|
|
1725
|
-
|
|
1726
|
-
// falls back to the plain project URL per-candidate if this is skipped.
|
|
1744
|
+
// Stage the new drafts under the scan batch id and bake link targets into them
|
|
1745
|
+
// (sub-second at TWITTER_PAGE_GEN_RATE=0). Best-effort: posting falls back to the
|
|
1746
|
+
// plain project URL per-candidate if gen is skipped.
|
|
1747
|
+
writePlan(args.batch_id, { candidates });
|
|
1727
1748
|
try {
|
|
1728
1749
|
await runPython("scripts/twitter_gen_links.py", ["--plan", planPath(args.batch_id)], {
|
|
1729
1750
|
timeoutMs: 120_000,
|
|
@@ -1733,25 +1754,45 @@ tool("submit_drafts", {
|
|
|
1733
1754
|
catch {
|
|
1734
1755
|
/* best effort — plan still posts with a plain-URL fallback */
|
|
1735
1756
|
}
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1757
|
+
const staged = readPlan(args.batch_id)?.candidates ?? candidates;
|
|
1758
|
+
// Accumulate into ONE persistent review queue so a continuous autopilot's drafts
|
|
1759
|
+
// PILE UP in the menu-bar cards instead of each run overwriting the last. New
|
|
1760
|
+
// drafts are appended; a thread already in the queue (by URL) is skipped (one
|
|
1761
|
+
// draft per thread). Posted entries are KEPT in place so the 1-based card
|
|
1762
|
+
// numbering stays stable across runs — the menu bar, the chat table, and
|
|
1763
|
+
// post_drafts all index the full array and filter on the `posted` flag.
|
|
1764
|
+
const queue = [
|
|
1765
|
+
...(readPlan(REVIEW_QUEUE_ID)?.candidates ?? []),
|
|
1766
|
+
];
|
|
1767
|
+
const seen = new Set(queue.map((c) => c.candidate_url).filter((u) => !!u));
|
|
1768
|
+
let added = 0;
|
|
1769
|
+
for (const nc of staged) {
|
|
1770
|
+
if (nc.candidate_url && seen.has(nc.candidate_url))
|
|
1771
|
+
continue;
|
|
1772
|
+
queue.push(nc);
|
|
1773
|
+
if (nc.candidate_url)
|
|
1774
|
+
seen.add(nc.candidate_url);
|
|
1775
|
+
added++;
|
|
1776
|
+
}
|
|
1777
|
+
writePlan(REVIEW_QUEUE_ID, { candidates: queue });
|
|
1778
|
+
const pending = queue.filter((c) => c.posted !== true);
|
|
1738
1779
|
// Drafts queued = the pipeline verified end-to-end without posting. This is the
|
|
1739
1780
|
// onboarding "draft_verified" terminal goal (formerly completed by draft_cycle).
|
|
1740
|
-
if (
|
|
1741
|
-
completeOnboardingMilestone("draft_verified", { outcome: "review_batch", draft_count:
|
|
1742
|
-
//
|
|
1781
|
+
if (added > 0)
|
|
1782
|
+
completeOnboardingMilestone("draft_verified", { outcome: "review_batch", draft_count: added });
|
|
1783
|
+
// Point the menu-bar review cards at the accumulated queue.
|
|
1743
1784
|
writeReviewRequest({
|
|
1744
|
-
batch_id:
|
|
1785
|
+
batch_id: REVIEW_QUEUE_ID,
|
|
1745
1786
|
project,
|
|
1746
|
-
count,
|
|
1747
|
-
plan_path: planPath(
|
|
1787
|
+
count: pending.length,
|
|
1788
|
+
plan_path: planPath(REVIEW_QUEUE_ID),
|
|
1748
1789
|
created_at: new Date().toISOString(),
|
|
1749
1790
|
});
|
|
1750
|
-
return textContent(
|
|
1751
|
-
`
|
|
1752
|
-
renderDraftsTable(
|
|
1753
|
-
`\n\nTo post
|
|
1754
|
-
`numbers to post.`);
|
|
1791
|
+
return textContent(`Queued ${added} new draft(s); ${pending.length} now awaiting approval in the menu-bar cards ` +
|
|
1792
|
+
`(review queue "${REVIEW_QUEUE_ID}"). Nothing posts until approved.\n\n` +
|
|
1793
|
+
renderDraftsTable({ candidates: queue }) +
|
|
1794
|
+
`\n\nTo post: the user approves in the menu bar, or call post_drafts with batch_id ` +
|
|
1795
|
+
`"${REVIEW_QUEUE_ID}" and the numbers to post.`);
|
|
1755
1796
|
});
|
|
1756
1797
|
appTool("dashboard", {
|
|
1757
1798
|
title: "Social Autoposter dashboard",
|
package/mcp/dist/version.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
//
|
|
3
3
|
// The "real" version is the top-level `social-autoposter` npm package version
|
|
4
4
|
// (e.g. 1.6.x) — that is what actually bundles this MCP's prebuilt dist/. The
|
|
5
|
-
// MCP's own package.json
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// MCP's own package.json and manifest are stamped to the same version at release
|
|
6
|
+
// time (scripts/release-mcpb.sh step 3b), but historically they were frozen at
|
|
7
|
+
// 0.0.1, so this module still resolves the true version from the most
|
|
8
|
+
// authoritative source available at runtime (and tolerates a stale co-located
|
|
9
|
+
// package.json on an old bundle). It can also check npm for a newer published
|
|
10
|
+
// release so we can deliver updates on demand.
|
|
9
11
|
import fs from "node:fs";
|
|
10
12
|
import path from "node:path";
|
|
11
13
|
import { fileURLToPath } from "node:url";
|
|
@@ -26,7 +28,8 @@ function readJsonVersion(p) {
|
|
|
26
28
|
// from the npm package version at every init/update. Authoritative on a
|
|
27
29
|
// real user install, where the top-level package.json is NOT copied.
|
|
28
30
|
// 2. <repo>/package.json — git checkout / dev machine: the meaningful 1.6.x.
|
|
29
|
-
// 3. mcp/package.json — co-located last resort (
|
|
31
|
+
// 3. mcp/package.json — co-located last resort (release-stamped to match,
|
|
32
|
+
// but may be stale on an older bundle).
|
|
30
33
|
export function resolveVersion() {
|
|
31
34
|
return (readJsonVersion(path.join(__dirname, "version.json")) ||
|
|
32
35
|
readJsonVersion(path.join(repoDir(), "package.json")) ||
|
package/mcp/dist/version.json
CHANGED
package/mcp/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"dxt_version": "0.1",
|
|
3
3
|
"name": "social-autoposter",
|
|
4
4
|
"display_name": "Social Autoposter",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "1.6.72",
|
|
6
6
|
"description": "Draft, review, approve, and autopilot X/Twitter posts. Thin desktop client over the social-autoposter pipeline.",
|
|
7
7
|
"long_description": "A guided assistant that drafts, reviews, and autopilots X/Twitter posts.\nTo get started:\n1. Fully quit and restart Claude (quit and reopen, not just close the window).\n2. Choose **Set up social-autoposter** from the prompt menu or type **Set me up on social-autoposter end to end**. The agent installs the owned runtime, connects X, discovers and configures your product, and verifies with a draft-only cycle.",
|
|
8
8
|
"author": {
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
"entry_point": "dist/index.js",
|
|
16
16
|
"mcp_config": {
|
|
17
17
|
"command": "node",
|
|
18
|
-
"args": [
|
|
18
|
+
"args": [
|
|
19
|
+
"${__dirname}/dist/index.js"
|
|
20
|
+
],
|
|
19
21
|
"env": {
|
|
20
22
|
"PATH": "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
21
23
|
}
|
|
@@ -43,7 +45,13 @@
|
|
|
43
45
|
"description": "Render the visual dashboard (MCP Apps UI): project setup, X connection, autopilot state, and 7-day stats, with buttons to run a draft cycle, connect X, and refresh. Also rendered automatically after state-changing actions."
|
|
44
46
|
}
|
|
45
47
|
],
|
|
46
|
-
"keywords": [
|
|
48
|
+
"keywords": [
|
|
49
|
+
"twitter",
|
|
50
|
+
"x",
|
|
51
|
+
"social",
|
|
52
|
+
"autoposter",
|
|
53
|
+
"marketing"
|
|
54
|
+
],
|
|
47
55
|
"license": "UNLICENSED",
|
|
48
56
|
"repository": {
|
|
49
57
|
"type": "git",
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m13v/social-autoposter-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.6.72",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Desktop MCP client for social-autoposter (X/Twitter rail): manual draft/review/approve loop, autopilot control, and stats. Thin wrapper over the existing pipeline scripts.",
|
|
6
6
|
"type": "module",
|
package/package.json
CHANGED
package/scripts/release-mcpb.sh
CHANGED
|
@@ -75,6 +75,31 @@ fs.writeFileSync(p, JSON.stringify({version:'$VERSION',installedAt:new Date().to
|
|
|
75
75
|
console.log(' '+fs.readFileSync(p,'utf8').trim());
|
|
76
76
|
"
|
|
77
77
|
|
|
78
|
+
# ---- 3b. Stamp manifest.json + mcp/package.json + lockfile version ----------
|
|
79
|
+
# Claude Desktop's extension "Details" panel reads version from manifest.json,
|
|
80
|
+
# so it must track the release version too (not the frozen 0.0.1 placeholder).
|
|
81
|
+
# mcp/package.json and its lockfile are stamped in lockstep so the three stay
|
|
82
|
+
# consistent (npm errors if package.json and package-lock.json disagree).
|
|
83
|
+
say "Stamping mcp/manifest.json + mcp/package.json + mcp/package-lock.json -> $VERSION"
|
|
84
|
+
node -e "
|
|
85
|
+
const fs=require('fs');
|
|
86
|
+
const V='$VERSION';
|
|
87
|
+
for (const p of ['$MCP_DIR/manifest.json','$MCP_DIR/package.json']) {
|
|
88
|
+
const j=JSON.parse(fs.readFileSync(p,'utf8'));
|
|
89
|
+
j.version=V;
|
|
90
|
+
fs.writeFileSync(p, JSON.stringify(j,null,2)+'\n');
|
|
91
|
+
console.log(' '+p.replace('$MCP_DIR/','mcp/')+' -> '+j.version);
|
|
92
|
+
}
|
|
93
|
+
const lp='$MCP_DIR/package-lock.json';
|
|
94
|
+
if (fs.existsSync(lp)) {
|
|
95
|
+
const l=JSON.parse(fs.readFileSync(lp,'utf8'));
|
|
96
|
+
l.version=V;
|
|
97
|
+
if (l.packages && l.packages['']) l.packages[''].version=V;
|
|
98
|
+
fs.writeFileSync(lp, JSON.stringify(l,null,2)+'\n');
|
|
99
|
+
console.log(' mcp/package-lock.json -> '+l.version);
|
|
100
|
+
}
|
|
101
|
+
"
|
|
102
|
+
|
|
78
103
|
# ---- 4. Pack the .mcpb ------------------------------------------------------
|
|
79
104
|
say "Packing $BUNDLE"
|
|
80
105
|
rm -f "$BUNDLE"
|
|
@@ -99,6 +124,10 @@ BUNDLE_VER=$(unzip -p "$BUNDLE" dist/version.json 2>/dev/null | node -p "JSON.pa
|
|
|
99
124
|
[[ "$BUNDLE_VER" == "$VERSION" ]] || die "bundle version.json=$BUNDLE_VER != $VERSION"
|
|
100
125
|
echo " version.json: $BUNDLE_VER ok"
|
|
101
126
|
|
|
127
|
+
MANIFEST_VER=$(unzip -p "$BUNDLE" manifest.json 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "?")
|
|
128
|
+
[[ "$MANIFEST_VER" == "$VERSION" ]] || die "bundle manifest.json=$MANIFEST_VER != $VERSION (Desktop Details panel would show the wrong version)"
|
|
129
|
+
echo " manifest.json: $MANIFEST_VER ok"
|
|
130
|
+
|
|
102
131
|
for f in "dist/index.js" "dist/runtime.js" "manifest.json"; do
|
|
103
132
|
# grep -c reads all input (no SIGPIPE); anchor on the time column + 3-space
|
|
104
133
|
# gutter so node_modules/.../dist/index.js does not false-match the top-level.
|