@cmo-so/cli 0.9.0 → 0.9.2
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 +170 -18
- package/package.json +1 -1
package/dist/cmo.mjs
CHANGED
|
@@ -7724,8 +7724,14 @@ function domainApi(api2, domain) {
|
|
|
7724
7724
|
/** How a posted Instagram post is doing (read from Instagram, kept 15 minutes; `refresh` reads again). */
|
|
7725
7725
|
socialInsights: (id, refresh = false) => api2.request(`${base}/social/${id}/insights${refresh ? "?refresh=true" : ""}`),
|
|
7726
7726
|
deletePostedSocial: (id) => api2.request(`${base}/social/${id}/published`, { method: "DELETE" }),
|
|
7727
|
-
/**
|
|
7728
|
-
|
|
7727
|
+
/**
|
|
7728
|
+
* Change a posted post. LinkedIn changes its text in place (likes and comments stay); with `repost`, and on
|
|
7729
|
+
* the other channels, the old one comes down and the new text goes out now.
|
|
7730
|
+
*/
|
|
7731
|
+
republishSocial: (id, socialCopy, repost = false) => post(`${base}/social/${id}/republish`, {
|
|
7732
|
+
...socialCopy ? { social_copy: socialCopy } : {},
|
|
7733
|
+
...repost ? { repost: true } : {}
|
|
7734
|
+
}),
|
|
7729
7735
|
restoreSocial: (id) => post(`${base}/social/${id}/restore`),
|
|
7730
7736
|
pauseSocial: () => post(`${base}/social/pause`),
|
|
7731
7737
|
/**
|
|
@@ -8725,6 +8731,18 @@ var confirmCopy = {
|
|
|
8725
8731
|
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`;
|
|
8726
8732
|
}
|
|
8727
8733
|
},
|
|
8734
|
+
social_publish_now: {
|
|
8735
|
+
en: (a) => `Post #${a.id} on its channel now, not at its posting time (even while posting is paused). Everyone who follows you can see it.`,
|
|
8736
|
+
"zh-Hant": (a) => `\u7ACB\u5373\u5728\u5B83\u7684\u983B\u9053\u767C\u51FA\u8CBC\u6587 #${a.id}\uFF0C\u4E0D\u7B49\u767C\u6587\u6642\u9593\uFF08\u66AB\u505C\u767C\u6587\u6642\u4E5F\u6703\u767C\uFF09\u3002\u767C\u51FA\u5F8C\u95DC\u6CE8\u4F60\u7684\u4EBA\u90FD\u770B\u5F97\u5230\u3002`
|
|
8737
|
+
},
|
|
8738
|
+
social_posted_update: {
|
|
8739
|
+
en: (a) => a.repost ? `Take post #${a.id} down (its likes and comments go) and put it up again${a.social_copy ? ` as ${q(a.social_copy)}` : ""}.` : `Change the text of the posted post #${a.id} to ${q(a.social_copy)}. On LinkedIn it stays up with its likes and comments; on other channels it comes down and goes out again.`,
|
|
8740
|
+
"zh-Hant": (a) => a.repost ? `\u64A4\u4E0B\u8CBC\u6587 #${a.id}\uFF08\u8B9A\u548C\u7559\u8A00\u6703\u6D88\u5931\uFF09\uFF0C\u518D\u91CD\u65B0\u767C\u51FA${a.social_copy ? `\uFF0C\u5167\u5BB9\u6539\u70BA ${q(a.social_copy)}` : ""}\u3002` : `\u628A\u5DF2\u767C\u51FA\u7684\u8CBC\u6587 #${a.id} \u6539\u70BA ${q(a.social_copy)}\u3002LinkedIn \u4E0A\u539F\u5730\u4FEE\u6539\uFF0C\u8B9A\u548C\u7559\u8A00\u4FDD\u7559\uFF1B\u5176\u4ED6\u983B\u9053\u6703\u64A4\u4E0B\u5F8C\u91CD\u767C\u3002`
|
|
8741
|
+
},
|
|
8742
|
+
social_posted_delete: {
|
|
8743
|
+
en: (a) => `Take post #${a.id} down from its channel, with its likes and comments. This can't be undone.`,
|
|
8744
|
+
"zh-Hant": (a) => `\u628A\u8CBC\u6587 #${a.id} \u5F9E\u5B83\u7684\u983B\u9053\u64A4\u4E0B\uFF0C\u8B9A\u548C\u7559\u8A00\u4E00\u8D77\u522A\u9664\uFF0C\u7121\u6CD5\u5FA9\u539F\u3002`
|
|
8745
|
+
},
|
|
8728
8746
|
social_skip: { en: (a) => `Skip post #${a.id}.`, "zh-Hant": (a) => `\u8DF3\u904E\u8CBC\u6587 #${a.id}\u3002` },
|
|
8729
8747
|
social_restore: {
|
|
8730
8748
|
en: (a) => `Bring back post #${a.id}.`,
|
|
@@ -8778,11 +8796,11 @@ var confirmCopy = {
|
|
|
8778
8796
|
},
|
|
8779
8797
|
social_pictures_attach: {
|
|
8780
8798
|
en: (a) => {
|
|
8781
|
-
const n = Array.isArray(a.urls) ? a.urls.length : 0;
|
|
8799
|
+
const n = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
|
|
8782
8800
|
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
8801
|
},
|
|
8784
8802
|
"zh-Hant": (a) => {
|
|
8785
|
-
const n = Array.isArray(a.urls) ? a.urls.length : 0;
|
|
8803
|
+
const n = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
|
|
8786
8804
|
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
8805
|
}
|
|
8788
8806
|
},
|
|
@@ -11553,6 +11571,17 @@ var channelsParam = {
|
|
|
11553
11571
|
items: { type: "string", enum: CHANNEL_KEYS },
|
|
11554
11572
|
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."
|
|
11555
11573
|
};
|
|
11574
|
+
var FILE_SCHEMA = {
|
|
11575
|
+
type: "object",
|
|
11576
|
+
properties: {
|
|
11577
|
+
download_url: { type: "string" },
|
|
11578
|
+
file_id: { type: "string" },
|
|
11579
|
+
mime_type: { type: "string" },
|
|
11580
|
+
file_name: { type: "string" }
|
|
11581
|
+
},
|
|
11582
|
+
required: ["download_url", "file_id"]
|
|
11583
|
+
};
|
|
11584
|
+
var fileLinks = (value) => (Array.isArray(value) ? value : value ? [value] : []).map((f) => String(f?.download_url ?? "").trim()).filter(Boolean);
|
|
11556
11585
|
var queuedAt = positionParam("the posts", "Leave out for the front (the next posting times).");
|
|
11557
11586
|
var channelsOf = (value) => {
|
|
11558
11587
|
const list2 = Array.isArray(value) ? value.map((v) => String(v).toLowerCase()).filter((v) => CHANNEL_KEYS.includes(v)) : [];
|
|
@@ -11676,6 +11705,59 @@ var socialTools = [
|
|
|
11676
11705
|
return { approved: results.filter((r) => r.ok).length, results };
|
|
11677
11706
|
}
|
|
11678
11707
|
}),
|
|
11708
|
+
tool({
|
|
11709
|
+
name: "social_publish_now",
|
|
11710
|
+
description: "Post one social post on its channel now instead of at its posting time: a draft waiting for approval, a queued post, or one that didn't go out. It goes even while posting is paused. It goes out in the background: the answer says it is publishing; read social_post_get after a minute for its link (or why it couldn't go). Not while its picture is being made.",
|
|
11711
|
+
parameters: { type: "object", properties: { id: { type: "integer" } }, required: ["id"] },
|
|
11712
|
+
group: "social",
|
|
11713
|
+
need: write5,
|
|
11714
|
+
risk: "W3",
|
|
11715
|
+
gates: ["VER"],
|
|
11716
|
+
run: async (ctx, { id }) => {
|
|
11717
|
+
const n = Number(id);
|
|
11718
|
+
if (!Number.isInteger(n) || n <= 0) throw new Error("Which post?");
|
|
11719
|
+
const post = await ws(ctx).publishSocialNow(n, { background: true });
|
|
11720
|
+
return post.status === "published" ? post : { ...post, note: "Posting now. Check social_post_get in a minute for its link." };
|
|
11721
|
+
}
|
|
11722
|
+
}),
|
|
11723
|
+
tool({
|
|
11724
|
+
name: "social_posted_update",
|
|
11725
|
+
description: "Change a social post that has already gone out. On LinkedIn the new text replaces the old in place: the post keeps its likes and comments (its pictures can't change this way). With repost, and on Facebook, Instagram and Google, the old post comes down with its likes and comments and the new version goes out now. Without social_copy and with repost, it goes out again as it is (e.g. after new pictures).",
|
|
11726
|
+
parameters: {
|
|
11727
|
+
type: "object",
|
|
11728
|
+
properties: {
|
|
11729
|
+
id: { type: "integer" },
|
|
11730
|
+
social_copy: { type: "string", description: "The post's new text" },
|
|
11731
|
+
repost: { type: "boolean", description: "Take it down and put it up again instead of editing it" }
|
|
11732
|
+
},
|
|
11733
|
+
required: ["id"]
|
|
11734
|
+
},
|
|
11735
|
+
group: "social",
|
|
11736
|
+
need: write5,
|
|
11737
|
+
risk: "W3",
|
|
11738
|
+
gates: ["VER"],
|
|
11739
|
+
run: async (ctx, { id, social_copy, repost }) => {
|
|
11740
|
+
const n = Number(id);
|
|
11741
|
+
if (!Number.isInteger(n) || n <= 0) throw new Error("Which post?");
|
|
11742
|
+
const copy = typeof social_copy === "string" ? social_copy.trim() : "";
|
|
11743
|
+
if (!copy && repost !== true) throw new Error("Give the new text (social_copy), or repost: true.");
|
|
11744
|
+
return ws(ctx).republishSocial(n, copy || void 0, repost === true);
|
|
11745
|
+
}
|
|
11746
|
+
}),
|
|
11747
|
+
tool({
|
|
11748
|
+
name: "social_posted_delete",
|
|
11749
|
+
description: "Take a social post that has gone out down from its channel (LinkedIn, Facebook, Instagram, Google). Its likes and comments go with it; this can't be undone.",
|
|
11750
|
+
parameters: { type: "object", properties: { id: { type: "integer" } }, required: ["id"] },
|
|
11751
|
+
group: "social",
|
|
11752
|
+
need: write5,
|
|
11753
|
+
risk: "W3",
|
|
11754
|
+
gates: ["VER"],
|
|
11755
|
+
run: async (ctx, { id }) => {
|
|
11756
|
+
const n = Number(id);
|
|
11757
|
+
if (!Number.isInteger(n) || n <= 0) throw new Error("Which post?");
|
|
11758
|
+
return ws(ctx).deletePostedSocial(n);
|
|
11759
|
+
}
|
|
11760
|
+
}),
|
|
11679
11761
|
tool({
|
|
11680
11762
|
name: "social_skip",
|
|
11681
11763
|
description: "Skip a LinkedIn post (it won't be posted; it can be restored).",
|
|
@@ -11922,27 +12004,35 @@ var socialTools = [
|
|
|
11922
12004
|
}),
|
|
11923
12005
|
tool({
|
|
11924
12006
|
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
|
|
12007
|
+
description: "Put the user's own pictures on a post, in order: pictures from this chat (files: ones the user added or that were made here) or public https links to image files (urls), JPEG, PNG or WebP, for example carousel slides. 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. If you can pass neither, give the user social_upload_link. Preview first.",
|
|
11926
12008
|
parameters: {
|
|
11927
12009
|
type: "object",
|
|
11928
12010
|
properties: {
|
|
11929
12011
|
id: { type: "integer", description: "The post (from social_list), not gone out yet" },
|
|
12012
|
+
files: {
|
|
12013
|
+
type: "array",
|
|
12014
|
+
items: FILE_SCHEMA,
|
|
12015
|
+
description: "Pictures from this chat, in the order they should appear"
|
|
12016
|
+
},
|
|
11930
12017
|
urls: {
|
|
11931
12018
|
type: "array",
|
|
11932
12019
|
items: { type: "string" },
|
|
11933
|
-
description: "Public https links to the image files, in the
|
|
12020
|
+
description: "Public https links to the image files, in order (after the files, when both are given)"
|
|
11934
12021
|
},
|
|
11935
12022
|
append: { type: "boolean", description: "Add after the post's current pictures instead of replacing them" }
|
|
11936
12023
|
},
|
|
11937
|
-
required: ["id"
|
|
12024
|
+
required: ["id"]
|
|
11938
12025
|
},
|
|
12026
|
+
fileParams: ["files"],
|
|
11939
12027
|
group: "social",
|
|
11940
12028
|
need: write5,
|
|
11941
12029
|
risk: "W1",
|
|
11942
12030
|
gates: ["VER"],
|
|
11943
|
-
run: async (ctx, { id, urls, append }) => {
|
|
11944
|
-
const list2 = (Array.isArray(urls) ? urls : []).map((u) => String(u).trim()).filter(
|
|
11945
|
-
|
|
12031
|
+
run: async (ctx, { id, urls, files, append }) => {
|
|
12032
|
+
const list2 = [...fileLinks(files), ...(Array.isArray(urls) ? urls : []).map((u) => String(u).trim())].filter(
|
|
12033
|
+
Boolean
|
|
12034
|
+
);
|
|
12035
|
+
if (!list2.length) throw new Error("Give the pictures (files from this chat, or https links).");
|
|
11946
12036
|
if (list2.length > 20) throw new Error("A post takes up to 20 pictures.");
|
|
11947
12037
|
if (list2.some((u) => !u.startsWith("https://"))) throw new Error("Every picture needs a public https link.");
|
|
11948
12038
|
const out = await ws(ctx).setSocialPictures(Number(id), { urls: list2, append: append === true });
|
|
@@ -11955,30 +12045,57 @@ var socialTools = [
|
|
|
11955
12045
|
}),
|
|
11956
12046
|
tool({
|
|
11957
12047
|
name: "social_carousel_pdf_attach",
|
|
11958
|
-
description: "A LinkedIn post goes out as this PDF: a swipeable document carousel (a public https link to
|
|
12048
|
+
description: "A LinkedIn post goes out as this PDF: a swipeable document carousel. The PDF from this chat (file: one the user added or that was made here) or a public https link to it (url); 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. If you can pass neither, give the user social_upload_link. Preview first.",
|
|
11959
12049
|
parameters: {
|
|
11960
12050
|
type: "object",
|
|
11961
12051
|
properties: {
|
|
11962
12052
|
id: { type: "integer", description: "A LinkedIn post (from social_list), not gone out yet" },
|
|
12053
|
+
file: { ...FILE_SCHEMA, description: "The PDF from this chat" },
|
|
11963
12054
|
url: { type: "string", description: "Public https link to the PDF file" },
|
|
11964
12055
|
title: {
|
|
11965
12056
|
type: "string",
|
|
11966
12057
|
description: "Shown on the carousel (up to 120 characters); the post's topic if left out"
|
|
11967
12058
|
}
|
|
11968
12059
|
},
|
|
11969
|
-
required: ["id"
|
|
12060
|
+
required: ["id"]
|
|
11970
12061
|
},
|
|
12062
|
+
fileParams: ["file"],
|
|
11971
12063
|
group: "social",
|
|
11972
12064
|
need: write5,
|
|
11973
12065
|
risk: "W1",
|
|
11974
12066
|
gates: ["VER", "LI"],
|
|
11975
|
-
run: async (ctx, { id, url, title }) => {
|
|
11976
|
-
const link = String(url ?? "").trim();
|
|
11977
|
-
if (!link.startsWith("https://"))
|
|
12067
|
+
run: async (ctx, { id, url, file, title }) => {
|
|
12068
|
+
const link = fileLinks(file)[0] ?? String(url ?? "").trim();
|
|
12069
|
+
if (!link.startsWith("https://"))
|
|
12070
|
+
throw new Error("Give the PDF (a file from this chat, or a public https link).");
|
|
11978
12071
|
const out = await ws(ctx).attachSocialCarouselPdf(Number(id), { url: link, ...title ? { title } : {} });
|
|
11979
12072
|
return { id: out.id, pages: out.pages, pictures: out.pictures, carousel: out.carousel ?? null };
|
|
11980
12073
|
}
|
|
11981
12074
|
}),
|
|
12075
|
+
tool({
|
|
12076
|
+
name: "social_upload_link",
|
|
12077
|
+
description: "A link that opens one post's picture in the CMO.so app, where the user uploads their own photos (up to 20, a carousel on LinkedIn) or, on a computer, a PDF carousel themselves. Use it when you can't pass the files (this chat can't send files to tools, or they are on the user's device). Give the link; nothing changes until they upload.",
|
|
12078
|
+
parameters: {
|
|
12079
|
+
type: "object",
|
|
12080
|
+
properties: { id: { type: "integer", description: "The post (from social_list), not gone out yet" } },
|
|
12081
|
+
required: ["id"]
|
|
12082
|
+
},
|
|
12083
|
+
group: "social",
|
|
12084
|
+
need: read5,
|
|
12085
|
+
risk: "R0",
|
|
12086
|
+
run: async (ctx, { id }) => {
|
|
12087
|
+
const post = await ws(ctx).socialPost(Number(id));
|
|
12088
|
+
if (["published", "publishing", "cancelled"].includes(String(post.status)))
|
|
12089
|
+
throw new Error("That post has already gone out (or was skipped): its pictures can't change.");
|
|
12090
|
+
const site = ctx.domain ? `&site=${encodeURIComponent(ctx.domain)}` : "";
|
|
12091
|
+
return {
|
|
12092
|
+
id: post.id,
|
|
12093
|
+
channel: post.provider ?? null,
|
|
12094
|
+
url: `https://app.cmo.so/social?picture=${post.id}${site}`,
|
|
12095
|
+
how: post.provider === "linkedin" ? "Open it, then Upload several photos (in order), or on a computer Upload a PDF carousel." : "Open it, then Upload several photos (in order)."
|
|
12096
|
+
};
|
|
12097
|
+
}
|
|
12098
|
+
}),
|
|
11982
12099
|
tool({
|
|
11983
12100
|
name: "social_carousel_set",
|
|
11984
12101
|
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.",
|
|
@@ -16499,7 +16616,7 @@ var program = new Command();
|
|
|
16499
16616
|
// package.json
|
|
16500
16617
|
var package_default = {
|
|
16501
16618
|
name: "@cmo-so/cli",
|
|
16502
|
-
version: "0.9.
|
|
16619
|
+
version: "0.9.2",
|
|
16503
16620
|
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.",
|
|
16504
16621
|
type: "module",
|
|
16505
16622
|
license: "MIT",
|
|
@@ -26688,6 +26805,30 @@ function description(t) {
|
|
|
26688
26805
|
].filter(Boolean);
|
|
26689
26806
|
return `${t.description}${notes.length ? ` (${notes.join("; ")}.)` : ""} Changes data: call without confirm for a preview, then with confirm: true and the confirmation after the user agrees.`;
|
|
26690
26807
|
}
|
|
26808
|
+
function fileMeta(t) {
|
|
26809
|
+
return t.fileParams?.length ? { "openai/fileParams": t.fileParams } : void 0;
|
|
26810
|
+
}
|
|
26811
|
+
function withFreshFiles(t, signed, now) {
|
|
26812
|
+
if (!t.fileParams?.length) return signed;
|
|
26813
|
+
const fresh = /* @__PURE__ */ new Map();
|
|
26814
|
+
const collect = (v) => {
|
|
26815
|
+
for (const f of Array.isArray(v) ? v : [v])
|
|
26816
|
+
if (f && typeof f === "object" && typeof f.file_id === "string")
|
|
26817
|
+
fresh.set(f.file_id, f);
|
|
26818
|
+
};
|
|
26819
|
+
for (const p of t.fileParams) collect(now[p]);
|
|
26820
|
+
const swap = (v) => {
|
|
26821
|
+
if (Array.isArray(v)) return v.map(swap);
|
|
26822
|
+
if (v && typeof v === "object" && typeof v.file_id === "string") {
|
|
26823
|
+
const f = fresh.get(v.file_id);
|
|
26824
|
+
return f && typeof f.download_url === "string" ? { ...v, download_url: f.download_url } : v;
|
|
26825
|
+
}
|
|
26826
|
+
return v;
|
|
26827
|
+
};
|
|
26828
|
+
const out = { ...signed };
|
|
26829
|
+
for (const p of t.fileParams) if (p in out) out[p] = swap(out[p]);
|
|
26830
|
+
return out;
|
|
26831
|
+
}
|
|
26691
26832
|
function toolArgs(args) {
|
|
26692
26833
|
const out = {};
|
|
26693
26834
|
for (const [k, v] of Object.entries(args ?? {})) if (!CONTROL_ARGS.includes(k) && v !== void 0) out[k] = v;
|
|
@@ -26817,7 +26958,7 @@ function createMcpServer(deps) {
|
|
|
26817
26958
|
description: description(t),
|
|
26818
26959
|
inputSchema: inputSchema(t),
|
|
26819
26960
|
annotations: { title: t.name.replace(/_/g, " "), ...annotations(t) },
|
|
26820
|
-
...deps.widgets && toolMeta(t.name) ? { _meta: toolMeta(t.name) } : {}
|
|
26961
|
+
...fileMeta(t) || deps.widgets && toolMeta(t.name) ? { _meta: { ...deps.widgets ? toolMeta(t.name) : {}, ...fileMeta(t) } } : {}
|
|
26821
26962
|
}))
|
|
26822
26963
|
};
|
|
26823
26964
|
});
|
|
@@ -26853,7 +26994,7 @@ function createMcpServer(deps) {
|
|
|
26853
26994
|
return fail(
|
|
26854
26995
|
access.keyAccess === "read" && isWrite(t) ? "This key can only read. Reconnect with write access to make changes." : `You don't have permission to do this on ${domain ?? "this website"}.`
|
|
26855
26996
|
);
|
|
26856
|
-
const callArgs = approval ? approval.calls[0]?.args ?? {} : toolArgs(args);
|
|
26997
|
+
const callArgs = approval ? withFreshFiles(t, approval.calls[0]?.args ?? {}, toolArgs(args)) : toolArgs(args);
|
|
26857
26998
|
const ctx = { api: api2, domain, client: "mcp" };
|
|
26858
26999
|
if (!isWrite(t)) return ok(await t.run(ctx, callArgs), viewForTool(t.name) ? { workspace: domain } : void 0);
|
|
26859
27000
|
let standing = null;
|
|
@@ -27501,6 +27642,17 @@ social.command("carousel <id>").description("a LinkedIn post with a set of pictu
|
|
|
27501
27642
|
});
|
|
27502
27643
|
})
|
|
27503
27644
|
);
|
|
27645
|
+
social.command("post-now <id>").description("post it on its channel now, not at its posting time (even while posting is paused)").action(act(async (opts, id) => tool2(opts, "social_publish_now", { id: ids2([id])[0] })));
|
|
27646
|
+
social.command("edit-posted <id> [text...]").description("change a post that has gone out (LinkedIn edits it in place, keeping likes and comments)").option("--repost", "take it down and put it up again instead").action(
|
|
27647
|
+
act(
|
|
27648
|
+
async (opts, id, text3) => tool2(opts, "social_posted_update", {
|
|
27649
|
+
id: ids2([id])[0],
|
|
27650
|
+
...text3.length ? { social_copy: text3.join(" ") } : {},
|
|
27651
|
+
...opts.repost ? { repost: true } : {}
|
|
27652
|
+
})
|
|
27653
|
+
)
|
|
27654
|
+
);
|
|
27655
|
+
social.command("delete-posted <id>").description("take a post that has gone out down from its channel, with its likes and comments").action(act(async (opts, id) => tool2(opts, "social_posted_delete", { id: ids2([id])[0] })));
|
|
27504
27656
|
social.command("skip <id>").description("skip a draft").action(act(async (opts, id) => tool2(opts, "social_skip", { id: ids2([id])[0] })));
|
|
27505
27657
|
social.command("restore <id>").description("bring a skipped draft back").action(act(async (opts, id) => tool2(opts, "social_restore", { id: ids2([id])[0] })));
|
|
27506
27658
|
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.9.
|
|
3
|
+
"version": "0.9.2",
|
|
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",
|