@cmo-so/mcp 0.8.0 → 0.9.0
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/dist/cmo-mcp.mjs +294 -63
- package/package.json +1 -1
package/dist/cmo-mcp.mjs
CHANGED
|
@@ -7199,7 +7199,7 @@ var require_dist = __commonJS({
|
|
|
7199
7199
|
// package.json
|
|
7200
7200
|
var package_default = {
|
|
7201
7201
|
name: "@cmo-so/cli",
|
|
7202
|
-
version: "0.
|
|
7202
|
+
version: "0.9.0",
|
|
7203
7203
|
description: "CMO.so on the command line: your websites, ideas, blog posts, LinkedIn queue and results, plus a local MCP server for Claude Desktop, Cursor and other MCP clients.",
|
|
7204
7204
|
type: "module",
|
|
7205
7205
|
license: "MIT",
|
|
@@ -7669,8 +7669,11 @@ function domainApi(api2, domain) {
|
|
|
7669
7669
|
}),
|
|
7670
7670
|
/** This website's topics, for assigning an idea to one. */
|
|
7671
7671
|
topics: () => api2.request(`${base}/topics`),
|
|
7672
|
-
/** Add a waiting idea to the end
|
|
7673
|
-
planIdea: (id) => post(
|
|
7672
|
+
/** Add a waiting idea to the plan: at the end, or at `position` (1-based in the answer). */
|
|
7673
|
+
planIdea: (id, position) => post(
|
|
7674
|
+
`${base}/ideas/${encodeURIComponent(id)}/plan`,
|
|
7675
|
+
position != null ? { position } : void 0
|
|
7676
|
+
),
|
|
7674
7677
|
/** One step up or down the 1-10 priority the planner sorts ideas by. */
|
|
7675
7678
|
ideaPriority: (id, delta) => post(`${base}/ideas/${encodeURIComponent(id)}/priority`, { delta }),
|
|
7676
7679
|
/** Take a post out of the plan; its idea can be restored to Ideas. */
|
|
@@ -7767,8 +7770,8 @@ function domainApi(api2, domain) {
|
|
|
7767
7770
|
...source ? { sitemap_url: source } : {}
|
|
7768
7771
|
}),
|
|
7769
7772
|
discardIdea: (id, restore = false) => post(`${base}/ideas/${encodeURIComponent(id)}/discard${restore ? "?restore=true" : ""}`),
|
|
7770
|
-
/** OK a draft (or retry a failed post): it goes to the front of the queue
|
|
7771
|
-
approveSocial: (id) => post(`${base}/social/${id}/approve
|
|
7773
|
+
/** OK a draft (or retry a failed post): it goes to the front of the queue, or to `position`; the post comes back with its eta. */
|
|
7774
|
+
approveSocial: (id, position) => post(`${base}/social/${id}/approve`, position != null ? { position } : void 0),
|
|
7772
7775
|
skipSocial: (id) => post(`${base}/social/${id}/skip`),
|
|
7773
7776
|
/** Post it on LinkedIn now, not at the next posting time (even while posting is paused). */
|
|
7774
7777
|
/** Post it now. `background`: the reply comes at once (status "publishing") and it goes out behind the
|
|
@@ -7844,9 +7847,15 @@ function domainApi(api2, domain) {
|
|
|
7844
7847
|
/** "Looks good, start": setup done, and Maggie writes the first week's posts. */
|
|
7845
7848
|
socialSetupConfirm: () => post(`${base}/social/setup/confirm`),
|
|
7846
7849
|
/** OK every post waiting for you (not a series that hasn't been approved), on one channel or all. */
|
|
7847
|
-
approveAllSocial: (channel) =>
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
+
approveAllSocial: (channel, position) => {
|
|
7851
|
+
const q2 = new URLSearchParams();
|
|
7852
|
+
if (channel) q2.set("channel", channel);
|
|
7853
|
+
if (position != null) q2.set("position", String(position));
|
|
7854
|
+
const qs = q2.toString();
|
|
7855
|
+
return post(
|
|
7856
|
+
`${base}/social/approve-all${qs ? `?${qs}` : ""}`
|
|
7857
|
+
);
|
|
7858
|
+
},
|
|
7850
7859
|
/** A Facebook sign-in link: after it the user lands on returnTo with ?facebook=connected (or =error). */
|
|
7851
7860
|
connectFacebook: (returnTo = "close") => post(`${base}/social/facebook/connect`, { return_to: returnTo }),
|
|
7852
7861
|
/** The Facebook Pages you connected, and the one this website posts to. */
|
|
@@ -7962,6 +7971,19 @@ function domainApi(api2, domain) {
|
|
|
7962
7971
|
photo,
|
|
7963
7972
|
...opts.instruction?.trim() ? { instruction: opts.instruction.trim() } : {}
|
|
7964
7973
|
}),
|
|
7974
|
+
/** Your own set of pictures for the post, in order (up to 20; free, no AI): base64 photos and/or public
|
|
7975
|
+
* https links to pictures. Send a few photos per call (16 MB request limit), `append` after the first.
|
|
7976
|
+
* On LinkedIn a set goes out as a swipeable PDF carousel unless setSocialCarousel says images. */
|
|
7977
|
+
setSocialPictures: (id, pictures) => post(`${base}/social/${id}/pictures`, pictures),
|
|
7978
|
+
/** A LinkedIn post goes out as this PDF (base64 under ~11 MB, or a public https link; up to 300 pages);
|
|
7979
|
+
* its first 20 pages become the post's pictures. */
|
|
7980
|
+
attachSocialCarouselPdf: (id, pdf) => post(`${base}/social/${id}/carousel/pdf`, pdf),
|
|
7981
|
+
/** How a LinkedIn post with a set goes out: "pdf" (swipeable carousel, the default) or "images"; its title. */
|
|
7982
|
+
setSocialCarousel: (id, changes) => api2.request(`${base}/social/${id}/carousel`, {
|
|
7983
|
+
method: "PATCH",
|
|
7984
|
+
headers: { "Content-Type": "application/json" },
|
|
7985
|
+
body: JSON.stringify(changes)
|
|
7986
|
+
}),
|
|
7965
7987
|
/** A new picture for every post here without one (or these), one after another in the background. */
|
|
7966
7988
|
fillSocialImages: (opts = {}) => post(
|
|
7967
7989
|
`${base}/social/images/fill`,
|
|
@@ -7995,20 +8017,24 @@ function domainApi(api2, domain) {
|
|
|
7995
8017
|
background: true,
|
|
7996
8018
|
...channels ? { channels } : {}
|
|
7997
8019
|
}),
|
|
7998
|
-
|
|
8020
|
+
/** `position`: where the posts are queued when approval mode is auto (the front by default). */
|
|
8021
|
+
startSocialFromPost: (sourcePostId, channels, position) => post(`${base}/social/from-post`, {
|
|
7999
8022
|
source_post_id: sourcePostId,
|
|
8000
8023
|
background: true,
|
|
8001
|
-
...channels ? { channels } : {}
|
|
8024
|
+
...channels ? { channels } : {},
|
|
8025
|
+
...position != null ? { position } : {}
|
|
8002
8026
|
}),
|
|
8003
|
-
startSocialPlan: (days, timezone, channels) => post(`${base}/social/plan`, {
|
|
8027
|
+
startSocialPlan: (days, timezone, channels, position) => post(`${base}/social/plan`, {
|
|
8004
8028
|
days,
|
|
8005
8029
|
timezone,
|
|
8006
8030
|
background: true,
|
|
8007
|
-
...channels ? { channels } : {}
|
|
8031
|
+
...channels ? { channels } : {},
|
|
8032
|
+
...position != null ? { position } : {}
|
|
8008
8033
|
}),
|
|
8009
|
-
startWriteSocialIdea: (id, channels) => post(`${base}/social/ideas/${id}/write`, {
|
|
8034
|
+
startWriteSocialIdea: (id, channels, position) => post(`${base}/social/ideas/${id}/write`, {
|
|
8010
8035
|
background: true,
|
|
8011
|
-
...channels ? { channels } : {}
|
|
8036
|
+
...channels ? { channels } : {},
|
|
8037
|
+
...position != null ? { position } : {}
|
|
8012
8038
|
}),
|
|
8013
8039
|
/** Disconnect the website's LinkedIn account. */
|
|
8014
8040
|
disconnectLinkedIn: (connectionId) => api2.request(`${base}/social/linkedin/${connectionId}`, { method: "DELETE" }),
|
|
@@ -8181,10 +8207,12 @@ function domainApi(api2, domain) {
|
|
|
8181
8207
|
}),
|
|
8182
8208
|
// docs/19 P3: autopilot cadence and publish-now.
|
|
8183
8209
|
autopilot: () => api2.request(`${base}/autopilot`),
|
|
8184
|
-
/** Posts per day
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8210
|
+
/** Posts per day (one of `choices`, up to the plan's limit). Pushed blogs: how many
|
|
8211
|
+
* AI CMO sends a day. Pull sites: the most the site collects a day; null = the plan's. */
|
|
8212
|
+
setPostsPerDay: (postsPerDay) => put(
|
|
8213
|
+
`${base}/autopilot/cadence`,
|
|
8214
|
+
{ posts_per_day: postsPerDay }
|
|
8215
|
+
),
|
|
8188
8216
|
/** Publish a ready draft to the connected site now (one post from today's quota). */
|
|
8189
8217
|
publishNow: (id) => post(
|
|
8190
8218
|
`${base}/posts/${encodeURIComponent(id)}/publish`
|
|
@@ -8255,6 +8283,8 @@ function domainApi(api2, domain) {
|
|
|
8255
8283
|
inboxReport: (id) => api2.request(`${base}/reports/${id}`),
|
|
8256
8284
|
saveReport: (r) => post(`${base}/reports`, r),
|
|
8257
8285
|
readAllReports: () => post(`${base}/reports/read-all`, {}),
|
|
8286
|
+
/** One social post in full (social_list clips its text). */
|
|
8287
|
+
socialPost: (id) => api2.request(`${base}/social/${id}`),
|
|
8258
8288
|
// Ops Autopilot: ideas the weekly clean-up archived (restorable), post health, Google reviews.
|
|
8259
8289
|
ideaCleanup: (limit = 50) => api2.request(`${base}/idea-cleanup?limit=${limit}`),
|
|
8260
8290
|
restoreIdeaCleanup: (id) => post(`${base}/idea-cleanup/${id}/restore`, {}),
|
|
@@ -8467,6 +8497,14 @@ var CHANNEL_EN = {
|
|
|
8467
8497
|
google_business: "Google Business"
|
|
8468
8498
|
};
|
|
8469
8499
|
var CHANNEL_ZH = { ...CHANNEL_EN, google_business: "Google \u5546\u5BB6" };
|
|
8500
|
+
var channelsEn = (a) => {
|
|
8501
|
+
const list2 = (Array.isArray(a.channels) ? a.channels : []).map((c) => CHANNEL_EN[String(c)] ?? String(c));
|
|
8502
|
+
return list2.length ? list2.join(list2.length === 2 ? " and " : ", ") : null;
|
|
8503
|
+
};
|
|
8504
|
+
var channelsZh = (a) => {
|
|
8505
|
+
const list2 = (Array.isArray(a.channels) ? a.channels : []).map((c) => CHANNEL_ZH[String(c)] ?? String(c));
|
|
8506
|
+
return list2.length ? list2.join("\u3001") : null;
|
|
8507
|
+
};
|
|
8470
8508
|
var named = (title, id) => {
|
|
8471
8509
|
if (title != null && String(title).trim()) return ` ${q(title)}`;
|
|
8472
8510
|
const s = String(id ?? "").trim();
|
|
@@ -8476,6 +8514,20 @@ var list = (xs, n = 5) => {
|
|
|
8476
8514
|
const a = (Array.isArray(xs) ? xs : xs == null ? [] : [xs]).map(String);
|
|
8477
8515
|
return a.length > n ? `${a.slice(0, n).join(", ")} +${a.length - n}` : a.join(", ");
|
|
8478
8516
|
};
|
|
8517
|
+
var placeEn = (p, items = "posts") => {
|
|
8518
|
+
const t = String(p ?? "").trim().toLowerCase();
|
|
8519
|
+
if (t === "end") return `at the end of the queue, after the ${items} already waiting`;
|
|
8520
|
+
if (t === "random") return "at a random place in the queue";
|
|
8521
|
+
if (/^\d+$/.test(t)) return Number(t) === 0 ? null : `after the first ${t} in the queue`;
|
|
8522
|
+
return null;
|
|
8523
|
+
};
|
|
8524
|
+
var placeZh = (p) => {
|
|
8525
|
+
const t = String(p ?? "").trim().toLowerCase();
|
|
8526
|
+
if (t === "end") return "\u6392\u5728\u968A\u5217\u6700\u5F8C\uFF0C\u5728\u5DF2\u5728\u7B49\u5F85\u7684\u4E4B\u5F8C";
|
|
8527
|
+
if (t === "random") return "\u96A8\u6A5F\u6392\u9032\u968A\u5217";
|
|
8528
|
+
if (/^\d+$/.test(t)) return Number(t) === 0 ? null : `\u6392\u5728\u968A\u5217\u524D ${t} \u7BC7\u4E4B\u5F8C`;
|
|
8529
|
+
return null;
|
|
8530
|
+
};
|
|
8479
8531
|
var ids = (a) => (Array.isArray(a.ids) && a.ids.length ? a.ids : a.id != null ? [a.id] : []).map(Number);
|
|
8480
8532
|
var keys = (a) => Object.keys(a ?? {}).filter((k) => a[k] !== void 0);
|
|
8481
8533
|
var videoChannels = (a) => (Array.isArray(a.channels) && a.channels.length ? a.channels : ["youtube"]).map(
|
|
@@ -8635,8 +8687,16 @@ var confirmCopy = {
|
|
|
8635
8687
|
"zh-Hant": (a) => `${Number(a.delta) > 0 ? "\u63D0\u9AD8" : "\u964D\u4F4E"}${named(a.title, a.id)} \u7684\u512A\u5148\u7D1A\u3002`
|
|
8636
8688
|
},
|
|
8637
8689
|
idea_plan: {
|
|
8638
|
-
en: (a) =>
|
|
8639
|
-
|
|
8690
|
+
en: (a) => {
|
|
8691
|
+
const t = String(a.position ?? "").trim().toLowerCase();
|
|
8692
|
+
const where = t === "front" || t === "0" ? "the front of the plan" : t === "random" ? "a random place in the plan" : /^\d+$/.test(t) ? `the plan, after the first ${t} planned posts` : "the end of the plan";
|
|
8693
|
+
return `Add${named(a.title, a.id)} to ${where}.`;
|
|
8694
|
+
},
|
|
8695
|
+
"zh-Hant": (a) => {
|
|
8696
|
+
const t = String(a.position ?? "").trim().toLowerCase();
|
|
8697
|
+
const where = t === "front" || t === "0" ? "\u8A08\u5283\u6700\u524D\u9762" : t === "random" ? "\u8A08\u5283\u4E2D\u7684\u96A8\u6A5F\u4F4D\u7F6E" : /^\d+$/.test(t) ? `\u8A08\u5283\u4E2D\u524D ${t} \u7BC7\u4E4B\u5F8C` : "\u8A08\u5283\u6700\u5F8C";
|
|
8698
|
+
return `\u628A${named(a.title, a.id)} \u52A0\u5230${where}\u3002`;
|
|
8699
|
+
}
|
|
8640
8700
|
},
|
|
8641
8701
|
idea_discard: {
|
|
8642
8702
|
en: (a) => `Remove the idea${named(a.title, a.id)}.`,
|
|
@@ -8706,23 +8766,29 @@ var confirmCopy = {
|
|
|
8706
8766
|
},
|
|
8707
8767
|
// ----------------------------------------------------------------- social
|
|
8708
8768
|
social_update_copy: {
|
|
8709
|
-
en: (a) => `Change the text of
|
|
8710
|
-
"zh-Hant": (a) => `\u4FEE\u6539
|
|
8769
|
+
en: (a) => `Change the text of post #${a.id}.`,
|
|
8770
|
+
"zh-Hant": (a) => `\u4FEE\u6539\u8CBC\u6587 #${a.id} \u7684\u5167\u5BB9\u3002`
|
|
8711
8771
|
},
|
|
8712
8772
|
social_approve: {
|
|
8713
8773
|
en: (a) => {
|
|
8714
8774
|
const n = ids(a);
|
|
8715
|
-
|
|
8775
|
+
const at = placeEn(a.position);
|
|
8776
|
+
if (at)
|
|
8777
|
+
return `Approve ${n.length > 1 ? `${n.length} posts: ${n.map((i) => `#${i}`).join(", ")}` : `post #${n[0]}`}. ${n.length > 1 ? "They are" : "It is"} queued ${at}, and go${n.length > 1 ? "" : "es"} out where your network can see ${n.length > 1 ? "them" : "it"}.`;
|
|
8778
|
+
return n.length > 1 ? `Approve ${n.length} posts: ${n.map((i) => `#${i}`).join(", ")}. Each goes out at your next posting times, where your network can see it.` : `Approve post #${n[0]}. It goes out at your next posting time, where your network can see it.`;
|
|
8716
8779
|
},
|
|
8717
8780
|
"zh-Hant": (a) => {
|
|
8718
8781
|
const n = ids(a);
|
|
8719
|
-
|
|
8782
|
+
const at = placeZh(a.position);
|
|
8783
|
+
if (at)
|
|
8784
|
+
return `\u6279\u51C6${n.length > 1 ? ` ${n.length} \u7BC7\u8CBC\u6587\uFF1A${n.map((i) => `#${i}`).join("\u3001")}` : `\u8CBC\u6587 #${n[0]}`}\u3002${at}\uFF0C\u767C\u51FA\u5F8C\u4F60\u7684\u4EBA\u8108\u90FD\u770B\u5F97\u5230\u3002`;
|
|
8785
|
+
return n.length > 1 ? `\u6279\u51C6 ${n.length} \u7BC7\u8CBC\u6587\uFF1A${n.map((i) => `#${i}`).join("\u3001")}\u3002\u6BCF\u7BC7\u5728\u63A5\u4E0B\u4F86\u7684\u767C\u6587\u6642\u9593\u767C\u51FA\uFF0C\u4F60\u7684\u4EBA\u8108\u90FD\u770B\u5F97\u5230\u3002` : `\u6279\u51C6\u8CBC\u6587 #${n[0]}\u3002\u5B83\u6703\u5728\u4E0B\u4E00\u500B\u767C\u6587\u6642\u9593\u767C\u51FA\uFF0C\u4F60\u7684\u4EBA\u8108\u90FD\u770B\u5F97\u5230\u3002`;
|
|
8720
8786
|
}
|
|
8721
8787
|
},
|
|
8722
|
-
social_skip: { en: (a) => `Skip
|
|
8788
|
+
social_skip: { en: (a) => `Skip post #${a.id}.`, "zh-Hant": (a) => `\u8DF3\u904E\u8CBC\u6587 #${a.id}\u3002` },
|
|
8723
8789
|
social_restore: {
|
|
8724
|
-
en: (a) => `Bring back
|
|
8725
|
-
"zh-Hant": (a) => `\u6062\u5FA9
|
|
8790
|
+
en: (a) => `Bring back post #${a.id}.`,
|
|
8791
|
+
"zh-Hant": (a) => `\u6062\u5FA9\u8CBC\u6587 #${a.id}\u3002`
|
|
8726
8792
|
},
|
|
8727
8793
|
youtube_connect_link: {
|
|
8728
8794
|
en: () => "Make a link to connect your YouTube channel.",
|
|
@@ -8763,28 +8829,52 @@ var confirmCopy = {
|
|
|
8763
8829
|
"zh-Hant": (a) => `\u4FEE\u6539 YouTube \u9810\u8A2D\uFF1A${keys(a).join("\u3001") || "\u7121"}\u3002`
|
|
8764
8830
|
},
|
|
8765
8831
|
social_image_new: {
|
|
8766
|
-
en: (a) => `Make a new picture for
|
|
8767
|
-
"zh-Hant": (a) => `\u70BA
|
|
8832
|
+
en: (a) => `Make a new picture for post #${a.id}${a.instruction ? `: ${String(a.instruction).slice(0, 300)}` : ""} (1 AI generation, in the background)${a.replace ? ", stopping the one being made for another post" : ""}.`,
|
|
8833
|
+
"zh-Hant": (a) => `\u70BA\u8CBC\u6587 #${a.id} \u751F\u6210\u65B0\u5716\u7247${a.instruction ? `\uFF1A${String(a.instruction).slice(0, 300)}` : ""}\uFF08\u7528 1 \u6B21 AI \u751F\u6210\uFF0C\u5728\u80CC\u666F\u9032\u884C\uFF09${a.replace ? "\uFF0C\u4E26\u505C\u6B62\u6B63\u5728\u70BA\u53E6\u4E00\u7BC7\u751F\u6210\u7684\u5716\u7247" : ""}\u3002`
|
|
8768
8834
|
},
|
|
8769
8835
|
social_image_stop: {
|
|
8770
|
-
en: () => "Stop the
|
|
8771
|
-
"zh-Hant": () => "\u505C\u6B62\u6B63\u5728\u751F\u6210\u7684
|
|
8836
|
+
en: () => "Stop the picture being made; the post keeps the one it had.",
|
|
8837
|
+
"zh-Hant": () => "\u505C\u6B62\u6B63\u5728\u751F\u6210\u7684\u5716\u7247\uFF1B\u8CBC\u6587\u4FDD\u7559\u539F\u4F86\u7684\u5716\u7247\u3002"
|
|
8838
|
+
},
|
|
8839
|
+
social_pictures_attach: {
|
|
8840
|
+
en: (a) => {
|
|
8841
|
+
const n = Array.isArray(a.urls) ? a.urls.length : 0;
|
|
8842
|
+
return `${a.append ? "Add" : "Put"} your ${n} picture${n === 1 ? "" : "s"} ${a.append ? "after the pictures on" : "on"} post #${a.id}${a.append ? "" : ", in place of its picture"} (free). On LinkedIn a set goes out as a swipeable PDF carousel.`;
|
|
8843
|
+
},
|
|
8844
|
+
"zh-Hant": (a) => {
|
|
8845
|
+
const n = Array.isArray(a.urls) ? a.urls.length : 0;
|
|
8846
|
+
return `${a.append ? `\u5728\u8CBC\u6587 #${a.id} \u73FE\u6709\u5716\u7247\u5F8C\u52A0\u4E0A` : `\u628A\u8CBC\u6587 #${a.id} \u7684\u5716\u7247\u63DB\u6210`}\u4F60\u7684 ${n} \u5F35\u5716\u7247\uFF08\u514D\u8CBB\uFF09\u3002\u5728 LinkedIn \u4E0A\uFF0C\u4E00\u7D44\u5716\u7247\u6703\u4EE5\u53EF\u6ED1\u52D5\u7684 PDF \u8F2A\u64AD\u767C\u51FA\u3002`;
|
|
8847
|
+
}
|
|
8848
|
+
},
|
|
8849
|
+
social_carousel_pdf_attach: {
|
|
8850
|
+
en: (a) => `LinkedIn post #${a.id} goes out as your PDF, a swipeable carousel${a.title ? ` titled ${q(a.title)}` : ""}; its first pages become the post's pictures (free).`,
|
|
8851
|
+
"zh-Hant": (a) => `LinkedIn \u8CBC\u6587 #${a.id} \u6703\u4EE5\u4F60\u7684 PDF \u767C\u51FA\uFF0C\u6210\u70BA\u53EF\u6ED1\u52D5\u7684\u8F2A\u64AD${a.title ? `\uFF0C\u6A19\u984C ${q(a.title)}` : ""}\uFF1BPDF \u7684\u524D\u5E7E\u9801\u4E5F\u6703\u6210\u70BA\u8CBC\u6587\u7684\u5716\u7247\uFF08\u514D\u8CBB\uFF09\u3002`
|
|
8852
|
+
},
|
|
8853
|
+
social_carousel_set: {
|
|
8854
|
+
en: (a) => `LinkedIn post #${a.id} goes out ${a.format === "images" ? "as several pictures" : a.format === "pdf" ? "as a swipeable PDF carousel" : "as it is set"}${a.title ? `, titled ${q(a.title)}` : ""}.`,
|
|
8855
|
+
"zh-Hant": (a) => `LinkedIn \u8CBC\u6587 #${a.id} ${a.format === "images" ? "\u4EE5\u591A\u5F35\u5716\u7247\u767C\u51FA" : a.format === "pdf" ? "\u4EE5\u53EF\u6ED1\u52D5\u7684 PDF \u8F2A\u64AD\u767C\u51FA" : "\u7167\u76EE\u524D\u8A2D\u5B9A\u767C\u51FA"}${a.title ? `\uFF0C\u6A19\u984C ${q(a.title)}` : ""}\u3002`
|
|
8772
8856
|
},
|
|
8773
8857
|
social_move: {
|
|
8774
|
-
en: (a) => `Move
|
|
8775
|
-
"zh-Hant": (a) => `\u628A
|
|
8858
|
+
en: (a) => `Move post #${a.id} ${a.to === "top" ? "to the top of its channel's queue" : `${a.to} in its channel's queue`}.`,
|
|
8859
|
+
"zh-Hant": (a) => `\u628A\u8CBC\u6587 #${a.id} ${a.to === "top" ? "\u79FB\u5230\u8A72\u6E20\u9053\u968A\u5217\u7684\u6700\u524D" : a.to === "up" ? "\u5728\u8A72\u6E20\u9053\u968A\u5217\u4E2D\u4E0A\u79FB" : "\u5728\u8A72\u6E20\u9053\u968A\u5217\u4E2D\u4E0B\u79FB"}\u3002`
|
|
8776
8860
|
},
|
|
8777
8861
|
social_set_paused: {
|
|
8778
|
-
en: (a) =>
|
|
8779
|
-
|
|
8862
|
+
en: (a) => {
|
|
8863
|
+
const on2 = a.channel ? `${CHANNEL_EN[String(a.channel)] ?? a.channel} posting` : "posting on every channel";
|
|
8864
|
+
return a.paused ? `Pause ${on2}.` : `Resume ${on2}.`;
|
|
8865
|
+
},
|
|
8866
|
+
"zh-Hant": (a) => {
|
|
8867
|
+
const on2 = a.channel ? `${CHANNEL_ZH[String(a.channel)] ?? a.channel} ` : "\u6240\u6709\u6E20\u9053\u7684";
|
|
8868
|
+
return a.paused ? `\u66AB\u505C${on2}\u767C\u6587\u3002` : `\u6062\u5FA9${on2}\u767C\u6587\u3002`;
|
|
8869
|
+
}
|
|
8780
8870
|
},
|
|
8781
8871
|
social_from_article: {
|
|
8782
|
-
en: (a) => `Write a
|
|
8783
|
-
"zh-Hant": (a) => `\u6839\u64DA ${q(a.title || a.source_id)}
|
|
8872
|
+
en: (a) => `Write ${channelsEn(a) ? `a ${channelsEn(a)} post` : "a post for each connected channel"} about ${q(a.title || a.source_id)}; it waits for your OK unless your posts go out on their own${placeEn(a.position) ? ` (then it is queued ${placeEn(a.position)})` : ""}.`,
|
|
8873
|
+
"zh-Hant": (a) => `\u6839\u64DA ${q(a.title || a.source_id)} ${channelsZh(a) ? `\u5BEB\u4E00\u7BC7 ${channelsZh(a)} \u8CBC\u6587` : "\u70BA\u6BCF\u500B\u5DF2\u9023\u63A5\u7684\u6E20\u9053\u5404\u5BEB\u4E00\u7BC7\u8CBC\u6587"}\uFF1B\u9664\u975E\u4F60\u8A2D\u5B9A\u8CBC\u6587\u81EA\u52D5\u767C\u51FA\uFF0C\u5426\u5247\u6703\u7B49\u4F60\u6279\u51C6${placeZh(a.position) ? `\uFF08\u81EA\u52D5\u767C\u51FA\u6642${placeZh(a.position)}\uFF09` : ""}\u3002`
|
|
8784
8874
|
},
|
|
8785
8875
|
social_plan_days: {
|
|
8786
|
-
en: (a) => `Write
|
|
8787
|
-
"zh-Hant": (a) => `\u7528\u4F60\u6700\u597D\u7684\u6587\u7AE0\u5BEB\u672A\u4F86 ${a.days} \u5929\u7684
|
|
8876
|
+
en: (a) => `Write ${channelsEn(a) ? `${channelsEn(a)} posts` : "posts on each connected channel"} for the next ${a.days} day${Number(a.days) === 1 ? "" : "s"} from your best articles; they wait for your OK unless your posts go out on their own${placeEn(a.position) ? ` (then they are queued ${placeEn(a.position)})` : ""}.`,
|
|
8877
|
+
"zh-Hant": (a) => `\u7528\u4F60\u6700\u597D\u7684\u6587\u7AE0\u5BEB\u672A\u4F86 ${a.days} \u5929\u7684${channelsZh(a) ? ` ${channelsZh(a)} ` : "\u5404\u6E20\u9053"}\u8CBC\u6587\uFF1B\u9664\u975E\u4F60\u8A2D\u5B9A\u8CBC\u6587\u81EA\u52D5\u767C\u51FA\uFF0C\u5426\u5247\u6703\u7B49\u4F60\u6279\u51C6${placeZh(a.position) ? `\uFF08\u81EA\u52D5\u767C\u51FA\u6642${placeZh(a.position)}\uFF09` : ""}\u3002`
|
|
8788
8878
|
},
|
|
8789
8879
|
social_idea_add: {
|
|
8790
8880
|
en: (a) => `Add the LinkedIn idea ${q(a.title)}.`,
|
|
@@ -8799,8 +8889,8 @@ var confirmCopy = {
|
|
|
8799
8889
|
"zh-Hant": (a) => `\u522A\u9664 LinkedIn idea #${a.id}${a.title ? ` ${q(a.title)}` : ""}\u3002`
|
|
8800
8890
|
},
|
|
8801
8891
|
social_idea_write: {
|
|
8802
|
-
en: (a) => `Write the
|
|
8803
|
-
"zh-Hant": (a) => `\u70BA idea #${a.id}${a.title ? ` ${q(a.title)}` : ""} \u5BEB
|
|
8892
|
+
en: (a) => `Write ${channelsEn(a) ? `the ${channelsEn(a)} post` : "a post for each connected channel"} for idea #${a.id}${a.title ? ` ${q(a.title)}` : ""}; your own idea's posts wait for your OK unless your posts go out on their own${placeEn(a.position) ? ` (then it is queued ${placeEn(a.position)})` : ""}.`,
|
|
8893
|
+
"zh-Hant": (a) => `\u70BA idea #${a.id}${a.title ? ` ${q(a.title)}` : ""} \u5BEB${channelsZh(a) ? ` ${channelsZh(a)} ` : "\u5404\u6E20\u9053\u7684"}\u8CBC\u6587\uFF1B\u4F60\u81EA\u5DF1 idea \u7684\u8CBC\u6587\uFF0C\u9664\u975E\u4F60\u8A2D\u5B9A\u81EA\u52D5\u767C\u51FA\uFF0C\u5426\u5247\u6703\u7B49\u4F60\u6279\u51C6${placeZh(a.position) ? `\uFF08\u81EA\u52D5\u767C\u51FA\u6642${placeZh(a.position)}\uFF09` : ""}\u3002`
|
|
8804
8894
|
},
|
|
8805
8895
|
social_idea_save: {
|
|
8806
8896
|
en: (a) => `Save this LinkedIn post for idea #${a.id} (one AI generation): ${q(a.text)}`,
|
|
@@ -8866,8 +8956,8 @@ var confirmCopy = {
|
|
|
8866
8956
|
"zh-Hant": () => "\u7522\u751F\u4E00\u500B\u9023\u63A5 LinkedIn \u7684\u9023\u7D50\u3002"
|
|
8867
8957
|
},
|
|
8868
8958
|
social_approve_all: {
|
|
8869
|
-
en: (a) => `Approve every ${a.channel ? `${CHANNEL_EN[String(a.channel)] ?? a.channel} ` : ""}post waiting for your OK${a.channel ? "" : ", on every channel"}, including any held for a picture check. They go out one by one at each channel's posting times, where people can see them.`,
|
|
8870
|
-
"zh-Hant": (a) => `\u6279\u51C6\u6240\u6709\u7B49\u4F60\u78BA\u8A8D\u7684${a.channel ? ` ${CHANNEL_ZH[String(a.channel)] ?? a.channel} ` : "\uFF08\u6240\u6709\u6E20\u9053\uFF09"}\u8CBC\u6587\uFF0C\u5305\u62EC\u56E0\u5716\u7247\u6AA2\u67E5\u800C\u66AB\u505C\u7684\u3002\u5B83\u5011\u6703\u5728\u5404\u6E20\u9053\
|
|
8959
|
+
en: (a) => `Approve every ${a.channel ? `${CHANNEL_EN[String(a.channel)] ?? a.channel} ` : ""}post waiting for your OK${a.channel ? "" : ", on every channel"}, including any held for a picture check. They go out one by one at each channel's posting times${placeEn(a.position) ? `, queued ${placeEn(a.position)}` : ""}, where people can see them.`,
|
|
8960
|
+
"zh-Hant": (a) => `\u6279\u51C6\u6240\u6709\u7B49\u4F60\u78BA\u8A8D\u7684${a.channel ? ` ${CHANNEL_ZH[String(a.channel)] ?? a.channel} ` : "\uFF08\u6240\u6709\u6E20\u9053\uFF09"}\u8CBC\u6587\uFF0C\u5305\u62EC\u56E0\u5716\u7247\u6AA2\u67E5\u800C\u66AB\u505C\u7684\u3002\u5B83\u5011\u6703\u5728\u5404\u6E20\u9053\u7684\u767C\u6587\u6642\u9593\u9010\u7BC7\u767C\u51FA${placeZh(a.position) ? `\uFF08${placeZh(a.position)}\uFF09` : ""}\uFF0C\u5927\u5BB6\u90FD\u770B\u5F97\u5230\u3002`
|
|
8871
8961
|
},
|
|
8872
8962
|
series_create: {
|
|
8873
8963
|
en: (a) => `Plan and write a LinkedIn series about ${q(a.brief)}: ${a.count || 3} posts that read as a series. Each waits in Review for your OK. Uses one AI generation a post.`,
|
|
@@ -9022,6 +9112,18 @@ var int = (v, min, max, fallback) => {
|
|
|
9022
9112
|
const n = Math.trunc(Number(v));
|
|
9023
9113
|
return Number.isFinite(n) ? Math.min(max, Math.max(min, n)) : fallback;
|
|
9024
9114
|
};
|
|
9115
|
+
var positionParam = (what, fallback) => ({
|
|
9116
|
+
type: "string",
|
|
9117
|
+
description: `Where ${what} go: "front", "end", "random", or a number of items to go after ("0" = front; more than there are = end). ${fallback} Scheduled or automated runs should use "end" so they don't jump ahead of what is already waiting.`
|
|
9118
|
+
});
|
|
9119
|
+
var positionOf = (value) => {
|
|
9120
|
+
if (value == null || value === "") return void 0;
|
|
9121
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
|
|
9122
|
+
const text3 = String(value).trim().toLowerCase();
|
|
9123
|
+
if (text3 === "front" || text3 === "end" || text3 === "random") return text3;
|
|
9124
|
+
if (/^\d+$/.test(text3)) return Number(text3);
|
|
9125
|
+
throw new Error('position must be "front", "end", "random" or a number of items to go after (0 = front).');
|
|
9126
|
+
};
|
|
9025
9127
|
function tool(t) {
|
|
9026
9128
|
const out = { ...t };
|
|
9027
9129
|
if (t.risk !== "R0" && !t.confirm)
|
|
@@ -9716,19 +9818,20 @@ var blogTools = [
|
|
|
9716
9818
|
}),
|
|
9717
9819
|
tool({
|
|
9718
9820
|
name: "idea_plan",
|
|
9719
|
-
description: "Add a waiting idea to the
|
|
9821
|
+
description: "Add a waiting idea to the plan (the plan holds up to 100): at the end, or where `position` says (front, random, after N planned posts). The answer's position counts from 1.",
|
|
9720
9822
|
parameters: {
|
|
9721
9823
|
type: "object",
|
|
9722
9824
|
properties: {
|
|
9723
9825
|
id: { type: "string" },
|
|
9724
|
-
title: { type: "string", description: "As listed (shown on the approval card and in History)" }
|
|
9826
|
+
title: { type: "string", description: "As listed (shown on the approval card and in History)" },
|
|
9827
|
+
position: positionParam("the idea", "Leave out for the end of the plan.")
|
|
9725
9828
|
},
|
|
9726
9829
|
required: ["id", "title"]
|
|
9727
9830
|
},
|
|
9728
9831
|
group: "blog",
|
|
9729
9832
|
need: write,
|
|
9730
9833
|
risk: "W1",
|
|
9731
|
-
run: async (ctx, { id }) => ws(ctx).planIdea(id)
|
|
9834
|
+
run: async (ctx, { id, position }) => ws(ctx).planIdea(id, positionOf(position))
|
|
9732
9835
|
}),
|
|
9733
9836
|
tool({
|
|
9734
9837
|
name: "idea_discard",
|
|
@@ -10899,6 +11002,32 @@ var opsTools = [
|
|
|
10899
11002
|
const { reviewer, comment, ...rest } = r;
|
|
10900
11003
|
return { ...rest, ...untrusted({ reviewer, comment }) };
|
|
10901
11004
|
}
|
|
11005
|
+
}),
|
|
11006
|
+
tool({
|
|
11007
|
+
name: "social_post_get",
|
|
11008
|
+
description: "One social post in full: its whole text (social_list shortens it to about 300 characters), its pictures, its link, its channel and status, when it goes out, and why it was held before going out, if it was. Use it to check a post's ending (a web address or phone number on Google, a cut-off sentence) before approving or fixing it.",
|
|
11009
|
+
parameters: { type: "object", properties: { id: { type: "integer" } }, required: ["id"] },
|
|
11010
|
+
group: "social",
|
|
11011
|
+
need: { resource: "autopilot", level: "read" },
|
|
11012
|
+
risk: "R0",
|
|
11013
|
+
run: async (ctx, { id }) => {
|
|
11014
|
+
const p = await ws(ctx).socialPost(int(id, 1, Number.MAX_SAFE_INTEGER, 0));
|
|
11015
|
+
return {
|
|
11016
|
+
id: p.id,
|
|
11017
|
+
channel: p.provider,
|
|
11018
|
+
status: p.status,
|
|
11019
|
+
eta: p.eta ?? null,
|
|
11020
|
+
text: p.social_copy,
|
|
11021
|
+
characters: String(p.social_copy ?? "").length,
|
|
11022
|
+
picture: p.image_url ?? null,
|
|
11023
|
+
pictures: p.image_urls ?? [],
|
|
11024
|
+
// LinkedIn with a set (or an uploaded PDF): a PDF carousel or several pictures.
|
|
11025
|
+
carousel: p.carousel ?? null,
|
|
11026
|
+
link: p.cta_url ?? p.source_url ?? null,
|
|
11027
|
+
held: p.held ?? null,
|
|
11028
|
+
failure: p.failure_message ?? null
|
|
11029
|
+
};
|
|
11030
|
+
}
|
|
10902
11031
|
})
|
|
10903
11032
|
];
|
|
10904
11033
|
|
|
@@ -10921,7 +11050,7 @@ var publishingTools = [
|
|
|
10921
11050
|
}),
|
|
10922
11051
|
tool({
|
|
10923
11052
|
name: "posts_per_day_set",
|
|
10924
|
-
description: "
|
|
11053
|
+
description: "Blog posts a day for the website (1, 2, 12, 24, 48 or 96, up to the plan's limit). Sites AI CMO pushes to: how many it publishes a day. Plugin / API sites: the most the site collects a day (it still decides how often it checks).",
|
|
10925
11054
|
parameters: {
|
|
10926
11055
|
type: "object",
|
|
10927
11056
|
properties: {
|
|
@@ -11484,6 +11613,7 @@ var channelsParam = {
|
|
|
11484
11613
|
items: { type: "string", enum: CHANNEL_KEYS },
|
|
11485
11614
|
description: "Which channels to write for: linkedin, facebook, instagram, google_business. Leave out to write for every connected channel (one post each, one credit each); give one only when the user names it. YouTube and TikTok take a video: use social_video_add."
|
|
11486
11615
|
};
|
|
11616
|
+
var queuedAt = positionParam("the posts", "Leave out for the front (the next posting times).");
|
|
11487
11617
|
var channelsOf = (value) => {
|
|
11488
11618
|
const list2 = Array.isArray(value) ? value.map((v) => String(v).toLowerCase()).filter((v) => CHANNEL_KEYS.includes(v)) : [];
|
|
11489
11619
|
return list2.length ? list2 : void 0;
|
|
@@ -11576,24 +11706,28 @@ var socialTools = [
|
|
|
11576
11706
|
}),
|
|
11577
11707
|
tool({
|
|
11578
11708
|
name: "social_approve",
|
|
11579
|
-
description: "Approve
|
|
11709
|
+
description: "Approve post drafts: each goes to the front of the user's queue and out at the next posting times, or where `position` says (end, random, after N posts). Pass every id to approve in one call.",
|
|
11580
11710
|
parameters: {
|
|
11581
11711
|
type: "object",
|
|
11582
|
-
properties: {
|
|
11712
|
+
properties: {
|
|
11713
|
+
ids: { type: "array", items: { type: "integer" }, description: "The drafts to approve" },
|
|
11714
|
+
position: queuedAt
|
|
11715
|
+
},
|
|
11583
11716
|
required: ["ids"]
|
|
11584
11717
|
},
|
|
11585
11718
|
group: "social",
|
|
11586
11719
|
need: write5,
|
|
11587
11720
|
risk: "W3",
|
|
11588
11721
|
gates: ["VER", "LI"],
|
|
11589
|
-
run: async (ctx, { ids: ids2, id }) => {
|
|
11722
|
+
run: async (ctx, { ids: ids2, id, position }) => {
|
|
11590
11723
|
const list2 = (Array.isArray(ids2) && ids2.length ? ids2 : id != null ? [id] : []).map(Number).slice(0, 20);
|
|
11591
11724
|
if (!list2.length) throw new Error("Which post?");
|
|
11725
|
+
const at = positionOf(position);
|
|
11592
11726
|
const api2 = ws(ctx);
|
|
11593
11727
|
const results = [];
|
|
11594
11728
|
for (const n of list2) {
|
|
11595
11729
|
try {
|
|
11596
|
-
await api2.approveSocial(n);
|
|
11730
|
+
await api2.approveSocial(n, at);
|
|
11597
11731
|
results.push({ id: n, ok: true });
|
|
11598
11732
|
} catch (e) {
|
|
11599
11733
|
results.push({ id: n, ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
@@ -11846,6 +11980,90 @@ var socialTools = [
|
|
|
11846
11980
|
return { id: out.id, picture: out.picture, pictures: out.image_urls?.length ? out.image_urls : [out.image_url] };
|
|
11847
11981
|
}
|
|
11848
11982
|
}),
|
|
11983
|
+
tool({
|
|
11984
|
+
name: "social_pictures_attach",
|
|
11985
|
+
description: "Put the user's own pictures on a post, in order: public https links to image files (JPEG, PNG or WebP), for example slides another assistant made. 1-20 pictures; append adds them after the post's current ones. Free (no AI). They replace Maggie's picture; a post from an idea shares them with its other channels. Instagram and Facebook post the first 10, Google Business the first. On LinkedIn a set goes out as a swipeable PDF carousel unless social_carousel_set says images. Preview first.",
|
|
11986
|
+
parameters: {
|
|
11987
|
+
type: "object",
|
|
11988
|
+
properties: {
|
|
11989
|
+
id: { type: "integer", description: "The post (from social_list), not gone out yet" },
|
|
11990
|
+
urls: {
|
|
11991
|
+
type: "array",
|
|
11992
|
+
items: { type: "string" },
|
|
11993
|
+
description: "Public https links to the image files, in the order they should appear"
|
|
11994
|
+
},
|
|
11995
|
+
append: { type: "boolean", description: "Add after the post's current pictures instead of replacing them" }
|
|
11996
|
+
},
|
|
11997
|
+
required: ["id", "urls"]
|
|
11998
|
+
},
|
|
11999
|
+
group: "social",
|
|
12000
|
+
need: write5,
|
|
12001
|
+
risk: "W1",
|
|
12002
|
+
gates: ["VER"],
|
|
12003
|
+
run: async (ctx, { id, urls, append }) => {
|
|
12004
|
+
const list2 = (Array.isArray(urls) ? urls : []).map((u) => String(u).trim()).filter(Boolean);
|
|
12005
|
+
if (!list2.length) throw new Error("Give at least one https link to a picture.");
|
|
12006
|
+
if (list2.length > 20) throw new Error("A post takes up to 20 pictures.");
|
|
12007
|
+
if (list2.some((u) => !u.startsWith("https://"))) throw new Error("Every picture needs a public https link.");
|
|
12008
|
+
const out = await ws(ctx).setSocialPictures(Number(id), { urls: list2, append: append === true });
|
|
12009
|
+
return {
|
|
12010
|
+
id: out.id,
|
|
12011
|
+
pictures: out.image_urls?.length ? out.image_urls : [out.image_url],
|
|
12012
|
+
carousel: out.carousel ?? null
|
|
12013
|
+
};
|
|
12014
|
+
}
|
|
12015
|
+
}),
|
|
12016
|
+
tool({
|
|
12017
|
+
name: "social_carousel_pdf_attach",
|
|
12018
|
+
description: "A LinkedIn post goes out as this PDF: a swipeable document carousel (a public https link to the PDF, up to 300 pages and 20 MB, no password). Its first 20 pages also become the post's pictures (what its other channels post). Free. Change the pictures later and the carousel is made from them instead. Preview first.",
|
|
12019
|
+
parameters: {
|
|
12020
|
+
type: "object",
|
|
12021
|
+
properties: {
|
|
12022
|
+
id: { type: "integer", description: "A LinkedIn post (from social_list), not gone out yet" },
|
|
12023
|
+
url: { type: "string", description: "Public https link to the PDF file" },
|
|
12024
|
+
title: {
|
|
12025
|
+
type: "string",
|
|
12026
|
+
description: "Shown on the carousel (up to 120 characters); the post's topic if left out"
|
|
12027
|
+
}
|
|
12028
|
+
},
|
|
12029
|
+
required: ["id", "url"]
|
|
12030
|
+
},
|
|
12031
|
+
group: "social",
|
|
12032
|
+
need: write5,
|
|
12033
|
+
risk: "W1",
|
|
12034
|
+
gates: ["VER", "LI"],
|
|
12035
|
+
run: async (ctx, { id, url, title }) => {
|
|
12036
|
+
const link = String(url ?? "").trim();
|
|
12037
|
+
if (!link.startsWith("https://")) throw new Error("Give a public https link to the PDF.");
|
|
12038
|
+
const out = await ws(ctx).attachSocialCarouselPdf(Number(id), { url: link, ...title ? { title } : {} });
|
|
12039
|
+
return { id: out.id, pages: out.pages, pictures: out.pictures, carousel: out.carousel ?? null };
|
|
12040
|
+
}
|
|
12041
|
+
}),
|
|
12042
|
+
tool({
|
|
12043
|
+
name: "social_carousel_set",
|
|
12044
|
+
description: "How a LinkedIn post with a set of pictures goes out: format pdf (a swipeable PDF carousel made from the pictures, the default) or images (several pictures); and the carousel's title. Preview first.",
|
|
12045
|
+
parameters: {
|
|
12046
|
+
type: "object",
|
|
12047
|
+
properties: {
|
|
12048
|
+
id: { type: "integer", description: "A LinkedIn post with a set of pictures (from social_list)" },
|
|
12049
|
+
format: { type: "string", enum: ["pdf", "images"] },
|
|
12050
|
+
title: { type: "string", description: "Shown on the carousel (up to 120 characters)" }
|
|
12051
|
+
},
|
|
12052
|
+
required: ["id"]
|
|
12053
|
+
},
|
|
12054
|
+
group: "social",
|
|
12055
|
+
need: write5,
|
|
12056
|
+
risk: "W1",
|
|
12057
|
+
gates: ["VER"],
|
|
12058
|
+
run: async (ctx, { id, format, title }) => {
|
|
12059
|
+
if (format == null && title == null) throw new Error("Say the format (pdf or images) or a title.");
|
|
12060
|
+
const out = await ws(ctx).setSocialCarousel(Number(id), {
|
|
12061
|
+
...format ? { format } : {},
|
|
12062
|
+
...title != null ? { title: String(title) } : {}
|
|
12063
|
+
});
|
|
12064
|
+
return { id: out.id, carousel: out.carousel ?? null };
|
|
12065
|
+
}
|
|
12066
|
+
}),
|
|
11849
12067
|
tool({
|
|
11850
12068
|
name: "social_post_insights",
|
|
11851
12069
|
description: "How a posted Instagram post is doing: views, reach, likes, comments, saves and shares, read from Instagram (kept 15 minutes; refresh: true reads again). Saves and shares per reach are the best signs of a good post. available: false says why (not posted yet, deleted on Instagram, the account needs reconnecting).",
|
|
@@ -11954,13 +12172,14 @@ var socialTools = [
|
|
|
11954
12172
|
}),
|
|
11955
12173
|
tool({
|
|
11956
12174
|
name: "social_from_article",
|
|
11957
|
-
description: "Maggie writes a post about one published article (id from social_sources_list) for each channel. It waits for the user's OK as a draft, unless their approval mode is auto (then it
|
|
12175
|
+
description: "Maggie writes a post about one published article (id from social_sources_list) for each channel. It waits for the user's OK as a draft, unless their approval mode is auto (then it is queued: at the front, or where `position` says). Written in the background (a minute or two): this returns at once with a job. Tell the user Maggie is writing and they will be told when it's ready; before saying what was written, check social_write_status (then social_list for the posts).",
|
|
11958
12176
|
parameters: {
|
|
11959
12177
|
type: "object",
|
|
11960
12178
|
properties: {
|
|
11961
12179
|
source_id: { type: "string" },
|
|
11962
12180
|
title: { type: "string", description: "As listed (shown on the approval card and in History)" },
|
|
11963
|
-
channels: channelsParam
|
|
12181
|
+
channels: channelsParam,
|
|
12182
|
+
position: queuedAt
|
|
11964
12183
|
},
|
|
11965
12184
|
required: ["source_id", "title"]
|
|
11966
12185
|
},
|
|
@@ -11968,17 +12187,18 @@ var socialTools = [
|
|
|
11968
12187
|
need: write5,
|
|
11969
12188
|
risk: "W2",
|
|
11970
12189
|
gates: ["VER", "LI", "LLM"],
|
|
11971
|
-
run: async (ctx, { source_id, channels }) => writing((await ws(ctx).startSocialFromPost(String(source_id), channelsOf(channels))).job)
|
|
12190
|
+
run: async (ctx, { source_id, channels, position }) => writing((await ws(ctx).startSocialFromPost(String(source_id), channelsOf(channels), positionOf(position))).job)
|
|
11972
12191
|
}),
|
|
11973
12192
|
tool({
|
|
11974
12193
|
name: "social_plan_days",
|
|
11975
|
-
description: "Maggie writes posts for the next 1\u20137 days from the best recent articles, on each channel. They wait for the user's OK as drafts, unless the user's approval mode is auto (then they are queued in order). Written in the background (a minute or two): this returns at once with a job. Tell the user Maggie is writing and they will be told when it's ready; before saying what was written, check social_write_status (then social_list for the posts).",
|
|
12194
|
+
description: "Maggie writes posts for the next 1\u20137 days from the best recent articles, on each channel. They wait for the user's OK as drafts, unless the user's approval mode is auto (then they are queued in order: at the front, or where `position` says). Written in the background (a minute or two): this returns at once with a job. Tell the user Maggie is writing and they will be told when it's ready; before saying what was written, check social_write_status (then social_list for the posts).",
|
|
11976
12195
|
parameters: {
|
|
11977
12196
|
type: "object",
|
|
11978
12197
|
properties: {
|
|
11979
12198
|
days: { type: "integer", minimum: 1, maximum: 7 },
|
|
11980
12199
|
timezone: { type: "string", description: "IANA timezone, if known" },
|
|
11981
|
-
channels: channelsParam
|
|
12200
|
+
channels: channelsParam,
|
|
12201
|
+
position: queuedAt
|
|
11982
12202
|
},
|
|
11983
12203
|
required: ["days"]
|
|
11984
12204
|
},
|
|
@@ -11986,7 +12206,14 @@ var socialTools = [
|
|
|
11986
12206
|
need: write5,
|
|
11987
12207
|
risk: "W2",
|
|
11988
12208
|
gates: ["VER", "LI", "LLM"],
|
|
11989
|
-
run: async (ctx, { days, timezone, channels }) => writing(
|
|
12209
|
+
run: async (ctx, { days, timezone, channels, position }) => writing(
|
|
12210
|
+
(await ws(ctx).startSocialPlan(
|
|
12211
|
+
int(days, 1, 7, 3),
|
|
12212
|
+
timezone || "UTC",
|
|
12213
|
+
channelsOf(channels),
|
|
12214
|
+
positionOf(position)
|
|
12215
|
+
)).job
|
|
12216
|
+
)
|
|
11990
12217
|
}),
|
|
11991
12218
|
tool({
|
|
11992
12219
|
name: "social_ideas_list",
|
|
@@ -12059,13 +12286,14 @@ var socialTools = [
|
|
|
12059
12286
|
}),
|
|
12060
12287
|
tool({
|
|
12061
12288
|
name: "social_idea_write",
|
|
12062
|
-
description: "Maggie writes one idea's post for each channel (1 credit per post). The user's own idea's posts wait for their OK (queued straight away only in approval mode auto); a campaign's wait for the campaign to start. Written in the background (a minute or two): this returns at once with a job. Tell the user Maggie is writing and they will be told when it's ready; before saying what was written, check social_write_status (then social_list for the posts).",
|
|
12289
|
+
description: "Maggie writes one idea's post for each channel (1 credit per post). The user's own idea's posts wait for their OK (queued straight away only in approval mode auto, at the front or where `position` says); a campaign's wait for the campaign to start. Written in the background (a minute or two): this returns at once with a job. Tell the user Maggie is writing and they will be told when it's ready; before saying what was written, check social_write_status (then social_list for the posts).",
|
|
12063
12290
|
parameters: {
|
|
12064
12291
|
type: "object",
|
|
12065
12292
|
properties: {
|
|
12066
12293
|
id: { type: "integer" },
|
|
12067
12294
|
title: { type: "string", description: "As listed (shown on the approval card and in History)" },
|
|
12068
|
-
channels: channelsParam
|
|
12295
|
+
channels: channelsParam,
|
|
12296
|
+
position: queuedAt
|
|
12069
12297
|
},
|
|
12070
12298
|
required: ["id", "title"]
|
|
12071
12299
|
},
|
|
@@ -12073,7 +12301,7 @@ var socialTools = [
|
|
|
12073
12301
|
need: write5,
|
|
12074
12302
|
risk: "W2",
|
|
12075
12303
|
gates: ["VER", "LI", "CR", "LLM"],
|
|
12076
|
-
run: async (ctx, { id, channels }) => writing((await ws(ctx).startWriteSocialIdea(Number(id), channelsOf(channels))).job)
|
|
12304
|
+
run: async (ctx, { id, channels, position }) => writing((await ws(ctx).startWriteSocialIdea(Number(id), channelsOf(channels), positionOf(position))).job)
|
|
12077
12305
|
}),
|
|
12078
12306
|
tool({
|
|
12079
12307
|
name: "social_idea_brief",
|
|
@@ -12199,13 +12427,16 @@ var socialTools = [
|
|
|
12199
12427
|
}),
|
|
12200
12428
|
tool({
|
|
12201
12429
|
name: "social_approve_all",
|
|
12202
|
-
description: "Approve every post waiting for the user's OK (not a series that hasn't been approved), on one channel or all: they go out one by one at each channel's next posting times.",
|
|
12203
|
-
parameters: {
|
|
12430
|
+
description: "Approve every post waiting for the user's OK (not a series that hasn't been approved), on one channel or all: they go out one by one at each channel's next posting times (from the front of the queue, or where `position` says).",
|
|
12431
|
+
parameters: {
|
|
12432
|
+
type: "object",
|
|
12433
|
+
properties: { channel: { type: "string", enum: CHANNEL_KEYS }, position: queuedAt }
|
|
12434
|
+
},
|
|
12204
12435
|
group: "social",
|
|
12205
12436
|
need: write5,
|
|
12206
12437
|
risk: "W3",
|
|
12207
12438
|
gates: ["VER", "LI"],
|
|
12208
|
-
run: async (ctx, { channel }) => ws(ctx).approveAllSocial(channelsOf(channel ? [channel] : [])?.[0])
|
|
12439
|
+
run: async (ctx, { channel, position }) => ws(ctx).approveAllSocial(channelsOf(channel ? [channel] : [])?.[0], positionOf(position))
|
|
12209
12440
|
}),
|
|
12210
12441
|
tool({
|
|
12211
12442
|
name: "series_create",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "CMO.so's MCP server for Claude Desktop, Cursor, VS Code and other MCP clients: manage your websites, blog posts, LinkedIn queue and results with Maggie.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|