@cmo-so/cli 0.5.0 → 0.6.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 +71 -5
- package/package.json +1 -1
package/dist/cmo.mjs
CHANGED
|
@@ -7932,6 +7932,10 @@ function domainApi(api2, domain) {
|
|
|
7932
7932
|
/** A post on one video channel (YouTube by default, or TikTok) from a ready video, waiting in Review; Maggie
|
|
7933
7933
|
* writes the details (YouTube) or the caption (TikTok) unless write is false. */
|
|
7934
7934
|
postFromVideo: (videoId, opts = {}) => post(`${base}/social/video/${videoId}/post`, opts),
|
|
7935
|
+
/** Make a LinkedIn, Facebook or Instagram post a video post: it goes out as this video (its picture is dropped). */
|
|
7936
|
+
attachVideo: (id, videoId) => post(`${base}/social/${id}/video`, { video_id: videoId }),
|
|
7937
|
+
/** Take the video off a post: it's a text post again. */
|
|
7938
|
+
detachVideo: (id) => api2.request(`${base}/social/${id}/video`, { method: "DELETE" }),
|
|
7935
7939
|
youtubeSettings: () => api2.request(`${base}/social/youtube/settings`),
|
|
7936
7940
|
setYouTubeSettings: (s) => put(`${base}/social/youtube/settings`, s),
|
|
7937
7941
|
/** A YouTube post's title, tags, visibility, made-for-kids or altered-content answer. */
|
|
@@ -8180,7 +8184,7 @@ var list = (xs, n = 5) => {
|
|
|
8180
8184
|
var ids = (a) => (Array.isArray(a.ids) && a.ids.length ? a.ids : a.id != null ? [a.id] : []).map(Number);
|
|
8181
8185
|
var keys = (a) => Object.keys(a ?? {}).filter((k) => a[k] !== void 0);
|
|
8182
8186
|
var videoChannels = (a) => (Array.isArray(a.channels) && a.channels.length ? a.channels : ["youtube"]).map(
|
|
8183
|
-
(c) =>
|
|
8187
|
+
(c) => ({ tiktok: "TikTok", linkedin: "LinkedIn", facebook: "Facebook", instagram: "Instagram" })[String(c)] ?? "YouTube"
|
|
8184
8188
|
);
|
|
8185
8189
|
var confirmCopy = {
|
|
8186
8190
|
// --------------------------------------------------------- account / setup
|
|
@@ -8367,6 +8371,14 @@ var confirmCopy = {
|
|
|
8367
8371
|
en: () => "Make a link to connect your YouTube channel.",
|
|
8368
8372
|
"zh-Hant": () => "\u7522\u751F\u9023\u63A5\u4F60 YouTube \u983B\u9053\u7684\u9023\u7D50\u3002"
|
|
8369
8373
|
},
|
|
8374
|
+
social_video_attach: {
|
|
8375
|
+
en: (a) => `Make post #${a.id} a video post with ${a.url} (its picture is removed).`,
|
|
8376
|
+
"zh-Hant": (a) => `\u628A\u8CBC\u6587 #${a.id} \u6539\u6210\u5F71\u7247\u8CBC\u6587\uFF0C\u7528 ${a.url}\uFF08\u5716\u7247\u6703\u79FB\u9664\uFF09\u3002`
|
|
8377
|
+
},
|
|
8378
|
+
social_video_detach: {
|
|
8379
|
+
en: (a) => `Take the video off post #${a.id} (it's a text post again).`,
|
|
8380
|
+
"zh-Hant": (a) => `\u79FB\u9664\u8CBC\u6587 #${a.id} \u7684\u5F71\u7247\uFF08\u8B8A\u56DE\u6587\u5B57\u8CBC\u6587\uFF09\u3002`
|
|
8381
|
+
},
|
|
8370
8382
|
tiktok_connect_link: {
|
|
8371
8383
|
en: () => "Make a link to connect your TikTok account.",
|
|
8372
8384
|
"zh-Hant": () => "\u7522\u751F\u9023\u63A5\u4F60 TikTok \u5E33\u865F\u7684\u9023\u7D50\u3002"
|
|
@@ -10407,7 +10419,7 @@ var write5 = { resource: "autopilot", level: "write" };
|
|
|
10407
10419
|
var moves2 = ["up", "down", "top"];
|
|
10408
10420
|
var CHANNEL_KEYS = ["linkedin", "facebook", "instagram", "google_business"];
|
|
10409
10421
|
var ALL_CHANNEL_KEYS = [...CHANNEL_KEYS, "youtube", "tiktok"];
|
|
10410
|
-
var VIDEO_KEYS = ["youtube", "tiktok"];
|
|
10422
|
+
var VIDEO_KEYS = ["linkedin", "facebook", "instagram", "youtube", "tiktok"];
|
|
10411
10423
|
var TIKTOK_PRIVACY = ["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "FOLLOWER_OF_CREATOR", "SELF_ONLY"];
|
|
10412
10424
|
var channelsParam = {
|
|
10413
10425
|
type: "array",
|
|
@@ -10440,6 +10452,9 @@ var item = (i) => ({
|
|
|
10440
10452
|
youtube: i.youtube ?? null,
|
|
10441
10453
|
video: i.video ? { status: i.video.status, seconds: i.video.duration, shape: i.video.shape, error: i.video.error } : null
|
|
10442
10454
|
} : {},
|
|
10455
|
+
...i.video_asset_id && i.provider !== "youtube" && i.provider !== "tiktok" ? {
|
|
10456
|
+
video: i.video ? { status: i.video.status, seconds: i.video.duration, shape: i.video.shape, error: i.video.error } : { id: i.video_asset_id }
|
|
10457
|
+
} : {},
|
|
10443
10458
|
...i.provider === "tiktok" ? {
|
|
10444
10459
|
tiktok: i.tiktok ?? null,
|
|
10445
10460
|
video: i.video ? { status: i.video.status, seconds: i.video.duration, shape: i.video.shape, error: i.video.error } : null
|
|
@@ -11421,6 +11436,44 @@ var socialTools = [
|
|
|
11421
11436
|
return { url: out.url, expires_in_seconds: out.expires_in_seconds };
|
|
11422
11437
|
}
|
|
11423
11438
|
}),
|
|
11439
|
+
tool({
|
|
11440
|
+
name: "social_video_attach",
|
|
11441
|
+
description: "Make a LinkedIn, Facebook or Instagram post that hasn't gone out a video post: it goes out as this video (a DIRECT link to a video file, 5-30 seconds; never a platform page) with its text, and its picture is removed (a video post has no picture). Not for Google Business (posts can't carry video). Preview first.",
|
|
11442
|
+
parameters: {
|
|
11443
|
+
type: "object",
|
|
11444
|
+
properties: {
|
|
11445
|
+
id: { type: "integer", description: "The post (from social_list)" },
|
|
11446
|
+
url: { type: "string", description: "Direct https link to the video file" }
|
|
11447
|
+
},
|
|
11448
|
+
required: ["id", "url"]
|
|
11449
|
+
},
|
|
11450
|
+
group: "social",
|
|
11451
|
+
need: write5,
|
|
11452
|
+
risk: "W1",
|
|
11453
|
+
gates: ["VER"],
|
|
11454
|
+
run: async (ctx, a) => {
|
|
11455
|
+
const d = ws(ctx);
|
|
11456
|
+
let v = await d.videoFromLink(String(a.url));
|
|
11457
|
+
const until = Date.now() + 15e4;
|
|
11458
|
+
while ((v.status === "checking" || v.status === "uploading") && Date.now() < until) {
|
|
11459
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
11460
|
+
v = await d.video(v.id);
|
|
11461
|
+
}
|
|
11462
|
+
if (v.status !== "ready")
|
|
11463
|
+
return { ok: false, video: { status: v.status, error: v.error, seconds: v.source_duration } };
|
|
11464
|
+
return { ok: true, post: item(await d.attachVideo(Number(a.id), v.id)) };
|
|
11465
|
+
}
|
|
11466
|
+
}),
|
|
11467
|
+
tool({
|
|
11468
|
+
name: "social_video_detach",
|
|
11469
|
+
description: "Take the video off a LinkedIn, Facebook or Instagram post that hasn't gone out: it's a text post again (a picture can be added after). Preview first.",
|
|
11470
|
+
parameters: { type: "object", properties: { id: { type: "integer" } }, required: ["id"] },
|
|
11471
|
+
group: "social",
|
|
11472
|
+
need: write5,
|
|
11473
|
+
risk: "W1",
|
|
11474
|
+
gates: ["VER"],
|
|
11475
|
+
run: async (ctx, a) => item(await ws(ctx).detachVideo(Number(a.id)))
|
|
11476
|
+
}),
|
|
11424
11477
|
tool({
|
|
11425
11478
|
name: "tiktok_connect_link",
|
|
11426
11479
|
description: "A link for the user to connect their TikTok account (or reconnect): they sign in to TikTok and allow Maggie to see their profile and post videos. Give them the link to open; never ask for a password. TikTok is in beta (testers only; until TikTok reviews our app, videos post as 'Only me' and the account must be private). TikTok posts are short videos (5-30 seconds) that the user approves one by one.",
|
|
@@ -11476,7 +11529,7 @@ var socialTools = [
|
|
|
11476
11529
|
}),
|
|
11477
11530
|
tool({
|
|
11478
11531
|
name: "social_video_add",
|
|
11479
|
-
description: "Make
|
|
11532
|
+
description: "Make video posts (channels: linkedin, facebook, instagram, youtube, tiktok; default youtube; one post each, never google_business: Google posts can't carry video) from a video: a DIRECT link to a video file (an .mp4 on their website, a public Dropbox link), never a YouTube / TikTok / Instagram page (we never download those; tell the user to upload the video in the app instead). The video must be 5-30 seconds: give start/end (seconds) to use a part of a longer one. vertical: make a wide video vertical (a Short) cropped at left, middle or right. Maggie writes YouTube's title, description and tags, TikTok's caption, or the LinkedIn / Facebook / Instagram text, from the video and the note (one AI generation per channel) unless write is false. Each post waits in Review: video posts are always approved by the user. YouTube asks whether it's made for kids; TikTok asks who can view it and the other TikTok settings (social_tiktok_set, as the user chooses). is_aigc: TikTok's AI-generated label, only when the user says the video was made with AI. Preview first.",
|
|
11480
11533
|
parameters: {
|
|
11481
11534
|
type: "object",
|
|
11482
11535
|
properties: {
|
|
@@ -11484,7 +11537,7 @@ var socialTools = [
|
|
|
11484
11537
|
channels: {
|
|
11485
11538
|
type: "array",
|
|
11486
11539
|
items: { type: "string", enum: [...VIDEO_KEYS] },
|
|
11487
|
-
description: "
|
|
11540
|
+
description: "Where the video goes (one post each); default youtube"
|
|
11488
11541
|
},
|
|
11489
11542
|
is_aigc: { type: "boolean", description: "TikTok: the video is AI-generated (the user said so)" },
|
|
11490
11543
|
note: { type: "string", description: "What the video is about, in the user's words" },
|
|
@@ -15209,7 +15262,7 @@ var program = new Command();
|
|
|
15209
15262
|
// package.json
|
|
15210
15263
|
var package_default = {
|
|
15211
15264
|
name: "@cmo-so/cli",
|
|
15212
|
-
version: "0.
|
|
15265
|
+
version: "0.6.0",
|
|
15213
15266
|
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.",
|
|
15214
15267
|
type: "module",
|
|
15215
15268
|
license: "MIT",
|
|
@@ -23427,6 +23480,19 @@ youtube.command("set <id>").description("a YouTube post's title, tags, visibilit
|
|
|
23427
23480
|
)
|
|
23428
23481
|
);
|
|
23429
23482
|
youtube.command("settings").description("your YouTube defaults (visibility, made for kids)").action(act(async (opts) => tool2(opts, "youtube_settings")));
|
|
23483
|
+
var video = social.command("video").description("video posts: LinkedIn, Facebook, Instagram, YouTube, TikTok");
|
|
23484
|
+
video.command("add <url>").description("posts from a direct link to a video file (5-30 s), one per channel; Maggie writes the words").option("--channel <names>", "linkedin, facebook, instagram, youtube, tiktok (comma-separated)", "youtube").option("--note <text>", "what the video is about").option("--no-write", "write the words yourself").action(
|
|
23485
|
+
act(
|
|
23486
|
+
async (opts, url) => tool2(opts, "social_video_add", {
|
|
23487
|
+
url,
|
|
23488
|
+
channels: (channelsOpt(opts.channel) ?? ["youtube"]).filter((c) => c !== "google_business"),
|
|
23489
|
+
...opts.note ? { note: opts.note } : {},
|
|
23490
|
+
write: opts.write !== false
|
|
23491
|
+
})
|
|
23492
|
+
)
|
|
23493
|
+
);
|
|
23494
|
+
video.command("attach <id> <url>").description("make a LinkedIn / Facebook / Instagram post a video post (its picture is removed)").action(act(async (opts, id, url) => tool2(opts, "social_video_attach", { id: ids2([id])[0], url })));
|
|
23495
|
+
video.command("detach <id>").description("take the video off a post (it's a text post again)").action(act(async (opts, id) => tool2(opts, "social_video_detach", { id: ids2([id])[0] })));
|
|
23430
23496
|
var tiktok = social.command("tiktok").description("your TikTok account (beta): short videos you approve one by one");
|
|
23431
23497
|
tiktok.command("connect").description("a link to connect your TikTok account").action(act(async (opts) => tool2(opts, "tiktok_connect_link")));
|
|
23432
23498
|
tiktok.command("account").description("what your TikTok account allows now: who can view, comments / Duet / Stitch, longest video").action(act(async (opts) => tool2(opts, "tiktok_creator")));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|