@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.
Files changed (2) hide show
  1. package/dist/cmo.mjs +140 -20
  2. 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
- makeAdVideo: (adId, estimateUsd) => post(`${base}/social/ad-videos/${adId}/make`, { confirm: true, estimate_usd: estimateUsd }),
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, make it with 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" ? "Ready: post it with social_video_add (video_id and channels) once video.status is ready." : v.status === "failed" ? "Failed: say why; ad_video_make again carries on from what was made." : ""
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: "Change an ad video's storyboard (beta) as the user asks: a scene's picture brief (still), its movement (motion), the narrator's line, the words on screen (caption) or its seconds (3-8); redo_scenes to make scenes' pictures and clips again; the end card (cta_headline, cta_sub); the voice (a name from ad_video_list voices, or none); the music (words, or none). Only what a change touches is made again (redo): a picture brief or movement means a new paid clip; lines, words, end card, voice or music are cheap. Says the new cost; then ad_video_make. Not while it's being made. Preview first.",
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 ? { scenes: a.scenes } : {},
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: "Make the ad video (beta): 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 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. 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.",
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(await ws(ctx).makeAdVideo(Number(id), Number(estimate_usd)))
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.10.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("--scene <id=field:text...>", "e.g. s2=line:Book today (fields: still, motion, line, caption, seconds)").option("--redo <sceneIds>", "make these scenes' pictures and clips again (comma-separated)").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(
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(spec);
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
- scenes[sid] = { ...scenes[sid] ?? {}, [field]: field === "seconds" ? Number(text3) : text3 };
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, usd) => tool2(opts, "ad_video_make", { id: ids2([id])[0], estimate_usd: Number(usd) })
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.10.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",