@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.
- package/dist/cmo-mcp.mjs +573 -225
- 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(
|
|
452
|
+
constructor(label2) {
|
|
453
453
|
super();
|
|
454
|
-
this.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(
|
|
462
|
+
constructor(label2) {
|
|
463
463
|
super();
|
|
464
|
-
this.label =
|
|
464
|
+
this.label = label2;
|
|
465
465
|
this.names = {};
|
|
466
466
|
}
|
|
467
467
|
render({ _n }) {
|
|
468
|
-
const
|
|
469
|
-
return `break${
|
|
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,
|
|
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
|
|
516
|
-
if (Array.isArray(
|
|
517
|
-
nodes.splice(i, 1, ...
|
|
518
|
-
else if (
|
|
519
|
-
nodes[i] =
|
|
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
|
|
530
|
-
if (
|
|
529
|
+
const n2 = nodes[i];
|
|
530
|
+
if (n2.optimizeNames(names, constants))
|
|
531
531
|
continue;
|
|
532
|
-
subtractNames(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,
|
|
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(
|
|
882
|
-
return this._leafNode(new Label(
|
|
881
|
+
label(label2) {
|
|
882
|
+
return this._leafNode(new Label(label2));
|
|
883
883
|
}
|
|
884
884
|
// `break` statement
|
|
885
|
-
break(
|
|
886
|
-
return this._leafNode(new Break(
|
|
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(
|
|
950
|
-
while (
|
|
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
|
|
965
|
-
if (
|
|
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
|
|
973
|
-
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 =
|
|
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
|
|
994
|
-
names[
|
|
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((
|
|
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
|
-
|
|
1009
|
+
items2.push(...c._items);
|
|
1010
1010
|
else
|
|
1011
|
-
|
|
1012
|
-
return
|
|
1011
|
+
items2.push(c);
|
|
1012
|
+
return items2;
|
|
1013
1013
|
}, []));
|
|
1014
|
-
function replaceName(
|
|
1015
|
-
const c = constants[
|
|
1016
|
-
if (c === void 0 || names[
|
|
1017
|
-
return
|
|
1018
|
-
delete names[
|
|
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
|
|
1027
|
-
names[
|
|
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,
|
|
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
|
|
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(
|
|
1684
|
-
|
|
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 (
|
|
2492
|
-
gen.assign((0, codegen_1._)`${evaluated}.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:
|
|
2958
|
+
items: items2 instanceof codegen_1.Name ? void 0 : items2,
|
|
2959
2959
|
dynamicProps: props instanceof codegen_1.Name,
|
|
2960
|
-
dynamicItems:
|
|
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
|
|
4499
|
+
const message2 = "schema is invalid: " + this.errorsText();
|
|
4500
4500
|
if (this.opts.validateSchema === "log")
|
|
4501
|
-
this.logger.error(
|
|
4501
|
+
this.logger.error(message2);
|
|
4502
4502
|
else
|
|
4503
|
-
throw new Error(
|
|
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
|
|
4971
|
-
it.items = util_1.mergeEvaluated.items(gen,
|
|
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(
|
|
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,
|
|
5516
|
+
validateAdditionalItems(cxt, items2);
|
|
5517
5517
|
}
|
|
5518
5518
|
};
|
|
5519
|
-
function validateAdditionalItems(cxt,
|
|
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:
|
|
5525
|
-
cxt.pass((0, codegen_1._)`${len} <= ${
|
|
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} <= ${
|
|
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",
|
|
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.
|
|
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(
|
|
7275
|
-
super(
|
|
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,
|
|
7281
|
-
super(
|
|
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: (
|
|
7511
|
+
addAccount: (label2) => request("/v1/maggie/bridge/accounts", {
|
|
7512
7512
|
method: "POST",
|
|
7513
7513
|
headers: { "Content-Type": "application/json" },
|
|
7514
|
-
body: JSON.stringify({ label:
|
|
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,
|
|
7534
|
-
const { type, bytes } = await requestBytes(`/v1/feedback/${encodeURIComponent(id)}/photos/${
|
|
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
|
|
7585
|
-
if (typeof
|
|
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
|
|
7604
|
-
chunk += B64.charAt(
|
|
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
|
|
7985
|
-
*
|
|
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
|
|
8473
|
-
const out = { ...entry, url, owner, label:
|
|
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
|
|
8491
|
-
const found =
|
|
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,
|
|
8540
|
+
var list = (xs, n2 = 5) => {
|
|
8520
8541
|
const a = (Array.isArray(xs) ? xs : xs == null ? [] : [xs]).map(String);
|
|
8521
|
-
return a.length >
|
|
8542
|
+
return a.length > n2 ? `${a.slice(0, n2).join(", ")} +${a.length - n2}` : a.join(", ");
|
|
8522
8543
|
};
|
|
8523
|
-
var placeEn = (p,
|
|
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 ${
|
|
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
|
|
8617
|
-
return a.url ? `Save ${whose ?? "the "}sitemap ${a.url}${
|
|
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
|
|
8622
|
-
return a.url ? `\u5132\u5B58${whose ?? ""} sitemap ${a.url}${
|
|
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
|
|
8807
|
+
const n2 = ids(a);
|
|
8781
8808
|
const at = placeEn(a.position);
|
|
8782
8809
|
if (at)
|
|
8783
|
-
return `Approve ${
|
|
8784
|
-
return
|
|
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
|
|
8814
|
+
const n2 = ids(a);
|
|
8788
8815
|
const at = placeZh(a.position);
|
|
8789
8816
|
if (at)
|
|
8790
|
-
return `\u6279\u51C6${
|
|
8791
|
-
return
|
|
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
|
|
8817
|
-
"zh-Hant": (a) => `\u628A\u8CBC\u6587 #${a.id} \u6539\u6210\u5F71\u7247\u8CBC\u6587\uFF0C\u7528 ${a
|
|
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
|
|
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
|
|
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
|
|
8860
|
-
return `${a.append ? "Add" : "Put"} your ${
|
|
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
|
|
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 ${
|
|
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) => `
|
|
8869
|
-
"zh-Hant": (a) =>
|
|
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
|
|
9140
|
+
const n2 = cost.aiGenerations;
|
|
9094
9141
|
const left = cost.aiLeft;
|
|
9095
9142
|
parts.push(
|
|
9096
|
-
lang === "zh-Hant" ? `\u7528 ${
|
|
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
|
|
9147
|
+
const n2 = cost.credits;
|
|
9101
9148
|
const left = cost.creditsLeft;
|
|
9102
9149
|
parts.push(
|
|
9103
|
-
lang === "zh-Hant" ? `\u6263 ${
|
|
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,
|
|
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,
|
|
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
|
|
9131
|
-
return Number.isFinite(
|
|
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
|
|
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 "${
|
|
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
|
|
10009
|
-
if (!Number.isFinite(
|
|
10010
|
-
const out = await ws(ctx).updateBrand({ internal_links: { count:
|
|
10011
|
-
return { internal_links: out.internal_links ?? { count:
|
|
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
|
|
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
|
|
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,
|
|
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((_,
|
|
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,
|
|
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
|
|
11839
|
+
for (const n2 of list2) {
|
|
11758
11840
|
try {
|
|
11759
|
-
await api2.approveSocial(
|
|
11760
|
-
results.push({ id:
|
|
11841
|
+
await api2.approveSocial(n2, at);
|
|
11842
|
+
results.push({ id: n2, ok: true });
|
|
11761
11843
|
} catch (e) {
|
|
11762
|
-
results.push({ id:
|
|
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
|
|
11778
|
-
if (!Number.isInteger(
|
|
11779
|
-
const post = await ws(ctx).publishSocialNow(
|
|
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
|
|
11801
|
-
if (!Number.isInteger(
|
|
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(
|
|
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
|
|
11817
|
-
if (!Number.isInteger(
|
|
11818
|
-
return ws(ctx).deletePostedSocial(
|
|
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)
|
|
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
|
|
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
|
|
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: {
|
|
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"
|
|
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:
|
|
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
|
|
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"
|
|
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(
|
|
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
|
|
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.
|
|
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(
|
|
13897
|
-
return typeof
|
|
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
|
|
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 =
|
|
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
|
|
15552
|
-
if (
|
|
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 (
|
|
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,
|
|
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(
|
|
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(
|
|
19349
|
-
return this.check(_minLength(
|
|
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(
|
|
19355
|
-
return this.check(_maxLength(
|
|
19620
|
+
max(n2, params) {
|
|
19621
|
+
return this.check(_maxLength(n2, params));
|
|
19356
19622
|
},
|
|
19357
|
-
length(
|
|
19358
|
-
return this.check(_length(
|
|
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,
|
|
21263
|
-
super(`MCP error ${code}: ${
|
|
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,
|
|
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,
|
|
21541
|
+
return new UrlElicitationRequiredError(errorData.elicitations, message2);
|
|
21276
21542
|
}
|
|
21277
21543
|
}
|
|
21278
|
-
return new _McpError(code,
|
|
21544
|
+
return new _McpError(code, message2, data);
|
|
21279
21545
|
}
|
|
21280
21546
|
};
|
|
21281
21547
|
var UrlElicitationRequiredError = class extends McpError {
|
|
21282
|
-
constructor(elicitations,
|
|
21283
|
-
super(ErrorCode.UrlElicitationRequired,
|
|
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
|
|
21370
|
-
const requestId =
|
|
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(
|
|
21641
|
+
resolver(message2);
|
|
21376
21642
|
} else {
|
|
21377
|
-
const errorMessage3 =
|
|
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 = (
|
|
21517
|
-
_onmessage?.(
|
|
21518
|
-
if (isJSONRPCResultResponse(
|
|
21519
|
-
this._onresponse(
|
|
21520
|
-
} else if (isJSONRPCRequest(
|
|
21521
|
-
this._onrequest(
|
|
21522
|
-
} else if (isJSONRPCNotification(
|
|
21523
|
-
this._onnotification(
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
|
22151
|
-
if (
|
|
22152
|
-
const requestId =
|
|
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
|
|
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,
|
|
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(
|
|
23338
|
-
return { content: text2({ error:
|
|
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))
|
|
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
|
-
|
|
23501
|
-
|
|
23502
|
-
|
|
23503
|
-
|
|
23504
|
-
|
|
23505
|
-
|
|
23506
|
-
|
|
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(
|
|
23653
|
-
return JSON.stringify(
|
|
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
|
|
23692
|
-
if (
|
|
24039
|
+
const message2 = this._readBuffer.readMessage();
|
|
24040
|
+
if (message2 === null) {
|
|
23693
24041
|
break;
|
|
23694
24042
|
}
|
|
23695
|
-
this.onmessage?.(
|
|
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(
|
|
24059
|
+
send(message2) {
|
|
23712
24060
|
return new Promise((resolve) => {
|
|
23713
|
-
const json = serializeMessage(
|
|
24061
|
+
const json = serializeMessage(message2);
|
|
23714
24062
|
if (this._stdout.write(json)) {
|
|
23715
24063
|
resolve();
|
|
23716
24064
|
} else {
|