@koda-sl/baker-cli 0.158.2 → 0.160.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/README.md +13 -1
- package/dist/cli.js +736 -80
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3501,7 +3501,7 @@ registerSchema({
|
|
|
3501
3501
|
name: { type: "string", description: "Action name (short, action-verb, \u22646 words)", required: true },
|
|
3502
3502
|
description: {
|
|
3503
3503
|
type: "string",
|
|
3504
|
-
description: "Context-complete description: what / why / where / done-when",
|
|
3504
|
+
description: "Context-complete description in markdown: what / why / where / done-when. Use `##` headings for those sections, `-` bullets for lists of items, and `**bold**` for the figures that drive the decision. Anything longer than a couple of sentences needs headings \u2014 it is read in a narrow panel.",
|
|
3505
3505
|
required: false
|
|
3506
3506
|
},
|
|
3507
3507
|
tags: {
|
|
@@ -4154,7 +4154,11 @@ registerSchema({
|
|
|
4154
4154
|
args: {
|
|
4155
4155
|
id: { type: "string", description: "Action ID or tempId (temp_*) of a task staged in this chat", required: true },
|
|
4156
4156
|
name: { type: "string", description: "New name", required: false },
|
|
4157
|
-
description: {
|
|
4157
|
+
description: {
|
|
4158
|
+
type: "string",
|
|
4159
|
+
description: "New description, in markdown \u2014 `##` headings per section, `-` bullets, `**bold**` for the decisive figures. REPLACES the whole description, so carry over anything still true.",
|
|
4160
|
+
required: false
|
|
4161
|
+
},
|
|
4158
4162
|
tags: {
|
|
4159
4163
|
type: "string",
|
|
4160
4164
|
description: "Comma-separated tag slugs \u2014 REPLACES existing tags ('' clears)",
|
|
@@ -4176,7 +4180,7 @@ var updateCommand = defineCommand17({
|
|
|
4176
4180
|
id: { type: "positional", description: "Action ID or tempId (temp_*)", required: false },
|
|
4177
4181
|
"action-id": { type: "string", description: "Action ID or tempId (temp_*)", required: false },
|
|
4178
4182
|
name: { type: "string", description: "New name", required: false },
|
|
4179
|
-
description: { type: "string", description: "New description", required: false },
|
|
4183
|
+
description: { type: "string", description: "New description (markdown)", required: false },
|
|
4180
4184
|
tags: { type: "string", description: "Comma-separated tag slugs \u2014 REPLACES existing ('' clears)", required: false },
|
|
4181
4185
|
priority: { type: "string", description: `Priority: ${ACTION_PRIORITIES.join("|")}|none`, required: false }
|
|
4182
4186
|
},
|
|
@@ -4840,7 +4844,10 @@ var callAdSchema = z13.object({
|
|
|
4840
4844
|
var appAdSchema = z13.object({
|
|
4841
4845
|
format: z13.literal("app"),
|
|
4842
4846
|
headlines: z13.array(z13.object({ text: z13.string().min(1).max(30) })).min(1),
|
|
4843
|
-
descriptions: z13.array(z13.object({ text: z13.string().min(1).max(90) })).min(1)
|
|
4847
|
+
descriptions: z13.array(z13.object({ text: z13.string().min(1).max(90) })).min(1),
|
|
4848
|
+
// An App campaign ad carries its images on its own content (`AppAdInfo.images`), not as
|
|
4849
|
+
// campaign-level asset links — same shape as a responsive display ad's marketing images.
|
|
4850
|
+
images: z13.array(refSchema).optional()
|
|
4844
4851
|
});
|
|
4845
4852
|
var videoAdSchema = z13.object({
|
|
4846
4853
|
format: z13.literal("video"),
|
|
@@ -6179,6 +6186,443 @@ import { defineCommand as defineCommand22 } from "citty";
|
|
|
6179
6186
|
// src/commands/ads/google/write-shared.ts
|
|
6180
6187
|
import { readFileSync as readFileSync2 } from "fs";
|
|
6181
6188
|
|
|
6189
|
+
// src/engine/copy-tells/patterns.ts
|
|
6190
|
+
var PHRASE_TELLS = {
|
|
6191
|
+
// §33 — the fake-candid pause before an ordinary claim. The single loudest
|
|
6192
|
+
// tell in AI-written Meta primary text. Anchored to a sentence start by the
|
|
6193
|
+
// detector, so "take a look at" and "honestly, I agree" mid-sentence are safe.
|
|
6194
|
+
"rhetorical-opener": [
|
|
6195
|
+
"honestly\\?",
|
|
6196
|
+
"honestly,",
|
|
6197
|
+
"here's the thing",
|
|
6198
|
+
"here is the thing",
|
|
6199
|
+
"the thing is",
|
|
6200
|
+
"let's be honest",
|
|
6201
|
+
"let's be real",
|
|
6202
|
+
"real talk",
|
|
6203
|
+
"look,",
|
|
6204
|
+
"listen,",
|
|
6205
|
+
"la verdad es que",
|
|
6206
|
+
"sinceramente,",
|
|
6207
|
+
"seamos honestos",
|
|
6208
|
+
"seamos sinceros",
|
|
6209
|
+
"mira,",
|
|
6210
|
+
"escucha,",
|
|
6211
|
+
"te lo digo claro"
|
|
6212
|
+
],
|
|
6213
|
+
// §9 — negative parallelism. Our landing doctrine already names this in prose;
|
|
6214
|
+
// this is the detector that was missing.
|
|
6215
|
+
"negation-pivot": [
|
|
6216
|
+
"it's not just",
|
|
6217
|
+
"it is not just",
|
|
6218
|
+
"it's not merely",
|
|
6219
|
+
"not only .{3,40} but also",
|
|
6220
|
+
"less about .{3,40} more about",
|
|
6221
|
+
"isn't about .{3,40} it's about",
|
|
6222
|
+
"no se trata solo de",
|
|
6223
|
+
"no se trata \xFAnicamente de",
|
|
6224
|
+
"no solo .{3,40} sino",
|
|
6225
|
+
"no es solo .{3,40} es",
|
|
6226
|
+
"m\xE1s que .{3,40}, es"
|
|
6227
|
+
],
|
|
6228
|
+
// §3 — present participle hung off a comma to fake depth. Detector requires
|
|
6229
|
+
// the preceding comma, so a sentence-initial participle is not a tell.
|
|
6230
|
+
"participle-tail": [
|
|
6231
|
+
"ensuring",
|
|
6232
|
+
"highlighting",
|
|
6233
|
+
"underscoring",
|
|
6234
|
+
"emphasizing",
|
|
6235
|
+
"showcasing",
|
|
6236
|
+
"fostering",
|
|
6237
|
+
"empowering",
|
|
6238
|
+
"reflecting",
|
|
6239
|
+
"symbolizing",
|
|
6240
|
+
"contributing to",
|
|
6241
|
+
"encompassing",
|
|
6242
|
+
"cultivating",
|
|
6243
|
+
"permitiendo",
|
|
6244
|
+
"garantizando",
|
|
6245
|
+
"impulsando",
|
|
6246
|
+
"reflejando",
|
|
6247
|
+
"consolidando",
|
|
6248
|
+
"potenciando"
|
|
6249
|
+
],
|
|
6250
|
+
// §8 — elaborate constructions standing in for "is" and "has".
|
|
6251
|
+
"copula-avoidance": [
|
|
6252
|
+
"serves as",
|
|
6253
|
+
"stands as",
|
|
6254
|
+
"boasts",
|
|
6255
|
+
"represents a",
|
|
6256
|
+
"marks a",
|
|
6257
|
+
"se erige como",
|
|
6258
|
+
"se posiciona como",
|
|
6259
|
+
"cuenta con",
|
|
6260
|
+
"se consolida como",
|
|
6261
|
+
"constituye un"
|
|
6262
|
+
],
|
|
6263
|
+
// §27 — pretending to cut through noise before restating an ordinary point.
|
|
6264
|
+
"authority-trope": [
|
|
6265
|
+
"the real question is",
|
|
6266
|
+
"at its core",
|
|
6267
|
+
"what really matters",
|
|
6268
|
+
"the heart of the matter",
|
|
6269
|
+
"the deeper issue",
|
|
6270
|
+
"in reality,",
|
|
6271
|
+
"la verdadera pregunta",
|
|
6272
|
+
"en el fondo,",
|
|
6273
|
+
"lo que realmente importa",
|
|
6274
|
+
"en esencia,"
|
|
6275
|
+
],
|
|
6276
|
+
// §28 — announcing the writing instead of doing it.
|
|
6277
|
+
signposting: [
|
|
6278
|
+
"let's dive in",
|
|
6279
|
+
"let's dive into",
|
|
6280
|
+
"let's explore",
|
|
6281
|
+
"let's break (?:this|it) down",
|
|
6282
|
+
"here's what you need to know",
|
|
6283
|
+
"here is what you need to know",
|
|
6284
|
+
"now let's look at",
|
|
6285
|
+
"without further ado",
|
|
6286
|
+
"vamos a ver",
|
|
6287
|
+
"vamos a analizar",
|
|
6288
|
+
"profundicemos",
|
|
6289
|
+
"esto es lo que necesitas saber",
|
|
6290
|
+
"te lo explicamos paso a paso"
|
|
6291
|
+
],
|
|
6292
|
+
// §20 — chatbot correspondence pasted in as content. Always a defect: it means
|
|
6293
|
+
// the agent shipped its own conversational frame into client-facing copy.
|
|
6294
|
+
//
|
|
6295
|
+
// This is the only tell the landing gate fires at ONE occurrence, so every
|
|
6296
|
+
// entry has to be unambiguous. The delivery openers are therefore bound to the
|
|
6297
|
+
// noun the assistant hands over ("here is a summary", "aquí tienes el
|
|
6298
|
+
// resumen"): a bare "here is a…" / "aquí tienes…" is ordinary marketing copy.
|
|
6299
|
+
"chat-artifact": [
|
|
6300
|
+
"i hope this helps",
|
|
6301
|
+
"let me know if",
|
|
6302
|
+
"would you like me to",
|
|
6303
|
+
"want me to",
|
|
6304
|
+
"here (?:is|are) (?:an? |the )?(?:overview|summary|breakdown|rundown|outline|draft|revised version)",
|
|
6305
|
+
"here's (?:an? |the )?(?:overview|summary|breakdown|rundown|outline|draft|revised version)",
|
|
6306
|
+
"certainly!",
|
|
6307
|
+
"great question",
|
|
6308
|
+
"you're absolutely right",
|
|
6309
|
+
"espero que (?:te sirva|esto ayude)",
|
|
6310
|
+
"av\xEDsame si",
|
|
6311
|
+
"\xBFquieres que",
|
|
6312
|
+
"aqu\xED tienes (?:un |una |el |la |los |las )?(?:resumen|listado|desglose|borrador|esquema|propuesta|versi\xF3n)",
|
|
6313
|
+
"por supuesto!"
|
|
6314
|
+
],
|
|
6315
|
+
// §5 — opinions attributed to nobody in particular.
|
|
6316
|
+
"vague-attribution": [
|
|
6317
|
+
"experts agree",
|
|
6318
|
+
"experts say",
|
|
6319
|
+
"experts argue",
|
|
6320
|
+
"studies show",
|
|
6321
|
+
"research shows",
|
|
6322
|
+
"industry reports",
|
|
6323
|
+
"observers have",
|
|
6324
|
+
"some critics",
|
|
6325
|
+
"it is believed that",
|
|
6326
|
+
"los expertos coinciden",
|
|
6327
|
+
"los expertos afirman",
|
|
6328
|
+
"los estudios demuestran",
|
|
6329
|
+
"seg\xFAn varios informes",
|
|
6330
|
+
"se cree que"
|
|
6331
|
+
],
|
|
6332
|
+
// §25 — vague upbeat endings that assert nothing.
|
|
6333
|
+
"hollow-conclusion": [
|
|
6334
|
+
"the future looks bright",
|
|
6335
|
+
"exciting times",
|
|
6336
|
+
"a step in the right direction",
|
|
6337
|
+
"the possibilities are endless",
|
|
6338
|
+
"the sky's the limit",
|
|
6339
|
+
"el futuro es prometedor",
|
|
6340
|
+
"las posibilidades son infinitas",
|
|
6341
|
+
"un paso en la direcci\xF3n correcta",
|
|
6342
|
+
"el cielo es el l\xEDmite"
|
|
6343
|
+
],
|
|
6344
|
+
// §23 — words that carry no load.
|
|
6345
|
+
"filler-phrase": [
|
|
6346
|
+
"in order to",
|
|
6347
|
+
"due to the fact that",
|
|
6348
|
+
"at this point in time",
|
|
6349
|
+
"has the ability to",
|
|
6350
|
+
"have the ability to",
|
|
6351
|
+
"it is important to note",
|
|
6352
|
+
"it is worth noting",
|
|
6353
|
+
"con el fin de",
|
|
6354
|
+
"debido al hecho de que",
|
|
6355
|
+
"en este momento del tiempo",
|
|
6356
|
+
"tiene la capacidad de",
|
|
6357
|
+
"cabe destacar",
|
|
6358
|
+
"cabe se\xF1alar",
|
|
6359
|
+
"es importante se\xF1alar"
|
|
6360
|
+
],
|
|
6361
|
+
// §32 — ordinary claims dressed as reusable aphorisms.
|
|
6362
|
+
"aphorism-formula": [
|
|
6363
|
+
"the language of",
|
|
6364
|
+
"the currency of",
|
|
6365
|
+
"the architecture of",
|
|
6366
|
+
"the backbone of",
|
|
6367
|
+
"el lenguaje de",
|
|
6368
|
+
"la moneda de",
|
|
6369
|
+
"la columna vertebral de"
|
|
6370
|
+
]
|
|
6371
|
+
};
|
|
6372
|
+
var SENTENCE_INITIAL_TELLS = /* @__PURE__ */ new Set(["rhetorical-opener"]);
|
|
6373
|
+
var COMMA_ANCHORED_TELLS = /* @__PURE__ */ new Set(["participle-tail"]);
|
|
6374
|
+
var STACCATO_MAX_WORDS = 4;
|
|
6375
|
+
var STACCATO_RUN = 3;
|
|
6376
|
+
var SYNONYM_OVERLAP = 0.6;
|
|
6377
|
+
var HEADLINE_SET_FLOOR = 3;
|
|
6378
|
+
var CYCLED_VERBS = [
|
|
6379
|
+
["boost", "increase", "grow", "raise", "lift", "improve", "enhance", "maximize", "drive"],
|
|
6380
|
+
["cut", "reduce", "lower", "slash", "shrink", "minimize", "decrease"],
|
|
6381
|
+
["get", "obtain", "receive", "secure", "unlock", "claim"],
|
|
6382
|
+
["fast", "quick", "rapid", "swift", "speedy", "instant"],
|
|
6383
|
+
["aumenta", "incrementa", "mejora", "impulsa", "potencia", "maximiza", "multiplica"],
|
|
6384
|
+
["reduce", "recorta", "baja", "disminuye", "minimiza"],
|
|
6385
|
+
["consigue", "obt\xE9n", "logra", "alcanza", "desbloquea"],
|
|
6386
|
+
["r\xE1pido", "veloz", "inmediato", "al instante"]
|
|
6387
|
+
];
|
|
6388
|
+
|
|
6389
|
+
// src/engine/copy-tells/detect.ts
|
|
6390
|
+
var SAMPLE_MAX = 60;
|
|
6391
|
+
function sample(text) {
|
|
6392
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
6393
|
+
return clean.length <= SAMPLE_MAX ? clean : `${clean.slice(0, SAMPLE_MAX)}\u2026`;
|
|
6394
|
+
}
|
|
6395
|
+
var DELIBERATE_REGEX = /\.\{\d+,\d+\}|\(\?:[^()]*\)|\\?\?/g;
|
|
6396
|
+
function escapeLiteral(text) {
|
|
6397
|
+
return text.replace(/[.*+^$?()|[\]{}\\]/g, (ch) => `\\${ch}`);
|
|
6398
|
+
}
|
|
6399
|
+
function toPattern(phrase) {
|
|
6400
|
+
let out = "";
|
|
6401
|
+
let last = 0;
|
|
6402
|
+
for (const m of phrase.matchAll(DELIBERATE_REGEX)) {
|
|
6403
|
+
const start = m.index ?? 0;
|
|
6404
|
+
out += escapeLiteral(phrase.slice(last, start)) + m[0];
|
|
6405
|
+
last = start + m[0].length;
|
|
6406
|
+
}
|
|
6407
|
+
return out + escapeLiteral(phrase.slice(last));
|
|
6408
|
+
}
|
|
6409
|
+
var WORD_START = "(?<![\\p{L}\\p{N}])";
|
|
6410
|
+
function matcherFor(id, phrase) {
|
|
6411
|
+
const body = toPattern(phrase);
|
|
6412
|
+
if (SENTENCE_INITIAL_TELLS.has(id)) return new RegExp(`(?:^|[.!?\xBF\xA1]\\s+|\\n)\\s*${body}`, "giu");
|
|
6413
|
+
if (COMMA_ANCHORED_TELLS.has(id)) return new RegExp(`,\\s+${body}\\b`, "giu");
|
|
6414
|
+
return new RegExp(`${WORD_START}${body}`, "giu");
|
|
6415
|
+
}
|
|
6416
|
+
var FALSE_RANGE_PATTERNS = [
|
|
6417
|
+
/\bfrom\s+([^,.;]{3,30}?)\s+to\s+([^,.;]{3,30}?)(?=[,.;]|$)/gi,
|
|
6418
|
+
/\bdesde\s+([^,.;]{3,30}?)\s+hasta\s+([^,.;]{3,30}?)(?=[,.;]|$)/gi
|
|
6419
|
+
];
|
|
6420
|
+
function countFalseRanges(text) {
|
|
6421
|
+
let count = 0;
|
|
6422
|
+
let first = "";
|
|
6423
|
+
for (const re of FALSE_RANGE_PATTERNS) {
|
|
6424
|
+
for (const m of text.matchAll(re)) {
|
|
6425
|
+
const left = m[1] ?? "";
|
|
6426
|
+
const right = m[2] ?? "";
|
|
6427
|
+
if (/\d/.test(left) || /\d/.test(right)) continue;
|
|
6428
|
+
count++;
|
|
6429
|
+
if (!first) first = m[0] ?? "";
|
|
6430
|
+
}
|
|
6431
|
+
}
|
|
6432
|
+
return { count, first };
|
|
6433
|
+
}
|
|
6434
|
+
function countStaccato(text) {
|
|
6435
|
+
const sentences = text.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter(Boolean);
|
|
6436
|
+
let run = 0;
|
|
6437
|
+
let count = 0;
|
|
6438
|
+
let first = "";
|
|
6439
|
+
let runStart = 0;
|
|
6440
|
+
for (const [i, s] of sentences.entries()) {
|
|
6441
|
+
const words2 = s.split(/\s+/).filter(Boolean).length;
|
|
6442
|
+
if (words2 > 0 && words2 <= STACCATO_MAX_WORDS) {
|
|
6443
|
+
if (run === 0) runStart = i;
|
|
6444
|
+
run++;
|
|
6445
|
+
if (run === STACCATO_RUN) {
|
|
6446
|
+
count++;
|
|
6447
|
+
if (!first) first = sentences.slice(runStart, i + 1).join(" ");
|
|
6448
|
+
}
|
|
6449
|
+
} else {
|
|
6450
|
+
run = 0;
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
return { count, first };
|
|
6454
|
+
}
|
|
6455
|
+
function countTitleCaseHeadings(text) {
|
|
6456
|
+
let count = 0;
|
|
6457
|
+
let first = "";
|
|
6458
|
+
for (const m of text.matchAll(/^\s{0,3}#{1,6}\s+(.+)$/gm)) {
|
|
6459
|
+
const heading = (m[1] ?? "").trim();
|
|
6460
|
+
const words2 = heading.split(/\s+/).filter((w) => new RegExp("\\p{L}", "u").test(w));
|
|
6461
|
+
if (words2.length < 4) continue;
|
|
6462
|
+
const capitalized = words2.filter((w) => new RegExp("^\\p{Lu}", "u").test(w)).length;
|
|
6463
|
+
if (capitalized / words2.length < 0.8) continue;
|
|
6464
|
+
count++;
|
|
6465
|
+
if (!first) first = heading;
|
|
6466
|
+
}
|
|
6467
|
+
return { count, first };
|
|
6468
|
+
}
|
|
6469
|
+
function countInlineHeaderList(text) {
|
|
6470
|
+
const matches = [...text.matchAll(/^\s*(?:[-*+]|\d+\.)\s+\*\*[^*\n]{2,40}:?\*\*:?\s/gm)];
|
|
6471
|
+
if (matches.length < 3) return { count: 0, first: "" };
|
|
6472
|
+
return { count: matches.length, first: (matches[0]?.[0] ?? "").trim() };
|
|
6473
|
+
}
|
|
6474
|
+
var EMOJI = /[\u{1F300}-\u{1FAFF}]|[\u{2600}-\u{27BF}]|[\u{1F000}-\u{1F2FF}]|\u{FE0F}/gu;
|
|
6475
|
+
var CURLY = /[‘’“”]/g;
|
|
6476
|
+
function countPhraseTell(text, id, phrases) {
|
|
6477
|
+
let count = 0;
|
|
6478
|
+
let first = "";
|
|
6479
|
+
for (const phrase of phrases) {
|
|
6480
|
+
for (const m of text.matchAll(matcherFor(id, phrase))) {
|
|
6481
|
+
count++;
|
|
6482
|
+
if (!first) first = m[0] ?? "";
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
return { count, first };
|
|
6486
|
+
}
|
|
6487
|
+
function countPattern(text, re) {
|
|
6488
|
+
const matches = text.match(re) ?? [];
|
|
6489
|
+
return { count: matches.length, first: matches.join("") };
|
|
6490
|
+
}
|
|
6491
|
+
function detectCopyTells(text) {
|
|
6492
|
+
if (!text.trim()) return [];
|
|
6493
|
+
const results = [
|
|
6494
|
+
...Object.entries(PHRASE_TELLS).map(([id, phrases]) => [
|
|
6495
|
+
id,
|
|
6496
|
+
countPhraseTell(text, id, phrases)
|
|
6497
|
+
]),
|
|
6498
|
+
["false-range", countFalseRanges(text)],
|
|
6499
|
+
["staccato-drama", countStaccato(text)],
|
|
6500
|
+
["title-case-heading", countTitleCaseHeadings(text)],
|
|
6501
|
+
["inline-header-list", countInlineHeaderList(text)],
|
|
6502
|
+
["curly-quote", countPattern(text, CURLY)],
|
|
6503
|
+
["emoji", countPattern(text, EMOJI)]
|
|
6504
|
+
];
|
|
6505
|
+
return results.filter(([, result]) => result.count > 0).map(([id, result]) => ({ id, count: result.count, sample: sample(result.first) }));
|
|
6506
|
+
}
|
|
6507
|
+
function words(headline) {
|
|
6508
|
+
return headline.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter(Boolean);
|
|
6509
|
+
}
|
|
6510
|
+
function canonical(word) {
|
|
6511
|
+
for (const [i, group] of CYCLED_VERBS.entries()) if (group.includes(word)) return `group:${i}`;
|
|
6512
|
+
return word;
|
|
6513
|
+
}
|
|
6514
|
+
function overlap(a, b) {
|
|
6515
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
6516
|
+
let shared = 0;
|
|
6517
|
+
for (const w of a) if (b.has(w)) shared++;
|
|
6518
|
+
return shared / Math.max(a.size, b.size);
|
|
6519
|
+
}
|
|
6520
|
+
function detectHeadlineSetTells(headlines) {
|
|
6521
|
+
const usable = headlines.map((h) => h.trim()).filter(Boolean);
|
|
6522
|
+
if (usable.length < HEADLINE_SET_FLOOR) return [];
|
|
6523
|
+
const sets = usable.map((h) => new Set(words(h).map(canonical)));
|
|
6524
|
+
const cycled = [];
|
|
6525
|
+
for (let i = 0; i < sets.length; i++) {
|
|
6526
|
+
for (let j = i + 1; j < sets.length; j++) {
|
|
6527
|
+
if (overlap(sets[i] ?? /* @__PURE__ */ new Set(), sets[j] ?? /* @__PURE__ */ new Set()) >= SYNONYM_OVERLAP) {
|
|
6528
|
+
cycled.push(`"${usable[i]}" / "${usable[j]}"`);
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6532
|
+
if (cycled.length === 0) return [];
|
|
6533
|
+
return [{ id: "synonym-cycling", count: cycled.length, sample: sample(cycled[0] ?? "") }];
|
|
6534
|
+
}
|
|
6535
|
+
|
|
6536
|
+
// src/commands/ads/copy-hints.ts
|
|
6537
|
+
var NOT_APPLICABLE_TO_ADS = /* @__PURE__ */ new Set([
|
|
6538
|
+
"title-case-heading",
|
|
6539
|
+
"staccato-drama",
|
|
6540
|
+
"emoji",
|
|
6541
|
+
"curly-quote",
|
|
6542
|
+
"inline-header-list"
|
|
6543
|
+
]);
|
|
6544
|
+
var COPY_KEYS = /* @__PURE__ */ new Set([
|
|
6545
|
+
"headline",
|
|
6546
|
+
"headlines",
|
|
6547
|
+
"longheadline",
|
|
6548
|
+
"longheadlines",
|
|
6549
|
+
"shortheadline",
|
|
6550
|
+
"shortheadlines",
|
|
6551
|
+
"description",
|
|
6552
|
+
"descriptions",
|
|
6553
|
+
"primarytext",
|
|
6554
|
+
"primarytexts",
|
|
6555
|
+
"message",
|
|
6556
|
+
"messages",
|
|
6557
|
+
"commentary",
|
|
6558
|
+
"body",
|
|
6559
|
+
"bodies",
|
|
6560
|
+
"text",
|
|
6561
|
+
"calltoaction",
|
|
6562
|
+
"linkdescription",
|
|
6563
|
+
"sitelinktext",
|
|
6564
|
+
"callout",
|
|
6565
|
+
"callouts",
|
|
6566
|
+
"introtext"
|
|
6567
|
+
]);
|
|
6568
|
+
var HEADLINE_SET_KEYS = /* @__PURE__ */ new Set(["headlines", "longheadlines", "shortheadlines"]);
|
|
6569
|
+
var MAX_HINTS = 5;
|
|
6570
|
+
var SYNONYM_PAIR_FLOOR = 2;
|
|
6571
|
+
function asStrings(value) {
|
|
6572
|
+
const one = (v) => {
|
|
6573
|
+
const text = typeof v === "string" ? v : v !== null && typeof v === "object" ? v.text : "";
|
|
6574
|
+
return typeof text === "string" && text.trim() ? text : void 0;
|
|
6575
|
+
};
|
|
6576
|
+
const values = Array.isArray(value) ? value : [value];
|
|
6577
|
+
return values.map(one).filter((v) => v !== void 0);
|
|
6578
|
+
}
|
|
6579
|
+
function collectCopy(value, depth = 0) {
|
|
6580
|
+
const texts = [];
|
|
6581
|
+
const headlineSets = [];
|
|
6582
|
+
if (depth > 4 || value === null || typeof value !== "object") return { texts, headlineSets };
|
|
6583
|
+
if (Array.isArray(value)) {
|
|
6584
|
+
for (const item of value) {
|
|
6585
|
+
const nested = collectCopy(item, depth + 1);
|
|
6586
|
+
texts.push(...nested.texts);
|
|
6587
|
+
headlineSets.push(...nested.headlineSets);
|
|
6588
|
+
}
|
|
6589
|
+
return { texts, headlineSets };
|
|
6590
|
+
}
|
|
6591
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
6592
|
+
const normalized = key.toLowerCase().replace(/[_-]/g, "");
|
|
6593
|
+
const strings = COPY_KEYS.has(normalized) ? asStrings(raw) : [];
|
|
6594
|
+
if (strings.length > 0) {
|
|
6595
|
+
texts.push(...strings);
|
|
6596
|
+
if (HEADLINE_SET_KEYS.has(normalized) && strings.length > 1) headlineSets.push(strings);
|
|
6597
|
+
continue;
|
|
6598
|
+
}
|
|
6599
|
+
const nested = collectCopy(raw, depth + 1);
|
|
6600
|
+
texts.push(...nested.texts);
|
|
6601
|
+
headlineSets.push(...nested.headlineSets);
|
|
6602
|
+
}
|
|
6603
|
+
return { texts, headlineSets };
|
|
6604
|
+
}
|
|
6605
|
+
function hintFor(tell) {
|
|
6606
|
+
if (tell.id === "synonym-cycling") {
|
|
6607
|
+
return `AI COPY TELL \u2014 ${tell.count} headline pairs are the same claim reworded (${tell.sample}). A cycled set gives the auction nothing to choose between: make each headline a different claim, not a different verb.`;
|
|
6608
|
+
}
|
|
6609
|
+
return `AI COPY TELL (${tell.id}) \u2014 "${tell.sample}". This construction reads as machine-written; rewrite it as a plain, specific claim before publishing.`;
|
|
6610
|
+
}
|
|
6611
|
+
function adCopyHints(payload) {
|
|
6612
|
+
const { texts, headlineSets } = collectCopy(payload);
|
|
6613
|
+
if (texts.length === 0) return [];
|
|
6614
|
+
const tells = detectCopyTells(texts.join("\n")).filter((t) => !NOT_APPLICABLE_TO_ADS.has(t.id));
|
|
6615
|
+
for (const set of headlineSets) {
|
|
6616
|
+
for (const tell of detectHeadlineSetTells(set)) {
|
|
6617
|
+
if (tell.count >= SYNONYM_PAIR_FLOOR) tells.push(tell);
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6620
|
+
tells.sort(
|
|
6621
|
+
(a, b) => (b.id === "synonym-cycling" ? 1 : 0) - (a.id === "synonym-cycling" ? 1 : 0) || b.count - a.count
|
|
6622
|
+
);
|
|
6623
|
+
return tells.slice(0, MAX_HINTS).map(hintFor);
|
|
6624
|
+
}
|
|
6625
|
+
|
|
6182
6626
|
// src/commands/ads/google/draft-status.ts
|
|
6183
6627
|
var OPERATION_LABELS = {
|
|
6184
6628
|
create: "Creating",
|
|
@@ -6450,7 +6894,7 @@ async function stageGoogleOp(raw, hints) {
|
|
|
6450
6894
|
try {
|
|
6451
6895
|
const chatId = requireChatId();
|
|
6452
6896
|
const response = await apiPost("/api/ads/google/draft/stage", { chatId, op: preflight.data });
|
|
6453
|
-
const allHints = [...noopHints(response.data), ...hints ?? []];
|
|
6897
|
+
const allHints = [...noopHints(response.data), ...hints ?? [], ...adCopyHints(preflight.data)];
|
|
6454
6898
|
writeJsonEnvelope(allHints.length > 0 ? { ...response, hints: allHints } : response);
|
|
6455
6899
|
} catch (err) {
|
|
6456
6900
|
handleGoogleError(err);
|
|
@@ -6492,15 +6936,16 @@ async function stageGoogleOps(rawOps, hints) {
|
|
|
6492
6936
|
...skipped.length > 0 ? [
|
|
6493
6937
|
`${skipped.length} of ${ops.length} op(s) NOT STAGED \u2014 already in the requested state: ${skipped.map((s) => s.reason).join("; ")}`
|
|
6494
6938
|
] : [],
|
|
6495
|
-
...hints ?? []
|
|
6939
|
+
...hints ?? [],
|
|
6940
|
+
...adCopyHints(ops)
|
|
6496
6941
|
];
|
|
6497
6942
|
writeJsonEnvelope(allHints.length > 0 ? { ...response, hints: allHints } : response);
|
|
6498
6943
|
} catch (err) {
|
|
6499
6944
|
handleGoogleError(err);
|
|
6500
6945
|
}
|
|
6501
6946
|
}
|
|
6502
|
-
async function stageUpdate(kind, customerId, target, payload) {
|
|
6503
|
-
await stageGoogleOp({ kind, customerId, target, payload });
|
|
6947
|
+
async function stageUpdate(kind, customerId, target, payload, hints) {
|
|
6948
|
+
await stageGoogleOp({ kind, customerId, target, payload }, hints);
|
|
6504
6949
|
}
|
|
6505
6950
|
async function stageTarget(kind, customerId, target) {
|
|
6506
6951
|
await stageGoogleOp({ kind, customerId, target });
|
|
@@ -8377,6 +8822,25 @@ function demandGenContentFromFlags(args, base) {
|
|
|
8377
8822
|
}
|
|
8378
8823
|
return content;
|
|
8379
8824
|
}
|
|
8825
|
+
function appContentFromFlags(args, base) {
|
|
8826
|
+
const content = {
|
|
8827
|
+
format: "app",
|
|
8828
|
+
...base !== null && typeof base === "object" && !Array.isArray(base) ? base : {}
|
|
8829
|
+
};
|
|
8830
|
+
const headlines = listFlag(args.headlines);
|
|
8831
|
+
if (headlines) {
|
|
8832
|
+
content.headlines = headlines.map((text) => ({ text }));
|
|
8833
|
+
}
|
|
8834
|
+
const descriptions = listFlag(args.descriptions);
|
|
8835
|
+
if (descriptions) {
|
|
8836
|
+
content.descriptions = descriptions.map((text) => ({ text }));
|
|
8837
|
+
}
|
|
8838
|
+
const images = listFlag(args["image-assets"]);
|
|
8839
|
+
if (images) {
|
|
8840
|
+
content.images = images;
|
|
8841
|
+
}
|
|
8842
|
+
return content;
|
|
8843
|
+
}
|
|
8380
8844
|
function adContentFromFlags(args, format, fileContent) {
|
|
8381
8845
|
if (format === "responsiveSearch") {
|
|
8382
8846
|
return fileContent ?? rsaContentFromFlags(args);
|
|
@@ -8390,8 +8854,48 @@ function adContentFromFlags(args, format, fileContent) {
|
|
|
8390
8854
|
if (format === "demandGen") {
|
|
8391
8855
|
return demandGenContentFromFlags(args, fileContent);
|
|
8392
8856
|
}
|
|
8857
|
+
if (format === "app") {
|
|
8858
|
+
return appContentFromFlags(args, fileContent);
|
|
8859
|
+
}
|
|
8393
8860
|
return fileContent ?? failWriteValidation(`--format ${format} needs --file with the ad content`);
|
|
8394
8861
|
}
|
|
8862
|
+
var adContentArgs = {
|
|
8863
|
+
format: {
|
|
8864
|
+
type: "string",
|
|
8865
|
+
description: "responsiveSearch (default) | responsiveDisplay | call | app | video | demandGen"
|
|
8866
|
+
},
|
|
8867
|
+
headlines: { type: "string", description: "Comma-separated headlines (RSA/RDA/demandGen/app)" },
|
|
8868
|
+
descriptions: { type: "string", description: "Comma-separated descriptions (RSA/RDA/demandGen/app)" },
|
|
8869
|
+
path1: { type: "string" },
|
|
8870
|
+
path2: { type: "string" },
|
|
8871
|
+
"long-headline": { type: "string", description: "Long headline (responsiveDisplay)" },
|
|
8872
|
+
"business-name": { type: "string", description: "Business name (responsiveDisplay/demandGen)" },
|
|
8873
|
+
"marketing-images": {
|
|
8874
|
+
type: "string",
|
|
8875
|
+
description: "Comma-separated marketing image asset refs (responsiveDisplay)"
|
|
8876
|
+
},
|
|
8877
|
+
"square-marketing-images": {
|
|
8878
|
+
type: "string",
|
|
8879
|
+
description: "Comma-separated square marketing image asset refs (responsiveDisplay)"
|
|
8880
|
+
},
|
|
8881
|
+
"logo-images": { type: "string", description: "Comma-separated logo image asset refs (responsiveDisplay)" },
|
|
8882
|
+
"video-assets": {
|
|
8883
|
+
type: "string",
|
|
8884
|
+
description: `Comma-separated video asset refs (video format) \u2014 stage the YouTube video as an asset first (assets create --file '{"type":"youtubeVideo",\u2026}')`
|
|
8885
|
+
},
|
|
8886
|
+
"image-assets": { type: "string", description: "Comma-separated image asset refs (demandGen/app)" },
|
|
8887
|
+
"square-image-assets": { type: "string", description: "Comma-separated square image asset refs (demandGen)" },
|
|
8888
|
+
"logo-image-assets": { type: "string", description: "Comma-separated logo image asset refs (demandGen)" },
|
|
8889
|
+
"final-url": { type: "string", description: "Comma-separated final URLs" }
|
|
8890
|
+
};
|
|
8891
|
+
function assertNoContentFlags(args) {
|
|
8892
|
+
const passed = Object.keys(adContentArgs).filter((flag) => flag !== "format" && args[flag] !== void 0);
|
|
8893
|
+
if (passed.length > 0) {
|
|
8894
|
+
failWriteValidation(
|
|
8895
|
+
`${passed.map((flag) => `--${flag}`).join(", ")} only build an ad's content alongside --format (responsiveSearch | responsiveDisplay | call | app | video | demandGen) \u2014 add it, or pass --file with the complete content`
|
|
8896
|
+
);
|
|
8897
|
+
}
|
|
8898
|
+
}
|
|
8395
8899
|
var adsCommand = defineCommand30({
|
|
8396
8900
|
meta: { name: "ads", description: "Stage ad create/update/pause/resume/remove" },
|
|
8397
8901
|
subCommands: {
|
|
@@ -8404,33 +8908,7 @@ var adsCommand = defineCommand30({
|
|
|
8404
8908
|
...customerIdArg,
|
|
8405
8909
|
...fileArg,
|
|
8406
8910
|
"ad-group-ref": { type: "string", description: "Ad group ref" },
|
|
8407
|
-
|
|
8408
|
-
type: "string",
|
|
8409
|
-
description: "responsiveSearch (default) | responsiveDisplay | call | app | video | demandGen"
|
|
8410
|
-
},
|
|
8411
|
-
headlines: { type: "string", description: "Comma-separated headlines (RSA/RDA/demandGen)" },
|
|
8412
|
-
descriptions: { type: "string", description: "Comma-separated descriptions (RSA/RDA/demandGen)" },
|
|
8413
|
-
path1: { type: "string" },
|
|
8414
|
-
path2: { type: "string" },
|
|
8415
|
-
"long-headline": { type: "string", description: "Long headline (responsiveDisplay)" },
|
|
8416
|
-
"business-name": { type: "string", description: "Business name (responsiveDisplay/demandGen)" },
|
|
8417
|
-
"marketing-images": {
|
|
8418
|
-
type: "string",
|
|
8419
|
-
description: "Comma-separated marketing image asset refs (responsiveDisplay)"
|
|
8420
|
-
},
|
|
8421
|
-
"square-marketing-images": {
|
|
8422
|
-
type: "string",
|
|
8423
|
-
description: "Comma-separated square marketing image asset refs (responsiveDisplay)"
|
|
8424
|
-
},
|
|
8425
|
-
"logo-images": { type: "string", description: "Comma-separated logo image asset refs (responsiveDisplay)" },
|
|
8426
|
-
"video-assets": {
|
|
8427
|
-
type: "string",
|
|
8428
|
-
description: `Comma-separated video asset refs (video format) \u2014 stage the YouTube video as an asset first (assets create --file '{"type":"youtubeVideo",\u2026}')`
|
|
8429
|
-
},
|
|
8430
|
-
"image-assets": { type: "string", description: "Comma-separated image asset refs (demandGen)" },
|
|
8431
|
-
"square-image-assets": { type: "string", description: "Comma-separated square image asset refs (demandGen)" },
|
|
8432
|
-
"logo-image-assets": { type: "string", description: "Comma-separated logo image asset refs (demandGen)" },
|
|
8433
|
-
"final-url": { type: "string", description: "Comma-separated final URLs" },
|
|
8911
|
+
...adContentArgs,
|
|
8434
8912
|
status: { type: "string" }
|
|
8435
8913
|
},
|
|
8436
8914
|
run: async ({ args }) => {
|
|
@@ -8446,17 +8924,27 @@ var adsCommand = defineCommand30({
|
|
|
8446
8924
|
}
|
|
8447
8925
|
}),
|
|
8448
8926
|
update: defineCommand30({
|
|
8449
|
-
meta: {
|
|
8450
|
-
|
|
8927
|
+
meta: {
|
|
8928
|
+
name: "update",
|
|
8929
|
+
description: "Stage an ad update. Any content change needs --format plus EVERY required field of that format (or --file with the complete content) \u2014 a field you send replaces its current value wholesale, so a list you pass must already include the entries you want to keep. Optional asset lists you omit entirely are left untouched on the ad. Status-only changes need neither --format nor content flags."
|
|
8930
|
+
},
|
|
8931
|
+
args: { ...customerIdArg, ...fileArg, ...adContentArgs, status: { type: "string" } },
|
|
8451
8932
|
run: async ({ args }) => {
|
|
8452
8933
|
const customerId = requireCustomerId(args);
|
|
8453
8934
|
const file = loadJsonFileArg(args.file);
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8935
|
+
const format = args.format;
|
|
8936
|
+
if (!format) {
|
|
8937
|
+
assertNoContentFlags(args);
|
|
8938
|
+
}
|
|
8939
|
+
const payload = mergePayload(file, {
|
|
8940
|
+
status: args.status,
|
|
8941
|
+
...format ? { content: adContentFromFlags(args, format, file.content) } : {}
|
|
8942
|
+
});
|
|
8943
|
+
await stageUpdate("google.ad.update", customerId, requireTarget(args, "ad"), payload, [
|
|
8944
|
+
...payload.content !== void 0 ? [
|
|
8945
|
+
"Each content field you sent replaces its current value wholesale at publish \u2014 re-check that every headline, description, final URL and asset you want to KEEP is in this payload, not just the ones you changed."
|
|
8946
|
+
] : []
|
|
8947
|
+
]);
|
|
8460
8948
|
}
|
|
8461
8949
|
}),
|
|
8462
8950
|
pause: statusCommand2("google.ad.pause", "ad"),
|
|
@@ -10535,7 +11023,7 @@ async function stageOp(raw, hints) {
|
|
|
10535
11023
|
chatId,
|
|
10536
11024
|
op: preflight.data
|
|
10537
11025
|
});
|
|
10538
|
-
const allHints = [...batchNudge(response.data), ...hints ?? []];
|
|
11026
|
+
const allHints = [...batchNudge(response.data), ...hints ?? [], ...adCopyHints(preflight.data)];
|
|
10539
11027
|
writeJsonEnvelope(allHints.length > 0 ? { ...response, hints: allHints } : response);
|
|
10540
11028
|
} catch (err) {
|
|
10541
11029
|
handleLinkedinError(err);
|
|
@@ -10562,7 +11050,8 @@ async function stageLinkedinOps(rawOps, hints) {
|
|
|
10562
11050
|
try {
|
|
10563
11051
|
const chatId = requireChatId();
|
|
10564
11052
|
const response = await apiPost("/api/ads/linkedin/draft/stage-batch", { chatId, ops });
|
|
10565
|
-
|
|
11053
|
+
const allHints = [...hints ?? [], ...adCopyHints(ops)];
|
|
11054
|
+
writeJsonEnvelope(allHints.length > 0 ? { ...response, hints: allHints } : response);
|
|
10566
11055
|
} catch (err) {
|
|
10567
11056
|
handleLinkedinError(err);
|
|
10568
11057
|
}
|
|
@@ -13648,7 +14137,8 @@ async function stageOp2(raw) {
|
|
|
13648
14137
|
try {
|
|
13649
14138
|
const chatId = requireChatId();
|
|
13650
14139
|
const response = await apiPost("/api/ads/meta/draft/stage", { chatId, op: preflight.data });
|
|
13651
|
-
|
|
14140
|
+
const copyHints = adCopyHints(preflight.data);
|
|
14141
|
+
writeJsonEnvelope(copyHints.length > 0 ? { ...response, hints: copyHints } : response);
|
|
13652
14142
|
} catch (err) {
|
|
13653
14143
|
handleMetaError(err);
|
|
13654
14144
|
}
|
|
@@ -17507,10 +17997,10 @@ function estSpeechS(text) {
|
|
|
17507
17997
|
var OBSERVED_WPS_MIN = 1;
|
|
17508
17998
|
var OBSERVED_WPS_MAX = 6;
|
|
17509
17999
|
function estSpeechWindowS(text, startS, endS) {
|
|
17510
|
-
const
|
|
18000
|
+
const words2 = wordCount(text);
|
|
17511
18001
|
const window = (endS ?? 0) - (startS ?? 0);
|
|
17512
|
-
if (
|
|
17513
|
-
const wps =
|
|
18002
|
+
if (words2 > 0 && window > 0.3) {
|
|
18003
|
+
const wps = words2 / window;
|
|
17514
18004
|
if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return window;
|
|
17515
18005
|
}
|
|
17516
18006
|
return estSpeechS(text);
|
|
@@ -18362,10 +18852,10 @@ function scrubFloatSentences(text, floatDescs) {
|
|
|
18362
18852
|
if (floatDescs.length === 0 || !text) return text;
|
|
18363
18853
|
const tokenSets = floatDescs.map((d) => new Set(floatTokens(d)));
|
|
18364
18854
|
const kept = text.split(/(?<=[.!?])\s+/).filter((sentence) => {
|
|
18365
|
-
const
|
|
18855
|
+
const words2 = new Set(floatTokens(sentence));
|
|
18366
18856
|
return !tokenSets.some((ts) => {
|
|
18367
18857
|
let hits = 0;
|
|
18368
|
-
for (const w of
|
|
18858
|
+
for (const w of words2) if (ts.has(w)) hits++;
|
|
18369
18859
|
return hits >= 2;
|
|
18370
18860
|
});
|
|
18371
18861
|
}).join(" ").trim();
|
|
@@ -19095,10 +19585,10 @@ function isOnCameraSpeaker(speaker, casts, cameraOn) {
|
|
|
19095
19585
|
if (NARRATOR_SPEAKERS.has(speaker.toLowerCase())) return false;
|
|
19096
19586
|
return casts.has(speaker);
|
|
19097
19587
|
}
|
|
19098
|
-
function makePresenterPresent(slots,
|
|
19588
|
+
function makePresenterPresent(slots, canonical2, opts = {}) {
|
|
19099
19589
|
const personSlots = slots.filter((s) => s.type.toLowerCase() === "person");
|
|
19100
19590
|
const bySpeaker = /* @__PURE__ */ new Map();
|
|
19101
|
-
for (const slot of personSlots) if (slot.castId) bySpeaker.set(
|
|
19591
|
+
for (const slot of personSlots) if (slot.castId) bySpeaker.set(canonical2(slot.castId), slot.presence);
|
|
19102
19592
|
const solePerson = !opts.strict && personSlots.length === 1 ? personSlots[0].presence : null;
|
|
19103
19593
|
return (speaker, sceneIndex) => {
|
|
19104
19594
|
const presence = bySpeaker.get(speaker) ?? solePerson;
|
|
@@ -19130,17 +19620,17 @@ function joinDialogueTexts(texts) {
|
|
|
19130
19620
|
}
|
|
19131
19621
|
const prevWords = prev.split(/\s+/).filter(Boolean);
|
|
19132
19622
|
const nextWords = text.split(/\s+/).filter(Boolean);
|
|
19133
|
-
let
|
|
19623
|
+
let overlap2 = 0;
|
|
19134
19624
|
const maxK = Math.min(JOIN_DEDUP_MAX_WORDS, prevWords.length, nextWords.length);
|
|
19135
19625
|
for (let k = maxK; k >= 1; k--) {
|
|
19136
19626
|
const tail = prevWords.slice(-k).map(joinKey).join(" ");
|
|
19137
19627
|
const head = nextWords.slice(0, k).map(joinKey).join(" ");
|
|
19138
19628
|
if (tail && tail === head) {
|
|
19139
|
-
|
|
19629
|
+
overlap2 = k;
|
|
19140
19630
|
break;
|
|
19141
19631
|
}
|
|
19142
19632
|
}
|
|
19143
|
-
const rest = nextWords.slice(
|
|
19633
|
+
const rest = nextWords.slice(overlap2).join(" ");
|
|
19144
19634
|
if (rest) out.push(rest);
|
|
19145
19635
|
}
|
|
19146
19636
|
return out.join(" ");
|
|
@@ -19158,13 +19648,13 @@ function collapseVoiceover(blueprint) {
|
|
|
19158
19648
|
const presenter = [...presenters][0];
|
|
19159
19649
|
return (speaker) => NARRATOR_SPEAKERS.has(speaker.toLowerCase()) ? presenter : speaker;
|
|
19160
19650
|
}
|
|
19161
|
-
function multiSpeakerScenes(blueprint, casts, cameraOn,
|
|
19651
|
+
function multiSpeakerScenes(blueprint, casts, cameraOn, canonical2, presentStrict) {
|
|
19162
19652
|
const multiSpeaker = /* @__PURE__ */ new Set();
|
|
19163
19653
|
blueprint.scenes.forEach((scene, i) => {
|
|
19164
19654
|
const onCamAll = new Set(
|
|
19165
19655
|
(scene.dialogue ?? []).map((l) => l.speaker ?? "voiceover").filter((sp) => isOnCameraSpeaker(sp, casts, cameraOn))
|
|
19166
19656
|
);
|
|
19167
|
-
const onCamPresent = [...onCamAll].filter((sp) => presentStrict(
|
|
19657
|
+
const onCamPresent = [...onCamAll].filter((sp) => presentStrict(canonical2(sp), i));
|
|
19168
19658
|
const effective = onCamPresent.length > 0 ? new Set(onCamPresent) : onCamAll;
|
|
19169
19659
|
if (effective.size >= 2) multiSpeaker.add(i);
|
|
19170
19660
|
});
|
|
@@ -19195,14 +19685,14 @@ function dialogueLines(blueprint, ctx) {
|
|
|
19195
19685
|
});
|
|
19196
19686
|
}).sort((a, b) => a.start - b.start);
|
|
19197
19687
|
}
|
|
19198
|
-
function buildPhrases(blueprint,
|
|
19688
|
+
function buildPhrases(blueprint, canonical2, compositeScenes, presenterPresent, presentStrict) {
|
|
19199
19689
|
const casts = castIdSet(blueprint);
|
|
19200
19690
|
const cameraOn = onCameraDialogue(blueprint);
|
|
19201
|
-
const multiSpeaker = multiSpeakerScenes(blueprint, casts, cameraOn,
|
|
19691
|
+
const multiSpeaker = multiSpeakerScenes(blueprint, casts, cameraOn, canonical2, presentStrict);
|
|
19202
19692
|
const lines = dialogueLines(blueprint, {
|
|
19203
19693
|
compositeScenes,
|
|
19204
19694
|
multiSpeaker,
|
|
19205
|
-
canonical,
|
|
19695
|
+
canonical: canonical2,
|
|
19206
19696
|
casts,
|
|
19207
19697
|
cameraOn,
|
|
19208
19698
|
presenterPresent
|
|
@@ -19251,12 +19741,12 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
|
|
|
19251
19741
|
flush();
|
|
19252
19742
|
return phrases;
|
|
19253
19743
|
}
|
|
19254
|
-
function makeVoiceFactory(blueprint,
|
|
19744
|
+
function makeVoiceFactory(blueprint, canonical2, nodes) {
|
|
19255
19745
|
const bySpeaker = /* @__PURE__ */ new Map();
|
|
19256
19746
|
const describe = (speaker) => {
|
|
19257
19747
|
for (const scene of blueprint.scenes)
|
|
19258
19748
|
for (const line of scene.dialogue ?? [])
|
|
19259
|
-
if (
|
|
19749
|
+
if (canonical2(line.speaker ?? "voiceover") === speaker && line.voice_description) return line.voice_description;
|
|
19260
19750
|
const cast = blueprint.global?.cast?.find((c) => c.id === speaker);
|
|
19261
19751
|
return cast?.description ?? blueprint.global?.voiceover?.voice_description ?? `${speaker} voice`;
|
|
19262
19752
|
};
|
|
@@ -19443,15 +19933,15 @@ function emitPhraseTts(phrase, voiceNode, idx, used, clock, nodes, out, language
|
|
|
19443
19933
|
speaker: phrase.speaker
|
|
19444
19934
|
});
|
|
19445
19935
|
}
|
|
19446
|
-
function emitCompositeInTimeline(composite, scene, i, isLast, env,
|
|
19936
|
+
function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical2, ensureVoiceNode, usedVoIds, nodes, out) {
|
|
19447
19937
|
const present2 = slotsForScene(env.slots, i);
|
|
19448
19938
|
const onCam = (scene.dialogue ?? []).filter(
|
|
19449
19939
|
(l) => Boolean(l.line?.trim()) && isOnCameraSpeaker(l.speaker ?? "voiceover", env.casts, env.cameraOn)
|
|
19450
19940
|
);
|
|
19451
|
-
const distinctSpeakers = new Set(onCam.map((l) =>
|
|
19941
|
+
const distinctSpeakers = new Set(onCam.map((l) => canonical2(l.speaker ?? "voiceover")));
|
|
19452
19942
|
let nativeTurn;
|
|
19453
19943
|
if (onCam.length > 0 && distinctSpeakers.size === 1) {
|
|
19454
|
-
const speaker =
|
|
19944
|
+
const speaker = canonical2(onCam[0]?.speaker ?? "voiceover");
|
|
19455
19945
|
const voiceNode = ensureVoiceNode(speaker);
|
|
19456
19946
|
const start = onCam[0]?.start_s ?? scene.start_s ?? 0;
|
|
19457
19947
|
const end = onCam[onCam.length - 1]?.end_s ?? scene.end_s ?? start;
|
|
@@ -19496,7 +19986,7 @@ function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, en
|
|
|
19496
19986
|
onCam,
|
|
19497
19987
|
scene,
|
|
19498
19988
|
i,
|
|
19499
|
-
|
|
19989
|
+
canonical2,
|
|
19500
19990
|
ensureVoiceNode,
|
|
19501
19991
|
usedVoIds,
|
|
19502
19992
|
env.clock,
|
|
@@ -19506,10 +19996,10 @@ function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, en
|
|
|
19506
19996
|
);
|
|
19507
19997
|
}
|
|
19508
19998
|
}
|
|
19509
|
-
function emitCompositeMultiSpeakerVoice(onCam, scene, i,
|
|
19999
|
+
function emitCompositeMultiSpeakerVoice(onCam, scene, i, canonical2, ensureVoiceNode, usedVoIds, clock, nodes, out, languageCode) {
|
|
19510
20000
|
const bySpeaker = /* @__PURE__ */ new Map();
|
|
19511
20001
|
for (const l of onCam) {
|
|
19512
|
-
const speaker =
|
|
20002
|
+
const speaker = canonical2(l.speaker ?? "voiceover");
|
|
19513
20003
|
const text = l.line.trim();
|
|
19514
20004
|
const start = l.start_s ?? scene.start_s ?? 0;
|
|
19515
20005
|
const end = l.end_s ?? start + estSpeechS(text);
|
|
@@ -19662,8 +20152,8 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
19662
20152
|
if (layeredComposition(s, uiRouted.has(i))) compositeScenes.add(i);
|
|
19663
20153
|
});
|
|
19664
20154
|
}
|
|
19665
|
-
const
|
|
19666
|
-
const ensureVoiceNode = makeVoiceFactory(blueprint,
|
|
20155
|
+
const canonical2 = collapseVoiceover(blueprint);
|
|
20156
|
+
const ensureVoiceNode = makeVoiceFactory(blueprint, canonical2, nodes);
|
|
19667
20157
|
const aspect = resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel));
|
|
19668
20158
|
const env = {
|
|
19669
20159
|
blueprint,
|
|
@@ -19690,9 +20180,9 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
19690
20180
|
nativeSegments: [],
|
|
19691
20181
|
sceneSlice: /* @__PURE__ */ new Map()
|
|
19692
20182
|
};
|
|
19693
|
-
const presenterPresent = makePresenterPresent(slots,
|
|
19694
|
-
const presentStrict = makePresenterPresent(slots,
|
|
19695
|
-
const phrases = buildPhrases(blueprint,
|
|
20183
|
+
const presenterPresent = makePresenterPresent(slots, canonical2);
|
|
20184
|
+
const presentStrict = makePresenterPresent(slots, canonical2, { strict: true });
|
|
20185
|
+
const phrases = buildPhrases(blueprint, canonical2, compositeScenes, presenterPresent, presentStrict);
|
|
19696
20186
|
const usedVoIds = /* @__PURE__ */ new Set();
|
|
19697
20187
|
const claimed = /* @__PURE__ */ new Set();
|
|
19698
20188
|
phrases.forEach((phrase, k) => {
|
|
@@ -19716,7 +20206,7 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
19716
20206
|
i,
|
|
19717
20207
|
i === lastIndex,
|
|
19718
20208
|
env,
|
|
19719
|
-
|
|
20209
|
+
canonical2,
|
|
19720
20210
|
ensureVoiceNode,
|
|
19721
20211
|
usedVoIds,
|
|
19722
20212
|
nodes,
|
|
@@ -28521,6 +29011,24 @@ var BUZZWORDS = [
|
|
|
28521
29011
|
"drive results",
|
|
28522
29012
|
"harness the power"
|
|
28523
29013
|
];
|
|
29014
|
+
var COPY_TELL_GATES = {
|
|
29015
|
+
"chat-artifact": { minCount: 1, severity: "warn" },
|
|
29016
|
+
"rhetorical-opener": { minCount: 2, severity: "warn" },
|
|
29017
|
+
"negation-pivot": { minCount: 2, severity: "warn" },
|
|
29018
|
+
"vague-attribution": { minCount: 2, severity: "warn" },
|
|
29019
|
+
"participle-tail": { minCount: 2, severity: "advisory" },
|
|
29020
|
+
"copula-avoidance": { minCount: 2, severity: "advisory" },
|
|
29021
|
+
"authority-trope": { minCount: 2, severity: "advisory" },
|
|
29022
|
+
signposting: { minCount: 2, severity: "advisory" },
|
|
29023
|
+
"hollow-conclusion": { minCount: 2, severity: "advisory" },
|
|
29024
|
+
"filler-phrase": { minCount: 2, severity: "advisory" },
|
|
29025
|
+
"aphorism-formula": { minCount: 2, severity: "advisory" },
|
|
29026
|
+
"false-range": { minCount: 2, severity: "advisory" },
|
|
29027
|
+
"staccato-drama": { minCount: 2, severity: "advisory" },
|
|
29028
|
+
"title-case-heading": { minCount: 2, severity: "advisory" },
|
|
29029
|
+
"inline-header-list": { minCount: 3, severity: "advisory" },
|
|
29030
|
+
emoji: { minCount: 3, severity: "advisory" }
|
|
29031
|
+
};
|
|
28524
29032
|
var AUTONOMY_CLAIMS = [
|
|
28525
29033
|
"fully autonomous",
|
|
28526
29034
|
"completely autonomous",
|
|
@@ -28678,6 +29186,86 @@ var RULE_META = {
|
|
|
28678
29186
|
severity: "warn",
|
|
28679
29187
|
note: "Dense em-dashes are an AI cadence tell (and brand copy bans them). Recast with commas, colons, or full stops."
|
|
28680
29188
|
},
|
|
29189
|
+
"chat-artifact": {
|
|
29190
|
+
family: "copy",
|
|
29191
|
+
severity: "warn",
|
|
29192
|
+
note: "Chatbot framing ('Here is an overview\u2026', 'av\xEDsame si') leaked into the page. Delete it and state the claim directly."
|
|
29193
|
+
},
|
|
29194
|
+
"rhetorical-opener": {
|
|
29195
|
+
family: "copy",
|
|
29196
|
+
severity: "warn",
|
|
29197
|
+
note: "Fake-candid openers ('Honestly?', 'La verdad es que') manufacture intimacy before an ordinary claim. Just say the thing."
|
|
29198
|
+
},
|
|
29199
|
+
"negation-pivot": {
|
|
29200
|
+
family: "copy",
|
|
29201
|
+
severity: "warn",
|
|
29202
|
+
note: "'It's not just X, it's Y' / 'no se trata solo de' is now a louder AI tell than any single word. Cut the setup, keep the claim."
|
|
29203
|
+
},
|
|
29204
|
+
"vague-attribution": {
|
|
29205
|
+
family: "copy",
|
|
29206
|
+
severity: "warn",
|
|
29207
|
+
note: "'Experts agree' / 'los estudios demuestran' attributes to nobody. Name the source and the year, or drop the claim."
|
|
29208
|
+
},
|
|
29209
|
+
"participle-tail": {
|
|
29210
|
+
family: "copy",
|
|
29211
|
+
severity: "advisory",
|
|
29212
|
+
note: "Participles hung off a comma (', ensuring\u2026', ', permitiendo\u2026') fake depth. End the sentence, or make it a real clause."
|
|
29213
|
+
},
|
|
29214
|
+
"copula-avoidance": {
|
|
29215
|
+
family: "copy",
|
|
29216
|
+
severity: "advisory",
|
|
29217
|
+
note: "'serves as' / 'cuenta con' dodges plain 'is' and 'has'. Use the simple verb \u2014 it reads as confidence, not flatness."
|
|
29218
|
+
},
|
|
29219
|
+
"authority-trope": {
|
|
29220
|
+
family: "copy",
|
|
29221
|
+
severity: "advisory",
|
|
29222
|
+
note: "'At its core' / 'en el fondo' pretends to reach a deeper truth before restating an ordinary point. Make the point once."
|
|
29223
|
+
},
|
|
29224
|
+
signposting: {
|
|
29225
|
+
family: "copy",
|
|
29226
|
+
severity: "advisory",
|
|
29227
|
+
note: "Announcing the writing ('Let's dive in', 'vamos a ver') instead of doing it. Delete the announcement, keep the content."
|
|
29228
|
+
},
|
|
29229
|
+
"hollow-conclusion": {
|
|
29230
|
+
family: "copy",
|
|
29231
|
+
severity: "advisory",
|
|
29232
|
+
note: "'The future looks bright' / 'las posibilidades son infinitas' asserts nothing. End on a concrete next step or a number."
|
|
29233
|
+
},
|
|
29234
|
+
"filler-phrase": {
|
|
29235
|
+
family: "copy",
|
|
29236
|
+
severity: "advisory",
|
|
29237
|
+
note: "Filler ('in order to', 'cabe destacar que') costs words and says nothing. Cut to the verb."
|
|
29238
|
+
},
|
|
29239
|
+
"aphorism-formula": {
|
|
29240
|
+
family: "copy",
|
|
29241
|
+
severity: "advisory",
|
|
29242
|
+
note: "'X is the currency of Y' sounds profound without adding precision. Replace it with the concrete claim it gestures at."
|
|
29243
|
+
},
|
|
29244
|
+
"false-range": {
|
|
29245
|
+
family: "copy",
|
|
29246
|
+
severity: "advisory",
|
|
29247
|
+
note: "'From onboarding to retention' pairs ends that aren't on a scale. List what you actually cover."
|
|
29248
|
+
},
|
|
29249
|
+
"staccato-drama": {
|
|
29250
|
+
family: "copy",
|
|
29251
|
+
severity: "advisory",
|
|
29252
|
+
note: "A run of very short sentences manufactures drama. One clipped line lands; three in a row read as engineered."
|
|
29253
|
+
},
|
|
29254
|
+
"title-case-heading": {
|
|
29255
|
+
family: "copy",
|
|
29256
|
+
severity: "advisory",
|
|
29257
|
+
note: "Title Case headings are an AI tell \u2014 and in Spanish the convention doesn't exist at all. Use sentence case."
|
|
29258
|
+
},
|
|
29259
|
+
"inline-header-list": {
|
|
29260
|
+
family: "copy",
|
|
29261
|
+
severity: "advisory",
|
|
29262
|
+
note: "'**Speed:** it is fast' bullets are AI list shape. Write the sentence, or let the bullet carry the content directly."
|
|
29263
|
+
},
|
|
29264
|
+
emoji: {
|
|
29265
|
+
family: "copy",
|
|
29266
|
+
severity: "advisory",
|
|
29267
|
+
note: "Emoji-decorated copy is a chatbot habit. Let the words and the layout carry the emphasis."
|
|
29268
|
+
},
|
|
28681
29269
|
"dark-glow": {
|
|
28682
29270
|
family: "borders_depth",
|
|
28683
29271
|
severity: "warn",
|
|
@@ -28880,6 +29468,14 @@ function includesPhraseAtWordStart(haystack, phrase) {
|
|
|
28880
29468
|
function stripHtmlToText(html) {
|
|
28881
29469
|
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ");
|
|
28882
29470
|
}
|
|
29471
|
+
function stripHtmlPreservingHeadings(html) {
|
|
29472
|
+
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(
|
|
29473
|
+
/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi,
|
|
29474
|
+
(_all, _lvl, inner) => `
|
|
29475
|
+
## ${String(inner).replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim()}
|
|
29476
|
+
`
|
|
29477
|
+
).replace(/<\/(?:p|div|section|li|tr|br)>|<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, " ").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n");
|
|
29478
|
+
}
|
|
28883
29479
|
function blankAstroFrontmatter(text) {
|
|
28884
29480
|
if (!text.startsWith("---")) return text;
|
|
28885
29481
|
const lines = text.split("\n");
|
|
@@ -29064,6 +29660,23 @@ var ANALYZERS = [
|
|
|
29064
29660
|
return [{ id: "aphoristic-cadence", snippet: `${count} aphoristic constructions: "${firstSample}"`, file }];
|
|
29065
29661
|
}
|
|
29066
29662
|
},
|
|
29663
|
+
// Shared AI copy tells (engine/copy-tells) — the Wikipedia "Signs of AI
|
|
29664
|
+
// writing" set, English and Spanish. Page-scope: every gate here is a count
|
|
29665
|
+
// threshold, and a landing is always split across small components, so
|
|
29666
|
+
// measuring per file would dilute a page-wide habit into nothing.
|
|
29667
|
+
{
|
|
29668
|
+
scope: "page",
|
|
29669
|
+
run: (text, file) => {
|
|
29670
|
+
const body = stripHtmlPreservingHeadings(text);
|
|
29671
|
+
const out = [];
|
|
29672
|
+
for (const tell of detectCopyTells(body)) {
|
|
29673
|
+
const gate = COPY_TELL_GATES[tell.id];
|
|
29674
|
+
if (!gate || tell.count < gate.minCount) continue;
|
|
29675
|
+
out.push({ id: tell.id, snippet: `${tell.count}\xD7 \u2014 "${tell.sample}"`, file });
|
|
29676
|
+
}
|
|
29677
|
+
return out;
|
|
29678
|
+
}
|
|
29679
|
+
},
|
|
29067
29680
|
// Gradient text in authored CSS. A rule block spans lines, so this scans a
|
|
29068
29681
|
// ±6-line window around each `background-clip: text` instead of one line
|
|
29069
29682
|
// (the Tailwind class form stays a line matcher above).
|
|
@@ -29494,6 +30107,20 @@ function parseScope(raw) {
|
|
|
29494
30107
|
function actingUserId() {
|
|
29495
30108
|
return getEnv().BAKER_ACTING_USER_ID;
|
|
29496
30109
|
}
|
|
30110
|
+
function customEntry(server) {
|
|
30111
|
+
const failed = server.health && !server.health.ok;
|
|
30112
|
+
const problem = server.health?.message ?? "This server's address is not serving tools.";
|
|
30113
|
+
return {
|
|
30114
|
+
name: server.name,
|
|
30115
|
+
scope: server.scope,
|
|
30116
|
+
enabled: server.enabled,
|
|
30117
|
+
...failed && !server.health?.transient ? { usable: false, problem } : {},
|
|
30118
|
+
...failed && server.health?.transient ? { lastCheck: "failed", problem } : {},
|
|
30119
|
+
...failed && server.health?.suggestedUrl ? { suggestedAddress: server.health.suggestedUrl } : {}
|
|
30120
|
+
};
|
|
30121
|
+
}
|
|
30122
|
+
var BROKEN_SERVER_HINT = "A server marked usable=false will NOT answer: its tools are absent from this turn. Say plainly that the tool is not working and relay `problem`; never claim its tools are loading, coming next message, or temporarily unavailable. Fixing it needs a person: the user updates its address in the dashboard (Brain \u2192 Integrations \u2192 MCPs). If `suggestedAddress` is present, that is the address to use.";
|
|
30123
|
+
var LAST_CHECK_FAILED_HINT = "A server with lastCheck=failed was unreachable or erroring when it was last checked \u2014 that may already have passed, and its tools ARE loaded this turn. Try the tool; if it works, say nothing about the check. If it fails, report `problem` as what happened rather than guessing.";
|
|
29497
30124
|
function parseHeaders(raw) {
|
|
29498
30125
|
const list = raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw];
|
|
29499
30126
|
const headers = {};
|
|
@@ -29540,8 +30167,14 @@ A tool the user names that is NOT listed here is simply not connected yet.`
|
|
|
29540
30167
|
...c.accountLabel ? { account: c.accountLabel } : {},
|
|
29541
30168
|
...c.readOnly ? { readOnly: true } : {}
|
|
29542
30169
|
}));
|
|
29543
|
-
const custom = data.custom.map(
|
|
30170
|
+
const custom = data.custom.map(customEntry);
|
|
29544
30171
|
const hints = [];
|
|
30172
|
+
if (data.custom.some((s) => s.health && !s.health.ok && !s.health.transient)) {
|
|
30173
|
+
hints.push(BROKEN_SERVER_HINT);
|
|
30174
|
+
}
|
|
30175
|
+
if (data.custom.some((s) => s.health && !s.health.ok && s.health.transient)) {
|
|
30176
|
+
hints.push(LAST_CHECK_FAILED_HINT);
|
|
30177
|
+
}
|
|
29545
30178
|
if (data.managedDisabled) {
|
|
29546
30179
|
hints.push("Managed integrations are disabled for this company \u2014 only custom MCP servers load.");
|
|
29547
30180
|
}
|
|
@@ -29577,7 +30210,15 @@ var listCommand13 = defineCommand139({
|
|
|
29577
30210
|
"/api/mcp/custom",
|
|
29578
30211
|
user ? { actingUserId: user } : void 0
|
|
29579
30212
|
);
|
|
29580
|
-
writeJson({
|
|
30213
|
+
writeJson({
|
|
30214
|
+
ok: true,
|
|
30215
|
+
// Detail view: the full row (address included) plus the usability verdict.
|
|
30216
|
+
// `health` itself is folded into `usable`/`problem` by `customEntry`.
|
|
30217
|
+
data: data.servers.map((server) => ({ ...server, health: void 0, ...customEntry(server) })),
|
|
30218
|
+
...data.servers.some((s) => s.health && !s.health.ok) ? {
|
|
30219
|
+
hints: data.servers.some((s) => s.health && !s.health.ok && !s.health.transient) ? [BROKEN_SERVER_HINT] : [LAST_CHECK_FAILED_HINT]
|
|
30220
|
+
} : {}
|
|
30221
|
+
});
|
|
29581
30222
|
} catch (err) {
|
|
29582
30223
|
fail6(err);
|
|
29583
30224
|
}
|
|
@@ -29588,7 +30229,11 @@ registerSchema({
|
|
|
29588
30229
|
description: "Register a custom MCP server. Tools appear as mcp__<name>__* on the next message. URL must be HTTPS. Scope: company (default, all chats) | org (org admin key) | user (only the current sender).",
|
|
29589
30230
|
args: {
|
|
29590
30231
|
name: { type: "string", description: "Server name \u2192 tools appear as mcp__<name>__*", required: true },
|
|
29591
|
-
url: {
|
|
30232
|
+
url: {
|
|
30233
|
+
type: "string",
|
|
30234
|
+
description: "Full HTTPS MCP endpoint (commonly ends in /mcp, not just the domain)",
|
|
30235
|
+
required: true
|
|
30236
|
+
},
|
|
29592
30237
|
scope: { type: "string", description: "user | user_org | company | org (default company)", required: false },
|
|
29593
30238
|
header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
|
|
29594
30239
|
}
|
|
@@ -29607,7 +30252,11 @@ Examples:
|
|
|
29607
30252
|
},
|
|
29608
30253
|
args: {
|
|
29609
30254
|
name: { type: "string", description: "Server name (mcp__<name>__*)", required: true },
|
|
29610
|
-
url: {
|
|
30255
|
+
url: {
|
|
30256
|
+
type: "string",
|
|
30257
|
+
description: "Full HTTPS MCP endpoint (commonly ends in /mcp, not just the domain)",
|
|
30258
|
+
required: true
|
|
30259
|
+
},
|
|
29611
30260
|
scope: { type: "string", description: "user | user_org | company | org (default company)", required: false },
|
|
29612
30261
|
header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
|
|
29613
30262
|
},
|
|
@@ -29623,7 +30272,14 @@ Examples:
|
|
|
29623
30272
|
...user ? { actingUserId: user } : {},
|
|
29624
30273
|
...headers ? { headers } : {}
|
|
29625
30274
|
});
|
|
29626
|
-
writeJson({
|
|
30275
|
+
writeJson({
|
|
30276
|
+
ok: true,
|
|
30277
|
+
data,
|
|
30278
|
+
hints: [
|
|
30279
|
+
"The address must be the full MCP endpoint (commonly ending in /mcp), not just the domain \u2014 a domain on its own usually answers sign-in and then serves no tools.",
|
|
30280
|
+
"The address is checked in the background. Run `baker mcp connected` on the next message to confirm it serves tools before promising the user anything."
|
|
30281
|
+
]
|
|
30282
|
+
});
|
|
29627
30283
|
} catch (err) {
|
|
29628
30284
|
fail6(err);
|
|
29629
30285
|
}
|