@productmaker/mcp 11.2.2 → 11.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +12 -0
  2. package/dist/http.js +1359 -472
  3. package/dist/stdio.js +1359 -472
  4. package/package.json +1 -1
package/dist/stdio.js CHANGED
@@ -15706,6 +15706,54 @@ var CAPABILITY_MODEL_GUIDANCE = {
15706
15706
  "es trabajo pagado: se cobra el m\xE1ximo del rango de su duraci\xF3n (m\xE1s cada versi\xF3n extra del gancho) y al terminar se devuelve lo que no use; requiere confirmaci\xF3n expl\xEDcita; tarda varios minutos"
15707
15707
  ]
15708
15708
  },
15709
+ create_brand: {
15710
+ summary: "Analiza la marca de un negocio de servicios o marca personal desde su Instagram, Facebook, TikTok o web.",
15711
+ useWhen: [
15712
+ "toma mi Instagram y hazme el an\xE1lisis de marca",
15713
+ "la persona comparte el Instagram o la web de su negocio de servicios y quiere su marca, landing o video"
15714
+ ],
15715
+ avoidWhen: [
15716
+ "la persona habla de un producto de dropshipping, un proveedor o un enlace de AliExpress o Dropi"
15717
+ ],
15718
+ defaults: [
15719
+ "reutiliza una marca existente de la misma direcci\xF3n antes de pagar otro an\xE1lisis"
15720
+ ],
15721
+ disclosures: [
15722
+ "es trabajo pagado: muestra el precio y requiere confirmaci\xF3n expl\xEDcita"
15723
+ ]
15724
+ },
15725
+ create_service_landing: {
15726
+ summary: "Crea y publica la landing page de un negocio de servicios a partir de su marca.",
15727
+ useWhen: [
15728
+ "hazme la landing page de mi negocio",
15729
+ "la persona tiene una marca de servicios y quiere su p\xE1gina para recibir clientes"
15730
+ ],
15731
+ avoidWhen: [
15732
+ "la persona quiere una landing de un producto de dropshipping o de una tarea de producto"
15733
+ ],
15734
+ defaults: [
15735
+ "pregunta una sola vez el contacto, la zona y los servicios con precio que falten"
15736
+ ],
15737
+ disclosures: [
15738
+ "es trabajo pagado: muestra el precio total y requiere confirmaci\xF3n expl\xEDcita; publicar no cobra"
15739
+ ]
15740
+ },
15741
+ create_service_images: {
15742
+ summary: "Crea im\xE1genes para anuncios de un negocio de servicios con su marca, sin foto de producto.",
15743
+ useWhen: [
15744
+ "hazme im\xE1genes para anunciar mi servicio",
15745
+ "la persona quiere im\xE1genes publicitarias de su negocio de servicios"
15746
+ ],
15747
+ avoidWhen: [
15748
+ "la persona quiere im\xE1genes de un producto de dropshipping con sus fotos"
15749
+ ],
15750
+ defaults: [
15751
+ "una imagen 4:5 de calidad est\xE1ndar con la marca"
15752
+ ],
15753
+ disclosures: [
15754
+ "es trabajo pagado: muestra el precio y requiere confirmaci\xF3n expl\xEDcita"
15755
+ ]
15756
+ },
15709
15757
  edit_image_creative: {
15710
15758
  summary: "Corrige UNA imagen ya generada de una tarea de creativos, sin regenerar el set completo.",
15711
15759
  useWhen: [
@@ -18799,6 +18847,92 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
18799
18847
  "task",
18800
18848
  "video"
18801
18849
  ], MCP_ONLY_CHANNEL_EXPOSURE),
18850
+ // MCP-only service journey (2026-10-11): brand analysis → service landing →
18851
+ // create_animated_video. WhatsApp has no adapter for either, so both are
18852
+ // declared unavailable there and its parity gate is unchanged.
18853
+ defineCapability("create_brand", "7f5680574f38f1005d162c2a2a3805be82896508793ebcdd5491b8459c803158", "credit_consuming", "creative", [
18854
+ "credit_consuming",
18855
+ "state_change"
18856
+ ], [
18857
+ slot("brand_action", false, [
18858
+ "action",
18859
+ "brandId",
18860
+ "confirmed"
18861
+ ], []),
18862
+ slot("brand_source", false, [
18863
+ "sourceUrl"
18864
+ ], []),
18865
+ slot("business_notes", false, [
18866
+ "businessNotes"
18867
+ ], []),
18868
+ slot("locale", false, [
18869
+ "language"
18870
+ ], [])
18871
+ ], [
18872
+ "owner_context"
18873
+ ], [
18874
+ "brand"
18875
+ ], MCP_ONLY_CHANNEL_EXPOSURE),
18876
+ defineCapability("create_service_landing", "f668f49e8006c610102ecb1760cdd9a238a9750c77aecf1762b58b8474e5495e", "credit_consuming", "creative", [
18877
+ "credit_consuming",
18878
+ "state_change"
18879
+ ], [
18880
+ slot("service_landing_action", false, [
18881
+ "action",
18882
+ "servicePageId",
18883
+ "operationId",
18884
+ "confirmed",
18885
+ "maxCredits"
18886
+ ], []),
18887
+ slot("brand", false, [
18888
+ "brandId"
18889
+ ], []),
18890
+ slot("business_info", false, [
18891
+ "businessInfo"
18892
+ ], []),
18893
+ slot("slug", false, [
18894
+ "slug"
18895
+ ], [])
18896
+ ], [
18897
+ "brand"
18898
+ ], [
18899
+ "service_landing"
18900
+ ], MCP_ONLY_CHANNEL_EXPOSURE),
18901
+ defineCapability("create_service_images", "1df35139abd6b04c5ebcdd5945154555a3f0d0e49b61f7d70c98cc5dad1e1347", "credit_consuming", "creative", [
18902
+ "credit_consuming",
18903
+ "state_change"
18904
+ ], [
18905
+ slot("brand", false, [
18906
+ "brandId"
18907
+ ], []),
18908
+ slot("service_brief", false, [
18909
+ "instruction",
18910
+ "delegateToAi"
18911
+ ], []),
18912
+ slot("format", false, [
18913
+ "format"
18914
+ ], []),
18915
+ slot("quantity", false, [
18916
+ "count"
18917
+ ], []),
18918
+ slot("quality", false, [
18919
+ "quality"
18920
+ ], []),
18921
+ slot("locale", false, [
18922
+ "language"
18923
+ ], []),
18924
+ slot("ad_copy", false, [
18925
+ "adCopy"
18926
+ ], []),
18927
+ slot("confirmation", false, [
18928
+ "confirmed"
18929
+ ], [])
18930
+ ], [
18931
+ "brand"
18932
+ ], [
18933
+ "task",
18934
+ "creative"
18935
+ ], MCP_ONLY_CHANNEL_EXPOSURE),
18802
18936
  // MCP-only recovery of an existing code edit. WhatsApp has no code-landing
18803
18937
  // editor or edit receipt input; it hands this recovery to ProductMaker Web.
