social-autoposter 1.6.72 → 1.6.74
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 +90 -35
- package/mcp/dist/version.js +8 -5
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +11 -3
- package/mcp/menubar/s4l_menubar.py +2 -9
- package/mcp/menubar/s4l_state.py +8 -30
- 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,15 @@ 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";
|
|
38
|
+
// The Desktop scheduled task onboarding creates for the autopilot. Its presence on
|
|
39
|
+
// disk is the single "autopilot is set up" signal the dashboard + menu bar share
|
|
40
|
+
// (the legacy launchd autopilot is retired).
|
|
41
|
+
const AUTOPILOT_TASK_ID = "social-autoposter-autopilot";
|
|
33
42
|
const TWITTER_AUTOPILOT_LABEL = "com.m13v.social-twitter-cycle";
|
|
34
43
|
const TWITTER_AUTOPILOT_PLIST = path.join(os.homedir(), "Library", "LaunchAgents", `${TWITTER_AUTOPILOT_LABEL}.plist`);
|
|
35
44
|
// Daily self-updater. Enabled alongside autopilot so a hands-free (headless)
|
|
@@ -152,8 +161,9 @@ const server = new McpServer({
|
|
|
152
161
|
"ONBOARDING IS A TERMINAL GOAL. When the user asks to set up, install, configure, or onboard " +
|
|
153
162
|
"social-autoposter, keep taking the next safe action until the owned runtime is ready, a " +
|
|
154
163
|
"project is fully configured with seeded search topics, X is connected with its real handle, " +
|
|
155
|
-
"
|
|
156
|
-
"without posting
|
|
164
|
+
"the draft path (`scan_candidates` -> draft -> `submit_drafts`) has verified the pipeline " +
|
|
165
|
+
"without posting, AND the recurring autopilot scheduled task has been created and run once " +
|
|
166
|
+
"(see SCHEDULE THE AUTOPILOT below). Do not ask whether to inspect " +
|
|
157
167
|
"status, install or repair owned dependencies, choose an auto-detected browser profile, connect " +
|
|
158
168
|
"X, scan the profile, research the product website, save conservative inferred fields, seed " +
|
|
159
169
|
"topics, retry a recoverable failure, or run draft-only verification. Do those things. The " +
|
|
@@ -161,12 +171,24 @@ const server = new McpServer({
|
|
|
161
171
|
"twitter.com session cookies into the managed browser: warn that macOS keychain prompts may " +
|
|
162
172
|
"appear, then proceed without a separate yes/no turn. Ask at most one bundled question, only " +
|
|
163
173
|
"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
|
-
"
|
|
174
|
+
"or pause for an unavoidable interactive login or OS prompt. Never auto-POST during setup, but " +
|
|
175
|
+
"DO schedule the draft-only autopilot as the final step (it only queues drafts for approval, it " +
|
|
176
|
+
"never posts on its own). When setup reaches done (" +
|
|
177
|
+
"runtime ready, a project configured with seeded topics, X connected, the draft path verified, " +
|
|
178
|
+
"and the autopilot scheduled task created and run once), call the `dashboard` tool so the user " +
|
|
168
179
|
"sees the finished setup rendered, then give " +
|
|
169
180
|
"the completion summary.\n\n" +
|
|
181
|
+
"SCHEDULE THE AUTOPILOT (final onboarding step). Once the draft path is verified, start the " +
|
|
182
|
+
"autopilot by calling the HOST tool `create_scheduled_task` with: taskId " +
|
|
183
|
+
"'social-autoposter-autopilot'; cronExpression '* * * * *' (continuous, like the pipeline; the " +
|
|
184
|
+
"host runs one at a time and skips overlapping fires, so cycles run back-to-back); and a " +
|
|
185
|
+
"self-contained prompt telling the run to call scan_candidates, draft ONE on-brand reply " +
|
|
186
|
+
"(<=250 chars, same language as the thread, adds real value) for each candidate worth engaging, " +
|
|
187
|
+
"then call submit_drafts with those drafts, and NOT post (drafts pile up in the menu-bar " +
|
|
188
|
+
"approval cards for the user to approve). Then run ONE cycle yourself now (scan_candidates -> " +
|
|
189
|
+
"draft -> submit_drafts) to seed the cards and pre-approve the tools, so unattended fires do not " +
|
|
190
|
+
"stall on a permission prompt. If the host exposes no create_scheduled_task tool, tell the user " +
|
|
191
|
+
"the autopilot could not be scheduled here and to run a draft cycle manually instead.\n\n" +
|
|
170
192
|
"BE PROACTIVE ABOUT MARKETING MOMENTS. Whenever the user ships, finishes, merges, or releases " +
|
|
171
193
|
"something worth talking about in this session (a new feature, a launch, a long-awaited fix, a " +
|
|
172
194
|
"milestone), don't wait to be asked: offer to tell the world by running a draft cycle " +
|
|
@@ -469,8 +491,11 @@ async function produceDrafts(project, onProgress) {
|
|
|
469
491
|
function renderDraftsTable(plan) {
|
|
470
492
|
const candidates = plan.candidates || [];
|
|
471
493
|
return candidates
|
|
472
|
-
|
|
473
|
-
|
|
494
|
+
// Number by FULL-array index (matches post_drafts + the menu bar), then drop
|
|
495
|
+
// already-posted entries so the cards only show what's still pending.
|
|
496
|
+
.map((c, i) => ({ c, n: i + 1 }))
|
|
497
|
+
.filter((e) => e.c.posted !== true)
|
|
498
|
+
.map(({ c, n }) => {
|
|
474
499
|
const author = c.thread_author ? `@${c.thread_author}` : "(unknown thread)";
|
|
475
500
|
const style = c.engagement_style ?? "?";
|
|
476
501
|
const reply = c.reply_text ?? "(empty)";
|
|
@@ -922,7 +947,7 @@ tool("project_config", {
|
|
|
922
947
|
(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
948
|
: projects.every((p) => p.ready)
|
|
924
949
|
? (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.
|
|
950
|
+
? "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
951
|
: "All configured projects are ready, but X is NOT connected — posting needs a logged-in " +
|
|
927
952
|
"x.com session. Detect sources and run project_config action:'connect_x', confirm:true; do not ask whether to proceed.")
|
|
928
953
|
: "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 +1059,9 @@ tool("project_config", {
|
|
|
1034
1059
|
note: (result.ready
|
|
1035
1060
|
? `Project '${result.project}' is fully configured.${seedNote} Next: if X is not connected, ` +
|
|
1036
1061
|
`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
|
|
1062
|
+
`action:'connect_x', confirm:true immediately. Once X is connected, run scan_candidates -> submit_drafts ` +
|
|
1063
|
+
`to verify without posting, then schedule the draft-only autopilot (create_scheduled_task, cron '* * * * *', ` +
|
|
1064
|
+
`prompt = scan_candidates -> draft -> submit_drafts) and run one cycle now.`
|
|
1039
1065
|
: `Saved what you provided for '${result.project}'. Still need: ${result.missing_required.join(", ")}. ` +
|
|
1040
1066
|
`First derive those fields from existing context, profile_scan, and website research, then ` +
|
|
1041
1067
|
`call project_config again with name='${result.project}'. Ask only if a required field is genuinely unknowable.`) +
|
|
@@ -1332,18 +1358,28 @@ function runtimeSnapshot() {
|
|
|
1332
1358
|
// and re-reads status. The tool itself returns the first-paint snapshot so the
|
|
1333
1359
|
// view has data the instant it loads.
|
|
1334
1360
|
// Is either launchd job (cycle / daily updater) currently loaded?
|
|
1361
|
+
// "Autopilot" is now the Claude Desktop scheduled task `social-autoposter-autopilot`
|
|
1362
|
+
// (created during onboarding via create_scheduled_task), NOT the legacy launchd job.
|
|
1363
|
+
// We can't read the host's enabled/paused flag, but the task's presence on disk is the
|
|
1364
|
+
// single signal the dashboard AND the menu bar key off of, so they stay aligned.
|
|
1335
1365
|
async function autopilotLoaded() {
|
|
1366
|
+
let autopilot_on = false;
|
|
1367
|
+
try {
|
|
1368
|
+
const cfg = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
1369
|
+
autopilot_on = fs.existsSync(path.join(cfg, "scheduled-tasks", AUTOPILOT_TASK_ID, "SKILL.md"));
|
|
1370
|
+
}
|
|
1371
|
+
catch {
|
|
1372
|
+
/* leave false */
|
|
1373
|
+
}
|
|
1374
|
+
let auto_update_on = false;
|
|
1336
1375
|
try {
|
|
1337
1376
|
const res = await run("launchctl", ["list"], { timeoutMs: 10_000 });
|
|
1338
|
-
|
|
1339
|
-
return {
|
|
1340
|
-
autopilot_on: lines.some((l) => l.includes(TWITTER_AUTOPILOT_LABEL)),
|
|
1341
|
-
auto_update_on: lines.some((l) => l.includes(UPDATER_LABEL)),
|
|
1342
|
-
};
|
|
1377
|
+
auto_update_on = res.stdout.split("\n").some((l) => l.includes(UPDATER_LABEL));
|
|
1343
1378
|
}
|
|
1344
1379
|
catch {
|
|
1345
|
-
|
|
1380
|
+
/* leave false */
|
|
1346
1381
|
}
|
|
1382
|
+
return { autopilot_on, auto_update_on };
|
|
1347
1383
|
}
|
|
1348
1384
|
// Assemble everything the panel needs in one shot (projects + X + autopilot +
|
|
1349
1385
|
// version). Resilient: any probe that throws degrades to a safe default rather
|
|
@@ -1719,11 +1755,10 @@ tool("submit_drafts", {
|
|
|
1719
1755
|
const project = (candidates.map((c) => c.matched_project).find((p) => !!p) ||
|
|
1720
1756
|
firstSc?.matched_project ||
|
|
1721
1757
|
"default");
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
//
|
|
1725
|
-
|
|
1726
|
-
// falls back to the plain project URL per-candidate if this is skipped.
|
|
1758
|
+
// Stage the new drafts under the scan batch id and bake link targets into them
|
|
1759
|
+
// (sub-second at TWITTER_PAGE_GEN_RATE=0). Best-effort: posting falls back to the
|
|
1760
|
+
// plain project URL per-candidate if gen is skipped.
|
|
1761
|
+
writePlan(args.batch_id, { candidates });
|
|
1727
1762
|
try {
|
|
1728
1763
|
await runPython("scripts/twitter_gen_links.py", ["--plan", planPath(args.batch_id)], {
|
|
1729
1764
|
timeoutMs: 120_000,
|
|
@@ -1733,25 +1768,45 @@ tool("submit_drafts", {
|
|
|
1733
1768
|
catch {
|
|
1734
1769
|
/* best effort — plan still posts with a plain-URL fallback */
|
|
1735
1770
|
}
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1771
|
+
const staged = readPlan(args.batch_id)?.candidates ?? candidates;
|
|
1772
|
+
// Accumulate into ONE persistent review queue so a continuous autopilot's drafts
|
|
1773
|
+
// PILE UP in the menu-bar cards instead of each run overwriting the last. New
|
|
1774
|
+
// drafts are appended; a thread already in the queue (by URL) is skipped (one
|
|
1775
|
+
// draft per thread). Posted entries are KEPT in place so the 1-based card
|
|
1776
|
+
// numbering stays stable across runs — the menu bar, the chat table, and
|
|
1777
|
+
// post_drafts all index the full array and filter on the `posted` flag.
|
|
1778
|
+
const queue = [
|
|
1779
|
+
...(readPlan(REVIEW_QUEUE_ID)?.candidates ?? []),
|
|
1780
|
+
];
|
|
1781
|
+
const seen = new Set(queue.map((c) => c.candidate_url).filter((u) => !!u));
|
|
1782
|
+
let added = 0;
|
|
1783
|
+
for (const nc of staged) {
|
|
1784
|
+
if (nc.candidate_url && seen.has(nc.candidate_url))
|
|
1785
|
+
continue;
|
|
1786
|
+
queue.push(nc);
|
|
1787
|
+
if (nc.candidate_url)
|
|
1788
|
+
seen.add(nc.candidate_url);
|
|
1789
|
+
added++;
|
|
1790
|
+
}
|
|
1791
|
+
writePlan(REVIEW_QUEUE_ID, { candidates: queue });
|
|
1792
|
+
const pending = queue.filter((c) => c.posted !== true);
|
|
1738
1793
|
// Drafts queued = the pipeline verified end-to-end without posting. This is the
|
|
1739
1794
|
// onboarding "draft_verified" terminal goal (formerly completed by draft_cycle).
|
|
1740
|
-
if (
|
|
1741
|
-
completeOnboardingMilestone("draft_verified", { outcome: "review_batch", draft_count:
|
|
1742
|
-
//
|
|
1795
|
+
if (added > 0)
|
|
1796
|
+
completeOnboardingMilestone("draft_verified", { outcome: "review_batch", draft_count: added });
|
|
1797
|
+
// Point the menu-bar review cards at the accumulated queue.
|
|
1743
1798
|
writeReviewRequest({
|
|
1744
|
-
batch_id:
|
|
1799
|
+
batch_id: REVIEW_QUEUE_ID,
|
|
1745
1800
|
project,
|
|
1746
|
-
count,
|
|
1747
|
-
plan_path: planPath(
|
|
1801
|
+
count: pending.length,
|
|
1802
|
+
plan_path: planPath(REVIEW_QUEUE_ID),
|
|
1748
1803
|
created_at: new Date().toISOString(),
|
|
1749
1804
|
});
|
|
1750
|
-
return textContent(
|
|
1751
|
-
`
|
|
1752
|
-
renderDraftsTable(
|
|
1753
|
-
`\n\nTo post
|
|
1754
|
-
`numbers to post.`);
|
|
1805
|
+
return textContent(`Queued ${added} new draft(s); ${pending.length} now awaiting approval in the menu-bar cards ` +
|
|
1806
|
+
`(review queue "${REVIEW_QUEUE_ID}"). Nothing posts until approved.\n\n` +
|
|
1807
|
+
renderDraftsTable({ candidates: queue }) +
|
|
1808
|
+
`\n\nTo post: the user approves in the menu bar, or call post_drafts with batch_id ` +
|
|
1809
|
+
`"${REVIEW_QUEUE_ID}" and the numbers to post.`);
|
|
1755
1810
|
});
|
|
1756
1811
|
appTool("dashboard", {
|
|
1757
1812
|
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.73",
|
|
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",
|
|
@@ -191,12 +191,8 @@ class S4LMenuBar(rumps.App):
|
|
|
191
191
|
except Exception:
|
|
192
192
|
pass
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if st.set_autopilot(desired):
|
|
197
|
-
sender.state = 1 if desired else 0
|
|
198
|
-
else:
|
|
199
|
-
self._notify("S4L", "Open Claude Desktop to change autopilot.")
|
|
194
|
+
# _toggle_ap removed: autopilot is the Claude Desktop scheduled task now, managed
|
|
195
|
+
# in the Scheduled tab. The menu bar mirrors the dashboard (no launchd toggle).
|
|
200
196
|
|
|
201
197
|
# ---- activity spinner -------------------------------------------------
|
|
202
198
|
# The server writes activity.json while a tool runs (scanning/drafting/
|
|
@@ -469,9 +465,6 @@ class S4LMenuBar(rumps.App):
|
|
|
469
465
|
out.append(self._label("7d stats — open dashboard"))
|
|
470
466
|
|
|
471
467
|
out.append(rumps.separator)
|
|
472
|
-
ap = rumps.MenuItem("Autopilot", callback=self._toggle_ap)
|
|
473
|
-
ap.state = 1 if snap.get("autopilot_on") else 0
|
|
474
|
-
out.append(ap)
|
|
475
468
|
out.append(
|
|
476
469
|
rumps.MenuItem("Run draft cycle in Claude", callback=self._draft)
|
|
477
470
|
)
|
package/mcp/menubar/s4l_state.py
CHANGED
|
@@ -103,7 +103,11 @@ def _launchctl_list() -> str:
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
def autopilot_loaded() -> bool:
|
|
106
|
-
|
|
106
|
+
# Autopilot is now the Claude Desktop scheduled task, not the legacy launchd job.
|
|
107
|
+
cfg = os.environ.get("CLAUDE_CONFIG_DIR") or os.path.join(str(Path.home()), ".claude")
|
|
108
|
+
return os.path.exists(
|
|
109
|
+
os.path.join(cfg, "scheduled-tasks", "social-autoposter-autopilot", "SKILL.md")
|
|
110
|
+
)
|
|
107
111
|
|
|
108
112
|
|
|
109
113
|
# ---- loopback panel server (live, when Claude Desktop is running) ----------
|
|
@@ -207,35 +211,9 @@ def stats_7d():
|
|
|
207
211
|
}
|
|
208
212
|
|
|
209
213
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
plist when Claude Desktop is closed. Returns False if it couldn't act."""
|
|
214
|
-
res = loopback_tool("autopilot", {"action": "enable" if enable else "disable"})
|
|
215
|
-
if res is not None:
|
|
216
|
-
return True
|
|
217
|
-
plist = str(
|
|
218
|
-
Path.home() / "Library" / "LaunchAgents" / (AUTOPILOT_LABEL + ".plist")
|
|
219
|
-
)
|
|
220
|
-
uid = os.getuid()
|
|
221
|
-
try:
|
|
222
|
-
if enable:
|
|
223
|
-
if not os.path.exists(plist):
|
|
224
|
-
return False # first enable needs the server to write the plist
|
|
225
|
-
subprocess.run(
|
|
226
|
-
["launchctl", "bootstrap", f"gui/{uid}", plist],
|
|
227
|
-
capture_output=True,
|
|
228
|
-
timeout=15,
|
|
229
|
-
)
|
|
230
|
-
else:
|
|
231
|
-
subprocess.run(
|
|
232
|
-
["launchctl", "bootout", f"gui/{uid}/{AUTOPILOT_LABEL}"],
|
|
233
|
-
capture_output=True,
|
|
234
|
-
timeout=15,
|
|
235
|
-
)
|
|
236
|
-
return True
|
|
237
|
-
except Exception:
|
|
238
|
-
return False
|
|
214
|
+
# set_autopilot() (the launchd toggle) was removed: the autopilot is now the Claude
|
|
215
|
+
# Desktop scheduled task `social-autoposter-autopilot`, managed in the Scheduled tab,
|
|
216
|
+
# so the menu bar no longer toggles a launchd job (it mirrors the dashboard instead).
|
|
239
217
|
|
|
240
218
|
|
|
241
219
|
def panel_url():
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m13v/social-autoposter-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.6.73",
|
|
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.
|