@cmo-so/mcp 0.8.1 → 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.
Files changed (2) hide show
  1. package/dist/cmo-mcp.mjs +268 -63
  2. 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.8.1",
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 of the plan. */
7673
- planIdea: (id) => post(`${base}/ideas/${encodeURIComponent(id)}/plan`),
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; the post comes back with its eta. */
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) => post(
7848
- `${base}/social/approve-all${channel ? `?channel=${channel}` : ""}`
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
- startSocialFromPost: (sourcePostId, channels) => post(`${base}/social/from-post`, {
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 for pushed blogs (one of `choices`, up to the plan's limit). */
8185
- setPostsPerDay: (postsPerDay) => put(`${base}/autopilot/cadence`, {
8186
- posts_per_day: postsPerDay
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`
@@ -8469,6 +8497,14 @@ var CHANNEL_EN = {
8469
8497
  google_business: "Google Business"
8470
8498
  };
8471
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
+ };
8472
8508
  var named = (title, id) => {
8473
8509
  if (title != null && String(title).trim()) return ` ${q(title)}`;
8474
8510
  const s = String(id ?? "").trim();
@@ -8478,6 +8514,20 @@ var list = (xs, n = 5) => {
8478
8514
  const a = (Array.isArray(xs) ? xs : xs == null ? [] : [xs]).map(String);
8479
8515
  return a.length > n ? `${a.slice(0, n).join(", ")} +${a.length - n}` : a.join(", ");
8480
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
+ };
8481
8531
  var ids = (a) => (Array.isArray(a.ids) && a.ids.length ? a.ids : a.id != null ? [a.id] : []).map(Number);
8482
8532
  var keys = (a) => Object.keys(a ?? {}).filter((k) => a[k] !== void 0);
8483
8533
  var videoChannels = (a) => (Array.isArray(a.channels) && a.channels.length ? a.channels : ["youtube"]).map(
@@ -8637,8 +8687,16 @@ var confirmCopy = {
8637
8687
  "zh-Hant": (a) => `${Number(a.delta) > 0 ? "\u63D0\u9AD8" : "\u964D\u4F4E"}${named(a.title, a.id)} \u7684\u512A\u5148\u7D1A\u3002`
8638
8688
  },
8639
8689
  idea_plan: {
8640
- en: (a) => `Add${named(a.title, a.id)} to the end of the plan.`,
8641
- "zh-Hant": (a) => `\u628A${named(a.title, a.id)} \u52A0\u5230\u8A08\u5283\u6700\u5F8C\u3002`
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
+ }
8642
8700
  },
8643
8701
  idea_discard: {
8644
8702
  en: (a) => `Remove the idea${named(a.title, a.id)}.`,
@@ -8708,23 +8766,29 @@ var confirmCopy = {
8708
8766
  },
8709
8767
  // ----------------------------------------------------------------- social
8710
8768
  social_update_copy: {
8711
- en: (a) => `Change the text of LinkedIn post #${a.id}.`,
8712
- "zh-Hant": (a) => `\u4FEE\u6539 LinkedIn \u8CBC\u6587 #${a.id} \u7684\u5167\u5BB9\u3002`
8769
+ en: (a) => `Change the text of post #${a.id}.`,
8770
+ "zh-Hant": (a) => `\u4FEE\u6539\u8CBC\u6587 #${a.id} \u7684\u5167\u5BB9\u3002`
8713
8771
  },
8714
8772
  social_approve: {
8715
8773
  en: (a) => {
8716
8774
  const n = ids(a);
8717
- return n.length > 1 ? `Approve ${n.length} LinkedIn posts: ${n.map((i) => `#${i}`).join(", ")}. Each goes out at your next posting times, where your network can see it.` : `Approve LinkedIn post #${n[0]}. It goes out at your next posting time, where your network can see it.`;
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.`;
8718
8779
  },
8719
8780
  "zh-Hant": (a) => {
8720
8781
  const n = ids(a);
8721
- return n.length > 1 ? `\u6279\u51C6 ${n.length} \u7BC7 LinkedIn \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 LinkedIn \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`;
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`;
8722
8786
  }
8723
8787
  },
8724
- social_skip: { en: (a) => `Skip LinkedIn post #${a.id}.`, "zh-Hant": (a) => `\u8DF3\u904E LinkedIn \u8CBC\u6587 #${a.id}\u3002` },
8788
+ social_skip: { en: (a) => `Skip post #${a.id}.`, "zh-Hant": (a) => `\u8DF3\u904E\u8CBC\u6587 #${a.id}\u3002` },
8725
8789
  social_restore: {
8726
- en: (a) => `Bring back LinkedIn post #${a.id}.`,
8727
- "zh-Hant": (a) => `\u6062\u5FA9 LinkedIn \u8CBC\u6587 #${a.id}\u3002`
8790
+ en: (a) => `Bring back post #${a.id}.`,
8791
+ "zh-Hant": (a) => `\u6062\u5FA9\u8CBC\u6587 #${a.id}\u3002`
8728
8792
  },
8729
8793
  youtube_connect_link: {
8730
8794
  en: () => "Make a link to connect your YouTube channel.",
@@ -8765,28 +8829,52 @@ var confirmCopy = {
8765
8829
  "zh-Hant": (a) => `\u4FEE\u6539 YouTube \u9810\u8A2D\uFF1A${keys(a).join("\u3001") || "\u7121"}\u3002`
8766
8830
  },
8767
8831
  social_image_new: {
8768
- en: (a) => `Make a new picture for LinkedIn 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" : ""}.`,
8769
- "zh-Hant": (a) => `\u70BA LinkedIn \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`
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`
8770
8834
  },
8771
8835
  social_image_stop: {
8772
- en: () => "Stop the LinkedIn picture being made; the post keeps the one it had.",
8773
- "zh-Hant": () => "\u505C\u6B62\u6B63\u5728\u751F\u6210\u7684 LinkedIn \u5716\u7247\uFF1B\u8CBC\u6587\u4FDD\u7559\u539F\u4F86\u7684\u5716\u7247\u3002"
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`
8774
8856
  },
8775
8857
  social_move: {
8776
- en: (a) => `Move LinkedIn post #${a.id} ${a.to === "top" ? "to the top of" : a.to} Upcoming.`,
8777
- "zh-Hant": (a) => `\u628A LinkedIn \u8CBC\u6587 #${a.id} ${a.to === "top" ? "\u79FB\u5230\u5373\u5C07\u767C\u51FA\u7684\u6700\u524D" : a.to === "up" ? "\u4E0A\u79FB" : "\u4E0B\u79FB"}\u3002`
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`
8778
8860
  },
8779
8861
  social_set_paused: {
8780
- en: (a) => a.paused ? "Pause all LinkedIn posting." : "Resume LinkedIn posting.",
8781
- "zh-Hant": (a) => a.paused ? "\u66AB\u505C\u6240\u6709 LinkedIn \u767C\u6587\u3002" : "\u6062\u5FA9 LinkedIn \u767C\u6587\u3002"
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
+ }
8782
8870
  },
8783
8871
  social_from_article: {
8784
- en: (a) => `Write a LinkedIn post about ${q(a.title || a.source_id)}; it waits for your OK unless your LinkedIn posts go out on their own.`,
8785
- "zh-Hant": (a) => `\u6839\u64DA ${q(a.title || a.source_id)} \u5BEB\u4E00\u7BC7 LinkedIn \u8CBC\u6587\uFF1B\u9664\u975E\u4F60\u8A2D\u5B9A\u8CBC\u6587\u81EA\u52D5\u767C\u51FA\uFF0C\u5426\u5247\u6703\u7B49\u4F60\u6279\u51C6\u3002`
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`
8786
8874
  },
8787
8875
  social_plan_days: {
8788
- en: (a) => `Write LinkedIn posts for the next ${a.days} day${Number(a.days) === 1 ? "" : "s"} from your best articles; they wait for your OK unless your LinkedIn posts go out on their own.`,
8789
- "zh-Hant": (a) => `\u7528\u4F60\u6700\u597D\u7684\u6587\u7AE0\u5BEB\u672A\u4F86 ${a.days} \u5929\u7684 LinkedIn \u8CBC\u6587\uFF1B\u9664\u975E\u4F60\u8A2D\u5B9A\u8CBC\u6587\u81EA\u52D5\u767C\u51FA\uFF0C\u5426\u5247\u6703\u7B49\u4F60\u6279\u51C6\u3002`
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`
8790
8878
  },
8791
8879
  social_idea_add: {
8792
8880
  en: (a) => `Add the LinkedIn idea ${q(a.title)}.`,
@@ -8801,8 +8889,8 @@ var confirmCopy = {
8801
8889
  "zh-Hant": (a) => `\u522A\u9664 LinkedIn idea #${a.id}${a.title ? ` ${q(a.title)}` : ""}\u3002`
8802
8890
  },
8803
8891
  social_idea_write: {
8804
- en: (a) => `Write the LinkedIn post for idea #${a.id}${a.title ? ` ${q(a.title)}` : ""}; your own idea's post waits for your OK unless your LinkedIn posts go out on their own.`,
8805
- "zh-Hant": (a) => `\u70BA idea #${a.id}${a.title ? ` ${q(a.title)}` : ""} \u5BEB LinkedIn \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\u3002`
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`
8806
8894
  },
8807
8895
  social_idea_save: {
8808
8896
  en: (a) => `Save this LinkedIn post for idea #${a.id} (one AI generation): ${q(a.text)}`,
@@ -8868,8 +8956,8 @@ var confirmCopy = {
8868
8956
  "zh-Hant": () => "\u7522\u751F\u4E00\u500B\u9023\u63A5 LinkedIn \u7684\u9023\u7D50\u3002"
8869
8957
  },
8870
8958
  social_approve_all: {
8871
- 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.`,
8872
- "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\u63A5\u4E0B\u4F86\u7684\u767C\u6587\u6642\u9593\u9010\u7BC7\u767C\u51FA\uFF0C\u5927\u5BB6\u90FD\u770B\u5F97\u5230\u3002`
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`
8873
8961
  },
8874
8962
  series_create: {
8875
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.`,
@@ -9024,6 +9112,18 @@ var int = (v, min, max, fallback) => {
9024
9112
  const n = Math.trunc(Number(v));
9025
9113
  return Number.isFinite(n) ? Math.min(max, Math.max(min, n)) : fallback;
9026
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
+ };
9027
9127
  function tool(t) {
9028
9128
  const out = { ...t };
9029
9129
  if (t.risk !== "R0" && !t.confirm)
@@ -9718,19 +9818,20 @@ var blogTools = [
9718
9818
  }),
9719
9819
  tool({
9720
9820
  name: "idea_plan",
9721
- description: "Add a waiting idea to the end of the plan (the plan holds up to 100).",
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.",
9722
9822
  parameters: {
9723
9823
  type: "object",
9724
9824
  properties: {
9725
9825
  id: { type: "string" },
9726
- 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.")
9727
9828
  },
9728
9829
  required: ["id", "title"]
9729
9830
  },
9730
9831
  group: "blog",
9731
9832
  need: write,
9732
9833
  risk: "W1",
9733
- run: async (ctx, { id }) => ws(ctx).planIdea(id)
9834
+ run: async (ctx, { id, position }) => ws(ctx).planIdea(id, positionOf(position))
9734
9835
  }),
9735
9836
  tool({
9736
9837
  name: "idea_discard",
@@ -10920,6 +11021,8 @@ var opsTools = [
10920
11021
  characters: String(p.social_copy ?? "").length,
10921
11022
  picture: p.image_url ?? null,
10922
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,
10923
11026
  link: p.cta_url ?? p.source_url ?? null,
10924
11027
  held: p.held ?? null,
10925
11028
  failure: p.failure_message ?? null
@@ -10947,7 +11050,7 @@ var publishingTools = [
10947
11050
  }),
10948
11051
  tool({
10949
11052
  name: "posts_per_day_set",
10950
- description: "How many blog posts a day AI CMO publishes to the website (1, 2, 12, 24, 48 or 96, up to the plan's limit). Only for sites AI CMO pushes to; plugin / API sites fetch on their own schedule.",
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).",
10951
11054
  parameters: {
10952
11055
  type: "object",
10953
11056
  properties: {
@@ -11510,6 +11613,7 @@ var channelsParam = {
11510
11613
  items: { type: "string", enum: CHANNEL_KEYS },
11511
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."
11512
11615
  };
11616
+ var queuedAt = positionParam("the posts", "Leave out for the front (the next posting times).");
11513
11617
  var channelsOf = (value) => {
11514
11618
  const list2 = Array.isArray(value) ? value.map((v) => String(v).toLowerCase()).filter((v) => CHANNEL_KEYS.includes(v)) : [];
11515
11619
  return list2.length ? list2 : void 0;
@@ -11602,24 +11706,28 @@ var socialTools = [
11602
11706
  }),
11603
11707
  tool({
11604
11708
  name: "social_approve",
11605
- description: "Approve LinkedIn post drafts: each goes to the front of the user's queue and out at the next posting times. Pass every id to approve in one call.",
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.",
11606
11710
  parameters: {
11607
11711
  type: "object",
11608
- properties: { ids: { type: "array", items: { type: "integer" }, description: "The drafts to approve" } },
11712
+ properties: {
11713
+ ids: { type: "array", items: { type: "integer" }, description: "The drafts to approve" },
11714
+ position: queuedAt
11715
+ },
11609
11716
  required: ["ids"]
11610
11717
  },
11611
11718
  group: "social",
11612
11719
  need: write5,
11613
11720
  risk: "W3",
11614
11721
  gates: ["VER", "LI"],
11615
- run: async (ctx, { ids: ids2, id }) => {
11722
+ run: async (ctx, { ids: ids2, id, position }) => {
11616
11723
  const list2 = (Array.isArray(ids2) && ids2.length ? ids2 : id != null ? [id] : []).map(Number).slice(0, 20);
11617
11724
  if (!list2.length) throw new Error("Which post?");
11725
+ const at = positionOf(position);
11618
11726
  const api2 = ws(ctx);
11619
11727
  const results = [];
11620
11728
  for (const n of list2) {
11621
11729
  try {
11622
- await api2.approveSocial(n);
11730
+ await api2.approveSocial(n, at);
11623
11731
  results.push({ id: n, ok: true });
11624
11732
  } catch (e) {
11625
11733
  results.push({ id: n, ok: false, error: e instanceof Error ? e.message : String(e) });
@@ -11872,6 +11980,90 @@ var socialTools = [
11872
11980
  return { id: out.id, picture: out.picture, pictures: out.image_urls?.length ? out.image_urls : [out.image_url] };
11873
11981
  }
11874
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
+ }),
11875
12067
  tool({
11876
12068
  name: "social_post_insights",
11877
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).",
@@ -11980,13 +12172,14 @@ var socialTools = [
11980
12172
  }),
11981
12173
  tool({
11982
12174
  name: "social_from_article",
11983
- 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 goes to the front of the queue). 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).",
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).",
11984
12176
  parameters: {
11985
12177
  type: "object",
11986
12178
  properties: {
11987
12179
  source_id: { type: "string" },
11988
12180
  title: { type: "string", description: "As listed (shown on the approval card and in History)" },
11989
- channels: channelsParam
12181
+ channels: channelsParam,
12182
+ position: queuedAt
11990
12183
  },
11991
12184
  required: ["source_id", "title"]
11992
12185
  },
@@ -11994,17 +12187,18 @@ var socialTools = [
11994
12187
  need: write5,
11995
12188
  risk: "W2",
11996
12189
  gates: ["VER", "LI", "LLM"],
11997
- 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)
11998
12191
  }),
11999
12192
  tool({
12000
12193
  name: "social_plan_days",
12001
- 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).",
12002
12195
  parameters: {
12003
12196
  type: "object",
12004
12197
  properties: {
12005
12198
  days: { type: "integer", minimum: 1, maximum: 7 },
12006
12199
  timezone: { type: "string", description: "IANA timezone, if known" },
12007
- channels: channelsParam
12200
+ channels: channelsParam,
12201
+ position: queuedAt
12008
12202
  },
12009
12203
  required: ["days"]
12010
12204
  },
@@ -12012,7 +12206,14 @@ var socialTools = [
12012
12206
  need: write5,
12013
12207
  risk: "W2",
12014
12208
  gates: ["VER", "LI", "LLM"],
12015
- run: async (ctx, { days, timezone, channels }) => writing((await ws(ctx).startSocialPlan(int(days, 1, 7, 3), timezone || "UTC", channelsOf(channels))).job)
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
+ )
12016
12217
  }),
12017
12218
  tool({
12018
12219
  name: "social_ideas_list",
@@ -12085,13 +12286,14 @@ var socialTools = [
12085
12286
  }),
12086
12287
  tool({
12087
12288
  name: "social_idea_write",
12088
- 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).",
12089
12290
  parameters: {
12090
12291
  type: "object",
12091
12292
  properties: {
12092
12293
  id: { type: "integer" },
12093
12294
  title: { type: "string", description: "As listed (shown on the approval card and in History)" },
12094
- channels: channelsParam
12295
+ channels: channelsParam,
12296
+ position: queuedAt
12095
12297
  },
12096
12298
  required: ["id", "title"]
12097
12299
  },
@@ -12099,7 +12301,7 @@ var socialTools = [
12099
12301
  need: write5,
12100
12302
  risk: "W2",
12101
12303
  gates: ["VER", "LI", "CR", "LLM"],
12102
- 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)
12103
12305
  }),
12104
12306
  tool({
12105
12307
  name: "social_idea_brief",
@@ -12225,13 +12427,16 @@ var socialTools = [
12225
12427
  }),
12226
12428
  tool({
12227
12429
  name: "social_approve_all",
12228
- 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.",
12229
- parameters: { type: "object", properties: { channel: { type: "string", enum: CHANNEL_KEYS } } },
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
+ },
12230
12435
  group: "social",
12231
12436
  need: write5,
12232
12437
  risk: "W3",
12233
12438
  gates: ["VER", "LI"],
12234
- 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))
12235
12440
  }),
12236
12441
  tool({
12237
12442
  name: "series_create",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmo-so/mcp",
3
- "version": "0.8.1",
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",