@cmo-so/cli 0.10.0 → 0.11.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 +140 -20
- package/package.json +1 -1
package/dist/cmo.mjs
CHANGED
|
@@ -8089,8 +8089,17 @@ function domainApi(api2, domain) {
|
|
|
8089
8089
|
headers: { "Content-Type": "application/json" },
|
|
8090
8090
|
body: JSON.stringify(changes)
|
|
8091
8091
|
}),
|
|
8092
|
-
/** Make it (billed): send back the estimate the person agreed to.
|
|
8093
|
-
|
|
8092
|
+
/** Make it (billed): send back the estimate the person agreed to (estimate.pictures_usd for stage
|
|
8093
|
+
* "pictures": only the first frames, to look at before the paid clips). A script that doesn't fit its
|
|
8094
|
+
* timing is refused (409 script_check) unless goAhead. */
|
|
8095
|
+
makeAdVideo: (adId, estimateUsd, opts = {}) => post(`${base}/social/ad-videos/${adId}/make`, {
|
|
8096
|
+
confirm: true,
|
|
8097
|
+
estimate_usd: estimateUsd,
|
|
8098
|
+
...opts.stage ? { stage: opts.stage } : {},
|
|
8099
|
+
...opts.goAhead ? { go_ahead: true } : {}
|
|
8100
|
+
}),
|
|
8101
|
+
/** The person's own photo as a scene's first frame (base64, or photo_url: a picture already on CMO.so). */
|
|
8102
|
+
adVideoScenePicture: (adId, sceneId, photo) => post(`${base}/social/ad-videos/${adId}/scenes/${encodeURIComponent(sceneId)}/picture`, photo),
|
|
8094
8103
|
/** Stop one being made (what's made is kept), or remove one. */
|
|
8095
8104
|
stopAdVideo: (adId) => api2.request(`${base}/social/ad-videos/${adId}`, { method: "DELETE" }),
|
|
8096
8105
|
/** Make a LinkedIn, Facebook or Instagram post a video post: it goes out as this video (its picture is dropped). */
|
|
@@ -8848,8 +8857,12 @@ var confirmCopy = {
|
|
|
8848
8857
|
"zh-Hant": (a) => `\u4FEE\u6539\u5EE3\u544A\u5F71\u7247 #${a.id} \u7684\u5206\u93E1\uFF1A${keys(a).filter((k) => k !== "id").join("\u3001") || "\u7121"}\u3002`
|
|
8849
8858
|
},
|
|
8850
8859
|
ad_video_make: {
|
|
8851
|
-
en: (a) => `Make ad video #${a.id} now. This costs about $${Number(a.estimate_usd ?? 0).toFixed(2)} (pictures, moving clips, voice and music), in the background
|
|
8852
|
-
"zh-Hant": (a) => `\u73FE\u5728\u88FD\u4F5C\u5EE3\u544A\u5F71\u7247 #${a.id}\uFF0C\u8CBB\u7528\u7D04 $${Number(a.estimate_usd ?? 0).toFixed(2)}\uFF08\u5716\u7247\u3001\u52D5\u614B\u7247\u6BB5\u3001\u914D\u97F3\u548C\u914D\u6A02\uFF09\uFF0C\u5728\u80CC\u666F\u9032\u884C\u3002`
|
|
8860
|
+
en: (a) => a.stage === "pictures" ? `Make the pictures of ad video #${a.id} now, to look at before the moving clips. This costs about $${Number(a.estimate_usd ?? 0).toFixed(2)}.` : `Make ad video #${a.id} now. This costs about $${Number(a.estimate_usd ?? 0).toFixed(2)} (pictures, moving clips, voice and music), in the background.${a.go_ahead ? " Its script doesn't fit its timing everywhere; it's made anyway." : ""}`,
|
|
8861
|
+
"zh-Hant": (a) => a.stage === "pictures" ? `\u73FE\u5728\u5148\u88FD\u4F5C\u5EE3\u544A\u5F71\u7247 #${a.id} \u7684\u5716\u7247\uFF0C\u770B\u904E\u518D\u505A\u52D5\u614B\u7247\u6BB5\u3002\u8CBB\u7528\u7D04 $${Number(a.estimate_usd ?? 0).toFixed(2)}\u3002` : `\u73FE\u5728\u88FD\u4F5C\u5EE3\u544A\u5F71\u7247 #${a.id}\uFF0C\u8CBB\u7528\u7D04 $${Number(a.estimate_usd ?? 0).toFixed(2)}\uFF08\u5716\u7247\u3001\u52D5\u614B\u7247\u6BB5\u3001\u914D\u97F3\u548C\u914D\u6A02\uFF09\uFF0C\u5728\u80CC\u666F\u9032\u884C\u3002${a.go_ahead ? "\u8173\u672C\u6709\u90E8\u5206\u6642\u9593\u4E0D\u5408\uFF0C\u4ECD\u7167\u6A23\u88FD\u4F5C\u3002" : ""}`
|
|
8862
|
+
},
|
|
8863
|
+
ad_video_picture: {
|
|
8864
|
+
en: (a) => `Use this picture as the first frame of scene ${a.scene} in ad video #${a.id} (its clip is made again).`,
|
|
8865
|
+
"zh-Hant": (a) => `\u628A\u9019\u5F35\u5716\u7247\u7528\u4F5C\u5EE3\u544A\u5F71\u7247 #${a.id} \u7B2C ${a.scene} \u683C\u7684\u7B2C\u4E00\u500B\u756B\u9762\uFF08\u9019\u683C\u7684\u52D5\u614B\u7247\u6BB5\u6703\u91CD\u505A\uFF09\u3002`
|
|
8853
8866
|
},
|
|
8854
8867
|
ad_video_stop: {
|
|
8855
8868
|
en: (a) => `Stop ad video #${a.id} if it's being made (what's made is kept), or remove it from the list.`,
|
|
@@ -11625,7 +11638,7 @@ var adVideo = (v) => ({
|
|
|
11625
11638
|
voice: v.plan.voice,
|
|
11626
11639
|
music: v.plan.music,
|
|
11627
11640
|
seconds: v.plan.seconds,
|
|
11628
|
-
end_card: v.plan.cta,
|
|
11641
|
+
end_card: { ...v.plan.cta, seconds: v.plan.card_seconds ?? 2.5 },
|
|
11629
11642
|
scenes: v.plan.scenes.map((s) => ({
|
|
11630
11643
|
id: s.id,
|
|
11631
11644
|
seconds: s.seconds,
|
|
@@ -11633,15 +11646,23 @@ var adVideo = (v) => ({
|
|
|
11633
11646
|
motion: s.motion,
|
|
11634
11647
|
line: s.line,
|
|
11635
11648
|
caption: s.caption,
|
|
11636
|
-
picture_url: s.still_url ?? null
|
|
11649
|
+
picture_url: s.still_url ?? null,
|
|
11650
|
+
own_picture: s.own_picture ?? false,
|
|
11651
|
+
...s.caption_in != null ? { caption_in: s.caption_in } : {},
|
|
11652
|
+
...s.caption_out != null ? { caption_out: s.caption_out } : {},
|
|
11653
|
+
...s.voice_at != null ? { voice_at: s.voice_at } : {}
|
|
11637
11654
|
}))
|
|
11638
11655
|
} : null,
|
|
11656
|
+
timeline: v.timeline ?? null,
|
|
11657
|
+
script_check: v.script_check ? { ok: v.script_check.ok, total: v.script_check.total, issues: v.script_check.issues } : null,
|
|
11658
|
+
pictures_ready: v.pictures_ready ?? false,
|
|
11639
11659
|
estimate: v.estimate,
|
|
11640
11660
|
cost_usd: v.cost_usd,
|
|
11641
11661
|
video_id: v.video_id,
|
|
11662
|
+
post: v.item ?? null,
|
|
11642
11663
|
video: v.video ? { status: v.video.status, preview_url: v.video.preview_url, poster_url: v.video.poster_url } : null,
|
|
11643
11664
|
error: v.error,
|
|
11644
|
-
next: v.status === "planned" ? `Show the storyboard and the cost (about $${(v.estimate?.usd ?? 0).toFixed(2)}); change it with ad_video_change,
|
|
11665
|
+
next: v.status === "planned" ? `Show the storyboard, the timeline, any script_check issues and the cost (about $${(v.estimate?.usd ?? 0).toFixed(2)}${v.pictures_ready ? "" : `; the pictures alone about $${(v.estimate?.pictures_usd ?? 0).toFixed(2)}`}); change it with ad_video_change (or ad_video_picture for the user's own photo); ${v.pictures_ready ? "the pictures are made: show them (picture_url), then" : "suggest ad_video_make stage pictures first to see the pictures cheaply, then"} ad_video_make when the user agrees to that cost.` : v.status === "running" ? "Being made; check again with ad_video_get in a minute or so." : v.status === "ready" ? v.item?.attached ? `Ready, and it's on the post it was made from (post ${v.item.id}): that post goes out as this video. To post it elsewhere too: social_video_add (video_id and channels) once video.status is ready.` : `Ready: post it with social_video_add (video_id and channels) once video.status is ready.${v.item?.skipped ? ` It wasn't put on post ${v.item.id}: ${v.item.skipped}` : ""}` : v.status === "failed" ? "Failed: say why; ad_video_make again carries on from what was made." : ""
|
|
11645
11666
|
});
|
|
11646
11667
|
var channelsParam = {
|
|
11647
11668
|
type: "array",
|
|
@@ -13062,7 +13083,7 @@ var socialTools = [
|
|
|
13062
13083
|
}),
|
|
13063
13084
|
tool({
|
|
13064
13085
|
name: "ad_video_plan",
|
|
13065
|
-
description: "Plan a short ad video (beta) and say what making it costs. Maggie writes a storyboard: 2-5 scenes, each with the first picture's brief (no words in pictures), what moves and one camera move, the narrator's line and the words on screen; an end card with the next step and the logo; a voice and music. One AI generation; nothing is billed by the second yet. brief: what the ad should do, in the user's words, or item_id: a post whose text is the brief. aspect 9:16 (Reels, TikTok, Shorts; default) or 16:9 (LinkedIn, YouTube); seconds 15, 20 (default) or 30; reference_url: a public YouTube ad whose rhythm to follow (Gemini watches it; never downloaded). Show the storyboard and the cost (estimate.items, estimate.usd) before ad_video_make. Preview first.",
|
|
13086
|
+
description: "Plan a short ad video (beta) and say what making it costs. Maggie writes a storyboard: 2-5 scenes, each with the first picture's brief (no words in pictures), what moves and one camera move, the narrator's line and the words on screen; an end card with the next step and the logo; a voice and music. One AI generation; nothing is billed by the second yet. brief: what the ad should do, in the user's words, or item_id: a post whose text is the brief (once made, the video goes on that post if it's still to go out on LinkedIn, Facebook or Instagram). aspect 9:16 (Reels, TikTok, Shorts; default) or 16:9 (LinkedIn, YouTube); seconds 15, 20 (default) or 30; reference_url: a public YouTube ad whose rhythm to follow (Gemini watches it; never downloaded). Show the storyboard and the cost (estimate.items, estimate.usd) before ad_video_make. Preview first.",
|
|
13066
13087
|
parameters: {
|
|
13067
13088
|
type: "object",
|
|
13068
13089
|
properties: {
|
|
@@ -13109,6 +13130,8 @@ var socialTools = [
|
|
|
13109
13130
|
cost_usd: v.cost_usd,
|
|
13110
13131
|
video_id: v.video_id,
|
|
13111
13132
|
video_status: v.video?.status ?? null,
|
|
13133
|
+
post_id: v.item?.id ?? null,
|
|
13134
|
+
on_post: v.item?.attached ?? false,
|
|
13112
13135
|
error: v.error
|
|
13113
13136
|
})),
|
|
13114
13137
|
voices: out.voices
|
|
@@ -13130,7 +13153,7 @@ var socialTools = [
|
|
|
13130
13153
|
}),
|
|
13131
13154
|
tool({
|
|
13132
13155
|
name: "ad_video_change",
|
|
13133
|
-
description:
|
|
13156
|
+
description: `Change an ad video's storyboard (beta) as the user asks, before making it (or after: only what changed is made again). A scene (scenes[]): its picture brief (still), movement (motion), the narrator's line, the words on screen (caption), its seconds (3-8), and timing inside it: caption_in / caption_out (when the words show and go, seconds into the scene) and voice_at (when the narrator starts); -1 puts a time back to its default; picture "ai" swaps the user's own photo back to an AI picture. add_scenes (still required; after: a scene id, else at the end); order: the scene ids to keep, in order (removes and reorders; 2-5 scenes); redo_scenes: make scenes' pictures and clips again. The whole video: style (the look; every AI picture and clip again), aspect 9:16 or 16:9 (every picture and clip again), seconds 15/20/30 (scenes rescaled), card_seconds (1.5-4); the end card (cta_headline, cta_sub); the voice (a name from ad_video_list voices, or none); the music (words, or none). redo says what's made again: a picture brief, the look or the shape mean new paid pictures and clips, a movement a new paid clip; lines, words, timing, order, end card, voice or music are cheap. Returns the new cost, the timeline and the script check (lines that don't fit their time, captions too fast to read, length); fix errors before ad_video_make. Not while it's being made. Preview first.`,
|
|
13134
13157
|
parameters: {
|
|
13135
13158
|
type: "object",
|
|
13136
13159
|
properties: {
|
|
@@ -13145,12 +13168,36 @@ var socialTools = [
|
|
|
13145
13168
|
motion: { type: "string" },
|
|
13146
13169
|
line: { type: "string" },
|
|
13147
13170
|
caption: { type: "string" },
|
|
13148
|
-
seconds: { type: "number" }
|
|
13171
|
+
seconds: { type: "number" },
|
|
13172
|
+
caption_in: { type: "number", description: "Words show at, seconds into the scene (-1: default)" },
|
|
13173
|
+
caption_out: { type: "number", description: "Words go at, seconds into the scene (-1: default)" },
|
|
13174
|
+
voice_at: { type: "number", description: "Narrator starts at, seconds into the scene (-1: default)" },
|
|
13175
|
+
picture: { type: "string", enum: ["ai"], description: "Back to an AI picture" }
|
|
13149
13176
|
},
|
|
13150
13177
|
required: ["id"]
|
|
13151
13178
|
}
|
|
13152
13179
|
},
|
|
13180
|
+
add_scenes: {
|
|
13181
|
+
type: "array",
|
|
13182
|
+
items: {
|
|
13183
|
+
type: "object",
|
|
13184
|
+
properties: {
|
|
13185
|
+
still: { type: "string", description: "The first frame's brief (no words in it)" },
|
|
13186
|
+
motion: { type: "string" },
|
|
13187
|
+
line: { type: "string" },
|
|
13188
|
+
caption: { type: "string" },
|
|
13189
|
+
seconds: { type: "number" },
|
|
13190
|
+
after: { type: "string", description: "Put it after this scene id" }
|
|
13191
|
+
},
|
|
13192
|
+
required: ["still"]
|
|
13193
|
+
}
|
|
13194
|
+
},
|
|
13195
|
+
order: { type: "array", items: { type: "string" }, description: "Scene ids to keep, in order" },
|
|
13153
13196
|
redo_scenes: { type: "array", items: { type: "string" } },
|
|
13197
|
+
style: { type: "string", description: "The look of every scene, one sentence" },
|
|
13198
|
+
aspect: { type: "string", enum: ["9:16", "16:9"] },
|
|
13199
|
+
seconds: { type: "integer", description: "15, 20 or 30" },
|
|
13200
|
+
card_seconds: { type: "number", description: "The end card's seconds, 1.5-4" },
|
|
13154
13201
|
cta_headline: { type: "string" },
|
|
13155
13202
|
cta_sub: { type: "string" },
|
|
13156
13203
|
voice: { type: "string", description: "A voice name, or none" },
|
|
@@ -13169,8 +13216,22 @@ var socialTools = [
|
|
|
13169
13216
|
...a.cta_sub != null ? { sub: a.cta_sub } : {}
|
|
13170
13217
|
};
|
|
13171
13218
|
const out = await ws(ctx).changeAdVideo(Number(a.id), {
|
|
13172
|
-
...a.scenes?.length ? {
|
|
13219
|
+
...a.scenes?.length ? {
|
|
13220
|
+
scenes: a.scenes.map((sc) => {
|
|
13221
|
+
const out2 = { ...sc };
|
|
13222
|
+
for (const k of ["caption_in", "caption_out", "voice_at"]) {
|
|
13223
|
+
if (out2[k] != null && Number(out2[k]) < 0) out2[k] = null;
|
|
13224
|
+
}
|
|
13225
|
+
return out2;
|
|
13226
|
+
})
|
|
13227
|
+
} : {},
|
|
13228
|
+
...a.add_scenes?.length ? { add_scenes: a.add_scenes } : {},
|
|
13229
|
+
...a.order?.length ? { order: a.order.map(String) } : {},
|
|
13173
13230
|
...a.redo_scenes?.length ? { redo_scenes: a.redo_scenes } : {},
|
|
13231
|
+
...a.style ? { style: a.style } : {},
|
|
13232
|
+
...a.aspect ? { aspect: a.aspect } : {},
|
|
13233
|
+
...a.seconds ? { seconds: Number(a.seconds) } : {},
|
|
13234
|
+
...a.card_seconds != null ? { card_seconds: Number(a.card_seconds) } : {},
|
|
13174
13235
|
...Object.keys(cta).length ? { cta } : {},
|
|
13175
13236
|
...a.voice != null ? { voice: /^none$/i.test(a.voice) ? false : a.voice } : {},
|
|
13176
13237
|
...a.music != null ? { music: /^none$/i.test(a.music) ? "" : a.music } : {},
|
|
@@ -13181,12 +13242,14 @@ var socialTools = [
|
|
|
13181
13242
|
}),
|
|
13182
13243
|
tool({
|
|
13183
13244
|
name: "ad_video_make",
|
|
13184
|
-
description:
|
|
13245
|
+
description: `Make the ad video (beta). stage "pictures" makes only the first frames (cheap: estimate.pictures_usd) so the user sees them (picture_url) and changes any before the paid clips; then make it again (stage all) for the rest. Stage all: its pictures (on the user's own ChatGPT when their Maggie Bridge has images on, else Gemini), the moving clips (Gemini, billed per second), the voice, the music, the words and the end card, cut into one video, in the background (several minutes; follow it with ad_video_get). It COSTS MONEY: estimate_usd must be the estimate.usd (or estimate.pictures_usd for stage pictures) from ad_video_plan / ad_video_get / ad_video_change that the user agreed to; if the cost changed it is refused with the new one. A script that doesn't fit its timing (script_check errors) is refused: fix it with ad_video_change, or go_ahead true only when the user says to make it anyway. Each website has a daily limit. Making it again after a change or a failure only makes what's missing. When ready, post it with social_video_add and its video_id. Preview first.`,
|
|
13185
13246
|
parameters: {
|
|
13186
13247
|
type: "object",
|
|
13187
13248
|
properties: {
|
|
13188
13249
|
id: { type: "integer", description: "The ad video" },
|
|
13189
|
-
estimate_usd: { type: "number", description: "The cost the user agreed to (estimate.usd)" }
|
|
13250
|
+
estimate_usd: { type: "number", description: "The cost the user agreed to (estimate.usd, or pictures_usd)" },
|
|
13251
|
+
stage: { type: "string", enum: ["all", "pictures"], description: "pictures: only the first frames" },
|
|
13252
|
+
go_ahead: { type: "boolean", description: "Make it although the script check found errors" }
|
|
13190
13253
|
},
|
|
13191
13254
|
required: ["id", "estimate_usd"]
|
|
13192
13255
|
},
|
|
@@ -13194,7 +13257,32 @@ var socialTools = [
|
|
|
13194
13257
|
need: write5,
|
|
13195
13258
|
risk: "W3",
|
|
13196
13259
|
gates: ["VER"],
|
|
13197
|
-
run: async (ctx, { id, estimate_usd }) => adVideo(
|
|
13260
|
+
run: async (ctx, { id, estimate_usd, stage, go_ahead }) => adVideo(
|
|
13261
|
+
await ws(ctx).makeAdVideo(Number(id), Number(estimate_usd), {
|
|
13262
|
+
...stage ? { stage } : {},
|
|
13263
|
+
...go_ahead ? { goAhead: true } : {}
|
|
13264
|
+
})
|
|
13265
|
+
)
|
|
13266
|
+
}),
|
|
13267
|
+
tool({
|
|
13268
|
+
name: "ad_video_picture",
|
|
13269
|
+
description: `Use the user's own photo as an ad video scene's first frame (beta): a picture already on CMO.so (picture_url: a post's picture from social_list / social_post_get). It is cropped to the video's shape; no AI picture, no cost for it; the scene's moving clip is made again. For a photo on their device, they use the Ad video screen in the CMO.so app. Back to an AI picture: ad_video_change scenes [{id, picture: "ai"}]. Not while it's being made. Preview first.`,
|
|
13270
|
+
parameters: {
|
|
13271
|
+
type: "object",
|
|
13272
|
+
properties: {
|
|
13273
|
+
id: { type: "integer", description: "The ad video" },
|
|
13274
|
+
scene: { type: "string", description: "Scene id, e.g. s2" },
|
|
13275
|
+
picture_url: { type: "string", description: "A picture on CMO.so (a post's picture)" }
|
|
13276
|
+
},
|
|
13277
|
+
required: ["id", "scene", "picture_url"]
|
|
13278
|
+
},
|
|
13279
|
+
group: "social",
|
|
13280
|
+
need: write5,
|
|
13281
|
+
risk: "W1",
|
|
13282
|
+
gates: ["VER"],
|
|
13283
|
+
run: async (ctx, a) => adVideo(
|
|
13284
|
+
await ws(ctx).adVideoScenePicture(Number(a.id), String(a.scene), { photo_url: String(a.picture_url).trim() })
|
|
13285
|
+
)
|
|
13198
13286
|
}),
|
|
13199
13287
|
tool({
|
|
13200
13288
|
name: "ad_video_stop",
|
|
@@ -16882,7 +16970,7 @@ var program = new Command();
|
|
|
16882
16970
|
// package.json
|
|
16883
16971
|
var package_default = {
|
|
16884
16972
|
name: "@cmo-so/cli",
|
|
16885
|
-
version: "0.
|
|
16973
|
+
version: "0.11.0",
|
|
16886
16974
|
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.",
|
|
16887
16975
|
type: "module",
|
|
16888
16976
|
license: "MIT",
|
|
@@ -28101,20 +28189,42 @@ ad.command("plan [brief...]").description("a storyboard and what making it costs
|
|
|
28101
28189
|
);
|
|
28102
28190
|
ad.command("list").description("this website's ad videos").action(act(async (opts) => tool2(opts, "ad_video_list")));
|
|
28103
28191
|
ad.command("show <id>").description("one ad video: storyboard, cost, status").action(act(async (opts, id) => tool2(opts, "ad_video_get", { id: ids2([id])[0] })));
|
|
28104
|
-
ad.command("change <id>").description("change the storyboard (only what a change touches is made again)").option(
|
|
28192
|
+
ad.command("change <id>").description("change the storyboard (only what a change touches is made again)").option(
|
|
28193
|
+
"--scene <id=field:text...>",
|
|
28194
|
+
"e.g. s2=line:Book today (fields: still, motion, line, caption, seconds, caption_in, caption_out, voice_at; picture:ai)"
|
|
28195
|
+
).option(
|
|
28196
|
+
"--add <field:text...>",
|
|
28197
|
+
"a new scene, e.g. --add still:A wide shot of the shop --add line:Come in (after:s1)"
|
|
28198
|
+
).option("--order <sceneIds>", "the scenes to keep, in order (comma-separated): removes and reorders").option("--redo <sceneIds>", "make these scenes' pictures and clips again (comma-separated)").option("--style <text>", "the look of every scene (every AI picture again)").option("--shape <aspect>", "9:16 or 16:9 (every picture again)").option("--length <seconds>", "15, 20 or 30 (scenes rescaled)").option("--card-seconds <n>", "the end card's seconds (1.5-4)").option("--headline <text>", "the end card's headline").option("--sub <text>", "the end card's line").option("--voice <name>", "a voice name, or none").option("--music <text>", "the music in words, or none").action(
|
|
28105
28199
|
act(
|
|
28106
28200
|
async (opts, id) => {
|
|
28107
28201
|
const scenes = {};
|
|
28108
28202
|
for (const spec of opts.scene ?? []) {
|
|
28109
|
-
const m = /^([a-z0-9]+)=(still|motion|line|caption|seconds):(.*)$/s.exec(
|
|
28203
|
+
const m = /^([a-z0-9]+)=(still|motion|line|caption|seconds|caption_in|caption_out|voice_at|picture):(.*)$/s.exec(
|
|
28204
|
+
spec
|
|
28205
|
+
);
|
|
28110
28206
|
if (!m) throw new Error(`--scene ${spec}: use <sceneId>=<field>:<text>, e.g. s2=line:Book today`);
|
|
28111
28207
|
const [, sid = "", field = "", text3 = ""] = m;
|
|
28112
|
-
|
|
28208
|
+
const numeric = ["seconds", "caption_in", "caption_out", "voice_at"].includes(field);
|
|
28209
|
+
scenes[sid] = { ...scenes[sid] ?? {}, [field]: numeric ? Number(text3) : text3 };
|
|
28210
|
+
}
|
|
28211
|
+
const added = {};
|
|
28212
|
+
for (const spec of opts.add ?? []) {
|
|
28213
|
+
const m = /^(still|motion|line|caption|seconds|after):(.*)$/s.exec(spec);
|
|
28214
|
+
if (!m) throw new Error(`--add ${spec}: use <field>:<text>, e.g. still:A wide shot of the shop`);
|
|
28215
|
+
const [, field = "", text3 = ""] = m;
|
|
28216
|
+
added[field] = field === "seconds" ? Number(text3) : text3;
|
|
28113
28217
|
}
|
|
28114
28218
|
return tool2(opts, "ad_video_change", {
|
|
28115
28219
|
id: ids2([id])[0],
|
|
28116
28220
|
...Object.keys(scenes).length ? { scenes: Object.entries(scenes).map(([sid, f]) => ({ id: sid, ...f })) } : {},
|
|
28221
|
+
...Object.keys(added).length ? { add_scenes: [added] } : {},
|
|
28222
|
+
...opts.order ? { order: opts.order.split(",").map((x) => x.trim()) } : {},
|
|
28117
28223
|
...opts.redo ? { redo_scenes: opts.redo.split(",").map((x) => x.trim()) } : {},
|
|
28224
|
+
...opts.style ? { style: opts.style } : {},
|
|
28225
|
+
...opts.shape ? { aspect: opts.shape } : {},
|
|
28226
|
+
...opts.length ? { seconds: Number(opts.length) } : {},
|
|
28227
|
+
...opts.cardSeconds ? { card_seconds: Number(opts.cardSeconds) } : {},
|
|
28118
28228
|
...opts.headline ? { cta_headline: opts.headline } : {},
|
|
28119
28229
|
...opts.sub ? { cta_sub: opts.sub } : {},
|
|
28120
28230
|
...opts.voice ? { voice: opts.voice } : {},
|
|
@@ -28123,9 +28233,19 @@ ad.command("change <id>").description("change the storyboard (only what a change
|
|
|
28123
28233
|
}
|
|
28124
28234
|
)
|
|
28125
28235
|
);
|
|
28126
|
-
ad.command("make <id> <usd>").description("make it: costs money; <usd> is the estimate you agreed to (from plan / show)").action(
|
|
28236
|
+
ad.command("make <id> <usd>").description("make it: costs money; <usd> is the estimate you agreed to (from plan / show)").option("--pictures", "only the pictures first (<usd> = estimate.pictures_usd), to look at before the clips").option("--go-ahead", "make it although the script check found lines that don't fit").action(
|
|
28237
|
+
act(
|
|
28238
|
+
async (opts, id, usd) => tool2(opts, "ad_video_make", {
|
|
28239
|
+
id: ids2([id])[0],
|
|
28240
|
+
estimate_usd: Number(usd),
|
|
28241
|
+
...opts.pictures ? { stage: "pictures" } : {},
|
|
28242
|
+
...opts.goAhead ? { go_ahead: true } : {}
|
|
28243
|
+
})
|
|
28244
|
+
)
|
|
28245
|
+
);
|
|
28246
|
+
ad.command("picture <id> <scene> <url>").description("your own photo (a picture on CMO.so, e.g. a post's) as a scene's first frame").action(
|
|
28127
28247
|
act(
|
|
28128
|
-
async (opts, id,
|
|
28248
|
+
async (opts, id, scene, url) => tool2(opts, "ad_video_picture", { id: ids2([id])[0], scene, picture_url: url })
|
|
28129
28249
|
)
|
|
28130
28250
|
);
|
|
28131
28251
|
ad.command("stop <id>").description("stop one being made (what's made is kept), or remove one").action(act(async (opts, id) => tool2(opts, "ad_video_stop", { id: ids2([id])[0] })));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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",
|