@cmo-so/cli 0.9.2 → 0.10.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 +687 -267
  2. package/package.json +1 -1
package/dist/cmo.mjs CHANGED
@@ -449,9 +449,9 @@ var require_codegen = __commonJS({
449
449
  }
450
450
  };
451
451
  var Label = class extends Node {
452
- constructor(label) {
452
+ constructor(label2) {
453
453
  super();
454
- this.label = label;
454
+ this.label = label2;
455
455
  this.names = {};
456
456
  }
457
457
  render({ _n }) {
@@ -459,14 +459,14 @@ var require_codegen = __commonJS({
459
459
  }
460
460
  };
461
461
  var Break = class extends Node {
462
- constructor(label) {
462
+ constructor(label2) {
463
463
  super();
464
- this.label = label;
464
+ this.label = label2;
465
465
  this.names = {};
466
466
  }
467
467
  render({ _n }) {
468
- const label = this.label ? ` ${this.label}` : "";
469
- return `break${label};` + _n;
468
+ const label2 = this.label ? ` ${this.label}` : "";
469
+ return `break${label2};` + _n;
470
470
  }
471
471
  };
472
472
  var Throw = class extends Node {
@@ -506,17 +506,17 @@ var require_codegen = __commonJS({
506
506
  this.nodes = nodes;
507
507
  }
508
508
  render(opts) {
509
- return this.nodes.reduce((code, n) => code + n.render(opts), "");
509
+ return this.nodes.reduce((code, n2) => code + n2.render(opts), "");
510
510
  }
511
511
  optimizeNodes() {
512
512
  const { nodes } = this;
513
513
  let i = nodes.length;
514
514
  while (i--) {
515
- const n = nodes[i].optimizeNodes();
516
- if (Array.isArray(n))
517
- nodes.splice(i, 1, ...n);
518
- else if (n)
519
- nodes[i] = n;
515
+ const n2 = nodes[i].optimizeNodes();
516
+ if (Array.isArray(n2))
517
+ nodes.splice(i, 1, ...n2);
518
+ else if (n2)
519
+ nodes[i] = n2;
520
520
  else
521
521
  nodes.splice(i, 1);
522
522
  }
@@ -526,16 +526,16 @@ var require_codegen = __commonJS({
526
526
  const { nodes } = this;
527
527
  let i = nodes.length;
528
528
  while (i--) {
529
- const n = nodes[i];
530
- if (n.optimizeNames(names, constants))
529
+ const n2 = nodes[i];
530
+ if (n2.optimizeNames(names, constants))
531
531
  continue;
532
- subtractNames(names, n.names);
532
+ subtractNames(names, n2.names);
533
533
  nodes.splice(i, 1);
534
534
  }
535
535
  return nodes.length > 0 ? this : void 0;
536
536
  }
537
537
  get names() {
538
- return this.nodes.reduce((names, n) => addNames(names, n.names), {});
538
+ return this.nodes.reduce((names, n2) => addNames(names, n2.names), {});
539
539
  }
540
540
  };
541
541
  var BlockNode = class extends ParentNode {
@@ -878,12 +878,12 @@ var require_codegen = __commonJS({
878
878
  return this._endBlockNode(For);
879
879
  }
880
880
  // `label` statement
881
- label(label) {
882
- return this._leafNode(new Label(label));
881
+ label(label2) {
882
+ return this._leafNode(new Label(label2));
883
883
  }
884
884
  // `break` statement
885
- break(label) {
886
- return this._leafNode(new Break(label));
885
+ break(label2) {
886
+ return this._leafNode(new Break(label2));
887
887
  }
888
888
  // `return` statement
889
889
  return(value) {
@@ -946,8 +946,8 @@ var require_codegen = __commonJS({
946
946
  endFunc() {
947
947
  return this._endBlockNode(Func);
948
948
  }
949
- optimize(n = 1) {
950
- while (n-- > 0) {
949
+ optimize(n2 = 1) {
950
+ while (n2-- > 0) {
951
951
  this._root.optimizeNodes();
952
952
  this._root.optimizeNames(this._root.names, this._constants);
953
953
  }
@@ -961,19 +961,19 @@ var require_codegen = __commonJS({
961
961
  this._nodes.push(node2);
962
962
  }
963
963
  _endBlockNode(N1, N2) {
964
- const n = this._currNode;
965
- if (n instanceof N1 || N2 && n instanceof N2) {
964
+ const n2 = this._currNode;
965
+ if (n2 instanceof N1 || N2 && n2 instanceof N2) {
966
966
  this._nodes.pop();
967
967
  return this;
968
968
  }
969
969
  throw new Error(`CodeGen: not in block "${N2 ? `${N1.kind}/${N2.kind}` : N1.kind}"`);
970
970
  }
971
971
  _elseNode(node2) {
972
- const n = this._currNode;
973
- if (!(n instanceof If)) {
972
+ const n2 = this._currNode;
973
+ if (!(n2 instanceof If)) {
974
974
  throw new Error('CodeGen: "else" without "if"');
975
975
  }
976
- this._currNode = n.else = node2;
976
+ this._currNode = n2.else = node2;
977
977
  return this;
978
978
  }
979
979
  get _root() {
@@ -990,8 +990,8 @@ var require_codegen = __commonJS({
990
990
  };
991
991
  exports.CodeGen = CodeGen;
992
992
  function addNames(names, from) {
993
- for (const n in from)
994
- names[n] = (names[n] || 0) + (from[n] || 0);
993
+ for (const n2 in from)
994
+ names[n2] = (names[n2] || 0) + (from[n2] || 0);
995
995
  return names;
996
996
  }
997
997
  function addExprNames(names, from) {
@@ -1002,20 +1002,20 @@ var require_codegen = __commonJS({
1002
1002
  return replaceName(expr);
1003
1003
  if (!canOptimize(expr))
1004
1004
  return expr;
1005
- return new code_1._Code(expr._items.reduce((items, c) => {
1005
+ return new code_1._Code(expr._items.reduce((items2, c) => {
1006
1006
  if (c instanceof code_1.Name)
1007
1007
  c = replaceName(c);
1008
1008
  if (c instanceof code_1._Code)
1009
- items.push(...c._items);
1009
+ items2.push(...c._items);
1010
1010
  else
1011
- items.push(c);
1012
- return items;
1011
+ items2.push(c);
1012
+ return items2;
1013
1013
  }, []));
1014
- function replaceName(n) {
1015
- const c = constants[n.str];
1016
- if (c === void 0 || names[n.str] !== 1)
1017
- return n;
1018
- delete names[n.str];
1014
+ function replaceName(n2) {
1015
+ const c = constants[n2.str];
1016
+ if (c === void 0 || names[n2.str] !== 1)
1017
+ return n2;
1018
+ delete names[n2.str];
1019
1019
  return c;
1020
1020
  }
1021
1021
  function canOptimize(e) {
@@ -1023,8 +1023,8 @@ var require_codegen = __commonJS({
1023
1023
  }
1024
1024
  }
1025
1025
  function subtractNames(names, from) {
1026
- for (const n in from)
1027
- names[n] = (names[n] || 0) - (from[n] || 0);
1026
+ for (const n2 in from)
1027
+ names[n2] = (names[n2] || 0) - (from[n2] || 0);
1028
1028
  }
1029
1029
  function not(x) {
1030
1030
  return typeof x == "boolean" || typeof x == "number" || x === null ? !x : (0, code_1._)`!${par(x)}`;
@@ -1167,7 +1167,7 @@ var require_util = __commonJS({
1167
1167
  mergeNames: (gen, from, to) => gen.if((0, codegen_1._)`${to} !== true && ${from} !== undefined`, () => gen.assign(to, (0, codegen_1._)`${from} === true ? true : ${to} > ${from} ? ${to} : ${from}`)),
1168
1168
  mergeToName: (gen, from, to) => gen.if((0, codegen_1._)`${to} !== true`, () => gen.assign(to, from === true ? true : (0, codegen_1._)`${to} > ${from} ? ${to} : ${from}`)),
1169
1169
  mergeValues: (from, to) => from === true ? true : Math.max(from, to),
1170
- resultToName: (gen, items) => gen.var("items", items)
1170
+ resultToName: (gen, items2) => gen.var("items", items2)
1171
1171
  })
1172
1172
  };
1173
1173
  function evaluatedPropsToName(gen, ps) {
@@ -1361,12 +1361,12 @@ var require_errors = __commonJS({
1361
1361
  }
1362
1362
  return [E.schemaPath, schPath];
1363
1363
  }
1364
- function extraErrorProps(cxt, { params, message }, keyValues) {
1364
+ function extraErrorProps(cxt, { params, message: message2 }, keyValues) {
1365
1365
  const { keyword, data, schemaValue, it } = cxt;
1366
1366
  const { opts, propertyName, topSchemaRef, schemaPath } = it;
1367
1367
  keyValues.push([E.keyword, keyword], [E.params, typeof params == "function" ? params(cxt) : params || (0, codegen_1._)`{}`]);
1368
1368
  if (opts.messages) {
1369
- keyValues.push([E.message, typeof message == "function" ? message(cxt) : message]);
1369
+ keyValues.push([E.message, typeof message2 == "function" ? message2(cxt) : message2]);
1370
1370
  }
1371
1371
  if (opts.verbose) {
1372
1372
  keyValues.push([E.schema, schemaValue], [E.parentSchema, (0, codegen_1._)`${topSchemaRef}${schemaPath}`], [names_1.default.data, data]);
@@ -1675,13 +1675,13 @@ var require_defaults = __commonJS({
1675
1675
  var codegen_1 = require_codegen();
1676
1676
  var util_1 = require_util();
1677
1677
  function assignDefaults(it, ty) {
1678
- const { properties, items } = it.schema;
1678
+ const { properties, items: items2 } = it.schema;
1679
1679
  if (ty === "object" && properties) {
1680
1680
  for (const key in properties) {
1681
1681
  assignDefault(it, key, properties[key].default);
1682
1682
  }
1683
- } else if (ty === "array" && Array.isArray(items)) {
1684
- items.forEach((sch, i) => assignDefault(it, i, sch.default));
1683
+ } else if (ty === "array" && Array.isArray(items2)) {
1684
+ items2.forEach((sch, i) => assignDefault(it, i, sch.default));
1685
1685
  }
1686
1686
  }
1687
1687
  exports.assignDefaults = assignDefaults;
@@ -2485,11 +2485,11 @@ var require_validate = __commonJS({
2485
2485
  gen.return((0, codegen_1._)`${names_1.default.errors} === 0`);
2486
2486
  }
2487
2487
  }
2488
- function assignEvaluated({ gen, evaluated, props, items }) {
2488
+ function assignEvaluated({ gen, evaluated, props, items: items2 }) {
2489
2489
  if (props instanceof codegen_1.Name)
2490
2490
  gen.assign((0, codegen_1._)`${evaluated}.props`, props);
2491
- if (items instanceof codegen_1.Name)
2492
- gen.assign((0, codegen_1._)`${evaluated}.items`, items);
2491
+ if (items2 instanceof codegen_1.Name)
2492
+ gen.assign((0, codegen_1._)`${evaluated}.items`, items2);
2493
2493
  }
2494
2494
  function schemaKeywords(it, types, typeErrors, errsCount) {
2495
2495
  const { gen, schema, data, allErrors, opts, self } = it;
@@ -2952,12 +2952,12 @@ var require_compile = __commonJS({
2952
2952
  validate2.source = { validateName, validateCode, scopeValues: gen._values };
2953
2953
  }
2954
2954
  if (this.opts.unevaluated) {
2955
- const { props, items } = schemaCxt;
2955
+ const { props, items: items2 } = schemaCxt;
2956
2956
  validate2.evaluated = {
2957
2957
  props: props instanceof codegen_1.Name ? void 0 : props,
2958
- items: items instanceof codegen_1.Name ? void 0 : items,
2958
+ items: items2 instanceof codegen_1.Name ? void 0 : items2,
2959
2959
  dynamicProps: props instanceof codegen_1.Name,
2960
- dynamicItems: items instanceof codegen_1.Name
2960
+ dynamicItems: items2 instanceof codegen_1.Name
2961
2961
  };
2962
2962
  if (validate2.source)
2963
2963
  validate2.source.evaluated = (0, codegen_1.stringify)(validate2.evaluated);
@@ -4496,11 +4496,11 @@ var require_core = __commonJS({
4496
4496
  }
4497
4497
  const valid = this.validate($schema, schema);
4498
4498
  if (!valid && throwOrLogError) {
4499
- const message = "schema is invalid: " + this.errorsText();
4499
+ const message2 = "schema is invalid: " + this.errorsText();
4500
4500
  if (this.opts.validateSchema === "log")
4501
- this.logger.error(message);
4501
+ this.logger.error(message2);
4502
4502
  else
4503
- throw new Error(message);
4503
+ throw new Error(message2);
4504
4504
  }
4505
4505
  return valid;
4506
4506
  }
@@ -4967,8 +4967,8 @@ var require_ref = __commonJS({
4967
4967
  it.items = util_1.mergeEvaluated.items(gen, schEvaluated.items, it.items);
4968
4968
  }
4969
4969
  } else {
4970
- const items = gen.var("items", (0, codegen_1._)`${source}.evaluated.items`);
4971
- it.items = util_1.mergeEvaluated.items(gen, items, it.items, codegen_1.Name);
4970
+ const items2 = gen.var("items", (0, codegen_1._)`${source}.evaluated.items`);
4971
+ it.items = util_1.mergeEvaluated.items(gen, items2, it.items, codegen_1.Name);
4972
4972
  }
4973
4973
  }
4974
4974
  }
@@ -5508,28 +5508,28 @@ var require_additionalItems = __commonJS({
5508
5508
  error: error2,
5509
5509
  code(cxt) {
5510
5510
  const { parentSchema, it } = cxt;
5511
- const { items } = parentSchema;
5512
- if (!Array.isArray(items)) {
5511
+ const { items: items2 } = parentSchema;
5512
+ if (!Array.isArray(items2)) {
5513
5513
  (0, util_1.checkStrictMode)(it, '"additionalItems" is ignored when "items" is not an array of schemas');
5514
5514
  return;
5515
5515
  }
5516
- validateAdditionalItems(cxt, items);
5516
+ validateAdditionalItems(cxt, items2);
5517
5517
  }
5518
5518
  };
5519
- function validateAdditionalItems(cxt, items) {
5519
+ function validateAdditionalItems(cxt, items2) {
5520
5520
  const { gen, schema, data, keyword, it } = cxt;
5521
5521
  it.items = true;
5522
5522
  const len = gen.const("len", (0, codegen_1._)`${data}.length`);
5523
5523
  if (schema === false) {
5524
- cxt.setParams({ len: items.length });
5525
- cxt.pass((0, codegen_1._)`${len} <= ${items.length}`);
5524
+ cxt.setParams({ len: items2.length });
5525
+ cxt.pass((0, codegen_1._)`${len} <= ${items2.length}`);
5526
5526
  } else if (typeof schema == "object" && !(0, util_1.alwaysValidSchema)(it, schema)) {
5527
- const valid = gen.var("valid", (0, codegen_1._)`${len} <= ${items.length}`);
5527
+ const valid = gen.var("valid", (0, codegen_1._)`${len} <= ${items2.length}`);
5528
5528
  gen.if((0, codegen_1.not)(valid), () => validateItems(valid));
5529
5529
  cxt.ok(valid);
5530
5530
  }
5531
5531
  function validateItems(valid) {
5532
- gen.forRange("i", items.length, len, (i) => {
5532
+ gen.forRange("i", items2.length, len, (i) => {
5533
5533
  cxt.subschema({ keyword, dataProp: i, dataPropType: util_1.Type.Num }, valid);
5534
5534
  if (!it.allErrors)
5535
5535
  gen.if((0, codegen_1.not)(valid), () => gen.break());
@@ -7211,14 +7211,14 @@ var OAuthError = class extends Error {
7211
7211
  }
7212
7212
  };
7213
7213
  var SignInRequiredError = class extends Error {
7214
- constructor(message = "Please sign in again.") {
7215
- super(message);
7214
+ constructor(message2 = "Please sign in again.") {
7215
+ super(message2);
7216
7216
  this.name = "SignInRequiredError";
7217
7217
  }
7218
7218
  };
7219
7219
  var ApiError = class extends Error {
7220
- constructor(status, message, requestId, detail = null) {
7221
- super(message);
7220
+ constructor(status, message2, requestId, detail = null) {
7221
+ super(message2);
7222
7222
  this.status = status;
7223
7223
  this.requestId = requestId;
7224
7224
  this.detail = detail;
@@ -7448,10 +7448,10 @@ function createApi({ baseUrl, session: session2, fetch: fetchImpl = globalThis.f
7448
7448
  }),
7449
7449
  models: () => request("/v1/maggie/bridge/models"),
7450
7450
  /** Add a ChatGPT account to the bridge (then connect it). */
7451
- addAccount: (label) => request("/v1/maggie/bridge/accounts", {
7451
+ addAccount: (label2) => request("/v1/maggie/bridge/accounts", {
7452
7452
  method: "POST",
7453
7453
  headers: { "Content-Type": "application/json" },
7454
- body: JSON.stringify({ label: label ?? null })
7454
+ body: JSON.stringify({ label: label2 ?? null })
7455
7455
  }),
7456
7456
  /** Make one of the bridge's ChatGPT accounts the one that answers. */
7457
7457
  chooseAccount: (id) => request(`/v1/maggie/bridge/accounts/${encodeURIComponent(id)}/use`, { method: "POST" }),
@@ -7470,8 +7470,8 @@ function createApi({ baseUrl, session: session2, fetch: fetchImpl = globalThis.f
7470
7470
  /** The user has read the team's latest reply or status change. */
7471
7471
  seen: (id) => request(`/v1/feedback/${encodeURIComponent(id)}/seen`, { method: "POST" }),
7472
7472
  /** One of the user's feedback photos as a data: URL (an <Image> source on web and native). */
7473
- photo: async (id, n) => {
7474
- const { type, bytes } = await requestBytes(`/v1/feedback/${encodeURIComponent(id)}/photos/${n}`);
7473
+ photo: async (id, n2) => {
7474
+ const { type, bytes } = await requestBytes(`/v1/feedback/${encodeURIComponent(id)}/photos/${n2}`);
7475
7475
  return dataUrl(type, bytes);
7476
7476
  }
7477
7477
  },
@@ -7521,8 +7521,8 @@ function errorMessage(data, status) {
7521
7521
  const detail = data?.detail;
7522
7522
  if (typeof detail === "string" && detail) return detail;
7523
7523
  if (detail && typeof detail === "object" && !Array.isArray(detail)) {
7524
- const message = detail.message;
7525
- if (typeof message === "string" && message) return message;
7524
+ const message2 = detail.message;
7525
+ if (typeof message2 === "string" && message2) return message2;
7526
7526
  }
7527
7527
  if (Array.isArray(detail)) {
7528
7528
  const first = detail[0];
@@ -7540,8 +7540,8 @@ function dataUrl(type, bytes) {
7540
7540
  const parts = [];
7541
7541
  let chunk = "";
7542
7542
  for (let i = 0; i < b.length; i += 3) {
7543
- const n = (b[i] ?? 0) << 16 | (b[i + 1] ?? 0) << 8 | (b[i + 2] ?? 0);
7544
- chunk += B64.charAt(n >> 18 & 63) + B64.charAt(n >> 12 & 63) + (i + 1 < b.length ? B64.charAt(n >> 6 & 63) : "=") + (i + 2 < b.length ? B64.charAt(n & 63) : "=");
7543
+ const n2 = (b[i] ?? 0) << 16 | (b[i + 1] ?? 0) << 8 | (b[i + 2] ?? 0);
7544
+ chunk += B64.charAt(n2 >> 18 & 63) + B64.charAt(n2 >> 12 & 63) + (i + 1 < b.length ? B64.charAt(n2 >> 6 & 63) : "=") + (i + 2 < b.length ? B64.charAt(n2 & 63) : "=");
7545
7545
  if (chunk.length >= 8192) {
7546
7546
  parts.push(chunk);
7547
7547
  chunk = "";
@@ -7921,8 +7921,9 @@ function domainApi(api2, domain) {
7921
7921
  * https links to pictures. Send a few photos per call (16 MB request limit), `append` after the first.
7922
7922
  * On LinkedIn a set goes out as a swipeable PDF carousel unless setSocialCarousel says images. */
7923
7923
  setSocialPictures: (id, pictures) => post(`${base}/social/${id}/pictures`, pictures),
7924
- /** A LinkedIn post goes out as this PDF (base64 under ~11 MB, or a public https link; up to 300 pages);
7925
- * its first 20 pages become the post's pictures. */
7924
+ /** A PDF carousel (base64 under ~11 MB, or a public https link; up to 300 pages): its first 20 pages become
7925
+ * the post's pictures; a LinkedIn post (or the idea's LinkedIn post) goes out as the PDF itself. LinkedIn,
7926
+ * Instagram, Facebook and TikTok posts. */
7926
7927
  attachSocialCarouselPdf: (id, pdf) => post(`${base}/social/${id}/carousel/pdf`, pdf),
7927
7928
  /** How a LinkedIn post with a set goes out: "pdf" (swipeable carousel, the default) or "images"; its title. */
7928
7929
  setSocialCarousel: (id, changes) => api2.request(`${base}/social/${id}/carousel`, {
@@ -8072,6 +8073,26 @@ function domainApi(api2, domain) {
8072
8073
  /** A post on one video channel (YouTube by default, or TikTok) from a ready video, waiting in Review; Maggie
8073
8074
  * writes the details (YouTube) or the caption (TikTok) unless write is false. */
8074
8075
  postFromVideo: (videoId, opts = {}) => post(`${base}/social/video/${videoId}/post`, opts),
8076
+ /** A TikTok photo post (a swipeable set; TikTok adds music) from this post's pictures, waiting in Review;
8077
+ * Maggie writes the caption unless `caption` is given or write is false. Beta, like TikTok. */
8078
+ tiktokPhotoPost: (id, opts = {}) => post(`${base}/social/${id}/tiktok-photo`, opts),
8079
+ /** Ad videos (Beta): this website's, with the voices and lengths to choose from. */
8080
+ adVideos: () => api2.request(
8081
+ `${base}/social/ad-videos`
8082
+ ),
8083
+ adVideo: (adId) => api2.request(`${base}/social/ad-videos/${adId}`),
8084
+ /** A storyboard and its cost (one AI generation; nothing billed by the second yet). */
8085
+ planAdVideo: (opts) => post(`${base}/social/ad-videos`, opts),
8086
+ /** Change the storyboard: only what a change touches is made again (`redo`), with the new cost. */
8087
+ changeAdVideo: (adId, changes) => api2.request(`${base}/social/ad-videos/${adId}`, {
8088
+ method: "PATCH",
8089
+ headers: { "Content-Type": "application/json" },
8090
+ body: JSON.stringify(changes)
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 }),
8094
+ /** Stop one being made (what's made is kept), or remove one. */
8095
+ stopAdVideo: (adId) => api2.request(`${base}/social/ad-videos/${adId}`, { method: "DELETE" }),
8075
8096
  /** Make a LinkedIn, Facebook or Instagram post a video post: it goes out as this video (its picture is dropped). */
8076
8097
  attachVideo: (id, videoId) => post(`${base}/social/${id}/video`, { video_id: videoId }),
8077
8098
  /** Take the video off a post: it's a text post again. */
@@ -8409,8 +8430,8 @@ function cleanSitemap(entry, domain) {
8409
8430
  if (!url) return "The sitemap must be a web address starting with http:// or https://.";
8410
8431
  let owner = SITEMAP_OWNERS.includes(entry.owner) ? entry.owner : "other";
8411
8432
  if (owner === "own" && domain && !onWebsite(url, domain)) owner = "other";
8412
- const label = (entry.label ?? "").trim().slice(0, SITEMAP_LABEL_MAX);
8413
- const out = { ...entry, url, owner, label: label || null, uses: sitemapUses(owner, entry.uses) };
8433
+ const label2 = (entry.label ?? "").trim().slice(0, SITEMAP_LABEL_MAX);
8434
+ const out = { ...entry, url, owner, label: label2 || null, uses: sitemapUses(owner, entry.uses) };
8414
8435
  if (!out.id) delete out.id;
8415
8436
  return out;
8416
8437
  }
@@ -8427,8 +8448,8 @@ function checkSitemaps(list2) {
8427
8448
  function sitemapCheckLine(check) {
8428
8449
  if (!check || check.status === "unchecked") return "Checked when you save";
8429
8450
  if (check.status === "error") return check.error?.trim() || "Couldn't read this sitemap.";
8430
- const n = check.url_count;
8431
- const found = n == null ? "Read" : `Found ${n.toLocaleString("en-GB")} ${n === 1 ? "page" : "pages"}`;
8451
+ const n2 = check.url_count;
8452
+ const found = n2 == null ? "Read" : `Found ${n2.toLocaleString("en-GB")} ${n2 === 1 ? "page" : "pages"}`;
8432
8453
  const at = check.checked_at ? new Date(check.checked_at) : null;
8433
8454
  const when = at && !Number.isNaN(at.getTime()) ? at.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }) : null;
8434
8455
  return when ? `${found} \xB7 checked ${when}` : found;
@@ -8456,13 +8477,13 @@ var named = (title, id) => {
8456
8477
  const s = String(id ?? "").trim();
8457
8478
  return s && s.length <= 8 ? ` #${s}` : "";
8458
8479
  };
8459
- var list = (xs, n = 5) => {
8480
+ var list = (xs, n2 = 5) => {
8460
8481
  const a = (Array.isArray(xs) ? xs : xs == null ? [] : [xs]).map(String);
8461
- return a.length > n ? `${a.slice(0, n).join(", ")} +${a.length - n}` : a.join(", ");
8482
+ return a.length > n2 ? `${a.slice(0, n2).join(", ")} +${a.length - n2}` : a.join(", ");
8462
8483
  };
8463
- var placeEn = (p, items = "posts") => {
8484
+ var placeEn = (p, items2 = "posts") => {
8464
8485
  const t = String(p ?? "").trim().toLowerCase();
8465
- if (t === "end") return `at the end of the queue, after the ${items} already waiting`;
8486
+ if (t === "end") return `at the end of the queue, after the ${items2} already waiting`;
8466
8487
  if (t === "random") return "at a random place in the queue";
8467
8488
  if (/^\d+$/.test(t)) return Number(t) === 0 ? null : `after the first ${t} in the queue`;
8468
8489
  return null;
@@ -8479,6 +8500,12 @@ var keys = (a) => Object.keys(a ?? {}).filter((k) => a[k] !== void 0);
8479
8500
  var videoChannels = (a) => (Array.isArray(a.channels) && a.channels.length ? a.channels : ["youtube"]).map(
8480
8501
  (c) => ({ tiktok: "TikTok", linkedin: "LinkedIn", facebook: "Facebook", instagram: "Instagram" })[String(c)] ?? "YouTube"
8481
8502
  );
8503
+ var videoFrom = (a, lang = "en") => {
8504
+ const name = a.file?.file_name;
8505
+ if (a.file) return name ? q(String(name)) : lang === "en" ? "your video" : "\u4F60\u7684\u5F71\u7247";
8506
+ if (a.video_id) return lang === "en" ? `video #${a.video_id}` : `\u5F71\u7247 #${a.video_id}`;
8507
+ return String(a.url ?? "");
8508
+ };
8482
8509
  var confirmCopy = {
8483
8510
  // --------------------------------------------------------- account / setup
8484
8511
  website_add: {
@@ -8553,13 +8580,13 @@ var confirmCopy = {
8553
8580
  sitemap_upsert: {
8554
8581
  en: (a) => {
8555
8582
  const whose = { own: "your site's ", competitor: "a competitor's ", other: "another site's " }[a.owner];
8556
- const label = a.label ? ` (${q(a.label)})` : "";
8557
- return a.url ? `Save ${whose ?? "the "}sitemap ${a.url}${label}.` : `Change the sitemap${label}: ${keys(a).filter((k) => k !== "id").join(", ")}.`;
8583
+ const label2 = a.label ? ` (${q(a.label)})` : "";
8584
+ return a.url ? `Save ${whose ?? "the "}sitemap ${a.url}${label2}.` : `Change the sitemap${label2}: ${keys(a).filter((k) => k !== "id").join(", ")}.`;
8558
8585
  },
8559
8586
  "zh-Hant": (a) => {
8560
8587
  const whose = { own: "\u81EA\u5DF1\u7DB2\u7AD9\u7684", competitor: "\u7AF6\u722D\u5C0D\u624B\u7684", other: "\u5176\u4ED6\u7DB2\u7AD9\u7684" }[a.owner];
8561
- const label = a.label ? `\uFF08${q(a.label)}\uFF09` : "";
8562
- return a.url ? `\u5132\u5B58${whose ?? ""} sitemap ${a.url}${label}\u3002` : `\u4FEE\u6539 sitemap${label}\uFF1A${keys(a).filter((k) => k !== "id").join("\u3001")}\u3002`;
8588
+ const label2 = a.label ? `\uFF08${q(a.label)}\uFF09` : "";
8589
+ return a.url ? `\u5132\u5B58${whose ?? ""} sitemap ${a.url}${label2}\u3002` : `\u4FEE\u6539 sitemap${label2}\uFF1A${keys(a).filter((k) => k !== "id").join("\u3001")}\u3002`;
8563
8590
  }
8564
8591
  },
8565
8592
  sitemap_remove: {
@@ -8717,18 +8744,18 @@ var confirmCopy = {
8717
8744
  },
8718
8745
  social_approve: {
8719
8746
  en: (a) => {
8720
- const n = ids(a);
8747
+ const n2 = ids(a);
8721
8748
  const at = placeEn(a.position);
8722
8749
  if (at)
8723
- 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"}.`;
8724
- 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.`;
8750
+ return `Approve ${n2.length > 1 ? `${n2.length} posts: ${n2.map((i) => `#${i}`).join(", ")}` : `post #${n2[0]}`}. ${n2.length > 1 ? "They are" : "It is"} queued ${at}, and go${n2.length > 1 ? "" : "es"} out where your network can see ${n2.length > 1 ? "them" : "it"}.`;
8751
+ return n2.length > 1 ? `Approve ${n2.length} posts: ${n2.map((i) => `#${i}`).join(", ")}. Each goes out at your next posting times, where your network can see it.` : `Approve post #${n2[0]}. It goes out at your next posting time, where your network can see it.`;
8725
8752
  },
8726
8753
  "zh-Hant": (a) => {
8727
- const n = ids(a);
8754
+ const n2 = ids(a);
8728
8755
  const at = placeZh(a.position);
8729
8756
  if (at)
8730
- 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`;
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`;
8757
+ return `\u6279\u51C6${n2.length > 1 ? ` ${n2.length} \u7BC7\u8CBC\u6587\uFF1A${n2.map((i) => `#${i}`).join("\u3001")}` : `\u8CBC\u6587 #${n2[0]}`}\u3002${at}\uFF0C\u767C\u51FA\u5F8C\u4F60\u7684\u4EBA\u8108\u90FD\u770B\u5F97\u5230\u3002`;
8758
+ return n2.length > 1 ? `\u6279\u51C6 ${n2.length} \u7BC7\u8CBC\u6587\uFF1A${n2.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 #${n2[0]}\u3002\u5B83\u6703\u5728\u4E0B\u4E00\u500B\u767C\u6587\u6642\u9593\u767C\u51FA\uFF0C\u4F60\u7684\u4EBA\u8108\u90FD\u770B\u5F97\u5230\u3002`;
8732
8759
  }
8733
8760
  },
8734
8761
  social_publish_now: {
@@ -8753,8 +8780,8 @@ var confirmCopy = {
8753
8780
  "zh-Hant": () => "\u7522\u751F\u9023\u63A5\u4F60 YouTube \u983B\u9053\u7684\u9023\u7D50\u3002"
8754
8781
  },
8755
8782
  social_video_attach: {
8756
- en: (a) => `Make post #${a.id} a video post with ${a.url} (its picture is removed).`,
8757
- "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`
8783
+ en: (a) => `Make post #${a.id} a video post with ${videoFrom(a)} (its picture is removed).`,
8784
+ "zh-Hant": (a) => `\u628A\u8CBC\u6587 #${a.id} \u6539\u6210\u5F71\u7247\u8CBC\u6587\uFF0C\u7528 ${videoFrom(a, "zh-Hant")}\uFF08\u5716\u7247\u6703\u79FB\u9664\uFF09\u3002`
8758
8785
  },
8759
8786
  social_video_detach: {
8760
8787
  en: (a) => `Take the video off post #${a.id} (it's a text post again).`,
@@ -8767,11 +8794,11 @@ var confirmCopy = {
8767
8794
  social_video_add: {
8768
8795
  en: (a) => {
8769
8796
  const names = videoChannels(a).join(" and ");
8770
- return `Make a ${names} post from ${a.url}${a.write === false ? "" : ` (Maggie writes it: one AI generation per channel)`}; it waits for your OK.`;
8797
+ return `Make a ${names} post from ${videoFrom(a)}${a.write === false ? "" : ` (Maggie writes it: one AI generation per channel)`}; it waits for your OK.`;
8771
8798
  },
8772
8799
  "zh-Hant": (a) => {
8773
8800
  const names = videoChannels(a).join(" \u548C ");
8774
- return `\u7528 ${a.url} \u505A ${names} \u5F71\u7247\u8CBC\u6587${a.write === false ? "" : "\uFF08Maggie \u4F86\u5BEB\uFF1A\u6BCF\u500B\u6E20\u9053\u7528\u4E00\u6B21 AI \u751F\u6210\uFF09"}\uFF0C\u7B49\u4F60\u78BA\u8A8D\u5F8C\u624D\u767C\u3002`;
8801
+ return `\u7528 ${videoFrom(a, "zh-Hant")} \u505A ${names} \u5F71\u7247\u8CBC\u6587${a.write === false ? "" : "\uFF08Maggie \u4F86\u5BEB\uFF1A\u6BCF\u500B\u6E20\u9053\u7528\u4E00\u6B21 AI \u751F\u6210\uFF09"}\uFF0C\u7B49\u4F60\u78BA\u8A8D\u5F8C\u624D\u767C\u3002`;
8775
8802
  }
8776
8803
  },
8777
8804
  social_tiktok_set: {
@@ -8787,8 +8814,8 @@ var confirmCopy = {
8787
8814
  "zh-Hant": (a) => `\u4FEE\u6539 YouTube \u9810\u8A2D\uFF1A${keys(a).join("\u3001") || "\u7121"}\u3002`
8788
8815
  },
8789
8816
  social_image_new: {
8790
- 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" : ""}.`,
8791
- "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`
8817
+ en: (a) => Number(a.count) >= 5 ? `Make a text carousel of ${a.count} slides for post #${a.id} (words on your brand's colours; free, no AI picture, in the background)${a.replace ? ", stopping the one being made for another post" : ""}.` : `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" : ""}.`,
8818
+ "zh-Hant": (a) => Number(a.count) >= 5 ? `\u70BA\u8CBC\u6587 #${a.id} \u505A ${a.count} \u9801\u6587\u5B57\u8F2A\u64AD\uFF08\u54C1\u724C\u8272\u4E0A\u7684\u6587\u5B57\uFF1B\u514D\u8CBB\uFF0C\u4E0D\u7528 AI \u751F\u5716\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` : `\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`
8792
8819
  },
8793
8820
  social_image_stop: {
8794
8821
  en: () => "Stop the picture being made; the post keeps the one it had.",
@@ -8796,17 +8823,37 @@ var confirmCopy = {
8796
8823
  },
8797
8824
  social_pictures_attach: {
8798
8825
  en: (a) => {
8799
- const n = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
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.`;
8826
+ const n2 = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
8827
+ return `${a.append ? "Add" : "Put"} your ${n2} picture${n2 === 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.`;
8801
8828
  },
8802
8829
  "zh-Hant": (a) => {
8803
- const n = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
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`;
8830
+ const n2 = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
8831
+ 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 ${n2} \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`;
8805
8832
  }
8806
8833
  },
8807
8834
  social_carousel_pdf_attach: {
8808
- 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).`,
8809
- "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`
8835
+ en: (a) => `Your PDF becomes post #${a.id}'s carousel${a.title ? `, titled ${q(a.title)}` : ""}: its first pages become the post's pictures, and on LinkedIn it goes out as the PDF itself (free).`,
8836
+ "zh-Hant": (a) => `\u4F60\u7684 PDF \u6210\u70BA\u8CBC\u6587 #${a.id} \u7684\u8F2A\u64AD${a.title ? `\uFF0C\u6A19\u984C ${q(a.title)}` : ""}\uFF1A\u524D\u5E7E\u9801\u6703\u6210\u70BA\u8CBC\u6587\u7684\u5716\u7247\uFF0C\u5728 LinkedIn \u4E0A\u76F4\u63A5\u4EE5 PDF \u767C\u51FA\uFF08\u514D\u8CBB\uFF09\u3002`
8837
+ },
8838
+ social_tiktok_photo: {
8839
+ en: (a) => `Make a TikTok photo post from post #${a.id}'s pictures${a.caption ? " with your caption" : " (Maggie writes the caption: 1 AI generation)"}; it waits for your OK.`,
8840
+ "zh-Hant": (a) => `\u7528\u8CBC\u6587 #${a.id} \u7684\u5716\u7247\u505A\u4E00\u5247 TikTok \u5716\u7247\u8CBC\u6587${a.caption ? "\uFF0C\u7528\u4F60\u7684\u6587\u6848" : "\uFF08Maggie \u5BEB\u6587\u6848\uFF1A\u7528 1 \u6B21 AI \u751F\u6210\uFF09"}\uFF0C\u7B49\u4F60\u78BA\u8A8D\u5F8C\u624D\u767C\u3002`
8841
+ },
8842
+ ad_video_plan: {
8843
+ en: (a) => `Plan a ${a.seconds ?? 20}-second ${a.aspect === "16:9" ? "landscape" : "vertical"} ad video${a.item_id ? ` from post #${a.item_id}` : ""}${a.reference_url ? ", following the YouTube ad you gave" : ""} (1 AI generation; nothing is billed by the second yet). You'll see the storyboard and the cost first.`,
8844
+ "zh-Hant": (a) => `\u898F\u5283\u4E00\u652F ${a.seconds ?? 20} \u79D2${a.aspect === "16:9" ? "\u6A6B\u5411" : "\u76F4\u5411"}\u5EE3\u544A\u5F71\u7247${a.item_id ? `\uFF08\u6839\u64DA\u8CBC\u6587 #${a.item_id}\uFF09` : ""}${a.reference_url ? "\uFF0C\u53C3\u8003\u4F60\u7D66\u7684 YouTube \u5EE3\u544A\u7BC0\u594F" : ""}\uFF08\u7528 1 \u6B21 AI \u751F\u6210\uFF0C\u9084\u4E0D\u6703\u6309\u79D2\u8A08\u8CBB\uFF09\u3002\u6703\u5148\u7D66\u4F60\u770B\u5206\u93E1\u548C\u8CBB\u7528\u3002`
8845
+ },
8846
+ ad_video_change: {
8847
+ en: (a) => `Change ad video #${a.id}'s storyboard: ${keys(a).filter((k) => k !== "id").join(", ") || "nothing"}.`,
8848
+ "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
+ },
8850
+ 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`
8853
+ },
8854
+ ad_video_stop: {
8855
+ en: (a) => `Stop ad video #${a.id} if it's being made (what's made is kept), or remove it from the list.`,
8856
+ "zh-Hant": (a) => `\u505C\u6B62\u88FD\u4F5C\u5EE3\u544A\u5F71\u7247 #${a.id}\uFF08\u5DF2\u505A\u597D\u7684\u90E8\u5206\u6703\u4FDD\u7559\uFF09\uFF0C\u6216\u628A\u5B83\u5F9E\u6E05\u55AE\u79FB\u9664\u3002`
8810
8857
  },
8811
8858
  social_carousel_set: {
8812
8859
  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)}` : ""}.`,
@@ -9030,17 +9077,17 @@ function confirmText(tool3, args, lang = "en") {
9030
9077
  function costText(cost2, lang = "en") {
9031
9078
  const parts = [];
9032
9079
  if (cost2.aiGenerations) {
9033
- const n = cost2.aiGenerations;
9080
+ const n2 = cost2.aiGenerations;
9034
9081
  const left = cost2.aiLeft;
9035
9082
  parts.push(
9036
- lang === "zh-Hant" ? `\u7528 ${n} \u6B21 AI \u751F\u6210${left == null ? "" : `\uFF08\u4ECA\u5929\u5269 ${left} \u6B21\uFF09`}\u3002` : `Uses ${n} AI generation${n === 1 ? "" : "s"}${left == null ? "" : ` (${left} left today)`}.`
9083
+ lang === "zh-Hant" ? `\u7528 ${n2} \u6B21 AI \u751F\u6210${left == null ? "" : `\uFF08\u4ECA\u5929\u5269 ${left} \u6B21\uFF09`}\u3002` : `Uses ${n2} AI generation${n2 === 1 ? "" : "s"}${left == null ? "" : ` (${left} left today)`}.`
9037
9084
  );
9038
9085
  }
9039
9086
  if (cost2.credits) {
9040
- const n = cost2.credits;
9087
+ const n2 = cost2.credits;
9041
9088
  const left = cost2.creditsLeft;
9042
9089
  parts.push(
9043
- lang === "zh-Hant" ? `\u6263 ${n} \u500B\u984D\u5EA6${left == null ? "" : `\uFF08\u5269 ${left}\uFF09`}\u3002` : `Uses ${n} credit${n === 1 ? "" : "s"}${left == null ? "" : ` (${left} left)`}.`
9090
+ lang === "zh-Hant" ? `\u6263 ${n2} \u500B\u984D\u5EA6${left == null ? "" : `\uFF08\u5269 ${left}\uFF09`}\u3002` : `Uses ${n2} credit${n2 === 1 ? "" : "s"}${left == null ? "" : ` (${left} left)`}.`
9044
9091
  );
9045
9092
  }
9046
9093
  return parts.join(" ");
@@ -9052,13 +9099,13 @@ function ws(ctx) {
9052
9099
  if (!ctx.domain) throw new Error("No website is selected yet. Add one first.");
9053
9100
  return domainApi(ctx.api, ctx.domain);
9054
9101
  }
9055
- var clip = (text3, n = 280) => typeof text3 === "string" ? text3.length > n ? `${text3.slice(0, n)}\u2026` : text3 : text3 ?? null;
9102
+ var clip = (text3, n2 = 280) => typeof text3 === "string" ? text3.length > n2 ? `${text3.slice(0, n2)}\u2026` : text3 : text3 ?? null;
9056
9103
  var plain = (html) => String(html ?? "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9057
9104
  var untrusted = (value) => ({ untrusted: value });
9058
9105
  function upsert(list2, item2, key) {
9059
9106
  const k = (key(item2) ?? "").trim().toLowerCase();
9060
9107
  const i = list2.findIndex((x) => (key(x) ?? "").trim().toLowerCase() === k);
9061
- return i >= 0 ? list2.map((x, n) => n === i ? { ...x, ...item2 } : x) : [...list2, item2];
9108
+ return i >= 0 ? list2.map((x, n2) => n2 === i ? { ...x, ...item2 } : x) : [...list2, item2];
9062
9109
  }
9063
9110
  function remove(list2, name, key) {
9064
9111
  const k = name.trim().toLowerCase();
@@ -9067,8 +9114,8 @@ function remove(list2, name, key) {
9067
9114
  return out;
9068
9115
  }
9069
9116
  var int = (v, min, max, fallback) => {
9070
- const n = Math.trunc(Number(v));
9071
- return Number.isFinite(n) ? Math.min(max, Math.max(min, n)) : fallback;
9117
+ const n2 = Math.trunc(Number(v));
9118
+ return Number.isFinite(n2) ? Math.min(max, Math.max(min, n2)) : fallback;
9072
9119
  };
9073
9120
  var positionParam = (what, fallback) => ({
9074
9121
  type: "string",
@@ -9237,12 +9284,12 @@ var accountTools = [
9237
9284
  risk: "R0",
9238
9285
  run: async (ctx, { platform }) => {
9239
9286
  const p = platform && CONNECT_FORMS.includes(platform) ? platform : "";
9240
- const label = p ? `Connect ${CONNECT_NAMES[p]}` : "Connect your website";
9287
+ const label2 = p ? `Connect ${CONNECT_NAMES[p]}` : "Connect your website";
9241
9288
  const link = `https://app.cmo.so/posts?connect=${p || "wordpress"}${ctx.domain ? `&site=${encodeURIComponent(ctx.domain)}` : ""}`;
9242
9289
  if (ctx.client === "chat")
9243
9290
  return {
9244
- card: { type: "connect", platform: p || null, label },
9245
- shown: `A "${label}" button now shows under your message. The user fills in the form there; say so in one line.`
9291
+ card: { type: "connect", platform: p || null, label: label2 },
9292
+ shown: `A "${label2}" button now shows under your message. The user fills in the form there; say so in one line.`
9246
9293
  };
9247
9294
  return { link, shown: "Give the user this link: it opens the form in the cmo.so app (web or phone)." };
9248
9295
  }
@@ -9945,10 +9992,10 @@ var brandTools = [
9945
9992
  need: write2,
9946
9993
  risk: "W1",
9947
9994
  run: async (ctx, { count }) => {
9948
- const n = Math.round(Number(count));
9949
- if (!Number.isFinite(n) || n < 0 || n > 10) throw new Error("count must be 0 to 10.");
9950
- const out = await ws(ctx).updateBrand({ internal_links: { count: n } });
9951
- return { internal_links: out.internal_links ?? { count: n } };
9995
+ const n2 = Math.round(Number(count));
9996
+ if (!Number.isFinite(n2) || n2 < 0 || n2 > 10) throw new Error("count must be 0 to 10.");
9997
+ const out = await ws(ctx).updateBrand({ internal_links: { count: n2 } });
9998
+ return { internal_links: out.internal_links ?? { count: n2 } };
9952
9999
  }
9953
10000
  }),
9954
10001
  tool({
@@ -9969,8 +10016,8 @@ var brandTools = [
9969
10016
  risk: "W1",
9970
10017
  run: async (ctx, { use_in_posts, ...item2 }) => {
9971
10018
  const t = (await ws(ctx).brand()).testimonials ?? { enabled: false, items: [] };
9972
- const items = upsert(t.items, item2, (i) => i.name);
9973
- return (await ws(ctx).updateBrand({ testimonials: { enabled: use_in_posts ?? t.enabled, items } })).testimonials;
10019
+ const items2 = upsert(t.items, item2, (i) => i.name);
10020
+ return (await ws(ctx).updateBrand({ testimonials: { enabled: use_in_posts ?? t.enabled, items: items2 } })).testimonials;
9974
10021
  }
9975
10022
  }),
9976
10023
  tool({
@@ -9988,8 +10035,8 @@ var brandTools = [
9988
10035
  risk: "W1",
9989
10036
  run: async (ctx, { name, use_in_posts }) => {
9990
10037
  const t = (await ws(ctx).brand()).testimonials ?? { enabled: false, items: [] };
9991
- const items = name ? remove(t.items, name, (i) => i.name) : t.items;
9992
- return (await ws(ctx).updateBrand({ testimonials: { enabled: use_in_posts ?? t.enabled, items } })).testimonials;
10038
+ const items2 = name ? remove(t.items, name, (i) => i.name) : t.items;
10039
+ return (await ws(ctx).updateBrand({ testimonials: { enabled: use_in_posts ?? t.enabled, items: items2 } })).testimonials;
9993
10040
  }
9994
10041
  }),
9995
10042
  tool({
@@ -10191,7 +10238,7 @@ var brandTools = [
10191
10238
  );
10192
10239
  if (typeof entry === "string") throw new Error(entry);
10193
10240
  if (old && sitemapUrl(old.url) !== entry.url) delete entry.check;
10194
- const next = old ? list2.map((x, n) => n === i ? entry : x) : [...list2, entry];
10241
+ const next = old ? list2.map((x, n2) => n2 === i ? entry : x) : [...list2, entry];
10195
10242
  const problem = checkSitemaps(next);
10196
10243
  if (problem) throw new Error(problem);
10197
10244
  const saved = (await ws(ctx).updateBrand({ sitemaps: next })).sitemaps ?? next;
@@ -10215,7 +10262,7 @@ var brandTools = [
10215
10262
  const list2 = (await ws(ctx).brand()).sitemaps ?? [];
10216
10263
  const i = findSitemap(list2, id, url);
10217
10264
  if (i < 0) throw new Error(`No sitemap ${id ?? url} was found. Use sitemap_list.`);
10218
- const saved = (await ws(ctx).updateBrand({ sitemaps: list2.filter((_, n) => n !== i) })).sitemaps ?? [];
10265
+ const saved = (await ws(ctx).updateBrand({ sitemaps: list2.filter((_, n2) => n2 !== i) })).sitemaps ?? [];
10219
10266
  return { removed: list2[i]?.url, sitemaps: saved.map((s) => s.url) };
10220
10267
  }
10221
10268
  }),
@@ -10374,7 +10421,7 @@ function sitemapResult(s) {
10374
10421
  // ../../packages/tools/src/feedback.ts
10375
10422
  var account = { resource: "account", level: "read" };
10376
10423
  var TYPES = ["bug", "quality", "idea", "praise", "other"];
10377
- var text = (v, n) => String(v ?? "").trim().slice(0, n);
10424
+ var text = (v, n2) => String(v ?? "").trim().slice(0, n2);
10378
10425
  var feedbackTools = [
10379
10426
  tool({
10380
10427
  name: "feedback_send",
@@ -11566,6 +11613,36 @@ var CHANNEL_KEYS = ["linkedin", "facebook", "instagram", "google_business"];
11566
11613
  var ALL_CHANNEL_KEYS = [...CHANNEL_KEYS, "youtube", "tiktok"];
11567
11614
  var VIDEO_KEYS = ["linkedin", "facebook", "instagram", "youtube", "tiktok"];
11568
11615
  var TIKTOK_PRIVACY = ["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "FOLLOWER_OF_CREATOR", "SELF_ONLY"];
11616
+ var SLIDES = [5, 6, 7, 8, 9, 10];
11617
+ var adVideo = (v) => ({
11618
+ id: v.id,
11619
+ status: v.status,
11620
+ step: v.step,
11621
+ aspect: v.aspect,
11622
+ plan: v.plan ? {
11623
+ title: v.plan.title,
11624
+ style: v.plan.style,
11625
+ voice: v.plan.voice,
11626
+ music: v.plan.music,
11627
+ seconds: v.plan.seconds,
11628
+ end_card: v.plan.cta,
11629
+ scenes: v.plan.scenes.map((s) => ({
11630
+ id: s.id,
11631
+ seconds: s.seconds,
11632
+ picture: s.still,
11633
+ motion: s.motion,
11634
+ line: s.line,
11635
+ caption: s.caption,
11636
+ picture_url: s.still_url ?? null
11637
+ }))
11638
+ } : null,
11639
+ estimate: v.estimate,
11640
+ cost_usd: v.cost_usd,
11641
+ video_id: v.video_id,
11642
+ video: v.video ? { status: v.video.status, preview_url: v.video.preview_url, poster_url: v.video.poster_url } : null,
11643
+ 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." : ""
11645
+ });
11569
11646
  var channelsParam = {
11570
11647
  type: "array",
11571
11648
  items: { type: "string", enum: CHANNEL_KEYS },
@@ -11582,6 +11659,11 @@ var FILE_SCHEMA = {
11582
11659
  required: ["download_url", "file_id"]
11583
11660
  };
11584
11661
  var fileLinks = (value) => (Array.isArray(value) ? value : value ? [value] : []).map((f) => String(f?.download_url ?? "").trim()).filter(Boolean);
11662
+ var videoLink = (a) => {
11663
+ const link = fileLinks(a.file)[0] ?? String(a.url ?? "").trim();
11664
+ if (!link.startsWith("https://")) throw new Error("Give the video (a file from this chat, or a direct https link).");
11665
+ return link;
11666
+ };
11585
11667
  var queuedAt = positionParam("the posts", "Leave out for the front (the next posting times).");
11586
11668
  var channelsOf = (value) => {
11587
11669
  const list2 = Array.isArray(value) ? value.map((v) => String(v).toLowerCase()).filter((v) => CHANNEL_KEYS.includes(v)) : [];
@@ -11694,12 +11776,12 @@ var socialTools = [
11694
11776
  const at = positionOf(position);
11695
11777
  const api2 = ws(ctx);
11696
11778
  const results = [];
11697
- for (const n of list2) {
11779
+ for (const n2 of list2) {
11698
11780
  try {
11699
- await api2.approveSocial(n, at);
11700
- results.push({ id: n, ok: true });
11781
+ await api2.approveSocial(n2, at);
11782
+ results.push({ id: n2, ok: true });
11701
11783
  } catch (e) {
11702
- results.push({ id: n, ok: false, error: e instanceof Error ? e.message : String(e) });
11784
+ results.push({ id: n2, ok: false, error: e instanceof Error ? e.message : String(e) });
11703
11785
  }
11704
11786
  }
11705
11787
  return { approved: results.filter((r) => r.ok).length, results };
@@ -11714,9 +11796,9 @@ var socialTools = [
11714
11796
  risk: "W3",
11715
11797
  gates: ["VER"],
11716
11798
  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 });
11799
+ const n2 = Number(id);
11800
+ if (!Number.isInteger(n2) || n2 <= 0) throw new Error("Which post?");
11801
+ const post = await ws(ctx).publishSocialNow(n2, { background: true });
11720
11802
  return post.status === "published" ? post : { ...post, note: "Posting now. Check social_post_get in a minute for its link." };
11721
11803
  }
11722
11804
  }),
@@ -11737,11 +11819,11 @@ var socialTools = [
11737
11819
  risk: "W3",
11738
11820
  gates: ["VER"],
11739
11821
  run: async (ctx, { id, social_copy, repost }) => {
11740
- const n = Number(id);
11741
- if (!Number.isInteger(n) || n <= 0) throw new Error("Which post?");
11822
+ const n2 = Number(id);
11823
+ if (!Number.isInteger(n2) || n2 <= 0) throw new Error("Which post?");
11742
11824
  const copy = typeof social_copy === "string" ? social_copy.trim() : "";
11743
11825
  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);
11826
+ return ws(ctx).republishSocial(n2, copy || void 0, repost === true);
11745
11827
  }
11746
11828
  }),
11747
11829
  tool({
@@ -11753,9 +11835,9 @@ var socialTools = [
11753
11835
  risk: "W3",
11754
11836
  gates: ["VER"],
11755
11837
  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);
11838
+ const n2 = Number(id);
11839
+ if (!Number.isInteger(n2) || n2 <= 0) throw new Error("Which post?");
11840
+ return ws(ctx).deletePostedSocial(n2);
11759
11841
  }
11760
11842
  }),
11761
11843
  tool({
@@ -11780,7 +11862,7 @@ var socialTools = [
11780
11862
  }),
11781
11863
  tool({
11782
11864
  name: "social_image_new",
11783
- description: "Make a new picture for a social post that hasn't gone out, or change its current one (mode: edit, as `instruction` says). Uses 1 AI generation. It is made in the background, on the user's own ChatGPT when their Maggie Bridge has images on, else by CMO.so; it takes up to a couple of minutes and appears on the post by itself (check with social_image_status). One picture is made at a time per website: while another is being made this one waits in line (status queued). It fails with image_busy only when this post already has one being made or in line; pass replace: true only when the user agrees to stop the one being made and make this one now. A post written from an article gets its own picture (from its text and the article's title) and then goes out as that picture. Every new picture carries a short title and line set on it; count: 4 makes a set of 4 pictures that tell one story (still 1 AI generation, drawn as one storyboard and cut); LinkedIn and Facebook post a set together as a collage. text_effect picks the style of those words (an id from social_text_effects); leave it out to let Maggie pick. style picks the picture style (an id from social_look: photo_headline, editorial, duotone, illustrated, bold_type, infographic); leave it out and Maggie takes turns with the brand's 3. bold_type and infographic make no AI picture (big words or a list in the brand's look) and cost nothing.",
11865
+ description: "Make a new picture for a social post that hasn't gone out, or change its current one (mode: edit, as `instruction` says). Uses 1 AI generation. It is made in the background, on the user's own ChatGPT when their Maggie Bridge has images on, else by CMO.so; it takes up to a couple of minutes and appears on the post by itself (check with social_image_status). One picture is made at a time per website: while another is being made this one waits in line (status queued). It fails with image_busy only when this post already has one being made or in line; pass replace: true only when the user agrees to stop the one being made and make this one now. A post written from an article gets its own picture (from its text and the article's title) and then goes out as that picture. Every new picture carries a short title and line set on it; count: 4 makes a set of 4 pictures that tell one story (still 1 AI generation, drawn as one storyboard and cut); LinkedIn and Facebook post a set together as a collage. text_effect picks the style of those words (an id from social_text_effects); leave it out to let Maggie pick. style picks the picture style (an id from social_look: photo_headline, editorial, duotone, illustrated, bold_type, infographic); leave it out and Maggie takes turns with the brand's 3. bold_type and infographic make no AI picture (big words or a list in the brand's look) and cost nothing. count 5-10 makes a text carousel: a cover with the hook, one point a slide and a last slide with the next step, words on the brand's colours (style bold_type or infographic), no AI picture and free; LinkedIn posts it as a swipeable PDF carousel, Instagram and Facebook as a carousel (the first 10), TikTok as a photo post (social_tiktok_photo).",
11784
11866
  parameters: {
11785
11867
  type: "object",
11786
11868
  properties: {
@@ -11797,7 +11879,7 @@ var socialTools = [
11797
11879
  },
11798
11880
  count: {
11799
11881
  type: "integer",
11800
- description: "1 (default) or 4: a set of 4 pictures telling one story, a line of words on each"
11882
+ description: "1 (default); 4: a set of 4 pictures telling one story, a line of words on each; 5-10: a text carousel of that many slides (free)"
11801
11883
  },
11802
11884
  text_effect: {
11803
11885
  type: "string",
@@ -11819,7 +11901,7 @@ var socialTools = [
11819
11901
  replace: replace === true,
11820
11902
  ...typeof style === "string" && style.trim() && mode !== "edit" ? { style: style.trim() } : {},
11821
11903
  ...mode === "edit" ? { mode: "edit" } : {},
11822
- ...Number(count) === 4 && mode !== "edit" ? { count: 4 } : {},
11904
+ ...mode !== "edit" && (Number(count) === 4 || SLIDES.includes(Number(count))) ? { count: Number(count) } : {},
11823
11905
  ...typeof text_effect === "string" && text_effect.trim() ? { textEffect: text_effect.trim() } : {},
11824
11906
  ...typeof instruction === "string" && instruction.trim() ? { instruction: instruction.trim().slice(0, 300) } : {}
11825
11907
  });
@@ -12045,11 +12127,14 @@ var socialTools = [
12045
12127
  }),
12046
12128
  tool({
12047
12129
  name: "social_carousel_pdf_attach",
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.",
12130
+ description: "A PDF carousel for a LinkedIn, Instagram, Facebook or TikTok post: 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 become the post's pictures: Instagram and Facebook post them as a carousel (the first 10), TikTok as a photo post. A LinkedIn post (this one, or the same idea's LinkedIn post) goes out as the PDF itself, a swipeable document carousel. Free. Change the pictures later and the LinkedIn carousel is made from them instead. If you can pass neither, give the user social_upload_link. Preview first.",
12049
12131
  parameters: {
12050
12132
  type: "object",
12051
12133
  properties: {
12052
- id: { type: "integer", description: "A LinkedIn post (from social_list), not gone out yet" },
12134
+ id: {
12135
+ type: "integer",
12136
+ description: "A LinkedIn, Instagram, Facebook or TikTok post (from social_list), not gone out yet"
12137
+ },
12053
12138
  file: { ...FILE_SCHEMA, description: "The PDF from this chat" },
12054
12139
  url: { type: "string", description: "Public https link to the PDF file" },
12055
12140
  title: {
@@ -12063,7 +12148,7 @@ var socialTools = [
12063
12148
  group: "social",
12064
12149
  need: write5,
12065
12150
  risk: "W1",
12066
- gates: ["VER", "LI"],
12151
+ gates: ["VER"],
12067
12152
  run: async (ctx, { id, url, file, title }) => {
12068
12153
  const link = fileLinks(file)[0] ?? String(url ?? "").trim();
12069
12154
  if (!link.startsWith("https://"))
@@ -12092,7 +12177,7 @@ var socialTools = [
12092
12177
  id: post.id,
12093
12178
  channel: post.provider ?? null,
12094
12179
  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)."
12180
+ how: ["linkedin", "instagram", "facebook", "tiktok"].includes(String(post.provider)) ? "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
12181
  };
12097
12182
  }
12098
12183
  }),
@@ -12790,22 +12875,24 @@ var socialTools = [
12790
12875
  }),
12791
12876
  tool({
12792
12877
  name: "social_video_attach",
12793
- 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.",
12878
+ description: "Make a LinkedIn, Facebook or Instagram post that hasn't gone out a video post: it goes out as this video (5-30 seconds) with its text, and its picture is removed (a video post has no picture). The video from this chat (file: one the user added) or a DIRECT https link to a video file (url; never a platform page). If you can pass neither, the user adds the video in the CMO.so app (Social). Not for Google Business (posts can't carry video). Preview first.",
12794
12879
  parameters: {
12795
12880
  type: "object",
12796
12881
  properties: {
12797
12882
  id: { type: "integer", description: "The post (from social_list)" },
12883
+ file: { ...FILE_SCHEMA, description: "The video from this chat (MP4 or MOV)" },
12798
12884
  url: { type: "string", description: "Direct https link to the video file" }
12799
12885
  },
12800
- required: ["id", "url"]
12886
+ required: ["id"]
12801
12887
  },
12888
+ fileParams: ["file"],
12802
12889
  group: "social",
12803
12890
  need: write5,
12804
12891
  risk: "W1",
12805
12892
  gates: ["VER"],
12806
12893
  run: async (ctx, a) => {
12807
12894
  const d = ws(ctx);
12808
- let v = await d.videoFromLink(String(a.url));
12895
+ let v = await d.videoFromLink(videoLink(a));
12809
12896
  const until = Date.now() + 15e4;
12810
12897
  while ((v.status === "checking" || v.status === "uploading") && Date.now() < until) {
12811
12898
  await new Promise((r) => setTimeout(r, 3e3));
@@ -12881,11 +12968,13 @@ var socialTools = [
12881
12968
  }),
12882
12969
  tool({
12883
12970
  name: "social_video_add",
12884
- 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.",
12971
+ 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 video already in CMO.so (video_id, e.g. a ready ad video's), the video from this chat (file: one the user added) or a DIRECT https link to a video file (url: an .mp4 on their website, a public Dropbox link), never a YouTube / TikTok / Instagram page (we never download those). If you can pass neither, tell the user to add the video in the CMO.so app (Social). 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.",
12885
12972
  parameters: {
12886
12973
  type: "object",
12887
12974
  properties: {
12975
+ file: { ...FILE_SCHEMA, description: "The video from this chat (MP4 or MOV)" },
12888
12976
  url: { type: "string", description: "Direct https link to the video file" },
12977
+ video_id: { type: "integer", description: "A video already in CMO.so (an ad video's video_id)" },
12889
12978
  channels: {
12890
12979
  type: "array",
12891
12980
  items: { type: "string", enum: [...VIDEO_KEYS] },
@@ -12897,9 +12986,9 @@ var socialTools = [
12897
12986
  start: { type: "number", description: "Start of the part to use, seconds" },
12898
12987
  end: { type: "number", description: "End of the part to use, seconds" },
12899
12988
  vertical: { type: "string", enum: ["left", "middle", "right"] }
12900
- },
12901
- required: ["url"]
12989
+ }
12902
12990
  },
12991
+ fileParams: ["file"],
12903
12992
  group: "social",
12904
12993
  need: write5,
12905
12994
  risk: "W2",
@@ -12911,7 +13000,7 @@ var socialTools = [
12911
13000
  ...a.end != null ? { end: Number(a.end) } : {},
12912
13001
  ...a.vertical ? { aspect: "9:16", crop_x: a.vertical === "left" ? 0 : a.vertical === "right" ? 1 : 0.5 } : {}
12913
13002
  };
12914
- let v = await d.videoFromLink(String(a.url), Object.keys(edit).length ? edit : void 0);
13003
+ let v = a.video_id ? await d.video(Number(a.video_id)) : await d.videoFromLink(videoLink(a), Object.keys(edit).length ? edit : void 0);
12915
13004
  const until = Date.now() + 15e4;
12916
13005
  while ((v.status === "checking" || v.status === "uploading") && Date.now() < until) {
12917
13006
  await new Promise((r) => setTimeout(r, 3e3));
@@ -12944,6 +13033,183 @@ var socialTools = [
12944
13033
  };
12945
13034
  }
12946
13035
  }),
13036
+ tool({
13037
+ name: "social_tiktok_photo",
13038
+ description: "A TikTok photo post (a swipeable set of pictures; TikTok adds music) from a post's pictures: a set, a text carousel (social_image_new count 5-10) or the pages of a PDF. A new TikTok post that waits in Review like every TikTok post. Maggie writes the caption from the post (one AI generation) unless caption is given. Then TikTok's settings: read tiktok_creator, ask who can view it and the rest, social_tiktok_set exactly as the user says. TikTok is in beta. Preview first.",
13039
+ parameters: {
13040
+ type: "object",
13041
+ properties: {
13042
+ id: { type: "integer", description: "A post with pictures (from social_list), not a video post" },
13043
+ note: { type: "string", description: "What to say in the caption, in the user's words" },
13044
+ caption: { type: "string", description: "The caption itself, when the user wrote it (no AI)" }
13045
+ },
13046
+ required: ["id"]
13047
+ },
13048
+ group: "social",
13049
+ need: write5,
13050
+ risk: "W2",
13051
+ gates: ["VER", "LLM"],
13052
+ run: async (ctx, { id, note, caption }) => {
13053
+ const out = await ws(ctx).tiktokPhotoPost(Number(id), {
13054
+ ...note ? { note: String(note).slice(0, 1e3) } : {},
13055
+ ...typeof caption === "string" && caption.trim() ? { caption: caption.trim(), write: false } : {}
13056
+ });
13057
+ return {
13058
+ post: item(out),
13059
+ next: "Show the caption; read tiktok_creator and ask who can view it, whether to allow comments and whether it promotes a brand, then social_tiktok_set exactly as they say; tell them the Music Usage Confirmation line. The user approves it."
13060
+ };
13061
+ }
13062
+ }),
13063
+ tool({
13064
+ 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.",
13066
+ parameters: {
13067
+ type: "object",
13068
+ properties: {
13069
+ brief: { type: "string", description: "What the ad should do, who it's for, the offer (user's words)" },
13070
+ item_id: { type: "integer", description: "A post (from social_list) to make the ad from" },
13071
+ aspect: { type: "string", enum: ["9:16", "16:9"] },
13072
+ seconds: { type: "integer", description: "15, 20 or 30" },
13073
+ scenes: { type: "integer", description: "2-5 (default 3, or 4 for 30 seconds)" },
13074
+ reference_url: { type: "string", description: "A public YouTube ad to follow the rhythm of" }
13075
+ }
13076
+ },
13077
+ group: "social",
13078
+ need: write5,
13079
+ risk: "W2",
13080
+ gates: ["VER", "LLM"],
13081
+ run: async (ctx, a) => {
13082
+ const out = await ws(ctx).planAdVideo({
13083
+ ...a.brief ? { brief: String(a.brief).slice(0, 3e3) } : {},
13084
+ ...a.item_id ? { item_id: Number(a.item_id) } : {},
13085
+ ...a.aspect ? { aspect: a.aspect } : {},
13086
+ ...a.seconds ? { seconds: Number(a.seconds) } : {},
13087
+ ...a.scenes ? { scenes: Number(a.scenes) } : {},
13088
+ ...a.reference_url ? { reference_url: String(a.reference_url).trim() } : {}
13089
+ });
13090
+ return adVideo(out);
13091
+ }
13092
+ }),
13093
+ tool({
13094
+ name: "ad_video_list",
13095
+ description: "This website's ad videos (beta): each with its status (planned, running and its step, ready, failed), the storyboard's title, its cost so far and, when ready, the video (video_id) to post.",
13096
+ parameters: none,
13097
+ group: "social",
13098
+ need: read5,
13099
+ risk: "R0",
13100
+ run: async (ctx) => {
13101
+ const out = await ws(ctx).adVideos();
13102
+ return {
13103
+ ad_videos: out.ad_videos.map((v) => ({
13104
+ id: v.id,
13105
+ status: v.status,
13106
+ step: v.step,
13107
+ title: v.plan?.title ?? null,
13108
+ aspect: v.aspect,
13109
+ cost_usd: v.cost_usd,
13110
+ video_id: v.video_id,
13111
+ video_status: v.video?.status ?? null,
13112
+ error: v.error
13113
+ })),
13114
+ voices: out.voices
13115
+ };
13116
+ }
13117
+ }),
13118
+ tool({
13119
+ name: "ad_video_get",
13120
+ description: "One ad video (beta): its storyboard (scenes with their pictures once made), what making the missing pieces costs (estimate), its status and step; when ready, video_id and the video's status (it is prepared for the channels in the background: post it once video.status is ready). While it's running, check again every minute or so.",
13121
+ parameters: {
13122
+ type: "object",
13123
+ properties: { id: { type: "integer", description: "The ad video (from ad_video_list)" } },
13124
+ required: ["id"]
13125
+ },
13126
+ group: "social",
13127
+ need: read5,
13128
+ risk: "R0",
13129
+ run: async (ctx, { id }) => adVideo(await ws(ctx).adVideo(Number(id)))
13130
+ }),
13131
+ tool({
13132
+ 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.",
13134
+ parameters: {
13135
+ type: "object",
13136
+ properties: {
13137
+ id: { type: "integer", description: "The ad video" },
13138
+ scenes: {
13139
+ type: "array",
13140
+ items: {
13141
+ type: "object",
13142
+ properties: {
13143
+ id: { type: "string", description: "Scene id, e.g. s2" },
13144
+ still: { type: "string" },
13145
+ motion: { type: "string" },
13146
+ line: { type: "string" },
13147
+ caption: { type: "string" },
13148
+ seconds: { type: "number" }
13149
+ },
13150
+ required: ["id"]
13151
+ }
13152
+ },
13153
+ redo_scenes: { type: "array", items: { type: "string" } },
13154
+ cta_headline: { type: "string" },
13155
+ cta_sub: { type: "string" },
13156
+ voice: { type: "string", description: "A voice name, or none" },
13157
+ music: { type: "string", description: "The music in words, or none" },
13158
+ title: { type: "string" }
13159
+ },
13160
+ required: ["id"]
13161
+ },
13162
+ group: "social",
13163
+ need: write5,
13164
+ risk: "W1",
13165
+ gates: ["VER"],
13166
+ run: async (ctx, a) => {
13167
+ const cta = {
13168
+ ...a.cta_headline != null ? { headline: a.cta_headline } : {},
13169
+ ...a.cta_sub != null ? { sub: a.cta_sub } : {}
13170
+ };
13171
+ const out = await ws(ctx).changeAdVideo(Number(a.id), {
13172
+ ...a.scenes?.length ? { scenes: a.scenes } : {},
13173
+ ...a.redo_scenes?.length ? { redo_scenes: a.redo_scenes } : {},
13174
+ ...Object.keys(cta).length ? { cta } : {},
13175
+ ...a.voice != null ? { voice: /^none$/i.test(a.voice) ? false : a.voice } : {},
13176
+ ...a.music != null ? { music: /^none$/i.test(a.music) ? "" : a.music } : {},
13177
+ ...a.title ? { title: a.title } : {}
13178
+ });
13179
+ return { ...adVideo(out), redo: out.redo ?? [] };
13180
+ }
13181
+ }),
13182
+ tool({
13183
+ 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.",
13185
+ parameters: {
13186
+ type: "object",
13187
+ properties: {
13188
+ id: { type: "integer", description: "The ad video" },
13189
+ estimate_usd: { type: "number", description: "The cost the user agreed to (estimate.usd)" }
13190
+ },
13191
+ required: ["id", "estimate_usd"]
13192
+ },
13193
+ group: "social",
13194
+ need: write5,
13195
+ risk: "W3",
13196
+ gates: ["VER"],
13197
+ run: async (ctx, { id, estimate_usd }) => adVideo(await ws(ctx).makeAdVideo(Number(id), Number(estimate_usd)))
13198
+ }),
13199
+ tool({
13200
+ name: "ad_video_stop",
13201
+ description: "Stop an ad video being made (after the piece it's on; what was made is kept and making it again carries on), or remove one that isn't being made from the list (posts made from it keep their video). Preview first.",
13202
+ parameters: {
13203
+ type: "object",
13204
+ properties: { id: { type: "integer", description: "The ad video" } },
13205
+ required: ["id"]
13206
+ },
13207
+ group: "social",
13208
+ need: write5,
13209
+ risk: "W1",
13210
+ gates: ["VER"],
13211
+ run: async (ctx, { id }) => ws(ctx).stopAdVideo(Number(id))
13212
+ }),
12947
13213
  tool({
12948
13214
  name: "social_youtube_set",
12949
13215
  description: "Change a YouTube post that hasn't gone out: title (at most 100 characters, aim 40-60), tags, visibility (public, unlisted, private), made_for_kids (YouTube asks for every video: ask the user, never guess) or synthetic (realistic AI-made or altered content). The description is the post's text (social_update_copy). Preview first.",
@@ -13253,8 +13519,8 @@ var CommanderError = class extends Error {
13253
13519
  * @param {string} code an id string representing the error
13254
13520
  * @param {string} message human-readable description of the error
13255
13521
  */
13256
- constructor(exitCode2, code, message) {
13257
- super(message);
13522
+ constructor(exitCode2, code, message2) {
13523
+ super(message2);
13258
13524
  Error.captureStackTrace(this, this.constructor);
13259
13525
  this.name = this.constructor.name;
13260
13526
  this.code = code;
@@ -13267,8 +13533,8 @@ var InvalidArgumentError = class extends CommanderError {
13267
13533
  * Constructs the InvalidArgumentError class
13268
13534
  * @param {string} [message] explanation of why argument is invalid
13269
13535
  */
13270
- constructor(message) {
13271
- super(1, "commander.invalidArgument", message);
13536
+ constructor(message2) {
13537
+ super(1, "commander.invalidArgument", message2);
13272
13538
  Error.captureStackTrace(this, this.constructor);
13273
13539
  this.name = this.constructor.name;
13274
13540
  }
@@ -13724,9 +13990,9 @@ var Help = class {
13724
13990
  * @param {Help} helper
13725
13991
  * @returns string[]
13726
13992
  */
13727
- formatItemList(heading, items, helper) {
13728
- if (items.length === 0) return [];
13729
- return [helper.styleTitle(heading), ...items, ""];
13993
+ formatItemList(heading, items2, helper) {
13994
+ if (items2.length === 0) return [];
13995
+ return [helper.styleTitle(heading), ...items2, ""];
13730
13996
  }
13731
13997
  /**
13732
13998
  * Group items by their help group heading.
@@ -14814,9 +15080,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
14814
15080
  * @return never
14815
15081
  * @private
14816
15082
  */
14817
- _exit(exitCode2, code, message) {
15083
+ _exit(exitCode2, code, message2) {
14818
15084
  if (this._exitCallback) {
14819
- this._exitCallback(new CommanderError(exitCode2, code, message));
15085
+ this._exitCallback(new CommanderError(exitCode2, code, message2));
14820
15086
  }
14821
15087
  process2.exit(exitCode2);
14822
15088
  }
@@ -14876,8 +15142,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
14876
15142
  return target.parseArg(value, previous);
14877
15143
  } catch (err) {
14878
15144
  if (err.code === "commander.invalidArgument") {
14879
- const message = `${invalidArgumentMessage} ${err.message}`;
14880
- this.error(message, { exitCode: err.exitCode, code: err.code });
15145
+ const message2 = `${invalidArgumentMessage} ${err.message}`;
15146
+ this.error(message2, { exitCode: err.exitCode, code: err.code });
14881
15147
  }
14882
15148
  throw err;
14883
15149
  }
@@ -15955,9 +16221,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
15955
16221
  * @param {string} [errorOptions.code] - an id string representing the error
15956
16222
  * @param {number} [errorOptions.exitCode] - used with process.exit
15957
16223
  */
15958
- error(message, errorOptions) {
16224
+ error(message2, errorOptions) {
15959
16225
  this._outputConfiguration.outputError(
15960
- `${message}
16226
+ `${message2}
15961
16227
  `,
15962
16228
  this._outputConfiguration.writeErr
15963
16229
  );
@@ -15971,7 +16237,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
15971
16237
  const config2 = errorOptions || {};
15972
16238
  const exitCode2 = config2.exitCode || 1;
15973
16239
  const code = config2.code || "commander.error";
15974
- this._exit(exitCode2, code, message);
16240
+ this._exit(exitCode2, code, message2);
15975
16241
  }
15976
16242
  /**
15977
16243
  * Apply any option related environment variables, if option does
@@ -16027,8 +16293,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
16027
16293
  * @private
16028
16294
  */
16029
16295
  missingArgument(name) {
16030
- const message = `error: missing required argument '${name}'`;
16031
- this.error(message, { code: "commander.missingArgument" });
16296
+ const message2 = `error: missing required argument '${name}'`;
16297
+ this.error(message2, { code: "commander.missingArgument" });
16032
16298
  }
16033
16299
  /**
16034
16300
  * `Option` is missing an argument.
@@ -16037,8 +16303,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
16037
16303
  * @private
16038
16304
  */
16039
16305
  optionMissingArgument(option) {
16040
- const message = `error: option '${option.flags}' argument missing`;
16041
- this.error(message, { code: "commander.optionMissingArgument" });
16306
+ const message2 = `error: option '${option.flags}' argument missing`;
16307
+ this.error(message2, { code: "commander.optionMissingArgument" });
16042
16308
  }
16043
16309
  /**
16044
16310
  * `Option` does not have a value, and is a mandatory option.
@@ -16047,8 +16313,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
16047
16313
  * @private
16048
16314
  */
16049
16315
  missingMandatoryOptionValue(option) {
16050
- const message = `error: required option '${option.flags}' not specified`;
16051
- this.error(message, { code: "commander.missingMandatoryOptionValue" });
16316
+ const message2 = `error: required option '${option.flags}' not specified`;
16317
+ this.error(message2, { code: "commander.missingMandatoryOptionValue" });
16052
16318
  }
16053
16319
  /**
16054
16320
  * `Option` conflicts with another option.
@@ -16081,8 +16347,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
16081
16347
  }
16082
16348
  return `option '${bestOption.flags}'`;
16083
16349
  };
16084
- const message = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
16085
- this.error(message, { code: "commander.conflictingOption" });
16350
+ const message2 = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
16351
+ this.error(message2, { code: "commander.conflictingOption" });
16086
16352
  }
16087
16353
  /**
16088
16354
  * Unknown option `flag`.
@@ -16103,8 +16369,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
16103
16369
  } while (command && !command._enablePositionalOptions);
16104
16370
  suggestion = suggestSimilar(flag, candidateFlags);
16105
16371
  }
16106
- const message = `error: unknown option '${flag}'${suggestion}`;
16107
- this.error(message, { code: "commander.unknownOption" });
16372
+ const message2 = `error: unknown option '${flag}'${suggestion}`;
16373
+ this.error(message2, { code: "commander.unknownOption" });
16108
16374
  }
16109
16375
  /**
16110
16376
  * Excess arguments, more than expected.
@@ -16119,8 +16385,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
16119
16385
  const received = receivedArgs.length;
16120
16386
  const forSubcommand = this.parent ? ` for '${this.name()}'` : "";
16121
16387
  const details2 = receivedArgs.join(", ");
16122
- const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${received}: ${details2}.`;
16123
- this.error(message, { code: "commander.excessArguments" });
16388
+ const message2 = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${received}: ${details2}.`;
16389
+ this.error(message2, { code: "commander.excessArguments" });
16124
16390
  }
16125
16391
  /**
16126
16392
  * Unknown command.
@@ -16138,8 +16404,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
16138
16404
  });
16139
16405
  suggestion = suggestSimilar(unknownName, candidateNames);
16140
16406
  }
16141
- const message = `error: unknown command '${unknownName}'${suggestion}`;
16142
- this.error(message, { code: "commander.unknownCommand" });
16407
+ const message2 = `error: unknown command '${unknownName}'${suggestion}`;
16408
+ this.error(message2, { code: "commander.unknownCommand" });
16143
16409
  }
16144
16410
  /**
16145
16411
  * Get or set the program version.
@@ -16616,7 +16882,7 @@ var program = new Command();
16616
16882
  // package.json
16617
16883
  var package_default = {
16618
16884
  name: "@cmo-so/cli",
16619
- version: "0.9.2",
16885
+ version: "0.10.0",
16620
16886
  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.",
16621
16887
  type: "module",
16622
16888
  license: "MIT",
@@ -16814,8 +17080,8 @@ function print(value, json, human) {
16814
17080
  `);
16815
17081
  return;
16816
17082
  }
16817
- const items = Array.isArray(value) ? value : isObj(value) && Array.isArray(value.items) ? value.items : null;
16818
- const out = human ? human(value) : items && items.every(isObj) ? table(items) : details(value);
17083
+ const items2 = Array.isArray(value) ? value : isObj(value) && Array.isArray(value.items) ? value.items : null;
17084
+ const out = human ? human(value) : items2 && items2.every(isObj) ? table(items2) : details(value);
16819
17085
  process.stdout.write(`${out}
16820
17086
  `);
16821
17087
  }
@@ -17414,8 +17680,8 @@ function prefixIssues(path2, issues) {
17414
17680
  return iss;
17415
17681
  });
17416
17682
  }
17417
- function unwrapMessage(message) {
17418
- return typeof message === "string" ? message : message?.message;
17683
+ function unwrapMessage(message2) {
17684
+ return typeof message2 === "string" ? message2 : message2?.message;
17419
17685
  }
17420
17686
  function sizeCaps(defChecks) {
17421
17687
  const caps = [];
@@ -17445,7 +17711,7 @@ function finalizeIssue(iss, ctx, config2) {
17445
17711
  iss.schema = iss.inst;
17446
17712
  }
17447
17713
  const schemaError = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : void 0;
17448
- const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config2.customError?.(iss)) ?? unwrapMessage(config2.localeError?.(iss)) ?? "Invalid input";
17714
+ const message2 = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config2.customError?.(iss)) ?? unwrapMessage(config2.localeError?.(iss)) ?? "Invalid input";
17449
17715
  const full = {};
17450
17716
  for (const k of Object.keys(iss)) {
17451
17717
  if (k === "inst" || k === "schema" || k === "continue" || k === "input" || k === "__proto__")
@@ -17453,7 +17719,7 @@ function finalizeIssue(iss, ctx, config2) {
17453
17719
  full[k] = iss[k];
17454
17720
  }
17455
17721
  full.path ?? (full.path = []);
17456
- full.message = message;
17722
+ full.message = message2;
17457
17723
  if (ctx?.reportInput) {
17458
17724
  full.input = iss.input;
17459
17725
  }
@@ -19069,10 +19335,10 @@ function capSize(inst, size) {
19069
19335
  const cap = Math.min(...caps.map((c) => c[0]));
19070
19336
  const parse3 = inst._zod.parse;
19071
19337
  inst._zod.parse = (payload, ctx) => {
19072
- const n = size(payload.value);
19073
- if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
19338
+ const n2 = size(payload.value);
19339
+ if (n2 > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
19074
19340
  for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
19075
- if (n > max && ch._zod.def.when(payload)) {
19341
+ if (n2 > max && ch._zod.def.when(payload)) {
19076
19342
  const start = payload.issues.length;
19077
19343
  ch._zod.check(payload);
19078
19344
  payload.issues[start].continue = false;
@@ -21168,12 +21434,12 @@ function initializeContext(params) {
21168
21434
  external: params?.external ?? void 0
21169
21435
  };
21170
21436
  }
21171
- function handleUnrepresentable(schema, ctx, json, params, message) {
21172
- const result = typeof ctx.unrepresentable === "function" ? ctx.unrepresentable({ zodSchema: schema, path: params.path, message }) : ctx.unrepresentable;
21437
+ function handleUnrepresentable(schema, ctx, json, params, message2) {
21438
+ const result = typeof ctx.unrepresentable === "function" ? ctx.unrepresentable({ zodSchema: schema, path: params.path, message: message2 }) : ctx.unrepresentable;
21173
21439
  if (result === "any")
21174
21440
  return false;
21175
21441
  if (result === void 0 || result === "throw")
21176
- throw new Error(message);
21442
+ throw new Error(message2);
21177
21443
  Object.assign(json, result);
21178
21444
  return true;
21179
21445
  }
@@ -22866,17 +23132,17 @@ var ZodArray = /* @__PURE__ */ $constructor("ZodArray", (inst, def) => {
22866
23132
  inst._zod.processJSONSchema = (ctx, json, params) => arrayProcessor(inst, ctx, json, params);
22867
23133
  inst.element = def.element;
22868
23134
  }, {
22869
- min(n, params) {
22870
- return this.check(_minLength(n, params));
23135
+ min(n2, params) {
23136
+ return this.check(_minLength(n2, params));
22871
23137
  },
22872
23138
  nonempty(params) {
22873
23139
  return this.check(_minLength(1, params));
22874
23140
  },
22875
- max(n, params) {
22876
- return this.check(_maxLength(n, params));
23141
+ max(n2, params) {
23142
+ return this.check(_maxLength(n2, params));
22877
23143
  },
22878
- length(n, params) {
22879
- return this.check(_length(n, params));
23144
+ length(n2, params) {
23145
+ return this.check(_length(n2, params));
22880
23146
  },
22881
23147
  unwrap() {
22882
23148
  return this.element;
@@ -24780,8 +25046,8 @@ var ServerResultSchema = union([
24780
25046
  CreateTaskResultSchema
24781
25047
  ]);
24782
25048
  var McpError = class _McpError extends Error {
24783
- constructor(code, message, data) {
24784
- super(`MCP error ${code}: ${message}`);
25049
+ constructor(code, message2, data) {
25050
+ super(`MCP error ${code}: ${message2}`);
24785
25051
  this.code = code;
24786
25052
  this.data = data;
24787
25053
  this.name = "McpError";
@@ -24789,19 +25055,19 @@ var McpError = class _McpError extends Error {
24789
25055
  /**
24790
25056
  * Factory method to create the appropriate error type based on the error code and data
24791
25057
  */
24792
- static fromError(code, message, data) {
25058
+ static fromError(code, message2, data) {
24793
25059
  if (code === ErrorCode.UrlElicitationRequired && data) {
24794
25060
  const errorData = data;
24795
25061
  if (errorData.elicitations) {
24796
- return new UrlElicitationRequiredError(errorData.elicitations, message);
25062
+ return new UrlElicitationRequiredError(errorData.elicitations, message2);
24797
25063
  }
24798
25064
  }
24799
- return new _McpError(code, message, data);
25065
+ return new _McpError(code, message2, data);
24800
25066
  }
24801
25067
  };
24802
25068
  var UrlElicitationRequiredError = class extends McpError {
24803
- constructor(elicitations, message = `URL elicitation${elicitations.length > 1 ? "s" : ""} required`) {
24804
- super(ErrorCode.UrlElicitationRequired, message, {
25069
+ constructor(elicitations, message2 = `URL elicitation${elicitations.length > 1 ? "s" : ""} required`) {
25070
+ super(ErrorCode.UrlElicitationRequired, message2, {
24805
25071
  elicitations
24806
25072
  });
24807
25073
  }
@@ -24887,15 +25153,15 @@ var Protocol = class {
24887
25153
  let queuedMessage;
24888
25154
  while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
24889
25155
  if (queuedMessage.type === "response" || queuedMessage.type === "error") {
24890
- const message = queuedMessage.message;
24891
- const requestId = message.id;
25156
+ const message2 = queuedMessage.message;
25157
+ const requestId = message2.id;
24892
25158
  const resolver = this._requestResolvers.get(requestId);
24893
25159
  if (resolver) {
24894
25160
  this._requestResolvers.delete(requestId);
24895
25161
  if (queuedMessage.type === "response") {
24896
- resolver(message);
25162
+ resolver(message2);
24897
25163
  } else {
24898
- const errorMessage3 = message;
25164
+ const errorMessage3 = message2;
24899
25165
  const error2 = new McpError(errorMessage3.error.code, errorMessage3.error.message, errorMessage3.error.data);
24900
25166
  resolver(error2);
24901
25167
  }
@@ -25034,16 +25300,16 @@ var Protocol = class {
25034
25300
  this._onerror(error2);
25035
25301
  };
25036
25302
  const _onmessage = this._transport?.onmessage;
25037
- this._transport.onmessage = (message, extra) => {
25038
- _onmessage?.(message, extra);
25039
- if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
25040
- this._onresponse(message);
25041
- } else if (isJSONRPCRequest(message)) {
25042
- this._onrequest(message, extra);
25043
- } else if (isJSONRPCNotification(message)) {
25044
- this._onnotification(message);
25303
+ this._transport.onmessage = (message2, extra) => {
25304
+ _onmessage?.(message2, extra);
25305
+ if (isJSONRPCResultResponse(message2) || isJSONRPCErrorResponse(message2)) {
25306
+ this._onresponse(message2);
25307
+ } else if (isJSONRPCRequest(message2)) {
25308
+ this._onrequest(message2, extra);
25309
+ } else if (isJSONRPCNotification(message2)) {
25310
+ this._onnotification(message2);
25045
25311
  } else {
25046
- this._onerror(new Error(`Unknown message type: ${JSON.stringify(message)}`));
25312
+ this._onerror(new Error(`Unknown message type: ${JSON.stringify(message2)}`));
25047
25313
  }
25048
25314
  };
25049
25315
  await this._transport.start();
@@ -25653,12 +25919,12 @@ var Protocol = class {
25653
25919
  * the error appropriately (e.g., by failing the task, logging, etc.). The Protocol layer
25654
25920
  * simply propagates the error.
25655
25921
  */
25656
- async _enqueueTaskMessage(taskId, message, sessionId) {
25922
+ async _enqueueTaskMessage(taskId, message2, sessionId) {
25657
25923
  if (!this._taskStore || !this._taskMessageQueue) {
25658
25924
  throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
25659
25925
  }
25660
25926
  const maxQueueSize = this._options?.maxTaskQueueSize;
25661
- await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
25927
+ await this._taskMessageQueue.enqueue(taskId, message2, sessionId, maxQueueSize);
25662
25928
  }
25663
25929
  /**
25664
25930
  * Clears the message queue for a task and rejects any pending request resolvers.
@@ -25668,9 +25934,9 @@ var Protocol = class {
25668
25934
  async _clearTaskQueue(taskId, sessionId) {
25669
25935
  if (this._taskMessageQueue) {
25670
25936
  const messages = await this._taskMessageQueue.dequeueAll(taskId, sessionId);
25671
- for (const message of messages) {
25672
- if (message.type === "request" && isJSONRPCRequest(message.message)) {
25673
- const requestId = message.message.id;
25937
+ for (const message2 of messages) {
25938
+ if (message2.type === "request" && isJSONRPCRequest(message2.message)) {
25939
+ const requestId = message2.message.id;
25674
25940
  const resolver = this._requestResolvers.get(requestId);
25675
25941
  if (resolver) {
25676
25942
  resolver(new McpError(ErrorCode.InternalError, "Task cancelled or completed"));
@@ -26483,6 +26749,75 @@ var Server = class extends Protocol {
26483
26749
  }
26484
26750
  };
26485
26751
 
26752
+ // ../../packages/mcp/src/messages.ts
26753
+ var UPPER = /* @__PURE__ */ new Set(["seo", "gsc", "gbp", "cta", "cmo", "dns", "ai", "api", "pdf", "url", "id"]);
26754
+ var n = (count, word, plural) => count === 1 ? `1 ${word}` : `${count ? count : "no"} ${plural ?? `${word}s`}`;
26755
+ function titleOf(name) {
26756
+ const words = name.split("_").map((w) => UPPER.has(w) ? w.toUpperCase() : w);
26757
+ const head = words[0] ?? "";
26758
+ return [head.charAt(0).toUpperCase() + head.slice(1), ...words.slice(1)].join(" ");
26759
+ }
26760
+ var LIST_KEYS = ["items", "results", "posts", "ideas", "workspaces", "rows"];
26761
+ function items(value) {
26762
+ if (Array.isArray(value)) return value;
26763
+ if (value && typeof value === "object") {
26764
+ for (const k of LIST_KEYS) {
26765
+ const v = value[k];
26766
+ if (Array.isArray(v)) return v;
26767
+ }
26768
+ }
26769
+ return null;
26770
+ }
26771
+ function label(x) {
26772
+ if (x && typeof x === "object") {
26773
+ const o = x;
26774
+ for (const k of ["title", "name", "domain", "label", "what", "text", "id"]) {
26775
+ if (o[k] !== void 0 && o[k] !== null && o[k] !== "") return String(o[k]);
26776
+ }
26777
+ return Object.entries(o).filter(([, v]) => typeof v !== "object").slice(0, 3).map(([k, v]) => `${k} ${v}`).join(", ");
26778
+ }
26779
+ return String(x);
26780
+ }
26781
+ function bodyLines(value) {
26782
+ const list2 = items(value);
26783
+ if (list2) {
26784
+ const out = list2.slice(0, 8).map((x) => `- ${label(x)}`.slice(0, 200));
26785
+ return list2.length > 8 ? [...out, `\u2026and ${list2.length - 8} more`] : out;
26786
+ }
26787
+ if (value && typeof value === "object") {
26788
+ return Object.entries(value).filter(([k, v]) => v !== null && v !== void 0 && v !== "" && typeof v !== "object" && k !== "next").slice(0, 8).map(([k, v]) => `${k.replace(/_/g, " ")}: ${v}`.slice(0, 200));
26789
+ }
26790
+ return value === null || value === void 0 || value === "" ? [] : [String(value).slice(0, 600)];
26791
+ }
26792
+ function said(name, value) {
26793
+ if (value && typeof value === "object" && !Array.isArray(value)) {
26794
+ for (const k of ["say", "headline", "summary", "message"]) {
26795
+ const v = value[k];
26796
+ if (typeof v === "string" && v.trim()) return (v.trim().split("\n")[0] ?? "").slice(0, 240);
26797
+ }
26798
+ }
26799
+ const list2 = items(value);
26800
+ if (list2) {
26801
+ let noun = name.endsWith("_list") ? name.slice(0, -5).split("_").pop() ?? "item" : "item";
26802
+ if (noun.endsWith("s")) noun = noun.slice(0, -1);
26803
+ return list2.length ? `I found ${n(list2.length, noun)}.` : "There's nothing there yet.";
26804
+ }
26805
+ if (typeof value === "string") return (value.trim().split("\n")[0] ?? "").slice(0, 240) || "Done.";
26806
+ return "Here it is.";
26807
+ }
26808
+ var lowerFirst = (s) => /^[A-Za-z][a-z]/.test(s) ? s.charAt(0).toLowerCase() + s.slice(1) : s;
26809
+ function message(name, value, opts = {}) {
26810
+ const title = titleOf(name);
26811
+ if (opts.error) return { say: `That didn't work: ${opts.error}`.slice(0, 300), title, lines: [] };
26812
+ if (opts.preview) return { say: "This needs your yes first; nothing has changed yet.", title, lines: [opts.preview] };
26813
+ if (opts.done) return { say: `Done: ${lowerFirst(opts.done)}`, title, lines: bodyLines(value) };
26814
+ return { say: said(name, value), title, lines: bodyLines(value) };
26815
+ }
26816
+ function messageText(m) {
26817
+ const body = [m.title ? `**${m.title}**` : "", ...m.lines].filter(Boolean).join("\n");
26818
+ return [m.say, body].filter(Boolean).join("\n\n").trim();
26819
+ }
26820
+
26486
26821
  // ../../packages/mcp/src/prompts.ts
26487
26822
  var workspaceArg = {
26488
26823
  name: "workspace",
@@ -26835,14 +27170,14 @@ function toolArgs(args) {
26835
27170
  return out;
26836
27171
  }
26837
27172
  function withIdempotency(api2, approvalId) {
26838
- let n = 0;
27173
+ let n2 = 0;
26839
27174
  return {
26840
27175
  ...api2,
26841
27176
  request: (path2, init = {}) => {
26842
27177
  const method = (init.method ?? "GET").toUpperCase();
26843
27178
  if (method === "GET" || method === "HEAD") return api2.request(path2, init);
26844
27179
  const headers = new Headers(init.headers);
26845
- headers.set("Idempotency-Key", idempotencyKey(approvalId, 0, n++));
27180
+ headers.set("Idempotency-Key", idempotencyKey(approvalId, 0, n2++));
26846
27181
  return api2.request(path2, { ...init, headers });
26847
27182
  }
26848
27183
  };
@@ -26850,13 +27185,18 @@ function withIdempotency(api2, approvalId) {
26850
27185
  var text2 = (value) => [
26851
27186
  { type: "text", text: typeof value === "string" ? value : JSON.stringify(value) }
26852
27187
  ];
26853
- function ok(result, extra) {
27188
+ function ok(result, extra, msg) {
26854
27189
  const value = result ?? { ok: true };
26855
27190
  const structured = value && typeof value === "object" && !Array.isArray(value) ? value : { items: value };
26856
- return { content: text2(value), structuredContent: { ...structured, ...extra } };
27191
+ if (!msg) return { content: text2(value), structuredContent: { ...structured, ...extra } };
27192
+ return {
27193
+ content: [...text2(messageText(msg)), ...text2(value)],
27194
+ structuredContent: { ...structured, ...extra, message: msg }
27195
+ };
26857
27196
  }
26858
- function fail(message) {
26859
- return { content: text2({ error: message }), isError: true };
27197
+ function fail(error2, tool3) {
27198
+ if (!tool3) return { content: text2({ error: error2 }), isError: true };
27199
+ return { content: [...text2(messageText(message(tool3, void 0, { error: error2 }))), ...text2({ error: error2 })], isError: true };
26860
27200
  }
26861
27201
  function errorMessage2(e) {
26862
27202
  if (e instanceof SignInRequiredError) return "The sign-in has expired. Reconnect CMO.so and try again.";
@@ -26996,7 +27336,10 @@ function createMcpServer(deps) {
26996
27336
  );
26997
27337
  const callArgs = approval ? withFreshFiles(t, approval.calls[0]?.args ?? {}, toolArgs(args)) : toolArgs(args);
26998
27338
  const ctx = { api: api2, domain, client: "mcp" };
26999
- if (!isWrite(t)) return ok(await t.run(ctx, callArgs), viewForTool(t.name) ? { workspace: domain } : void 0);
27339
+ if (!isWrite(t)) {
27340
+ const value = await t.run(ctx, callArgs);
27341
+ return ok(value, viewForTool(t.name) ? { workspace: domain } : void 0, message(t.name, value));
27342
+ }
27000
27343
  let standing = null;
27001
27344
  if (!approval && !deps.previewOnly && domain && isStanding(t.name)) {
27002
27345
  const claim2 = await api2.account?.claimStanding({ website: domain, tool: t.name, units: standingUnits(t.name, callArgs) }).catch(() => null);
@@ -27018,14 +27361,19 @@ function createMcpServer(deps) {
27018
27361
  const calls = [{ name: t.name, args: callArgs }];
27019
27362
  const summary2 = t.confirm?.(callArgs, "en") ?? t.description;
27020
27363
  const cost2 = await previewCost(api2, domain, t);
27021
- return ok({
27022
- status: "needs_confirmation",
27023
- preview: cost2 ? `${summary2} ${cost2}` : summary2,
27024
- risk: t.risk,
27025
- workspace: domain,
27026
- confirmation: signer.sign({ calls, principal, domain }),
27027
- next: "Show the preview to the user. If they agree, call this tool again with confirm: true and this confirmation (it runs exactly what was previewed). Nothing has changed yet."
27028
- });
27364
+ const preview = cost2 ? `${summary2} ${cost2}` : summary2;
27365
+ return ok(
27366
+ {
27367
+ status: "needs_confirmation",
27368
+ preview,
27369
+ risk: t.risk,
27370
+ workspace: domain,
27371
+ confirmation: signer.sign({ calls, principal, domain }),
27372
+ next: "Show the preview to the user. If they agree, call this tool again with confirm: true and this confirmation (it runs exactly what was previewed). Nothing has changed yet."
27373
+ },
27374
+ void 0,
27375
+ message(t.name, void 0, { preview })
27376
+ );
27029
27377
  }
27030
27378
  const summary = `${t.confirm?.(callArgs, "en") ?? t.description}${standing ? ` (standing permission #${standing})` : ""}`;
27031
27379
  try {
@@ -27033,14 +27381,14 @@ function createMcpServer(deps) {
27033
27381
  const added = t.name === "website_add" ? result?.domain : null;
27034
27382
  const where = typeof added === "string" && added ? added : domain;
27035
27383
  await deps.audit?.({ domain: where, actions: [{ tool: t.name, summary, ok: true, action_id: approval.id }] }).catch(() => void 0);
27036
- return ok(result);
27384
+ return ok(result, void 0, message(t.name, result, { done: summary }));
27037
27385
  } catch (e) {
27038
27386
  const error2 = errorMessage2(e);
27039
27387
  await deps.audit?.({ domain, actions: [{ tool: t.name, summary, ok: false, error: error2, action_id: approval.id }] }).catch(() => void 0);
27040
- return fail(error2);
27388
+ return fail(error2, t.name);
27041
27389
  }
27042
27390
  } catch (e) {
27043
- return fail(errorMessage2(e));
27391
+ return fail(errorMessage2(e), t.name);
27044
27392
  }
27045
27393
  });
27046
27394
  server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
@@ -27170,8 +27518,8 @@ var ReadBuffer = class {
27170
27518
  function deserializeMessage(line) {
27171
27519
  return JSONRPCMessageSchema.parse(JSON.parse(line));
27172
27520
  }
27173
- function serializeMessage(message) {
27174
- return JSON.stringify(message) + "\n";
27521
+ function serializeMessage(message2) {
27522
+ return JSON.stringify(message2) + "\n";
27175
27523
  }
27176
27524
 
27177
27525
  // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
@@ -27209,11 +27557,11 @@ var StdioServerTransport = class {
27209
27557
  processReadBuffer() {
27210
27558
  while (true) {
27211
27559
  try {
27212
- const message = this._readBuffer.readMessage();
27213
- if (message === null) {
27560
+ const message2 = this._readBuffer.readMessage();
27561
+ if (message2 === null) {
27214
27562
  break;
27215
27563
  }
27216
- this.onmessage?.(message);
27564
+ this.onmessage?.(message2);
27217
27565
  } catch (error2) {
27218
27566
  this.onerror?.(error2);
27219
27567
  }
@@ -27229,9 +27577,9 @@ var StdioServerTransport = class {
27229
27577
  this._readBuffer.clear();
27230
27578
  this.onclose?.();
27231
27579
  }
27232
- send(message) {
27580
+ send(message2) {
27233
27581
  return new Promise((resolve) => {
27234
- const json = serializeMessage(message);
27582
+ const json = serializeMessage(message2);
27235
27583
  if (this._stdout.write(json)) {
27236
27584
  resolve();
27237
27585
  } else {
@@ -27259,8 +27607,8 @@ async function serveStdio(version2) {
27259
27607
  import { randomUUID as randomUUID3 } from "node:crypto";
27260
27608
  import { createInterface } from "node:readline/promises";
27261
27609
  var CliError = class extends Error {
27262
- constructor(message, code = 2) {
27263
- super(message);
27610
+ constructor(message2, code = 2) {
27611
+ super(message2);
27264
27612
  this.code = code;
27265
27613
  }
27266
27614
  };
@@ -27315,14 +27663,14 @@ async function cost(api2, domain, t) {
27315
27663
  }
27316
27664
  function withIdempotency2(api2) {
27317
27665
  const base = randomUUID3();
27318
- let n = 0;
27666
+ let n2 = 0;
27319
27667
  return {
27320
27668
  ...api2,
27321
27669
  request: (path2, init = {}) => {
27322
27670
  const method = (init.method ?? "GET").toUpperCase();
27323
27671
  if (method === "GET" || method === "HEAD") return api2.request(path2, init);
27324
27672
  const headers = new Headers(init.headers);
27325
- headers.set("Idempotency-Key", `${base}-${n++}`);
27673
+ headers.set("Idempotency-Key", `${base}-${n2++}`);
27326
27674
  return api2.request(path2, { ...init, headers });
27327
27675
  }
27328
27676
  };
@@ -27388,9 +27736,9 @@ async function tool2(opts, name, args = {}, human) {
27388
27736
  print(out.result, opts.json, human);
27389
27737
  }
27390
27738
  var ids2 = (list2) => list2.map((x) => {
27391
- const n = Number(x);
27392
- if (!Number.isInteger(n)) throw new CliError(`Not an id: ${x}`);
27393
- return n;
27739
+ const n2 = Number(x);
27740
+ if (!Number.isInteger(n2)) throw new CliError(`Not an id: ${x}`);
27741
+ return n2;
27394
27742
  });
27395
27743
  var program2 = new Command("cmo").description("CMO.so on the command line. Docs: https://ai.cmo.so/hot/mcp").version(VERSION2).option("--json", "machine-readable output").option("-w, --workspace <domain>", "the website to act on").option("-y, --yes", "don't ask before changing things (for scripts and CI)").option("--dry-run", "show what a change would do, without doing it").showHelpAfterError();
27396
27744
  program2.command("login").description("sign in in your browser (device code)").option("--no-browser", "only print the link").action(
@@ -27626,7 +27974,18 @@ social.command("pictures <id> <urls...>").description("your own pictures on a po
27626
27974
  async (opts, id, urls) => tool2(opts, "social_pictures_attach", { id: ids2([id])[0], urls, append: opts.append === true })
27627
27975
  )
27628
27976
  );
27629
- 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(
27977
+ social.command("slides <id> [count]").description("a text carousel of 5-10 slides for a post (a cover, one point a slide, the next step; free)").option("--style <style>", "bold_type (big words, the default) or infographic (lists and numbers)").action(
27978
+ act(
27979
+ async (opts, id, count) => tool2(opts, "social_image_new", {
27980
+ id: ids2([id])[0],
27981
+ count: Number(count ?? 7),
27982
+ ...opts.style ? { style: opts.style } : {}
27983
+ })
27984
+ )
27985
+ );
27986
+ social.command("carousel <id>").description(
27987
+ "a post's carousel: --pdf for LinkedIn, Instagram, Facebook or TikTok; --format / --title for LinkedIn's"
27988
+ ).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: its pages become the pictures (LinkedIn: the PDF)").action(
27630
27989
  act(async (opts, id) => {
27631
27990
  const post = ids2([id])[0];
27632
27991
  if (opts.pdf)
@@ -27718,6 +28077,58 @@ video.command("add <url>").description("posts from a direct link to a video file
27718
28077
  })
27719
28078
  )
27720
28079
  );
28080
+ video.command("post <videoId>").description("posts from a video already in CMO.so (e.g. a ready ad video's video_id), one per channel").option("--channel <names>", "linkedin, facebook, instagram, youtube, tiktok (comma-separated)", "youtube").option("--note <text>", "what the video is about").action(
28081
+ act(
28082
+ async (opts, videoId) => tool2(opts, "social_video_add", {
28083
+ video_id: ids2([videoId])[0],
28084
+ channels: (channelsOpt(opts.channel) ?? ["youtube"]).filter((c) => c !== "google_business"),
28085
+ ...opts.note ? { note: opts.note } : {}
28086
+ })
28087
+ )
28088
+ );
28089
+ var ad = video.command("ad").description("ad videos (beta): plan, change, make (billed), post");
28090
+ ad.command("plan [brief...]").description("a storyboard and what making it costs (one AI generation)").option("--seconds <n>", "15, 20 or 30", "20").option("--landscape", "16:9 instead of vertical 9:16").option("--scenes <n>", "2-5").option("--from-post <id>", "make it from a post's text").option("--reference <youtubeUrl>", "a YouTube ad whose rhythm to follow").action(
28091
+ act(
28092
+ async (opts, brief2) => tool2(opts, "ad_video_plan", {
28093
+ ...brief2?.length ? { brief: brief2.join(" ") } : {},
28094
+ ...opts.fromPost ? { item_id: ids2([opts.fromPost])[0] } : {},
28095
+ seconds: Number(opts.seconds),
28096
+ aspect: opts.landscape ? "16:9" : "9:16",
28097
+ ...opts.scenes ? { scenes: Number(opts.scenes) } : {},
28098
+ ...opts.reference ? { reference_url: opts.reference } : {}
28099
+ })
28100
+ )
28101
+ );
28102
+ ad.command("list").description("this website's ad videos").action(act(async (opts) => tool2(opts, "ad_video_list")));
28103
+ 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(
28105
+ act(
28106
+ async (opts, id) => {
28107
+ const scenes = {};
28108
+ for (const spec of opts.scene ?? []) {
28109
+ const m = /^([a-z0-9]+)=(still|motion|line|caption|seconds):(.*)$/s.exec(spec);
28110
+ if (!m) throw new Error(`--scene ${spec}: use <sceneId>=<field>:<text>, e.g. s2=line:Book today`);
28111
+ const [, sid = "", field = "", text3 = ""] = m;
28112
+ scenes[sid] = { ...scenes[sid] ?? {}, [field]: field === "seconds" ? Number(text3) : text3 };
28113
+ }
28114
+ return tool2(opts, "ad_video_change", {
28115
+ id: ids2([id])[0],
28116
+ ...Object.keys(scenes).length ? { scenes: Object.entries(scenes).map(([sid, f]) => ({ id: sid, ...f })) } : {},
28117
+ ...opts.redo ? { redo_scenes: opts.redo.split(",").map((x) => x.trim()) } : {},
28118
+ ...opts.headline ? { cta_headline: opts.headline } : {},
28119
+ ...opts.sub ? { cta_sub: opts.sub } : {},
28120
+ ...opts.voice ? { voice: opts.voice } : {},
28121
+ ...opts.music ? { music: opts.music } : {}
28122
+ });
28123
+ }
28124
+ )
28125
+ );
28126
+ ad.command("make <id> <usd>").description("make it: costs money; <usd> is the estimate you agreed to (from plan / show)").action(
28127
+ act(
28128
+ async (opts, id, usd) => tool2(opts, "ad_video_make", { id: ids2([id])[0], estimate_usd: Number(usd) })
28129
+ )
28130
+ );
28131
+ 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] })));
27721
28132
  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 })));
27722
28133
  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] })));
27723
28134
  var tiktok = social.command("tiktok").description("your TikTok account (beta): short videos you approve one by one");
@@ -27737,6 +28148,15 @@ tiktok.command("add <url>").description("a TikTok post from a direct link to a v
27737
28148
  })
27738
28149
  )
27739
28150
  );
28151
+ tiktok.command("photo <id>").description("a TikTok photo post from a post's pictures (a set, a text carousel or a PDF's pages)").option("--note <text>", "what to say in the caption").option("--caption <text>", "your own caption (no AI)").action(
28152
+ act(
28153
+ async (opts, id) => tool2(opts, "social_tiktok_photo", {
28154
+ id: ids2([id])[0],
28155
+ ...opts.note ? { note: opts.note } : {},
28156
+ ...opts.caption ? { caption: opts.caption } : {}
28157
+ })
28158
+ )
28159
+ );
27740
28160
  tiktok.command("set <id>").description("a TikTok post's settings, as you choose them (nothing is preset)").option("--privacy <p>", "everyone, friends, followers or only-me (what your account offers)").option("--comment <yes|no>").option("--duet <yes|no>").option("--stitch <yes|no>").option("--promotes <what>", "none, your-brand, branded or both (content disclosure)").option("--ai <yes|no>", "AI-generated content label").action(
27741
28161
  act(
27742
28162
  async (opts, id) => {
@@ -27860,12 +28280,12 @@ feedback.command("send <summary...>", { isDefault: true }).description("send fee
27860
28280
  );
27861
28281
  feedback.command("ls").description("what you sent, its status and the team's reply").action(
27862
28282
  act(async (opts) => {
27863
- const { items } = await api().feedback.mine();
28283
+ const { items: items2 } = await api().feedback.mine();
27864
28284
  print(
27865
- items,
28285
+ items2,
27866
28286
  opts.json,
27867
- () => items.length ? table(
27868
- items.map((f) => ({
28287
+ () => items2.length ? table(
28288
+ items2.map((f) => ({
27869
28289
  sent: f.created_at.slice(0, 10),
27870
28290
  type: f.type,
27871
28291
  status: f.status.replace("_", " ") + (f.unread ? " (new reply)" : ""),