@cmo-so/mcp 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-mcp.mjs +573 -225
  2. package/package.json +1 -1
package/dist/cmo-mcp.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());
@@ -7199,7 +7199,7 @@ var require_dist = __commonJS({
7199
7199
  // package.json
7200
7200
  var package_default = {
7201
7201
  name: "@cmo-so/cli",
7202
- version: "0.9.2",
7202
+ version: "0.10.0",
7203
7203
  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.",
7204
7204
  type: "module",
7205
7205
  license: "MIT",
@@ -7271,14 +7271,14 @@ var OAuthError = class extends Error {
7271
7271
  }
7272
7272
  };
7273
7273
  var SignInRequiredError = class extends Error {
7274
- constructor(message = "Please sign in again.") {
7275
- super(message);
7274
+ constructor(message2 = "Please sign in again.") {
7275
+ super(message2);
7276
7276
  this.name = "SignInRequiredError";
7277
7277
  }
7278
7278
  };
7279
7279
  var ApiError = class extends Error {
7280
- constructor(status, message, requestId, detail = null) {
7281
- super(message);
7280
+ constructor(status, message2, requestId, detail = null) {
7281
+ super(message2);
7282
7282
  this.status = status;
7283
7283
  this.requestId = requestId;
7284
7284
  this.detail = detail;
@@ -7508,10 +7508,10 @@ function createApi({ baseUrl, session: session2, fetch: fetchImpl = globalThis.f
7508
7508
  }),
7509
7509
  models: () => request("/v1/maggie/bridge/models"),
7510
7510
  /** Add a ChatGPT account to the bridge (then connect it). */
7511
- addAccount: (label) => request("/v1/maggie/bridge/accounts", {
7511
+ addAccount: (label2) => request("/v1/maggie/bridge/accounts", {
7512
7512
  method: "POST",
7513
7513
  headers: { "Content-Type": "application/json" },
7514
- body: JSON.stringify({ label: label ?? null })
7514
+ body: JSON.stringify({ label: label2 ?? null })
7515
7515
  }),
7516
7516
  /** Make one of the bridge's ChatGPT accounts the one that answers. */
7517
7517
  chooseAccount: (id) => request(`/v1/maggie/bridge/accounts/${encodeURIComponent(id)}/use`, { method: "POST" }),
@@ -7530,8 +7530,8 @@ function createApi({ baseUrl, session: session2, fetch: fetchImpl = globalThis.f
7530
7530
  /** The user has read the team's latest reply or status change. */
7531
7531
  seen: (id) => request(`/v1/feedback/${encodeURIComponent(id)}/seen`, { method: "POST" }),
7532
7532
  /** One of the user's feedback photos as a data: URL (an <Image> source on web and native). */
7533
- photo: async (id, n) => {
7534
- const { type, bytes } = await requestBytes(`/v1/feedback/${encodeURIComponent(id)}/photos/${n}`);
7533
+ photo: async (id, n2) => {
7534
+ const { type, bytes } = await requestBytes(`/v1/feedback/${encodeURIComponent(id)}/photos/${n2}`);
7535
7535
  return dataUrl(type, bytes);
7536
7536
  }
7537
7537
  },
@@ -7581,8 +7581,8 @@ function errorMessage(data, status) {
7581
7581
  const detail = data?.detail;
7582
7582
  if (typeof detail === "string" && detail) return detail;
7583
7583
  if (detail && typeof detail === "object" && !Array.isArray(detail)) {
7584
- const message = detail.message;
7585
- if (typeof message === "string" && message) return message;
7584
+ const message2 = detail.message;
7585
+ if (typeof message2 === "string" && message2) return message2;
7586
7586
  }
7587
7587
  if (Array.isArray(detail)) {
7588
7588
  const first = detail[0];
@@ -7600,8 +7600,8 @@ function dataUrl(type, bytes) {
7600
7600
  const parts = [];
7601
7601
  let chunk = "";
7602
7602
  for (let i = 0; i < b.length; i += 3) {
7603
- const n = (b[i] ?? 0) << 16 | (b[i + 1] ?? 0) << 8 | (b[i + 2] ?? 0);
7604
- 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) : "=");
7603
+ const n2 = (b[i] ?? 0) << 16 | (b[i + 1] ?? 0) << 8 | (b[i + 2] ?? 0);
7604
+ 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) : "=");
7605
7605
  if (chunk.length >= 8192) {
7606
7606
  parts.push(chunk);
7607
7607
  chunk = "";
@@ -7981,8 +7981,9 @@ function domainApi(api2, domain) {
7981
7981
  * https links to pictures. Send a few photos per call (16 MB request limit), `append` after the first.
7982
7982
  * On LinkedIn a set goes out as a swipeable PDF carousel unless setSocialCarousel says images. */
7983
7983
  setSocialPictures: (id, pictures) => post(`${base}/social/${id}/pictures`, pictures),
7984
- /** A LinkedIn post goes out as this PDF (base64 under ~11 MB, or a public https link; up to 300 pages);
7985
- * its first 20 pages become the post's pictures. */
7984
+ /** A PDF carousel (base64 under ~11 MB, or a public https link; up to 300 pages): its first 20 pages become
7985
+ * the post's pictures; a LinkedIn post (or the idea's LinkedIn post) goes out as the PDF itself. LinkedIn,
7986
+ * Instagram, Facebook and TikTok posts. */
7986
7987
  attachSocialCarouselPdf: (id, pdf) => post(`${base}/social/${id}/carousel/pdf`, pdf),
7987
7988
  /** How a LinkedIn post with a set goes out: "pdf" (swipeable carousel, the default) or "images"; its title. */
7988
7989
  setSocialCarousel: (id, changes) => api2.request(`${base}/social/${id}/carousel`, {
@@ -8132,6 +8133,26 @@ function domainApi(api2, domain) {
8132
8133
  /** A post on one video channel (YouTube by default, or TikTok) from a ready video, waiting in Review; Maggie
8133
8134
  * writes the details (YouTube) or the caption (TikTok) unless write is false. */
8134
8135
  postFromVideo: (videoId, opts = {}) => post(`${base}/social/video/${videoId}/post`, opts),
8136
+ /** A TikTok photo post (a swipeable set; TikTok adds music) from this post's pictures, waiting in Review;
8137
+ * Maggie writes the caption unless `caption` is given or write is false. Beta, like TikTok. */
8138
+ tiktokPhotoPost: (id, opts = {}) => post(`${base}/social/${id}/tiktok-photo`, opts),
8139
+ /** Ad videos (Beta): this website's, with the voices and lengths to choose from. */
8140
+ adVideos: () => api2.request(
8141
+ `${base}/social/ad-videos`
8142
+ ),
8143
+ adVideo: (adId) => api2.request(`${base}/social/ad-videos/${adId}`),
8144
+ /** A storyboard and its cost (one AI generation; nothing billed by the second yet). */
8145
+ planAdVideo: (opts) => post(`${base}/social/ad-videos`, opts),
8146
+ /** Change the storyboard: only what a change touches is made again (`redo`), with the new cost. */
8147
+ changeAdVideo: (adId, changes) => api2.request(`${base}/social/ad-videos/${adId}`, {
8148
+ method: "PATCH",
8149
+ headers: { "Content-Type": "application/json" },
8150
+ body: JSON.stringify(changes)
8151
+ }),
8152
+ /** Make it (billed): send back the estimate the person agreed to. */
8153
+ makeAdVideo: (adId, estimateUsd) => post(`${base}/social/ad-videos/${adId}/make`, { confirm: true, estimate_usd: estimateUsd }),
8154
+ /** Stop one being made (what's made is kept), or remove one. */
8155
+ stopAdVideo: (adId) => api2.request(`${base}/social/ad-videos/${adId}`, { method: "DELETE" }),
8135
8156
  /** Make a LinkedIn, Facebook or Instagram post a video post: it goes out as this video (its picture is dropped). */
8136
8157
  attachVideo: (id, videoId) => post(`${base}/social/${id}/video`, { video_id: videoId }),
8137
8158
  /** Take the video off a post: it's a text post again. */
@@ -8469,8 +8490,8 @@ function cleanSitemap(entry, domain) {
8469
8490
  if (!url) return "The sitemap must be a web address starting with http:// or https://.";
8470
8491
  let owner = SITEMAP_OWNERS.includes(entry.owner) ? entry.owner : "other";
8471
8492
  if (owner === "own" && domain && !onWebsite(url, domain)) owner = "other";
8472
- const label = (entry.label ?? "").trim().slice(0, SITEMAP_LABEL_MAX);
8473
- const out = { ...entry, url, owner, label: label || null, uses: sitemapUses(owner, entry.uses) };
8493
+ const label2 = (entry.label ?? "").trim().slice(0, SITEMAP_LABEL_MAX);
8494
+ const out = { ...entry, url, owner, label: label2 || null, uses: sitemapUses(owner, entry.uses) };
8474
8495
  if (!out.id) delete out.id;
8475
8496
  return out;
8476
8497
  }
@@ -8487,8 +8508,8 @@ function checkSitemaps(list2) {
8487
8508
  function sitemapCheckLine(check) {
8488
8509
  if (!check || check.status === "unchecked") return "Checked when you save";
8489
8510
  if (check.status === "error") return check.error?.trim() || "Couldn't read this sitemap.";
8490
- const n = check.url_count;
8491
- const found = n == null ? "Read" : `Found ${n.toLocaleString("en-GB")} ${n === 1 ? "page" : "pages"}`;
8511
+ const n2 = check.url_count;
8512
+ const found = n2 == null ? "Read" : `Found ${n2.toLocaleString("en-GB")} ${n2 === 1 ? "page" : "pages"}`;
8492
8513
  const at = check.checked_at ? new Date(check.checked_at) : null;
8493
8514
  const when = at && !Number.isNaN(at.getTime()) ? at.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }) : null;
8494
8515
  return when ? `${found} \xB7 checked ${when}` : found;
@@ -8516,13 +8537,13 @@ var named = (title, id) => {
8516
8537
  const s = String(id ?? "").trim();
8517
8538
  return s && s.length <= 8 ? ` #${s}` : "";
8518
8539
  };
8519
- var list = (xs, n = 5) => {
8540
+ var list = (xs, n2 = 5) => {
8520
8541
  const a = (Array.isArray(xs) ? xs : xs == null ? [] : [xs]).map(String);
8521
- return a.length > n ? `${a.slice(0, n).join(", ")} +${a.length - n}` : a.join(", ");
8542
+ return a.length > n2 ? `${a.slice(0, n2).join(", ")} +${a.length - n2}` : a.join(", ");
8522
8543
  };
8523
- var placeEn = (p, items = "posts") => {
8544
+ var placeEn = (p, items2 = "posts") => {
8524
8545
  const t = String(p ?? "").trim().toLowerCase();
8525
- if (t === "end") return `at the end of the queue, after the ${items} already waiting`;
8546
+ if (t === "end") return `at the end of the queue, after the ${items2} already waiting`;
8526
8547
  if (t === "random") return "at a random place in the queue";
8527
8548
  if (/^\d+$/.test(t)) return Number(t) === 0 ? null : `after the first ${t} in the queue`;
8528
8549
  return null;
@@ -8539,6 +8560,12 @@ var keys = (a) => Object.keys(a ?? {}).filter((k) => a[k] !== void 0);
8539
8560
  var videoChannels = (a) => (Array.isArray(a.channels) && a.channels.length ? a.channels : ["youtube"]).map(
8540
8561
  (c) => ({ tiktok: "TikTok", linkedin: "LinkedIn", facebook: "Facebook", instagram: "Instagram" })[String(c)] ?? "YouTube"
8541
8562
  );
8563
+ var videoFrom = (a, lang = "en") => {
8564
+ const name = a.file?.file_name;
8565
+ if (a.file) return name ? q(String(name)) : lang === "en" ? "your video" : "\u4F60\u7684\u5F71\u7247";
8566
+ if (a.video_id) return lang === "en" ? `video #${a.video_id}` : `\u5F71\u7247 #${a.video_id}`;
8567
+ return String(a.url ?? "");
8568
+ };
8542
8569
  var confirmCopy = {
8543
8570
  // --------------------------------------------------------- account / setup
8544
8571
  website_add: {
@@ -8613,13 +8640,13 @@ var confirmCopy = {
8613
8640
  sitemap_upsert: {
8614
8641
  en: (a) => {
8615
8642
  const whose = { own: "your site's ", competitor: "a competitor's ", other: "another site's " }[a.owner];
8616
- const label = a.label ? ` (${q(a.label)})` : "";
8617
- return a.url ? `Save ${whose ?? "the "}sitemap ${a.url}${label}.` : `Change the sitemap${label}: ${keys(a).filter((k) => k !== "id").join(", ")}.`;
8643
+ const label2 = a.label ? ` (${q(a.label)})` : "";
8644
+ return a.url ? `Save ${whose ?? "the "}sitemap ${a.url}${label2}.` : `Change the sitemap${label2}: ${keys(a).filter((k) => k !== "id").join(", ")}.`;
8618
8645
  },
8619
8646
  "zh-Hant": (a) => {
8620
8647
  const whose = { own: "\u81EA\u5DF1\u7DB2\u7AD9\u7684", competitor: "\u7AF6\u722D\u5C0D\u624B\u7684", other: "\u5176\u4ED6\u7DB2\u7AD9\u7684" }[a.owner];
8621
- const label = a.label ? `\uFF08${q(a.label)}\uFF09` : "";
8622
- 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`;
8648
+ const label2 = a.label ? `\uFF08${q(a.label)}\uFF09` : "";
8649
+ 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`;
8623
8650
  }
8624
8651
  },
8625
8652
  sitemap_remove: {
@@ -8777,18 +8804,18 @@ var confirmCopy = {
8777
8804
  },
8778
8805
  social_approve: {
8779
8806
  en: (a) => {
8780
- const n = ids(a);
8807
+ const n2 = ids(a);
8781
8808
  const at = placeEn(a.position);
8782
8809
  if (at)
8783
- 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"}.`;
8784
- 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.`;
8810
+ 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"}.`;
8811
+ 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.`;
8785
8812
  },
8786
8813
  "zh-Hant": (a) => {
8787
- const n = ids(a);
8814
+ const n2 = ids(a);
8788
8815
  const at = placeZh(a.position);
8789
8816
  if (at)
8790
- 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`;
8791
- 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`;
8817
+ 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`;
8818
+ 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`;
8792
8819
  }
8793
8820
  },
8794
8821
  social_publish_now: {
@@ -8813,8 +8840,8 @@ var confirmCopy = {
8813
8840
  "zh-Hant": () => "\u7522\u751F\u9023\u63A5\u4F60 YouTube \u983B\u9053\u7684\u9023\u7D50\u3002"
8814
8841
  },
8815
8842
  social_video_attach: {
8816
- en: (a) => `Make post #${a.id} a video post with ${a.url} (its picture is removed).`,
8817
- "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`
8843
+ en: (a) => `Make post #${a.id} a video post with ${videoFrom(a)} (its picture is removed).`,
8844
+ "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`
8818
8845
  },
8819
8846
  social_video_detach: {
8820
8847
  en: (a) => `Take the video off post #${a.id} (it's a text post again).`,
@@ -8827,11 +8854,11 @@ var confirmCopy = {
8827
8854
  social_video_add: {
8828
8855
  en: (a) => {
8829
8856
  const names = videoChannels(a).join(" and ");
8830
- return `Make a ${names} post from ${a.url}${a.write === false ? "" : ` (Maggie writes it: one AI generation per channel)`}; it waits for your OK.`;
8857
+ return `Make a ${names} post from ${videoFrom(a)}${a.write === false ? "" : ` (Maggie writes it: one AI generation per channel)`}; it waits for your OK.`;
8831
8858
  },
8832
8859
  "zh-Hant": (a) => {
8833
8860
  const names = videoChannels(a).join(" \u548C ");
8834
- 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`;
8861
+ 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`;
8835
8862
  }
8836
8863
  },
8837
8864
  social_tiktok_set: {
@@ -8847,8 +8874,8 @@ var confirmCopy = {
8847
8874
  "zh-Hant": (a) => `\u4FEE\u6539 YouTube \u9810\u8A2D\uFF1A${keys(a).join("\u3001") || "\u7121"}\u3002`
8848
8875
  },
8849
8876
  social_image_new: {
8850
- 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" : ""}.`,
8851
- "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`
8877
+ 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" : ""}.`,
8878
+ "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`
8852
8879
  },
8853
8880
  social_image_stop: {
8854
8881
  en: () => "Stop the picture being made; the post keeps the one it had.",
@@ -8856,17 +8883,37 @@ var confirmCopy = {
8856
8883
  },
8857
8884
  social_pictures_attach: {
8858
8885
  en: (a) => {
8859
- const n = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
8860
- 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.`;
8886
+ const n2 = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
8887
+ 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.`;
8861
8888
  },
8862
8889
  "zh-Hant": (a) => {
8863
- const n = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
8864
- 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`;
8890
+ const n2 = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
8891
+ 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`;
8865
8892
  }
8866
8893
  },
8867
8894
  social_carousel_pdf_attach: {
8868
- 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).`,
8869
- "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`
8895
+ 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).`,
8896
+ "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`
8897
+ },
8898
+ social_tiktok_photo: {
8899
+ 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.`,
8900
+ "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`
8901
+ },
8902
+ ad_video_plan: {
8903
+ 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.`,
8904
+ "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`
8905
+ },
8906
+ ad_video_change: {
8907
+ en: (a) => `Change ad video #${a.id}'s storyboard: ${keys(a).filter((k) => k !== "id").join(", ") || "nothing"}.`,
8908
+ "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`
8909
+ },
8910
+ ad_video_make: {
8911
+ 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.`,
8912
+ "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`
8913
+ },
8914
+ ad_video_stop: {
8915
+ en: (a) => `Stop ad video #${a.id} if it's being made (what's made is kept), or remove it from the list.`,
8916
+ "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`
8870
8917
  },
8871
8918
  social_carousel_set: {
8872
8919
  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)}` : ""}.`,
@@ -9090,17 +9137,17 @@ function confirmText(tool2, args, lang = "en") {
9090
9137
  function costText(cost, lang = "en") {
9091
9138
  const parts = [];
9092
9139
  if (cost.aiGenerations) {
9093
- const n = cost.aiGenerations;
9140
+ const n2 = cost.aiGenerations;
9094
9141
  const left = cost.aiLeft;
9095
9142
  parts.push(
9096
- 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)`}.`
9143
+ 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)`}.`
9097
9144
  );
9098
9145
  }
9099
9146
  if (cost.credits) {
9100
- const n = cost.credits;
9147
+ const n2 = cost.credits;
9101
9148
  const left = cost.creditsLeft;
9102
9149
  parts.push(
9103
- 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)`}.`
9150
+ 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)`}.`
9104
9151
  );
9105
9152
  }
9106
9153
  return parts.join(" ");
@@ -9112,13 +9159,13 @@ function ws(ctx) {
9112
9159
  if (!ctx.domain) throw new Error("No website is selected yet. Add one first.");
9113
9160
  return domainApi(ctx.api, ctx.domain);
9114
9161
  }
9115
- var clip = (text3, n = 280) => typeof text3 === "string" ? text3.length > n ? `${text3.slice(0, n)}\u2026` : text3 : text3 ?? null;
9162
+ var clip = (text3, n2 = 280) => typeof text3 === "string" ? text3.length > n2 ? `${text3.slice(0, n2)}\u2026` : text3 : text3 ?? null;
9116
9163
  var plain = (html) => String(html ?? "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9117
9164
  var untrusted = (value) => ({ untrusted: value });
9118
9165
  function upsert(list2, item2, key) {
9119
9166
  const k = (key(item2) ?? "").trim().toLowerCase();
9120
9167
  const i = list2.findIndex((x) => (key(x) ?? "").trim().toLowerCase() === k);
9121
- return i >= 0 ? list2.map((x, n) => n === i ? { ...x, ...item2 } : x) : [...list2, item2];
9168
+ return i >= 0 ? list2.map((x, n2) => n2 === i ? { ...x, ...item2 } : x) : [...list2, item2];
9122
9169
  }
9123
9170
  function remove(list2, name, key) {
9124
9171
  const k = name.trim().toLowerCase();
@@ -9127,8 +9174,8 @@ function remove(list2, name, key) {
9127
9174
  return out;
9128
9175
  }
9129
9176
  var int = (v, min, max, fallback) => {
9130
- const n = Math.trunc(Number(v));
9131
- return Number.isFinite(n) ? Math.min(max, Math.max(min, n)) : fallback;
9177
+ const n2 = Math.trunc(Number(v));
9178
+ return Number.isFinite(n2) ? Math.min(max, Math.max(min, n2)) : fallback;
9132
9179
  };
9133
9180
  var positionParam = (what, fallback) => ({
9134
9181
  type: "string",
@@ -9297,12 +9344,12 @@ var accountTools = [
9297
9344
  risk: "R0",
9298
9345
  run: async (ctx, { platform }) => {
9299
9346
  const p = platform && CONNECT_FORMS.includes(platform) ? platform : "";
9300
- const label = p ? `Connect ${CONNECT_NAMES[p]}` : "Connect your website";
9347
+ const label2 = p ? `Connect ${CONNECT_NAMES[p]}` : "Connect your website";
9301
9348
  const link = `https://app.cmo.so/posts?connect=${p || "wordpress"}${ctx.domain ? `&site=${encodeURIComponent(ctx.domain)}` : ""}`;
9302
9349
  if (ctx.client === "chat")
9303
9350
  return {
9304
- card: { type: "connect", platform: p || null, label },
9305
- shown: `A "${label}" button now shows under your message. The user fills in the form there; say so in one line.`
9351
+ card: { type: "connect", platform: p || null, label: label2 },
9352
+ shown: `A "${label2}" button now shows under your message. The user fills in the form there; say so in one line.`
9306
9353
  };
9307
9354
  return { link, shown: "Give the user this link: it opens the form in the cmo.so app (web or phone)." };
9308
9355
  }
@@ -10005,10 +10052,10 @@ var brandTools = [
10005
10052
  need: write2,
10006
10053
  risk: "W1",
10007
10054
  run: async (ctx, { count }) => {
10008
- const n = Math.round(Number(count));
10009
- if (!Number.isFinite(n) || n < 0 || n > 10) throw new Error("count must be 0 to 10.");
10010
- const out = await ws(ctx).updateBrand({ internal_links: { count: n } });
10011
- return { internal_links: out.internal_links ?? { count: n } };
10055
+ const n2 = Math.round(Number(count));
10056
+ if (!Number.isFinite(n2) || n2 < 0 || n2 > 10) throw new Error("count must be 0 to 10.");
10057
+ const out = await ws(ctx).updateBrand({ internal_links: { count: n2 } });
10058
+ return { internal_links: out.internal_links ?? { count: n2 } };
10012
10059
  }
10013
10060
  }),
10014
10061
  tool({
@@ -10029,8 +10076,8 @@ var brandTools = [
10029
10076
  risk: "W1",
10030
10077
  run: async (ctx, { use_in_posts, ...item2 }) => {
10031
10078
  const t = (await ws(ctx).brand()).testimonials ?? { enabled: false, items: [] };
10032
- const items = upsert(t.items, item2, (i) => i.name);
10033
- return (await ws(ctx).updateBrand({ testimonials: { enabled: use_in_posts ?? t.enabled, items } })).testimonials;
10079
+ const items2 = upsert(t.items, item2, (i) => i.name);
10080
+ return (await ws(ctx).updateBrand({ testimonials: { enabled: use_in_posts ?? t.enabled, items: items2 } })).testimonials;
10034
10081
  }
10035
10082
  }),
10036
10083
  tool({
@@ -10048,8 +10095,8 @@ var brandTools = [
10048
10095
  risk: "W1",
10049
10096
  run: async (ctx, { name, use_in_posts }) => {
10050
10097
  const t = (await ws(ctx).brand()).testimonials ?? { enabled: false, items: [] };
10051
- const items = name ? remove(t.items, name, (i) => i.name) : t.items;
10052
- return (await ws(ctx).updateBrand({ testimonials: { enabled: use_in_posts ?? t.enabled, items } })).testimonials;
10098
+ const items2 = name ? remove(t.items, name, (i) => i.name) : t.items;
10099
+ return (await ws(ctx).updateBrand({ testimonials: { enabled: use_in_posts ?? t.enabled, items: items2 } })).testimonials;
10053
10100
  }
10054
10101
  }),
10055
10102
  tool({
@@ -10251,7 +10298,7 @@ var brandTools = [
10251
10298
  );
10252
10299
  if (typeof entry === "string") throw new Error(entry);
10253
10300
  if (old && sitemapUrl(old.url) !== entry.url) delete entry.check;
10254
- const next = old ? list2.map((x, n) => n === i ? entry : x) : [...list2, entry];
10301
+ const next = old ? list2.map((x, n2) => n2 === i ? entry : x) : [...list2, entry];
10255
10302
  const problem = checkSitemaps(next);
10256
10303
  if (problem) throw new Error(problem);
10257
10304
  const saved = (await ws(ctx).updateBrand({ sitemaps: next })).sitemaps ?? next;
@@ -10275,7 +10322,7 @@ var brandTools = [
10275
10322
  const list2 = (await ws(ctx).brand()).sitemaps ?? [];
10276
10323
  const i = findSitemap(list2, id, url);
10277
10324
  if (i < 0) throw new Error(`No sitemap ${id ?? url} was found. Use sitemap_list.`);
10278
- const saved = (await ws(ctx).updateBrand({ sitemaps: list2.filter((_, n) => n !== i) })).sitemaps ?? [];
10325
+ const saved = (await ws(ctx).updateBrand({ sitemaps: list2.filter((_, n2) => n2 !== i) })).sitemaps ?? [];
10279
10326
  return { removed: list2[i]?.url, sitemaps: saved.map((s) => s.url) };
10280
10327
  }
10281
10328
  }),
@@ -10434,7 +10481,7 @@ function sitemapResult(s) {
10434
10481
  // ../../packages/tools/src/feedback.ts
10435
10482
  var account = { resource: "account", level: "read" };
10436
10483
  var TYPES = ["bug", "quality", "idea", "praise", "other"];
10437
- var text = (v, n) => String(v ?? "").trim().slice(0, n);
10484
+ var text = (v, n2) => String(v ?? "").trim().slice(0, n2);
10438
10485
  var feedbackTools = [
10439
10486
  tool({
10440
10487
  name: "feedback_send",
@@ -11626,6 +11673,36 @@ var CHANNEL_KEYS = ["linkedin", "facebook", "instagram", "google_business"];
11626
11673
  var ALL_CHANNEL_KEYS = [...CHANNEL_KEYS, "youtube", "tiktok"];
11627
11674
  var VIDEO_KEYS = ["linkedin", "facebook", "instagram", "youtube", "tiktok"];
11628
11675
  var TIKTOK_PRIVACY = ["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "FOLLOWER_OF_CREATOR", "SELF_ONLY"];
11676
+ var SLIDES = [5, 6, 7, 8, 9, 10];
11677
+ var adVideo = (v) => ({
11678
+ id: v.id,
11679
+ status: v.status,
11680
+ step: v.step,
11681
+ aspect: v.aspect,
11682
+ plan: v.plan ? {
11683
+ title: v.plan.title,
11684
+ style: v.plan.style,
11685
+ voice: v.plan.voice,
11686
+ music: v.plan.music,
11687
+ seconds: v.plan.seconds,
11688
+ end_card: v.plan.cta,
11689
+ scenes: v.plan.scenes.map((s) => ({
11690
+ id: s.id,
11691
+ seconds: s.seconds,
11692
+ picture: s.still,
11693
+ motion: s.motion,
11694
+ line: s.line,
11695
+ caption: s.caption,
11696
+ picture_url: s.still_url ?? null
11697
+ }))
11698
+ } : null,
11699
+ estimate: v.estimate,
11700
+ cost_usd: v.cost_usd,
11701
+ video_id: v.video_id,
11702
+ video: v.video ? { status: v.video.status, preview_url: v.video.preview_url, poster_url: v.video.poster_url } : null,
11703
+ error: v.error,
11704
+ 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." : ""
11705
+ });
11629
11706
  var channelsParam = {
11630
11707
  type: "array",
11631
11708
  items: { type: "string", enum: CHANNEL_KEYS },
@@ -11642,6 +11719,11 @@ var FILE_SCHEMA = {
11642
11719
  required: ["download_url", "file_id"]
11643
11720
  };
11644
11721
  var fileLinks = (value) => (Array.isArray(value) ? value : value ? [value] : []).map((f) => String(f?.download_url ?? "").trim()).filter(Boolean);
11722
+ var videoLink = (a) => {
11723
+ const link = fileLinks(a.file)[0] ?? String(a.url ?? "").trim();
11724
+ if (!link.startsWith("https://")) throw new Error("Give the video (a file from this chat, or a direct https link).");
11725
+ return link;
11726
+ };
11645
11727
  var queuedAt = positionParam("the posts", "Leave out for the front (the next posting times).");
11646
11728
  var channelsOf = (value) => {
11647
11729
  const list2 = Array.isArray(value) ? value.map((v) => String(v).toLowerCase()).filter((v) => CHANNEL_KEYS.includes(v)) : [];
@@ -11754,12 +11836,12 @@ var socialTools = [
11754
11836
  const at = positionOf(position);
11755
11837
  const api2 = ws(ctx);
11756
11838
  const results = [];
11757
- for (const n of list2) {
11839
+ for (const n2 of list2) {
11758
11840
  try {
11759
- await api2.approveSocial(n, at);
11760
- results.push({ id: n, ok: true });
11841
+ await api2.approveSocial(n2, at);
11842
+ results.push({ id: n2, ok: true });
11761
11843
  } catch (e) {
11762
- results.push({ id: n, ok: false, error: e instanceof Error ? e.message : String(e) });
11844
+ results.push({ id: n2, ok: false, error: e instanceof Error ? e.message : String(e) });
11763
11845
  }
11764
11846
  }
11765
11847
  return { approved: results.filter((r) => r.ok).length, results };
@@ -11774,9 +11856,9 @@ var socialTools = [
11774
11856
  risk: "W3",
11775
11857
  gates: ["VER"],
11776
11858
  run: async (ctx, { id }) => {
11777
- const n = Number(id);
11778
- if (!Number.isInteger(n) || n <= 0) throw new Error("Which post?");
11779
- const post = await ws(ctx).publishSocialNow(n, { background: true });
11859
+ const n2 = Number(id);
11860
+ if (!Number.isInteger(n2) || n2 <= 0) throw new Error("Which post?");
11861
+ const post = await ws(ctx).publishSocialNow(n2, { background: true });
11780
11862
  return post.status === "published" ? post : { ...post, note: "Posting now. Check social_post_get in a minute for its link." };
11781
11863
  }
11782
11864
  }),
@@ -11797,11 +11879,11 @@ var socialTools = [
11797
11879
  risk: "W3",
11798
11880
  gates: ["VER"],
11799
11881
  run: async (ctx, { id, social_copy, repost }) => {
11800
- const n = Number(id);
11801
- if (!Number.isInteger(n) || n <= 0) throw new Error("Which post?");
11882
+ const n2 = Number(id);
11883
+ if (!Number.isInteger(n2) || n2 <= 0) throw new Error("Which post?");
11802
11884
  const copy = typeof social_copy === "string" ? social_copy.trim() : "";
11803
11885
  if (!copy && repost !== true) throw new Error("Give the new text (social_copy), or repost: true.");
11804
- return ws(ctx).republishSocial(n, copy || void 0, repost === true);
11886
+ return ws(ctx).republishSocial(n2, copy || void 0, repost === true);
11805
11887
  }
11806
11888
  }),
11807
11889
  tool({
@@ -11813,9 +11895,9 @@ var socialTools = [
11813
11895
  risk: "W3",
11814
11896
  gates: ["VER"],
11815
11897
  run: async (ctx, { id }) => {
11816
- const n = Number(id);
11817
- if (!Number.isInteger(n) || n <= 0) throw new Error("Which post?");
11818
- return ws(ctx).deletePostedSocial(n);
11898
+ const n2 = Number(id);
11899
+ if (!Number.isInteger(n2) || n2 <= 0) throw new Error("Which post?");
11900
+ return ws(ctx).deletePostedSocial(n2);
11819
11901
  }
11820
11902
  }),
11821
11903
  tool({
@@ -11840,7 +11922,7 @@ var socialTools = [
11840
11922
  }),
11841
11923
  tool({
11842
11924
  name: "social_image_new",
11843
- 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.",
11925
+ 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).",
11844
11926
  parameters: {
11845
11927
  type: "object",
11846
11928
  properties: {
@@ -11857,7 +11939,7 @@ var socialTools = [
11857
11939
  },
11858
11940
  count: {
11859
11941
  type: "integer",
11860
- description: "1 (default) or 4: a set of 4 pictures telling one story, a line of words on each"
11942
+ 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)"
11861
11943
  },
11862
11944
  text_effect: {
11863
11945
  type: "string",
@@ -11879,7 +11961,7 @@ var socialTools = [
11879
11961
  replace: replace === true,
11880
11962
  ...typeof style === "string" && style.trim() && mode !== "edit" ? { style: style.trim() } : {},
11881
11963
  ...mode === "edit" ? { mode: "edit" } : {},
11882
- ...Number(count) === 4 && mode !== "edit" ? { count: 4 } : {},
11964
+ ...mode !== "edit" && (Number(count) === 4 || SLIDES.includes(Number(count))) ? { count: Number(count) } : {},
11883
11965
  ...typeof text_effect === "string" && text_effect.trim() ? { textEffect: text_effect.trim() } : {},
11884
11966
  ...typeof instruction === "string" && instruction.trim() ? { instruction: instruction.trim().slice(0, 300) } : {}
11885
11967
  });
@@ -12105,11 +12187,14 @@ var socialTools = [
12105
12187
  }),
12106
12188
  tool({
12107
12189
  name: "social_carousel_pdf_attach",
12108
- 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.",
12190
+ 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.",
12109
12191
  parameters: {
12110
12192
  type: "object",
12111
12193
  properties: {
12112
- id: { type: "integer", description: "A LinkedIn post (from social_list), not gone out yet" },
12194
+ id: {
12195
+ type: "integer",
12196
+ description: "A LinkedIn, Instagram, Facebook or TikTok post (from social_list), not gone out yet"
12197
+ },
12113
12198
  file: { ...FILE_SCHEMA, description: "The PDF from this chat" },
12114
12199
  url: { type: "string", description: "Public https link to the PDF file" },
12115
12200
  title: {
@@ -12123,7 +12208,7 @@ var socialTools = [
12123
12208
  group: "social",
12124
12209
  need: write5,
12125
12210
  risk: "W1",
12126
- gates: ["VER", "LI"],
12211
+ gates: ["VER"],
12127
12212
  run: async (ctx, { id, url, file, title }) => {
12128
12213
  const link = fileLinks(file)[0] ?? String(url ?? "").trim();
12129
12214
  if (!link.startsWith("https://"))
@@ -12152,7 +12237,7 @@ var socialTools = [
12152
12237
  id: post.id,
12153
12238
  channel: post.provider ?? null,
12154
12239
  url: `https://app.cmo.so/social?picture=${post.id}${site}`,
12155
- 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)."
12240
+ 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)."
12156
12241
  };
12157
12242
  }
12158
12243
  }),
@@ -12850,22 +12935,24 @@ var socialTools = [
12850
12935
  }),
12851
12936
  tool({
12852
12937
  name: "social_video_attach",
12853
- 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.",
12938
+ 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.",
12854
12939
  parameters: {
12855
12940
  type: "object",
12856
12941
  properties: {
12857
12942
  id: { type: "integer", description: "The post (from social_list)" },
12943
+ file: { ...FILE_SCHEMA, description: "The video from this chat (MP4 or MOV)" },
12858
12944
  url: { type: "string", description: "Direct https link to the video file" }
12859
12945
  },
12860
- required: ["id", "url"]
12946
+ required: ["id"]
12861
12947
  },
12948
+ fileParams: ["file"],
12862
12949
  group: "social",
12863
12950
  need: write5,
12864
12951
  risk: "W1",
12865
12952
  gates: ["VER"],
12866
12953
  run: async (ctx, a) => {
12867
12954
  const d = ws(ctx);
12868
- let v = await d.videoFromLink(String(a.url));
12955
+ let v = await d.videoFromLink(videoLink(a));
12869
12956
  const until = Date.now() + 15e4;
12870
12957
  while ((v.status === "checking" || v.status === "uploading") && Date.now() < until) {
12871
12958
  await new Promise((r) => setTimeout(r, 3e3));
@@ -12941,11 +13028,13 @@ var socialTools = [
12941
13028
  }),
12942
13029
  tool({
12943
13030
  name: "social_video_add",
12944
- 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.",
13031
+ 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.",
12945
13032
  parameters: {
12946
13033
  type: "object",
12947
13034
  properties: {
13035
+ file: { ...FILE_SCHEMA, description: "The video from this chat (MP4 or MOV)" },
12948
13036
  url: { type: "string", description: "Direct https link to the video file" },
13037
+ video_id: { type: "integer", description: "A video already in CMO.so (an ad video's video_id)" },
12949
13038
  channels: {
12950
13039
  type: "array",
12951
13040
  items: { type: "string", enum: [...VIDEO_KEYS] },
@@ -12957,9 +13046,9 @@ var socialTools = [
12957
13046
  start: { type: "number", description: "Start of the part to use, seconds" },
12958
13047
  end: { type: "number", description: "End of the part to use, seconds" },
12959
13048
  vertical: { type: "string", enum: ["left", "middle", "right"] }
12960
- },
12961
- required: ["url"]
13049
+ }
12962
13050
  },
13051
+ fileParams: ["file"],
12963
13052
  group: "social",
12964
13053
  need: write5,
12965
13054
  risk: "W2",
@@ -12971,7 +13060,7 @@ var socialTools = [
12971
13060
  ...a.end != null ? { end: Number(a.end) } : {},
12972
13061
  ...a.vertical ? { aspect: "9:16", crop_x: a.vertical === "left" ? 0 : a.vertical === "right" ? 1 : 0.5 } : {}
12973
13062
  };
12974
- let v = await d.videoFromLink(String(a.url), Object.keys(edit).length ? edit : void 0);
13063
+ let v = a.video_id ? await d.video(Number(a.video_id)) : await d.videoFromLink(videoLink(a), Object.keys(edit).length ? edit : void 0);
12975
13064
  const until = Date.now() + 15e4;
12976
13065
  while ((v.status === "checking" || v.status === "uploading") && Date.now() < until) {
12977
13066
  await new Promise((r) => setTimeout(r, 3e3));
@@ -13004,6 +13093,183 @@ var socialTools = [
13004
13093
  };
13005
13094
  }
13006
13095
  }),
13096
+ tool({
13097
+ name: "social_tiktok_photo",
13098
+ 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.",
13099
+ parameters: {
13100
+ type: "object",
13101
+ properties: {
13102
+ id: { type: "integer", description: "A post with pictures (from social_list), not a video post" },
13103
+ note: { type: "string", description: "What to say in the caption, in the user's words" },
13104
+ caption: { type: "string", description: "The caption itself, when the user wrote it (no AI)" }
13105
+ },
13106
+ required: ["id"]
13107
+ },
13108
+ group: "social",
13109
+ need: write5,
13110
+ risk: "W2",
13111
+ gates: ["VER", "LLM"],
13112
+ run: async (ctx, { id, note, caption }) => {
13113
+ const out = await ws(ctx).tiktokPhotoPost(Number(id), {
13114
+ ...note ? { note: String(note).slice(0, 1e3) } : {},
13115
+ ...typeof caption === "string" && caption.trim() ? { caption: caption.trim(), write: false } : {}
13116
+ });
13117
+ return {
13118
+ post: item(out),
13119
+ 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."
13120
+ };
13121
+ }
13122
+ }),
13123
+ tool({
13124
+ name: "ad_video_plan",
13125
+ 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.",
13126
+ parameters: {
13127
+ type: "object",
13128
+ properties: {
13129
+ brief: { type: "string", description: "What the ad should do, who it's for, the offer (user's words)" },
13130
+ item_id: { type: "integer", description: "A post (from social_list) to make the ad from" },
13131
+ aspect: { type: "string", enum: ["9:16", "16:9"] },
13132
+ seconds: { type: "integer", description: "15, 20 or 30" },
13133
+ scenes: { type: "integer", description: "2-5 (default 3, or 4 for 30 seconds)" },
13134
+ reference_url: { type: "string", description: "A public YouTube ad to follow the rhythm of" }
13135
+ }
13136
+ },
13137
+ group: "social",
13138
+ need: write5,
13139
+ risk: "W2",
13140
+ gates: ["VER", "LLM"],
13141
+ run: async (ctx, a) => {
13142
+ const out = await ws(ctx).planAdVideo({
13143
+ ...a.brief ? { brief: String(a.brief).slice(0, 3e3) } : {},
13144
+ ...a.item_id ? { item_id: Number(a.item_id) } : {},
13145
+ ...a.aspect ? { aspect: a.aspect } : {},
13146
+ ...a.seconds ? { seconds: Number(a.seconds) } : {},
13147
+ ...a.scenes ? { scenes: Number(a.scenes) } : {},
13148
+ ...a.reference_url ? { reference_url: String(a.reference_url).trim() } : {}
13149
+ });
13150
+ return adVideo(out);
13151
+ }
13152
+ }),
13153
+ tool({
13154
+ name: "ad_video_list",
13155
+ 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.",
13156
+ parameters: none,
13157
+ group: "social",
13158
+ need: read5,
13159
+ risk: "R0",
13160
+ run: async (ctx) => {
13161
+ const out = await ws(ctx).adVideos();
13162
+ return {
13163
+ ad_videos: out.ad_videos.map((v) => ({
13164
+ id: v.id,
13165
+ status: v.status,
13166
+ step: v.step,
13167
+ title: v.plan?.title ?? null,
13168
+ aspect: v.aspect,
13169
+ cost_usd: v.cost_usd,
13170
+ video_id: v.video_id,
13171
+ video_status: v.video?.status ?? null,
13172
+ error: v.error
13173
+ })),
13174
+ voices: out.voices
13175
+ };
13176
+ }
13177
+ }),
13178
+ tool({
13179
+ name: "ad_video_get",
13180
+ 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.",
13181
+ parameters: {
13182
+ type: "object",
13183
+ properties: { id: { type: "integer", description: "The ad video (from ad_video_list)" } },
13184
+ required: ["id"]
13185
+ },
13186
+ group: "social",
13187
+ need: read5,
13188
+ risk: "R0",
13189
+ run: async (ctx, { id }) => adVideo(await ws(ctx).adVideo(Number(id)))
13190
+ }),
13191
+ tool({
13192
+ name: "ad_video_change",
13193
+ 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.",
13194
+ parameters: {
13195
+ type: "object",
13196
+ properties: {
13197
+ id: { type: "integer", description: "The ad video" },
13198
+ scenes: {
13199
+ type: "array",
13200
+ items: {
13201
+ type: "object",
13202
+ properties: {
13203
+ id: { type: "string", description: "Scene id, e.g. s2" },
13204
+ still: { type: "string" },
13205
+ motion: { type: "string" },
13206
+ line: { type: "string" },
13207
+ caption: { type: "string" },
13208
+ seconds: { type: "number" }
13209
+ },
13210
+ required: ["id"]
13211
+ }
13212
+ },
13213
+ redo_scenes: { type: "array", items: { type: "string" } },
13214
+ cta_headline: { type: "string" },
13215
+ cta_sub: { type: "string" },
13216
+ voice: { type: "string", description: "A voice name, or none" },
13217
+ music: { type: "string", description: "The music in words, or none" },
13218
+ title: { type: "string" }
13219
+ },
13220
+ required: ["id"]
13221
+ },
13222
+ group: "social",
13223
+ need: write5,
13224
+ risk: "W1",
13225
+ gates: ["VER"],
13226
+ run: async (ctx, a) => {
13227
+ const cta = {
13228
+ ...a.cta_headline != null ? { headline: a.cta_headline } : {},
13229
+ ...a.cta_sub != null ? { sub: a.cta_sub } : {}
13230
+ };
13231
+ const out = await ws(ctx).changeAdVideo(Number(a.id), {
13232
+ ...a.scenes?.length ? { scenes: a.scenes } : {},
13233
+ ...a.redo_scenes?.length ? { redo_scenes: a.redo_scenes } : {},
13234
+ ...Object.keys(cta).length ? { cta } : {},
13235
+ ...a.voice != null ? { voice: /^none$/i.test(a.voice) ? false : a.voice } : {},
13236
+ ...a.music != null ? { music: /^none$/i.test(a.music) ? "" : a.music } : {},
13237
+ ...a.title ? { title: a.title } : {}
13238
+ });
13239
+ return { ...adVideo(out), redo: out.redo ?? [] };
13240
+ }
13241
+ }),
13242
+ tool({
13243
+ name: "ad_video_make",
13244
+ description: "Make the ad video (beta): its pictures (on the user's own ChatGPT when their Maggie Bridge has images on, else Gemini), the moving clips (Gemini, billed per second), the voice, the music, the words and the end card, cut into one video, in the background (several minutes; follow it with ad_video_get). It COSTS MONEY: estimate_usd must be the estimate.usd from ad_video_plan / ad_video_get / ad_video_change that the user agreed to; if the cost changed it is refused with the new one. Each website has a daily limit. Making it again after a change or a failure only makes what's missing. When ready, post it with social_video_add and its video_id. Preview first.",
13245
+ parameters: {
13246
+ type: "object",
13247
+ properties: {
13248
+ id: { type: "integer", description: "The ad video" },
13249
+ estimate_usd: { type: "number", description: "The cost the user agreed to (estimate.usd)" }
13250
+ },
13251
+ required: ["id", "estimate_usd"]
13252
+ },
13253
+ group: "social",
13254
+ need: write5,
13255
+ risk: "W3",
13256
+ gates: ["VER"],
13257
+ run: async (ctx, { id, estimate_usd }) => adVideo(await ws(ctx).makeAdVideo(Number(id), Number(estimate_usd)))
13258
+ }),
13259
+ tool({
13260
+ name: "ad_video_stop",
13261
+ 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.",
13262
+ parameters: {
13263
+ type: "object",
13264
+ properties: { id: { type: "integer", description: "The ad video" } },
13265
+ required: ["id"]
13266
+ },
13267
+ group: "social",
13268
+ need: write5,
13269
+ risk: "W1",
13270
+ gates: ["VER"],
13271
+ run: async (ctx, { id }) => ws(ctx).stopAdVideo(Number(id))
13272
+ }),
13007
13273
  tool({
13008
13274
  name: "social_youtube_set",
13009
13275
  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.",
@@ -13893,8 +14159,8 @@ function prefixIssues(path, issues) {
13893
14159
  return iss;
13894
14160
  });
13895
14161
  }
13896
- function unwrapMessage(message) {
13897
- return typeof message === "string" ? message : message?.message;
14162
+ function unwrapMessage(message2) {
14163
+ return typeof message2 === "string" ? message2 : message2?.message;
13898
14164
  }
13899
14165
  function sizeCaps(defChecks) {
13900
14166
  const caps = [];
@@ -13924,7 +14190,7 @@ function finalizeIssue(iss, ctx, config2) {
13924
14190
  iss.schema = iss.inst;
13925
14191
  }
13926
14192
  const schemaError = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : void 0;
13927
- 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";
14193
+ 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";
13928
14194
  const full = {};
13929
14195
  for (const k of Object.keys(iss)) {
13930
14196
  if (k === "inst" || k === "schema" || k === "continue" || k === "input" || k === "__proto__")
@@ -13932,7 +14198,7 @@ function finalizeIssue(iss, ctx, config2) {
13932
14198
  full[k] = iss[k];
13933
14199
  }
13934
14200
  full.path ?? (full.path = []);
13935
- full.message = message;
14201
+ full.message = message2;
13936
14202
  if (ctx?.reportInput) {
13937
14203
  full.input = iss.input;
13938
14204
  }
@@ -15548,10 +15814,10 @@ function capSize(inst, size) {
15548
15814
  const cap = Math.min(...caps.map((c) => c[0]));
15549
15815
  const parse3 = inst._zod.parse;
15550
15816
  inst._zod.parse = (payload, ctx) => {
15551
- const n = size(payload.value);
15552
- if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
15817
+ const n2 = size(payload.value);
15818
+ if (n2 > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
15553
15819
  for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
15554
- if (n > max && ch._zod.def.when(payload)) {
15820
+ if (n2 > max && ch._zod.def.when(payload)) {
15555
15821
  const start = payload.issues.length;
15556
15822
  ch._zod.check(payload);
15557
15823
  payload.issues[start].continue = false;
@@ -17647,12 +17913,12 @@ function initializeContext(params) {
17647
17913
  external: params?.external ?? void 0
17648
17914
  };
17649
17915
  }
17650
- function handleUnrepresentable(schema, ctx, json, params, message) {
17651
- const result = typeof ctx.unrepresentable === "function" ? ctx.unrepresentable({ zodSchema: schema, path: params.path, message }) : ctx.unrepresentable;
17916
+ function handleUnrepresentable(schema, ctx, json, params, message2) {
17917
+ const result = typeof ctx.unrepresentable === "function" ? ctx.unrepresentable({ zodSchema: schema, path: params.path, message: message2 }) : ctx.unrepresentable;
17652
17918
  if (result === "any")
17653
17919
  return false;
17654
17920
  if (result === void 0 || result === "throw")
17655
- throw new Error(message);
17921
+ throw new Error(message2);
17656
17922
  Object.assign(json, result);
17657
17923
  return true;
17658
17924
  }
@@ -19345,17 +19611,17 @@ var ZodArray = /* @__PURE__ */ $constructor("ZodArray", (inst, def) => {
19345
19611
  inst._zod.processJSONSchema = (ctx, json, params) => arrayProcessor(inst, ctx, json, params);
19346
19612
  inst.element = def.element;
19347
19613
  }, {
19348
- min(n, params) {
19349
- return this.check(_minLength(n, params));
19614
+ min(n2, params) {
19615
+ return this.check(_minLength(n2, params));
19350
19616
  },
19351
19617
  nonempty(params) {
19352
19618
  return this.check(_minLength(1, params));
19353
19619
  },
19354
- max(n, params) {
19355
- return this.check(_maxLength(n, params));
19620
+ max(n2, params) {
19621
+ return this.check(_maxLength(n2, params));
19356
19622
  },
19357
- length(n, params) {
19358
- return this.check(_length(n, params));
19623
+ length(n2, params) {
19624
+ return this.check(_length(n2, params));
19359
19625
  },
19360
19626
  unwrap() {
19361
19627
  return this.element;
@@ -21259,8 +21525,8 @@ var ServerResultSchema = union([
21259
21525
  CreateTaskResultSchema
21260
21526
  ]);
21261
21527
  var McpError = class _McpError extends Error {
21262
- constructor(code, message, data) {
21263
- super(`MCP error ${code}: ${message}`);
21528
+ constructor(code, message2, data) {
21529
+ super(`MCP error ${code}: ${message2}`);
21264
21530
  this.code = code;
21265
21531
  this.data = data;
21266
21532
  this.name = "McpError";
@@ -21268,19 +21534,19 @@ var McpError = class _McpError extends Error {
21268
21534
  /**
21269
21535
  * Factory method to create the appropriate error type based on the error code and data
21270
21536
  */
21271
- static fromError(code, message, data) {
21537
+ static fromError(code, message2, data) {
21272
21538
  if (code === ErrorCode.UrlElicitationRequired && data) {
21273
21539
  const errorData = data;
21274
21540
  if (errorData.elicitations) {
21275
- return new UrlElicitationRequiredError(errorData.elicitations, message);
21541
+ return new UrlElicitationRequiredError(errorData.elicitations, message2);
21276
21542
  }
21277
21543
  }
21278
- return new _McpError(code, message, data);
21544
+ return new _McpError(code, message2, data);
21279
21545
  }
21280
21546
  };
21281
21547
  var UrlElicitationRequiredError = class extends McpError {
21282
- constructor(elicitations, message = `URL elicitation${elicitations.length > 1 ? "s" : ""} required`) {
21283
- super(ErrorCode.UrlElicitationRequired, message, {
21548
+ constructor(elicitations, message2 = `URL elicitation${elicitations.length > 1 ? "s" : ""} required`) {
21549
+ super(ErrorCode.UrlElicitationRequired, message2, {
21284
21550
  elicitations
21285
21551
  });
21286
21552
  }
@@ -21366,15 +21632,15 @@ var Protocol = class {
21366
21632
  let queuedMessage;
21367
21633
  while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
21368
21634
  if (queuedMessage.type === "response" || queuedMessage.type === "error") {
21369
- const message = queuedMessage.message;
21370
- const requestId = message.id;
21635
+ const message2 = queuedMessage.message;
21636
+ const requestId = message2.id;
21371
21637
  const resolver = this._requestResolvers.get(requestId);
21372
21638
  if (resolver) {
21373
21639
  this._requestResolvers.delete(requestId);
21374
21640
  if (queuedMessage.type === "response") {
21375
- resolver(message);
21641
+ resolver(message2);
21376
21642
  } else {
21377
- const errorMessage3 = message;
21643
+ const errorMessage3 = message2;
21378
21644
  const error2 = new McpError(errorMessage3.error.code, errorMessage3.error.message, errorMessage3.error.data);
21379
21645
  resolver(error2);
21380
21646
  }
@@ -21513,16 +21779,16 @@ var Protocol = class {
21513
21779
  this._onerror(error2);
21514
21780
  };
21515
21781
  const _onmessage = this._transport?.onmessage;
21516
- this._transport.onmessage = (message, extra) => {
21517
- _onmessage?.(message, extra);
21518
- if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
21519
- this._onresponse(message);
21520
- } else if (isJSONRPCRequest(message)) {
21521
- this._onrequest(message, extra);
21522
- } else if (isJSONRPCNotification(message)) {
21523
- this._onnotification(message);
21782
+ this._transport.onmessage = (message2, extra) => {
21783
+ _onmessage?.(message2, extra);
21784
+ if (isJSONRPCResultResponse(message2) || isJSONRPCErrorResponse(message2)) {
21785
+ this._onresponse(message2);
21786
+ } else if (isJSONRPCRequest(message2)) {
21787
+ this._onrequest(message2, extra);
21788
+ } else if (isJSONRPCNotification(message2)) {
21789
+ this._onnotification(message2);
21524
21790
  } else {
21525
- this._onerror(new Error(`Unknown message type: ${JSON.stringify(message)}`));
21791
+ this._onerror(new Error(`Unknown message type: ${JSON.stringify(message2)}`));
21526
21792
  }
21527
21793
  };
21528
21794
  await this._transport.start();
@@ -22132,12 +22398,12 @@ var Protocol = class {
22132
22398
  * the error appropriately (e.g., by failing the task, logging, etc.). The Protocol layer
22133
22399
  * simply propagates the error.
22134
22400
  */
22135
- async _enqueueTaskMessage(taskId, message, sessionId) {
22401
+ async _enqueueTaskMessage(taskId, message2, sessionId) {
22136
22402
  if (!this._taskStore || !this._taskMessageQueue) {
22137
22403
  throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
22138
22404
  }
22139
22405
  const maxQueueSize = this._options?.maxTaskQueueSize;
22140
- await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
22406
+ await this._taskMessageQueue.enqueue(taskId, message2, sessionId, maxQueueSize);
22141
22407
  }
22142
22408
  /**
22143
22409
  * Clears the message queue for a task and rejects any pending request resolvers.
@@ -22147,9 +22413,9 @@ var Protocol = class {
22147
22413
  async _clearTaskQueue(taskId, sessionId) {
22148
22414
  if (this._taskMessageQueue) {
22149
22415
  const messages = await this._taskMessageQueue.dequeueAll(taskId, sessionId);
22150
- for (const message of messages) {
22151
- if (message.type === "request" && isJSONRPCRequest(message.message)) {
22152
- const requestId = message.message.id;
22416
+ for (const message2 of messages) {
22417
+ if (message2.type === "request" && isJSONRPCRequest(message2.message)) {
22418
+ const requestId = message2.message.id;
22153
22419
  const resolver = this._requestResolvers.get(requestId);
22154
22420
  if (resolver) {
22155
22421
  resolver(new McpError(ErrorCode.InternalError, "Task cancelled or completed"));
@@ -22962,6 +23228,75 @@ var Server = class extends Protocol {
22962
23228
  }
22963
23229
  };
22964
23230
 
23231
+ // ../../packages/mcp/src/messages.ts
23232
+ var UPPER = /* @__PURE__ */ new Set(["seo", "gsc", "gbp", "cta", "cmo", "dns", "ai", "api", "pdf", "url", "id"]);
23233
+ var n = (count, word, plural) => count === 1 ? `1 ${word}` : `${count ? count : "no"} ${plural ?? `${word}s`}`;
23234
+ function titleOf(name) {
23235
+ const words = name.split("_").map((w) => UPPER.has(w) ? w.toUpperCase() : w);
23236
+ const head = words[0] ?? "";
23237
+ return [head.charAt(0).toUpperCase() + head.slice(1), ...words.slice(1)].join(" ");
23238
+ }
23239
+ var LIST_KEYS = ["items", "results", "posts", "ideas", "workspaces", "rows"];
23240
+ function items(value) {
23241
+ if (Array.isArray(value)) return value;
23242
+ if (value && typeof value === "object") {
23243
+ for (const k of LIST_KEYS) {
23244
+ const v = value[k];
23245
+ if (Array.isArray(v)) return v;
23246
+ }
23247
+ }
23248
+ return null;
23249
+ }
23250
+ function label(x) {
23251
+ if (x && typeof x === "object") {
23252
+ const o = x;
23253
+ for (const k of ["title", "name", "domain", "label", "what", "text", "id"]) {
23254
+ if (o[k] !== void 0 && o[k] !== null && o[k] !== "") return String(o[k]);
23255
+ }
23256
+ return Object.entries(o).filter(([, v]) => typeof v !== "object").slice(0, 3).map(([k, v]) => `${k} ${v}`).join(", ");
23257
+ }
23258
+ return String(x);
23259
+ }
23260
+ function bodyLines(value) {
23261
+ const list2 = items(value);
23262
+ if (list2) {
23263
+ const out = list2.slice(0, 8).map((x) => `- ${label(x)}`.slice(0, 200));
23264
+ return list2.length > 8 ? [...out, `\u2026and ${list2.length - 8} more`] : out;
23265
+ }
23266
+ if (value && typeof value === "object") {
23267
+ 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));
23268
+ }
23269
+ return value === null || value === void 0 || value === "" ? [] : [String(value).slice(0, 600)];
23270
+ }
23271
+ function said(name, value) {
23272
+ if (value && typeof value === "object" && !Array.isArray(value)) {
23273
+ for (const k of ["say", "headline", "summary", "message"]) {
23274
+ const v = value[k];
23275
+ if (typeof v === "string" && v.trim()) return (v.trim().split("\n")[0] ?? "").slice(0, 240);
23276
+ }
23277
+ }
23278
+ const list2 = items(value);
23279
+ if (list2) {
23280
+ let noun = name.endsWith("_list") ? name.slice(0, -5).split("_").pop() ?? "item" : "item";
23281
+ if (noun.endsWith("s")) noun = noun.slice(0, -1);
23282
+ return list2.length ? `I found ${n(list2.length, noun)}.` : "There's nothing there yet.";
23283
+ }
23284
+ if (typeof value === "string") return (value.trim().split("\n")[0] ?? "").slice(0, 240) || "Done.";
23285
+ return "Here it is.";
23286
+ }
23287
+ var lowerFirst = (s) => /^[A-Za-z][a-z]/.test(s) ? s.charAt(0).toLowerCase() + s.slice(1) : s;
23288
+ function message(name, value, opts = {}) {
23289
+ const title = titleOf(name);
23290
+ if (opts.error) return { say: `That didn't work: ${opts.error}`.slice(0, 300), title, lines: [] };
23291
+ if (opts.preview) return { say: "This needs your yes first; nothing has changed yet.", title, lines: [opts.preview] };
23292
+ if (opts.done) return { say: `Done: ${lowerFirst(opts.done)}`, title, lines: bodyLines(value) };
23293
+ return { say: said(name, value), title, lines: bodyLines(value) };
23294
+ }
23295
+ function messageText(m) {
23296
+ const body = [m.title ? `**${m.title}**` : "", ...m.lines].filter(Boolean).join("\n");
23297
+ return [m.say, body].filter(Boolean).join("\n\n").trim();
23298
+ }
23299
+
22965
23300
  // ../../packages/mcp/src/prompts.ts
22966
23301
  var workspaceArg = {
22967
23302
  name: "workspace",
@@ -23314,14 +23649,14 @@ function toolArgs(args) {
23314
23649
  return out;
23315
23650
  }
23316
23651
  function withIdempotency(api2, approvalId) {
23317
- let n = 0;
23652
+ let n2 = 0;
23318
23653
  return {
23319
23654
  ...api2,
23320
23655
  request: (path, init = {}) => {
23321
23656
  const method = (init.method ?? "GET").toUpperCase();
23322
23657
  if (method === "GET" || method === "HEAD") return api2.request(path, init);
23323
23658
  const headers = new Headers(init.headers);
23324
- headers.set("Idempotency-Key", idempotencyKey(approvalId, 0, n++));
23659
+ headers.set("Idempotency-Key", idempotencyKey(approvalId, 0, n2++));
23325
23660
  return api2.request(path, { ...init, headers });
23326
23661
  }
23327
23662
  };
@@ -23329,13 +23664,18 @@ function withIdempotency(api2, approvalId) {
23329
23664
  var text2 = (value) => [
23330
23665
  { type: "text", text: typeof value === "string" ? value : JSON.stringify(value) }
23331
23666
  ];
23332
- function ok(result, extra) {
23667
+ function ok(result, extra, msg) {
23333
23668
  const value = result ?? { ok: true };
23334
23669
  const structured = value && typeof value === "object" && !Array.isArray(value) ? value : { items: value };
23335
- return { content: text2(value), structuredContent: { ...structured, ...extra } };
23670
+ if (!msg) return { content: text2(value), structuredContent: { ...structured, ...extra } };
23671
+ return {
23672
+ content: [...text2(messageText(msg)), ...text2(value)],
23673
+ structuredContent: { ...structured, ...extra, message: msg }
23674
+ };
23336
23675
  }
23337
- function fail(message) {
23338
- return { content: text2({ error: message }), isError: true };
23676
+ function fail(error2, tool2) {
23677
+ if (!tool2) return { content: text2({ error: error2 }), isError: true };
23678
+ return { content: [...text2(messageText(message(tool2, void 0, { error: error2 }))), ...text2({ error: error2 })], isError: true };
23339
23679
  }
23340
23680
  function errorMessage2(e) {
23341
23681
  if (e instanceof SignInRequiredError) return "The sign-in has expired. Reconnect CMO.so and try again.";
@@ -23475,7 +23815,10 @@ function createMcpServer(deps) {
23475
23815
  );
23476
23816
  const callArgs = approval ? withFreshFiles(t, approval.calls[0]?.args ?? {}, toolArgs(args)) : toolArgs(args);
23477
23817
  const ctx = { api: api2, domain, client: "mcp" };
23478
- if (!isWrite(t)) return ok(await t.run(ctx, callArgs), viewForTool(t.name) ? { workspace: domain } : void 0);
23818
+ if (!isWrite(t)) {
23819
+ const value = await t.run(ctx, callArgs);
23820
+ return ok(value, viewForTool(t.name) ? { workspace: domain } : void 0, message(t.name, value));
23821
+ }
23479
23822
  let standing = null;
23480
23823
  if (!approval && !deps.previewOnly && domain && isStanding(t.name)) {
23481
23824
  const claim2 = await api2.account?.claimStanding({ website: domain, tool: t.name, units: standingUnits(t.name, callArgs) }).catch(() => null);
@@ -23497,14 +23840,19 @@ function createMcpServer(deps) {
23497
23840
  const calls = [{ name: t.name, args: callArgs }];
23498
23841
  const summary2 = t.confirm?.(callArgs, "en") ?? t.description;
23499
23842
  const cost = await previewCost(api2, domain, t);
23500
- return ok({
23501
- status: "needs_confirmation",
23502
- preview: cost ? `${summary2} ${cost}` : summary2,
23503
- risk: t.risk,
23504
- workspace: domain,
23505
- confirmation: signer.sign({ calls, principal, domain }),
23506
- 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."
23507
- });
23843
+ const preview = cost ? `${summary2} ${cost}` : summary2;
23844
+ return ok(
23845
+ {
23846
+ status: "needs_confirmation",
23847
+ preview,
23848
+ risk: t.risk,
23849
+ workspace: domain,
23850
+ confirmation: signer.sign({ calls, principal, domain }),
23851
+ 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."
23852
+ },
23853
+ void 0,
23854
+ message(t.name, void 0, { preview })
23855
+ );
23508
23856
  }
23509
23857
  const summary = `${t.confirm?.(callArgs, "en") ?? t.description}${standing ? ` (standing permission #${standing})` : ""}`;
23510
23858
  try {
@@ -23512,14 +23860,14 @@ function createMcpServer(deps) {
23512
23860
  const added = t.name === "website_add" ? result?.domain : null;
23513
23861
  const where = typeof added === "string" && added ? added : domain;
23514
23862
  await deps.audit?.({ domain: where, actions: [{ tool: t.name, summary, ok: true, action_id: approval.id }] }).catch(() => void 0);
23515
- return ok(result);
23863
+ return ok(result, void 0, message(t.name, result, { done: summary }));
23516
23864
  } catch (e) {
23517
23865
  const error2 = errorMessage2(e);
23518
23866
  await deps.audit?.({ domain, actions: [{ tool: t.name, summary, ok: false, error: error2, action_id: approval.id }] }).catch(() => void 0);
23519
- return fail(error2);
23867
+ return fail(error2, t.name);
23520
23868
  }
23521
23869
  } catch (e) {
23522
- return fail(errorMessage2(e));
23870
+ return fail(errorMessage2(e), t.name);
23523
23871
  }
23524
23872
  });
23525
23873
  server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
@@ -23649,8 +23997,8 @@ var ReadBuffer = class {
23649
23997
  function deserializeMessage(line) {
23650
23998
  return JSONRPCMessageSchema.parse(JSON.parse(line));
23651
23999
  }
23652
- function serializeMessage(message) {
23653
- return JSON.stringify(message) + "\n";
24000
+ function serializeMessage(message2) {
24001
+ return JSON.stringify(message2) + "\n";
23654
24002
  }
23655
24003
 
23656
24004
  // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
@@ -23688,11 +24036,11 @@ var StdioServerTransport = class {
23688
24036
  processReadBuffer() {
23689
24037
  while (true) {
23690
24038
  try {
23691
- const message = this._readBuffer.readMessage();
23692
- if (message === null) {
24039
+ const message2 = this._readBuffer.readMessage();
24040
+ if (message2 === null) {
23693
24041
  break;
23694
24042
  }
23695
- this.onmessage?.(message);
24043
+ this.onmessage?.(message2);
23696
24044
  } catch (error2) {
23697
24045
  this.onerror?.(error2);
23698
24046
  }
@@ -23708,9 +24056,9 @@ var StdioServerTransport = class {
23708
24056
  this._readBuffer.clear();
23709
24057
  this.onclose?.();
23710
24058
  }
23711
- send(message) {
24059
+ send(message2) {
23712
24060
  return new Promise((resolve) => {
23713
- const json = serializeMessage(message);
24061
+ const json = serializeMessage(message2);
23714
24062
  if (this._stdout.write(json)) {
23715
24063
  resolve();
23716
24064
  } else {