@cmo-so/cli 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.
- package/dist/cmo.mjs +294 -64
- package/package.json +1 -1
package/dist/cmo.mjs
CHANGED
|
@@ -7609,8 +7609,11 @@ function domainApi(api2, domain) {
|
|
|
7609
7609
|
}),
|
|
7610
7610
|
/** This website's topics, for assigning an idea to one. */
|
|
7611
7611
|
topics: () => api2.request(`${base}/topics`),
|
|
7612
|
-
/** Add a waiting idea to the end
|
|
7613
|
-
planIdea: (id) => post(
|
|
7612
|
+
/** Add a waiting idea to the plan: at the end, or at `position` (1-based in the answer). */
|
|
7613
|
+
planIdea: (id, position) => post(
|
|
7614
|
+
`${base}/ideas/${encodeURIComponent(id)}/plan`,
|
|
7615
|
+
position != null ? { position } : void 0
|
|
7616
|
+
),
|
|
7614
7617
|
/** One step up or down the 1-10 priority the planner sorts ideas by. */
|
|
7615
7618
|
ideaPriority: (id, delta) => post(`${base}/ideas/${encodeURIComponent(id)}/priority`, { delta }),
|
|
7616
7619
|
/** Take a post out of the plan; its idea can be restored to Ideas. */
|
|
@@ -7707,8 +7710,8 @@ function domainApi(api2, domain) {
|
|
|
7707
7710
|
...source ? { sitemap_url: source } : {}
|
|
7708
7711
|
}),
|
|
7709
7712
|
discardIdea: (id, restore = false) => post(`${base}/ideas/${encodeURIComponent(id)}/discard${restore ? "?restore=true" : ""}`),
|
|
7710
|
-
/** OK a draft (or retry a failed post): it goes to the front of the queue
|
|
7711
|
-
approveSocial: (id) => post(`${base}/social/${id}/approve
|
|
7713
|
+
/** 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. */
|
|
7714
|
+
approveSocial: (id, position) => post(`${base}/social/${id}/approve`, position != null ? { position } : void 0),
|
|
7712
7715
|
skipSocial: (id) => post(`${base}/social/${id}/skip`),
|
|
7713
7716
|
/** Post it on LinkedIn now, not at the next posting time (even while posting is paused). */
|
|
7714
7717
|
/** Post it now. `background`: the reply comes at once (status "publishing") and it goes out behind the
|
|
@@ -7784,9 +7787,15 @@ function domainApi(api2, domain) {
|
|
|
7784
7787
|
/** "Looks good, start": setup done, and Maggie writes the first week's posts. */
|
|
7785
7788
|
socialSetupConfirm: () => post(`${base}/social/setup/confirm`),
|
|
7786
7789
|
/** OK every post waiting for you (not a series that hasn't been approved), on one channel or all. */
|
|
7787
|
-
approveAllSocial: (channel) =>
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
+
approveAllSocial: (channel, position) => {
|
|
7791
|
+
const q2 = new URLSearchParams();
|
|
7792
|
+
if (channel) q2.set("channel", channel);
|
|
7793
|
+
if (position != null) q2.set("position", String(position));
|
|
7794
|
+
const qs = q2.toString();
|
|
7795
|
+
return post(
|
|
7796
|
+
`${base}/social/approve-all${qs ? `?${qs}` : ""}`
|
|
7797
|
+
);
|
|
7798
|
+
},
|
|
7790
7799
|
/** A Facebook sign-in link: after it the user lands on returnTo with ?facebook=connected (or =error). */
|
|
7791
7800
|
connectFacebook: (returnTo = "close") => post(`${base}/social/facebook/connect`, { return_to: returnTo }),
|
|
7792
7801
|
/** The Facebook Pages you connected, and the one this website posts to. */
|
|
@@ -7902,6 +7911,19 @@ function domainApi(api2, domain) {
|
|
|
7902
7911
|
photo,
|
|
7903
7912
|
...opts.instruction?.trim() ? { instruction: opts.instruction.trim() } : {}
|
|
7904
7913
|
}),
|
|
7914
|
+
/** Your own set of pictures for the post, in order (up to 20; free, no AI): base64 photos and/or public
|
|
7915
|
+
* https links to pictures. Send a few photos per call (16 MB request limit), `append` after the first.
|
|
7916
|
+
* On LinkedIn a set goes out as a swipeable PDF carousel unless setSocialCarousel says images. */
|
|
7917
|
+
setSocialPictures: (id, pictures) => post(`${base}/social/${id}/pictures`, pictures),
|
|
7918
|
+
/** A LinkedIn post goes out as this PDF (base64 under ~11 MB, or a public https link; up to 300 pages);
|
|
7919
|
+
* its first 20 pages become the post's pictures. */
|
|
7920
|
+
attachSocialCarouselPdf: (id, pdf) => post(`${base}/social/${id}/carousel/pdf`, pdf),
|
|
7921
|
+
/** How a LinkedIn post with a set goes out: "pdf" (swipeable carousel, the default) or "images"; its title. */
|
|
7922
|
+
setSocialCarousel: (id, changes) => api2.request(`${base}/social/${id}/carousel`, {
|
|
7923
|
+
method: "PATCH",
|
|
7924
|
+
headers: { "Content-Type": "application/json" },
|
|
7925
|
+
body: JSON.stringify(changes)
|
|
7926
|
+
}),
|
|
7905
7927
|
/** A new picture for every post here without one (or these), one after another in the background. */
|
|
7906
7928
|
fillSocialImages: (opts = {}) => post(
|
|
7907
7929
|
`${base}/social/images/fill`,
|
|
@@ -7935,20 +7957,24 @@ function domainApi(api2, domain) {
|
|
|
7935
7957
|
background: true,
|
|
7936
7958
|
...channels ? { channels } : {}
|
|
7937
7959
|
}),
|
|
7938
|
-
|
|
7960
|
+
/** `position`: where the posts are queued when approval mode is auto (the front by default). */
|
|
7961
|
+
startSocialFromPost: (sourcePostId, channels, position) => post(`${base}/social/from-post`, {
|
|
7939
7962
|
source_post_id: sourcePostId,
|
|
7940
7963
|
background: true,
|
|
7941
|
-
...channels ? { channels } : {}
|
|
7964
|
+
...channels ? { channels } : {},
|
|
7965
|
+
...position != null ? { position } : {}
|
|
7942
7966
|
}),
|
|
7943
|
-
startSocialPlan: (days, timezone, channels) => post(`${base}/social/plan`, {
|
|
7967
|
+
startSocialPlan: (days, timezone, channels, position) => post(`${base}/social/plan`, {
|
|
7944
7968
|
days,
|
|
7945
7969
|
timezone,
|
|
7946
7970
|
background: true,
|
|
7947
|
-
...channels ? { channels } : {}
|
|
7971
|
+
...channels ? { channels } : {},
|
|
7972
|
+
...position != null ? { position } : {}
|
|
7948
7973
|
}),
|
|
7949
|
-
startWriteSocialIdea: (id, channels) => post(`${base}/social/ideas/${id}/write`, {
|
|
7974
|
+
startWriteSocialIdea: (id, channels, position) => post(`${base}/social/ideas/${id}/write`, {
|
|
7950
7975
|
background: true,
|
|
7951
|
-
...channels ? { channels } : {}
|
|
7976
|
+
...channels ? { channels } : {},
|
|
7977
|
+
...position != null ? { position } : {}
|
|
7952
7978
|
}),
|
|
7953
7979
|
/** Disconnect the website's LinkedIn account. */
|
|
7954
7980
|
disconnectLinkedIn: (connectionId) => api2.request(`${base}/social/linkedin/${connectionId}`, { method: "DELETE" }),
|
|
@@ -8121,10 +8147,12 @@ function domainApi(api2, domain) {
|
|
|
8121
8147
|
}),
|
|
8122
8148
|
// docs/19 P3: autopilot cadence and publish-now.
|
|
8123
8149
|
autopilot: () => api2.request(`${base}/autopilot`),
|
|
8124
|
-
/** Posts per day
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8150
|
+
/** Posts per day (one of `choices`, up to the plan's limit). Pushed blogs: how many
|
|
8151
|
+
* AI CMO sends a day. Pull sites: the most the site collects a day; null = the plan's. */
|
|
8152
|
+
setPostsPerDay: (postsPerDay) => put(
|
|
8153
|
+
`${base}/autopilot/cadence`,
|
|
8154
|
+
{ posts_per_day: postsPerDay }
|
|
8155
|
+
),
|
|
8128
8156
|
/** Publish a ready draft to the connected site now (one post from today's quota). */
|
|
8129
8157
|
publishNow: (id) => post(
|
|
8130
8158
|
`${base}/posts/${encodeURIComponent(id)}/publish`
|
|
@@ -8409,6 +8437,14 @@ var CHANNEL_EN = {
|
|
|
8409
8437
|
google_business: "Google Business"
|
|
8410
8438
|
};
|
|
8411
8439
|
var CHANNEL_ZH = { ...CHANNEL_EN, google_business: "Google \u5546\u5BB6" };
|
|
8440
|
+
var channelsEn = (a) => {
|
|
8441
|
+
const list2 = (Array.isArray(a.channels) ? a.channels : []).map((c) => CHANNEL_EN[String(c)] ?? String(c));
|
|
8442
|
+
return list2.length ? list2.join(list2.length === 2 ? " and " : ", ") : null;
|
|
8443
|
+
};
|
|
8444
|
+
var channelsZh = (a) => {
|
|
8445
|
+
const list2 = (Array.isArray(a.channels) ? a.channels : []).map((c) => CHANNEL_ZH[String(c)] ?? String(c));
|
|
8446
|
+
return list2.length ? list2.join("\u3001") : null;
|
|
8447
|
+
};
|
|
8412
8448
|
var named = (title, id) => {
|
|
8413
8449
|
if (title != null && String(title).trim()) return ` ${q(title)}`;
|
|
8414
8450
|
const s = String(id ?? "").trim();
|
|
@@ -8418,6 +8454,20 @@ var list = (xs, n = 5) => {
|
|
|
8418
8454
|
const a = (Array.isArray(xs) ? xs : xs == null ? [] : [xs]).map(String);
|
|
8419
8455
|
return a.length > n ? `${a.slice(0, n).join(", ")} +${a.length - n}` : a.join(", ");
|
|
8420
8456
|
};
|
|
8457
|
+
var placeEn = (p, items = "posts") => {
|
|
8458
|
+
const t = String(p ?? "").trim().toLowerCase();
|
|
8459
|
+
if (t === "end") return `at the end of the queue, after the ${items} already waiting`;
|
|
8460
|
+
if (t === "random") return "at a random place in the queue";
|
|
8461
|
+
if (/^\d+$/.test(t)) return Number(t) === 0 ? null : `after the first ${t} in the queue`;
|
|
8462
|
+
return null;
|
|
8463
|
+
};
|
|
8464
|
+
var placeZh = (p) => {
|
|
8465
|
+
const t = String(p ?? "").trim().toLowerCase();
|
|
8466
|
+
if (t === "end") return "\u6392\u5728\u968A\u5217\u6700\u5F8C\uFF0C\u5728\u5DF2\u5728\u7B49\u5F85\u7684\u4E4B\u5F8C";
|
|
8467
|
+
if (t === "random") return "\u96A8\u6A5F\u6392\u9032\u968A\u5217";
|
|
8468
|
+
if (/^\d+$/.test(t)) return Number(t) === 0 ? null : `\u6392\u5728\u968A\u5217\u524D ${t} \u7BC7\u4E4B\u5F8C`;
|
|
8469
|
+
return null;
|
|
8470
|
+
};
|
|
8421
8471
|
var ids = (a) => (Array.isArray(a.ids) && a.ids.length ? a.ids : a.id != null ? [a.id] : []).map(Number);
|
|
8422
8472
|
var keys = (a) => Object.keys(a ?? {}).filter((k) => a[k] !== void 0);
|
|
8423
8473
|
var videoChannels = (a) => (Array.isArray(a.channels) && a.channels.length ? a.channels : ["youtube"]).map(
|
|
@@ -8577,8 +8627,16 @@ var confirmCopy = {
|
|
|
8577
8627
|
"zh-Hant": (a) => `${Number(a.delta) > 0 ? "\u63D0\u9AD8" : "\u964D\u4F4E"}${named(a.title, a.id)} \u7684\u512A\u5148\u7D1A\u3002`
|
|
8578
8628
|
},
|
|
8579
8629
|
idea_plan: {
|
|
8580
|
-
en: (a) =>
|
|
8581
|
-
|
|
8630
|
+
en: (a) => {
|
|
8631
|
+
const t = String(a.position ?? "").trim().toLowerCase();
|
|
8632
|
+
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";
|
|
8633
|
+
return `Add${named(a.title, a.id)} to ${where}.`;
|
|
8634
|
+
},
|
|
8635
|
+
"zh-Hant": (a) => {
|
|
8636
|
+
const t = String(a.position ?? "").trim().toLowerCase();
|
|
8637
|
+
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";
|
|
8638
|
+
return `\u628A${named(a.title, a.id)} \u52A0\u5230${where}\u3002`;
|
|
8639
|
+
}
|
|
8582
8640
|
},
|
|
8583
8641
|
idea_discard: {
|
|
8584
8642
|
en: (a) => `Remove the idea${named(a.title, a.id)}.`,
|
|
@@ -8648,23 +8706,29 @@ var confirmCopy = {
|
|
|
8648
8706
|
},
|
|
8649
8707
|
// ----------------------------------------------------------------- social
|
|
8650
8708
|
social_update_copy: {
|
|
8651
|
-
en: (a) => `Change the text of
|
|
8652
|
-
"zh-Hant": (a) => `\u4FEE\u6539
|
|
8709
|
+
en: (a) => `Change the text of post #${a.id}.`,
|
|
8710
|
+
"zh-Hant": (a) => `\u4FEE\u6539\u8CBC\u6587 #${a.id} \u7684\u5167\u5BB9\u3002`
|
|
8653
8711
|
},
|
|
8654
8712
|
social_approve: {
|
|
8655
8713
|
en: (a) => {
|
|
8656
8714
|
const n = ids(a);
|
|
8657
|
-
|
|
8715
|
+
const at = placeEn(a.position);
|
|
8716
|
+
if (at)
|
|
8717
|
+
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"}.`;
|
|
8718
|
+
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.`;
|
|
8658
8719
|
},
|
|
8659
8720
|
"zh-Hant": (a) => {
|
|
8660
8721
|
const n = ids(a);
|
|
8661
|
-
|
|
8722
|
+
const at = placeZh(a.position);
|
|
8723
|
+
if (at)
|
|
8724
|
+
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`;
|
|
8725
|
+
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`;
|
|
8662
8726
|
}
|
|
8663
8727
|
},
|
|
8664
|
-
social_skip: { en: (a) => `Skip
|
|
8728
|
+
social_skip: { en: (a) => `Skip post #${a.id}.`, "zh-Hant": (a) => `\u8DF3\u904E\u8CBC\u6587 #${a.id}\u3002` },
|
|
8665
8729
|
social_restore: {
|
|
8666
|
-
en: (a) => `Bring back
|
|
8667
|
-
"zh-Hant": (a) => `\u6062\u5FA9
|
|
8730
|
+
en: (a) => `Bring back post #${a.id}.`,
|
|
8731
|
+
"zh-Hant": (a) => `\u6062\u5FA9\u8CBC\u6587 #${a.id}\u3002`
|
|
8668
8732
|
},
|
|
8669
8733
|
youtube_connect_link: {
|
|
8670
8734
|
en: () => "Make a link to connect your YouTube channel.",
|
|
@@ -8705,28 +8769,52 @@ var confirmCopy = {
|
|
|
8705
8769
|
"zh-Hant": (a) => `\u4FEE\u6539 YouTube \u9810\u8A2D\uFF1A${keys(a).join("\u3001") || "\u7121"}\u3002`
|
|
8706
8770
|
},
|
|
8707
8771
|
social_image_new: {
|
|
8708
|
-
en: (a) => `Make a new picture for
|
|
8709
|
-
"zh-Hant": (a) => `\u70BA
|
|
8772
|
+
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" : ""}.`,
|
|
8773
|
+
"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`
|
|
8710
8774
|
},
|
|
8711
8775
|
social_image_stop: {
|
|
8712
|
-
en: () => "Stop the
|
|
8713
|
-
"zh-Hant": () => "\u505C\u6B62\u6B63\u5728\u751F\u6210\u7684
|
|
8776
|
+
en: () => "Stop the picture being made; the post keeps the one it had.",
|
|
8777
|
+
"zh-Hant": () => "\u505C\u6B62\u6B63\u5728\u751F\u6210\u7684\u5716\u7247\uFF1B\u8CBC\u6587\u4FDD\u7559\u539F\u4F86\u7684\u5716\u7247\u3002"
|
|
8778
|
+
},
|
|
8779
|
+
social_pictures_attach: {
|
|
8780
|
+
en: (a) => {
|
|
8781
|
+
const n = Array.isArray(a.urls) ? a.urls.length : 0;
|
|
8782
|
+
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.`;
|
|
8783
|
+
},
|
|
8784
|
+
"zh-Hant": (a) => {
|
|
8785
|
+
const n = Array.isArray(a.urls) ? a.urls.length : 0;
|
|
8786
|
+
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`;
|
|
8787
|
+
}
|
|
8788
|
+
},
|
|
8789
|
+
social_carousel_pdf_attach: {
|
|
8790
|
+
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).`,
|
|
8791
|
+
"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`
|
|
8792
|
+
},
|
|
8793
|
+
social_carousel_set: {
|
|
8794
|
+
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)}` : ""}.`,
|
|
8795
|
+
"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`
|
|
8714
8796
|
},
|
|
8715
8797
|
social_move: {
|
|
8716
|
-
en: (a) => `Move
|
|
8717
|
-
"zh-Hant": (a) => `\u628A
|
|
8798
|
+
en: (a) => `Move post #${a.id} ${a.to === "top" ? "to the top of its channel's queue" : `${a.to} in its channel's queue`}.`,
|
|
8799
|
+
"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`
|
|
8718
8800
|
},
|
|
8719
8801
|
social_set_paused: {
|
|
8720
|
-
en: (a) =>
|
|
8721
|
-
|
|
8802
|
+
en: (a) => {
|
|
8803
|
+
const on2 = a.channel ? `${CHANNEL_EN[String(a.channel)] ?? a.channel} posting` : "posting on every channel";
|
|
8804
|
+
return a.paused ? `Pause ${on2}.` : `Resume ${on2}.`;
|
|
8805
|
+
},
|
|
8806
|
+
"zh-Hant": (a) => {
|
|
8807
|
+
const on2 = a.channel ? `${CHANNEL_ZH[String(a.channel)] ?? a.channel} ` : "\u6240\u6709\u6E20\u9053\u7684";
|
|
8808
|
+
return a.paused ? `\u66AB\u505C${on2}\u767C\u6587\u3002` : `\u6062\u5FA9${on2}\u767C\u6587\u3002`;
|
|
8809
|
+
}
|
|
8722
8810
|
},
|
|
8723
8811
|
social_from_article: {
|
|
8724
|
-
en: (a) => `Write a
|
|
8725
|
-
"zh-Hant": (a) => `\u6839\u64DA ${q(a.title || a.source_id)}
|
|
8812
|
+
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)})` : ""}.`,
|
|
8813
|
+
"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`
|
|
8726
8814
|
},
|
|
8727
8815
|
social_plan_days: {
|
|
8728
|
-
en: (a) => `Write
|
|
8729
|
-
"zh-Hant": (a) => `\u7528\u4F60\u6700\u597D\u7684\u6587\u7AE0\u5BEB\u672A\u4F86 ${a.days} \u5929\u7684
|
|
8816
|
+
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)})` : ""}.`,
|
|
8817
|
+
"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`
|
|
8730
8818
|
},
|
|
8731
8819
|
social_idea_add: {
|
|
8732
8820
|
en: (a) => `Add the LinkedIn idea ${q(a.title)}.`,
|
|
@@ -8741,8 +8829,8 @@ var confirmCopy = {
|
|
|
8741
8829
|
"zh-Hant": (a) => `\u522A\u9664 LinkedIn idea #${a.id}${a.title ? ` ${q(a.title)}` : ""}\u3002`
|
|
8742
8830
|
},
|
|
8743
8831
|
social_idea_write: {
|
|
8744
|
-
en: (a) => `Write the
|
|
8745
|
-
"zh-Hant": (a) => `\u70BA idea #${a.id}${a.title ? ` ${q(a.title)}` : ""} \u5BEB
|
|
8832
|
+
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)})` : ""}.`,
|
|
8833
|
+
"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`
|
|
8746
8834
|
},
|
|
8747
8835
|
social_idea_save: {
|
|
8748
8836
|
en: (a) => `Save this LinkedIn post for idea #${a.id} (one AI generation): ${q(a.text)}`,
|
|
@@ -8808,8 +8896,8 @@ var confirmCopy = {
|
|
|
8808
8896
|
"zh-Hant": () => "\u7522\u751F\u4E00\u500B\u9023\u63A5 LinkedIn \u7684\u9023\u7D50\u3002"
|
|
8809
8897
|
},
|
|
8810
8898
|
social_approve_all: {
|
|
8811
|
-
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.`,
|
|
8812
|
-
"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\
|
|
8899
|
+
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.`,
|
|
8900
|
+
"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`
|
|
8813
8901
|
},
|
|
8814
8902
|
series_create: {
|
|
8815
8903
|
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.`,
|
|
@@ -8964,6 +9052,18 @@ var int = (v, min, max, fallback) => {
|
|
|
8964
9052
|
const n = Math.trunc(Number(v));
|
|
8965
9053
|
return Number.isFinite(n) ? Math.min(max, Math.max(min, n)) : fallback;
|
|
8966
9054
|
};
|
|
9055
|
+
var positionParam = (what, fallback) => ({
|
|
9056
|
+
type: "string",
|
|
9057
|
+
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.`
|
|
9058
|
+
});
|
|
9059
|
+
var positionOf = (value) => {
|
|
9060
|
+
if (value == null || value === "") return void 0;
|
|
9061
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
|
|
9062
|
+
const text3 = String(value).trim().toLowerCase();
|
|
9063
|
+
if (text3 === "front" || text3 === "end" || text3 === "random") return text3;
|
|
9064
|
+
if (/^\d+$/.test(text3)) return Number(text3);
|
|
9065
|
+
throw new Error('position must be "front", "end", "random" or a number of items to go after (0 = front).');
|
|
9066
|
+
};
|
|
8967
9067
|
function tool(t) {
|
|
8968
9068
|
const out = { ...t };
|
|
8969
9069
|
if (t.risk !== "R0" && !t.confirm)
|
|
@@ -9658,19 +9758,20 @@ var blogTools = [
|
|
|
9658
9758
|
}),
|
|
9659
9759
|
tool({
|
|
9660
9760
|
name: "idea_plan",
|
|
9661
|
-
description: "Add a waiting idea to the
|
|
9761
|
+
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.",
|
|
9662
9762
|
parameters: {
|
|
9663
9763
|
type: "object",
|
|
9664
9764
|
properties: {
|
|
9665
9765
|
id: { type: "string" },
|
|
9666
|
-
title: { type: "string", description: "As listed (shown on the approval card and in History)" }
|
|
9766
|
+
title: { type: "string", description: "As listed (shown on the approval card and in History)" },
|
|
9767
|
+
position: positionParam("the idea", "Leave out for the end of the plan.")
|
|
9667
9768
|
},
|
|
9668
9769
|
required: ["id", "title"]
|
|
9669
9770
|
},
|
|
9670
9771
|
group: "blog",
|
|
9671
9772
|
need: write,
|
|
9672
9773
|
risk: "W1",
|
|
9673
|
-
run: async (ctx, { id }) => ws(ctx).planIdea(id)
|
|
9774
|
+
run: async (ctx, { id, position }) => ws(ctx).planIdea(id, positionOf(position))
|
|
9674
9775
|
}),
|
|
9675
9776
|
tool({
|
|
9676
9777
|
name: "idea_discard",
|
|
@@ -10860,6 +10961,8 @@ var opsTools = [
|
|
|
10860
10961
|
characters: String(p.social_copy ?? "").length,
|
|
10861
10962
|
picture: p.image_url ?? null,
|
|
10862
10963
|
pictures: p.image_urls ?? [],
|
|
10964
|
+
// LinkedIn with a set (or an uploaded PDF): a PDF carousel or several pictures.
|
|
10965
|
+
carousel: p.carousel ?? null,
|
|
10863
10966
|
link: p.cta_url ?? p.source_url ?? null,
|
|
10864
10967
|
held: p.held ?? null,
|
|
10865
10968
|
failure: p.failure_message ?? null
|
|
@@ -10887,7 +10990,7 @@ var publishingTools = [
|
|
|
10887
10990
|
}),
|
|
10888
10991
|
tool({
|
|
10889
10992
|
name: "posts_per_day_set",
|
|
10890
|
-
description: "
|
|
10993
|
+
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).",
|
|
10891
10994
|
parameters: {
|
|
10892
10995
|
type: "object",
|
|
10893
10996
|
properties: {
|
|
@@ -11450,6 +11553,7 @@ var channelsParam = {
|
|
|
11450
11553
|
items: { type: "string", enum: CHANNEL_KEYS },
|
|
11451
11554
|
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."
|
|
11452
11555
|
};
|
|
11556
|
+
var queuedAt = positionParam("the posts", "Leave out for the front (the next posting times).");
|
|
11453
11557
|
var channelsOf = (value) => {
|
|
11454
11558
|
const list2 = Array.isArray(value) ? value.map((v) => String(v).toLowerCase()).filter((v) => CHANNEL_KEYS.includes(v)) : [];
|
|
11455
11559
|
return list2.length ? list2 : void 0;
|
|
@@ -11542,24 +11646,28 @@ var socialTools = [
|
|
|
11542
11646
|
}),
|
|
11543
11647
|
tool({
|
|
11544
11648
|
name: "social_approve",
|
|
11545
|
-
description: "Approve
|
|
11649
|
+
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.",
|
|
11546
11650
|
parameters: {
|
|
11547
11651
|
type: "object",
|
|
11548
|
-
properties: {
|
|
11652
|
+
properties: {
|
|
11653
|
+
ids: { type: "array", items: { type: "integer" }, description: "The drafts to approve" },
|
|
11654
|
+
position: queuedAt
|
|
11655
|
+
},
|
|
11549
11656
|
required: ["ids"]
|
|
11550
11657
|
},
|
|
11551
11658
|
group: "social",
|
|
11552
11659
|
need: write5,
|
|
11553
11660
|
risk: "W3",
|
|
11554
11661
|
gates: ["VER", "LI"],
|
|
11555
|
-
run: async (ctx, { ids: ids3, id }) => {
|
|
11662
|
+
run: async (ctx, { ids: ids3, id, position }) => {
|
|
11556
11663
|
const list2 = (Array.isArray(ids3) && ids3.length ? ids3 : id != null ? [id] : []).map(Number).slice(0, 20);
|
|
11557
11664
|
if (!list2.length) throw new Error("Which post?");
|
|
11665
|
+
const at = positionOf(position);
|
|
11558
11666
|
const api2 = ws(ctx);
|
|
11559
11667
|
const results = [];
|
|
11560
11668
|
for (const n of list2) {
|
|
11561
11669
|
try {
|
|
11562
|
-
await api2.approveSocial(n);
|
|
11670
|
+
await api2.approveSocial(n, at);
|
|
11563
11671
|
results.push({ id: n, ok: true });
|
|
11564
11672
|
} catch (e) {
|
|
11565
11673
|
results.push({ id: n, ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
@@ -11812,6 +11920,90 @@ var socialTools = [
|
|
|
11812
11920
|
return { id: out.id, picture: out.picture, pictures: out.image_urls?.length ? out.image_urls : [out.image_url] };
|
|
11813
11921
|
}
|
|
11814
11922
|
}),
|
|
11923
|
+
tool({
|
|
11924
|
+
name: "social_pictures_attach",
|
|
11925
|
+
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.",
|
|
11926
|
+
parameters: {
|
|
11927
|
+
type: "object",
|
|
11928
|
+
properties: {
|
|
11929
|
+
id: { type: "integer", description: "The post (from social_list), not gone out yet" },
|
|
11930
|
+
urls: {
|
|
11931
|
+
type: "array",
|
|
11932
|
+
items: { type: "string" },
|
|
11933
|
+
description: "Public https links to the image files, in the order they should appear"
|
|
11934
|
+
},
|
|
11935
|
+
append: { type: "boolean", description: "Add after the post's current pictures instead of replacing them" }
|
|
11936
|
+
},
|
|
11937
|
+
required: ["id", "urls"]
|
|
11938
|
+
},
|
|
11939
|
+
group: "social",
|
|
11940
|
+
need: write5,
|
|
11941
|
+
risk: "W1",
|
|
11942
|
+
gates: ["VER"],
|
|
11943
|
+
run: async (ctx, { id, urls, append }) => {
|
|
11944
|
+
const list2 = (Array.isArray(urls) ? urls : []).map((u) => String(u).trim()).filter(Boolean);
|
|
11945
|
+
if (!list2.length) throw new Error("Give at least one https link to a picture.");
|
|
11946
|
+
if (list2.length > 20) throw new Error("A post takes up to 20 pictures.");
|
|
11947
|
+
if (list2.some((u) => !u.startsWith("https://"))) throw new Error("Every picture needs a public https link.");
|
|
11948
|
+
const out = await ws(ctx).setSocialPictures(Number(id), { urls: list2, append: append === true });
|
|
11949
|
+
return {
|
|
11950
|
+
id: out.id,
|
|
11951
|
+
pictures: out.image_urls?.length ? out.image_urls : [out.image_url],
|
|
11952
|
+
carousel: out.carousel ?? null
|
|
11953
|
+
};
|
|
11954
|
+
}
|
|
11955
|
+
}),
|
|
11956
|
+
tool({
|
|
11957
|
+
name: "social_carousel_pdf_attach",
|
|
11958
|
+
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.",
|
|
11959
|
+
parameters: {
|
|
11960
|
+
type: "object",
|
|
11961
|
+
properties: {
|
|
11962
|
+
id: { type: "integer", description: "A LinkedIn post (from social_list), not gone out yet" },
|
|
11963
|
+
url: { type: "string", description: "Public https link to the PDF file" },
|
|
11964
|
+
title: {
|
|
11965
|
+
type: "string",
|
|
11966
|
+
description: "Shown on the carousel (up to 120 characters); the post's topic if left out"
|
|
11967
|
+
}
|
|
11968
|
+
},
|
|
11969
|
+
required: ["id", "url"]
|
|
11970
|
+
},
|
|
11971
|
+
group: "social",
|
|
11972
|
+
need: write5,
|
|
11973
|
+
risk: "W1",
|
|
11974
|
+
gates: ["VER", "LI"],
|
|
11975
|
+
run: async (ctx, { id, url, title }) => {
|
|
11976
|
+
const link = String(url ?? "").trim();
|
|
11977
|
+
if (!link.startsWith("https://")) throw new Error("Give a public https link to the PDF.");
|
|
11978
|
+
const out = await ws(ctx).attachSocialCarouselPdf(Number(id), { url: link, ...title ? { title } : {} });
|
|
11979
|
+
return { id: out.id, pages: out.pages, pictures: out.pictures, carousel: out.carousel ?? null };
|
|
11980
|
+
}
|
|
11981
|
+
}),
|
|
11982
|
+
tool({
|
|
11983
|
+
name: "social_carousel_set",
|
|
11984
|
+
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.",
|
|
11985
|
+
parameters: {
|
|
11986
|
+
type: "object",
|
|
11987
|
+
properties: {
|
|
11988
|
+
id: { type: "integer", description: "A LinkedIn post with a set of pictures (from social_list)" },
|
|
11989
|
+
format: { type: "string", enum: ["pdf", "images"] },
|
|
11990
|
+
title: { type: "string", description: "Shown on the carousel (up to 120 characters)" }
|
|
11991
|
+
},
|
|
11992
|
+
required: ["id"]
|
|
11993
|
+
},
|
|
11994
|
+
group: "social",
|
|
11995
|
+
need: write5,
|
|
11996
|
+
risk: "W1",
|
|
11997
|
+
gates: ["VER"],
|
|
11998
|
+
run: async (ctx, { id, format, title }) => {
|
|
11999
|
+
if (format == null && title == null) throw new Error("Say the format (pdf or images) or a title.");
|
|
12000
|
+
const out = await ws(ctx).setSocialCarousel(Number(id), {
|
|
12001
|
+
...format ? { format } : {},
|
|
12002
|
+
...title != null ? { title: String(title) } : {}
|
|
12003
|
+
});
|
|
12004
|
+
return { id: out.id, carousel: out.carousel ?? null };
|
|
12005
|
+
}
|
|
12006
|
+
}),
|
|
11815
12007
|
tool({
|
|
11816
12008
|
name: "social_post_insights",
|
|
11817
12009
|
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).",
|
|
@@ -11920,13 +12112,14 @@ var socialTools = [
|
|
|
11920
12112
|
}),
|
|
11921
12113
|
tool({
|
|
11922
12114
|
name: "social_from_article",
|
|
11923
|
-
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
|
|
12115
|
+
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).",
|
|
11924
12116
|
parameters: {
|
|
11925
12117
|
type: "object",
|
|
11926
12118
|
properties: {
|
|
11927
12119
|
source_id: { type: "string" },
|
|
11928
12120
|
title: { type: "string", description: "As listed (shown on the approval card and in History)" },
|
|
11929
|
-
channels: channelsParam
|
|
12121
|
+
channels: channelsParam,
|
|
12122
|
+
position: queuedAt
|
|
11930
12123
|
},
|
|
11931
12124
|
required: ["source_id", "title"]
|
|
11932
12125
|
},
|
|
@@ -11934,17 +12127,18 @@ var socialTools = [
|
|
|
11934
12127
|
need: write5,
|
|
11935
12128
|
risk: "W2",
|
|
11936
12129
|
gates: ["VER", "LI", "LLM"],
|
|
11937
|
-
run: async (ctx, { source_id, channels }) => writing((await ws(ctx).startSocialFromPost(String(source_id), channelsOf(channels))).job)
|
|
12130
|
+
run: async (ctx, { source_id, channels, position }) => writing((await ws(ctx).startSocialFromPost(String(source_id), channelsOf(channels), positionOf(position))).job)
|
|
11938
12131
|
}),
|
|
11939
12132
|
tool({
|
|
11940
12133
|
name: "social_plan_days",
|
|
11941
|
-
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).",
|
|
12134
|
+
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).",
|
|
11942
12135
|
parameters: {
|
|
11943
12136
|
type: "object",
|
|
11944
12137
|
properties: {
|
|
11945
12138
|
days: { type: "integer", minimum: 1, maximum: 7 },
|
|
11946
12139
|
timezone: { type: "string", description: "IANA timezone, if known" },
|
|
11947
|
-
channels: channelsParam
|
|
12140
|
+
channels: channelsParam,
|
|
12141
|
+
position: queuedAt
|
|
11948
12142
|
},
|
|
11949
12143
|
required: ["days"]
|
|
11950
12144
|
},
|
|
@@ -11952,7 +12146,14 @@ var socialTools = [
|
|
|
11952
12146
|
need: write5,
|
|
11953
12147
|
risk: "W2",
|
|
11954
12148
|
gates: ["VER", "LI", "LLM"],
|
|
11955
|
-
run: async (ctx, { days, timezone, channels }) => writing(
|
|
12149
|
+
run: async (ctx, { days, timezone, channels, position }) => writing(
|
|
12150
|
+
(await ws(ctx).startSocialPlan(
|
|
12151
|
+
int(days, 1, 7, 3),
|
|
12152
|
+
timezone || "UTC",
|
|
12153
|
+
channelsOf(channels),
|
|
12154
|
+
positionOf(position)
|
|
12155
|
+
)).job
|
|
12156
|
+
)
|
|
11956
12157
|
}),
|
|
11957
12158
|
tool({
|
|
11958
12159
|
name: "social_ideas_list",
|
|
@@ -12025,13 +12226,14 @@ var socialTools = [
|
|
|
12025
12226
|
}),
|
|
12026
12227
|
tool({
|
|
12027
12228
|
name: "social_idea_write",
|
|
12028
|
-
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).",
|
|
12229
|
+
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).",
|
|
12029
12230
|
parameters: {
|
|
12030
12231
|
type: "object",
|
|
12031
12232
|
properties: {
|
|
12032
12233
|
id: { type: "integer" },
|
|
12033
12234
|
title: { type: "string", description: "As listed (shown on the approval card and in History)" },
|
|
12034
|
-
channels: channelsParam
|
|
12235
|
+
channels: channelsParam,
|
|
12236
|
+
position: queuedAt
|
|
12035
12237
|
},
|
|
12036
12238
|
required: ["id", "title"]
|
|
12037
12239
|
},
|
|
@@ -12039,7 +12241,7 @@ var socialTools = [
|
|
|
12039
12241
|
need: write5,
|
|
12040
12242
|
risk: "W2",
|
|
12041
12243
|
gates: ["VER", "LI", "CR", "LLM"],
|
|
12042
|
-
run: async (ctx, { id, channels }) => writing((await ws(ctx).startWriteSocialIdea(Number(id), channelsOf(channels))).job)
|
|
12244
|
+
run: async (ctx, { id, channels, position }) => writing((await ws(ctx).startWriteSocialIdea(Number(id), channelsOf(channels), positionOf(position))).job)
|
|
12043
12245
|
}),
|
|
12044
12246
|
tool({
|
|
12045
12247
|
name: "social_idea_brief",
|
|
@@ -12165,13 +12367,16 @@ var socialTools = [
|
|
|
12165
12367
|
}),
|
|
12166
12368
|
tool({
|
|
12167
12369
|
name: "social_approve_all",
|
|
12168
|
-
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.",
|
|
12169
|
-
parameters: {
|
|
12370
|
+
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).",
|
|
12371
|
+
parameters: {
|
|
12372
|
+
type: "object",
|
|
12373
|
+
properties: { channel: { type: "string", enum: CHANNEL_KEYS }, position: queuedAt }
|
|
12374
|
+
},
|
|
12170
12375
|
group: "social",
|
|
12171
12376
|
need: write5,
|
|
12172
12377
|
risk: "W3",
|
|
12173
12378
|
gates: ["VER", "LI"],
|
|
12174
|
-
run: async (ctx, { channel }) => ws(ctx).approveAllSocial(channelsOf(channel ? [channel] : [])?.[0])
|
|
12379
|
+
run: async (ctx, { channel, position }) => ws(ctx).approveAllSocial(channelsOf(channel ? [channel] : [])?.[0], positionOf(position))
|
|
12175
12380
|
}),
|
|
12176
12381
|
tool({
|
|
12177
12382
|
name: "series_create",
|
|
@@ -16294,7 +16499,7 @@ var program = new Command();
|
|
|
16294
16499
|
// package.json
|
|
16295
16500
|
var package_default = {
|
|
16296
16501
|
name: "@cmo-so/cli",
|
|
16297
|
-
version: "0.
|
|
16502
|
+
version: "0.9.0",
|
|
16298
16503
|
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.",
|
|
16299
16504
|
type: "module",
|
|
16300
16505
|
license: "MIT",
|
|
@@ -27270,7 +27475,32 @@ ${table(s.queue ?? [], ["id", "channel", "status", "eta", "text"])}`;
|
|
|
27270
27475
|
})
|
|
27271
27476
|
)
|
|
27272
27477
|
);
|
|
27273
|
-
social.command("approve <ids...>").description("approve drafts: they go out at the next posting times").
|
|
27478
|
+
social.command("approve <ids...>").description("approve drafts: they go out at the next posting times").option("--position <where>", "front (default), end, random, or a number of posts to go after").action(
|
|
27479
|
+
act(
|
|
27480
|
+
async (opts, list2) => tool2(opts, "social_approve", { ids: ids2(list2), ...opts.position ? { position: opts.position } : {} })
|
|
27481
|
+
)
|
|
27482
|
+
);
|
|
27483
|
+
social.command("pictures <id> <urls...>").description("your own pictures on a post, in order (public https links, up to 20); a PDF carousel on LinkedIn").option("--append", "add after the post's current pictures").action(
|
|
27484
|
+
act(
|
|
27485
|
+
async (opts, id, urls) => tool2(opts, "social_pictures_attach", { id: ids2([id])[0], urls, append: opts.append === true })
|
|
27486
|
+
)
|
|
27487
|
+
);
|
|
27488
|
+
social.command("carousel <id>").description("a LinkedIn post with a set of pictures: a swipeable PDF carousel or several pictures").option("--format <format>", "pdf (a swipeable carousel, the default) or images").option("--title <text>", "shown on the carousel").option("--pdf <url>", "a public https link to your own PDF: it goes out as it is").action(
|
|
27489
|
+
act(async (opts, id) => {
|
|
27490
|
+
const post = ids2([id])[0];
|
|
27491
|
+
if (opts.pdf)
|
|
27492
|
+
return tool2(opts, "social_carousel_pdf_attach", {
|
|
27493
|
+
id: post,
|
|
27494
|
+
url: opts.pdf,
|
|
27495
|
+
...opts.title ? { title: opts.title } : {}
|
|
27496
|
+
});
|
|
27497
|
+
return tool2(opts, "social_carousel_set", {
|
|
27498
|
+
id: post,
|
|
27499
|
+
...opts.format ? { format: opts.format } : {},
|
|
27500
|
+
...opts.title ? { title: opts.title } : {}
|
|
27501
|
+
});
|
|
27502
|
+
})
|
|
27503
|
+
);
|
|
27274
27504
|
social.command("skip <id>").description("skip a draft").action(act(async (opts, id) => tool2(opts, "social_skip", { id: ids2([id])[0] })));
|
|
27275
27505
|
social.command("restore <id>").description("bring a skipped draft back").action(act(async (opts, id) => tool2(opts, "social_restore", { id: ids2([id])[0] })));
|
|
27276
27506
|
social.command("draft <topic...>").description("add a post idea for Maggie to write (every connected channel unless --channel)").option("--summary <text>", "what the post should say").option("--channel <names>", "linkedin, facebook, instagram, google_business (comma-separated)").action(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|