18804
18938
  defineCapability("verify_landing_commerce", "4b98ea73f291428fa19ec91ec37deb44a776b76cb19b0d47aef20ee905da7d10", "external_mutation", "publishing", [
@@ -22033,7 +22167,7 @@ var ApiClient = class {
22033
22167
  };
22034
22168
 
22035
22169
  // src/index.ts
22036
- var MCP_VERSION = "11.2.2";
22170
+ var MCP_VERSION = "11.3.0";
22037
22171
 
22038
22172
  // src/meta-upload-app.ts
22039
22173
  import { readFileSync } from "fs";
@@ -26386,8 +26520,756 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
26386
26520
  }
26387
26521
  __name(registerTaskTools, "registerTaskTools");
26388
26522
 
26389
- // src/tools/creatives.ts
26523
+ // src/tools/brands.ts
26390
26524
  import { z as z111 } from "zod";
26525
+ var guidance2 = getProductMakerCapabilityModelGuidance("create_brand");
26526
+ var BRAND_START_REPLAY_WINDOW_MS = 15 * 60 * 1e3;
26527
+ var BRAND_NOTES_MAX_CHARS = 2e4;
26528
+ var CreateBrandInput = z111.object({
26529
+ action: z111.enum([
26530
+ "start",
26531
+ "status",
26532
+ "list"
26533
+ ]).default("start").describe("start cotiza (sin confirmed) o inicia (confirmed=true) el an\xE1lisis; status consulta brandId hasta ready o failed; list muestra las marcas que el usuario ya tiene."),
26534
+ sourceUrl: z111.string().trim().min(1).max(500).optional().describe("Instagram, Facebook, TikTok o web p\xFAblica del negocio, como la dio el usuario (\xAB@sanlucasbarberia\xBB, \xABinstagram.com/sanlucasbarberia\xBB, \xABmibarberia.com\xBB). Obligatorio para start."),
26535
+ businessNotes: z111.string().trim().max(BRAND_NOTES_MAX_CHARS).optional().describe("Opcional. Lo que el usuario cuente de su negocio en el chat (servicios y precios, WhatsApp, ciudad o zona, horarios). Se guarda como documento del negocio y llega tal cual a la marca y a su landing. Copia sus palabras; no inventes datos."),
26536
+ language: z111.enum([
26537
+ "es",
26538
+ "en",
26539
+ "pt"
26540
+ ]).optional().describe("Idioma de la conversaci\xF3n. Solo se usa si la p\xE1gina del negocio no deja claro su idioma."),
26541
+ confirmed: z111.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, start solo cotiza y no cobra."),
26542
+ brandId: z111.string().uuid().optional().describe("ID de la marca. Obligatorio para status; no lo inventes.")
26543
+ }).strict();
26544
+ function normalizeBrandSourceUrl(raw) {
26545
+ const value = raw.trim();
26546
+ const handle = /^@([A-Za-z0-9._]{1,30})$/.exec(value);
26547
+ if (handle) return `https://www.instagram.com/${handle[1]}/`;
26548
+ return normalizeWebAddress(value);
26549
+ }
26550
+ __name(normalizeBrandSourceUrl, "normalizeBrandSourceUrl");
26551
+ function brandSourceKey(url) {
26552
+ try {
26553
+ const parsed = new URL(url);
26554
+ const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
26555
+ const path3 = parsed.pathname.replace(/\/+$/, "").toLowerCase();
26556
+ return `${host}${path3}`;
26557
+ } catch {
26558
+ return url.trim().toLowerCase();
26559
+ }
26560
+ }
26561
+ __name(brandSourceKey, "brandSourceKey");
26562
+ var EXTRACTION_ERROR_TEXT = {
26563
+ invalid_url: "La direcci\xF3n no es v\xE1lida o no es p\xFAblica. Pide el enlace que abre sin iniciar sesi\xF3n.",
26564
+ not_business_page: "No encontramos contenido del negocio en esa p\xE1gina. Pide el Instagram o la web principal del negocio.",
26565
+ scrape_failed: "No pudimos abrir la p\xE1gina (puede estar ca\xEDda o bloqueando visitas autom\xE1ticas). Pide otro enlace del negocio.",
26566
+ scrape_too_large: "La p\xE1gina es demasiado grande. Pide otro enlace m\xE1s simple del negocio, por ejemplo su Instagram.",
26567
+ scrape_timeout: "La p\xE1gina tard\xF3 demasiado en responder. Se puede volver a intentar en unos minutos.",
26568
+ llm_failed: "Hubo un error temporal al analizar la marca. Se puede volver a intentar en unos minutos.",
26569
+ llm_invalid_output: "El an\xE1lisis devolvi\xF3 algo inesperado. Se puede volver a intentar.",
26570
+ worker_exception: "Ocurri\xF3 un error inesperado durante el an\xE1lisis. Se puede volver a intentar.",
26571
+ BRAND_EXTRACTION_REQUIRES_REVIEW: "El an\xE1lisis se detuvo de forma segura y necesita revisi\xF3n del equipo de ProductMaker antes de reintentarlo."
26572
+ };
26573
+ function describeExtractionError(code) {
26574
+ return (code ? EXTRACTION_ERROR_TEXT[code] : void 0) ?? "No pudimos analizar la marca. Se puede volver a intentar o probar con otro enlace del negocio.";
26575
+ }
26576
+ __name(describeExtractionError, "describeExtractionError");
26577
+ function paletteColors(palette) {
26578
+ if (!palette) return [];
26579
+ const colors = [];
26580
+ for (const key of [
26581
+ "primary",
26582
+ "secondary",
26583
+ "tertiary",
26584
+ "neutral"
26585
+ ]) {
26586
+ const value = palette[key];
26587
+ if (typeof value === "string") colors.push(value);
26588
+ }
26589
+ const accents = palette["accents"];
26590
+ if (Array.isArray(accents)) {
26591
+ for (const accent of accents) {
26592
+ if (typeof accent === "string") colors.push(accent);
26593
+ }
26594
+ }
26595
+ return colors.slice(0, 6);
26596
+ }
26597
+ __name(paletteColors, "paletteColors");
26598
+ function summarizeBrand(brand) {
26599
+ return {
26600
+ brandId: brand.id,
26601
+ status: brand.status,
26602
+ name: brand.name,
26603
+ ...brand.sourceUrl ? {
26604
+ source: brand.sourceUrl
26605
+ } : {},
26606
+ ...brand.oneLiner ? {
26607
+ oneLiner: brand.oneLiner
26608
+ } : {},
26609
+ ...brand.overview ? {
26610
+ overview: brand.overview
26611
+ } : {},
26612
+ tone: brand.toneAdjectives ?? [],
26613
+ values: brand.coreValues ?? [],
26614
+ keyMessages: brand.keyMessages ?? [],
26615
+ colors: paletteColors(brand.palette),
26616
+ hasLogo: Boolean(brand.logoKey),
26617
+ audiences: (brand.personas ?? []).map((persona) => ({
26618
+ name: persona.name,
26619
+ ...persona.demographics ? {
26620
+ who: persona.demographics
26621
+ } : {},
26622
+ ...persona.painPoints ? {
26623
+ painPoints: persona.painPoints
26624
+ } : {},
26625
+ ...persona.motivations ? {
26626
+ motivations: persona.motivations
26627
+ } : {}
26628
+ }))
26629
+ };
26630
+ }
26631
+ __name(summarizeBrand, "summarizeBrand");
26632
+ function describeBrandLanding(page) {
26633
+ if (!page) return null;
26634
+ return {
26635
+ servicePageId: page.id,
26636
+ status: page.publicUrl ? "published" : "draft",
26637
+ ...page.publicUrl ? {
26638
+ publicUrl: page.publicUrl
26639
+ } : {}
26640
+ };
26641
+ }
26642
+ __name(describeBrandLanding, "describeBrandLanding");
26643
+ function statusResponse(brand, landing = null) {
26644
+ if (brand.status === "ready") {
26645
+ const landingNext = landing?.publicUrl ? `Esta marca ya tiene su landing publicada en ${landing.publicUrl}: comp\xE1rtela y ofrece el video animado (create_animated_video con ese enlace) o im\xE1genes (create_service_images con este brandId).` : landing ? "Esta marca ya tiene una landing en borrador: ret\xF3mala con create_service_landing action=prepare y este brandId (no crea otra)." : "Luego ofrece crear su landing page con create_service_landing action=prepare y este brandId. Si pidi\xF3 tambi\xE9n un video, dile que va despu\xE9s de publicar la landing.";
26646
+ return asText({
26647
+ brand: summarizeBrand(brand),
26648
+ ...landing ? {
26649
+ serviceLanding: landing
26650
+ } : {},
26651
+ nextAction: `Muestra al usuario el an\xE1lisis de su marca (nombre, frase, tono, colores, mensajes clave y p\xFAblico) en pocas l\xEDneas. ${landingNext}`
26652
+ });
26653
+ }
26654
+ if (brand.status === "failed") {
26655
+ return asText({
26656
+ brandId: brand.id,
26657
+ status: "failed",
26658
+ reason: describeExtractionError(brand.extractionError),
26659
+ nextAction: "Explica el motivo en una frase y ofrece la salida que indica. No reintentes sin que el usuario lo pida: un nuevo an\xE1lisis se cobra de nuevo."
26660
+ });
26661
+ }
26662
+ return asText({
26663
+ brandId: brand.id,
26664
+ status: "extracting",
26665
+ nextAction: "El an\xE1lisis sigue en curso (suele tardar 1 a 3 minutos). Llama create_brand action=status con este brandId otra vez; no inicies otro an\xE1lisis."
26666
+ });
26667
+ }
26668
+ __name(statusResponse, "statusResponse");
26669
+ async function findRecentBrand(api, url, nowMs) {
26670
+ const brands = await api.getJson("/v1/brands");
26671
+ const key = brandSourceKey(url);
26672
+ return brands.find((brand) => {
26673
+ if (!brand.sourceUrl || brandSourceKey(brand.sourceUrl) !== key) return false;
26674
+ const updatedMs = Date.parse(brand.updatedAt);
26675
+ return brand.status !== "failed" && Number.isFinite(updatedMs) && nowMs - updatedMs < BRAND_START_REPLAY_WINDOW_MS;
26676
+ }) ?? null;
26677
+ }
26678
+ __name(findRecentBrand, "findRecentBrand");
26679
+ function registerBrandTools(server, api, options = {}) {
26680
+ const now = options.now ?? Date.now;
26681
+ async function findBrandLanding(brandId) {
26682
+ try {
26683
+ const { pages } = await api.getJson("/v1/service-pages");
26684
+ return describeBrandLanding(pages.find((page) => page.brandId === brandId) ?? null);
26685
+ } catch {
26686
+ return null;
26687
+ }
26688
+ }
26689
+ __name(findBrandLanding, "findBrandLanding");
26690
+ server.registerTool("create_brand", {
26691
+ description: guidance2.summary + " Sirve para NEGOCIOS DE SERVICIOS y marcas personales (barber\xEDa, consultorio, estudio, academia, agencia\u2026) y devuelve nombre, frase, tono, colores, logo, mensajes clave y p\xFAblico. Es el primer paso para su landing page (create_service_landing) y su video animado (create_animated_video). NO es para productos de dropshipping: para un producto, proveedor o enlace de AliExpress/Dropi usa las herramientas de producto. Flujo: action=start con sourceUrl (y businessNotes si el usuario cont\xF3 precios, WhatsApp o zona) devuelve el precio SIN cobrar; mu\xE9stralo y, cuando el usuario acepte, repite start con confirmed=true. Despu\xE9s llama action=status con el brandId hasta ready (1 a 3 minutos) o failed. Si el usuario ya tiene marcas, action=list te deja reutilizar una sin pagar otro an\xE1lisis. Repetir un start confirmado de la misma direcci\xF3n devuelve la marca que ya se est\xE1 analizando, sin cobrar dos veces.",
26692
+ inputSchema: CreateBrandInput.shape,
26693
+ annotations: CREATES_PAID_OPEN_WORLD
26694
+ }, async (rawInput) => {
26695
+ try {
26696
+ const input = CreateBrandInput.parse(rawInput);
26697
+ if (input.action === "list") {
26698
+ const brands = await api.getJson("/v1/brands");
26699
+ return asText({
26700
+ brands: brands.map((brand) => ({
26701
+ brandId: brand.id,
26702
+ name: brand.name,
26703
+ status: brand.status,
26704
+ ...brand.sourceUrl ? {
26705
+ source: brand.sourceUrl
26706
+ } : {}
26707
+ })),
26708
+ nextAction: brands.length === 0 ? "El usuario a\xFAn no tiene marcas. Pide el Instagram o la web de su negocio y usa action=start." : "Si una de estas es la marca del negocio, \xFAsala (create_brand action=status para ver su an\xE1lisis, o create_service_landing con su brandId). Si no, usa action=start."
26709
+ });
26710
+ }
26711
+ if (input.action === "status") {
26712
+ if (!input.brandId) {
26713
+ throw mcp(McpErrorCode.INVALID_INPUT, "brandId es obligatorio para consultar una marca. Usa el que devolvi\xF3 start o list.");
26714
+ }
26715
+ const brand = await api.getJson(`/v1/brands/${encodeURIComponent(input.brandId)}`);
26716
+ return statusResponse(brand, brand.status === "ready" ? await findBrandLanding(brand.id) : null);
26717
+ }
26718
+ const sourceUrl = input.sourceUrl ? normalizeBrandSourceUrl(input.sourceUrl) : null;
26719
+ if (!sourceUrl) {
26720
+ throw mcp(McpErrorCode.INVALID_INPUT, "sourceUrl debe ser el Instagram, Facebook, TikTok o la web p\xFAblica del negocio (ej. @mibarberia o mibarberia.com). No se cobr\xF3 nada.");
26721
+ }
26722
+ const existing = await findRecentBrand(api, sourceUrl, now());
26723
+ if (existing && input.confirmed === true) {
26724
+ const brand = await api.getJson(`/v1/brands/${encodeURIComponent(existing.id)}`);
26725
+ if (!input.businessNotes) return statusResponse(brand);
26726
+ return asText({
26727
+ brandId: brand.id,
26728
+ status: brand.status,
26729
+ replayed: true,
26730
+ notesSaved: false,
26731
+ nextAction: "Esta marca ya se estaba analizando (no se cobr\xF3 otra vez) y las notas del negocio NO entraron en este an\xE1lisis. P\xE1salas tal cual en businessInfo de create_service_landing (prepare o build) para que la landing las use. Sigue con create_brand action=status y este brandId."
26732
+ });
26733
+ }
26734
+ const estimate = await api.postJson("/v1/billing/estimate", {
26735
+ type: "brand-extract",
26736
+ options: {}
26737
+ });
26738
+ const priceCredits = Math.ceil(estimate.estimatedMaxCostCredits);
26739
+ if (input.confirmed !== true) {
26740
+ return asText({
26741
+ sourceUrl,
26742
+ charged: false,
26743
+ ...existing ? {
26744
+ existingBrand: {
26745
+ brandId: existing.id,
26746
+ status: existing.status
26747
+ }
26748
+ } : {},
26749
+ priceCredits,
26750
+ userBalanceCredits: estimate.userBalanceCredits,
26751
+ hasSufficientBalance: estimate.hasSufficientBalance,
26752
+ nextAction: existing ? `Esta direcci\xF3n ya tiene una marca analizada hace pocos minutos (brandId ${existing.id}): \xFAsala con create_brand action=status, sin pagar otro an\xE1lisis. Si el usuario cont\xF3 datos nuevos del negocio, p\xE1salos en businessInfo de create_service_landing.` : estimate.hasSufficientBalance ? `Dile al usuario que el an\xE1lisis de su marca cuesta hasta ${priceCredits} cr\xE9ditos y preg\xFAntale si quiere hacerlo. Solo si acepta, llama create_brand action=start con el mismo sourceUrl${input.businessNotes ? ", las mismas businessNotes" : ""} y confirmed=true.` : `El saldo (${estimate.userBalanceCredits} cr\xE9ditos) no alcanza para el an\xE1lisis (${priceCredits} cr\xE9ditos). Dile que recargue en productmaker.app y no inicies nada.`
26753
+ });
26754
+ }
26755
+ await requirePaidCreativeConfirmation(server, `An\xE1lisis de marca de ${sourceUrl}${input.businessNotes ? " con los datos que contaste del negocio" : ""}. Precio: hasta ${priceCredits} cr\xE9ditos.`);
26756
+ const notes = input.businessNotes?.trim();
26757
+ const created = await api.postMultipart("/v1/brands/extract", notes ? {
26758
+ files: {
26759
+ buffer: Buffer.from(notes, "utf8"),
26760
+ mimetype: "text/plain",
26761
+ filename: "notas-del-negocio.txt"
26762
+ }
26763
+ } : {}, {
26764
+ url: sourceUrl,
26765
+ locale: input.language
26766
+ });
26767
+ return asText({
26768
+ brandId: created.brandId,
26769
+ status: "extracting",
26770
+ priceCredits,
26771
+ nextAction: "El an\xE1lisis empez\xF3 (1 a 3 minutos). Dile al usuario que lo est\xE1s preparando y llama create_brand action=status con este brandId hasta ready o failed."
26772
+ });
26773
+ } catch (error) {
26774
+ throw translateError(error);
26775
+ }
26776
+ });
26777
+ }
26778
+ __name(registerBrandTools, "registerBrandTools");
26779
+
26780
+ // src/tools/service-landing.ts
26781
+ import { createHash as createHash3 } from "crypto";
26782
+ import { z as z112 } from "zod";
26783
+ var guidance3 = getProductMakerCapabilityModelGuidance("create_service_landing");
26784
+ var SERVICE_LANDING_KEY_NAMESPACE = "6f1d8c2a-3b4e-5f60-8a7b-9c0d1e2f3a4b";
26785
+ var SERVICE_LANDING_INFO_MAX_CHARS = 2e4;
26786
+ var CreateServiceLandingInput = z112.object({
26787
+ action: z112.enum([
26788
+ "prepare",
26789
+ "build",
26790
+ "status",
26791
+ "publish"
26792
+ ]).default("prepare").describe("prepare (gratis) crea o retoma la landing de la marca y da el precio; build (pagado, confirmed=true) lee los datos del negocio y luego crea la p\xE1gina; status sigue el avance; publish la publica y devuelve su enlace."),
26793
+ brandId: z112.string().uuid().optional().describe("ID de la marca (de create_brand). Obligatorio para prepare. Nunca un taskId de producto."),
26794
+ servicePageId: z112.string().uuid().optional().describe("ID de la landing que devolvi\xF3 prepare. Obligatorio para build, status y publish."),
26795
+ businessInfo: z112.string().trim().max(SERVICE_LANDING_INFO_MAX_CHARS).optional().describe("Opcional. Lo que el usuario cont\xF3 del negocio y no estaba en su marca: servicios y precios, WhatsApp o tel\xE9fono, ciudad o zona, horarios. Copia sus palabras; no inventes datos."),
26796
+ confirmed: z112.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio total y que el usuario acepte. Obligatorio para build."),
26797
+ maxCredits: z112.number().int().positive().optional().describe("El total en cr\xE9ditos que el usuario acept\xF3 (el que devolvi\xF3 prepare). Obligatorio para build: ninguna etapa se inicia si su precio supera este total."),
26798
+ operationId: z112.string().uuid().optional().describe("Opcional en status: la operaci\xF3n que devolvi\xF3 build."),
26799
+ slug: z112.string().trim().toLowerCase().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(64).optional().describe("Opcional en publish: la direcci\xF3n corta de la p\xE1gina (ej. sanlucas-barberia). Si falta se usa la sugerida desde el nombre del negocio.")
26800
+ }).strict();
26801
+ function serviceKey(...parts) {
26802
+ return deriveUuidV5(parts.join(":"), SERVICE_LANDING_KEY_NAMESPACE);
26803
+ }
26804
+ __name(serviceKey, "serviceKey");
26805
+ function textHash(text2) {
26806
+ return createHash3("sha256").update(text2, "utf8").digest("hex").slice(0, 16);
26807
+ }
26808
+ __name(textHash, "textHash");
26809
+ function quoteRange(quote) {
26810
+ const max = Math.ceil(quote.estimatedMaxCostCredits ?? quote.credits);
26811
+ const min = Math.ceil(Math.min(quote.estimatedMinCostCredits ?? max, max));
26812
+ return {
26813
+ min,
26814
+ max
26815
+ };
26816
+ }
26817
+ __name(quoteRange, "quoteRange");
26818
+ function serviceApiCode(error) {
26819
+ if (!(error instanceof ApiHttpError)) return null;
26820
+ const body = error.body;
26821
+ if (!body || typeof body !== "object") return null;
26822
+ const record = body;
26823
+ for (const value of [
26824
+ record.code,
26825
+ record.errorCode,
26826
+ record.message
26827
+ ]) {
26828
+ if (typeof value === "string" && /^[A-Z][A-Z0-9_]{2,63}$/.test(value)) return value;
26829
+ }
26830
+ return null;
26831
+ }
26832
+ __name(serviceApiCode, "serviceApiCode");
26833
+ function suggestServiceSlug(name) {
26834
+ const slug = (name ?? "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/g, "");
26835
+ return slug || "mi-servicio";
26836
+ }
26837
+ __name(suggestServiceSlug, "suggestServiceSlug");
26838
+ function serviceEditorUrl(api, pageId) {
26839
+ try {
26840
+ const url = new URL(api.publicUrl("/"));
26841
+ if (!url.hostname.startsWith("api.")) return null;
26842
+ url.hostname = url.hostname.slice("api.".length);
26843
+ url.pathname = `/es/service-pages/${encodeURIComponent(pageId)}/edit`;
26844
+ return url.toString();
26845
+ } catch {
26846
+ return null;
26847
+ }
26848
+ }
26849
+ __name(serviceEditorUrl, "serviceEditorUrl");
26850
+ var MISSING_LABELS = {
26851
+ "business.name": "nombre del negocio",
26852
+ services: "servicios (y su precio, si lo tiene)",
26853
+ "contact.channels": "WhatsApp, tel\xE9fono o enlace para reservar",
26854
+ "contact.primaryCta": "c\xF3mo quiere que lo contacten primero"
26855
+ };
26856
+ function describeMissing(fields) {
26857
+ return [
26858
+ ...new Set(fields.map((field) => MISSING_LABELS[field]).filter((label) => Boolean(label)))
26859
+ ];
26860
+ }
26861
+ __name(describeMissing, "describeMissing");
26862
+ function summarizeFacts(facts) {
26863
+ return {
26864
+ ...facts.business.name ? {
26865
+ business: facts.business.name
26866
+ } : {},
26867
+ ...facts.business.description ? {
26868
+ description: facts.business.description
26869
+ } : {},
26870
+ ...facts.business.coverage ? {
26871
+ area: facts.business.coverage
26872
+ } : {},
26873
+ services: facts.services.filter((service) => service.name).map((service) => ({
26874
+ name: service.name,
26875
+ ...service.price ? {
26876
+ price: service.price
26877
+ } : {}
26878
+ })),
26879
+ contact: facts.contact.channels.map((channel) => channel.value ? `${channel.kind}: ${channel.value}` : channel.kind)
26880
+ };
26881
+ }
26882
+ __name(summarizeFacts, "summarizeFacts");
26883
+ var OPERATION_ERROR_TEXT = {
26884
+ SERVICE_PAGE_SOURCES_REQUIRED: "No hay informaci\xF3n del negocio para escribir la p\xE1gina. Pide al usuario que cuente sus servicios y vuelve a llamar build con businessInfo.",
26885
+ SOURCE_TEXT_REQUIRED: "No hay informaci\xF3n del negocio para escribir la p\xE1gina. Pide al usuario que cuente sus servicios y vuelve a llamar build con businessInfo.",
26886
+ SERVICE_PAGE_PROVIDER_REJECTED: "El servicio de IA no estuvo disponible y no se cobr\xF3. Ofrece reintentar en unos minutos con build (mismo maxCredits)."
26887
+ };
26888
+ function operationFailureText(code) {
26889
+ return (code ? OPERATION_ERROR_TEXT[code] : void 0) ?? "La etapa fall\xF3 y no se cobr\xF3. Ofrece volver a intentarlo con build (mismo maxCredits); si vuelve a fallar, sugiere terminarla en la web de ProductMaker.";
26890
+ }
26891
+ __name(operationFailureText, "operationFailureText");
26892
+ function isPageGenerated(page) {
26893
+ return page.artifact !== null && page.artifact !== void 0;
26894
+ }
26895
+ __name(isPageGenerated, "isPageGenerated");
26896
+ function registerServiceLandingTools(server, api) {
26897
+ const pagePath = /* @__PURE__ */ __name((id) => `/v1/service-pages/${encodeURIComponent(id)}`, "pagePath");
26898
+ const readPage = /* @__PURE__ */ __name((id) => api.getJson(pagePath(id)), "readPage");
26899
+ async function findPageForBrand(brandId) {
26900
+ const { pages } = await api.getJson("/v1/service-pages");
26901
+ return pages.find((page) => page.brandId === brandId) ?? null;
26902
+ }
26903
+ __name(findPageForBrand, "findPageForBrand");
26904
+ async function addBusinessInfo(page, info) {
26905
+ try {
26906
+ await api.postMultipart(`${pagePath(page.id)}/intake`, {}, {
26907
+ expectedRevision: String(page.draftRevision),
26908
+ idempotencyKey: serviceKey(page.id, "intake", textHash(info)),
26909
+ text: info
26910
+ });
26911
+ } catch (error) {
26912
+ if (serviceApiCode(error) !== "SERVICE_PAGE_CONFLICT_REVISION") throw error;
26913
+ }
26914
+ return readPage(page.id);
26915
+ }
26916
+ __name(addBusinessInfo, "addBusinessInfo");
26917
+ async function quote(page, purpose) {
26918
+ return api.postJson(`${pagePath(page.id)}/quotes/${purpose}`, {
26919
+ expectedRevision: page.draftRevision
26920
+ });
26921
+ }
26922
+ __name(quote, "quote");
26923
+ async function startOperation(page, purpose, quoted) {
26924
+ const path3 = purpose === "fact-extraction" ? `${pagePath(page.id)}/intake/extract` : `${pagePath(page.id)}/generate`;
26925
+ return api.postJson(path3, {
26926
+ quoteId: quoted.quoteId,
26927
+ idempotencyKey: serviceKey(page.id, purpose, page.draftRevision),
26928
+ expectedRevision: page.draftRevision
26929
+ });
26930
+ }
26931
+ __name(startOperation, "startOperation");
26932
+ function generatedResponse(page) {
26933
+ const editorUrl = serviceEditorUrl(api, page.id);
26934
+ return asText({
26935
+ servicePageId: page.id,
26936
+ status: "generated",
26937
+ ...page.publicUrl ? {
26938
+ publicUrl: page.publicUrl
26939
+ } : {},
26940
+ ...editorUrl ? {
26941
+ editorUrl
26942
+ } : {},
26943
+ suggestedSlug: page.slug ?? suggestServiceSlug(page.businessName),
26944
+ nextAction: page.publicUrl ? `La p\xE1gina ya est\xE1 publicada en ${page.publicUrl}. Si el usuario pidi\xF3 un video, sigue con create_animated_video action=prepare y landingUrl=${page.publicUrl}; si pidi\xF3 im\xE1genes, con create_service_images y brandId=${page.brandId ?? "(el de su marca)"}.` : `La p\xE1gina est\xE1 lista. ${editorUrl ? `Dale al usuario el enlace para verla y editarla (${editorUrl}). ` : ""}Preg\xFAntale si la publicas con la direcci\xF3n sugerida (o la que prefiera) y, si acepta, llama action=publish.`
26945
+ });
26946
+ }
26947
+ __name(generatedResponse, "generatedResponse");
26948
+ server.registerTool("create_service_landing", {
26949
+ description: guidance3.summary + " Parte del brandId de create_brand y termina con el enlace p\xFAblico de la p\xE1gina. NO es para productos de dropshipping (para eso generate_landing / create_product_task). Flujo: 1) action=prepare con brandId (gratis): crea o retoma la landing y devuelve el precio estimado total. 2) Antes de cobrar, pregunta UNA sola vez lo que el usuario no haya dicho ya (WhatsApp o tel\xE9fono, ciudad o zona, servicios y precios); puede omitirlo. 3) Cuando acepte el precio, action=build con servicePageId, confirmed=true, maxCredits y businessInfo con sus respuestas: primero lee los datos del negocio. 4) action=status con servicePageId y operationId hasta que termine; cuando lo indique, vuelve a llamar build con el mismo maxCredits para crear la p\xE1gina (1 a 3 minutos). 5) Muestra el enlace para verla y, si acepta, action=publish. Repetir una llamada no cobra dos veces. Con el enlace publicado sigue create_animated_video si el usuario quiere un video, y create_service_images (con el mismo brandId) si quiere im\xE1genes.",
26950
+ inputSchema: CreateServiceLandingInput.shape,
26951
+ annotations: CREATES_PAID
26952
+ }, async (rawInput) => {
26953
+ try {
26954
+ const input = CreateServiceLandingInput.parse(rawInput);
26955
+ if (input.action === "prepare") {
26956
+ if (!input.brandId) {
26957
+ throw mcp(McpErrorCode.INVALID_INPUT, "brandId es obligatorio: primero analiza la marca con create_brand (o elige una con create_brand action=list). No se cobr\xF3 nada.");
26958
+ }
26959
+ const brand = await api.getJson(`/v1/brands/${encodeURIComponent(input.brandId)}`);
26960
+ if (brand.status !== "ready") {
26961
+ return asText({
26962
+ brandId: input.brandId,
26963
+ charged: false,
26964
+ nextAction: brand.status === "extracting" ? "La marca todav\xEDa se est\xE1 analizando. Llama create_brand action=status hasta ready y luego vuelve a prepare." : "El an\xE1lisis de esta marca fall\xF3. Revisa create_brand action=status y ofrece analizarla de nuevo."
26965
+ });
26966
+ }
26967
+ const existing = await findPageForBrand(input.brandId);
26968
+ let page2 = existing ? await readPage(existing.id) : await api.postJson("/v1/service-pages", {
26969
+ brandId: input.brandId,
26970
+ includeBrandLogo: Boolean(brand.logoKey),
26971
+ idempotencyKey: serviceKey(input.brandId, "create")
26972
+ });
26973
+ if (isPageGenerated(page2)) return generatedResponse(page2);
26974
+ if (input.businessInfo) page2 = await addBusinessInfo(page2, input.businessInfo);
26975
+ const factsQuote = page2.factExtractionCompleted ? null : await quote(page2, "fact-extraction");
26976
+ const generationQuote = await quote(page2, "generation");
26977
+ const facts = factsQuote ? quoteRange(factsQuote) : {
26978
+ min: 0,
26979
+ max: 0
26980
+ };
26981
+ const build = quoteRange(generationQuote);
26982
+ const totalMin = facts.min + build.min;
26983
+ const totalCredits = facts.max + build.max;
26984
+ const requiredBalanceCredits = Math.max(factsQuote?.credits ?? 0, generationQuote.credits);
26985
+ const priceText = totalMin === totalCredits ? `${totalCredits} cr\xE9ditos` : `entre ${totalMin} y ${totalCredits} cr\xE9ditos`;
26986
+ return asText({
26987
+ servicePageId: page2.id,
26988
+ charged: false,
26989
+ ...existing ? {
26990
+ resumed: true
26991
+ } : {},
26992
+ business: page2.businessName,
26993
+ known: summarizeFacts(page2.facts),
26994
+ price: {
26995
+ ...factsQuote ? {
26996
+ readBusinessInfoCredits: facts
26997
+ } : {},
26998
+ buildPageCredits: build,
26999
+ totalMinCredits: totalMin,
27000
+ totalCredits,
27001
+ requiredBalanceCredits
27002
+ },
27003
+ nextAction: `En un solo mensaje: dile al usuario que crear su landing cuesta ${priceText} en total (se cobra lo que se use; necesita al menos ${requiredBalanceCredits} cr\xE9ditos de saldo para empezar) y preg\xFAntale, solo si no lo dijo ya en esta conversaci\xF3n, su WhatsApp o tel\xE9fono, su ciudad o zona y sus servicios con precio (puede omitirlo). Cuando acepte el precio, llama action=build con servicePageId=${page2.id}, confirmed=true, maxCredits=${totalCredits} y businessInfo con lo que respondi\xF3.`
27004
+ });
27005
+ }
27006
+ if (!input.servicePageId) {
27007
+ throw mcp(McpErrorCode.INVALID_INPUT, "servicePageId es obligatorio: usa el que devolvi\xF3 prepare. No se cobr\xF3 nada.");
27008
+ }
27009
+ if (input.action === "status") {
27010
+ let operation2 = null;
27011
+ if (input.operationId) {
27012
+ operation2 = await api.getJson(`/v1/service-pages/operations/${encodeURIComponent(input.operationId)}`);
27013
+ if (operation2.recoverable && (operation2.status === "failed" || operation2.status === "requires_reconciliation")) {
27014
+ operation2 = await api.postJson(`${pagePath(input.servicePageId)}/operations/${encodeURIComponent(operation2.opId)}/recover`, {});
27015
+ }
27016
+ if (operation2.status === "queued" || operation2.status === "running") {
27017
+ return asText({
27018
+ servicePageId: input.servicePageId,
27019
+ operationId: operation2.opId,
27020
+ step: operation2.kind === "generation" ? "creando la p\xE1gina" : "leyendo los datos del negocio",
27021
+ status: operation2.status,
27022
+ nextAction: "Sigue en curso. Llama action=status otra vez con el mismo operationId en unos segundos; no inicies otra etapa."
27023
+ });
27024
+ }
27025
+ if (operation2.status === "requires_reconciliation") {
27026
+ return asText({
27027
+ servicePageId: input.servicePageId,
27028
+ operationId: operation2.opId,
27029
+ status: "in_review",
27030
+ // Unknown on purpose: the charge is exactly what is being
27031
+ // confirmed. Settlement only charges a completed step.
27032
+ nextAction: "Esta etapa qued\xF3 en revisi\xF3n por un problema temporal: estamos confirmando si se complet\xF3. Si no se complet\xF3, no se cobra. Dile al usuario que sus datos est\xE1n guardados; vuelve a llamar action=status con el mismo operationId en unos minutos. No inicies otra etapa mientras tanto; si sigue igual, sugiere escribir a soporte de ProductMaker desde la web."
27033
+ });
27034
+ }
27035
+ if (operation2.status !== "succeeded") {
27036
+ return asText({
27037
+ servicePageId: input.servicePageId,
27038
+ operationId: operation2.opId,
27039
+ status: "failed",
27040
+ charged: false,
27041
+ reason: operationFailureText(operation2.errorCode)
27042
+ });
27043
+ }
27044
+ }
27045
+ const page2 = await readPage(input.servicePageId);
27046
+ if (isPageGenerated(page2)) return generatedResponse(page2);
27047
+ const missing = describeMissing(page2.missingFields);
27048
+ return asText({
27049
+ servicePageId: page2.id,
27050
+ status: page2.factExtractionCompleted ? "business_info_read" : "waiting_for_build",
27051
+ found: summarizeFacts(page2.facts),
27052
+ ...missing.length ? {
27053
+ stillMissing: missing
27054
+ } : {},
27055
+ nextAction: page2.factExtractionCompleted ? `Muestra en pocas l\xEDneas lo que encontramos del negocio${missing.length ? " y lo que falta (puede agregarlo en businessInfo o seguir sin eso)" : ""}. Luego llama action=build con el mismo servicePageId, confirmed=true y el mismo maxCredits para crear la p\xE1gina.` : "A\xFAn no se leyeron los datos del negocio. Llama action=build con confirmed=true y el maxCredits que acept\xF3 el usuario."
27056
+ });
27057
+ }
27058
+ if (input.action === "publish") {
27059
+ const page2 = await readPage(input.servicePageId);
27060
+ if (!isPageGenerated(page2)) {
27061
+ throw mcp(McpErrorCode.INVALID_INPUT, "La p\xE1gina todav\xEDa no est\xE1 creada. Termina action=build y action=status antes de publicar.");
27062
+ }
27063
+ if (page2.publicUrl) return generatedResponse(page2);
27064
+ const slug = input.slug ?? suggestServiceSlug(page2.businessName);
27065
+ try {
27066
+ const published = await api.postJson(`${pagePath(page2.id)}/publish`, {
27067
+ expectedRevision: page2.draftRevision,
27068
+ slug,
27069
+ idempotencyKey: serviceKey(page2.id, "publish", slug)
27070
+ });
27071
+ return asText({
27072
+ servicePageId: page2.id,
27073
+ status: "published",
27074
+ publicUrl: published.publicUrl,
27075
+ nextAction: `Comparte el enlace ${published.publicUrl} con el usuario. Si pidi\xF3 un video, sigue con create_animated_video action=prepare y landingUrl=${published.publicUrl}; si pidi\xF3 im\xE1genes, con create_service_images y brandId=${page2.brandId ?? "(el de su marca)"}.`
27076
+ });
27077
+ } catch (error) {
27078
+ if (serviceApiCode(error)?.includes("SLUG")) {
27079
+ return asText({
27080
+ servicePageId: page2.id,
27081
+ status: "slug_taken",
27082
+ slug,
27083
+ nextAction: `La direcci\xF3n \xAB${slug}\xBB ya est\xE1 en uso. Prop\xF3n otra (por ejemplo \xAB${slug}-${(/* @__PURE__ */ new Date()).getFullYear()}\xBB o con la ciudad) y llama action=publish con slug.`
27084
+ });
27085
+ }
27086
+ throw error;
27087
+ }
27088
+ }
27089
+ if (input.confirmed !== true || !input.maxCredits) {
27090
+ throw mcp(McpErrorCode.INVALID_INPUT, "build cobra cr\xE9ditos: primero llama action=prepare, muestra el precio total y, cuando el usuario acepte, env\xEDa confirmed=true y maxCredits. No se cobr\xF3 nada.");
27091
+ }
27092
+ let page = await readPage(input.servicePageId);
27093
+ if (isPageGenerated(page)) return generatedResponse(page);
27094
+ const revisionBefore = page.draftRevision;
27095
+ if (input.businessInfo) page = await addBusinessInfo(page, input.businessInfo);
27096
+ const addedInfo = page.draftRevision !== revisionBefore;
27097
+ const needsFacts = !page.factExtractionCompleted || addedInfo;
27098
+ const purpose = needsFacts ? "fact-extraction" : "generation";
27099
+ const quoted = await quote(page, purpose);
27100
+ const credits = quoteRange(quoted).max;
27101
+ if (credits > input.maxCredits) {
27102
+ return asText({
27103
+ servicePageId: page.id,
27104
+ charged: false,
27105
+ priceCredits: credits,
27106
+ nextAction: `El precio subi\xF3 a ${credits} cr\xE9ditos, m\xE1s de lo aceptado (${input.maxCredits}). Preg\xFAntale al usuario si sigue y, si acepta, repite build con maxCredits=${credits}.`
27107
+ });
27108
+ }
27109
+ await requirePaidCreativeConfirmation(server, needsFacts ? `Leer los datos del negocio para su landing. Precio: hasta ${credits} cr\xE9ditos (total aceptado: ${input.maxCredits}).` : `Crear la landing page del negocio. Precio: hasta ${credits} cr\xE9ditos (total aceptado: ${input.maxCredits}).`);
27110
+ const operation = await startOperation(page, purpose, quoted);
27111
+ return asText({
27112
+ servicePageId: page.id,
27113
+ operationId: operation.opId,
27114
+ step: needsFacts ? "leyendo los datos del negocio" : "creando la p\xE1gina",
27115
+ status: operation.status,
27116
+ priceCredits: credits,
27117
+ nextAction: needsFacts ? "Dile al usuario que est\xE1s leyendo los datos de su negocio. Llama action=status con servicePageId y este operationId hasta que termine." : "Dile al usuario que est\xE1s creando su p\xE1gina (1 a 3 minutos). Llama action=status con servicePageId y este operationId hasta que termine."
27118
+ });
27119
+ } catch (error) {
27120
+ throw translateError(error);
27121
+ }
27122
+ });
27123
+ }
27124
+ __name(registerServiceLandingTools, "registerServiceLandingTools");
27125
+
27126
+ // src/tools/service-images.ts
27127
+ import { z as z113 } from "zod";
27128
+ var guidance4 = getProductMakerCapabilityModelGuidance("create_service_images");
27129
+ var SERVICE_IMAGE_FORMATS = [
27130
+ "4:5",
27131
+ "9:16",
27132
+ "1:1"
27133
+ ];
27134
+ var SERVICE_IMAGE_MAX_COUNT = 3;
27135
+ var LANGUAGE_TAGS = {
27136
+ es: "es-CO",
27137
+ en: "en-US",
27138
+ pt: "pt-BR"
27139
+ };
27140
+ var CreateServiceImagesInput = z113.object({
27141
+ brandId: z113.string().uuid().optional().describe("ID de la marca (de create_brand). Recomendado: da nombre, colores, tono y servicios. Nunca un taskId de producto."),
27142
+ instruction: z113.string().trim().max(4e3).optional().describe("Qu\xE9 servicio o promoci\xF3n mostrar, con las palabras del usuario (ej. \xABcorte + barba a $35.000 los martes\xBB). Si el usuario deja que la IA decida, om\xEDtelo y usa delegateToAi=true."),
27143
+ delegateToAi: z113.boolean().optional().describe("true si el usuario prefiere que la IA elija qu\xE9 mostrar a partir de su marca."),
27144
+ format: z113.enum(SERVICE_IMAGE_FORMATS).optional().describe('Formato: "4:5" (feed, por defecto), "9:16" (historias/reels) o "1:1" (cuadrado).'),
27145
+ count: z113.number().int().min(1).max(SERVICE_IMAGE_MAX_COUNT).optional().describe("Cu\xE1ntas im\xE1genes, de 1 a 3 (por defecto 1)."),
27146
+ quality: z113.enum([
27147
+ "standard",
27148
+ "high"
27149
+ ]).optional().describe('Calidad: "standard" (por defecto) o "high" (m\xE1s cara; no disponible en 4:5).'),
27150
+ language: z113.enum([
27151
+ "es",
27152
+ "en",
27153
+ "pt"
27154
+ ]).optional().describe("Idioma de los textos de la imagen (por defecto es)."),
27155
+ adCopy: z113.boolean().optional().describe("Opcional. true agrega textos para el anuncio (titular y descripci\xF3n). Por defecto false."),
27156
+ confirmed: z113.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, solo cotiza y no cobra.")
27157
+ }).strict();
27158
+ function serviceImageModel(quality) {
27159
+ return quality === "high" ? AD_CREATIVE_MODELS.PREMIUM : AD_CREATIVE_MODELS.STANDARD;
27160
+ }
27161
+ __name(serviceImageModel, "serviceImageModel");
27162
+ function registerServiceImageTools(server, api, options = {}) {
27163
+ const now = options.now ?? Date.now;
27164
+ server.registerTool("create_service_images", {
27165
+ description: guidance4.summary + " Im\xE1genes para anuncios de un NEGOCIO DE SERVICIOS o marca personal, con su marca (brandId de create_brand), sin foto de producto. NO es para productos de dropshipping (para eso generate_image_creatives). Llama primero sin confirmed: devuelve el precio SIN cobrar; mu\xE9stralo y, cuando el usuario acepte, repite la llamada igual con confirmed=true. Pregunta qu\xE9 servicio o promoci\xF3n mostrar si el usuario no lo dijo (o usa delegateToAi=true si prefiere que la IA elija). Devuelve un taskId: sigue get_task_status hasta done (trae las im\xE1genes) o error. Repetir la misma llamada confirmada no cobra dos veces.",
27166
+ inputSchema: CreateServiceImagesInput.shape,
27167
+ annotations: CREATES_PAID
27168
+ }, async (rawInput) => {
27169
+ try {
27170
+ const input = CreateServiceImagesInput.parse(rawInput);
27171
+ const brief = ServiceImageInputSchema.safeParse({
27172
+ instruction: input.instruction ?? "",
27173
+ intent: input.delegateToAi ? "delegate_to_ai" : "user_brief"
27174
+ });
27175
+ if (!brief.success) {
27176
+ throw mcp(McpErrorCode.INVALID_INPUT, "Pregunta al usuario qu\xE9 servicio o promoci\xF3n quiere mostrar (instruction), o usa delegateToAi=true si prefiere que la IA elija. No se cobr\xF3 nada.");
27177
+ }
27178
+ const format = input.format ?? "4:5";
27179
+ const count = input.count ?? 1;
27180
+ const imageModel = serviceImageModel(input.quality ?? "standard");
27181
+ const tierError = validateImageTierForRatio(format, imageModel);
27182
+ if (tierError) {
27183
+ throw mcp(McpErrorCode.INVALID_INPUT, `${tierError} Usa quality="standard" o otro formato. No se cobr\xF3 nada.`);
27184
+ }
27185
+ const language = LANGUAGE_TAGS[input.language ?? "es"];
27186
+ const adCopy = input.adCopy ?? false;
27187
+ const estimate = await api.postJson("/v1/billing/estimate", {
27188
+ type: "image-creative",
27189
+ options: {
27190
+ subject: "service",
27191
+ nVariants: count,
27192
+ imageModel,
27193
+ aspectRatio: format,
27194
+ generateAdCopy: adCopy,
27195
+ requiresExtraction: false,
27196
+ requiresAngles: false,
27197
+ competitorScanEnabled: false
27198
+ }
27199
+ });
27200
+ const max = Math.ceil(estimate.estimatedMaxCostCredits);
27201
+ const min = Math.ceil(Math.min(estimate.estimatedMinCostCredits ?? max, max));
27202
+ const priceText = min === max ? `${max} cr\xE9ditos` : `entre ${min} y ${max} cr\xE9ditos`;
27203
+ const summary = `${count} imagen${count === 1 ? "" : "es"} ${format} de calidad ${input.quality === "high" ? "alta" : "est\xE1ndar"}${input.brandId ? " con la marca" : ""}`;
27204
+ if (input.confirmed !== true) {
27205
+ return asText({
27206
+ charged: false,
27207
+ images: {
27208
+ count,
27209
+ format,
27210
+ quality: input.quality ?? "standard"
27211
+ },
27212
+ priceCredits: {
27213
+ min,
27214
+ max
27215
+ },
27216
+ userBalanceCredits: estimate.userBalanceCredits,
27217
+ nextAction: estimate.hasSufficientBalance ? `Dile al usuario que ${summary} cuesta ${priceText} y preg\xFAntale si sigue. Solo si acepta, repite la llamada con los mismos datos y confirmed=true.` : `El saldo (${estimate.userBalanceCredits} cr\xE9ditos) no alcanza (${priceText}). Dile que recargue en productmaker.app y no generes nada.`
27218
+ });
27219
+ }
27220
+ await requirePaidCreativeConfirmation(server, `${summary}. Precio: ${priceText}.`);
27221
+ const fields = {
27222
+ serviceBrief: JSON.stringify(brief.data),
27223
+ brandId: input.brandId,
27224
+ aspectRatio: format,
27225
+ imageModel,
27226
+ nVariants: String(count),
27227
+ language,
27228
+ competitorScanEnabled: "false",
27229
+ generateAdCopy: String(adCopy)
27230
+ };
27231
+ const created = await api.postMultipart("/v1/creatives/images", {}, fields, {
27232
+ idempotencyKey: derivePaidRequestKey({
27233
+ fingerprint: api.fingerprint(),
27234
+ tool: "create_service_images",
27235
+ input: {
27236
+ brief: brief.data,
27237
+ brandId: input.brandId ?? null,
27238
+ format,
27239
+ count,
27240
+ imageModel,
27241
+ language,
27242
+ adCopy
27243
+ },
27244
+ nowMs: now()
27245
+ })
27246
+ });
27247
+ const taskId = created.taskId ?? created.jobId;
27248
+ if (!taskId) {
27249
+ throw new Error("ProductMaker no devolvi\xF3 el id de la tarea de im\xE1genes.");
27250
+ }
27251
+ return asText({
27252
+ taskId,
27253
+ status: created.status ?? "queued",
27254
+ ...created.replayed === true ? {
27255
+ replayed: true
27256
+ } : {},
27257
+ priceCredits: {
27258
+ min,
27259
+ max
27260
+ },
27261
+ nextAction: "Dile al usuario que est\xE1s creando sus im\xE1genes. Llama get_task_status con este taskId (waitSeconds=45) hasta done (trae las im\xE1genes) o error."
27262
+ });
27263
+ } catch (error) {
27264
+ if (error instanceof z113.ZodError) throw invalidInputFromZod(error);
27265
+ throw translateError(error);
27266
+ }
27267
+ });
27268
+ }
27269
+ __name(registerServiceImageTools, "registerServiceImageTools");
27270
+
27271
+ // src/tools/creatives.ts
27272
+ import { z as z114 } from "zod";
26391
27273
  var generateFreeVideoGuidance = getProductMakerCapabilityModelGuidance("generate_free_video");
26392
27274
  var generateVideoCreativeGuidance = getProductMakerCapabilityModelGuidance("generate_video_creative");
26393
27275
  var generateImageCreativesGuidance = getProductMakerCapabilityModelGuidance("generate_image_creatives");
@@ -26398,72 +27280,72 @@ var CreativeRefs = {
26398
27280
  images: ImagesInput,
26399
27281
  language: Lang.describe('Idioma del contenido. Ej. "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
26400
27282
  country: Country.describe('Pa\xEDs objetivo ISO. Ej. "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago y tono. REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
26401
- price: z111.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
26402
- offer: z111.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
26403
- compareAtPrice: z111.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
26404
- productContext: z111.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
26405
- productLabel: z111.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
26406
- buyerPersona: z111.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
26407
- isCombo: z111.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
26408
- competitorScanEnabled: z111.boolean().optional().describe("Opcional. Default false: omitirlo significa SIN estudio de mercado. Antes de crear, llama estimate_market_study_cost, muestra al usuario el costo con y sin estudio y preg\xFAntale si lo quiere; env\xEDa true solo si lo acepta (o si solicita la narrativa an\xE1lisis de mercado). En video, true analiza anuncios de la competencia y desbloquea la narrativa an\xE1lisis de mercado; si el estudio no encuentra datos suficientes, degrada al planner cl\xE1sico."),
26409
- brandId: z111.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer el creativo.")
27283
+ price: z114.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
27284
+ offer: z114.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
27285
+ compareAtPrice: z114.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
27286
+ productContext: z114.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
27287
+ productLabel: z114.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
27288
+ buyerPersona: z114.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
27289
+ isCombo: z114.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
27290
+ competitorScanEnabled: z114.boolean().optional().describe("Opcional. Default false: omitirlo significa SIN estudio de mercado. Antes de crear, llama estimate_market_study_cost, muestra al usuario el costo con y sin estudio y preg\xFAntale si lo quiere; env\xEDa true solo si lo acepta (o si solicita la narrativa an\xE1lisis de mercado). En video, true analiza anuncios de la competencia y desbloquea la narrativa an\xE1lisis de mercado; si el estudio no encuentra datos suficientes, degrada al planner cl\xE1sico."),
27291
+ brandId: z114.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer el creativo.")
26410
27292
  };
26411
- var VideoPresenter = z111.discriminatedUnion("mode", [
26412
- z111.object({
26413
- mode: z111.literal("none")
27293
+ var VideoPresenter = z114.discriminatedUnion("mode", [
27294
+ z114.object({
27295
+ mode: z114.literal("none")
26414
27296
  }).strict(),
26415
- z111.object({
26416
- mode: z111.literal("saved_actor"),
26417
- actorId: z111.string().trim().min(1).max(120).describe("ID devuelto por list_actors para un actor guardado del usuario.")
27297
+ z114.object({
27298
+ mode: z114.literal("saved_actor"),
27299
+ actorId: z114.string().trim().min(1).max(120).describe("ID devuelto por list_actors para un actor guardado del usuario.")
26418
27300
  }).strict(),
26419
- z111.object({
26420
- mode: z111.literal("described"),
26421
- description: z111.string().trim().min(1).max(500).describe("Descripci\xF3n concreta de la persona que presenta el producto.")
27301
+ z114.object({
27302
+ mode: z114.literal("described"),
27303
+ description: z114.string().trim().min(1).max(500).describe("Descripci\xF3n concreta de la persona que presenta el producto.")
26422
27304
  }).strict()
26423
27305
  ]);
26424
- var VideoCreativeSetup = z111.discriminatedUnion("mode", [
26425
- z111.object({
26426
- mode: z111.literal("automatic").describe('S\xF3lo cuando el usuario pidi\xF3 expl\xEDcitamente "decide todo".')
27306
+ var VideoCreativeSetup = z114.discriminatedUnion("mode", [
27307
+ z114.object({
27308
+ mode: z114.literal("automatic").describe('S\xF3lo cuando el usuario pidi\xF3 expl\xEDcitamente "decide todo".')
26427
27309
  }).strict(),
26428
- z111.object({
26429
- mode: z111.literal("guided"),
27310
+ z114.object({
27311
+ mode: z114.literal("guided"),
26430
27312
  videoPreset: VideoPresetEnum.default("auto").describe('Narrativa opcional. Con estudio de mercado no la preguntes: se usa "auto" y el estudio define la estructura. Sin estudio conserva la elecci\xF3n del usuario. Todas las narrativas funcionan en Un solo clip y Multiescena.'),
26431
- buyerPersona: z111.string().trim().min(1).max(360),
27313
+ buyerPersona: z114.string().trim().min(1).max(360),
26432
27314
  animationStyle: AnimationStyleEnum.optional().describe('Opcional. Estilo visual SOLO para presets animados ("pixar_problem" o "pixar_sassy") \u2014 la API lo rechaza con cualquier otro preset. Opciones: "pixar_3d" (default, look 3D de pel\xEDcula animada), "claymation" (plastilina stop-motion artesanal), "wes_anderson" (tableau sim\xE9trico pastel), "sci_fi" (futurista con HUDs y ne\xF3n), "realista" (fotorrealista, fiel al producto). Si el usuario pide un look animado sin nombrar el estilo, omite el campo.'),
26433
27315
  presenter: VideoPresenter,
26434
- creativeDirection: z111.string().trim().min(1).max(500).nullable().describe("Direcci\xF3n visual elegida. null significa que el usuario deleg\xF3 expl\xEDcitamente esta decisi\xF3n.")
27316
+ creativeDirection: z114.string().trim().min(1).max(500).nullable().describe("Direcci\xF3n visual elegida. null significa que el usuario deleg\xF3 expl\xEDcitamente esta decisi\xF3n.")
26435
27317
  }).strict()
26436
27318
  ]);
26437
- var VideoInput = z111.object({
27319
+ var VideoInput = z114.object({
26438
27320
  images: CreativeRefs.images.optional(),
26439
27321
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
26440
- title: z111.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
26441
- primaryAngle: z111.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
27322
+ title: z114.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
27323
+ primaryAngle: z114.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
26442
27324
  language: CreativeRefs.language,
26443
27325
  country: CreativeRefs.country,
26444
- aspectRatio: z111.enum([
27326
+ aspectRatio: z114.enum([
26445
27327
  "9:16",
26446
27328
  "16:9",
26447
27329
  "Auto"
26448
27330
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
26449
27331
  generateAiEdit: AiEditRequestSchema.optional().describe('Opcional. Pide la Edici\xF3n con IA: una pasada que a\xF1ade texto, efectos y m\xFAsica sobre cada video entregado (variante with_ai_edit; se cobra al entregar, por video). Nivel: "economy" (Econ\xF3mica, Sonnet 5.5, m\xE1s barata) o "pro" (Opus 5.5, mejor calidad, ~10 min). true equivale a "pro"; false/omitido = sin edici\xF3n.'),
26450
- durationSeconds: z111.number().int().min(4).max(30).default(15).describe("Opcional. Duraci\xF3n objetivo: Un solo clip Econ\xF3mico o Medio usa 15s; Un solo clip Pro usa 15s o 30s; Multiescena usa 15s o 30s totales. La combinaci\xF3n de formato y calidad determina la ruta y el costo."),
26451
- videoAngleCount: z111.number().int().min(1).max(3).describe("Cantidad de videos a renderizar: 1, 2 o 3. Es una decisi\xF3n de costo obligatoria: preg\xFAntala incluso cuando creativeSetup.mode=automatic."),
27332
+ durationSeconds: z114.number().int().min(4).max(30).default(15).describe("Opcional. Duraci\xF3n objetivo: Un solo clip Econ\xF3mico o Medio usa 15s; Un solo clip Pro usa 15s o 30s; Multiescena usa 15s o 30s totales. La combinaci\xF3n de formato y calidad determina la ruta y el costo."),
27333
+ videoAngleCount: z114.number().int().min(1).max(3).describe("Cantidad de videos a renderizar: 1, 2 o 3. Es una decisi\xF3n de costo obligatoria: preg\xFAntala incluso cuando creativeSetup.mode=automatic."),
26452
27334
  angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "ugc_how_to", "ugc_unboxing_chaos", "problem_product_cta" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase", "wearable_showcase", "cinematic", "wild_card", "animated_problem_pixar", "sassy_product_pixar", "market" (narrativa an\xE1lisis de mercado: adapta el video a los patrones ganadores estudiados de la competencia; requiere competitorScanEnabled: true). angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. La lista no puede superar videoAngleCount.'),
26453
27335
  creativeSetup: VideoCreativeSetup.describe("Configuraci\xF3n creativa obligatoria. En guided pregunta narrativa, p\xFAblico, presentador y direcci\xF3n antes de confirmar. Usa automatic s\xF3lo si el usuario deleg\xF3 todo expl\xEDcitamente."),
26454
27336
  videoStructure: VideoStructureEnum.default("single_take").describe('Opcional. Formato: "single_take" = Un solo clip (generado completo; puede incluir cambios de plano) o "multi_scene" = Multiescena (varias tomas concatenadas). Default: single_take.'),
26455
27337
  videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El sistema elige el proveedor internamente. Default: economy.'),
26456
27338
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render: "480p" o "720p". Si se omite, se usa 480p; elige 720p expl\xEDcitamente si necesitas m\xE1s nitidez.'),
26457
- features: z111.array(z111.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
27339
+ features: z114.array(z114.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
26458
27340
  price: CreativeRefs.price,
26459
27341
  offer: CreativeRefs.offer,
26460
27342
  productContext: CreativeRefs.productContext,
26461
27343
  productLabel: CreativeRefs.productLabel,
26462
- cta: z111.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
26463
- scriptNotes: z111.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal: creativeSetup.mode=guided activa siempre la revisi\xF3n palabra-por-palabra con get_video_script / approve_video_script.'),
27344
+ cta: z114.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
27345
+ scriptNotes: z114.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal: creativeSetup.mode=guided activa siempre la revisi\xF3n palabra-por-palabra con get_video_script / approve_video_script.'),
26464
27346
  creativeGender: CreativeGenderEnum.optional().describe('Opcional. QUI\xC9N APARECE EN PANTALLA en el video: mujer ("female"), hombre ("male") o ambos ("mixed"). NO es la voz; cuando se fija, la voz se alinea sola con el personaje. M\xE1ndalo en cuanto el usuario diga qui\xE9n debe SALIR en el video, con cualquier redacci\xF3n ("quiero una mujer", "que salga un hombre", "protagonista femenina"): si no lo mandas, el g\xE9nero lo inventa la IA y el usuario puede recibir lo contrario de lo que pidi\xF3 sin ning\xFAn aviso. NO lo mandes cuando hable del P\xDABLICO COMPRADOR ("esto es para hombres", "mi p\xFAblico son mujeres", "va dirigido a mam\xE1s"): eso es a qui\xE9n se le vende, no qui\xE9n act\xFAa, y fijarlo ah\xED pinea un protagonista que nadie pidi\xF3. "mixed" solo cuando pida expl\xEDcitamente ambos, y ten en cuenta que no garantiza una mezcla: no fija ning\xFAn g\xE9nero y cada creativo elige su protagonista. No lo preguntes como paso obligatorio. Si creativeSetup.presenter.mode="saved_actor", el personaje guardado manda sobre este campo.'),
26465
27347
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
26466
- replicateWinnerWhenFound: z111.boolean().optional().describe("Conserva la preferencia de copiar un ganador si existe. false permite estudiar el mercado sin reemplazar un \xE1ngulo por el anuncio ganador. No a\xF1adas una pregunta obligatoria para este campo."),
27348
+ replicateWinnerWhenFound: z114.boolean().optional().describe("Conserva la preferencia de copiar un ganador si existe. false permite estudiar el mercado sin reemplazar un \xE1ngulo por el anuncio ganador. No a\xF1adas una pregunta obligatoria para este campo."),
26467
27349
  brandId: CreativeRefs.brandId
26468
27350
  }).strict();
26469
27351
  var FreeVideoQualityEnum = VideoQualityEnum;
@@ -26481,7 +27363,7 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
26481
27363
  length: 29
26482
27364
  }, (_, index) => index + 2)
26483
27365
  };
26484
- var FreeVideoModelIdEnum = z111.enum([
27366
+ var FreeVideoModelIdEnum = z114.enum([
26485
27367
  "grok-imagine-video-1-5-preview",
26486
27368
  "minimax-h3/reference-to-video",
26487
27369
  "wan/3-0-video",
@@ -26492,66 +27374,66 @@ var FREE_SELECTABLE_MODEL_IDS = [
26492
27374
  "minimax-h3/reference-to-video",
26493
27375
  "wan/3-0-video"
26494
27376
  ];
26495
- var FreeVideoInput = z111.object({
27377
+ var FreeVideoInput = z114.object({
26496
27378
  images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
26497
- rawPrompt: z111.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
27379
+ rawPrompt: z114.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
26498
27380
  language: CreativeRefs.language,
26499
27381
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro (Wan).'),
26500
27382
  // Retired in MCP 7.0.0 (Task 11): kept in the schema, accepted, and
26501
27383
  // parsed ONLY so the handler can reject it with a message naming its
26502
27384
  // replacement — dropping it silently would turn a caller's explicit
26503
27385
  // Pro++ pick into a quiet fallback to Wan.
26504
- freeProModel: z111.enum([
27386
+ freeProModel: z114.enum([
26505
27387
  "wan",
26506
27388
  "seedance"
26507
27389
  ]).optional().describe("ELIMINADO en MCP 7.0.0 \u2014 usa `modelId`. Enviar este campo produce un error."),
26508
27390
  modelId: FreeVideoModelIdEnum.optional().describe('Opcional. Elige expl\xEDcitamente el modelo del registro en vez del default de la calidad: "grok-imagine-video-1-5-preview" (Grok 1.5), "minimax-h3/reference-to-video" (H3 Max) o "wan/3-0-video" (Wan 3.0). Om\xEDtelo para usar el modelo por defecto de la calidad elegida. El modelo determina la duraci\xF3n permitida (Grok 6/8/10/15s, H3 Max 4-15s, Wan 2-30s) y el precio. "bytedance/seedance-2-5" est\xE1 inactivo y se rechaza con un error claro, nunca un 500.'),
26509
- durationSeconds: z111.number().int().min(2).max(30).describe("Duraci\xF3n exacta. Econ\xF3mico: 6, 8, 10 o 15s; Medio: enteros de 4 a 15s; Pro Wan: 2 a 30s; Pro++ Seedance: 4 a 30s."),
27391
+ durationSeconds: z114.number().int().min(2).max(30).describe("Duraci\xF3n exacta. Econ\xF3mico: 6, 8, 10 o 15s; Medio: enteros de 4 a 15s; Pro Wan: 2 a 30s; Pro++ Seedance: 4 a 30s."),
26510
27392
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
26511
- generateAiMusic: z111.boolean().default(false).describe('Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false. Equivale a generateAiEdit (nivel "pro"): la Edici\xF3n con IA trae su propia m\xFAsica.'),
27393
+ generateAiMusic: z114.boolean().default(false).describe('Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false. Equivale a generateAiEdit (nivel "pro"): la Edici\xF3n con IA trae su propia m\xFAsica.'),
26512
27394
  generateAiEdit: AiEditRequestSchema.optional().describe('Opcional. Pide la Edici\xF3n con IA: una pasada que a\xF1ade texto, gr\xE1ficos y m\xFAsica sobre el video ya entregado (variante with_ai_edit; se cobra al entregar). Nivel: "economy" (Econ\xF3mica, Sonnet 5.5, m\xE1s barata) o "pro" (Opus 5.5, mejor calidad, ~10 min). true equivale a "pro". Default sin edici\xF3n.'),
26513
- addSubtitles: z111.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
26514
- idempotencyKey: z111.string().uuid().optional().describe("Opcional. Se acepta por compatibilidad pero se ignora: la identidad del reintento se deriva autom\xE1ticamente de los argumentos sem\xE1nticos, as\xED que un retry con los mismos argumentos ya es idempotente sin este campo.")
27395
+ addSubtitles: z114.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
27396
+ idempotencyKey: z114.string().uuid().optional().describe("Opcional. Se acepta por compatibilidad pero se ignora: la identidad del reintento se deriva autom\xE1ticamente de los argumentos sem\xE1nticos, as\xED que un retry con los mismos argumentos ya es idempotente sin este campo.")
26515
27397
  }).strict();
26516
27398
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
26517
- var ImageCreativeSetup = z111.discriminatedUnion("mode", [
26518
- z111.object({
26519
- mode: z111.literal("automatic")
27399
+ var ImageCreativeSetup = z114.discriminatedUnion("mode", [
27400
+ z114.object({
27401
+ mode: z114.literal("automatic")
26520
27402
  }).strict(),
26521
- z111.object({
26522
- mode: z111.literal("guided"),
26523
- creativeArchetype: z111.union([
27403
+ z114.object({
27404
+ mode: z114.literal("guided"),
27405
+ creativeArchetype: z114.union([
26524
27406
  ImageCreativeArchetypeEnum,
26525
- z111.literal("ai_decides")
27407
+ z114.literal("ai_decides")
26526
27408
  ]),
26527
- visualTreatment: z111.union([
27409
+ visualTreatment: z114.union([
26528
27410
  ImageCreativeVisualTreatmentEnum,
26529
- z111.literal("ai_decides")
27411
+ z114.literal("ai_decides")
26530
27412
  ]),
26531
27413
  aspectRatio: ImageCreativeAspectRatioEnum,
26532
- nVariants: z111.union([
26533
- z111.literal(1),
26534
- z111.literal(2),
26535
- z111.literal(3),
26536
- z111.literal(4),
26537
- z111.literal(6)
27414
+ nVariants: z114.union([
27415
+ z114.literal(1),
27416
+ z114.literal(2),
27417
+ z114.literal(3),
27418
+ z114.literal(4),
27419
+ z114.literal(6)
26538
27420
  ]),
26539
- imageModel: z111.enum(PUBLIC_IMAGE_QUALITY_INPUTS).describe('Calidad: "image:1k" (Est\xE1ndar) o "image:2k" (Premium). Los tokens gpt-image-2-5:{1k,2k} y gpt-image-2:{1k,2k} se siguen aceptando.'),
26540
- styleMix: z111.boolean().nullable().describe("Decisi\xF3n s\xF3lo para 2\u20134 variantes. Usa null para 1 o 6, cuando la UI no hace esta pregunta."),
26541
- generateAdCopy: z111.boolean(),
26542
- competitorScanEnabled: z111.boolean(),
26543
- productContext: z111.string().trim().min(1).max(3e3).nullable(),
26544
- creativeDirection: z111.string().trim().min(1).max(500).nullable(),
26545
- brandColors: z111.string().trim().min(1).max(240).nullable().describe('Colores en lenguaje natural, igual que en el chat web. Ej. "Azul y Dorado". null significa sin colores espec\xEDficos.'),
26546
- headline: z111.string().trim().min(1).max(120).nullable(),
26547
- subheadline: z111.string().trim().min(1).max(160).nullable()
27421
+ imageModel: z114.enum(PUBLIC_IMAGE_QUALITY_INPUTS).describe('Calidad: "image:1k" (Est\xE1ndar) o "image:2k" (Premium). Los tokens gpt-image-2-5:{1k,2k} y gpt-image-2:{1k,2k} se siguen aceptando.'),
27422
+ styleMix: z114.boolean().nullable().describe("Decisi\xF3n s\xF3lo para 2\u20134 variantes. Usa null para 1 o 6, cuando la UI no hace esta pregunta."),
27423
+ generateAdCopy: z114.boolean(),
27424
+ competitorScanEnabled: z114.boolean(),
27425
+ productContext: z114.string().trim().min(1).max(3e3).nullable(),
27426
+ creativeDirection: z114.string().trim().min(1).max(500).nullable(),
27427
+ brandColors: z114.string().trim().min(1).max(240).nullable().describe('Colores en lenguaje natural, igual que en el chat web. Ej. "Azul y Dorado". null significa sin colores espec\xEDficos.'),
27428
+ headline: z114.string().trim().min(1).max(120).nullable(),
27429
+ subheadline: z114.string().trim().min(1).max(160).nullable()
26548
27430
  }).strict()
26549
27431
  ]).superRefine((setup, context) => {
26550
27432
  if (setup.mode !== "guided") return;
26551
27433
  const needsStyleMix = setup.nVariants >= 2 && setup.nVariants <= 4;
26552
27434
  if (needsStyleMix && setup.styleMix === null) {
26553
27435
  context.addIssue({
26554
- code: z111.ZodIssueCode.custom,
27436
+ code: z114.ZodIssueCode.custom,
26555
27437
  path: [
26556
27438
  "styleMix"
26557
27439
  ],
@@ -26560,7 +27442,7 @@ var ImageCreativeSetup = z111.discriminatedUnion("mode", [
26560
27442
  }
26561
27443
  if (!needsStyleMix && setup.styleMix !== null) {
26562
27444
  context.addIssue({
26563
- code: z111.ZodIssueCode.custom,
27445
+ code: z114.ZodIssueCode.custom,
26564
27446
  path: [
26565
27447
  "styleMix"
26566
27448
  ],
@@ -26569,7 +27451,7 @@ var ImageCreativeSetup = z111.discriminatedUnion("mode", [
26569
27451
  }
26570
27452
  if (imageResolutionOf(setup.imageModel) === "2k" && setup.aspectRatio === "4:5") {
26571
27453
  context.addIssue({
26572
- code: z111.ZodIssueCode.custom,
27454
+ code: z114.ZodIssueCode.custom,
26573
27455
  path: [
26574
27456
  "aspectRatio"
26575
27457
  ],
@@ -26577,55 +27459,55 @@ var ImageCreativeSetup = z111.discriminatedUnion("mode", [
26577
27459
  });
26578
27460
  }
26579
27461
  });
26580
- var LandingCreativeSetup = z111.discriminatedUnion("mode", [
26581
- z111.object({
26582
- mode: z111.literal("automatic")
27462
+ var LandingCreativeSetup = z114.discriminatedUnion("mode", [
27463
+ z114.object({
27464
+ mode: z114.literal("automatic")
26583
27465
  }).strict(),
26584
- z111.object({
26585
- mode: z111.literal("guided"),
27466
+ z114.object({
27467
+ mode: z114.literal("guided"),
26586
27468
  landingStrategy: LandingStrategyEnum,
26587
27469
  landingImageFormat: LandingImageFormatEnum,
26588
- competitorScanEnabled: z111.boolean(),
26589
- paymentMethods: z111.string().trim().min(1).max(240).nullable(),
26590
- productContext: z111.string().trim().min(1).max(3e3).nullable(),
26591
- creativeDirection: z111.string().trim().min(1).max(500).nullable(),
26592
- headline: z111.string().trim().min(1).max(120).nullable(),
26593
- subheadline: z111.string().trim().min(1).max(160).nullable()
27470
+ competitorScanEnabled: z114.boolean(),
27471
+ paymentMethods: z114.string().trim().min(1).max(240).nullable(),
27472
+ productContext: z114.string().trim().min(1).max(3e3).nullable(),
27473
+ creativeDirection: z114.string().trim().min(1).max(500).nullable(),
27474
+ headline: z114.string().trim().min(1).max(120).nullable(),
27475
+ subheadline: z114.string().trim().min(1).max(160).nullable()
26594
27476
  }).strict()
26595
27477
  ]);
26596
- var ImageInput = z111.object({
27478
+ var ImageInput = z114.object({
26597
27479
  images: CreativeRefs.images.optional(),
26598
27480
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
26599
- productTitle: z111.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
26600
- primaryAngle: z111.string().min(1).optional().describe('Opcional. \xC1ngulo de venta principal. Si se omite, ProductMaker lo genera y creativeSetup.mode="guided" lo muestra para revisi\xF3n antes de crear las im\xE1genes.'),
27481
+ productTitle: z114.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
27482
+ primaryAngle: z114.string().min(1).optional().describe('Opcional. \xC1ngulo de venta principal. Si se omite, ProductMaker lo genera y creativeSetup.mode="guided" lo muestra para revisi\xF3n antes de crear las im\xE1genes.'),
26601
27483
  buyerPersona: CreativeRefs.buyerPersona,
26602
27484
  language: CreativeRefs.language,
26603
27485
  country: CreativeRefs.country,
26604
27486
  creativeSetup: ImageCreativeSetup.describe("Configuraci\xF3n obligatoria equivalente al wizard web. Usa guided salvo que el usuario delegue expl\xEDcitamente todo; presenta las decisiones guiadas en un \xFAnico checklist compacto."),
26605
27487
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
26606
- features: z111.array(z111.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
27488
+ features: z114.array(z114.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
26607
27489
  price: CreativeRefs.price,
26608
- omitPrice: z111.boolean().optional().describe("Pon true \xFAnicamente cuando el usuario pida expl\xEDcitamente una imagen sin precio. Evita que el backend reutilice un precio extra\xEDdo del producto. No lo combines con price ni compareAtPrice."),
27490
+ omitPrice: z114.boolean().optional().describe("Pon true \xFAnicamente cuando el usuario pida expl\xEDcitamente una imagen sin precio. Evita que el backend reutilice un precio extra\xEDdo del producto. No lo combines con price ni compareAtPrice."),
26609
27491
  compareAtPrice: CreativeRefs.compareAtPrice,
26610
27492
  offer: CreativeRefs.offer,
26611
27493
  productLabel: CreativeRefs.productLabel,
26612
27494
  isCombo: CreativeRefs.isCombo,
26613
27495
  brandId: CreativeRefs.brandId
26614
27496
  });
26615
- var EditImageCreativeInput = z111.object({
26616
- taskId: z111.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
26617
- variantIndex: z111.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
26618
- clientRequestId: z111.string().min(1).max(160).optional().describe("Identidad estable de esta correcci\xF3n; reutil\xEDzala al reconciliar una respuesta perdida, no para otra instrucci\xF3n."),
26619
- instruction: z111.string().min(1).max(1e3).describe('Qu\xE9 cambiar en esa imagen. Ej. "quita el badge de env\xEDo gratis", "cambia el fondo a blanco".')
27497
+ var EditImageCreativeInput = z114.object({
27498
+ taskId: z114.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
27499
+ variantIndex: z114.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
27500
+ clientRequestId: z114.string().min(1).max(160).optional().describe("Identidad estable de esta correcci\xF3n; reutil\xEDzala al reconciliar una respuesta perdida, no para otra instrucci\xF3n."),
27501
+ instruction: z114.string().min(1).max(1e3).describe('Qu\xE9 cambiar en esa imagen. Ej. "quita el badge de env\xEDo gratis", "cambia el fondo a blanco".')
26620
27502
  });
26621
- var LandingInput = z111.object({
26622
- includeInitialLandingGif: z111.boolean().optional().describe("Opcional. En nuevas landings cl\xE1sicas habilitadas, omitirlo incluye el primer GIF del producto y su coste en el total. Env\xEDa false si el usuario lo excluye: no se genera ni se reserva su coste. Conserva la elecci\xF3n al confirmar y al reintentar."),
27503
+ var LandingInput = z114.object({
27504
+ includeInitialLandingGif: z114.boolean().optional().describe("Opcional. En nuevas landings cl\xE1sicas habilitadas, omitirlo incluye el primer GIF del producto y su coste en el total. Env\xEDa false si el usuario lo excluye: no se genera ni se reserva su coste. Conserva la elecci\xF3n al confirmar y al reintentar."),
26623
27505
  images: CreativeRefs.images.optional(),
26624
27506
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
26625
27507
  language: CreativeRefs.language,
26626
27508
  country: CreativeRefs.country,
26627
27509
  creativeSetup: LandingCreativeSetup.describe("Configuraci\xF3n obligatoria de landing cl\xE1sica equivalente al wizard web. No expone landing editable/code mientras siga siendo admin-only."),
26628
- salesAngle: z111.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
27510
+ salesAngle: z114.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
26629
27511
  buyerPersona: CreativeRefs.buyerPersona,
26630
27512
  price: CreativeRefs.price,
26631
27513
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -26907,8 +27789,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
26907
27789
  server.registerTool("get_image_edit_status", {
26908
27790
  description: imageEditStatusGuidance.summary,
26909
27791
  inputSchema: {
26910
- taskId: z111.string().uuid(),
26911
- operationId: z111.string().uuid()
27792
+ taskId: z114.string().uuid(),
27793
+ operationId: z114.string().uuid()
26912
27794
  },
26913
27795
  annotations: READS_INTERNAL
26914
27796
  }, async (input) => {
@@ -26965,10 +27847,10 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
26965
27847
  __name(registerCreativeTools, "registerCreativeTools");
26966
27848
 
26967
27849
  // src/tools/creative-variants.ts
26968
- import { z as z112 } from "zod";
27850
+ import { z as z115 } from "zod";
26969
27851
  var estimateCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("estimate_creative_variant");
26970
27852
  var createCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("create_creative_variant");
26971
- var LandingSectionKeyEnum = z112.enum([
27853
+ var LandingSectionKeyEnum = z115.enum([
26972
27854
  "problem_hook",
26973
27855
  "problem_cost",
26974
27856
  "solution_mechanism",
@@ -27008,24 +27890,24 @@ var LandingSectionKeyEnum = z112.enum([
27008
27890
  "lifestyle_fit",
27009
27891
  "join_lifestyle"
27010
27892
  ]);
27011
- var CreativeVariantKindEnum = z112.enum([
27893
+ var CreativeVariantKindEnum = z115.enum([
27012
27894
  "video",
27013
27895
  "image",
27014
27896
  "landing"
27015
27897
  ]);
27016
- var EstimateCreativeVariantInputBase = z112.object({
27017
- sourceTaskId: z112.string().uuid().describe("ID de la tarea original completada."),
27898
+ var EstimateCreativeVariantInputBase = z115.object({
27899
+ sourceTaskId: z115.string().uuid().describe("ID de la tarea original completada."),
27018
27900
  kind: CreativeVariantKindEnum,
27019
27901
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
27020
27902
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
27021
- nVariants: z112.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
27903
+ nVariants: z115.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
27022
27904
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
27023
- instructions: z112.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
27905
+ instructions: z115.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
27024
27906
  }).strict();
27025
27907
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
27026
27908
  if (input.kind === "video" && input.nVariants !== void 0) {
27027
27909
  ctx.addIssue({
27028
- code: z112.ZodIssueCode.custom,
27910
+ code: z115.ZodIssueCode.custom,
27029
27911
  path: [
27030
27912
  "nVariants"
27031
27913
  ],
@@ -27035,7 +27917,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
27035
27917
  if (input.kind !== "video") {
27036
27918
  if (input.videoPreset !== void 0) {
27037
27919
  ctx.addIssue({
27038
- code: z112.ZodIssueCode.custom,
27920
+ code: z115.ZodIssueCode.custom,
27039
27921
  path: [
27040
27922
  "videoPreset"
27041
27923
  ],
@@ -27044,7 +27926,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
27044
27926
  }
27045
27927
  if (input.videoResolution !== void 0) {
27046
27928
  ctx.addIssue({
27047
- code: z112.ZodIssueCode.custom,
27929
+ code: z115.ZodIssueCode.custom,
27048
27930
  path: [
27049
27931
  "videoResolution"
27050
27932
  ],
@@ -27054,7 +27936,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
27054
27936
  }
27055
27937
  if (input.kind !== "image" && input.nVariants !== void 0) {
27056
27938
  ctx.addIssue({
27057
- code: z112.ZodIssueCode.custom,
27939
+ code: z115.ZodIssueCode.custom,
27058
27940
  path: [
27059
27941
  "nVariants"
27060
27942
  ],
@@ -27064,7 +27946,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
27064
27946
  if (input.kind !== "landing") {
27065
27947
  if (input.sectionKey !== void 0) {
27066
27948
  ctx.addIssue({
27067
- code: z112.ZodIssueCode.custom,
27949
+ code: z115.ZodIssueCode.custom,
27068
27950
  path: [
27069
27951
  "sectionKey"
27070
27952
  ],
@@ -27073,7 +27955,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
27073
27955
  }
27074
27956
  if (input.instructions !== void 0) {
27075
27957
  ctx.addIssue({
27076
- code: z112.ZodIssueCode.custom,
27958
+ code: z115.ZodIssueCode.custom,
27077
27959
  path: [
27078
27960
  "instructions"
27079
27961
  ],
@@ -27082,31 +27964,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
27082
27964
  }
27083
27965
  }
27084
27966
  });
27085
- var CreateCreativeVariantInputBase = z112.object({
27086
- sourceTaskId: z112.string().uuid().describe("ID de la tarea original completada."),
27967
+ var CreateCreativeVariantInputBase = z115.object({
27968
+ sourceTaskId: z115.string().uuid().describe("ID de la tarea original completada."),
27087
27969
  kind: CreativeVariantKindEnum,
27088
- confirm: z112.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
27089
- clientRequestId: z112.string().uuid().describe("UUID estable para esta intenci\xF3n (obligatorio por compatibilidad de contrato; el servidor deriva la identidad real del request desde los argumentos sem\xE1nticos, as\xED que un UUID nuevo en un retry NO crea una variante duplicada). Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n."),
27090
- angle: z112.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
27091
- buyerPersona: z112.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
27092
- scriptNotes: z112.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
27093
- creativeDirection: z112.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
27970
+ confirm: z115.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
27971
+ clientRequestId: z115.string().uuid().describe("UUID estable para esta intenci\xF3n (obligatorio por compatibilidad de contrato; el servidor deriva la identidad real del request desde los argumentos sem\xE1nticos, as\xED que un UUID nuevo en un retry NO crea una variante duplicada). Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n."),
27972
+ angle: z115.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
27973
+ buyerPersona: z115.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
27974
+ scriptNotes: z115.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
27975
+ creativeDirection: z115.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
27094
27976
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
27095
27977
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
27096
- generateAiMusic: z112.boolean().optional().describe("Opcional para video."),
27097
- nVariants: z112.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
27978
+ generateAiMusic: z115.boolean().optional().describe("Opcional para video."),
27979
+ nVariants: z115.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
27098
27980
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
27099
27981
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
27100
27982
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
27101
27983
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
27102
- styleMix: z112.boolean().optional().describe("Opcional para imagen."),
27984
+ styleMix: z115.boolean().optional().describe("Opcional para imagen."),
27103
27985
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
27104
- instructions: z112.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
27986
+ instructions: z115.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
27105
27987
  }).strict();
27106
27988
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
27107
27989
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
27108
27990
  ctx.addIssue({
27109
- code: z112.ZodIssueCode.custom,
27991
+ code: z115.ZodIssueCode.custom,
27110
27992
  path: [
27111
27993
  "angle"
27112
27994
  ],
@@ -27115,7 +27997,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
27115
27997
  }
27116
27998
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
27117
27999
  ctx.addIssue({
27118
- code: z112.ZodIssueCode.custom,
28000
+ code: z115.ZodIssueCode.custom,
27119
28001
  path: [
27120
28002
  "instructions"
27121
28003
  ],
@@ -27161,7 +28043,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
27161
28043
  for (const field of fieldsByKind[input.kind]) {
27162
28044
  if (input[field] !== void 0) {
27163
28045
  ctx.addIssue({
27164
- code: z112.ZodIssueCode.custom,
28046
+ code: z115.ZodIssueCode.custom,
27165
28047
  path: [
27166
28048
  field
27167
28049
  ],
@@ -27294,13 +28176,13 @@ function registerCreativeVariantTools(server, api) {
27294
28176
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
27295
28177
 
27296
28178
  // src/tools/script-review.ts
27297
- import { z as z113 } from "zod";
28179
+ import { z as z116 } from "zod";
27298
28180
  var getVideoScriptGuidance = getProductMakerCapabilityModelGuidance("get_video_script");
27299
28181
  var regenerateVideoScriptGuidance = getProductMakerCapabilityModelGuidance("regenerate_video_script");
27300
28182
  var approveVideoScriptGuidance = getProductMakerCapabilityModelGuidance("approve_video_script");
27301
- var GetVideoScriptInput = z113.object({
27302
- taskId: z113.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
27303
- angleKey: z113.string().min(1).optional().describe('Clave del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). REQUERIDO para tareas multi-angle: si se omite, el backend resuelve a "angle_0" LITERAL (no "el primer pendiente"), as\xED que en multi-angle donde angle_0 ya est\xE9 aprobado/saltado obtendr\xEDas status="approved" y nunca alcanzar\xEDas el \xE1ngulo realmente pendiente. Para single-angle puede omitirse.')
28183
+ var GetVideoScriptInput = z116.object({
28184
+ taskId: z116.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
28185
+ angleKey: z116.string().min(1).optional().describe('Clave del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). REQUERIDO para tareas multi-angle: si se omite, el backend resuelve a "angle_0" LITERAL (no "el primer pendiente"), as\xED que en multi-angle donde angle_0 ya est\xE9 aprobado/saltado obtendr\xEDas status="approved" y nunca alcanzar\xEDas el \xE1ngulo realmente pendiente. Para single-angle puede omitirse.')
27304
28186
  }).strict();
27305
28187
  var ApproveVideoScriptInput = ApproveVideoScriptCapabilityRequest;
27306
28188
  var RegenerateVideoScriptInput = RegenerateVideoScriptCapabilityRequest.extend({
@@ -27383,7 +28265,7 @@ function registerScriptReviewTools(server, api) {
27383
28265
  __name(registerScriptReviewTools, "registerScriptReviewTools");
27384
28266
 
27385
28267
  // src/tools/connections.ts
27386
- import { z as z114 } from "zod";
28268
+ import { z as z117 } from "zod";
27387
28269
  var listShopifyShopsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_shops");
27388
28270
  var listShopifyProductsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_products");
27389
28271
  var listMetaAdAccountsGuidance = getProductMakerCapabilityModelGuidance("list_meta_ad_accounts");
@@ -27392,7 +28274,7 @@ var listTikTokAdvertisersGuidance = getProductMakerCapabilityModelGuidance("list
27392
28274
  function registerConnectionTools(server, api) {
27393
28275
  server.registerTool("list_shopify_shops", {
27394
28276
  description: listShopifyShopsGuidance.summary + " Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.",
27395
- inputSchema: z114.object({}).shape,
28277
+ inputSchema: z117.object({}).shape,
27396
28278
  annotations: READS_INTERNAL
27397
28279
  }, async () => {
27398
28280
  try {
@@ -27404,10 +28286,10 @@ function registerConnectionTools(server, api) {
27404
28286
  });
27405
28287
  server.registerTool("list_shopify_products", {
27406
28288
  description: listShopifyProductsGuidance.summary + ' Usa el `id` devuelto junto con `publicationMode="existing"` en `publish_to_shopify`. Si `taskId` se proporciona, la respuesta tambi\xE9n indica el producto publicado actualmente para esa tarea y tienda. No modifica Shopify.',
27407
- inputSchema: z114.object({
27408
- shopDomain: z114.string().min(3).max(120),
27409
- query: z114.string().trim().max(120).optional(),
27410
- taskId: z114.string().uuid().optional()
28289
+ inputSchema: z117.object({
28290
+ shopDomain: z117.string().min(3).max(120),
28291
+ query: z117.string().trim().max(120).optional(),
28292
+ taskId: z117.string().uuid().optional()
27411
28293
  }).shape,
27412
28294
  annotations: READS_EXTERNAL
27413
28295
  }, async ({ shopDomain, query, taskId }) => {
@@ -27425,7 +28307,7 @@ function registerConnectionTools(server, api) {
27425
28307
  });
27426
28308
  server.registerTool("list_meta_ad_accounts", {
27427
28309
  description: listMetaAdAccountsGuidance.summary + " Para publicar, prefiere `list_meta_destinations`, que tambi\xE9n devuelve el portfolio y las p\xE1ginas disponibles. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar Meta en productmaker.app/integrations.",
27428
- inputSchema: z114.object({}).shape,
28310
+ inputSchema: z117.object({}).shape,
27429
28311
  annotations: READS_EXTERNAL
27430
28312
  }, async () => {
27431
28313
  try {
@@ -27437,7 +28319,7 @@ function registerConnectionTools(server, api) {
27437
28319
  });
27438
28320
  server.registerTool("list_meta_destinations", {
27439
28321
  description: listMetaDestinationsGuidance.summary + " Es obligatorio llamar esta herramienta antes de publicar, mostrar las opciones y enviar al publicador el `portfolioId`, `adAccountId` y `pageId` elegidos; nunca elijas silenciosamente la primera p\xE1gina o cuenta. No modifica Meta.",
27440
- inputSchema: z114.object({}).shape,
28322
+ inputSchema: z117.object({}).shape,
27441
28323
  annotations: READS_EXTERNAL,
27442
28324
  _meta: {
27443
28325
  "openai/widgetAccessible": true
@@ -27452,7 +28334,7 @@ function registerConnectionTools(server, api) {
27452
28334
  });
27453
28335
  server.registerTool("list_tiktok_advertisers", {
27454
28336
  description: listTikTokAdvertisersGuidance.summary + " Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.",
27455
- inputSchema: z114.object({}).shape,
28337
+ inputSchema: z117.object({}).shape,
27456
28338
  annotations: READS_EXTERNAL
27457
28339
  }, async () => {
27458
28340
  try {
@@ -27466,7 +28348,7 @@ function registerConnectionTools(server, api) {
27466
28348
  __name(registerConnectionTools, "registerConnectionTools");
27467
28349
 
27468
28350
  // src/tools/actors.ts
27469
- import { z as z115 } from "zod";
28351
+ import { z as z118 } from "zod";
27470
28352
  var listActorsGuidance = getProductMakerCapabilityModelGuidance("list_actors");
27471
28353
  var actorVoiceGuidance = {
27472
28354
  capabilities: getProductMakerCapabilityModelGuidance("get_actor_voice_capabilities"),
@@ -27506,7 +28388,7 @@ function sanitizeVoiceResult(value) {
27506
28388
  __name(sanitizeVoiceResult, "sanitizeVoiceResult");
27507
28389
  function registerActorTools(server, api) {
27508
28390
  const actor = {
27509
- actorId: z115.string().min(1).max(64)
28391
+ actorId: z118.string().min(1).max(64)
27510
28392
  };
27511
28393
  const safe = /* @__PURE__ */ __name(async (request) => {
27512
28394
  try {
@@ -27535,8 +28417,8 @@ function registerActorTools(server, api) {
27535
28417
  inputSchema: {
27536
28418
  ...actor,
27537
28419
  draft: ActorVoiceDraftSchema,
27538
- requestKey: z115.string().trim().min(1).max(128),
27539
- acceptedQuoteId: z115.string().trim().min(1).max(128)
28420
+ requestKey: z118.string().trim().min(1).max(128),
28421
+ acceptedQuoteId: z118.string().trim().min(1).max(128)
27540
28422
  },
27541
28423
  annotations: CREATES_PAID
27542
28424
  }, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews`, body)));
@@ -27544,7 +28426,7 @@ function registerActorTools(server, api) {
27544
28426
  description: actorVoiceGuidance.get.summary,
27545
28427
  inputSchema: {
27546
28428
  ...actor,
27547
- candidateId: z115.string().min(1).max(64)
28429
+ candidateId: z118.string().min(1).max(64)
27548
28430
  },
27549
28431
  annotations: READS_INTERNAL
27550
28432
  }, async ({ actorId, candidateId }) => safe(api.getJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews/${encodeURIComponent(candidateId)}`)));
@@ -27552,15 +28434,15 @@ function registerActorTools(server, api) {
27552
28434
  description: actorVoiceGuidance.approve.summary,
27553
28435
  inputSchema: {
27554
28436
  ...actor,
27555
- candidateId: z115.string().min(1).max(64),
27556
- expectedRevision: z115.number().int().positive(),
27557
- expectedApprovedRevision: z115.number().int().positive().nullable().optional()
28437
+ candidateId: z118.string().min(1).max(64),
28438
+ expectedRevision: z118.number().int().positive(),
28439
+ expectedApprovedRevision: z118.number().int().positive().nullable().optional()
27558
28440
  },
27559
28441
  annotations: CREATES_INTERNAL
27560
28442
  }, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/approve`, body)));
27561
28443
  server.registerTool("list_actors", {
27562
28444
  description: listActorsGuidance.summary + " Devuelve id, nombre, descripci\xF3n, g\xE9nero y voz privada del personaje. Cuando el personaje tiene una voz personalizada, `voice.identityId` y `voice.revision` identifican la versi\xF3n aprobada \u2014 conserva la revisi\xF3n al llamar `approve_actor_voice` (expectedApprovedRevision) para reemplazarla. Los IDs de proveedor nunca se exponen por MCP. Usa el `id` como `actorId` al crear o editar una tarea. Solo aparecen los personajes listos para usar; si la lista est\xE1 vac\xEDa, dile al usuario que cree uno en productmaker.app antes de intentar asignarlo. No modifica nada.",
27563
- inputSchema: z115.object({}).shape,
28445
+ inputSchema: z118.object({}).shape,
27564
28446
  annotations: READS_INTERNAL
27565
28447
  }, async () => {
27566
28448
  try {
@@ -27593,7 +28475,7 @@ function registerActorTools(server, api) {
27593
28475
  __name(registerActorTools, "registerActorTools");
27594
28476
 
27595
28477
  // src/tools/publish.ts
27596
- import { z as z116 } from "zod";
28478
+ import { z as z119 } from "zod";
27597
28479
 
27598
28480
  // src/tools/meta-publish-attempt-id.ts
27599
28481
  var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
@@ -27632,40 +28514,40 @@ var publishToShopifyGuidance = getProductMakerCapabilityModelGuidance("publish_t
27632
28514
  var publishToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_to_meta");
27633
28515
  var publishCreativesToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_creatives_to_meta");
27634
28516
  var publishToTikTokGuidance = getProductMakerCapabilityModelGuidance("publish_to_tiktok");
27635
- var ShopifyInput = z116.object({
27636
- taskId: z116.string().min(1),
27637
- shopDomain: z116.string().min(1),
28517
+ var ShopifyInput = z119.object({
28518
+ taskId: z119.string().min(1),
28519
+ shopDomain: z119.string().min(1),
27638
28520
  /** Override: number of landing images to include. Default = ALL images the
27639
28521
  * task generated (the MCP fetches /v1/results first to count). Bounded
27640
28522
  * generously here; the backend validates each index against
27641
28523
  * landing.images.length and rejects out-of-range. */
27642
- numLandingImages: z116.number().int().min(1).max(20).optional(),
27643
- variantIndex: z116.number().int().min(0).optional(),
28524
+ numLandingImages: z119.number().int().min(1).max(20).optional(),
28525
+ variantIndex: z119.number().int().min(0).optional(),
27644
28526
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
27645
28527
  * When true, the landing renders variant-picker pills; when false, it ships
27646
28528
  * a single-option product page. Defaults to false. */
27647
- showVariantPicker: z116.boolean().optional(),
28529
+ showVariantPicker: z119.boolean().optional(),
27648
28530
  /** Default `auto` updates the product previously published for this task and
27649
28531
  * shop. Use `existing` with targetProductId to choose another product, or
27650
28532
  * `new` only when the user explicitly wants a duplicate/new listing. */
27651
- publicationMode: z116.enum([
28533
+ publicationMode: z119.enum([
27652
28534
  "auto",
27653
28535
  "existing",
27654
28536
  "new"
27655
28537
  ]).optional(),
27656
28538
  /** Shopify product GID, numeric id, or admin product URL. Required when
27657
28539
  * publicationMode is `existing`. */
27658
- targetProductId: z116.string().trim().min(1).max(300).optional(),
28540
+ targetProductId: z119.string().trim().min(1).max(300).optional(),
27659
28541
  /** Add an announcement bar text above add-to-cart (optional). */
27660
- announcementBarEnabled: z116.boolean().optional(),
27661
- announcementBarText: z116.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional(),
28542
+ announcementBarEnabled: z119.boolean().optional(),
28543
+ announcementBarText: z119.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional(),
27662
28544
  commerceCorrection: PublishCommerceCorrectionSchema.optional().describe("Complete seller-confirmed commerce values, with the revision and fingerprint returned in recovery. Never invent missing prices or convert currency."),
27663
- confirmed: z116.boolean().optional().describe("Required as true only when submitting commerceCorrection, after showing its values and obtaining explicit seller confirmation.")
28545
+ confirmed: z119.boolean().optional().describe("Required as true only when submitting commerceCorrection, after showing its values and obtaining explicit seller confirmation.")
27664
28546
  });
27665
28547
  var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
27666
28548
  if (value.commerceCorrection && value.confirmed !== true) {
27667
28549
  ctx.addIssue({
27668
- code: z116.ZodIssueCode.custom,
28550
+ code: z119.ZodIssueCode.custom,
27669
28551
  path: [
27670
28552
  "confirmed"
27671
28553
  ],
@@ -27674,7 +28556,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
27674
28556
  }
27675
28557
  if (value.announcementBarEnabled && !value.announcementBarText) {
27676
28558
  ctx.addIssue({
27677
- code: z116.ZodIssueCode.custom,
28559
+ code: z119.ZodIssueCode.custom,
27678
28560
  path: [
27679
28561
  "announcementBarText"
27680
28562
  ],
@@ -27683,7 +28565,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
27683
28565
  }
27684
28566
  if (!value.announcementBarEnabled && value.announcementBarText) {
27685
28567
  ctx.addIssue({
27686
- code: z116.ZodIssueCode.custom,
28568
+ code: z119.ZodIssueCode.custom,
27687
28569
  path: [
27688
28570
  "announcementBarText"
27689
28571
  ],
@@ -27697,91 +28579,91 @@ function parseShopifyInput(rawInput) {
27697
28579
  return parsed.data;
27698
28580
  }
27699
28581
  __name(parseShopifyInput, "parseShopifyInput");
27700
- var McpCampaignConfig = z116.object({
28582
+ var McpCampaignConfig = z119.object({
27701
28583
  /** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
27702
28584
  * converts to the account currency server-side. Do NOT pre-convert: on a COP
27703
28585
  * account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
27704
- dailyBudgetUsd: z116.number().positive().optional(),
28586
+ dailyBudgetUsd: z119.number().positive().optional(),
27705
28587
  /** ISO-3166-1 alpha-2 country codes or resolvable names (CountryIso2Schema
27706
28588
  * normalizes at validation). Meta accepts max 1 (single-country ad set).
27707
28589
  * TikTok takes only the first entry. */
27708
- targetCountries: z116.array(CountryIso2Schema).max(1).optional(),
27709
- campaignName: z116.string().trim().min(1).max(100).optional()
28590
+ targetCountries: z119.array(CountryIso2Schema).max(1).optional(),
28591
+ campaignName: z119.string().trim().min(1).max(100).optional()
27710
28592
  });
27711
28593
  var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
27712
- var MetaInput = z116.object({
27713
- taskId: z116.string().min(1),
28594
+ var MetaInput = z119.object({
28595
+ taskId: z119.string().min(1),
27714
28596
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
27715
- portfolioId: z116.string().trim().min(1),
28597
+ portfolioId: z119.string().trim().min(1),
27716
28598
  /** Exact Facebook Page selected from `list_meta_destinations`. */
27717
- pageId: z116.string().trim().min(1),
28599
+ pageId: z119.string().trim().min(1),
27718
28600
  adAccountId: McpAdAccountId,
27719
- pixelId: z116.string().min(1).optional(),
27720
- landingUrl: z116.string().url().optional(),
28601
+ pixelId: z119.string().min(1).optional(),
28602
+ landingUrl: z119.string().url().optional(),
27721
28603
  /** Select which angles to publish by 0-based index. Omit to publish all
27722
28604
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
27723
- angleIndexes: z116.array(z116.number().int().nonnegative()).optional(),
28605
+ angleIndexes: z119.array(z119.number().int().nonnegative()).optional(),
27724
28606
  campaignConfig: McpCampaignConfig.optional(),
27725
28607
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
27726
28608
  * uses the auto-generated copy attached to the task. Capped at 500 chars
27727
28609
  * (Meta's primary-text limit). */
27728
- primaryText: z116.string().trim().min(1).max(500).optional(),
28610
+ primaryText: z119.string().trim().min(1).max(500).optional(),
27729
28611
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
27730
28612
  * (wa.me) instead of a landing page. */
27731
- phoneNumber: z116.string().min(8).max(32).optional()
28613
+ phoneNumber: z119.string().min(8).max(32).optional()
27732
28614
  });
27733
- var GroupedTaskSource = z116.object({
27734
- type: z116.literal("task"),
27735
- taskId: z116.string().min(1),
27736
- angleIndex: z116.number().int().min(0).optional(),
27737
- videoVariant: z116.enum([
28615
+ var GroupedTaskSource = z119.object({
28616
+ type: z119.literal("task"),
28617
+ taskId: z119.string().min(1),
28618
+ angleIndex: z119.number().int().min(0).optional(),
28619
+ videoVariant: z119.enum([
27738
28620
  "with_subtitles",
27739
28621
  "without_subtitles"
27740
28622
  ]).optional(),
27741
- imageVariantIndex: z116.number().int().min(0).optional()
27742
- });
27743
- var GroupedMediaSource = z116.object({
27744
- type: z116.literal("media"),
27745
- url: z116.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
27746
- path: z116.string().min(1).optional().describe('Local file path only when this MCP runs over stdio. On the hosted MCP, call request_media_upload and then use source.type="asset".'),
27747
- thumbnailUrl: z116.string().url().optional(),
27748
- thumbnailPath: z116.string().min(1).optional()
28623
+ imageVariantIndex: z119.number().int().min(0).optional()
28624
+ });
28625
+ var GroupedMediaSource = z119.object({
28626
+ type: z119.literal("media"),
28627
+ url: z119.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
28628
+ path: z119.string().min(1).optional().describe('Local file path only when this MCP runs over stdio. On the hosted MCP, call request_media_upload and then use source.type="asset".'),
28629
+ thumbnailUrl: z119.string().url().optional(),
28630
+ thumbnailPath: z119.string().min(1).optional()
27749
28631
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
27750
28632
  message: "media source needs exactly one of `url` or `path`"
27751
28633
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
27752
28634
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
27753
28635
  });
27754
- var GroupedUploadedAssetSource = z116.object({
27755
- type: z116.literal("asset"),
28636
+ var GroupedUploadedAssetSource = z119.object({
28637
+ type: z119.literal("asset"),
27756
28638
  /** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
27757
- assetId: z116.string().uuid(),
27758
- thumbnailAssetId: z116.string().uuid().optional()
28639
+ assetId: z119.string().uuid(),
28640
+ thumbnailAssetId: z119.string().uuid().optional()
27759
28641
  });
27760
- var GroupedCreative = z116.object({
27761
- type: z116.enum([
28642
+ var GroupedCreative = z119.object({
28643
+ type: z119.enum([
27762
28644
  "image",
27763
28645
  "video"
27764
28646
  ]),
27765
- source: z116.union([
28647
+ source: z119.union([
27766
28648
  GroupedTaskSource,
27767
28649
  GroupedMediaSource,
27768
28650
  GroupedUploadedAssetSource
27769
28651
  ]),
27770
- title: z116.string().max(120).optional(),
27771
- description: z116.string().max(500).optional(),
28652
+ title: z119.string().max(120).optional(),
28653
+ description: z119.string().max(500).optional(),
27772
28654
  cta: MetaCtaType.optional(),
27773
- creativeId: z116.string().min(1).optional()
28655
+ creativeId: z119.string().min(1).optional()
27774
28656
  });
27775
- var GroupedCampaignConfigBase = z116.object({
28657
+ var GroupedCampaignConfigBase = z119.object({
27776
28658
  campaignType: MetaCampaignType.default("sales"),
27777
28659
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
27778
28660
  budgetMode: MetaBudgetMode.optional(),
27779
- campaignDailyBudget: z116.number().int().positive().optional(),
27780
- adSets: z116.array(z116.object({
27781
- tempId: z116.string().min(1).max(80),
27782
- name: z116.string().min(1).max(100),
27783
- dailyBudget: z116.number().int().positive().optional(),
27784
- creativeIds: z116.array(z116.string().min(1)).min(1)
28661
+ campaignDailyBudget: z119.number().int().positive().optional(),
28662
+ adSets: z119.array(z119.object({
28663
+ tempId: z119.string().min(1).max(80),
28664
+ name: z119.string().min(1).max(100),
28665
+ dailyBudget: z119.number().int().positive().optional(),
28666
+ creativeIds: z119.array(z119.string().min(1)).min(1)
27785
28667
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
27786
28668
  /**
27787
28669
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -27789,17 +28671,17 @@ var GroupedCampaignConfigBase = z116.object({
27789
28671
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
27790
28672
  * the API since #849 — unreachable from this tool.
27791
28673
  */
27792
- destination: z116.discriminatedUnion("type", [
27793
- z116.object({
27794
- type: z116.literal("landing"),
27795
- landingUrl: z116.string().url()
28674
+ destination: z119.discriminatedUnion("type", [
28675
+ z119.object({
28676
+ type: z119.literal("landing"),
28677
+ landingUrl: z119.string().url()
27796
28678
  }),
27797
- z116.object({
27798
- type: z116.literal("whatsapp"),
27799
- phoneNumber: z116.string().min(8).max(32)
28679
+ z119.object({
28680
+ type: z119.literal("whatsapp"),
28681
+ phoneNumber: z119.string().min(8).max(32)
27800
28682
  })
27801
28683
  ]),
27802
- saveAsDefault: z116.boolean().default(false),
28684
+ saveAsDefault: z119.boolean().default(false),
27803
28685
  budgetPolicy: MetaBudgetPolicy.optional()
27804
28686
  });
27805
28687
  function resolveBudgetMode(value) {
@@ -27813,7 +28695,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
27813
28695
  const budgetMode = resolveBudgetMode(value);
27814
28696
  if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
27815
28697
  ctx.addIssue({
27816
- code: z116.ZodIssueCode.custom,
28698
+ code: z119.ZodIssueCode.custom,
27817
28699
  path: [
27818
28700
  "campaignDailyBudget"
27819
28701
  ],
@@ -27824,7 +28706,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
27824
28706
  value.adSets.forEach((adSet, index) => {
27825
28707
  if (adSet.dailyBudget === void 0) {
27826
28708
  ctx.addIssue({
27827
- code: z116.ZodIssueCode.custom,
28709
+ code: z119.ZodIssueCode.custom,
27828
28710
  path: [
27829
28711
  "adSets",
27830
28712
  index,
@@ -27839,28 +28721,28 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
27839
28721
  ...value,
27840
28722
  budgetMode: resolveBudgetMode(value)
27841
28723
  }));
27842
- var PublishCreativesToMetaInputBase = z116.object({
27843
- creatives: z116.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
28724
+ var PublishCreativesToMetaInputBase = z119.object({
28725
+ creatives: z119.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
27844
28726
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
27845
- portfolioId: z116.string().trim().min(1),
28727
+ portfolioId: z119.string().trim().min(1),
27846
28728
  /** Exact Facebook Page selected from `list_meta_destinations`. */
27847
- pageId: z116.string().trim().min(1),
28729
+ pageId: z119.string().trim().min(1),
27848
28730
  adAccountId: McpAdAccountId,
27849
- pixelId: z116.string().min(1).optional(),
28731
+ pixelId: z119.string().min(1).optional(),
27850
28732
  /** Required unless `campaignConfig.destination` is whatsapp — see the
27851
28733
  * superRefine below. Mirrors the API's `FacebookCreateCampaignFromTasksBodySchema`. */
27852
- landingUrl: z116.string().url().optional(),
28734
+ landingUrl: z119.string().url().optional(),
27853
28735
  /** ISO-2 code or resolvable country name — mirrors the API contract
27854
28736
  * (CountryIso2Schema), which normalizes names to alpha-2 at validation. */
27855
28737
  targetCountry: CountryIso2Schema,
27856
- campaignName: z116.string().trim().min(1).max(100),
28738
+ campaignName: z119.string().trim().min(1).max(100),
27857
28739
  campaignConfig: GroupedCampaignConfig.optional()
27858
28740
  });
27859
28741
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
27860
28742
  const destination = value.campaignConfig?.destination;
27861
28743
  if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
27862
28744
  ctx.addIssue({
27863
- code: z116.ZodIssueCode.custom,
28745
+ code: z119.ZodIssueCode.custom,
27864
28746
  path: [
27865
28747
  "landingUrl"
27866
28748
  ],
@@ -27869,7 +28751,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
27869
28751
  }
27870
28752
  if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
27871
28753
  ctx.addIssue({
27872
- code: z116.ZodIssueCode.custom,
28754
+ code: z119.ZodIssueCode.custom,
27873
28755
  path: [
27874
28756
  "campaignConfig",
27875
28757
  "destination",
@@ -27888,16 +28770,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
27888
28770
  }
27889
28771
  return value;
27890
28772
  });
27891
- var PresignUploadResponseSchema = z116.object({
27892
- uploadUrl: z116.string().url().refine((value) => {
28773
+ var PresignUploadResponseSchema = z119.object({
28774
+ uploadUrl: z119.string().url().refine((value) => {
27893
28775
  const url = new URL(value);
27894
28776
  return !url.username && !url.password && url.protocol === "https:";
27895
28777
  }, "presigned upload URL must be HTTPS without embedded credentials"),
27896
- publicUrl: z116.string().url().refine((value) => {
28778
+ publicUrl: z119.string().url().refine((value) => {
27897
28779
  const url = new URL(value);
27898
28780
  return !url.username && !url.password;
27899
28781
  }, "public media URL must not contain embedded credentials"),
27900
- stagingAssetId: z116.string().uuid()
28782
+ stagingAssetId: z119.string().uuid()
27901
28783
  });
27902
28784
  async function uploadResolvedMedia(api, media, assetRole) {
27903
28785
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
@@ -27922,16 +28804,16 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
27922
28804
  return uploadResolvedMedia(api, media, assetRole);
27923
28805
  }
27924
28806
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
27925
- var TikTokInput = z116.object({
27926
- taskId: z116.string().min(1),
27927
- advertiserId: z116.string().min(1),
27928
- pixelId: z116.string().min(1).optional(),
27929
- landingUrl: z116.string().url().optional(),
27930
- angleIndexes: z116.array(z116.number().int().nonnegative()).optional(),
28807
+ var TikTokInput = z119.object({
28808
+ taskId: z119.string().min(1),
28809
+ advertiserId: z119.string().min(1),
28810
+ pixelId: z119.string().min(1).optional(),
28811
+ landingUrl: z119.string().url().optional(),
28812
+ angleIndexes: z119.array(z119.number().int().nonnegative()).optional(),
27931
28813
  campaignConfig: McpCampaignConfig.optional(),
27932
28814
  /** Override the ad's description (TikTok's body copy). If omitted,
27933
28815
  * ProductMaker uses the auto-generated copy attached to the task. */
27934
- primaryText: z116.string().trim().min(1).max(500).optional()
28816
+ primaryText: z119.string().trim().min(1).max(500).optional()
27935
28817
  });
27936
28818
  function registerPublishTools(server, api, opts = {}) {
27937
28819
  const transport = opts.transport ?? "http";
@@ -28283,39 +29165,39 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
28283
29165
 
28284
29166
  // src/tools/meta-upload.ts
28285
29167
  import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
28286
- import { z as z117 } from "zod";
29168
+ import { z as z120 } from "zod";
28287
29169
  var requestMediaUploadGuidance = getProductMakerCapabilityModelGuidance("request_media_upload");
28288
- var UploadSessionInputBase = z117.object({
28289
- mediaKind: z117.enum([
29170
+ var UploadSessionInputBase = z120.object({
29171
+ mediaKind: z120.enum([
28290
29172
  "image",
28291
29173
  "video",
28292
29174
  "mixed"
28293
29175
  ]).default("mixed").describe("The kind of file the user will select. Use image for product references and landing pages, video for a single video, and mixed only when the consumer supports both."),
28294
- assetRole: z117.enum([
29176
+ assetRole: z120.enum([
28295
29177
  "media",
28296
29178
  "thumbnail"
28297
29179
  ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
28298
- uploadPurpose: z117.enum([
29180
+ uploadPurpose: z120.enum([
28299
29181
  "product_reference",
28300
29182
  "creative",
28301
29183
  "generic"
28302
29184
  ]).default("generic").describe("The user's purpose for these files. Use product_reference for a product photo used by tasks or landings, creative for media that will become an ad or creative, and generic only when neither applies."),
28303
- maxFiles: z117.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
29185
+ maxFiles: z120.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
28304
29186
  });
28305
- var OpenAiFileReference = z117.object({
28306
- download_url: z117.string().url().describe("Required by ChatGPT's openai/fileParams schema. The App prefers getFileDownloadUrl(file_id) for a fresh URL, and the server never fetches this value."),
28307
- file_id: z117.string().min(1),
28308
- mime_type: z117.string().min(1).optional(),
28309
- file_name: z117.string().min(1).optional()
29187
+ var OpenAiFileReference = z120.object({
29188
+ download_url: z120.string().url().describe("Required by ChatGPT's openai/fileParams schema. The App prefers getFileDownloadUrl(file_id) for a fresh URL, and the server never fetches this value."),
29189
+ file_id: z120.string().min(1),
29190
+ mime_type: z120.string().min(1).optional(),
29191
+ file_name: z120.string().min(1).optional()
28310
29192
  });
28311
29193
  var UploadSessionToolInputShape = {
28312
29194
  ...UploadSessionInputBase.shape,
28313
- files: z117.array(OpenAiFileReference).max(10).optional().describe("Optional OpenAI-hosted file references provided by ChatGPT. They are only metadata for the UI bridge and are never forwarded to ProductMaker.")
29195
+ files: z120.array(OpenAiFileReference).max(10).optional().describe("Optional OpenAI-hosted file references provided by ChatGPT. They are only metadata for the UI bridge and are never forwarded to ProductMaker.")
28314
29196
  };
28315
- var UploadSessionToolInput = z117.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
29197
+ var UploadSessionToolInput = z120.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
28316
29198
  if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
28317
29199
  ctx.addIssue({
28318
- code: z117.ZodIssueCode.custom,
29200
+ code: z120.ZodIssueCode.custom,
28319
29201
  path: [
28320
29202
  "mediaKind"
28321
29203
  ],
@@ -28324,7 +29206,7 @@ var UploadSessionToolInput = z117.object(UploadSessionToolInputShape).superRefin
28324
29206
  }
28325
29207
  if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
28326
29208
  ctx.addIssue({
28327
- code: z117.ZodIssueCode.custom,
29209
+ code: z120.ZodIssueCode.custom,
28328
29210
  path: [
28329
29211
  "maxFiles"
28330
29212
  ],
@@ -28333,7 +29215,7 @@ var UploadSessionToolInput = z117.object(UploadSessionToolInputShape).superRefin
28333
29215
  }
28334
29216
  if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
28335
29217
  ctx.addIssue({
28336
- code: z117.ZodIssueCode.custom,
29218
+ code: z120.ZodIssueCode.custom,
28337
29219
  path: [
28338
29220
  "maxFiles"
28339
29221
  ],
@@ -28341,38 +29223,38 @@ var UploadSessionToolInput = z117.object(UploadSessionToolInputShape).superRefin
28341
29223
  });
28342
29224
  }
28343
29225
  });
28344
- var UploadSessionResponse = z117.object({
28345
- sessionId: z117.string().uuid(),
28346
- uploadPageUrl: z117.string().url(),
28347
- mediaKind: z117.enum([
29226
+ var UploadSessionResponse = z120.object({
29227
+ sessionId: z120.string().uuid(),
29228
+ uploadPageUrl: z120.string().url(),
29229
+ mediaKind: z120.enum([
28348
29230
  "image",
28349
29231
  "video",
28350
29232
  "mixed"
28351
29233
  ]),
28352
- assetRole: z117.enum([
29234
+ assetRole: z120.enum([
28353
29235
  "media",
28354
29236
  "thumbnail"
28355
29237
  ]),
28356
- maxFiles: z117.number().int().positive(),
28357
- expiresAt: z117.string().datetime()
29238
+ maxFiles: z120.number().int().positive(),
29239
+ expiresAt: z120.string().datetime()
28358
29240
  });
28359
- var UploadStatusInput = z117.object({
28360
- sessionId: z117.string().uuid().describe("The sessionId returned by request_media_upload.")
29241
+ var UploadStatusInput = z120.object({
29242
+ sessionId: z120.string().uuid().describe("The sessionId returned by request_media_upload.")
28361
29243
  });
28362
- var UniversalUploadStatusResponse = z117.discriminatedUnion("status", [
28363
- z117.object({
28364
- status: z117.literal("pending"),
28365
- assets: z117.array(z117.never()).length(0)
29244
+ var UniversalUploadStatusResponse = z120.discriminatedUnion("status", [
29245
+ z120.object({
29246
+ status: z120.literal("pending"),
29247
+ assets: z120.array(z120.never()).length(0)
28366
29248
  }),
28367
- z117.object({
28368
- status: z117.literal("uploaded"),
28369
- assets: z117.array(z117.object({
28370
- assetId: z117.string().uuid(),
28371
- mediaKind: z117.enum([
29249
+ z120.object({
29250
+ status: z120.literal("uploaded"),
29251
+ assets: z120.array(z120.object({
29252
+ assetId: z120.string().uuid(),
29253
+ mediaKind: z120.enum([
28372
29254
  "image",
28373
29255
  "video"
28374
29256
  ]),
28375
- assetRole: z117.enum([
29257
+ assetRole: z120.enum([
28376
29258
  "media",
28377
29259
  "thumbnail"
28378
29260
  ])
@@ -28380,13 +29262,13 @@ var UniversalUploadStatusResponse = z117.discriminatedUnion("status", [
28380
29262
  })
28381
29263
  ]);
28382
29264
  var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
28383
- uploadPurpose: z117.enum([
29265
+ uploadPurpose: z120.enum([
28384
29266
  "product_reference",
28385
29267
  "creative",
28386
29268
  "generic"
28387
29269
  ]),
28388
- checkToolName: z117.literal("check_media_upload"),
28389
- resolveEndpoint: z117.string().url()
29270
+ checkToolName: z120.literal("check_media_upload"),
29271
+ resolveEndpoint: z120.string().url()
28390
29272
  });
28391
29273
  function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
28392
29274
  const url = new URL(uploadPageUrl);
@@ -28477,15 +29359,15 @@ function registerMediaUploadTools(server, api) {
28477
29359
  __name(registerMediaUploadTools, "registerMediaUploadTools");
28478
29360
 
28479
29361
  // src/tools/campaigns.ts
28480
- import { z as z118 } from "zod";
29362
+ import { z as z121 } from "zod";
28481
29363
  var getMetaCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_meta_campaign_status");
28482
29364
  var getTikTokCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_tiktok_campaign_status");
28483
- var MetaStatusInput = z118.object({
28484
- campaignId: z118.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
29365
+ var MetaStatusInput = z121.object({
29366
+ campaignId: z121.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
28485
29367
  });
28486
- var TikTokStatusInput = z118.object({
28487
- campaignId: z118.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
28488
- advertiserId: z118.string().min(1).describe("The same advertiserId used to publish the campaign.")
29368
+ var TikTokStatusInput = z121.object({
29369
+ campaignId: z121.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
29370
+ advertiserId: z121.string().min(1).describe("The same advertiserId used to publish the campaign.")
28489
29371
  });
28490
29372
  function registerCampaignTools(server, api) {
28491
29373
  server.registerTool("get_meta_campaign_status", {
@@ -28590,10 +29472,10 @@ function renderTikTokStatusMarkdown(r) {
28590
29472
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
28591
29473
 
28592
29474
  // ../../packages/product-economics/src/product-costing.ts
28593
- import { z as z120 } from "zod";
29475
+ import { z as z123 } from "zod";
28594
29476
 
28595
29477
  // ../../packages/product-economics/src/product-costing-countries.ts
28596
- import { z as z119 } from "zod";
29478
+ import { z as z122 } from "zod";
28597
29479
 
28598
29480
  // ../../packages/product-economics/src/product-costing-shipping-references.json
28599
29481
  var product_costing_shipping_references_default = {
@@ -28657,21 +29539,21 @@ var product_costing_shipping_references_default = {
28657
29539
  };
28658
29540
 
28659
29541
  // ../../packages/product-economics/src/product-costing-countries.ts
28660
- var ShippingReferenceSchema = z119.object({
28661
- reference: z119.number().positive(),
28662
- min: z119.number().nonnegative(),
28663
- max: z119.number().positive(),
28664
- updatedAt: z119.string().date(),
28665
- confidence: z119.enum([
29542
+ var ShippingReferenceSchema = z122.object({
29543
+ reference: z122.number().positive(),
29544
+ min: z122.number().nonnegative(),
29545
+ max: z122.number().positive(),
29546
+ updatedAt: z122.string().date(),
29547
+ confidence: z122.enum([
28666
29548
  "high",
28667
29549
  "medium",
28668
29550
  "medium-low"
28669
29551
  ]),
28670
- sources: z119.array(z119.string().url().startsWith("https://")).min(1).max(3)
29552
+ sources: z122.array(z122.string().url().startsWith("https://")).min(1).max(3)
28671
29553
  }).superRefine(({ reference, min, max }, context) => {
28672
29554
  if (min > reference || reference > max) {
28673
29555
  context.addIssue({
28674
- code: z119.ZodIssueCode.custom,
29556
+ code: z122.ZodIssueCode.custom,
28675
29557
  message: "shipping_reference_outside_range",
28676
29558
  path: [
28677
29559
  "reference"
@@ -28679,7 +29561,7 @@ var ShippingReferenceSchema = z119.object({
28679
29561
  });
28680
29562
  }
28681
29563
  });
28682
- var SHIPPING_REFERENCES = z119.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
29564
+ var SHIPPING_REFERENCES = z122.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
28683
29565
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
28684
29566
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
28685
29567
  "MX",
@@ -28861,18 +29743,18 @@ function getProductCostingCountryName(country) {
28861
29743
  __name(getProductCostingCountryName, "getProductCostingCountryName");
28862
29744
 
28863
29745
  // ../../packages/product-economics/src/product-costing.ts
28864
- var ProductCostingInputSchema = z120.object({
28865
- country: z120.string().refine(isProductCostingCountryCode, "unsupported_country"),
28866
- supplierCost: z120.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
28867
- shippingCost: z120.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
28868
- returnRate: z120.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
28869
- cancellationRate: z120.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
28870
- adsManagerCpa: z120.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
28871
- desiredMargin: z120.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
29746
+ var ProductCostingInputSchema = z123.object({
29747
+ country: z123.string().refine(isProductCostingCountryCode, "unsupported_country"),
29748
+ supplierCost: z123.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
29749
+ shippingCost: z123.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
29750
+ returnRate: z123.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
29751
+ cancellationRate: z123.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
29752
+ adsManagerCpa: z123.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
29753
+ desiredMargin: z123.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
28872
29754
  }).superRefine(({ returnRate, cancellationRate }, context) => {
28873
29755
  if (returnRate + cancellationRate >= 1) {
28874
29756
  context.addIssue({
28875
- code: z120.ZodIssueCode.custom,
29757
+ code: z123.ZodIssueCode.custom,
28876
29758
  message: "delivery_rate_positive",
28877
29759
  path: [
28878
29760
  "cancellationRate"
@@ -28969,15 +29851,15 @@ function calculateProductCosting(rawInput) {
28969
29851
  __name(calculateProductCosting, "calculateProductCosting");
28970
29852
 
28971
29853
  // src/tools/product-costing.ts
28972
- import { z as z121 } from "zod";
29854
+ import { z as z124 } from "zod";
28973
29855
  var costProductGuidance = getProductMakerCapabilityModelGuidance("cost_product");
28974
- var optionalAmount = /* @__PURE__ */ __name((description) => z121.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
28975
- var optionalPercent = /* @__PURE__ */ __name((description) => z121.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
28976
- var ProductCostingToolInput = z121.object({
28977
- country: z121.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
29856
+ var optionalAmount = /* @__PURE__ */ __name((description) => z124.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
29857
+ var optionalPercent = /* @__PURE__ */ __name((description) => z124.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
29858
+ var ProductCostingToolInput = z124.object({
29859
+ country: z124.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
28978
29860
  message: "Pa\xEDs no soportado por ProductMaker."
28979
29861
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
28980
- supplierCost: z121.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
29862
+ supplierCost: z124.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
28981
29863
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
28982
29864
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
28983
29865
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -29140,7 +30022,7 @@ function formatPercent(value) {
29140
30022
  __name(formatPercent, "formatPercent");
29141
30023
 
29142
30024
  // src/tools/product-research.ts
29143
- import { z as z122 } from "zod";
30025
+ import { z as z125 } from "zod";
29144
30026
 
29145
30027
  // src/tools/product-research-idempotency.ts
29146
30028
  var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
@@ -29511,17 +30393,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
29511
30393
  ]);
29512
30394
  var POLL_INTERVAL_MS = 5e3;
29513
30395
  var MAX_WAIT_SECONDS = 120;
29514
- var FindInput = z122.object({
30396
+ var FindInput = z125.object({
29515
30397
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
29516
30398
  categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
29517
- confirmed: z122.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
29518
- waitSeconds: z122.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
30399
+ confirmed: z125.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
30400
+ waitSeconds: z125.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
29519
30401
  });
29520
- var GetInput = z122.object({
29521
- runId: z122.string().uuid().describe("ID del run devuelto por find_winning_products.")
30402
+ var GetInput = z125.object({
30403
+ runId: z125.string().uuid().describe("ID del run devuelto por find_winning_products.")
29522
30404
  });
29523
- var ListInput = z122.object({
29524
- limit: z122.number().int().min(1).max(20).default(10)
30405
+ var ListInput = z125.object({
30406
+ limit: z125.number().int().min(1).max(20).default(10)
29525
30407
  });
29526
30408
  function textResult(text2) {
29527
30409
  return {
@@ -29660,17 +30542,17 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
29660
30542
  __name(registerProductResearchTools, "registerProductResearchTools");
29661
30543
 
29662
30544
  // src/tools/dropshipping-projects.ts
29663
- import { z as z123 } from "zod";
30545
+ import { z as z126 } from "zod";
29664
30546
  var startDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("start_dropshipping_project");
29665
30547
  var costDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("cost_dropshipping_project");
29666
30548
  var guideDropshippingLaunchGuidance = getProductMakerCapabilityModelGuidance("guide_dropshipping_launch");
29667
- var ProjectId = z123.string().uuid();
29668
- var DropshippingProjectStatus = z123.enum([
30549
+ var ProjectId = z126.string().uuid();
30550
+ var DropshippingProjectStatus = z126.enum([
29669
30551
  "active",
29670
30552
  "completed",
29671
30553
  "archived"
29672
30554
  ]);
29673
- var DropshippingProjectPhase = z123.enum([
30555
+ var DropshippingProjectPhase = z126.enum([
29674
30556
  "research",
29675
30557
  "costing",
29676
30558
  "media",
@@ -29679,21 +30561,21 @@ var DropshippingProjectPhase = z123.enum([
29679
30561
  "advertising",
29680
30562
  "complete"
29681
30563
  ]);
29682
- var DropshippingRewardMilestone = z123.enum([
30564
+ var DropshippingRewardMilestone = z126.enum([
29683
30565
  "product_selected",
29684
30566
  "costing_saved",
29685
30567
  "product_media_ready",
29686
30568
  "creatives_completed",
29687
30569
  "campaign_created"
29688
30570
  ]);
29689
- var TargetCountry = z123.enum([
30571
+ var TargetCountry = z126.enum([
29690
30572
  "CO",
29691
30573
  "MX",
29692
30574
  "PE",
29693
30575
  "EC",
29694
30576
  "CL"
29695
30577
  ]);
29696
- var ResearchCategory = z123.enum([
30578
+ var ResearchCategory = z126.enum([
29697
30579
  "auto",
29698
30580
  "automotive",
29699
30581
  "kitchen",
@@ -29708,7 +30590,7 @@ var ResearchCategory = z123.enum([
29708
30590
  "garden",
29709
30591
  "cleaning"
29710
30592
  ]);
29711
- var HttpUrl3 = z123.string().url().max(2e3).refine((value) => {
30593
+ var HttpUrl3 = z126.string().url().max(2e3).refine((value) => {
29712
30594
  try {
29713
30595
  return [
29714
30596
  "http:",
@@ -29718,45 +30600,45 @@ var HttpUrl3 = z123.string().url().max(2e3).refine((value) => {
29718
30600
  return false;
29719
30601
  }
29720
30602
  }, "URL must use HTTP(S)");
29721
- var ResearchCandidateOrigin = z123.object({
29722
- type: z123.literal("research_candidate"),
30603
+ var ResearchCandidateOrigin = z126.object({
30604
+ type: z126.literal("research_candidate"),
29723
30605
  runId: ProjectId,
29724
- candidateSourceId: z123.string().min(1).max(160)
30606
+ candidateSourceId: z126.string().min(1).max(160)
29725
30607
  });
29726
- var TaskOrigin = z123.object({
29727
- type: z123.literal("task"),
30608
+ var TaskOrigin = z126.object({
30609
+ type: z126.literal("task"),
29728
30610
  taskId: ProjectId
29729
30611
  });
29730
- var ManualOrigin = z123.object({
29731
- type: z123.literal("manual"),
30612
+ var ManualOrigin = z126.object({
30613
+ type: z126.literal("manual"),
29732
30614
  clientRequestId: ProjectId,
29733
- country: z123.string().min(2).max(3).optional()
30615
+ country: z126.string().min(2).max(3).optional()
29734
30616
  });
29735
- var StartProjectInput = z123.object({
29736
- origin: z123.discriminatedUnion("type", [
30617
+ var StartProjectInput = z126.object({
30618
+ origin: z126.discriminatedUnion("type", [
29737
30619
  ResearchCandidateOrigin,
29738
30620
  TaskOrigin,
29739
30621
  ManualOrigin
29740
30622
  ]),
29741
- productName: z123.string().trim().min(1).max(240).optional(),
30623
+ productName: z126.string().trim().min(1).max(240).optional(),
29742
30624
  productUrl: HttpUrl3.optional(),
29743
- country: z123.string().min(2).max(3).optional(),
29744
- languageCode: z123.string().min(2).max(16).optional()
30625
+ country: z126.string().min(2).max(3).optional(),
30626
+ languageCode: z126.string().min(2).max(16).optional()
29745
30627
  }).strict();
29746
- var CostProjectInput = z123.object({
30628
+ var CostProjectInput = z126.object({
29747
30629
  projectId: ProjectId,
29748
- version: z123.number().int().positive().describe("Version le\xEDda del proyecto. Copia la \xFAltima que viste; no se la pidas al usuario."),
29749
- country: z123.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
30630
+ version: z126.number().int().positive().describe("Version le\xEDda del proyecto. Copia la \xFAltima que viste; no se la pidas al usuario."),
30631
+ country: z126.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
29750
30632
  message: "Pa\xEDs no soportado por ProductMaker."
29751
30633
  }).describe("C\xF3digo ISO-2 del pa\xEDs del proyecto. Usa el `targetCountry` que devuelve el proyecto."),
29752
- supplierCost: z123.number().finite().positive().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. \xDANICO dato que debes pedirle al usuario: nunca lo inventes."),
29753
- shippingCost: z123.number().finite().nonnegative().optional().describe("Costo de un env\xEDo. Omite para usar el valor gu\xEDa del pa\xEDs en vez de inventarlo."),
29754
- returnRate: z123.number().finite().min(0).lt(1).optional().describe("Fracci\xF3n de pedidos devueltos (0.25 = 25%). Omite para usar el valor gu\xEDa del pa\xEDs."),
29755
- cancellationRate: z123.number().finite().min(0).lt(1).optional().describe("Fracci\xF3n de pedidos cancelados (0.15 = 15%). Omite para usar el valor gu\xEDa del pa\xEDs."),
29756
- adsManagerCpa: z123.number().finite().nonnegative().optional().describe("Costo por compra de Ads Manager. Omite para usar el valor gu\xEDa del pa\xEDs."),
29757
- desiredMargin: z123.number().finite().min(0).lt(1).optional().describe("Margen deseado (0.1 = 10%). Omite para usar el valor gu\xEDa del pa\xEDs.")
29758
- });
29759
- var GuideInput = z123.object({
30634
+ supplierCost: z126.number().finite().positive().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. \xDANICO dato que debes pedirle al usuario: nunca lo inventes."),
30635
+ shippingCost: z126.number().finite().nonnegative().optional().describe("Costo de un env\xEDo. Omite para usar el valor gu\xEDa del pa\xEDs en vez de inventarlo."),
30636
+ returnRate: z126.number().finite().min(0).lt(1).optional().describe("Fracci\xF3n de pedidos devueltos (0.25 = 25%). Omite para usar el valor gu\xEDa del pa\xEDs."),
30637
+ cancellationRate: z126.number().finite().min(0).lt(1).optional().describe("Fracci\xF3n de pedidos cancelados (0.15 = 15%). Omite para usar el valor gu\xEDa del pa\xEDs."),
30638
+ adsManagerCpa: z126.number().finite().nonnegative().optional().describe("Costo por compra de Ads Manager. Omite para usar el valor gu\xEDa del pa\xEDs."),
30639
+ desiredMargin: z126.number().finite().min(0).lt(1).optional().describe("Margen deseado (0.1 = 10%). Omite para usar el valor gu\xEDa del pa\xEDs.")
30640
+ });
30641
+ var GuideInput = z126.object({
29760
30642
  projectId: ProjectId.optional()
29761
30643
  }).strict();
29762
30644
  var NEXT_ACTION_KINDS = [
@@ -29774,29 +30656,29 @@ var NEXT_ACTION_KINDS = [
29774
30656
  "repair_meta_campaign",
29775
30657
  "review_paused_campaign"
29776
30658
  ];
29777
- var DropshippingProjectNextAction = z123.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind) => z123.object({
29778
- kind: z123.literal(kind),
29779
- href: z123.string().min(1)
30659
+ var DropshippingProjectNextAction = z126.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind) => z126.object({
30660
+ kind: z126.literal(kind),
30661
+ href: z126.string().min(1)
29780
30662
  })));
29781
- var CostingInputSchema = z123.object({
29782
- country: z123.string().min(2).max(3),
29783
- supplierCost: z123.number().finite().positive(),
29784
- shippingCost: z123.number().finite().nonnegative(),
29785
- returnRate: z123.number().finite().min(0).lt(1),
29786
- cancellationRate: z123.number().finite().min(0).lt(1),
29787
- adsManagerCpa: z123.number().finite().nonnegative(),
29788
- desiredMargin: z123.number().finite().min(0).lt(1)
30663
+ var CostingInputSchema = z126.object({
30664
+ country: z126.string().min(2).max(3),
30665
+ supplierCost: z126.number().finite().positive(),
30666
+ shippingCost: z126.number().finite().nonnegative(),
30667
+ returnRate: z126.number().finite().min(0).lt(1),
30668
+ cancellationRate: z126.number().finite().min(0).lt(1),
30669
+ adsManagerCpa: z126.number().finite().nonnegative(),
30670
+ desiredMargin: z126.number().finite().min(0).lt(1)
29789
30671
  }).superRefine(({ returnRate, cancellationRate }, context) => {
29790
30672
  if (returnRate + cancellationRate >= 1) context.addIssue({
29791
- code: z123.ZodIssueCode.custom,
30673
+ code: z126.ZodIssueCode.custom,
29792
30674
  path: [
29793
30675
  "cancellationRate"
29794
30676
  ]
29795
30677
  });
29796
30678
  });
29797
- var FiniteCostingNumber = z123.number().finite();
29798
- var CostingResultSchema = z123.object({
29799
- country: z123.string().min(2).max(3),
30679
+ var FiniteCostingNumber = z126.number().finite();
30680
+ var CostingResultSchema = z126.object({
30681
+ country: z126.string().min(2).max(3),
29800
30682
  deliveryRate: FiniteCostingNumber,
29801
30683
  adjustedShippingCost: FiniteCostingNumber,
29802
30684
  adjustedCpa: FiniteCostingNumber,
@@ -29809,45 +30691,45 @@ var CostingResultSchema = z123.object({
29809
30691
  estimatedProfit: FiniteCostingNumber,
29810
30692
  actualMargin: FiniteCostingNumber,
29811
30693
  breakEvenAdsManagerCpa: FiniteCostingNumber,
29812
- wasPriceAutoAdjusted: z123.boolean(),
29813
- profitabilityStatus: z123.literal("healthy")
29814
- });
29815
- var RewardProgressSchema = z123.object({
29816
- campaignKey: z123.literal("dropshipping-launch-v1"),
29817
- totalCredits: z123.number().int().positive(),
29818
- earnedCredits: z123.number().int().nonnegative(),
29819
- milestones: z123.array(z123.object({
30694
+ wasPriceAutoAdjusted: z126.boolean(),
30695
+ profitabilityStatus: z126.literal("healthy")
30696
+ });
30697
+ var RewardProgressSchema = z126.object({
30698
+ campaignKey: z126.literal("dropshipping-launch-v1"),
30699
+ totalCredits: z126.number().int().positive(),
30700
+ earnedCredits: z126.number().int().nonnegative(),
30701
+ milestones: z126.array(z126.object({
29820
30702
  milestone: DropshippingRewardMilestone,
29821
- credits: z123.number().int().positive(),
29822
- available: z123.boolean().optional(),
29823
- earned: z123.boolean(),
30703
+ credits: z126.number().int().positive(),
30704
+ available: z126.boolean().optional(),
30705
+ earned: z126.boolean(),
29824
30706
  grantId: ProjectId.nullable(),
29825
- grantedAt: z123.string().datetime().nullable()
30707
+ grantedAt: z126.string().datetime().nullable()
29826
30708
  })).length(DropshippingRewardMilestone.options.length)
29827
30709
  });
29828
- var ProjectResponseSchema = z123.object({
30710
+ var ProjectResponseSchema = z126.object({
29829
30711
  id: ProjectId,
29830
30712
  taskId: ProjectId.nullable(),
29831
30713
  status: DropshippingProjectStatus,
29832
- originKey: z123.string().min(1),
30714
+ originKey: z126.string().min(1),
29833
30715
  researchRunId: ProjectId.nullable(),
29834
- candidateSourceId: z123.string().nullable(),
29835
- productName: z123.string().nullable(),
30716
+ candidateSourceId: z126.string().nullable(),
30717
+ productName: z126.string().nullable(),
29836
30718
  productUrl: HttpUrl3.nullable(),
29837
30719
  referenceImageUrl: HttpUrl3.nullable(),
29838
- targetCountry: z123.string().nullable(),
29839
- languageCode: z123.string().nullable(),
29840
- hasReferenceImage: z123.boolean(),
29841
- hasWinnerReferenceImage: z123.boolean(),
30720
+ targetCountry: z126.string().nullable(),
30721
+ languageCode: z126.string().nullable(),
30722
+ hasReferenceImage: z126.boolean(),
30723
+ hasWinnerReferenceImage: z126.boolean(),
29842
30724
  costingInput: CostingInputSchema.nullable(),
29843
30725
  costingResult: CostingResultSchema.nullable(),
29844
- costingVersion: z123.number().int().positive().nullable(),
29845
- version: z123.number().int().positive(),
30726
+ costingVersion: z126.number().int().positive().nullable(),
30727
+ version: z126.number().int().positive(),
29846
30728
  phase: DropshippingProjectPhase,
29847
30729
  nextAction: DropshippingProjectNextAction.nullable(),
29848
30730
  rewards: RewardProgressSchema,
29849
- createdAt: z123.string().datetime(),
29850
- updatedAt: z123.string().datetime()
30731
+ createdAt: z126.string().datetime(),
30732
+ updatedAt: z126.string().datetime()
29851
30733
  }).passthrough();
29852
30734
  var NEXT_ACTION_GUIDANCE = {
29853
30735
  cost_product: "Llama `cost_dropshipping_project` con el costo real del proveedor. Nunca lo inventes: p\xEDdeselo al usuario. Es el \xDANICO dato que debes pedirle: omite env\xEDo, devoluciones, cancelaciones, CPA y margen para que ProductMaker use los valores gu\xEDa del pa\xEDs, y luego dile al usuario cu\xE1les us\xF3 por si quiere corregirlos.",
@@ -29867,14 +30749,14 @@ var NEXT_ACTION_GUIDANCE = {
29867
30749
  function projectText(raw, referenceValues = []) {
29868
30750
  const project = ProjectResponseSchema.parse(raw);
29869
30751
  const kind = project.nextAction?.kind;
29870
- const guidance2 = kind ? NEXT_ACTION_GUIDANCE[kind] : void 0;
30752
+ const guidance5 = kind ? NEXT_ACTION_GUIDANCE[kind] : void 0;
29871
30753
  const winnerReferenceResult = project.hasWinnerReferenceImage ? WinnerReferenceInput.safeParse({
29872
30754
  runId: project.researchRunId,
29873
30755
  candidateSourceId: project.candidateSourceId
29874
30756
  }) : void 0;
29875
30757
  const winnerReference = winnerReferenceResult?.success ? winnerReferenceResult.data : void 0;
29876
30758
  const standaloneCreativeGuidance = winnerReference ? "Si el usuario pide s\xF3lo crear contenido con este producto, no contin\xFAes el costeo y no pidas el costo del proveedor. Llama `prepare_winning_product_image` con `winnerReference`, muestra la foto y, despu\xE9s de su elecci\xF3n, pasa el mismo `winnerReference` a la herramienta creativa independiente solicitada." : void 0;
29877
- const guidedNextActionGuidance = guidance2 ? `S\xF3lo si el usuario quiere continuar el lanzamiento guiado completo: ${guidance2}` : void 0;
30759
+ const guidedNextActionGuidance = guidance5 ? `S\xF3lo si el usuario quiere continuar el lanzamiento guiado completo: ${guidance5}` : void 0;
29878
30760
  const referenceNotice = referenceValues.length > 0 ? `Valores gu\xEDa de ${project.targetCountry ?? "el pa\xEDs"} que ProductMaker us\xF3 porque no los diste: ${referenceValues.join(", ")}. Son un punto de partida editable, no datos del usuario: dile cu\xE1les fueron y ofr\xE9cele corregirlos. Si te da los suyos, vuelve a llamar cost_dropshipping_project con la version nueva.` : void 0;
29879
30761
  return {
29880
30762
  content: [
@@ -29967,58 +30849,58 @@ function registerDropshippingProjectTools(server, api) {
29967
30849
  __name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
29968
30850
 
29969
30851
  // src/tools/daily-control.ts
29970
- import { z as z124 } from "zod";
30852
+ import { z as z127 } from "zod";
29971
30853
  var getDailyControlGuidance = getProductMakerCapabilityModelGuidance("get_daily_control");
29972
30854
  var linkCampaignToProjectGuidance = getProductMakerCapabilityModelGuidance("link_campaign_to_project");
29973
30855
  var reportDailyReturnsGuidance = getProductMakerCapabilityModelGuidance("report_daily_returns");
29974
- var ProviderCampaignIdentifier = z124.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
29975
- var DateOnly = z124.string().regex(/^\d{4}-\d{2}-\d{2}$/);
29976
- var DailyControlInput = z124.object({
30856
+ var ProviderCampaignIdentifier = z127.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
30857
+ var DateOnly = z127.string().regex(/^\d{4}-\d{2}-\d{2}$/);
30858
+ var DailyControlInput = z127.object({
29977
30859
  date: DateOnly.optional().describe("D\xEDa final de la ventana (YYYY-MM-DD). Si se omite, la ventana termina AYER en UTC \u2014 nunca hoy, porque el d\xEDa en curso est\xE1 incompleto \u2014 y el servidor puede correrla un d\xEDa m\xE1s atr\xE1s si en la zona horaria de la tienda ese d\xEDa todav\xEDa no ha cerrado."),
29978
- days: z124.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
29979
- projectId: z124.string().uuid().optional().describe("Opcional: limita el reporte a un solo proyecto en vez de todos los productos activos.")
30860
+ days: z127.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
30861
+ projectId: z127.string().uuid().optional().describe("Opcional: limita el reporte a un solo proyecto en vez de todos los productos activos.")
29980
30862
  }).strict();
29981
- var LinkCampaignInput = z124.object({
29982
- projectId: z124.string().uuid(),
29983
- provider: z124.enum([
30863
+ var LinkCampaignInput = z127.object({
30864
+ projectId: z127.string().uuid(),
30865
+ provider: z127.enum([
29984
30866
  "meta",
29985
30867
  "tiktok"
29986
30868
  ]),
29987
30869
  accountId: ProviderCampaignIdentifier.describe("Cuenta publicitaria del proveedor. Para Meta el servidor la IGNORA y deriva la cuenta real de la campa\xF1a \u2014 se sigue pidiendo aqu\xED solo porque TikTok s\xED la necesita para resolver la campa\xF1a."),
29988
30870
  campaignId: ProviderCampaignIdentifier
29989
30871
  }).strict();
29990
- var ReportReturnsInput = z124.object({
29991
- projectId: z124.string().uuid(),
30872
+ var ReportReturnsInput = z127.object({
30873
+ projectId: z127.string().uuid(),
29992
30874
  date: DateOnly,
29993
- returnedOrders: z124.number().int().min(0).max(1e5),
29994
- note: z124.string().trim().max(280).optional()
30875
+ returnedOrders: z127.number().int().min(0).max(1e5),
30876
+ note: z127.string().trim().max(280).optional()
29995
30877
  }).strict();
29996
- var DailyControlStatusMirror = DailyControlStatusEnum.or(z124.string());
29997
- var DailyControlLightMirror = DailyControlLightEnum.or(z124.string());
29998
- var DailyControlSourceMirror = DailyControlSourceEnum.or(z124.string());
29999
- var DailyControlVerdictMirror = DailyControlVerdictEnum.or(z124.string());
30000
- var DailyControlDayMirror = z124.object({
30001
- date: z124.string(),
30002
- returnedOrders: z124.number(),
30878
+ var DailyControlStatusMirror = DailyControlStatusEnum.or(z127.string());
30879
+ var DailyControlLightMirror = DailyControlLightEnum.or(z127.string());
30880
+ var DailyControlSourceMirror = DailyControlSourceEnum.or(z127.string());
30881
+ var DailyControlVerdictMirror = DailyControlVerdictEnum.or(z127.string());
30882
+ var DailyControlDayMirror = z127.object({
30883
+ date: z127.string(),
30884
+ returnedOrders: z127.number(),
30003
30885
  returnedSource: DailyControlSourceMirror,
30004
30886
  cancelledSource: DailyControlSourceMirror,
30005
- hasEstimates: z124.boolean(),
30887
+ hasEstimates: z127.boolean(),
30006
30888
  light: DailyControlLightMirror
30007
30889
  }).passthrough();
30008
- var DailyControlProductMirror = z124.object({
30009
- projectId: z124.string().uuid(),
30010
- productName: z124.string().nullable(),
30890
+ var DailyControlProductMirror = z127.object({
30891
+ projectId: z127.string().uuid(),
30892
+ productName: z127.string().nullable(),
30011
30893
  status: DailyControlStatusMirror,
30012
- statusMessage: z124.string().nullable(),
30013
- days: z124.array(DailyControlDayMirror),
30894
+ statusMessage: z127.string().nullable(),
30895
+ days: z127.array(DailyControlDayMirror),
30014
30896
  verdict: DailyControlVerdictMirror.nullable(),
30015
- rationale: z124.string().nullable()
30897
+ rationale: z127.string().nullable()
30016
30898
  }).passthrough();
30017
- var DailyControlResponseMirror = z124.object({
30018
- since: z124.string(),
30019
- until: z124.string(),
30020
- products: z124.array(DailyControlProductMirror),
30021
- archivedProjectSpend: z124.array(z124.unknown())
30899
+ var DailyControlResponseMirror = z127.object({
30900
+ since: z127.string(),
30901
+ until: z127.string(),
30902
+ products: z127.array(DailyControlProductMirror),
30903
+ archivedProjectSpend: z127.array(z127.unknown())
30022
30904
  }).passthrough();
30023
30905
  var MCP_WARNINGS_KEY = "_mcpDailyControlWarnings";
30024
30906
  function withWarnings(response) {
@@ -30093,7 +30975,7 @@ __name(registerDailyControlTools, "registerDailyControlTools");
30093
30975
  // src/tools/product-control.ts
30094
30976
  import { readFile, stat } from "fs/promises";
30095
30977
  import { basename as basename2, isAbsolute, resolve as resolve3 } from "path";
30096
- import { z as z125 } from "zod";
30978
+ import { z as z128 } from "zod";
30097
30979
  var getProductControlGuidance = getProductMakerCapabilityModelGuidance("get_product_control");
30098
30980
  var listImportableProductsGuidance = getProductMakerCapabilityModelGuidance("list_importable_products");
30099
30981
  var importProductControlsGuidance = getProductMakerCapabilityModelGuidance("import_product_controls");
@@ -30106,36 +30988,36 @@ var createOperationGuidance = getProductMakerCapabilityModelGuidance("create_ope
30106
30988
  var mergeProductsGuidance = getProductMakerCapabilityModelGuidance("merge_products");
30107
30989
  var syncShopifyOrdersGuidance = getProductMakerCapabilityModelGuidance("sync_shopify_orders");
30108
30990
  var importOrdersFileGuidance = getProductMakerCapabilityModelGuidance("import_orders_file");
30109
- var DateOnly2 = z125.string().regex(/^\d{4}-\d{2}-\d{2}$/);
30991
+ var DateOnly2 = z128.string().regex(/^\d{4}-\d{2}-\d{2}$/);
30110
30992
  var IMPORT_BATCH_MAX2 = 50;
30111
- var ProductControlListInput = z125.object({
30112
- days: z125.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
30993
+ var ProductControlListInput = z128.object({
30994
+ days: z128.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
30113
30995
  date: DateOnly2.optional().describe("D\xEDa final de la ventana (YYYY-MM-DD). Si se omite, termina en el \xFAltimo d\xEDa cerrado \u2014 nunca hoy, que est\xE1 incompleto."),
30114
- shopDomain: z125.string().min(1).optional().describe("Tienda, si tiene m\xE1s de una conectada.")
30996
+ shopDomain: z128.string().min(1).optional().describe("Tienda, si tiene m\xE1s de una conectada.")
30115
30997
  }).strict();
30116
- var ImportableProductsInput = z125.object({
30117
- q: z125.string().trim().max(120).optional().describe("B\xFAsqueda por t\xEDtulo. Sin esto un cat\xE1logo grande es inrecorrible."),
30118
- after: z125.string().max(512).optional().describe("Cursor de la p\xE1gina anterior, tal como vino en la respuesta."),
30119
- shopDomain: z125.string().min(1).optional()
30998
+ var ImportableProductsInput = z128.object({
30999
+ q: z128.string().trim().max(120).optional().describe("B\xFAsqueda por t\xEDtulo. Sin esto un cat\xE1logo grande es inrecorrible."),
31000
+ after: z128.string().max(512).optional().describe("Cursor de la p\xE1gina anterior, tal como vino en la respuesta."),
31001
+ shopDomain: z128.string().min(1).optional()
30120
31002
  }).strict();
30121
- var ImportProductControlsInput = z125.object({
30122
- shopifyProductIds: z125.array(z125.string().min(1).max(64)).min(1).max(IMPORT_BATCH_MAX2).describe("Identificadores tal como vinieron de list_importable_products. NO los inventes ni los deduzcas del t\xEDtulo."),
31003
+ var ImportProductControlsInput = z128.object({
31004
+ shopifyProductIds: z128.array(z128.string().min(1).max(64)).min(1).max(IMPORT_BATCH_MAX2).describe("Identificadores tal como vinieron de list_importable_products. NO los inventes ni los deduzcas del t\xEDtulo."),
30123
31005
  // Obligatorio, como en el endpoint: con dos tiendas conectadas, importar
30124
31006
  // sin decir a cuál archivaría el producto bajo la tienda equivocada.
30125
- shopDomain: z125.string().min(1)
31007
+ shopDomain: z128.string().min(1)
30126
31008
  }).strict();
30127
- var SetProductCostingInput = z125.object({
30128
- shopDomain: z125.string().min(1),
30129
- shopifyProductId: z125.string().min(1).max(64),
30130
- targetCountry: z125.string().length(2).describe("Pa\xEDs de venta en ISO-2, que fija moneda y referencia de flete."),
30131
- supplierCost: z125.number().finite().positive().describe("Lo que le cuesta el producto al proveedor. Nunca cero."),
30132
- shippingCost: z125.number().finite().nonnegative(),
30133
- returnRate: z125.number().finite().min(0).lt(1).describe("Proporci\xF3n 0\u20131, no porcentaje: 0.15 es quince por ciento."),
30134
- cancellationRate: z125.number().finite().min(0).lt(1)
31009
+ var SetProductCostingInput = z128.object({
31010
+ shopDomain: z128.string().min(1),
31011
+ shopifyProductId: z128.string().min(1).max(64),
31012
+ targetCountry: z128.string().length(2).describe("Pa\xEDs de venta en ISO-2, que fija moneda y referencia de flete."),
31013
+ supplierCost: z128.number().finite().positive().describe("Lo que le cuesta el producto al proveedor. Nunca cero."),
31014
+ shippingCost: z128.number().finite().nonnegative(),
31015
+ returnRate: z128.number().finite().min(0).lt(1).describe("Proporci\xF3n 0\u20131, no porcentaje: 0.15 es quince por ciento."),
31016
+ cancellationRate: z128.number().finite().min(0).lt(1)
30135
31017
  }).strict();
30136
- var Count = z125.number().int().min(0).max(1e5).nullable().optional();
30137
- var ReportProductCountsInput = z125.object({
30138
- productControlId: z125.string().uuid().describe("El identificador que devuelve get_product_control."),
31018
+ var Count = z128.number().int().min(0).max(1e5).nullable().optional();
31019
+ var ReportProductCountsInput = z128.object({
31020
+ productControlId: z128.string().uuid().describe("El identificador que devuelve get_product_control."),
30139
31021
  date: DateOnly2,
30140
31022
  orders: Count.describe("Pedidos que entraron ese d\xEDa."),
30141
31023
  cancelledOrders: Count,
@@ -30145,36 +31027,36 @@ var ReportProductCountsInput = z125.object({
30145
31027
  }).strict();
30146
31028
  var MAX_ORDERS_FILE_BYTES = 8 * 1024 * 1024;
30147
31029
  var SyncShopifyOrdersInput = SyncShopifyOrdersSchema;
30148
- var ImportOrdersFileInput = z125.object({
31030
+ var ImportOrdersFileInput = z128.object({
30149
31031
  /** Ruta ABSOLUTA en la máquina de quien usa el MCP. */
30150
- path: z125.string().min(1),
30151
- operationId: z125.string().uuid().optional(),
31032
+ path: z128.string().min(1),
31033
+ operationId: z128.string().uuid().optional(),
30152
31034
  /** Solo mirar: devuelve el resumen y no escribe nada. */
30153
- dryRun: z125.boolean().optional()
31035
+ dryRun: z128.boolean().optional()
30154
31036
  }).strict();
30155
- var CreateOperationInput = z125.object({
30156
- countryCode: z125.string().length(2),
30157
- label: z125.string().trim().min(1).max(60).optional()
31037
+ var CreateOperationInput = z128.object({
31038
+ countryCode: z128.string().length(2),
31039
+ label: z128.string().trim().min(1).max(60).optional()
30158
31040
  }).strict();
30159
- var MergeProductsInput = z125.object({
30160
- sourceId: z125.string().uuid(),
30161
- targetId: z125.string().uuid()
31041
+ var MergeProductsInput = z128.object({
31042
+ sourceId: z128.string().uuid(),
31043
+ targetId: z128.string().uuid()
30162
31044
  }).strict();
30163
- var SetMonthlyExpenseInput = z125.object({
30164
- month: z125.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
31045
+ var SetMonthlyExpenseInput = z128.object({
31046
+ month: z128.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
30165
31047
  label: SaveMonthlyExpenseSchema.shape.label,
30166
- amount: z125.number().finite().min(0),
30167
- operationId: z125.string().uuid().optional()
31048
+ amount: z128.number().finite().min(0),
31049
+ operationId: z128.string().uuid().optional()
30168
31050
  }).strict();
30169
- var SetAdSpendInput = z125.object({
30170
- month: z125.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
30171
- operationId: z125.string().uuid().optional(),
31051
+ var SetAdSpendInput = z128.object({
31052
+ month: z128.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
31053
+ operationId: z128.string().uuid().optional(),
30172
31054
  /** null RETIRA la corrección; cero afirma que no pautó. */
30173
- amount: z125.number().finite().min(0).nullable()
31055
+ amount: z128.number().finite().min(0).nullable()
30174
31056
  }).strict();
30175
- var GetFinancesInput = z125.object({
30176
- month: z125.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional().describe("Mes en YYYY-MM. Si se omite, el mes en curso. Solo mes calendario."),
30177
- operationId: z125.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
31057
+ var GetFinancesInput = z128.object({
31058
+ month: z128.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional().describe("Mes en YYYY-MM. Si se omite, el mes en curso. Solo mes calendario."),
31059
+ operationId: z128.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
30178
31060
  }).strict();
30179
31061
  function registerProductControlTools(server, api, { transport = "http" } = {}) {
30180
31062
  server.registerTool("get_product_control", {
@@ -30364,28 +31246,28 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
30364
31246
  __name(registerProductControlTools, "registerProductControlTools");
30365
31247
 
30366
31248
  // src/tools/market-study-estimate.ts
30367
- import { z as z126 } from "zod";
31249
+ import { z as z129 } from "zod";
30368
31250
  var estimateMarketStudyGuidance = getProductMakerCapabilityModelGuidance("estimate_market_study_cost");
30369
- var ProductTaskOutputEnum = z126.enum([
31251
+ var ProductTaskOutputEnum = z129.enum([
30370
31252
  "landing",
30371
31253
  "video",
30372
31254
  "image"
30373
31255
  ]);
30374
- var EstimateMarketStudyInput = z126.object({
30375
- kind: z126.enum([
31256
+ var EstimateMarketStudyInput = z129.object({
31257
+ kind: z129.enum([
30376
31258
  "video",
30377
31259
  "product_task"
30378
31260
  ]).describe('"video" para generate_video_creative; "product_task" para create_product_task.'),
30379
- videoAngleCount: z126.number().int().min(1).max(3).describe("Los mismos 1, 2 o 3 videos que vas a pedir al crear."),
30380
- durationSeconds: z126.number().int().min(4).max(30).default(15).describe("La misma duraci\xF3n que enviar\xE1s al crear."),
31261
+ videoAngleCount: z129.number().int().min(1).max(3).describe("Los mismos 1, 2 o 3 videos que vas a pedir al crear."),
31262
+ durationSeconds: z129.number().int().min(4).max(30).default(15).describe("La misma duraci\xF3n que enviar\xE1s al crear."),
30381
31263
  videoStructure: VideoStructureEnum.default("single_take"),
30382
31264
  videoQuality: VideoQualityEnum.default("economy"),
30383
31265
  videoResolution: VideoResolutionEnum.default("480p"),
30384
- productImageCount: z126.number().int().min(1).max(5).optional().describe("Cantidad de fotos del producto que adjuntar\xE1s (1-5)."),
31266
+ productImageCount: z129.number().int().min(1).max(5).optional().describe("Cantidad de fotos del producto que adjuntar\xE1s (1-5)."),
30385
31267
  outputs: ProductTaskOutputEnum.array().min(1).max(3).optional().describe('Solo para kind="product_task": los mismos outputs de create_product_task. Default: todos.'),
30386
- generateAiMusic: z126.boolean().optional(),
31268
+ generateAiMusic: z129.boolean().optional(),
30387
31269
  generateAiEdit: AiEditRequestSchema.optional(),
30388
- replicateWinnerWhenFound: z126.boolean().optional().describe("La misma preferencia que enviar\xE1s al crear. false excluye la reserva de r\xE9plica del ganador de la cotizaci\xF3n con estudio.")
31270
+ replicateWinnerWhenFound: z129.boolean().optional().describe("La misma preferencia que enviar\xE1s al crear. false excluye la reserva de r\xE9plica del ganador de la cotizaci\xF3n con estudio.")
30389
31271
  }).strict();
30390
31272
  function buildMarketStudyEstimateRequests(input) {
30391
31273
  const quotesVideo = input.kind === "video" || (input.outputs ?? [
@@ -30521,6 +31403,8 @@ var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
30521
31403
  "Preserve all price disclosures and explicit confirmations. New enabled classic landings include the initial product GIF and its cost by default; disclose that choice before confirming. Pass includeInitialLandingGif=false when the user excludes it, and preserve that flag on retries.",
30522
31404
  "For Shopify publication recovery, preserve the canonical recovery issues, revision and fingerprint. Ask the seller for missing values and confirm the exact currency, unit price and pack totals before submitting commerceCorrection with confirmed:true. Never invent prices, convert currency or erase a visible offer. Follow open_editor in ProductMaker when required; a changed context needs fresh review. commerceCorrectionSaved proves saved inputs only: check any uncertain publication in the task and store before another attempt, especially publicationMode=new.",
30523
31405
  "Before a paid creative call, show a compact summary of what will be generated and the commercial inputs, then wait for explicit confirmation. Whenever video is requested, ask videoAngleCount (1, 2 or 3) even if the user delegates creative decisions, because quantity changes cost. Use create_product_task for a combined/autopilot request and generate_video_creative when the user asks only for one to three videos. Every standalone image, landing, and video generation requires creativeSetup. Use guided unless the user explicitly delegates every creative decision; only then use automatic, which maps to the web AI-decides-all toggle. Ask each guided setup as one compact checklist, not a serial interrogation: image covers archetype, treatment, format, quantity, quality, style mix, ad copy, competitor scan, context, direction, colors, headline and subheadline; landing covers classic strategy, format, competitor scan, payment methods, context, direction, headline and subheadline; video covers one explicit yes/no market-video study choice, narrative preset, buyer persona, presenter and creative direction. The market study is OFF by default: omitting competitorScanEnabled means no study. Before asking that yes/no choice for a video or a combined task with video, call estimate_market_study_cost and show the user both totals (without and with the study); never set competitorScanEnabled=true unless the user accepted that study (or requested the narrativa an\xE1lisis de mercado). Callers can mix narratives per video using angleNarratives (e.g. ['market', 'review', 'sassy_product_pixar']) in create_product_task and generate_video_creative; 'market' represents the narrativa an\xE1lisis de mercado. Guided mode requests angle reviews, and video also requests script review; show every numbered angle candidate, let the user select up to the authorized videoAngleCount and apply edits only to the numbered angles they name. Warn before confirmation that multi-product combo mode currently skips the script-review pause. Show each returned draft and concrete choices, then wait for the user. When get_video_script returns ready or interactionState awaiting_user_review, present it once and NEVER call get_video_script or get_task_status again until the user answers. After resolving a video angle review, follow reviewNextAction and poll get_video_script directly until ready/error/approved/skipped; approved or skipped resumes get_task_status. After other resolve_pending_action calls or approve_video_script succeeds, resume get_task_status until another review or a terminal state.",
31406
+ "Route by business type before choosing tools. A service business or personal brand (barbershop, clinic, studio, academy, agency, consultant), its Instagram/Facebook/TikTok/website, or a request for a brand analysis uses the service journey: create_brand, then create_service_landing, then create_animated_video with the published landing URL and create_service_images with the brandId. A physical product, supplier, marketplace or AliExpress/Dropi link uses the dropshipping tools. Never mix them: never pass a product task to the service tools or a brandId to the product tools. If the type is unclear, ask one short question.",
31407
+ "In the service journey, run each step's free action first (create_brand action=start without confirmed, create_service_landing action=prepare), show its price, and wait for explicit acceptance before the paid call. Ask the user for missing business data (contact, area, services and prices) once, in a single message, and pass their own words as businessNotes or businessInfo; never invent them. Keep polling status while a step runs and follow each result's nextAction. Publish the landing only after the user agrees, then offer the animated video from its publicUrl and the service images.",
30524
31408
  "For an animated service video, call create_animated_video action=prepare first: URL capture is free and never generates. Poll action=status until ready or a terminal failure. Show the ready preview and exact format, duration, language and price, then wait for explicit user confirmation before action=generate with websiteCaptureId and confirmed=true. A blocked page needs another public URL without login; never ask for passwords. Do not treat capture readiness as permission to spend credits. Retry a lost generation receipt with the same websiteCaptureId and choices even after expiry: this replays the paid task. Keep generationRequestId unchanged when supplied. For another video explicitly requested and confirmed by the user, supply a fresh generationRequestId; never rotate it automatically after an error.",
30525
31409
  "ProductMaker Meta campaigns remain PAUSED.",
30526
31410
  "If the user asks for deeper Meta metrics and no suitable Meta tools are connected, suggest Meta's official MCP as an optional extension. Point only to Meta-owned documentation or repositories, never auto-install it or request tokens, and never claim that it is installed.",
@@ -30589,6 +31473,9 @@ function createServer(opts) {
30589
31473
  proxyBaseUrl,
30590
31474
  transport
30591
31475
  });
31476
+ registerBrandTools(server, api);
31477
+ registerServiceLandingTools(server, api);
31478
+ registerServiceImageTools(server, api);
30592
31479
  registerAnimatedVideoTools(server, api);
30593
31480
  registerCreativeTools(server, api, {
30594
31481
  transport