@productmaker/mcp 6.0.0 → 6.1.1

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 (3) hide show
  1. package/dist/http.js +823 -80
  2. package/dist/stdio.js +1644 -903
  3. package/package.json +3 -3
package/dist/stdio.js CHANGED
@@ -264,7 +264,7 @@ var ApiClient = class {
264
264
  };
265
265
 
266
266
  // src/index.ts
267
- var MCP_VERSION = "6.0.0";
267
+ var MCP_VERSION = "6.1.1";
268
268
 
269
269
  // src/meta-upload-app.ts
270
270
  import { readFileSync } from "fs";
@@ -335,630 +335,339 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
335
335
  }
336
336
  __name(registerMediaUploadAppResource, "registerMediaUploadAppResource");
337
337
 
338
- // src/tools/tasks.ts
339
- import { z as z51 } from "zod";
338
+ // ../../packages/types/src/video-enums.ts
339
+ import { z } from "zod";
340
+ var VideoFormat = z.enum([
341
+ "single_8s",
342
+ "single_15s",
343
+ "double_30s"
344
+ ]);
345
+ var VideoStyle = z.enum([
346
+ "ugc",
347
+ "cartoon_3d",
348
+ "product_only",
349
+ "ai_dynamic"
350
+ ]);
351
+ var VideoModel = z.enum([
352
+ "veo",
353
+ "wan",
354
+ "bytedance",
355
+ "seedance",
356
+ "grok",
357
+ "kling"
358
+ ]);
359
+ var NarrativeStyle = z.enum([
360
+ "review",
361
+ "ugc_how_to",
362
+ "problem_product_cta",
363
+ "product_showcase",
364
+ "cinematic",
365
+ "wild_card",
366
+ "metaphor_product_cta",
367
+ "wearable_showcase",
368
+ "animated_problem_pixar",
369
+ "sassy_product_pixar",
370
+ "ugc_unboxing_chaos",
371
+ "ugc_curiosity_reveal",
372
+ "ugc_listicle_3",
373
+ "ugc_skeptic_confession",
374
+ "ugc_demo_review",
375
+ "gap_replication"
376
+ ]);
377
+ var VideoResolution = z.enum([
378
+ "480p",
379
+ "720p"
380
+ ]);
381
+ var VideoPreset = z.enum([
382
+ "auto",
383
+ "ugc_dynamic",
384
+ "ugc_how_to",
385
+ "problem_solution",
386
+ "product_showcase",
387
+ "cinematic",
388
+ "wild_card",
389
+ "wearable_showcase",
390
+ "pixar_problem",
391
+ "pixar_sassy",
392
+ "ugc_unboxing_chaos",
393
+ "ugc_curiosity_reveal",
394
+ "ugc_listicle_3",
395
+ "ugc_skeptic_confession",
396
+ "ugc_demo_review",
397
+ "metaphor_product"
398
+ ]);
399
+ var VoiceId = z.string();
400
+ var _VIDEO_MODEL_SET = new Set(VideoModel.options);
340
401
 
341
- // src/resolve-image.ts
342
- import { lookup as dnsLookup } from "dns/promises";
343
- import { promises as fs, createReadStream } from "fs";
344
- import * as path from "path";
345
- import * as os from "os";
346
- var MAX_BYTES = 20 * 1024 * 1024;
347
- var IMAGE_FORMATS = [
348
- {
349
- mime: "image/jpeg",
350
- exts: [
351
- ".jpg",
352
- ".jpeg"
353
- ]
354
- },
355
- {
356
- mime: "image/png",
357
- exts: [
358
- ".png"
359
- ]
360
- },
361
- {
362
- mime: "image/webp",
363
- exts: [
364
- ".webp"
365
- ]
366
- }
367
- ];
368
- var ALLOWED_MIMES = new Set(IMAGE_FORMATS.map((f) => f.mime));
369
- var ALLOWED_EXTS = new Set(IMAGE_FORMATS.flatMap((f) => f.exts));
370
- var ResolveImageError = class extends Error {
371
- static {
372
- __name(this, "ResolveImageError");
373
- }
374
- code;
375
- constructor(code, message) {
376
- super(message), this.code = code;
377
- this.name = "ResolveImageError";
378
- }
379
- };
380
- function sniffMime(buf) {
381
- if (buf.length < 4) return null;
382
- if (buf.readUInt8(0) === 137 && buf.readUInt8(1) === 80 && buf.readUInt8(2) === 78 && buf.readUInt8(3) === 71) return "image/png";
383
- if (buf.readUInt8(0) === 255 && buf.readUInt8(1) === 216 && buf.readUInt8(2) === 255) return "image/jpeg";
384
- if (buf.length < 12) return null;
385
- if (buf.readUInt8(0) === 82 && buf.readUInt8(1) === 73 && buf.readUInt8(2) === 70 && buf.readUInt8(3) === 70 && buf.readUInt8(8) === 87 && buf.readUInt8(9) === 69 && buf.readUInt8(10) === 66 && buf.readUInt8(11) === 80) return "image/webp";
386
- return null;
387
- }
388
- __name(sniffMime, "sniffMime");
389
- function isPrivateHost(host) {
390
- if (host === "localhost" || host === "0.0.0.0") return true;
391
- if (host === "metadata.google.internal" || host === "169.254.169.254") return true;
392
- if (/^127\./.test(host)) return true;
393
- if (/^10\./.test(host)) return true;
394
- if (/^192\.168\./.test(host)) return true;
395
- if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(host)) return true;
396
- if (host.startsWith("169.254.")) return true;
397
- if (host === "[::1]" || host === "::1") return true;
398
- if (/^\[?0*:(?:0*:){6}0*1\]?$/i.test(host)) return true;
399
- const ipv4Mapped = host.match(/^\[?::ffff:([0-9a-f.:]+)\]?$/i);
400
- if (ipv4Mapped && ipv4Mapped[1]) {
401
- const inner = ipv4Mapped[1];
402
- if (inner.includes(".")) return isPrivateHost(inner);
403
- const parts = inner.split(":").map((h) => parseInt(h, 16));
404
- if (parts.length === 2 && parts.every((n) => !Number.isNaN(n))) {
405
- const bytes = [
406
- parts[0] >> 8,
407
- parts[0] & 255,
408
- parts[1] >> 8,
409
- parts[1] & 255
410
- ];
411
- return isPrivateHost(bytes.join("."));
402
+ // ../../packages/types/src/task.ts
403
+ import { z as z2 } from "zod";
404
+ var TaskStage = z2.enum([
405
+ "extract",
406
+ "angles",
407
+ "video_generator",
408
+ "creative",
409
+ "landing"
410
+ ]);
411
+ var TaskStageStatus = z2.enum([
412
+ "pending",
413
+ "success",
414
+ "error"
415
+ ]);
416
+ var StageStatusSnapshot = z2.object({
417
+ extract: TaskStageStatus,
418
+ angles: TaskStageStatus,
419
+ video_generator: TaskStageStatus,
420
+ creative: TaskStageStatus,
421
+ landing: TaskStageStatus
422
+ });
423
+ var TaskKind = z2.enum([
424
+ "autopilot",
425
+ "image",
426
+ "video",
427
+ "landing",
428
+ "mix",
429
+ "service"
430
+ ]);
431
+
432
+ // ../../packages/types/src/product.ts
433
+ import { z as z4 } from "zod";
434
+
435
+ // ../../packages/types/src/product-reference-set.ts
436
+ import { z as z3 } from "zod";
437
+ var ProductReferenceScopeSchema = z3.enum([
438
+ "base",
439
+ "video_view_pack"
440
+ ]);
441
+ var SaleConfigurationSchema = z3.object({
442
+ quantity: z3.number().int().positive(),
443
+ variantSelections: z3.array(z3.object({
444
+ axisKey: z3.string().min(1).max(120),
445
+ valueKey: z3.string().min(1).max(120)
446
+ })).superRefine((selections, context) => {
447
+ const axes = selections.map((selection) => selection.axisKey);
448
+ if (new Set(axes).size !== axes.length) {
449
+ context.addIssue({
450
+ code: z3.ZodIssueCode.custom,
451
+ message: "variant_axes_must_be_unique"
452
+ });
412
453
  }
413
- }
414
- if (/^\[?f[cd][0-9a-f]{2}:/i.test(host)) return true;
415
- if (/^\[?fe[89ab][0-9a-f]:/i.test(host)) return true;
416
- return false;
417
- }
418
- __name(isPrivateHost, "isPrivateHost");
419
- async function assertHostResolvesPublic(hostname) {
420
- let records;
421
- try {
422
- records = await dnsLookup(hostname, {
423
- all: true
454
+ })
455
+ });
456
+ var SaleItemSchema = z3.object({
457
+ productKey: z3.string().min(1).max(120),
458
+ configurations: z3.array(SaleConfigurationSchema).min(1),
459
+ includedComponentKeys: z3.array(z3.string().min(1).max(120)).superRefine((keys, context) => {
460
+ if (new Set(keys).size !== keys.length) {
461
+ context.addIssue({
462
+ code: z3.ZodIssueCode.custom,
463
+ message: "included_components_must_be_unique"
464
+ });
465
+ }
466
+ })
467
+ });
468
+ var SaleStructureDecisionSchema = z3.object({
469
+ kind: z3.enum([
470
+ "single_product",
471
+ "distinct_product_combo",
472
+ "select_single_product"
473
+ ]),
474
+ items: z3.array(SaleItemSchema).min(1)
475
+ }).superRefine((value, context) => {
476
+ const productKeys = new Set(value.items.map((item) => item.productKey));
477
+ if (value.kind !== "distinct_product_combo" && value.items.length !== 1) {
478
+ context.addIssue({
479
+ code: z3.ZodIssueCode.custom,
480
+ path: [
481
+ "items"
482
+ ],
483
+ message: "single_product_decision_requires_one_item"
424
484
  });
425
- } catch {
426
- if (process.env.NODE_ENV === "test" || process.env.VITEST) return;
427
- throw new ResolveImageError("INVALID_INPUT", `imageUrl DNS lookup failed for ${hostname}`);
428
485
  }
429
- for (const { address } of records) {
430
- if (isPrivateHost(address)) {
431
- throw new ResolveImageError("INVALID_INPUT", `imageUrl host resolves to a private address (${address})`);
432
- }
486
+ if (value.kind === "distinct_product_combo" && productKeys.size < 2) {
487
+ context.addIssue({
488
+ code: z3.ZodIssueCode.custom,
489
+ path: [
490
+ "items"
491
+ ],
492
+ message: "combo_requires_distinct_products"
493
+ });
433
494
  }
434
- }
435
- __name(assertHostResolvesPublic, "assertHostResolvesPublic");
436
- function expandUserPath(raw) {
437
- let s = raw.trim();
438
- if (s === "~") return os.homedir();
439
- if (s.startsWith("~/") || s.startsWith("~\\")) s = path.join(os.homedir(), s.slice(2));
440
- s = s.replace(/^\$HOME(?=$|[/\\])/, os.homedir());
441
- s = s.replace(/^%USERPROFILE%(?=$|[/\\])/i, os.homedir());
442
- return s;
443
- }
444
- __name(expandUserPath, "expandUserPath");
445
- async function readFileWithCap(absPath, max) {
446
- const chunks = [];
447
- let total = 0;
448
- let exceeded = false;
449
- const stream = createReadStream(absPath);
450
- try {
451
- for await (const chunk of stream) {
452
- total += chunk.length;
453
- if (total > max) {
454
- exceeded = true;
455
- break;
456
- }
457
- chunks.push(chunk);
458
- }
459
- } catch (e) {
460
- const code = e.code;
461
- if (code === "ENOENT") throw new ResolveImageError("IMAGE_PATH_NOT_FOUND", "file disappeared during read");
462
- if (code === "EACCES" || code === "EPERM") throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `cannot read file (${code})`);
463
- throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", e.message);
464
- } finally {
465
- if (!stream.destroyed) stream.destroy();
466
- }
467
- if (exceeded) throw new ResolveImageError("IMAGE_TOO_LARGE", `file exceeds ${max} bytes`);
468
- return Buffer.concat(chunks, total);
469
- }
470
- __name(readFileWithCap, "readFileWithCap");
471
- async function resolvePath(rawPath) {
472
- const expanded = expandUserPath(rawPath);
473
- const abs = path.resolve(expanded);
474
- const ext = path.extname(abs).toLowerCase();
475
- if (!ALLOWED_EXTS.has(ext)) {
476
- throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `path extension ${ext || "(none)"} is not in the image allowlist (.jpg .jpeg .png .webp)`);
477
- }
478
- let stat;
479
- try {
480
- stat = await fs.stat(abs);
481
- } catch (e) {
482
- const code = e.code;
483
- if (code === "ENOENT") {
484
- throw new ResolveImageError("IMAGE_PATH_NOT_FOUND", `no such file: ${abs}`);
485
- }
486
- if (code === "EACCES" || code === "EPERM") {
487
- throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `cannot access ${abs} (${code})`);
488
- }
489
- throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `stat failed: ${e.message}`);
490
- }
491
- if (!stat.isFile()) {
492
- throw new ResolveImageError("IMAGE_PATH_NOT_A_FILE", `${abs} is not a regular file`);
493
- }
494
- if (stat.size > MAX_BYTES) {
495
- throw new ResolveImageError("IMAGE_TOO_LARGE", `${stat.size} bytes (cap ${MAX_BYTES})`);
496
- }
497
- const buf = await readFileWithCap(abs, MAX_BYTES);
498
- const sniffed = sniffMime(buf);
499
- if (!sniffed) {
500
- throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes do not match PNG/JPEG/WEBP for ${abs}`);
501
- }
502
- return {
503
- buffer: buf,
504
- mimetype: sniffed
505
- };
506
- }
507
- __name(resolvePath, "resolvePath");
508
- async function resolveImageUrl(rawUrl) {
509
- let u;
510
- try {
511
- u = new URL(rawUrl);
512
- } catch {
513
- throw new ResolveImageError("INVALID_INPUT", "imageUrl is not a valid URL");
514
- }
515
- if (u.protocol !== "https:") throw new ResolveImageError("INVALID_INPUT", "imageUrl must be HTTPS");
516
- if (isPrivateHost(u.hostname)) throw new ResolveImageError("INVALID_INPUT", "imageUrl host not allowed");
517
- await assertHostResolvesPublic(u.hostname);
518
- let res;
519
- try {
520
- res = await fetch(rawUrl, {
521
- signal: AbortSignal.timeout(3e4),
522
- redirect: "error"
495
+ });
496
+ var ProductReferenceRejectionReasonSchema = z3.enum([
497
+ "replacement_requested",
498
+ "wrong_product_identity",
499
+ "wrong_variant",
500
+ "wrong_color",
501
+ "wrong_quantity_or_packaging",
502
+ "wrong_view",
503
+ "low_quality",
504
+ "not_a_product_photo",
505
+ "other"
506
+ ]);
507
+ var ProductReferenceResolutionDecisionSchema = z3.enum([
508
+ "approve",
509
+ "reject",
510
+ "observed_only",
511
+ "correct_view"
512
+ ]);
513
+ var IdentityAnchorSchema = z3.object({
514
+ productKey: z3.string().min(1).max(120),
515
+ variantKey: z3.string().min(1).max(120).optional(),
516
+ primaryRefKey: z3.string().min(1).max(200)
517
+ });
518
+ var ProductReferenceApprovalSchema = z3.object({
519
+ version: z3.literal(1),
520
+ scope: ProductReferenceScopeSchema,
521
+ scopeKey: z3.string().min(1).max(120),
522
+ scopeHash: z3.string().min(1).max(128),
523
+ baseReferenceSetHash: z3.string().min(1).max(128),
524
+ approvedRefKeys: z3.array(z3.string().min(1).max(200)).min(1),
525
+ identityAnchors: z3.array(IdentityAnchorSchema).min(1),
526
+ approvedAt: z3.string().datetime(),
527
+ approvedBy: z3.string().min(1).max(200),
528
+ invalidatedAt: z3.string().datetime().optional(),
529
+ invalidationReason: z3.enum([
530
+ "evidence_changed",
531
+ "assignment_changed",
532
+ "required_output_changed",
533
+ "prepared_content_changed",
534
+ "anchor_changed"
535
+ ]).optional()
536
+ });
537
+ var ProductReferencePreviewSchema = z3.object({
538
+ refKey: z3.string().min(1).max(200),
539
+ assetKey: z3.string().min(1).max(2e3).optional(),
540
+ previewUrl: z3.string().url(),
541
+ estimatedCreditCents: z3.number().int().nonnegative().optional(),
542
+ retryAllowanceCents: z3.number().int().nonnegative().optional(),
543
+ source: z3.enum([
544
+ "upload",
545
+ "product_page",
546
+ "internet",
547
+ "cleaned",
548
+ "observed",
549
+ "generated"
550
+ ]),
551
+ productKey: z3.string().min(1).max(120),
552
+ variantKey: z3.string().min(1).max(120).optional(),
553
+ label: z3.string().min(1).max(160),
554
+ view: z3.string().min(1).max(80),
555
+ isAuthoritative: z3.boolean()
556
+ });
557
+ var ApprovedProductReferencesSchema = z3.object({
558
+ scopeSnapshotId: z3.string().uuid(),
559
+ scopeHash: z3.string().min(1).max(128),
560
+ previews: z3.array(ProductReferencePreviewSchema).min(1)
561
+ });
562
+ var ProductReferencePendingActionSchema = z3.object({
563
+ kind: z3.literal("product_reference_set_approval"),
564
+ phase: z3.enum([
565
+ "preparation",
566
+ "final"
567
+ ]).default("final"),
568
+ scope: ProductReferenceScopeSchema,
569
+ scopeKey: z3.string().min(1).max(120),
570
+ referenceVersionId: z3.string().uuid(),
571
+ scopeSnapshotId: z3.string().uuid(),
572
+ baseReferenceSetHash: z3.string().min(1).max(128),
573
+ scopeHash: z3.string().min(1).max(128),
574
+ approvedRefKeys: z3.array(z3.string().min(1).max(200)).min(1),
575
+ /** Server-owned selection shown when preparation has not started yet.
576
+ * It may be empty when every candidate came from internet discovery, forcing
577
+ * an explicit user choice before any paid cleaning call. */
578
+ selectedRefKeys: z3.array(z3.string().min(1).max(200)).optional(),
579
+ /** True when the server selected sensible defaults without a user photo-review
580
+ * interaction. Clients must let the user change this selection before approval. */
581
+ selectionReviewRequired: z3.boolean().optional(),
582
+ identityAnchors: z3.array(IdentityAnchorSchema).min(1),
583
+ previews: z3.array(ProductReferencePreviewSchema).min(1),
584
+ allowedDecisions: z3.array(ProductReferenceResolutionDecisionSchema).min(1),
585
+ maxAuthorizedCreditCents: z3.number().int().nonnegative().optional(),
586
+ costBreakdown: z3.object({
587
+ preparationCreditCents: z3.number().int().nonnegative(),
588
+ retryReserveCreditCents: z3.number().int().nonnegative(),
589
+ videoViewPerIdentityCreditCents: z3.number().int().nonnegative().optional()
590
+ }).optional(),
591
+ recovery: z3.object({
592
+ kind: z3.enum([
593
+ "replace_evidence",
594
+ "combo_component_evidence"
595
+ ]),
596
+ currentSelectionHash: z3.string().min(1).max(128),
597
+ minimumImageCount: z3.number().int().positive(),
598
+ affectedRefKeys: z3.array(z3.string().min(1).max(200))
599
+ }).optional(),
600
+ reason: z3.string().max(500).optional()
601
+ });
602
+ var ProductReferencePreparationCostSchema = z3.object({
603
+ preparationCreditCents: z3.number().int().nonnegative(),
604
+ retryReserveCreditCents: z3.number().int().nonnegative(),
605
+ additionalVideoViewCreditCents: z3.number().int().nonnegative(),
606
+ maxCreditCents: z3.number().int().nonnegative()
607
+ });
608
+ var ResolutionEnvelopeSchema = z3.object({
609
+ expectedScopeHash: z3.string().min(1).max(128),
610
+ scope: ProductReferenceScopeSchema,
611
+ scopeKey: z3.string().min(1).max(120),
612
+ scopeSnapshotId: z3.string().uuid()
613
+ });
614
+ var ResolveProductReferenceSetRequestSchema = z3.discriminatedUnion("decision", [
615
+ ResolutionEnvelopeSchema.extend({
616
+ decision: z3.literal("approve"),
617
+ /** Exact subset selected from the server-owned base previews. The backend
618
+ * validates and freezes it before preparation or final generation resumes. */
619
+ selectedRefKeys: z3.array(z3.string().min(1).max(200)).min(1).optional()
620
+ }),
621
+ ResolutionEnvelopeSchema.extend({
622
+ decision: z3.literal("reject"),
623
+ reason: ProductReferenceRejectionReasonSchema,
624
+ affectedRefKeys: z3.array(z3.string().min(1).max(200)).min(1),
625
+ recoveryAction: z3.enum([
626
+ "remove",
627
+ "replace",
628
+ "upload",
629
+ "return_to_selection"
630
+ ])
631
+ }),
632
+ ResolutionEnvelopeSchema.extend({
633
+ decision: z3.literal("observed_only")
634
+ }),
635
+ ResolutionEnvelopeSchema.extend({
636
+ decision: z3.literal("correct_view"),
637
+ affectedRefKeys: z3.array(z3.string().min(1).max(200)).length(1),
638
+ reason: ProductReferenceRejectionReasonSchema
639
+ })
640
+ ]).superRefine((value, context) => {
641
+ if (value.scope === "base" && (value.decision === "observed_only" || value.decision === "correct_view")) {
642
+ context.addIssue({
643
+ code: z3.ZodIssueCode.custom,
644
+ path: [
645
+ "scope"
646
+ ],
647
+ message: "base_scope_does_not_support_video_only_decision"
523
648
  });
524
- } catch (e) {
525
- throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", e.message);
526
- }
527
- if (!res.ok) throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", `HTTP ${res.status}`);
528
- const cl = res.headers.get("content-length");
529
- if (cl && Number(cl) > MAX_BYTES) {
530
- throw new ResolveImageError("IMAGE_TOO_LARGE", `content-length ${cl}`);
531
649
  }
532
- const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
533
- if (!ALLOWED_MIMES.has(ct)) throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `content-type ${ct}`);
534
- let buf;
535
- try {
536
- buf = Buffer.from(await readResponseBytes(res, MAX_BYTES));
537
- } catch (error) {
538
- if (error instanceof ResponseBodyTooLargeError) {
539
- throw new ResolveImageError("IMAGE_TOO_LARGE", `image URL exceeds ${MAX_BYTES} bytes`);
540
- }
541
- throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", "image response could not be read");
650
+ if (value.scope !== "base" && value.decision === "approve" && value.selectedRefKeys) {
651
+ context.addIssue({
652
+ code: z3.ZodIssueCode.custom,
653
+ path: [
654
+ "selectedRefKeys"
655
+ ],
656
+ message: "selected_references_are_only_valid_for_base_preparation"
657
+ });
542
658
  }
543
- const sniffed = sniffMime(buf);
544
- if (!sniffed || sniffed !== ct) {
545
- throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes (${sniffed ?? "unknown"}) do not match content-type (${ct})`);
546
- }
547
- return {
548
- buffer: buf,
549
- mimetype: sniffed
550
- };
551
- }
552
- __name(resolveImageUrl, "resolveImageUrl");
553
- async function resolveImages(images, opts = {
554
- transport: "http"
555
- }) {
556
- if (!images.length) throw new ResolveImageError("INVALID_INPUT", "images: at least one entry required");
557
- if (images.length > 5) throw new ResolveImageError("INVALID_INPUT", "images: max 5 per request");
558
- return Promise.all(images.map((ref) => resolveOne(ref, opts.transport)));
559
- }
560
- __name(resolveImages, "resolveImages");
561
- async function resolveOne(ref, transport) {
562
- const hasUrl = Boolean(ref.url);
563
- const hasPath = Boolean(ref.path);
564
- if (hasUrl === hasPath) {
565
- throw new ResolveImageError("INVALID_INPUT", "each image entry must have exactly one of `url` or `path`");
566
- }
567
- if (hasPath) {
568
- if (transport === "http") {
569
- throw new ResolveImageError("PATH_NOT_SUPPORTED_IN_HTTP_MODE", "Local file paths are only available when running the MCP locally (npm). For the hosted endpoint at mcp.productmaker.app, pass a public HTTPS `url` instead, or ask the user to host the photo first.");
570
- }
571
- return resolvePath(ref.path);
572
- }
573
- return resolveImageUrl(ref.url);
574
- }
575
- __name(resolveOne, "resolveOne");
576
-
577
- // src/status-poller.ts
578
- var TERMINAL = /* @__PURE__ */ new Set([
579
- "done",
580
- "complete",
581
- "error"
582
- ]);
583
- async function pollStatus(fetcher, waitSeconds, signal, opts = {}) {
584
- const intervalMs = opts.intervalMs ?? 3e3;
585
- const maxWait = opts.maxWaitSeconds ?? 120;
586
- const effective = Math.min(Math.max(0, waitSeconds), maxWait);
587
- let snapshot = await fetcher();
588
- if (effective === 0) return snapshot;
589
- if (TERMINAL.has(snapshot.status) || snapshot.status === "awaiting_review" || snapshot.pendingUserAction != null) return snapshot;
590
- const deadline = Date.now() + effective * 1e3;
591
- while (Date.now() < deadline) {
592
- if (signal?.aborted) throw new Error("aborted");
593
- await sleep(intervalMs, signal);
594
- const next = await fetcher();
595
- if (next.status !== snapshot.status || next.phase !== snapshot.phase || TERMINAL.has(next.status) || next.status === "awaiting_review" || next.pendingUserAction != null) {
596
- return next;
597
- }
598
- snapshot = next;
599
- }
600
- return snapshot;
601
- }
602
- __name(pollStatus, "pollStatus");
603
- function sleep(ms, signal) {
604
- return new Promise((resolve3, reject) => {
605
- const onAbort = /* @__PURE__ */ __name(() => {
606
- clearTimeout(t);
607
- reject(new Error("aborted"));
608
- }, "onAbort");
609
- const t = setTimeout(() => {
610
- signal?.removeEventListener("abort", onAbort);
611
- resolve3();
612
- }, ms);
613
- signal?.addEventListener("abort", onAbort, {
614
- once: true
615
- });
616
- });
617
- }
618
- __name(sleep, "sleep");
619
-
620
- // src/errors.ts
621
- import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
622
-
623
- // src/resolve-media.ts
624
- import { lookup as dnsLookup2 } from "dns/promises";
625
- import { promises as fs2, createReadStream as createReadStream2 } from "fs";
626
- import * as os2 from "os";
627
- import * as path2 from "path";
628
-
629
- // ../../packages/types/src/video-enums.ts
630
- import { z } from "zod";
631
- var VideoFormat = z.enum([
632
- "single_8s",
633
- "single_15s",
634
- "double_30s"
635
- ]);
636
- var VideoStyle = z.enum([
637
- "ugc",
638
- "cartoon_3d",
639
- "product_only",
640
- "ai_dynamic"
641
- ]);
642
- var VideoModel = z.enum([
643
- "veo",
644
- "wan",
645
- "bytedance",
646
- "seedance",
647
- "grok",
648
- "kling"
649
- ]);
650
- var NarrativeStyle = z.enum([
651
- "review",
652
- "ugc_how_to",
653
- "problem_product_cta",
654
- "product_showcase",
655
- "cinematic",
656
- "wild_card",
657
- "metaphor_product_cta",
658
- "wearable_showcase",
659
- "animated_problem_pixar",
660
- "sassy_product_pixar",
661
- "ugc_unboxing_chaos",
662
- "ugc_curiosity_reveal",
663
- "ugc_listicle_3",
664
- "ugc_skeptic_confession",
665
- "ugc_demo_review",
666
- "gap_replication"
667
- ]);
668
- var VideoResolution = z.enum([
669
- "480p",
670
- "720p"
671
- ]);
672
- var VideoPreset = z.enum([
673
- "auto",
674
- "ugc_dynamic",
675
- "ugc_how_to",
676
- "problem_solution",
677
- "product_showcase",
678
- "cinematic",
679
- "wild_card",
680
- "wearable_showcase",
681
- "pixar_problem",
682
- "pixar_sassy",
683
- "ugc_unboxing_chaos",
684
- "ugc_curiosity_reveal",
685
- "ugc_listicle_3",
686
- "ugc_skeptic_confession",
687
- "ugc_demo_review",
688
- "metaphor_product"
689
- ]);
690
- var VoiceId = z.string();
691
- var _VIDEO_MODEL_SET = new Set(VideoModel.options);
692
-
693
- // ../../packages/types/src/task.ts
694
- import { z as z2 } from "zod";
695
- var TaskStage = z2.enum([
696
- "extract",
697
- "angles",
698
- "video_generator",
699
- "creative",
700
- "landing"
701
- ]);
702
- var TaskStageStatus = z2.enum([
703
- "pending",
704
- "success",
705
- "error"
706
- ]);
707
- var StageStatusSnapshot = z2.object({
708
- extract: TaskStageStatus,
709
- angles: TaskStageStatus,
710
- video_generator: TaskStageStatus,
711
- creative: TaskStageStatus,
712
- landing: TaskStageStatus
713
- });
714
- var TaskKind = z2.enum([
715
- "autopilot",
716
- "image",
717
- "video",
718
- "landing",
719
- "mix",
720
- "service"
721
- ]);
722
-
723
- // ../../packages/types/src/product.ts
724
- import { z as z4 } from "zod";
725
-
726
- // ../../packages/types/src/product-reference-set.ts
727
- import { z as z3 } from "zod";
728
- var ProductReferenceScopeSchema = z3.enum([
729
- "base",
730
- "video_view_pack"
731
- ]);
732
- var SaleConfigurationSchema = z3.object({
733
- quantity: z3.number().int().positive(),
734
- variantSelections: z3.array(z3.object({
735
- axisKey: z3.string().min(1).max(120),
736
- valueKey: z3.string().min(1).max(120)
737
- })).superRefine((selections, context) => {
738
- const axes = selections.map((selection) => selection.axisKey);
739
- if (new Set(axes).size !== axes.length) {
740
- context.addIssue({
741
- code: z3.ZodIssueCode.custom,
742
- message: "variant_axes_must_be_unique"
743
- });
744
- }
745
- })
746
659
  });
747
- var SaleItemSchema = z3.object({
748
- productKey: z3.string().min(1).max(120),
749
- configurations: z3.array(SaleConfigurationSchema).min(1),
750
- includedComponentKeys: z3.array(z3.string().min(1).max(120)).superRefine((keys, context) => {
751
- if (new Set(keys).size !== keys.length) {
752
- context.addIssue({
753
- code: z3.ZodIssueCode.custom,
754
- message: "included_components_must_be_unique"
755
- });
756
- }
757
- })
660
+ var ResolveProductReferenceSetResponseSchema = z3.object({
661
+ success: z3.literal(true),
662
+ taskId: z3.string().uuid(),
663
+ resolutionId: z3.string().uuid(),
664
+ decision: ProductReferenceResolutionDecisionSchema
758
665
  });
759
- var SaleStructureDecisionSchema = z3.object({
760
- kind: z3.enum([
761
- "single_product",
762
- "distinct_product_combo",
763
- "select_single_product"
764
- ]),
765
- items: z3.array(SaleItemSchema).min(1)
766
- }).superRefine((value, context) => {
767
- const productKeys = new Set(value.items.map((item) => item.productKey));
768
- if (value.kind !== "distinct_product_combo" && value.items.length !== 1) {
769
- context.addIssue({
770
- code: z3.ZodIssueCode.custom,
771
- path: [
772
- "items"
773
- ],
774
- message: "single_product_decision_requires_one_item"
775
- });
776
- }
777
- if (value.kind === "distinct_product_combo" && productKeys.size < 2) {
778
- context.addIssue({
779
- code: z3.ZodIssueCode.custom,
780
- path: [
781
- "items"
782
- ],
783
- message: "combo_requires_distinct_products"
784
- });
785
- }
786
- });
787
- var ProductReferenceRejectionReasonSchema = z3.enum([
788
- "replacement_requested",
789
- "wrong_product_identity",
790
- "wrong_variant",
791
- "wrong_color",
792
- "wrong_quantity_or_packaging",
793
- "wrong_view",
794
- "low_quality",
795
- "not_a_product_photo",
796
- "other"
797
- ]);
798
- var ProductReferenceResolutionDecisionSchema = z3.enum([
799
- "approve",
800
- "reject",
801
- "observed_only",
802
- "correct_view"
803
- ]);
804
- var IdentityAnchorSchema = z3.object({
805
- productKey: z3.string().min(1).max(120),
806
- variantKey: z3.string().min(1).max(120).optional(),
807
- primaryRefKey: z3.string().min(1).max(200)
808
- });
809
- var ProductReferenceApprovalSchema = z3.object({
810
- version: z3.literal(1),
811
- scope: ProductReferenceScopeSchema,
812
- scopeKey: z3.string().min(1).max(120),
813
- scopeHash: z3.string().min(1).max(128),
814
- baseReferenceSetHash: z3.string().min(1).max(128),
815
- approvedRefKeys: z3.array(z3.string().min(1).max(200)).min(1),
816
- identityAnchors: z3.array(IdentityAnchorSchema).min(1),
817
- approvedAt: z3.string().datetime(),
818
- approvedBy: z3.string().min(1).max(200),
819
- invalidatedAt: z3.string().datetime().optional(),
820
- invalidationReason: z3.enum([
821
- "evidence_changed",
822
- "assignment_changed",
823
- "required_output_changed",
824
- "prepared_content_changed",
825
- "anchor_changed"
826
- ]).optional()
827
- });
828
- var ProductReferencePreviewSchema = z3.object({
829
- refKey: z3.string().min(1).max(200),
830
- assetKey: z3.string().min(1).max(2e3).optional(),
831
- previewUrl: z3.string().url(),
832
- estimatedCreditCents: z3.number().int().nonnegative().optional(),
833
- retryAllowanceCents: z3.number().int().nonnegative().optional(),
834
- source: z3.enum([
835
- "upload",
836
- "product_page",
837
- "internet",
838
- "cleaned",
839
- "observed",
840
- "generated"
841
- ]),
842
- productKey: z3.string().min(1).max(120),
843
- variantKey: z3.string().min(1).max(120).optional(),
844
- label: z3.string().min(1).max(160),
845
- view: z3.string().min(1).max(80),
846
- isAuthoritative: z3.boolean()
847
- });
848
- var ApprovedProductReferencesSchema = z3.object({
849
- scopeSnapshotId: z3.string().uuid(),
850
- scopeHash: z3.string().min(1).max(128),
851
- previews: z3.array(ProductReferencePreviewSchema).min(1)
852
- });
853
- var ProductReferencePendingActionSchema = z3.object({
854
- kind: z3.literal("product_reference_set_approval"),
855
- phase: z3.enum([
856
- "preparation",
857
- "final"
858
- ]).default("final"),
859
- scope: ProductReferenceScopeSchema,
860
- scopeKey: z3.string().min(1).max(120),
861
- referenceVersionId: z3.string().uuid(),
862
- scopeSnapshotId: z3.string().uuid(),
863
- baseReferenceSetHash: z3.string().min(1).max(128),
864
- scopeHash: z3.string().min(1).max(128),
865
- approvedRefKeys: z3.array(z3.string().min(1).max(200)).min(1),
866
- /** Server-owned selection shown when preparation has not started yet.
867
- * It may be empty when every candidate came from internet discovery, forcing
868
- * an explicit user choice before any paid cleaning call. */
869
- selectedRefKeys: z3.array(z3.string().min(1).max(200)).optional(),
870
- /** True when the server selected sensible defaults without a user photo-review
871
- * interaction. Clients must let the user change this selection before approval. */
872
- selectionReviewRequired: z3.boolean().optional(),
873
- identityAnchors: z3.array(IdentityAnchorSchema).min(1),
874
- previews: z3.array(ProductReferencePreviewSchema).min(1),
875
- allowedDecisions: z3.array(ProductReferenceResolutionDecisionSchema).min(1),
876
- maxAuthorizedCreditCents: z3.number().int().nonnegative().optional(),
877
- costBreakdown: z3.object({
878
- preparationCreditCents: z3.number().int().nonnegative(),
879
- retryReserveCreditCents: z3.number().int().nonnegative(),
880
- videoViewPerIdentityCreditCents: z3.number().int().nonnegative().optional()
881
- }).optional(),
882
- recovery: z3.object({
883
- kind: z3.enum([
884
- "replace_evidence",
885
- "combo_component_evidence"
886
- ]),
887
- currentSelectionHash: z3.string().min(1).max(128),
888
- minimumImageCount: z3.number().int().positive(),
889
- affectedRefKeys: z3.array(z3.string().min(1).max(200))
890
- }).optional(),
891
- reason: z3.string().max(500).optional()
892
- });
893
- var ProductReferencePreparationCostSchema = z3.object({
894
- preparationCreditCents: z3.number().int().nonnegative(),
895
- retryReserveCreditCents: z3.number().int().nonnegative(),
896
- additionalVideoViewCreditCents: z3.number().int().nonnegative(),
897
- maxCreditCents: z3.number().int().nonnegative()
898
- });
899
- var ResolutionEnvelopeSchema = z3.object({
900
- expectedScopeHash: z3.string().min(1).max(128),
901
- scope: ProductReferenceScopeSchema,
902
- scopeKey: z3.string().min(1).max(120),
903
- scopeSnapshotId: z3.string().uuid()
904
- });
905
- var ResolveProductReferenceSetRequestSchema = z3.discriminatedUnion("decision", [
906
- ResolutionEnvelopeSchema.extend({
907
- decision: z3.literal("approve"),
908
- /** Exact subset selected from the server-owned base previews. The backend
909
- * validates and freezes it before preparation or final generation resumes. */
910
- selectedRefKeys: z3.array(z3.string().min(1).max(200)).min(1).optional()
911
- }),
912
- ResolutionEnvelopeSchema.extend({
913
- decision: z3.literal("reject"),
914
- reason: ProductReferenceRejectionReasonSchema,
915
- affectedRefKeys: z3.array(z3.string().min(1).max(200)).min(1),
916
- recoveryAction: z3.enum([
917
- "remove",
918
- "replace",
919
- "upload",
920
- "return_to_selection"
921
- ])
922
- }),
923
- ResolutionEnvelopeSchema.extend({
924
- decision: z3.literal("observed_only")
925
- }),
926
- ResolutionEnvelopeSchema.extend({
927
- decision: z3.literal("correct_view"),
928
- affectedRefKeys: z3.array(z3.string().min(1).max(200)).length(1),
929
- reason: ProductReferenceRejectionReasonSchema
930
- })
931
- ]).superRefine((value, context) => {
932
- if (value.scope === "base" && (value.decision === "observed_only" || value.decision === "correct_view")) {
933
- context.addIssue({
934
- code: z3.ZodIssueCode.custom,
935
- path: [
936
- "scope"
937
- ],
938
- message: "base_scope_does_not_support_video_only_decision"
939
- });
940
- }
941
- if (value.scope !== "base" && value.decision === "approve" && value.selectedRefKeys) {
942
- context.addIssue({
943
- code: z3.ZodIssueCode.custom,
944
- path: [
945
- "selectedRefKeys"
946
- ],
947
- message: "selected_references_are_only_valid_for_base_preparation"
948
- });
949
- }
950
- });
951
- var ResolveProductReferenceSetResponseSchema = z3.object({
952
- success: z3.literal(true),
953
- taskId: z3.string().uuid(),
954
- resolutionId: z3.string().uuid(),
955
- decision: ProductReferenceResolutionDecisionSchema
956
- });
957
- var ProductReferenceResolutionSchema = z3.object({
958
- scope: ProductReferenceScopeSchema,
959
- scopeKey: z3.string().min(1).max(120),
960
- expectedScopeHash: z3.string().min(1).max(128),
961
- decision: ProductReferenceResolutionDecisionSchema
666
+ var ProductReferenceResolutionSchema = z3.object({
667
+ scope: ProductReferenceScopeSchema,
668
+ scopeKey: z3.string().min(1).max(120),
669
+ expectedScopeHash: z3.string().min(1).max(128),
670
+ decision: ProductReferenceResolutionDecisionSchema
962
671
  });
963
672
  var ReferencePreparationPlanSchema = z3.object({
964
673
  version: z3.literal(1),
@@ -2900,6 +2609,11 @@ var IngestOptions = z15.object({
2900
2609
  * scan. Default true preserves the existing flow; users with their own
2901
2610
  * competitor intel or in privacy-sensitive niches can disable. */
2902
2611
  competitorScanEnabled: z15.boolean().default(true),
2612
+ /** Consent for a replicable competitor winner to override an explicitly
2613
+ * chosen videoPreset. Default true preserves the 2026-08-04 decision;
2614
+ * only a deliberate `false` suspends replication. See
2615
+ * `shouldAskReplicationConsent` for when this is even asked. */
2616
+ replicateWinnerWhenFound: z15.boolean().default(true),
2903
2617
  brandId: z15.string().uuid().optional()
2904
2618
  }).partial().superRefine((data, ctx) => {
2905
2619
  const message = validatePriceInversion(data.price, data.compareAtPrice);
@@ -4542,14 +4256,7 @@ var CostEstimationType = z25.enum([
4542
4256
  "service-creative",
4543
4257
  "actor-grid"
4544
4258
  ]);
4545
- var IngestEstimationOptions = z25.object({
4546
- /** Number of product references that the extract stage will clean. */
4547
- productImageCount: z25.number().int().min(1).max(INGEST_MAX_REFERENCE_IMAGES).optional(),
4548
- videoModel: VideoModel.optional(),
4549
- videoTier: VideoTier.optional(),
4550
- multiAngleVideos: z25.boolean().optional(),
4551
- videoAngleCount: z25.number().int().min(1).max(3).optional(),
4552
- ugcFormat: VideoFormat.optional(),
4259
+ var SharedOutputEstimationFields = {
4553
4260
  generateAdCopy: z25.boolean().optional(),
4554
4261
  landingEnabled: z25.boolean().optional(),
4555
4262
  /** Landing render mode. 'code' (structured config + ~6 1K images) is markedly
@@ -4566,6 +4273,20 @@ var IngestEstimationOptions = z25.object({
4566
4273
  * ~$0.48 worst case: 20 ads × 1.2¢ + 12 transcripts × 2¢). Default
4567
4274
  * behavior (undefined / true) keeps the full ceiling. */
4568
4275
  competitorScanEnabled: z25.boolean().optional(),
4276
+ /** Declining the winner replica makes `gap_replication` unreachable, so its
4277
+ * forced-STANDARD ceiling must not be quoted. See
4278
+ * `.agent/specs/winner-replication-consent.spec.test.ts`. */
4279
+ replicateWinnerWhenFound: z25.boolean().optional()
4280
+ };
4281
+ var IngestEstimationOptions = z25.object({
4282
+ /** Number of product references that the extract stage will clean. */
4283
+ productImageCount: z25.number().int().min(1).max(INGEST_MAX_REFERENCE_IMAGES).optional(),
4284
+ videoModel: VideoModel.optional(),
4285
+ videoTier: VideoTier.optional(),
4286
+ multiAngleVideos: z25.boolean().optional(),
4287
+ videoAngleCount: z25.number().int().min(1).max(3).optional(),
4288
+ ugcFormat: VideoFormat.optional(),
4289
+ ...SharedOutputEstimationFields,
4569
4290
  videoStyle: VideoStyle.optional(),
4570
4291
  narrativeStyle: NarrativeStyle.optional(),
4571
4292
  videoPreset: VideoPreset.optional(),
@@ -4587,20 +4308,7 @@ var IngestResumeEstimationOptions = z25.object({
4587
4308
  videoResolution: VideoResolution.optional(),
4588
4309
  selectedAngleIndices: z25.array(z25.number().int().nonnegative()).min(1).max(3).optional(),
4589
4310
  angleVideoOptions: z25.array(AngleVideoOptions).max(3).optional(),
4590
- generateAdCopy: z25.boolean().optional(),
4591
- landingEnabled: z25.boolean().optional(),
4592
- /** Landing render mode. 'code' (~6 1K images) is cheaper than 'classic' (~12 2K
4593
- * images). Undefined ⇒ classic (the default render mode). */
4594
- landingMode: LandingModeSchema.optional(),
4595
- /** Classic landing strategy. Undefined follows the public classic default; code mode ignores it. */
4596
- landingStrategy: LandingStrategySchema.optional(),
4597
- adCreativeEnabled: z25.boolean().optional(),
4598
- /** When false, skip video generation cost. Multi-output autopilot
4599
- * selections (e.g. {landing, image}) set this false. */
4600
- videoGeneratorEnabled: z25.boolean().optional(),
4601
- /** When false, drop the Apify + Whisper competitor-spy ceiling (up to ~$0.48
4602
- * worst case) from the resume estimate so the chat preview matches what's billed. */
4603
- competitorScanEnabled: z25.boolean().optional(),
4311
+ ...SharedOutputEstimationFields,
4604
4312
  /** Whether the ingest flow will generate the ad creative image (typically true when angle_0 is included). */
4605
4313
  generateCreative: z25.boolean().optional(),
4606
4314
  generateAiMusic: z25.boolean().optional(),
@@ -4641,6 +4349,7 @@ var VideoCreativeEstimationOptions = z25.object({
4641
4349
  requiresAngles: z25.boolean().optional(),
4642
4350
  /** When false, omit the Meta/Whisper competitor-scan ceiling from the estimate. */
4643
4351
  competitorScanEnabled: z25.boolean().optional(),
4352
+ replicateWinnerWhenFound: z25.boolean().optional(),
4644
4353
  /** Quote the competitor scan even when product extraction is skipped (direct video auto path). */
4645
4354
  requiresCompetitorScan: z25.boolean().optional(),
4646
4355
  generateAiMusic: z25.boolean().optional(),
@@ -9685,64 +9394,754 @@ var ServiceMediaAnalysisSchema = z45.object({
9685
9394
  items: z45.array(ServiceMediaAnalysisItemSchema).max(15)
9686
9395
  });
9687
9396
 
9688
- // ../../packages/types/src/daily-control.ts
9689
- import { z as z46 } from "zod";
9690
- var DailyControlStatusSchema = z46.enum([
9691
- "ok",
9692
- /** Shopify no puede leerse: falta scope `read_orders` o la tienda no está conectada. */
9693
- "sales_unavailable",
9694
- /** La tienda no expone su zona horaria: sin ella no se puede agrupar por día. */
9695
- "timezone_unavailable",
9696
- /** El usuario nunca conectó la plataforma de anuncios (Meta o TikTok). */
9697
- "ads_not_connected",
9698
- /**
9699
- * El proyecto no tiene NINGUNA campaña vinculada (ni Meta ni TikTok). Sin
9700
- * campaña no hay gasto que leer, y "gasto 0" contra ventas reales produce
9701
- * un costo por venta de 0 — es decir, todo verde y veredicto `scale` para
9702
- * un producto del que no sabemos nada. Se degrada en vez de calcular.
9703
- */
9704
- "no_campaign_linked",
9705
- /** La cuenta de anuncios respondió pero no reportó su moneda. Nunca se asume. */
9706
- "currency_unknown",
9707
- /** La moneda de la cuenta de anuncios difiere de la del país costeado. No se convierte. */
9708
- "currency_mismatch",
9709
- /** El proyecto nunca se costeó: no hay break-even contra el cual comparar. */
9710
- "no_costing",
9711
- /** Alguna fuente de gasto publicitario falló (campaña inaccesible o error tras reintentos). */
9712
- "pauta_pendiente",
9713
- /**
9714
- * El producto está en su control pero YA NO existe en la tienda (lo borró o
9715
- * lo archivó). No es «sin datos»: no hay precio que leer y nunca va a
9716
- * haberlo, así que la única salida es quitarlo del control. Sin este valor
9717
- * la fila se degradaba como «falta costeo» y la mandaba a llenar un
9718
- * formulario que no arregla nada.
9719
- */
9720
- "product_missing"
9721
- ]);
9722
- var DailyControlLightSchema = z46.enum([
9723
- "green",
9724
- "amber",
9725
- "red",
9726
- "none"
9727
- ]);
9728
- var DailyControlVerdictSchema = z46.enum([
9729
- "scale",
9730
- "hold",
9731
- "kill",
9732
- "insufficient_sample"
9733
- ]);
9734
- var DailyControlSourceSchema = z46.enum([
9735
- "shopify",
9736
- "shopify_settling",
9737
- "user",
9738
- "estimated"
9739
- ]);
9740
- var DailyControlDaySchema = z46.object({
9741
- date: z46.string(),
9742
- orders: z46.number(),
9743
- cancelledOrders: z46.number(),
9744
- /** Medido por Shopify (`cancelledAt`) o estimado con la tasa del costeo — ver `cancelledSource`. */
9745
- cancelledSource: DailyControlSourceSchema,
9397
+ // ../../packages/types/src/internal/deep-freeze.ts
9398
+ function deepFreeze(value) {
9399
+ if (value !== null && typeof value === "object") {
9400
+ for (const nested of Object.values(value)) {
9401
+ deepFreeze(nested);
9402
+ }
9403
+ Object.freeze(value);
9404
+ }
9405
+ return value;
9406
+ }
9407
+ __name(deepFreeze, "deepFreeze");
9408
+
9409
+ // ../../packages/types/src/productmaker-capability-model-guidance.ts
9410
+ var CAPABILITY_MODEL_GUIDANCE = {
9411
+ approve_video_script: {
9412
+ summary: "Aprueba el guion del video (con o sin ediciones).",
9413
+ useWhen: [
9414
+ "aprueba el guion del video",
9415
+ "la persona aprueba el guion del video, con o sin cambios"
9416
+ ],
9417
+ avoidWhen: [
9418
+ "la persona quiere regenerar por completo el guion"
9419
+ ],
9420
+ defaults: [
9421
+ "conserva las escenas que la persona no cambi\xF3"
9422
+ ],
9423
+ disclosures: [
9424
+ "la aprobaci\xF3n inicia render pagado y requiere confirmaci\xF3n expl\xEDcita"
9425
+ ]
9426
+ },
9427
+ check_meta_media_upload: {
9428
+ summary: "Comprueba si los medios preparados ya est\xE1n disponibles.",
9429
+ useWhen: [
9430
+ "ya termin\xE9 de subir los archivos de Meta",
9431
+ "la persona indica que termin\xF3 de preparar sus medios para Meta"
9432
+ ],
9433
+ avoidWhen: [
9434
+ "todav\xEDa no existe una preparaci\xF3n de medios en curso"
9435
+ ],
9436
+ defaults: [
9437
+ "si a\xFAn no est\xE1n disponibles, espera sin crear otra preparaci\xF3n"
9438
+ ],
9439
+ disclosures: []
9440
+ },
9441
+ check_media_upload: {
9442
+ summary: "Comprueba si la preparaci\xF3n segura de medios termin\xF3.",
9443
+ useWhen: [
9444
+ "la persona indica que termin\xF3 de preparar sus medios"
9445
+ ],
9446
+ avoidWhen: [
9447
+ "todav\xEDa no existe una preparaci\xF3n de medios en curso"
9448
+ ],
9449
+ defaults: [
9450
+ "si a\xFAn no est\xE1n disponibles, espera sin crear otra preparaci\xF3n"
9451
+ ],
9452
+ disclosures: []
9453
+ },
9454
+ cost_dropshipping_project: {
9455
+ summary: "Guarda el costeo de un proyecto guiado.",
9456
+ useWhen: [
9457
+ "calcula el margen de mi proyecto dropshipping",
9458
+ "la persona pide calcular el margen de su proyecto de dropshipping"
9459
+ ],
9460
+ avoidWhen: [
9461
+ "la persona s\xF3lo quiere estimar un producto fuera de un proyecto"
9462
+ ],
9463
+ defaults: [
9464
+ "recalcula el resultado can\xF3nico con los datos entregados"
9465
+ ],
9466
+ disclosures: [
9467
+ "modifica el proyecto y requiere confirmaci\xF3n expl\xEDcita",
9468
+ "pide el costo real del proveedor y no lo inventes"
9469
+ ]
9470
+ },
9471
+ cost_product: {
9472
+ summary: "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en cualquiera de los pa\xEDses disponibles en ProductMaker.",
9473
+ useWhen: [
9474
+ "cu\xE1nto cuesta este producto",
9475
+ "la persona pregunta cu\xE1nto cobrar, la rentabilidad o el margen de un producto"
9476
+ ],
9477
+ avoidWhen: [
9478
+ "la persona quiere guardar el costeo de un proyecto guiado"
9479
+ ],
9480
+ defaults: [
9481
+ "usa valores gu\xEDa editables salvo el costo real del proveedor"
9482
+ ],
9483
+ disclosures: [
9484
+ "es una consulta gratuita y no crea tareas"
9485
+ ]
9486
+ },
9487
+ create_creative_variant: {
9488
+ summary: "Crea un video, imagen o secci\xF3n de landing adicional dentro de una tarea existente.",
9489
+ useWhen: [
9490
+ "crea una variante de imagen",
9491
+ "la persona pide una variante creativa de una tarea existente"
9492
+ ],
9493
+ avoidWhen: [
9494
+ "la persona todav\xEDa no tiene una tarea creativa de origen"
9495
+ ],
9496
+ defaults: [
9497
+ "reutiliza el producto y referencias de la tarea de origen"
9498
+ ],
9499
+ disclosures: [
9500
+ "es trabajo pagado; cotiza y espera confirmaci\xF3n expl\xEDcita"
9501
+ ]
9502
+ },
9503
+ create_product_task: {
9504
+ summary: "Crea una tarea autopilot combinada.",
9505
+ useWhen: [
9506
+ "crea una landing y un video",
9507
+ "la persona pide expl\xEDcitamente al menos dos entregables entre landing, video e im\xE1genes creativas"
9508
+ ],
9509
+ avoidWhen: [
9510
+ "la persona pide un solo entregable; usa su generador dedicado"
9511
+ ],
9512
+ defaults: [
9513
+ "si la selecci\xF3n de resultados se omite, genera landing, video e im\xE1genes creativas en una misma tarea"
9514
+ ],
9515
+ disclosures: [
9516
+ "debes resumir todos los resultados, cotizar y esperar confirmaci\xF3n expl\xEDcita"
9517
+ ]
9518
+ },
9519
+ create_service_video: {
9520
+ summary: "Crea un video para un servicio desde su landing.",
9521
+ useWhen: [
9522
+ "crea un video para mi servicio",
9523
+ "la persona pide un video para promocionar un servicio"
9524
+ ],
9525
+ avoidWhen: [
9526
+ "la persona pide un video de producto o un video libre"
9527
+ ],
9528
+ defaults: [
9529
+ "usa la landing del servicio como contexto principal"
9530
+ ],
9531
+ disclosures: [
9532
+ "es trabajo pagado y requiere confirmaci\xF3n expl\xEDcita"
9533
+ ]
9534
+ },
9535
+ edit_image_creative: {
9536
+ summary: "Corrige UNA imagen ya generada de una tarea de creativos, sin regenerar el set completo.",
9537
+ useWhen: [
9538
+ "edita esta imagen creativa",
9539
+ "la persona pide editar una imagen creativa concreta"
9540
+ ],
9541
+ avoidWhen: [
9542
+ "la persona pide regenerar todas las im\xE1genes"
9543
+ ],
9544
+ defaults: [
9545
+ "modifica \xFAnicamente la imagen elegida"
9546
+ ],
9547
+ disclosures: [
9548
+ "es trabajo pagado y requiere confirmaci\xF3n expl\xEDcita"
9549
+ ]
9550
+ },
9551
+ edit_task_draft: {
9552
+ summary: "Edita el draft de un angle de marketing antes de la generaci\xF3n.",
9553
+ useWhen: [
9554
+ "edita el borrador del \xE1ngulo creativo",
9555
+ "la persona pide cambiar el borrador de un \xE1ngulo creativo"
9556
+ ],
9557
+ avoidWhen: [
9558
+ "la generaci\xF3n ya termin\xF3"
9559
+ ],
9560
+ defaults: [
9561
+ "conserva los campos que la persona no cambi\xF3"
9562
+ ],
9563
+ disclosures: [
9564
+ "modifica el estado de la tarea y requiere confirmaci\xF3n expl\xEDcita"
9565
+ ]
9566
+ },
9567
+ estimate_creative_variant: {
9568
+ summary: "Calcula el costo de crear una variante desde una tarea existente, sin consumir cr\xE9ditos.",
9569
+ useWhen: [
9570
+ "cu\xE1nto cuesta una variante creativa",
9571
+ "la persona pregunta cu\xE1nto cuesta una variante creativa"
9572
+ ],
9573
+ avoidWhen: [
9574
+ "la persona ya confirm\xF3 que quiere crear la variante cotizada"
9575
+ ],
9576
+ defaults: [
9577
+ "estima con la configuraci\xF3n real de la tarea de origen"
9578
+ ],
9579
+ disclosures: [
9580
+ "la cotizaci\xF3n es gratuita y no crea la variante"
9581
+ ]
9582
+ },
9583
+ find_winning_products: {
9584
+ summary: "Busca productos ganadores de dropshipping para un pa\xEDs y categor\xEDa, y explica POR QU\xC9 la IA eligi\xF3 cada uno (razones, riesgos y evidencia real de anuncios en Meta Ads).",
9585
+ useWhen: [
9586
+ "investiga productos ganadores para dropshipping",
9587
+ "la persona pide investigar productos ganadores para dropshipping"
9588
+ ],
9589
+ avoidWhen: [
9590
+ "ya existe una investigaci\xF3n pagada \xFAtil para el mismo objetivo"
9591
+ ],
9592
+ defaults: [
9593
+ "presenta razones, riesgos y evidencia por producto"
9594
+ ],
9595
+ disclosures: [
9596
+ "cuesta 2000 cr\xE9ditos y requiere aceptaci\xF3n expl\xEDcita"
9597
+ ]
9598
+ },
9599
+ generate_free_video: {
9600
+ summary: "Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia.",
9601
+ useWhen: [
9602
+ "genera un video libre creativo",
9603
+ "la persona pide un video libre con un prompt o guion espec\xEDfico"
9604
+ ],
9605
+ avoidWhen: [
9606
+ "la persona pide un creativo de producto o de servicio"
9607
+ ],
9608
+ defaults: [
9609
+ "respeta el prompt o guion entregado"
9610
+ ],
9611
+ disclosures: [
9612
+ "es trabajo pagado; cotiza y espera confirmaci\xF3n expl\xEDcita"
9613
+ ]
9614
+ },
9615
+ generate_image_creatives: {
9616
+ summary: "Genera variantes de imagen creativa.",
9617
+ useWhen: [
9618
+ "genera im\xE1genes creativas del producto",
9619
+ "la persona pide im\xE1genes creativas de un producto"
9620
+ ],
9621
+ avoidWhen: [
9622
+ "la persona pide una sola edici\xF3n sobre una imagen existente"
9623
+ ],
9624
+ defaults: [
9625
+ "genera s\xF3lo im\xE1genes creativas"
9626
+ ],
9627
+ disclosures: [
9628
+ "es trabajo pagado; cotiza y espera confirmaci\xF3n expl\xEDcita"
9629
+ ]
9630
+ },
9631
+ generate_landing: {
9632
+ summary: "Genera una landing page cl\xE1sica basada en im\xE1genes, lista para Shopify.",
9633
+ useWhen: [
9634
+ "genera una landing creativa",
9635
+ "quiero crear una landing page",
9636
+ "quiero crear una p\xE1gina de producto",
9637
+ "la persona pide \xFAnicamente una landing o p\xE1gina de producto"
9638
+ ],
9639
+ avoidWhen: [
9640
+ "la persona pide un paquete combinado de dos o m\xE1s entregables"
9641
+ ],
9642
+ defaults: [
9643
+ "genera \xFAnicamente la landing"
9644
+ ],
9645
+ disclosures: [
9646
+ "es trabajo pagado y requiere cotizaci\xF3n y confirmaci\xF3n expl\xEDcita"
9647
+ ]
9648
+ },
9649
+ generate_video_creative: {
9650
+ summary: "Genera entre 1 y 3 videos creativos independientes dentro de un mismo task.",
9651
+ useWhen: [
9652
+ "genera un video creativo",
9653
+ "la persona pide un video creativo de producto"
9654
+ ],
9655
+ avoidWhen: [
9656
+ "la persona pide un paquete combinado o un video libre"
9657
+ ],
9658
+ defaults: [
9659
+ "genera \xFAnicamente el video creativo"
9660
+ ],
9661
+ disclosures: [
9662
+ "es trabajo pagado; cotiza y espera confirmaci\xF3n expl\xEDcita"
9663
+ ]
9664
+ },
9665
+ get_meta_campaign_status: {
9666
+ summary: "Consulta el estado actual + m\xE9tricas lifetime (spend, impressions, clicks, reach) de una campa\xF1a Meta publicada.",
9667
+ useWhen: [
9668
+ "consulta el estado de la campa\xF1a Meta",
9669
+ "la persona pregunta por el estado o rendimiento de una campa\xF1a Meta"
9670
+ ],
9671
+ avoidWhen: [
9672
+ "la campa\xF1a a\xFAn no fue publicada"
9673
+ ],
9674
+ defaults: [
9675
+ "devuelve m\xE9tricas acumuladas sin modificar la campa\xF1a"
9676
+ ],
9677
+ disclosures: [
9678
+ "es una consulta de solo lectura"
9679
+ ]
9680
+ },
9681
+ get_daily_control: {
9682
+ summary: "Muestra el control diario de rentabilidad por producto con m\xE9tricas, sem\xE1foro y veredicto.",
9683
+ useWhen: [
9684
+ "revisa la rentabilidad diaria de mis productos",
9685
+ "la persona quiere decidir si escalar, mantener o detener un producto"
9686
+ ],
9687
+ avoidWhen: [
9688
+ "la persona quiere modificar una campa\xF1a publicitaria"
9689
+ ],
9690
+ defaults: [
9691
+ "consulta los \xFAltimos tres d\xEDas cerrados"
9692
+ ],
9693
+ disclosures: [
9694
+ "es una consulta de solo lectura y distingue cifras medidas de estimadas"
9695
+ ]
9696
+ },
9697
+ get_product_research_run: {
9698
+ summary: "Devuelve el reporte completo de una investigaci\xF3n de productos ganadores: score, veredicto, confianza, razones, riesgos y evidencia de Meta Ads por producto.",
9699
+ useWhen: [
9700
+ "consulta el estado de la investigaci\xF3n",
9701
+ "la persona pide consultar una investigaci\xF3n existente"
9702
+ ],
9703
+ avoidWhen: [
9704
+ "la persona quiere iniciar una investigaci\xF3n nueva"
9705
+ ],
9706
+ defaults: [
9707
+ "devuelve el reporte completo disponible"
9708
+ ],
9709
+ disclosures: [
9710
+ "es una consulta gratuita y de solo lectura"
9711
+ ]
9712
+ },
9713
+ get_task_status: {
9714
+ summary: "Devuelve estado y entregables.",
9715
+ useWhen: [
9716
+ "consulta el estado de mi tarea",
9717
+ "la persona pide consultar el estado o resultado de una tarea"
9718
+ ],
9719
+ avoidWhen: [
9720
+ "todav\xEDa no existe una tarea identificable"
9721
+ ],
9722
+ defaults: [
9723
+ "contin\xFAa consultando hasta una revisi\xF3n o estado terminal cuando corresponda"
9724
+ ],
9725
+ disclosures: [
9726
+ "es una consulta de solo lectura"
9727
+ ]
9728
+ },
9729
+ get_tiktok_campaign_status: {
9730
+ summary: "Consulta el estado actual + reporte lifetime (spend, impressions, clicks, reach) de una campa\xF1a TikTok publicada.",
9731
+ useWhen: [
9732
+ "consulta el estado de la campa\xF1a TikTok",
9733
+ "la persona pregunta por el estado o rendimiento de una campa\xF1a TikTok"
9734
+ ],
9735
+ avoidWhen: [
9736
+ "la campa\xF1a a\xFAn no fue publicada"
9737
+ ],
9738
+ defaults: [
9739
+ "devuelve m\xE9tricas acumuladas sin modificar la campa\xF1a"
9740
+ ],
9741
+ disclosures: [
9742
+ "es una consulta de solo lectura"
9743
+ ]
9744
+ },
9745
+ get_video_script: {
9746
+ summary: "Lee el guion del video pendiente de revisi\xF3n.",
9747
+ useWhen: [
9748
+ "consulta el guion del video",
9749
+ "la persona pide consultar el guion de un video en revisi\xF3n"
9750
+ ],
9751
+ avoidWhen: [
9752
+ "la tarea no tiene revisi\xF3n de guion pendiente"
9753
+ ],
9754
+ defaults: [
9755
+ "conserva el contenido del guion sin modificarlo"
9756
+ ],
9757
+ disclosures: [
9758
+ "es una consulta de solo lectura"
9759
+ ]
9760
+ },
9761
+ guide_dropshipping_launch: {
9762
+ summary: "Gu\xEDa read-only de un recorrido COMPLETO de dropshipping, desde producto/costeo hasta creativos, tienda y publicidad.",
9763
+ useWhen: [
9764
+ "ay\xFAdame con mi proyecto dropshipping",
9765
+ "la persona pide ayuda para avanzar un proyecto completo de dropshipping"
9766
+ ],
9767
+ avoidWhen: [
9768
+ "la persona pide una operaci\xF3n puntual"
9769
+ ],
9770
+ defaults: [
9771
+ "propone el siguiente paso sin ejecutar cambios"
9772
+ ],
9773
+ disclosures: [
9774
+ "es una gu\xEDa de solo lectura y no activa campa\xF1as"
9775
+ ]
9776
+ },
9777
+ list_actors: {
9778
+ summary: "Lista los personajes guardados del usuario (personas creadas con IA que pueden protagonizar los videos e im\xE1genes).",
9779
+ useWhen: [
9780
+ "lista mis personajes disponibles",
9781
+ "la persona pide ver sus personajes disponibles"
9782
+ ],
9783
+ avoidWhen: [
9784
+ "la persona quiere crear un personaje nuevo"
9785
+ ],
9786
+ defaults: [
9787
+ "muestra \xFAnicamente personajes listos para usar"
9788
+ ],
9789
+ disclosures: [
9790
+ "es una consulta de solo lectura"
9791
+ ]
9792
+ },
9793
+ link_campaign_to_project: {
9794
+ summary: "Vincula una campa\xF1a publicitaria existente con un proyecto para incluir su gasto en el control diario.",
9795
+ useWhen: [
9796
+ "vincula esta campa\xF1a con mi proyecto",
9797
+ "la persona quiere medir una campa\xF1a creada fuera de ProductMaker"
9798
+ ],
9799
+ avoidWhen: [
9800
+ "la campa\xF1a ya est\xE1 vinculada al proyecto correcto"
9801
+ ],
9802
+ defaults: [
9803
+ "verifica la propiedad de la campa\xF1a antes de vincularla"
9804
+ ],
9805
+ disclosures: [
9806
+ "modifica el proyecto y requiere confirmaci\xF3n expl\xEDcita; no modifica la campa\xF1a publicitaria"
9807
+ ]
9808
+ },
9809
+ list_meta_ad_accounts: {
9810
+ summary: "Lista las cuentas publicitarias de Meta conectadas.",
9811
+ useWhen: [
9812
+ "lista mis cuentas publicitarias de Meta",
9813
+ "la persona pide ver sus cuentas publicitarias de Meta"
9814
+ ],
9815
+ avoidWhen: [
9816
+ "la persona necesita elegir adem\xE1s portfolio y p\xE1gina"
9817
+ ],
9818
+ defaults: [
9819
+ "muestra \xFAnicamente cuentas conectadas"
9820
+ ],
9821
+ disclosures: [
9822
+ "es una consulta de solo lectura"
9823
+ ]
9824
+ },
9825
+ list_meta_destinations: {
9826
+ summary: "Lista los destinos de publicaci\xF3n de Meta del usuario: Business portfolios, cuentas publicitarias dentro de cada portfolio y Facebook Pages disponibles.",
9827
+ useWhen: [
9828
+ "lista mis destinos de Meta",
9829
+ "la persona necesita elegir un destino antes de publicar en Meta"
9830
+ ],
9831
+ avoidWhen: [
9832
+ "la persona s\xF3lo pregunta por el estado de una campa\xF1a"
9833
+ ],
9834
+ defaults: [
9835
+ "presenta las opciones para que la persona elija"
9836
+ ],
9837
+ disclosures: [
9838
+ "es una consulta de solo lectura y no elige silenciosamente"
9839
+ ]
9840
+ },
9841
+ list_product_research_runs: {
9842
+ summary: "Lista las investigaciones de productos ganadores ya pagadas por el usuario, de la m\xE1s reciente a la m\xE1s antigua.",
9843
+ useWhen: [
9844
+ "lista mis investigaciones de productos",
9845
+ "la persona pide ver sus investigaciones de productos"
9846
+ ],
9847
+ avoidWhen: [
9848
+ "la persona ya eligi\xF3 una investigaci\xF3n concreta para consultar"
9849
+ ],
9850
+ defaults: [
9851
+ "ordena de la m\xE1s reciente a la m\xE1s antigua"
9852
+ ],
9853
+ disclosures: [
9854
+ "es una consulta gratuita y de solo lectura"
9855
+ ]
9856
+ },
9857
+ list_shopify_products: {
9858
+ summary: "Busca productos en una tienda Shopify conectada para elegir el target de una republicaci\xF3n.",
9859
+ useWhen: [
9860
+ "lista los productos de mi tienda Shopify",
9861
+ "la persona pide listar productos de su tienda Shopify"
9862
+ ],
9863
+ avoidWhen: [
9864
+ "la persona s\xF3lo necesita listar sus tiendas conectadas"
9865
+ ],
9866
+ defaults: [
9867
+ "no modifica ning\xFAn producto"
9868
+ ],
9869
+ disclosures: [
9870
+ "es una consulta de solo lectura"
9871
+ ]
9872
+ },
9873
+ list_shopify_shops: {
9874
+ summary: "Lista las tiendas Shopify conectadas por este usuario.",
9875
+ useWhen: [
9876
+ "lista mis tiendas Shopify conectadas",
9877
+ "la persona pide ver sus tiendas Shopify conectadas"
9878
+ ],
9879
+ avoidWhen: [
9880
+ "la persona ya eligi\xF3 una tienda y busca un producto"
9881
+ ],
9882
+ defaults: [
9883
+ "muestra \xFAnicamente tiendas conectadas"
9884
+ ],
9885
+ disclosures: [
9886
+ "es una consulta de solo lectura"
9887
+ ]
9888
+ },
9889
+ list_tasks: {
9890
+ summary: "Lista de forma read-only las tareas recientes del usuario.",
9891
+ useWhen: [
9892
+ "lista mis tareas recientes",
9893
+ "la persona pide listar o buscar sus tareas recientes"
9894
+ ],
9895
+ avoidWhen: [
9896
+ "la persona ya identific\xF3 una tarea y pide su estado"
9897
+ ],
9898
+ defaults: [
9899
+ "muestra las tareas recientes sin modificarlas"
9900
+ ],
9901
+ disclosures: [
9902
+ "es una consulta de solo lectura"
9903
+ ]
9904
+ },
9905
+ list_tiktok_advertisers: {
9906
+ summary: "Lista las cuentas de TikTok Ads conectadas.",
9907
+ useWhen: [
9908
+ "lista mis anunciantes TikTok conectados",
9909
+ "la persona pide ver sus anunciantes TikTok conectados"
9910
+ ],
9911
+ avoidWhen: [
9912
+ "la persona s\xF3lo pregunta por el estado de una campa\xF1a"
9913
+ ],
9914
+ defaults: [
9915
+ "muestra \xFAnicamente cuentas conectadas"
9916
+ ],
9917
+ disclosures: [
9918
+ "es una consulta de solo lectura"
9919
+ ]
9920
+ },
9921
+ publish_creatives_to_meta: {
9922
+ summary: "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta 10 creativos.",
9923
+ useWhen: [
9924
+ "publica estos creativos en Meta",
9925
+ "la persona pide publicar varios creativos juntos en Meta"
9926
+ ],
9927
+ avoidWhen: [
9928
+ "la persona quiere publicar creativos de una sola tarea"
9929
+ ],
9930
+ defaults: [
9931
+ "crea la campa\xF1a pausada con los creativos confirmados"
9932
+ ],
9933
+ disclosures: [
9934
+ "requiere destino y confirmaci\xF3n expl\xEDcitos; el gasto publicitario es externo"
9935
+ ]
9936
+ },
9937
+ publish_to_meta: {
9938
+ summary: "Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker.",
9939
+ useWhen: [
9940
+ "publica la campa\xF1a en Meta",
9941
+ "la persona pide publicar en Meta los creativos de una tarea"
9942
+ ],
9943
+ avoidWhen: [
9944
+ "la persona quiere agrupar creativos de varias tareas"
9945
+ ],
9946
+ defaults: [
9947
+ "crea la campa\xF1a pausada"
9948
+ ],
9949
+ disclosures: [
9950
+ "requiere destino y confirmaci\xF3n expl\xEDcitos; el gasto publicitario es externo"
9951
+ ]
9952
+ },
9953
+ publish_to_shopify: {
9954
+ summary: "Publica o actualiza la landing en una tienda Shopify conectada.",
9955
+ useWhen: [
9956
+ "publica el producto en Shopify",
9957
+ "la persona pide publicar un producto o landing en Shopify"
9958
+ ],
9959
+ avoidWhen: [
9960
+ "la tarea todav\xEDa no est\xE1 completa"
9961
+ ],
9962
+ defaults: [
9963
+ "actualiza la publicaci\xF3n existente de esa tarea o crea una nueva"
9964
+ ],
9965
+ disclosures: [
9966
+ "requiere tienda, variantes y confirmaci\xF3n expl\xEDcitas"
9967
+ ]
9968
+ },
9969
+ publish_to_tiktok: {
9970
+ summary: "Crea una campa\xF1a de TikTok Ads desde la tarea.",
9971
+ useWhen: [
9972
+ "publica la campa\xF1a en TikTok",
9973
+ "la persona pide publicar una campa\xF1a en TikTok"
9974
+ ],
9975
+ avoidWhen: [
9976
+ "la tarea todav\xEDa no tiene creativos publicables"
9977
+ ],
9978
+ defaults: [
9979
+ "crea la campa\xF1a pausada"
9980
+ ],
9981
+ disclosures: [
9982
+ "requiere anunciante y confirmaci\xF3n expl\xEDcitos; el gasto publicitario es externo"
9983
+ ]
9984
+ },
9985
+ regenerate_video_script: {
9986
+ summary: "Re-rolea (regenera) el guion del video pendiente de revisi\xF3n.",
9987
+ useWhen: [
9988
+ "regenera el guion del video creativo",
9989
+ "la persona pide regenerar el guion completo del video"
9990
+ ],
9991
+ avoidWhen: [
9992
+ "la persona quiere editar s\xF3lo algunas escenas"
9993
+ ],
9994
+ defaults: [
9995
+ "mantiene la tarea y reemplaza el guion pendiente"
9996
+ ],
9997
+ disclosures: [
9998
+ "consume cr\xE9ditos y requiere confirmaci\xF3n expl\xEDcita salvo recuperaci\xF3n necesaria"
9999
+ ]
10000
+ },
10001
+ report_daily_returns: {
10002
+ summary: "Registra el n\xFAmero real de pedidos devueltos de un producto en un d\xEDa concreto.",
10003
+ useWhen: [
10004
+ "registra las devoluciones reales de ayer",
10005
+ "la persona conoce el n\xFAmero real de devoluciones de un producto"
10006
+ ],
10007
+ avoidWhen: [
10008
+ "la persona no conoce el dato exacto"
10009
+ ],
10010
+ defaults: [
10011
+ "reemplaza la estimaci\xF3n de ese d\xEDa con el dato informado"
10012
+ ],
10013
+ disclosures: [
10014
+ "modifica el control diario y requiere confirmaci\xF3n expl\xEDcita; nunca inventes el n\xFAmero"
10015
+ ]
10016
+ },
10017
+ request_meta_media_upload: {
10018
+ summary: "Prepara im\xE1genes o videos del usuario para una publicaci\xF3n en Meta.",
10019
+ useWhen: [
10020
+ "quiero subir una imagen a Meta",
10021
+ "la persona necesita preparar im\xE1genes o videos para Meta"
10022
+ ],
10023
+ avoidWhen: [
10024
+ "los medios ya est\xE1n disponibles para la publicaci\xF3n"
10025
+ ],
10026
+ defaults: [
10027
+ "prepara s\xF3lo los medios solicitados"
10028
+ ],
10029
+ disclosures: [
10030
+ "no genera creativos ni publica campa\xF1as"
10031
+ ]
10032
+ },
10033
+ request_media_upload: {
10034
+ summary: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario.",
10035
+ useWhen: [
10036
+ "la persona necesita preparar im\xE1genes o videos para otra capacidad"
10037
+ ],
10038
+ avoidWhen: [
10039
+ "los medios ya est\xE1n disponibles para la capacidad solicitada"
10040
+ ],
10041
+ defaults: [
10042
+ "prepara s\xF3lo los medios solicitados"
10043
+ ],
10044
+ disclosures: [
10045
+ "no genera creativos, publica ni consume cr\xE9ditos"
10046
+ ]
10047
+ },
10048
+ resolve_pending_action: {
10049
+ summary: "Resuelve la decisi\xF3n pendiente de una tarea usando la respuesta o delegaci\xF3n expl\xEDcita de la persona.",
10050
+ useWhen: [
10051
+ "confirma la acci\xF3n pendiente del video",
10052
+ "la persona responde o delega una decisi\xF3n pendiente"
10053
+ ],
10054
+ avoidWhen: [
10055
+ "todav\xEDa no existe una decisi\xF3n pendiente o la persona no respondi\xF3"
10056
+ ],
10057
+ defaults: [
10058
+ "aplica \xFAnicamente la decisi\xF3n expresada"
10059
+ ],
10060
+ disclosures: [
10061
+ "puede iniciar trabajo pagado y requiere autoridad expl\xEDcita"
10062
+ ]
10063
+ },
10064
+ start_dropshipping_project: {
10065
+ summary: "Inicia o reanuda un proyecto opcional de dropshipping.",
10066
+ useWhen: [
10067
+ "inicia un proyecto dropshipping",
10068
+ "la persona pide iniciar o continuar un proyecto de dropshipping"
10069
+ ],
10070
+ avoidWhen: [
10071
+ "la persona s\xF3lo pide una operaci\xF3n puntual"
10072
+ ],
10073
+ defaults: [
10074
+ "conserva el progreso existente cuando corresponde"
10075
+ ],
10076
+ disclosures: [
10077
+ "modifica el proyecto, no consume cr\xE9ditos y requiere confirmaci\xF3n expl\xEDcita"
10078
+ ]
10079
+ }
10080
+ };
10081
+ var PRODUCTMAKER_CAPABILITY_MODEL_GUIDANCE = deepFreeze(CAPABILITY_MODEL_GUIDANCE);
10082
+ function getProductMakerCapabilityModelGuidance(name) {
10083
+ return PRODUCTMAKER_CAPABILITY_MODEL_GUIDANCE[name];
10084
+ }
10085
+ __name(getProductMakerCapabilityModelGuidance, "getProductMakerCapabilityModelGuidance");
10086
+
10087
+ // ../../packages/types/src/daily-control.ts
10088
+ import { z as z46 } from "zod";
10089
+ var DailyControlStatusSchema = z46.enum([
10090
+ "ok",
10091
+ /** Shopify no puede leerse: falta scope `read_orders` o la tienda no está conectada. */
10092
+ "sales_unavailable",
10093
+ /** La tienda no expone su zona horaria: sin ella no se puede agrupar por día. */
10094
+ "timezone_unavailable",
10095
+ /** El usuario nunca conectó la plataforma de anuncios (Meta o TikTok). */
10096
+ "ads_not_connected",
10097
+ /**
10098
+ * El proyecto no tiene NINGUNA campaña vinculada (ni Meta ni TikTok). Sin
10099
+ * campaña no hay gasto que leer, y "gasto 0" contra ventas reales produce
10100
+ * un costo por venta de 0 — es decir, todo verde y veredicto `scale` para
10101
+ * un producto del que no sabemos nada. Se degrada en vez de calcular.
10102
+ */
10103
+ "no_campaign_linked",
10104
+ /** La cuenta de anuncios respondió pero no reportó su moneda. Nunca se asume. */
10105
+ "currency_unknown",
10106
+ /** La moneda de la cuenta de anuncios difiere de la del país costeado. No se convierte. */
10107
+ "currency_mismatch",
10108
+ /** El proyecto nunca se costeó: no hay break-even contra el cual comparar. */
10109
+ "no_costing",
10110
+ /** Alguna fuente de gasto publicitario falló (campaña inaccesible o error tras reintentos). */
10111
+ "pauta_pendiente",
10112
+ /**
10113
+ * El producto está en su control pero YA NO existe en la tienda (lo borró o
10114
+ * lo archivó). No es «sin datos»: no hay precio que leer y nunca va a
10115
+ * haberlo, así que la única salida es quitarlo del control. Sin este valor
10116
+ * la fila se degradaba como «falta costeo» y la mandaba a llenar un
10117
+ * formulario que no arregla nada.
10118
+ */
10119
+ "product_missing"
10120
+ ]);
10121
+ var DailyControlLightSchema = z46.enum([
10122
+ "green",
10123
+ "amber",
10124
+ "red",
10125
+ "none"
10126
+ ]);
10127
+ var DailyControlVerdictSchema = z46.enum([
10128
+ "scale",
10129
+ "hold",
10130
+ "kill",
10131
+ "insufficient_sample"
10132
+ ]);
10133
+ var DailyControlSourceSchema = z46.enum([
10134
+ "shopify",
10135
+ "shopify_settling",
10136
+ "user",
10137
+ "estimated"
10138
+ ]);
10139
+ var DailyControlDaySchema = z46.object({
10140
+ date: z46.string(),
10141
+ orders: z46.number(),
10142
+ cancelledOrders: z46.number(),
10143
+ /** Medido por Shopify (`cancelledAt`) o estimado con la tasa del costeo — ver `cancelledSource`. */
10144
+ cancelledSource: DailyControlSourceSchema,
9746
10145
  returnedOrders: z46.number(),
9747
10146
  /** Reportado por el usuario o estimado con la tasa del costeo — ver `returnedSource`. */
9748
10147
  returnedSource: DailyControlSourceSchema,
@@ -10054,6 +10453,7 @@ var WHATSAPP_ONLY_CHANNEL_EXPOSURE = {
10054
10453
  function defineCapability(name, schemaHash, confirmation, routingDomain, effects, inputSlots, consumes, produces, channelExposure = SHARED_CHANNEL_EXPOSURE, conversationInputs) {
10055
10454
  return {
10056
10455
  name,
10456
+ modelGuidance: getProductMakerCapabilityModelGuidance(name),
10057
10457
  schemaHash,
10058
10458
  confirmation,
10059
10459
  routingDomain,
@@ -10094,6 +10494,7 @@ function slot(name, required, mcp2, whatsapp = mcp2, source = "user") {
10094
10494
  __name(slot, "slot");
10095
10495
  var SERVER_SELECTED_OR_ARTIFACT_FIELDS = /* @__PURE__ */ new Set([
10096
10496
  "actionKind",
10497
+ "accountId",
10097
10498
  "actorId",
10098
10499
  "adAccountId",
10099
10500
  "advertiserId",
@@ -10101,6 +10502,7 @@ var SERVER_SELECTED_OR_ARTIFACT_FIELDS = /* @__PURE__ */ new Set([
10101
10502
  "campaignId",
10102
10503
  "projectId",
10103
10504
  "pixelId",
10505
+ "provider",
10104
10506
  "runId",
10105
10507
  "sessionId",
10106
10508
  "shopDomain",
@@ -10116,16 +10518,6 @@ function whatsappFieldAuthority(field, source) {
10116
10518
  return SERVER_SELECTED_OR_ARTIFACT_FIELDS.has(field) ? "server_selected_or_artifact" : "user";
10117
10519
  }
10118
10520
  __name(whatsappFieldAuthority, "whatsappFieldAuthority");
10119
- function deepFreeze(value) {
10120
- if (value !== null && typeof value === "object") {
10121
- for (const nested of Object.values(value)) {
10122
- deepFreeze(nested);
10123
- }
10124
- Object.freeze(value);
10125
- }
10126
- return value;
10127
- }
10128
- __name(deepFreeze, "deepFreeze");
10129
10521
  var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
10130
10522
  defineCapability("approve_video_script", "bab794a9700a8d3f42b97f444a776e7ed4dcd833038c52588f951b50970ff3fa", "credit_consuming", "creative", [
10131
10523
  "credit_consuming",
@@ -10274,7 +10666,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
10274
10666
  ], [
10275
10667
  "creative_variant"
10276
10668
  ]),
10277
- defineCapability("create_product_task", "1e112848ec1433249e7add9994ca07b5322a743f7444808bd51b8257c3988366", "credit_consuming", "creative", [
10669
+ defineCapability("create_product_task", "ba700dbec8fb6454d55f996ae4567b9280256ae3b62fceb77fd729b9aa21425c", "credit_consuming", "creative", [
10278
10670
  "credit_consuming",
10279
10671
  "state_change"
10280
10672
  ], [
@@ -10325,7 +10717,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
10325
10717
  "videoResolution",
10326
10718
  "videoAngleCount",
10327
10719
  "angleNarratives",
10328
- "competitorScanEnabled"
10720
+ "competitorScanEnabled",
10721
+ "replicateWinnerWhenFound"
10329
10722
  ], [
10330
10723
  "videoPreset",
10331
10724
  "durationSeconds",
@@ -10334,7 +10727,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
10334
10727
  "videoResolution",
10335
10728
  "multiAngleVideos",
10336
10729
  "angleNarratives",
10337
- "competitorScanEnabled"
10730
+ "competitorScanEnabled",
10731
+ "replicateWinnerWhenFound"
10338
10732
  ]),
10339
10733
  slot("brand_direction", false, [
10340
10734
  "brandId",
@@ -10649,7 +11043,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
10649
11043
  fieldCompletion: "all"
10650
11044
  }
10651
11045
  ]),
10652
- defineCapability("generate_video_creative", "be67eb577c59070d8f77a0b783e4a6e9534ed917dda03d60200fbeb9365db66d", "credit_consuming", "creative", [
11046
+ defineCapability("generate_video_creative", "d99875f4e137c570510e2adac423e5419c3ed135d4505007d9bdfe30ad1b43d5", "credit_consuming", "creative", [
10653
11047
  "credit_consuming",
10654
11048
  "state_change"
10655
11049
  ], [
@@ -10679,7 +11073,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
10679
11073
  "features",
10680
11074
  "cta",
10681
11075
  "scriptNotes",
10682
- "competitorScanEnabled"
11076
+ "competitorScanEnabled",
11077
+ "replicateWinnerWhenFound"
10683
11078
  ], [
10684
11079
  "aspectRatio",
10685
11080
  "durationSeconds",
@@ -10691,7 +11086,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
10691
11086
  "scriptNotes",
10692
11087
  "pauseForVideoScriptReview",
10693
11088
  "pauseForAngleReview",
10694
- "competitorScanEnabled"
11089
+ "competitorScanEnabled",
11090
+ "replicateWinnerWhenFound"
10695
11091
  ]),
10696
11092
  slot("commercial_offer", false, [
10697
11093
  "price",
@@ -11133,7 +11529,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
11133
11529
  "campaign_link"
11134
11530
  ], [
11135
11531
  "daily_control_report"
11136
- ], MCP_ONLY_CHANNEL_EXPOSURE),
11532
+ ]),
11137
11533
  defineCapability("link_campaign_to_project", "42785a9781412e0ad7cf7095b7188f315dea4003002777d5a4b1b9cc79ea05e0", "state_change", "business", [
11138
11534
  "state_change"
11139
11535
  ], [
@@ -11153,7 +11549,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
11153
11549
  "dropshipping_project"
11154
11550
  ], [
11155
11551
  "campaign_link"
11156
- ], MCP_ONLY_CHANNEL_EXPOSURE),
11552
+ ]),
11157
11553
  defineCapability("report_daily_returns", "7130d216656cc29a47fc972b4edad5003a6653fca48d95052fbf2b561636e91a", "state_change", "business", [
11158
11554
  "state_change"
11159
11555
  ], [
@@ -11173,170 +11569,459 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
11173
11569
  "dropshipping_project"
11174
11570
  ], [
11175
11571
  "daily_control_report"
11176
- ], MCP_ONLY_CHANNEL_EXPOSURE)
11572
+ ])
11177
11573
  ]);
11178
11574
  var PRODUCTMAKER_CAPABILITY_NAMES = PRODUCTMAKER_CAPABILITY_REGISTRY.map((capability) => capability.name);
11179
11575
  var PRODUCTMAKER_MCP_CAPABILITY_REGISTRY = PRODUCTMAKER_CAPABILITY_REGISTRY.filter((capability) => capability.channelExposure.mcp === "direct");
11180
11576
  var PRODUCTMAKER_WHATSAPP_CAPABILITY_REGISTRY = PRODUCTMAKER_CAPABILITY_REGISTRY.filter((capability) => capability.channelExposure.whatsapp === "adapted");
11181
11577
  var PRODUCTMAKER_WHATSAPP_CAPABILITY_NAMES = PRODUCTMAKER_WHATSAPP_CAPABILITY_REGISTRY.map((capability) => capability.name);
11182
11578
 
11183
- // ../../packages/types/src/productmaker-chat-capabilities.ts
11184
- function toLegacyCapabilityCatalog(catalog) {
11185
- return catalog.map(({ name, confirmation, schemaHash }) => ({
11186
- name,
11187
- confirmation,
11188
- schemaHash
11189
- }));
11579
+ // ../../packages/types/src/productmaker-chat-capabilities.ts
11580
+ function toLegacyCapabilityCatalog(catalog) {
11581
+ return catalog.map(({ name, confirmation, schemaHash }) => ({
11582
+ name,
11583
+ confirmation,
11584
+ schemaHash
11585
+ }));
11586
+ }
11587
+ __name(toLegacyCapabilityCatalog, "toLegacyCapabilityCatalog");
11588
+ var PRODUCTMAKER_MCP_CAPABILITIES = toLegacyCapabilityCatalog(PRODUCTMAKER_MCP_CAPABILITY_REGISTRY);
11589
+ var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITY_REGISTRY.map((capability) => capability.name);
11590
+
11591
+ // ../../packages/types/src/actor.ts
11592
+ import { z as z48 } from "zod";
11593
+ var ActorStatusSchema = z48.enum([
11594
+ "draft",
11595
+ "generating",
11596
+ "ready",
11597
+ "failed"
11598
+ ]);
11599
+ var ACTOR_NAME_MAX_CHARS = 60;
11600
+ var ACTOR_DESCRIPTION_MAX_CHARS = 600;
11601
+ var ACTOR_CANDIDATE_COUNT = 4;
11602
+ var ActorGenderSchema = z48.enum([
11603
+ "female",
11604
+ "male",
11605
+ "non_binary"
11606
+ ]);
11607
+ var ActorSchema = z48.object({
11608
+ id: z48.string(),
11609
+ name: z48.string().min(1).max(ACTOR_NAME_MAX_CHARS),
11610
+ description: z48.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
11611
+ gender: ActorGenderSchema,
11612
+ voiceId: z48.string().nullable().optional(),
11613
+ brandId: z48.string().nullable().optional(),
11614
+ status: ActorStatusSchema,
11615
+ /**
11616
+ * A candidate-grid run is in flight RIGHT NOW.
11617
+ *
11618
+ * Separate from `status` because the two answer different questions:
11619
+ * `status` is the PUBLISHED state (a personaje being regenerated stays
11620
+ * `ready`, so it keeps serving the renders that already reference it), and
11621
+ * this flag is the live one the wizard polls. It is derived from a lock
11622
+ * timestamp with a TTL, so an orphaned run reads as `false` instead of
11623
+ * pinning the personaje forever.
11624
+ */
11625
+ generating: z48.boolean().optional(),
11626
+ photoUrl: z48.string().url().nullable().optional(),
11627
+ avatarUrl: z48.string().url().nullable().optional(),
11628
+ candidateUrls: z48.array(z48.string().url()).optional(),
11629
+ /** Indices of candidateUrls that failed the anatomy check (Task 6). */
11630
+ flaggedCandidateIndices: z48.array(z48.number().int()).optional()
11631
+ });
11632
+ var CreateActorRequest = z48.object({
11633
+ name: z48.string().min(1).max(ACTOR_NAME_MAX_CHARS),
11634
+ description: z48.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
11635
+ gender: ActorGenderSchema,
11636
+ voiceId: z48.string().optional(),
11637
+ brandId: z48.string().optional(),
11638
+ sourcePhotoKey: z48.string().optional()
11639
+ }).refine((v) => v.description.trim().length > 0 || !!v.sourcePhotoKey, {
11640
+ message: "Se necesita una descripci\xF3n o una foto"
11641
+ });
11642
+ var UpdateActorRequest = z48.object({
11643
+ name: z48.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
11644
+ description: z48.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
11645
+ gender: ActorGenderSchema.optional(),
11646
+ voiceId: z48.string().nullable().optional(),
11647
+ brandId: z48.string().nullable().optional()
11648
+ });
11649
+ var ApproveActorRequest = z48.object({
11650
+ candidateIndex: z48.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
11651
+ });
11652
+
11653
+ // ../../packages/types/src/referral-dashboard.ts
11654
+ import { z as z49 } from "zod";
11655
+ var REFERRAL_DASHBOARD_RAW_STATUSES = [
11656
+ "ACTIVE",
11657
+ "PENDING",
11658
+ "GRACE",
11659
+ "CANCELLED",
11660
+ "EXPIRED"
11661
+ ];
11662
+ var REFERRAL_DASHBOARD_STATES = [
11663
+ "active",
11664
+ "pending",
11665
+ "inactive",
11666
+ "no_plan"
11667
+ ];
11668
+ var REFERRAL_DASHBOARD_PLAN_KINDS = [
11669
+ "current",
11670
+ "last",
11671
+ "none"
11672
+ ];
11673
+ var ReferralDashboardRawStatusSchema = z49.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
11674
+ var ReferralDashboardStateSchema = z49.enum(REFERRAL_DASHBOARD_STATES);
11675
+ var ReferralDashboardPlanKindSchema = z49.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
11676
+ var ReferralDashboardStatusSchema = z49.enum([
11677
+ "all",
11678
+ "active",
11679
+ "pending",
11680
+ "inactive"
11681
+ ]).default("all");
11682
+ var ReferralDashboardPlanSchema = z49.string().trim().min(1).max(100).optional();
11683
+ var ReferralDashboardFiltersSchema = z49.object({
11684
+ status: ReferralDashboardStatusSchema,
11685
+ plan: ReferralDashboardPlanSchema
11686
+ });
11687
+ var ReferralDashboardSubscriptionSchema = z49.object({
11688
+ planName: z49.string().nullable(),
11689
+ planKind: ReferralDashboardPlanKindSchema,
11690
+ rawStatus: ReferralDashboardRawStatusSchema.nullable(),
11691
+ state: ReferralDashboardStateSchema,
11692
+ currentPeriodEnd: z49.string().datetime({
11693
+ offset: true
11694
+ }).nullable(),
11695
+ cancelAtPeriodEnd: z49.boolean()
11696
+ });
11697
+ var ReferralDashboardReferralSchema = z49.object({
11698
+ id: z49.string().min(1),
11699
+ registeredAt: z49.string().datetime({
11700
+ offset: true
11701
+ }),
11702
+ // Kept in the response for compatibility with existing referral API clients.
11703
+ // The referrer dashboard intentionally does not render these legacy fields.
11704
+ expiresAt: z49.string().datetime({
11705
+ offset: true
11706
+ }).nullable().optional(),
11707
+ status: z49.string().min(1).optional(),
11708
+ referredUser: z49.object({
11709
+ email: z49.string().nullable()
11710
+ }),
11711
+ subscription: ReferralDashboardSubscriptionSchema
11712
+ });
11713
+ var ReferralDashboardReferralsPageSchema = z49.object({
11714
+ referrals: z49.array(ReferralDashboardReferralSchema),
11715
+ total: z49.number().int().nonnegative()
11716
+ });
11717
+ var ReferralDashboardPlanBreakdownSchema = z49.object({
11718
+ planName: z49.string().nullable(),
11719
+ count: z49.number().int().nonnegative()
11720
+ });
11721
+ var ReferralDashboardMonthlyRegistrationSchema = z49.object({
11722
+ month: z49.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
11723
+ count: z49.number().int().nonnegative()
11724
+ });
11725
+ var ReferralDashboardOverviewSchema = z49.object({
11726
+ totalReferredUsers: z49.number().int().nonnegative(),
11727
+ activeUsers: z49.number().int().nonnegative(),
11728
+ pendingUsers: z49.number().int().nonnegative(),
11729
+ inactiveUsers: z49.number().int().nonnegative(),
11730
+ noPlanUsers: z49.number().int().nonnegative(),
11731
+ planBreakdown: z49.array(ReferralDashboardPlanBreakdownSchema),
11732
+ monthlyRegistrations: z49.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
11733
+ });
11734
+
11735
+ // src/tools/tasks.ts
11736
+ import { z as z51 } from "zod";
11737
+
11738
+ // src/resolve-image.ts
11739
+ import { lookup as dnsLookup } from "dns/promises";
11740
+ import { promises as fs, createReadStream } from "fs";
11741
+ import * as path from "path";
11742
+ import * as os from "os";
11743
+ var MAX_BYTES = 20 * 1024 * 1024;
11744
+ var IMAGE_FORMATS = [
11745
+ {
11746
+ mime: "image/jpeg",
11747
+ exts: [
11748
+ ".jpg",
11749
+ ".jpeg"
11750
+ ]
11751
+ },
11752
+ {
11753
+ mime: "image/png",
11754
+ exts: [
11755
+ ".png"
11756
+ ]
11757
+ },
11758
+ {
11759
+ mime: "image/webp",
11760
+ exts: [
11761
+ ".webp"
11762
+ ]
11763
+ }
11764
+ ];
11765
+ var ALLOWED_MIMES = new Set(IMAGE_FORMATS.map((f) => f.mime));
11766
+ var ALLOWED_EXTS = new Set(IMAGE_FORMATS.flatMap((f) => f.exts));
11767
+ var ResolveImageError = class extends Error {
11768
+ static {
11769
+ __name(this, "ResolveImageError");
11770
+ }
11771
+ code;
11772
+ constructor(code, message) {
11773
+ super(message), this.code = code;
11774
+ this.name = "ResolveImageError";
11775
+ }
11776
+ };
11777
+ function sniffMime(buf) {
11778
+ if (buf.length < 4) return null;
11779
+ if (buf.readUInt8(0) === 137 && buf.readUInt8(1) === 80 && buf.readUInt8(2) === 78 && buf.readUInt8(3) === 71) return "image/png";
11780
+ if (buf.readUInt8(0) === 255 && buf.readUInt8(1) === 216 && buf.readUInt8(2) === 255) return "image/jpeg";
11781
+ if (buf.length < 12) return null;
11782
+ if (buf.readUInt8(0) === 82 && buf.readUInt8(1) === 73 && buf.readUInt8(2) === 70 && buf.readUInt8(3) === 70 && buf.readUInt8(8) === 87 && buf.readUInt8(9) === 69 && buf.readUInt8(10) === 66 && buf.readUInt8(11) === 80) return "image/webp";
11783
+ return null;
11784
+ }
11785
+ __name(sniffMime, "sniffMime");
11786
+ function isPrivateHost(host) {
11787
+ if (host === "localhost" || host === "0.0.0.0") return true;
11788
+ if (host === "metadata.google.internal" || host === "169.254.169.254") return true;
11789
+ if (/^127\./.test(host)) return true;
11790
+ if (/^10\./.test(host)) return true;
11791
+ if (/^192\.168\./.test(host)) return true;
11792
+ if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(host)) return true;
11793
+ if (host.startsWith("169.254.")) return true;
11794
+ if (host === "[::1]" || host === "::1") return true;
11795
+ if (/^\[?0*:(?:0*:){6}0*1\]?$/i.test(host)) return true;
11796
+ const ipv4Mapped = host.match(/^\[?::ffff:([0-9a-f.:]+)\]?$/i);
11797
+ if (ipv4Mapped && ipv4Mapped[1]) {
11798
+ const inner = ipv4Mapped[1];
11799
+ if (inner.includes(".")) return isPrivateHost(inner);
11800
+ const parts = inner.split(":").map((h) => parseInt(h, 16));
11801
+ if (parts.length === 2 && parts.every((n) => !Number.isNaN(n))) {
11802
+ const bytes = [
11803
+ parts[0] >> 8,
11804
+ parts[0] & 255,
11805
+ parts[1] >> 8,
11806
+ parts[1] & 255
11807
+ ];
11808
+ return isPrivateHost(bytes.join("."));
11809
+ }
11810
+ }
11811
+ if (/^\[?f[cd][0-9a-f]{2}:/i.test(host)) return true;
11812
+ if (/^\[?fe[89ab][0-9a-f]:/i.test(host)) return true;
11813
+ return false;
11814
+ }
11815
+ __name(isPrivateHost, "isPrivateHost");
11816
+ async function assertHostResolvesPublic(hostname) {
11817
+ let records;
11818
+ try {
11819
+ records = await dnsLookup(hostname, {
11820
+ all: true
11821
+ });
11822
+ } catch {
11823
+ if (process.env.NODE_ENV === "test" || process.env.VITEST) return;
11824
+ throw new ResolveImageError("INVALID_INPUT", `imageUrl DNS lookup failed for ${hostname}`);
11825
+ }
11826
+ for (const { address } of records) {
11827
+ if (isPrivateHost(address)) {
11828
+ throw new ResolveImageError("INVALID_INPUT", `imageUrl host resolves to a private address (${address})`);
11829
+ }
11830
+ }
11831
+ }
11832
+ __name(assertHostResolvesPublic, "assertHostResolvesPublic");
11833
+ function expandUserPath(raw) {
11834
+ let s = raw.trim();
11835
+ if (s === "~") return os.homedir();
11836
+ if (s.startsWith("~/") || s.startsWith("~\\")) s = path.join(os.homedir(), s.slice(2));
11837
+ s = s.replace(/^\$HOME(?=$|[/\\])/, os.homedir());
11838
+ s = s.replace(/^%USERPROFILE%(?=$|[/\\])/i, os.homedir());
11839
+ return s;
11840
+ }
11841
+ __name(expandUserPath, "expandUserPath");
11842
+ async function readFileWithCap(absPath, max) {
11843
+ const chunks = [];
11844
+ let total = 0;
11845
+ let exceeded = false;
11846
+ const stream = createReadStream(absPath);
11847
+ try {
11848
+ for await (const chunk of stream) {
11849
+ total += chunk.length;
11850
+ if (total > max) {
11851
+ exceeded = true;
11852
+ break;
11853
+ }
11854
+ chunks.push(chunk);
11855
+ }
11856
+ } catch (e) {
11857
+ const code = e.code;
11858
+ if (code === "ENOENT") throw new ResolveImageError("IMAGE_PATH_NOT_FOUND", "file disappeared during read");
11859
+ if (code === "EACCES" || code === "EPERM") throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `cannot read file (${code})`);
11860
+ throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", e.message);
11861
+ } finally {
11862
+ if (!stream.destroyed) stream.destroy();
11863
+ }
11864
+ if (exceeded) throw new ResolveImageError("IMAGE_TOO_LARGE", `file exceeds ${max} bytes`);
11865
+ return Buffer.concat(chunks, total);
11866
+ }
11867
+ __name(readFileWithCap, "readFileWithCap");
11868
+ async function resolvePath(rawPath) {
11869
+ const expanded = expandUserPath(rawPath);
11870
+ const abs = path.resolve(expanded);
11871
+ const ext = path.extname(abs).toLowerCase();
11872
+ if (!ALLOWED_EXTS.has(ext)) {
11873
+ throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `path extension ${ext || "(none)"} is not in the image allowlist (.jpg .jpeg .png .webp)`);
11874
+ }
11875
+ let stat;
11876
+ try {
11877
+ stat = await fs.stat(abs);
11878
+ } catch (e) {
11879
+ const code = e.code;
11880
+ if (code === "ENOENT") {
11881
+ throw new ResolveImageError("IMAGE_PATH_NOT_FOUND", `no such file: ${abs}`);
11882
+ }
11883
+ if (code === "EACCES" || code === "EPERM") {
11884
+ throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `cannot access ${abs} (${code})`);
11885
+ }
11886
+ throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `stat failed: ${e.message}`);
11887
+ }
11888
+ if (!stat.isFile()) {
11889
+ throw new ResolveImageError("IMAGE_PATH_NOT_A_FILE", `${abs} is not a regular file`);
11890
+ }
11891
+ if (stat.size > MAX_BYTES) {
11892
+ throw new ResolveImageError("IMAGE_TOO_LARGE", `${stat.size} bytes (cap ${MAX_BYTES})`);
11893
+ }
11894
+ const buf = await readFileWithCap(abs, MAX_BYTES);
11895
+ const sniffed = sniffMime(buf);
11896
+ if (!sniffed) {
11897
+ throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes do not match PNG/JPEG/WEBP for ${abs}`);
11898
+ }
11899
+ return {
11900
+ buffer: buf,
11901
+ mimetype: sniffed
11902
+ };
11903
+ }
11904
+ __name(resolvePath, "resolvePath");
11905
+ async function resolveImageUrl(rawUrl) {
11906
+ let u;
11907
+ try {
11908
+ u = new URL(rawUrl);
11909
+ } catch {
11910
+ throw new ResolveImageError("INVALID_INPUT", "imageUrl is not a valid URL");
11911
+ }
11912
+ if (u.protocol !== "https:") throw new ResolveImageError("INVALID_INPUT", "imageUrl must be HTTPS");
11913
+ if (isPrivateHost(u.hostname)) throw new ResolveImageError("INVALID_INPUT", "imageUrl host not allowed");
11914
+ await assertHostResolvesPublic(u.hostname);
11915
+ let res;
11916
+ try {
11917
+ res = await fetch(rawUrl, {
11918
+ signal: AbortSignal.timeout(3e4),
11919
+ redirect: "error"
11920
+ });
11921
+ } catch (e) {
11922
+ throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", e.message);
11923
+ }
11924
+ if (!res.ok) throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", `HTTP ${res.status}`);
11925
+ const cl = res.headers.get("content-length");
11926
+ if (cl && Number(cl) > MAX_BYTES) {
11927
+ throw new ResolveImageError("IMAGE_TOO_LARGE", `content-length ${cl}`);
11928
+ }
11929
+ const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
11930
+ if (!ALLOWED_MIMES.has(ct)) throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `content-type ${ct}`);
11931
+ let buf;
11932
+ try {
11933
+ buf = Buffer.from(await readResponseBytes(res, MAX_BYTES));
11934
+ } catch (error) {
11935
+ if (error instanceof ResponseBodyTooLargeError) {
11936
+ throw new ResolveImageError("IMAGE_TOO_LARGE", `image URL exceeds ${MAX_BYTES} bytes`);
11937
+ }
11938
+ throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", "image response could not be read");
11939
+ }
11940
+ const sniffed = sniffMime(buf);
11941
+ if (!sniffed || sniffed !== ct) {
11942
+ throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes (${sniffed ?? "unknown"}) do not match content-type (${ct})`);
11943
+ }
11944
+ return {
11945
+ buffer: buf,
11946
+ mimetype: sniffed
11947
+ };
11190
11948
  }
11191
- __name(toLegacyCapabilityCatalog, "toLegacyCapabilityCatalog");
11192
- var PRODUCTMAKER_MCP_CAPABILITIES = toLegacyCapabilityCatalog(PRODUCTMAKER_MCP_CAPABILITY_REGISTRY);
11193
- var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITY_REGISTRY.map((capability) => capability.name);
11949
+ __name(resolveImageUrl, "resolveImageUrl");
11950
+ async function resolveImages(images, opts = {
11951
+ transport: "http"
11952
+ }) {
11953
+ if (!images.length) throw new ResolveImageError("INVALID_INPUT", "images: at least one entry required");
11954
+ if (images.length > 5) throw new ResolveImageError("INVALID_INPUT", "images: max 5 per request");
11955
+ return Promise.all(images.map((ref) => resolveOne(ref, opts.transport)));
11956
+ }
11957
+ __name(resolveImages, "resolveImages");
11958
+ async function resolveOne(ref, transport) {
11959
+ const hasUrl = Boolean(ref.url);
11960
+ const hasPath = Boolean(ref.path);
11961
+ if (hasUrl === hasPath) {
11962
+ throw new ResolveImageError("INVALID_INPUT", "each image entry must have exactly one of `url` or `path`");
11963
+ }
11964
+ if (hasPath) {
11965
+ if (transport === "http") {
11966
+ throw new ResolveImageError("PATH_NOT_SUPPORTED_IN_HTTP_MODE", "Local file paths are only available when running the MCP locally (npm). For the hosted endpoint at mcp.productmaker.app, pass a public HTTPS `url` instead, or ask the user to host the photo first.");
11967
+ }
11968
+ return resolvePath(ref.path);
11969
+ }
11970
+ return resolveImageUrl(ref.url);
11971
+ }
11972
+ __name(resolveOne, "resolveOne");
11194
11973
 
11195
- // ../../packages/types/src/actor.ts
11196
- import { z as z48 } from "zod";
11197
- var ActorStatusSchema = z48.enum([
11198
- "draft",
11199
- "generating",
11200
- "ready",
11201
- "failed"
11202
- ]);
11203
- var ACTOR_NAME_MAX_CHARS = 60;
11204
- var ACTOR_DESCRIPTION_MAX_CHARS = 600;
11205
- var ACTOR_CANDIDATE_COUNT = 4;
11206
- var ActorGenderSchema = z48.enum([
11207
- "female",
11208
- "male",
11209
- "non_binary"
11974
+ // src/status-poller.ts
11975
+ var TERMINAL = /* @__PURE__ */ new Set([
11976
+ "done",
11977
+ "complete",
11978
+ "error"
11210
11979
  ]);
11211
- var ActorSchema = z48.object({
11212
- id: z48.string(),
11213
- name: z48.string().min(1).max(ACTOR_NAME_MAX_CHARS),
11214
- description: z48.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
11215
- gender: ActorGenderSchema,
11216
- voiceId: z48.string().nullable().optional(),
11217
- brandId: z48.string().nullable().optional(),
11218
- status: ActorStatusSchema,
11219
- /**
11220
- * A candidate-grid run is in flight RIGHT NOW.
11221
- *
11222
- * Separate from `status` because the two answer different questions:
11223
- * `status` is the PUBLISHED state (a personaje being regenerated stays
11224
- * `ready`, so it keeps serving the renders that already reference it), and
11225
- * this flag is the live one the wizard polls. It is derived from a lock
11226
- * timestamp with a TTL, so an orphaned run reads as `false` instead of
11227
- * pinning the personaje forever.
11228
- */
11229
- generating: z48.boolean().optional(),
11230
- photoUrl: z48.string().url().nullable().optional(),
11231
- avatarUrl: z48.string().url().nullable().optional(),
11232
- candidateUrls: z48.array(z48.string().url()).optional(),
11233
- /** Indices of candidateUrls that failed the anatomy check (Task 6). */
11234
- flaggedCandidateIndices: z48.array(z48.number().int()).optional()
11235
- });
11236
- var CreateActorRequest = z48.object({
11237
- name: z48.string().min(1).max(ACTOR_NAME_MAX_CHARS),
11238
- description: z48.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
11239
- gender: ActorGenderSchema,
11240
- voiceId: z48.string().optional(),
11241
- brandId: z48.string().optional(),
11242
- sourcePhotoKey: z48.string().optional()
11243
- }).refine((v) => v.description.trim().length > 0 || !!v.sourcePhotoKey, {
11244
- message: "Se necesita una descripci\xF3n o una foto"
11245
- });
11246
- var UpdateActorRequest = z48.object({
11247
- name: z48.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
11248
- description: z48.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
11249
- gender: ActorGenderSchema.optional(),
11250
- voiceId: z48.string().nullable().optional(),
11251
- brandId: z48.string().nullable().optional()
11252
- });
11253
- var ApproveActorRequest = z48.object({
11254
- candidateIndex: z48.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
11255
- });
11980
+ async function pollStatus(fetcher, waitSeconds, signal, opts = {}) {
11981
+ const intervalMs = opts.intervalMs ?? 3e3;
11982
+ const maxWait = opts.maxWaitSeconds ?? 120;
11983
+ const effective = Math.min(Math.max(0, waitSeconds), maxWait);
11984
+ let snapshot = await fetcher();
11985
+ if (effective === 0) return snapshot;
11986
+ if (TERMINAL.has(snapshot.status) || snapshot.status === "awaiting_review" || snapshot.pendingUserAction != null) return snapshot;
11987
+ const deadline = Date.now() + effective * 1e3;
11988
+ while (Date.now() < deadline) {
11989
+ if (signal?.aborted) throw new Error("aborted");
11990
+ await sleep(intervalMs, signal);
11991
+ const next = await fetcher();
11992
+ if (next.status !== snapshot.status || next.phase !== snapshot.phase || TERMINAL.has(next.status) || next.status === "awaiting_review" || next.pendingUserAction != null) {
11993
+ return next;
11994
+ }
11995
+ snapshot = next;
11996
+ }
11997
+ return snapshot;
11998
+ }
11999
+ __name(pollStatus, "pollStatus");
12000
+ function sleep(ms, signal) {
12001
+ return new Promise((resolve3, reject) => {
12002
+ const onAbort = /* @__PURE__ */ __name(() => {
12003
+ clearTimeout(t);
12004
+ reject(new Error("aborted"));
12005
+ }, "onAbort");
12006
+ const t = setTimeout(() => {
12007
+ signal?.removeEventListener("abort", onAbort);
12008
+ resolve3();
12009
+ }, ms);
12010
+ signal?.addEventListener("abort", onAbort, {
12011
+ once: true
12012
+ });
12013
+ });
12014
+ }
12015
+ __name(sleep, "sleep");
11256
12016
 
11257
- // ../../packages/types/src/referral-dashboard.ts
11258
- import { z as z49 } from "zod";
11259
- var REFERRAL_DASHBOARD_RAW_STATUSES = [
11260
- "ACTIVE",
11261
- "PENDING",
11262
- "GRACE",
11263
- "CANCELLED",
11264
- "EXPIRED"
11265
- ];
11266
- var REFERRAL_DASHBOARD_STATES = [
11267
- "active",
11268
- "pending",
11269
- "inactive",
11270
- "no_plan"
11271
- ];
11272
- var REFERRAL_DASHBOARD_PLAN_KINDS = [
11273
- "current",
11274
- "last",
11275
- "none"
11276
- ];
11277
- var ReferralDashboardRawStatusSchema = z49.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
11278
- var ReferralDashboardStateSchema = z49.enum(REFERRAL_DASHBOARD_STATES);
11279
- var ReferralDashboardPlanKindSchema = z49.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
11280
- var ReferralDashboardStatusSchema = z49.enum([
11281
- "all",
11282
- "active",
11283
- "pending",
11284
- "inactive"
11285
- ]).default("all");
11286
- var ReferralDashboardPlanSchema = z49.string().trim().min(1).max(100).optional();
11287
- var ReferralDashboardFiltersSchema = z49.object({
11288
- status: ReferralDashboardStatusSchema,
11289
- plan: ReferralDashboardPlanSchema
11290
- });
11291
- var ReferralDashboardSubscriptionSchema = z49.object({
11292
- planName: z49.string().nullable(),
11293
- planKind: ReferralDashboardPlanKindSchema,
11294
- rawStatus: ReferralDashboardRawStatusSchema.nullable(),
11295
- state: ReferralDashboardStateSchema,
11296
- currentPeriodEnd: z49.string().datetime({
11297
- offset: true
11298
- }).nullable(),
11299
- cancelAtPeriodEnd: z49.boolean()
11300
- });
11301
- var ReferralDashboardReferralSchema = z49.object({
11302
- id: z49.string().min(1),
11303
- registeredAt: z49.string().datetime({
11304
- offset: true
11305
- }),
11306
- // Kept in the response for compatibility with existing referral API clients.
11307
- // The referrer dashboard intentionally does not render these legacy fields.
11308
- expiresAt: z49.string().datetime({
11309
- offset: true
11310
- }).nullable().optional(),
11311
- status: z49.string().min(1).optional(),
11312
- referredUser: z49.object({
11313
- email: z49.string().nullable()
11314
- }),
11315
- subscription: ReferralDashboardSubscriptionSchema
11316
- });
11317
- var ReferralDashboardReferralsPageSchema = z49.object({
11318
- referrals: z49.array(ReferralDashboardReferralSchema),
11319
- total: z49.number().int().nonnegative()
11320
- });
11321
- var ReferralDashboardPlanBreakdownSchema = z49.object({
11322
- planName: z49.string().nullable(),
11323
- count: z49.number().int().nonnegative()
11324
- });
11325
- var ReferralDashboardMonthlyRegistrationSchema = z49.object({
11326
- month: z49.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
11327
- count: z49.number().int().nonnegative()
11328
- });
11329
- var ReferralDashboardOverviewSchema = z49.object({
11330
- totalReferredUsers: z49.number().int().nonnegative(),
11331
- activeUsers: z49.number().int().nonnegative(),
11332
- pendingUsers: z49.number().int().nonnegative(),
11333
- inactiveUsers: z49.number().int().nonnegative(),
11334
- noPlanUsers: z49.number().int().nonnegative(),
11335
- planBreakdown: z49.array(ReferralDashboardPlanBreakdownSchema),
11336
- monthlyRegistrations: z49.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
11337
- });
12017
+ // src/errors.ts
12018
+ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
11338
12019
 
11339
12020
  // src/resolve-media.ts
12021
+ import { lookup as dnsLookup2 } from "dns/promises";
12022
+ import { promises as fs2, createReadStream as createReadStream2 } from "fs";
12023
+ import * as os2 from "os";
12024
+ import * as path2 from "path";
11340
12025
  var MAX_MEDIA_BYTES = CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES;
11341
12026
  var MEDIA_FORMATS = [
11342
12027
  {
@@ -13040,6 +13725,21 @@ ${summary}`,
13040
13725
  __name(requirePaidCreativeConfirmation, "requirePaidCreativeConfirmation");
13041
13726
 
13042
13727
  // src/tools/tasks.ts
13728
+ var createProductTaskGuidance = getProductMakerCapabilityModelGuidance("create_product_task");
13729
+ function renderCreateProductTaskDefaultForMcp(fragment) {
13730
+ const outputs = fragment.match(/genera (landing), (video) e (imágenes creativas)/u);
13731
+ return outputs ? `Por defecto genera ${outputs[1]} + ${outputs[2]} + im\xE1genes en UN MISMO TASK.` : fragment;
13732
+ }
13733
+ __name(renderCreateProductTaskDefaultForMcp, "renderCreateProductTaskDefaultForMcp");
13734
+ function renderCreateProductTaskAvoidWhenForMcp(fragment) {
13735
+ return /un solo entregable/u.test(fragment) ? "Para 1 solo output, usa generate_landing / generate_video_creative / generate_image_creatives." : fragment;
13736
+ }
13737
+ __name(renderCreateProductTaskAvoidWhenForMcp, "renderCreateProductTaskAvoidWhenForMcp");
13738
+ var createProductTaskDefaultDescription = renderCreateProductTaskDefaultForMcp(createProductTaskGuidance.defaults[0]);
13739
+ var createProductTaskSingleOutputDescription = renderCreateProductTaskAvoidWhenForMcp(createProductTaskGuidance.avoidWhen[0]);
13740
+ var createServiceVideoGuidance = getProductMakerCapabilityModelGuidance("create_service_video");
13741
+ var listTasksGuidance = getProductMakerCapabilityModelGuidance("list_tasks");
13742
+ var editTaskDraftGuidance = getProductMakerCapabilityModelGuidance("edit_task_draft");
13043
13743
  var CreateProductTaskInput = z51.object({
13044
13744
  images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
13045
13745
  projectId: z51.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
@@ -13067,8 +13767,9 @@ var CreateProductTaskInput = z51.object({
13067
13767
  "landing",
13068
13768
  "video",
13069
13769
  "image"
13070
- ])).min(2).max(3).optional().describe('Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). Para 1 solo output, usa generate_landing / generate_video_creative / generate_image_creatives.'),
13770
+ ])).min(2).max(3).optional().describe(`Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). ${createProductTaskSingleOutputDescription}`),
13071
13771
  competitorScanEnabled: z51.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
13772
+ replicateWinnerWhenFound: z51.boolean().optional().describe('Opcional. Consentimiento para que, si encontramos un anuncio GANADOR de la competencia que podemos replicar, ese ganador reemplace la narrativa concreta que el usuario eligi\xF3 (videoPreset). Default true: la r\xE9plica del ganador es el formato que mejor convierte y sigue ganando por defecto. P\xE1salo en false SOLO si el usuario dice expl\xEDcitamente que quiere conservar su estilo elegido pase lo que pase (ej. pidi\xF3 Pixar y quiere Pixar aunque haya un ganador live-action). Irrelevante cuando videoPreset es "auto" (no hay elecci\xF3n que respetar) o cuando competitorScanEnabled es false (nunca habr\xE1 ganador).'),
13072
13773
  brandId: z51.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 los outputs generados."),
13073
13774
  actorId: z51.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
13074
13775
  creativeDirection: z51.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
@@ -13452,7 +14153,11 @@ function resolveServicePanelEdits(panels, editsByBeat, editsByScene, fieldLabel)
13452
14153
  }
13453
14154
  __name(resolveServicePanelEdits, "resolveServicePanelEdits");
13454
14155
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
13455
- server.tool("create_product_task", "Crea una tarea autopilot combinada. Por defecto genera landing + video + im\xE1genes en UN MISMO TASK; \xFAsala cuando el usuario pide todo o varios outputs, no creando tareas individuales. Usa images para solicitudes directas. Usa projectId \xFAnicamente si el usuario pidi\xF3 expl\xEDcitamente continuar el proyecto guiado que acaba de costear; nunca busques, infieras ni menciones proyectos previos.\n\nREQUERIDOS: images o projectId expl\xEDcito, language y country. Cuando los outputs incluyen video (tambi\xE9n por default), pregunta SIEMPRE videoAngleCount: 1, 2 o 3; es una decisi\xF3n de costo incluso con aiDecidesAll. Si falta una foto local, llama primero `request_media_upload`. Pregunta price, offer, paymentMethods y compareAtPrice cuando aplique; nunca asumas country/language.\n\nANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explica que inicia trabajo pagado y espera confirmaci\xF3n expl\xEDcita. aiDecidesAll omitido/false pausa para reviews conversacionales; muestra todos los \xE1ngulos candidatos y permite escoger/editar hasta el m\xE1ximo autorizado. Usa aiDecidesAll=true s\xF3lo si el usuario pidi\xF3 \u201Cdecide todo\u201D o \u201Cno me preguntes\u201D.", CreateProductTaskInput.shape, CREATES_EXTERNAL, async (input) => {
14156
+ server.tool("create_product_task", createProductTaskGuidance.summary + ` ${createProductTaskDefaultDescription} \xDAsala cuando el usuario pide todo o varios outputs, no creando tareas individuales. Usa images para solicitudes directas. Usa projectId \xFAnicamente si el usuario pidi\xF3 expl\xEDcitamente continuar el proyecto guiado que acaba de costear; nunca busques, infieras ni menciones proyectos previos.
14157
+
14158
+ REQUERIDOS: images o projectId expl\xEDcito, language y country. Cuando los outputs incluyen video (tambi\xE9n por default), pregunta SIEMPRE videoAngleCount: 1, 2 o 3; es una decisi\xF3n de costo incluso con aiDecidesAll. Si falta una foto local, llama primero \`request_media_upload\`. Pregunta price, offer, paymentMethods y compareAtPrice cuando aplique; nunca asumas country/language.
14159
+
14160
+ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explica que inicia trabajo pagado y espera confirmaci\xF3n expl\xEDcita. aiDecidesAll omitido/false pausa para reviews conversacionales; muestra todos los \xE1ngulos candidatos y permite escoger/editar hasta el m\xE1ximo autorizado. Usa aiDecidesAll=true s\xF3lo si el usuario pidi\xF3 \u201Cdecide todo\u201D o \u201Cno me preguntes\u201D.`, CreateProductTaskInput.shape, CREATES_EXTERNAL, async (input) => {
13456
14161
  try {
13457
14162
  const selectedOutputs = input.outputs ?? DEFAULT_PRODUCT_TASK_OUTPUTS;
13458
14163
  if (!input.images && !input.projectId) {
@@ -13503,6 +14208,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
13503
14208
  "options.angleNarratives": input.angleNarratives,
13504
14209
  "options.selectedOutputs": input.outputs,
13505
14210
  "options.competitorScanEnabled": input.competitorScanEnabled,
14211
+ "options.replicateWinnerWhenFound": input.replicateWinnerWhenFound,
13506
14212
  "options.brandId": input.brandId,
13507
14213
  "options.actorId": input.actorId,
13508
14214
  "options.creativeDirection": input.creativeDirection,
@@ -13532,7 +14238,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
13532
14238
  throw translateError(e);
13533
14239
  }
13534
14240
  });
13535
- server.tool("create_service_video", "Crea un video para un servicio desde su landing. Esta acci\xF3n consume cr\xE9ditos: confirma la intenci\xF3n del usuario antes de llamarla. `automatic` genera directamente; `guided` devuelve conceptos para que el usuario apruebe uno mediante `resolve_pending_action`. La web tambi\xE9n acepta demos y anuncios de referencia; el transporte MCP de video adjunto todav\xEDa falla cerrado hasta poder subir bytes de forma segura.", CreateServiceVideoInput.shape, CREATES_EXTERNAL, async (input) => {
14241
+ server.tool("create_service_video", createServiceVideoGuidance.summary + " Esta acci\xF3n consume cr\xE9ditos: confirma la intenci\xF3n del usuario antes de llamarla. `automatic` genera directamente; `guided` devuelve conceptos para que el usuario apruebe uno mediante `resolve_pending_action`. La web tambi\xE9n acepta demos y anuncios de referencia; el transporte MCP de video adjunto todav\xEDa falla cerrado hasta poder subir bytes de forma segura.", CreateServiceVideoInput.shape, CREATES_EXTERNAL, async (input) => {
13536
14242
  try {
13537
14243
  await requirePaidCreativeConfirmation(server, `Video de servicio en ${input.language}. Modo: ${input.reviewMode === "automatic" ? "la IA decide todo" : "reviews conversacionales"}.`);
13538
14244
  const created = await api.postJson("/v2/ingest", {
@@ -13658,7 +14364,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
13658
14364
  throw translateError(e);
13659
14365
  }
13660
14366
  });
13661
- server.tool("list_tasks", "Lista de forma read-only las tareas recientes del usuario. \xDAsala para encontrar una tarea previa por nombre o revisar el historial antes de pedir un taskId. Soporta paginaci\xF3n con limit/offset y filtro de texto. No crea, reanuda ni modifica tareas.", ListTasksInput.shape, READS_INTERNAL, async (input) => {
14367
+ server.tool("list_tasks", listTasksGuidance.summary + " \xDAsala para encontrar una tarea previa por nombre o revisar el historial antes de pedir un taskId. Soporta paginaci\xF3n con limit/offset y filtro de texto. No crea, reanuda ni modifica tareas.", ListTasksInput.shape, READS_INTERNAL, async (input) => {
13662
14368
  try {
13663
14369
  const qs = new URLSearchParams();
13664
14370
  if (input.limit != null) qs.set("limit", String(input.limit));
@@ -13671,7 +14377,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
13671
14377
  throw translateError(e);
13672
14378
  }
13673
14379
  });
13674
- server.tool("edit_task_draft", "Edita el draft de un angle de marketing antes de la generaci\xF3n. Campos editables: angle (texto del angle), buyerPersona, actor (descripci\xF3n del personaje), actorId (ID de un personaje guardado; usa list_actors), gender (female|male), scriptNotes. Use this only before generation completes. Editing already-generated images or videos is not supported.", EditTaskDraftInput.shape, OVERWRITES_INTERNAL, async (input) => {
14380
+ server.tool("edit_task_draft", editTaskDraftGuidance.summary + " Campos editables: angle (texto del angle), buyerPersona, actor (descripci\xF3n del personaje), actorId (ID de un personaje guardado; usa list_actors), gender (female|male), scriptNotes. Use this only before generation completes. Editing already-generated images or videos is not supported.", EditTaskDraftInput.shape, OVERWRITES_INTERNAL, async (input) => {
13675
14381
  try {
13676
14382
  const angle = await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/angles/${input.angleIndex}`, input.angle ?? {});
13677
14383
  return asText({
@@ -14055,6 +14761,11 @@ __name(registerTaskTools, "registerTaskTools");
14055
14761
  // src/tools/creatives.ts
14056
14762
  import { randomUUID } from "crypto";
14057
14763
  import { z as z52 } from "zod";
14764
+ var generateFreeVideoGuidance = getProductMakerCapabilityModelGuidance("generate_free_video");
14765
+ var generateVideoCreativeGuidance = getProductMakerCapabilityModelGuidance("generate_video_creative");
14766
+ var generateImageCreativesGuidance = getProductMakerCapabilityModelGuidance("generate_image_creatives");
14767
+ var editImageCreativeGuidance = getProductMakerCapabilityModelGuidance("edit_image_creative");
14768
+ var generateLandingGuidance = getProductMakerCapabilityModelGuidance("generate_landing");
14058
14769
  var CreativeRefs = {
14059
14770
  images: ImagesInput,
14060
14771
  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.'),
@@ -14066,6 +14777,7 @@ var CreativeRefs = {
14066
14777
  productLabel: z52.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.'),
14067
14778
  buyerPersona: z52.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.'),
14068
14779
  isCombo: z52.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
14780
+ replicateWinnerWhenFound: z52.boolean().optional().describe('Opcional. Consentimiento para que, si encontramos un anuncio GANADOR de la competencia que podemos replicar, ese ganador reemplace la narrativa concreta que el usuario eligi\xF3 (videoPreset). Default true: la r\xE9plica del ganador es el formato que mejor convierte y sigue ganando por defecto. P\xE1salo en false SOLO si el usuario dice expl\xEDcitamente que quiere conservar su estilo elegido pase lo que pase (ej. pidi\xF3 Pixar y quiere Pixar aunque haya un ganador live-action). Irrelevante cuando videoPreset es "auto" (no hay elecci\xF3n que respetar) o cuando competitorScanEnabled es false (nunca habr\xE1 ganador).'),
14069
14781
  competitorScanEnabled: z52.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
14070
14782
  brandId: z52.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.")
14071
14783
  };
@@ -14121,6 +14833,7 @@ var VideoInput = z52.object({
14121
14833
  cta: z52.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.'),
14122
14834
  scriptNotes: z52.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.'),
14123
14835
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
14836
+ replicateWinnerWhenFound: CreativeRefs.replicateWinnerWhenFound,
14124
14837
  brandId: CreativeRefs.brandId
14125
14838
  }).strict();
14126
14839
  var FreeVideoQualityEnum = z52.enum([
@@ -14334,7 +15047,7 @@ function resolveLandingSetup(setup) {
14334
15047
  }
14335
15048
  __name(resolveLandingSetup, "resolveLandingSetup");
14336
15049
  function registerCreativeTools(server, api, { transport = "http" } = {}) {
14337
- server.tool("generate_free_video", 'Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia. Si el usuario adjunta o menciona una imagen local, llama primero request_media_upload con mediaKind=image y usa el assetId devuelto; no pidas path ni URL. Devuelve un taskId y procesa el job de forma as\xEDncrona.\n\nREQUERIDOS: rawPrompt, quality, durationSeconds y videoResolution.\n\nCALIDAD: "economical" = Econ\xF3mico y "high_detail" = Alto detalle. La duraci\xF3n debe pertenecer a la lista admitida por la calidad escogida; si el usuario pide otra, preg\xFAntale una duraci\xF3n v\xE1lida.\n\nSUBT\xCDTULOS: si addSubtitles=true, el rawPrompt completo se usa como texto exacto de subt\xEDtulos y debe tener m\xE1ximo 500 caracteres; no existe una pregunta adicional de transcripci\xF3n.\n\nPara videos normales con narrativa/\xE1ngulo usa generate_video_creative. Para tareas combinadas de producto usa create_product_task.', FreeVideoInput.shape, CREATES_EXTERNAL, async (input) => {
15050
+ server.tool("generate_free_video", generateFreeVideoGuidance.summary + ' Si el usuario adjunta o menciona una imagen local, llama primero request_media_upload con mediaKind=image y usa el assetId devuelto; no pidas path ni URL. Devuelve un taskId y procesa el job de forma as\xEDncrona.\n\nREQUERIDOS: rawPrompt, quality, durationSeconds y videoResolution.\n\nCALIDAD: "economical" = Econ\xF3mico y "high_detail" = Alto detalle. La duraci\xF3n debe pertenecer a la lista admitida por la calidad escogida; si el usuario pide otra, preg\xFAntale una duraci\xF3n v\xE1lida.\n\nSUBT\xCDTULOS: si addSubtitles=true, el rawPrompt completo se usa como texto exacto de subt\xEDtulos y debe tener m\xE1ximo 500 caracteres; no existe una pregunta adicional de transcripci\xF3n.\n\nPara videos normales con narrativa/\xE1ngulo usa generate_video_creative. Para tareas combinadas de producto usa create_product_task.', FreeVideoInput.shape, CREATES_EXTERNAL, async (input) => {
14338
15051
  try {
14339
15052
  const allowedDurations = FREE_VIDEO_DURATIONS_BY_QUALITY[input.quality];
14340
15053
  if (!allowedDurations.includes(input.durationSeconds)) {
@@ -14365,7 +15078,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
14365
15078
  throw translateError(e);
14366
15079
  }
14367
15080
  });
14368
- server.tool("generate_video_creative", "Genera entre 1 y 3 videos creativos independientes dentro de un mismo task. Devuelve un taskId. Esta solicitud es directa: nunca busques, reutilices ni menciones un DropshippingProject.\n\nREQUERIDOS: images (1-5 fotos), title, primaryAngle, language, country, aspectRatio, videoAngleCount y creativeSetup. Pregunta SIEMPRE cu\xE1ntos videos quiere (1, 2 o 3): la cantidad cambia el costo y no forma parte de \u2018la IA decide todo\u2019. Si falta una foto local, llama primero `request_media_upload` y usa los assetId; no pidas path ni URL. PREGUNTA price y offer si faltan; no asumas country/language.\n\nCONFIGURACI\xD3N: salvo que el usuario diga expl\xEDcitamente que la IA decida todo, usa creativeSetup.mode=guided y pregunta narrativa, p\xFAblico comprador, presentador (actor guardado, descripci\xF3n o ninguno) y direcci\xF3n creativa. creativeDirection=null s\xF3lo despu\xE9s de que el usuario delegue esa decisi\xF3n concreta. No inventes valores de setup.\n\nANTES de invocar, muestra un resumen compacto que incluya cantidad, esas cuatro decisiones y espera confirmaci\xF3n expl\xEDcita porque inicia trabajo pagado. 15s usa Realista; 30s usa Multiescena y se factura por escena, as\xED que avisa ese mayor costo. Advierte tambi\xE9n que, si luego el usuario confirma que las fotos son de varios productos (combo), el pipeline r\xE1pido actualmente omite la pausa de guion aunque mantiene las dem\xE1s revisiones.\n\nREVIEWS: guided muestra los \xE1ngulos candidatos y permite escoger hasta videoAngleCount, editar cada \xE1ngulo y revisar sus guiones. automatic equivale al toggle web \u201CDejar que la IA decida todo\u201D y omite ambas reviews. scriptNotes agrega restricciones al guion; get_video_script y approve_video_script manejan la review palabra por palabra.", VideoInput.shape, CREATES_EXTERNAL, async (input) => {
15081
+ server.tool("generate_video_creative", generateVideoCreativeGuidance.summary + " Devuelve un taskId. Esta solicitud es directa: nunca busques, reutilices ni menciones un DropshippingProject.\n\nREQUERIDOS: images (1-5 fotos), title, primaryAngle, language, country, aspectRatio, videoAngleCount y creativeSetup. Pregunta SIEMPRE cu\xE1ntos videos quiere (1, 2 o 3): la cantidad cambia el costo y no forma parte de \u2018la IA decide todo\u2019. Si falta una foto local, llama primero `request_media_upload` y usa los assetId; no pidas path ni URL. PREGUNTA price y offer si faltan; no asumas country/language.\n\nCONFIGURACI\xD3N: salvo que el usuario diga expl\xEDcitamente que la IA decida todo, usa creativeSetup.mode=guided y pregunta narrativa, p\xFAblico comprador, presentador (actor guardado, descripci\xF3n o ninguno) y direcci\xF3n creativa. creativeDirection=null s\xF3lo despu\xE9s de que el usuario delegue esa decisi\xF3n concreta. No inventes valores de setup.\n\nANTES de invocar, muestra un resumen compacto que incluya cantidad, esas cuatro decisiones y espera confirmaci\xF3n expl\xEDcita porque inicia trabajo pagado. 15s usa Realista; 30s usa Multiescena y se factura por escena, as\xED que avisa ese mayor costo. Advierte tambi\xE9n que, si luego el usuario confirma que las fotos son de varios productos (combo), el pipeline r\xE1pido actualmente omite la pausa de guion aunque mantiene las dem\xE1s revisiones.\n\nREVIEWS: guided muestra los \xE1ngulos candidatos y permite escoger hasta videoAngleCount, editar cada \xE1ngulo y revisar sus guiones. automatic equivale al toggle web \u201CDejar que la IA decida todo\u201D y omite ambas reviews. scriptNotes agrega restricciones al guion; get_video_script y approve_video_script manejan la review palabra por palabra.", VideoInput.shape, CREATES_EXTERNAL, async (input) => {
14369
15082
  try {
14370
15083
  await requirePaidCreativeConfirmation(server, input.creativeSetup.mode === "automatic" ? `${input.videoAngleCount} video${input.videoAngleCount === 1 ? "" : "s"} de ${input.durationSeconds}s para \u201C${input.title}\u201D en ${input.country}. Configuraci\xF3n: la IA decide todo.` : `${input.videoAngleCount} video${input.videoAngleCount === 1 ? "" : "s"} de ${input.durationSeconds}s para \u201C${input.title}\u201D en ${input.country}. Narrativa: ${input.creativeSetup.videoPreset}. P\xFAblico: ${input.creativeSetup.buyerPersona}. Presentador: ${input.creativeSetup.presenter.mode}. Direcci\xF3n: ${input.creativeSetup.creativeDirection ?? "la IA decide"}. Si despu\xE9s confirmas que las fotos son de varios productos (combo), el pipeline r\xE1pido omite actualmente la pausa de revisi\xF3n del guion.`);
14371
15084
  const guidedSetup = input.creativeSetup.mode === "guided" ? input.creativeSetup : null;
@@ -14413,7 +15126,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
14413
15126
  throw translateError(e);
14414
15127
  }
14415
15128
  });
14416
- server.tool("generate_image_creatives", "Genera variantes de imagen creativa. Devuelve un taskId. Esta solicitud es directa: nunca busques, reutilices ni menciones un DropshippingProject.\n\nREQUERIDOS: images (1-5 fotos), productTitle, language, country y creativeSetup. Si falta una foto local, llama primero `request_media_upload` y usa los assetId. Pregunta price, offer y compareAtPrice cuando aplique; no asumas country/language. primaryAngle es opcional: si falta, ProductMaker lo genera y guided lo muestra para revisi\xF3n.\n\nCONFIGURACI\xD3N: usa guided salvo delegaci\xF3n total expl\xEDcita. No hagas un interrogatorio campo por campo: presenta UN checklist compacto con arquetipo, tratamiento visual, formato, cantidad, calidad, mezcla condicional, copy, competencia, contexto, direcci\xF3n, colores y copy exacto; propone los defaults recomendados y permite que el usuario delegue los campos con ai_decides o null donde el schema lo admite. La UI ofrece 1, 2, 3, 4 o 6 variantes: para 2\u20134 pregunta styleMix; para 1 o 6 usa null, y 6 activa autom\xE1ticamente una imagen de cada estilo. Premium 2K no admite 4:5.\n\nANTES de invocar, muestra el resumen de esas decisiones y espera confirmaci\xF3n expl\xEDcita porque inicia trabajo pagado. guided pausa para revisar el \xE1ngulo; automatic equivale al toggle web \u201CDejar que la IA decida todo\u201D y omite la review.", ImageInput.shape, CREATES_EXTERNAL, async (input) => {
15129
+ server.tool("generate_image_creatives", generateImageCreativesGuidance.summary + " Devuelve un taskId. Esta solicitud es directa: nunca busques, reutilices ni menciones un DropshippingProject.\n\nREQUERIDOS: images (1-5 fotos), productTitle, language, country y creativeSetup. Si falta una foto local, llama primero `request_media_upload` y usa los assetId. Pregunta price, offer y compareAtPrice cuando aplique; no asumas country/language. primaryAngle es opcional: si falta, ProductMaker lo genera y guided lo muestra para revisi\xF3n.\n\nCONFIGURACI\xD3N: usa guided salvo delegaci\xF3n total expl\xEDcita. No hagas un interrogatorio campo por campo: presenta UN checklist compacto con arquetipo, tratamiento visual, formato, cantidad, calidad, mezcla condicional, copy, competencia, contexto, direcci\xF3n, colores y copy exacto; propone los defaults recomendados y permite que el usuario delegue los campos con ai_decides o null donde el schema lo admite. La UI ofrece 1, 2, 3, 4 o 6 variantes: para 2\u20134 pregunta styleMix; para 1 o 6 usa null, y 6 activa autom\xE1ticamente una imagen de cada estilo. Premium 2K no admite 4:5.\n\nANTES de invocar, muestra el resumen de esas decisiones y espera confirmaci\xF3n expl\xEDcita porque inicia trabajo pagado. guided pausa para revisar el \xE1ngulo; automatic equivale al toggle web \u201CDejar que la IA decida todo\u201D y omite la review.", ImageInput.shape, CREATES_EXTERNAL, async (input) => {
14417
15130
  try {
14418
15131
  const guidedSetup = input.creativeSetup.mode === "guided" ? input.creativeSetup : null;
14419
15132
  const setupFields = guidedSetup ? resolveImageSetup(guidedSetup) : {};
@@ -14438,7 +15151,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
14438
15151
  throw translateError(e);
14439
15152
  }
14440
15153
  });
14441
- server.tool("edit_image_creative", "Corrige UNA imagen ya generada de una tarea de creativos, sin regenerar el set completo.\n\nCUESTA CR\xC9DITOS: cobra una sola imagen (sin brief ni extracci\xF3n), mucho menos que volver a generar todo. ANTES de invocar, PREG\xDANTALE al usuario si quiere pagar esa correcci\xF3n.\n\nSolo funciona con la tarea en estado `done` \u2014 si a\xFAn est\xE1 procesando, espera con get_task_status. Una tarea en error terminal no se puede corregir.\n\nUsa variantIndex para elegir cu\xE1l de las im\xE1genes corregir (0 = la primera) e instruction para describir el cambio en lenguaje natural.", EditImageCreativeInput.shape, OVERWRITES_EXTERNAL, async (input) => {
15154
+ server.tool("edit_image_creative", editImageCreativeGuidance.summary + "\n\nCUESTA CR\xC9DITOS: cobra una sola imagen (sin brief ni extracci\xF3n), mucho menos que volver a generar todo. ANTES de invocar, PREG\xDANTALE al usuario si quiere pagar esa correcci\xF3n.\n\nSolo funciona con la tarea en estado `done` \u2014 si a\xFAn est\xE1 procesando, espera con get_task_status. Una tarea en error terminal no se puede corregir.\n\nUsa variantIndex para elegir cu\xE1l de las im\xE1genes corregir (0 = la primera) e instruction para describir el cambio en lenguaje natural.", EditImageCreativeInput.shape, OVERWRITES_EXTERNAL, async (input) => {
14442
15155
  try {
14443
15156
  await requirePaidCreativeConfirmation(server, `Correcci\xF3n pagada de la imagen ${input.variantIndex + 1}.`);
14444
15157
  const r = await api.postJson(`/v1/creatives/images/${encodeURIComponent(input.taskId)}/regenerate`, {
@@ -14450,7 +15163,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
14450
15163
  throw translateError(e);
14451
15164
  }
14452
15165
  });
14453
- server.tool("generate_landing", "Genera una landing page cl\xE1sica basada en im\xE1genes, lista para Shopify. No expone landing editable/code mientras siga siendo admin-only. Devuelve un taskId. Esta solicitud es directa: nunca busques, reutilices ni menciones un DropshippingProject.\n\nREQUERIDOS: images (1-5 fotos), language, country y creativeSetup. Ante \u201Ccrea una landing con este producto\u201D y una foto local, llama primero `request_media_upload` y usa los assetId. Pregunta price, offer y compareAtPrice cuando aplique; no asumas country/language.\n\nCONFIGURACI\xD3N: usa guided salvo delegaci\xF3n total expl\xEDcita. Presenta UN checklist compacto \u2014no preguntas seriales\u2014 con estrategia, formato, competencia, m\xE9todos de pago, contexto, direcci\xF3n creativa y headline/subheadline exactos opcionales. Prop\xF3n defaults recomendados; null significa que el usuario deleg\xF3 esa decisi\xF3n concreta, nunca que el agente la olvid\xF3.\n\nANTES de invocar, muestra el resumen y espera confirmaci\xF3n expl\xEDcita porque inicia trabajo pagado. guided pausa para revisar el \xE1ngulo; automatic equivale al toggle web \u201CDejar que la IA decida todo\u201D y omite la review.", LandingInput.shape, CREATES_EXTERNAL, async (input) => {
15166
+ server.tool("generate_landing", generateLandingGuidance.summary + " No expone landing editable/code mientras siga siendo admin-only. Devuelve un taskId. Esta solicitud es directa: nunca busques, reutilices ni menciones un DropshippingProject.\n\nREQUERIDOS: images (1-5 fotos), language, country y creativeSetup. Ante \u201Ccrea una landing con este producto\u201D y una foto local, llama primero `request_media_upload` y usa los assetId. Pregunta price, offer y compareAtPrice cuando aplique; no asumas country/language.\n\nCONFIGURACI\xD3N: usa guided salvo delegaci\xF3n total expl\xEDcita. Presenta UN checklist compacto \u2014no preguntas seriales\u2014 con estrategia, formato, competencia, m\xE9todos de pago, contexto, direcci\xF3n creativa y headline/subheadline exactos opcionales. Prop\xF3n defaults recomendados; null significa que el usuario deleg\xF3 esa decisi\xF3n concreta, nunca que el agente la olvid\xF3.\n\nANTES de invocar, muestra el resumen y espera confirmaci\xF3n expl\xEDcita porque inicia trabajo pagado. guided pausa para revisar el \xE1ngulo; automatic equivale al toggle web \u201CDejar que la IA decida todo\u201D y omite la review.", LandingInput.shape, CREATES_EXTERNAL, async (input) => {
14454
15167
  try {
14455
15168
  const guidedSetup = input.creativeSetup.mode === "guided" ? input.creativeSetup : null;
14456
15169
  const setupFields = guidedSetup ? resolveLandingSetup(guidedSetup) : {};
@@ -14480,6 +15193,8 @@ __name(registerCreativeTools, "registerCreativeTools");
14480
15193
 
14481
15194
  // src/tools/creative-variants.ts
14482
15195
  import { z as z53 } from "zod";
15196
+ var estimateCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("estimate_creative_variant");
15197
+ var createCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("create_creative_variant");
14483
15198
  var LandingSectionKeyEnum = z53.enum([
14484
15199
  "problem_hook",
14485
15200
  "problem_cost",
@@ -14707,7 +15422,7 @@ function estimatePayload(input) {
14707
15422
  }
14708
15423
  __name(estimatePayload, "estimatePayload");
14709
15424
  function registerCreativeVariantTools(server, api) {
14710
- server.tool("estimate_creative_variant", "Calcula el costo de crear una variante desde una tarea existente, sin consumir cr\xE9ditos. Usa el task origen para resolver el modelo/tier real y devuelve m\xEDnimo, m\xE1ximo, saldo y si alcanza. Para landing env\xEDa las mismas instructions/sectionKey que usar\xE1s al crear: si hay instructions sin sectionKey, el rango incluye la clasificaci\xF3n pagada con IA. Llama esto antes de create_creative_variant y muestra el rango al usuario.", EstimateCreativeVariantInputBase.shape, READS_INTERNAL, async (input) => {
15425
+ server.tool("estimate_creative_variant", estimateCreativeVariantGuidance.summary + " Usa el task origen para resolver el modelo/tier real y devuelve m\xEDnimo, m\xE1ximo, saldo y si alcanza. Para landing env\xEDa las mismas instructions/sectionKey que usar\xE1s al crear: si hay instructions sin sectionKey, el rango incluye la clasificaci\xF3n pagada con IA. Llama esto antes de create_creative_variant y muestra el rango al usuario.", EstimateCreativeVariantInputBase.shape, READS_INTERNAL, async (input) => {
14711
15426
  try {
14712
15427
  const validatedInput = parseVariantInput(EstimateCreativeVariantInput, input);
14713
15428
  const estimate = await api.postJson(`/v1/tasks/${encodeURIComponent(validatedInput.sourceTaskId)}/creative-variants/estimate`, estimatePayload(validatedInput));
@@ -14720,7 +15435,7 @@ function registerCreativeVariantTools(server, api) {
14720
15435
  throw translateError(error);
14721
15436
  }
14722
15437
  });
14723
- server.tool("create_creative_variant", "Crea un video, imagen o secci\xF3n de landing adicional dentro de una tarea existente. ES UNA ACCI\xD3N DE PAGO: primero llama estimate_creative_variant, muestra el rango de cr\xE9ditos al usuario y solo despu\xE9s invoca este tool con confirm=true. Las recomendaciones IA tambi\xE9n son acciones pagas a 2x del costo real. clientRequestId es obligatorio: genera un UUID por intenci\xF3n y reutiliza el mismo ante timeouts o transporte ambiguo. Si recibes status=error, pide confirmaci\xF3n para un intento nuevo y usa otro UUID. Reutiliza el producto y referencias del task origen; crea un child task independiente y devuelve variantTaskId. Para landing puedes enviar instructions en texto libre y omitir sectionKey: el backend lo clasifica con IA. No expone ni permite cambiar videoModel, videoTier, videoStyle o narrativeStyle; esos valores se heredan del task origen.", CreateCreativeVariantInputBase.shape, CREATES_EXTERNAL, async (input) => {
15438
+ server.tool("create_creative_variant", createCreativeVariantGuidance.summary + " ES UNA ACCI\xD3N DE PAGO: primero llama estimate_creative_variant, muestra el rango de cr\xE9ditos al usuario y solo despu\xE9s invoca este tool con confirm=true. Las recomendaciones IA tambi\xE9n son acciones pagas a 2x del costo real. clientRequestId es obligatorio: genera un UUID por intenci\xF3n y reutiliza el mismo ante timeouts o transporte ambiguo. Si recibes status=error, pide confirmaci\xF3n para un intento nuevo y usa otro UUID. Reutiliza el producto y referencias del task origen; crea un child task independiente y devuelve variantTaskId. Para landing puedes enviar instructions en texto libre y omitir sectionKey: el backend lo clasifica con IA. No expone ni permite cambiar videoModel, videoTier, videoStyle o narrativeStyle; esos valores se heredan del task origen.", CreateCreativeVariantInputBase.shape, CREATES_EXTERNAL, async (input) => {
14724
15439
  try {
14725
15440
  const validatedInput = parseVariantInput(CreateCreativeVariantInput, input);
14726
15441
  const estimateInput = EstimateCreativeVariantInput.parse({
@@ -14791,6 +15506,9 @@ __name(registerCreativeVariantTools, "registerCreativeVariantTools");
14791
15506
 
14792
15507
  // src/tools/script-review.ts
14793
15508
  import { z as z54 } from "zod";
15509
+ var getVideoScriptGuidance = getProductMakerCapabilityModelGuidance("get_video_script");
15510
+ var regenerateVideoScriptGuidance = getProductMakerCapabilityModelGuidance("regenerate_video_script");
15511
+ var approveVideoScriptGuidance = getProductMakerCapabilityModelGuidance("approve_video_script");
14794
15512
  var GetVideoScriptInput = z54.object({
14795
15513
  taskId: z54.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
14796
15514
  angleKey: z54.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.')
@@ -14814,7 +15532,7 @@ var RegenerateVideoScriptInput = z54.object({
14814
15532
  narrativeStyle: NarrativeStyleEnum.describe('Narrativa para el nuevo guion. Incluye valores hist\xF3ricos porque esta herramienta tambi\xE9n regenera tareas antiguas. REQUERIDO \u2014 el backend NO acepta "mantener la actual"; siempre tienes que elegir una. Si el usuario quiere re-rolear con la misma narrativa, pasa la que ya tiene el \xE1ngulo (la ves en get_video_script o en get_task_status).')
14815
15533
  }).strict();
14816
15534
  function registerScriptReviewTools(server, api) {
14817
- server.tool("get_video_script", 'Lee el guion del video pendiente de revisi\xF3n. S\xF3lo funciona cuando la tarea fue creada con `pauseForVideoScriptReview: true` (via generate_video_creative). Devuelve { status, angleKey, perClipSeconds?, scenes: [{ index, dialogue, maxWords, minWords?, beatId?, persuasiveFunction?, winnerVisual?, expectedVisual? }], error? }. En escenas copiadas de un winner, `persuasiveFunction` explica qu\xE9 logra ese beat, `winnerVisual` conserva la observaci\xF3n fuente y `expectedVisual` describe nuestra adaptaci\xF3n localizada; muestra los tres junto al di\xE1logo para que una edici\xF3n no desaline\xE9 guion y visual. El campo `error` (cuando viene con status="ready") es el motivo legible por el que un intento previo de aprobaci\xF3n fue rechazado; mu\xE9straselo al usuario.\n\nREGLA DURA: status="ready" es un estado terminal de interacci\xF3n. Presenta el guion una sola vez y NO vuelvas a llamar get_video_script ni get_task_status hasta que el usuario responda aprobar, editar o regenerar. La respuesta a\xF1ade interactionState="awaiting_user_review" y nextAction="present_script_and_wait".\n\nCU\xC1NDO LLAMAR: no existe un status top-level "awaiting_script_review" en get_task_status. Si la tarea fue creada con pauseForVideoScriptReview=true, polea get_task_status hasta que `status === "processing"` y llama este tool; su campo `status` es la se\xF1al real:\n- "ready" \u2192 presenta todas las escenas y DETENTE hasta la respuesta del usuario.\n- "not_ready" \u2192 espera 5\u201310s y reintenta este tool.\n- "approved" \u2192 ya fue aprobado; en multi-angle consulta otro angleKey.\n- "skipped" \u2192 el usuario opt\xF3 por no revisar.\n- "error" \u2192 usa `regenerate_video_script` como recuperaci\xF3n.\n\nMULTI-ANGLE: pasa angleKey expl\xEDcito. Cuando uno est\xE9 ready, detente en ese \xE1ngulo y espera la decisi\xF3n antes de consultar otro.', GetVideoScriptInput.shape, READS_INTERNAL, async (input) => {
15535
+ server.tool("get_video_script", getVideoScriptGuidance.summary + ' S\xF3lo funciona cuando la tarea fue creada con `pauseForVideoScriptReview: true` (via generate_video_creative). Devuelve { status, angleKey, perClipSeconds?, scenes: [{ index, dialogue, maxWords, minWords?, beatId?, persuasiveFunction?, winnerVisual?, expectedVisual? }], error? }. En escenas copiadas de un winner, `persuasiveFunction` explica qu\xE9 logra ese beat, `winnerVisual` conserva la observaci\xF3n fuente y `expectedVisual` describe nuestra adaptaci\xF3n localizada; muestra los tres junto al di\xE1logo para que una edici\xF3n no desaline\xE9 guion y visual. El campo `error` (cuando viene con status="ready") es el motivo legible por el que un intento previo de aprobaci\xF3n fue rechazado; mu\xE9straselo al usuario.\n\nREGLA DURA: status="ready" es un estado terminal de interacci\xF3n. Presenta el guion una sola vez y NO vuelvas a llamar get_video_script ni get_task_status hasta que el usuario responda aprobar, editar o regenerar. La respuesta a\xF1ade interactionState="awaiting_user_review" y nextAction="present_script_and_wait".\n\nCU\xC1NDO LLAMAR: no existe un status top-level "awaiting_script_review" en get_task_status. Si la tarea fue creada con pauseForVideoScriptReview=true, polea get_task_status hasta que `status === "processing"` y llama este tool; su campo `status` es la se\xF1al real:\n- "ready" \u2192 presenta todas las escenas y DETENTE hasta la respuesta del usuario.\n- "not_ready" \u2192 espera 5\u201310s y reintenta este tool.\n- "approved" \u2192 ya fue aprobado; en multi-angle consulta otro angleKey.\n- "skipped" \u2192 el usuario opt\xF3 por no revisar.\n- "error" \u2192 usa `regenerate_video_script` como recuperaci\xF3n.\n\nMULTI-ANGLE: pasa angleKey expl\xEDcito. Cuando uno est\xE9 ready, detente en ese \xE1ngulo y espera la decisi\xF3n antes de consultar otro.', GetVideoScriptInput.shape, READS_INTERNAL, async (input) => {
14818
15536
  try {
14819
15537
  const qs = input.angleKey ? `?angleKey=${encodeURIComponent(input.angleKey)}` : "";
14820
15538
  const r = await api.getJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script${qs}`);
@@ -14846,7 +15564,7 @@ function registerScriptReviewTools(server, api) {
14846
15564
  throw translateError(e);
14847
15565
  }
14848
15566
  });
14849
- server.tool("regenerate_video_script", 'Re-rolea (regenera) el guion del video pendiente de revisi\xF3n. \xDAsalo cuando:\n- get_video_script devuelve status="error" (el LLM fall\xF3 al redactar).\n- El usuario rechaza el guion entero y prefiere reintentar (no editar l\xEDnea por l\xEDnea).\n- Quieres probar otra narrativa (pasa `narrativeStyle`).\n\nFlujo: llama este tool \u2192 polea get_video_script hasta que vuelva a status="ready" \u2192 mu\xE9strale el nuevo guion al usuario \u2192 approve_video_script.\n\nIMPORTANTE: regenerar CUESTA cr\xE9ditos de LLM. No lo invoques sin confirmaci\xF3n del usuario salvo que el status sea "error" (sin retry el usuario no tiene salida).\n\nMULTI-ANGLE: pasa el mismo `angleKey` que usaste con get_video_script.', RegenerateVideoScriptInput.shape, OVERWRITES_EXTERNAL, async (input) => {
15567
+ server.tool("regenerate_video_script", regenerateVideoScriptGuidance.summary + ' \xDAsalo cuando:\n- get_video_script devuelve status="error" (el LLM fall\xF3 al redactar).\n- El usuario rechaza el guion entero y prefiere reintentar (no editar l\xEDnea por l\xEDnea).\n- Quieres probar otra narrativa (pasa `narrativeStyle`).\n\nFlujo: llama este tool \u2192 polea get_video_script hasta que vuelva a status="ready" \u2192 mu\xE9strale el nuevo guion al usuario \u2192 approve_video_script.\n\nIMPORTANTE: regenerar CUESTA cr\xE9ditos de LLM. No lo invoques sin confirmaci\xF3n del usuario salvo que el status sea "error" (sin retry el usuario no tiene salida).\n\nMULTI-ANGLE: pasa el mismo `angleKey` que usaste con get_video_script.', RegenerateVideoScriptInput.shape, OVERWRITES_EXTERNAL, async (input) => {
14850
15568
  try {
14851
15569
  await requirePaidCreativeConfirmation(server, `Regeneraci\xF3n pagada del guion con narrativa ${input.narrativeStyle}.`);
14852
15570
  const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/regenerate`, {
@@ -14860,7 +15578,7 @@ function registerScriptReviewTools(server, api) {
14860
15578
  });
14861
15579
  server.tool(
14862
15580
  "approve_video_script",
14863
- 'Aprueba el guion del video (con o sin ediciones). Tras llamarlo, el render arranca y consume cr\xE9ditos.\n\nREGLAS DURAS:\n1. INCLUYE EN scenes[] S\xD3LO las escenas que cambian. El backend mergea por `index` \u2014 las escenas no incluidas conservan su di\xE1logo original. scenes vac\xEDo es rechazado con 400, as\xED que si el usuario no quiere editar nada, manda AL MENOS UNA escena con su `dialogue` original (ser\xE1 un no-op sem\xE1ntico pero satisface el min(1)).\n2. Para cada entrada incluida cuyo `dialogue` difiera del original, el backend valida `minWords \u2264 words(dialogue) \u2264 maxWords` (los rangos vienen en get_video_script). Si te sales del rango, rechaza con 400 \u2014 NO recortes en silencio; p\xEDdele al usuario que ajuste.\n3. Escenas con LLM-original ya fuera de rango: el backend NO valida el rango POR-ESCENA si no las incluyes (la regla #2 s\xF3lo aplica a escenas editadas).\n4. TOPE GLOBAL: adem\xE1s del rango por-escena, el backend rechaza con 400 ("El guion no cabe en el tiempo del video. Ac\xF3rtalo y vuelve a aprobar.") cuando el TOTAL de palabras de TODO el guion (incluidas las escenas que no editaste) supera el cupo de la voz \u2014 porque ese guion se pasar\xEDa del tope de duraci\xF3n al renderizar. Si lo ves, acorta las escenas m\xE1s largas (la suma de `maxWords` de get_video_script es el techo total) y reintenta; o usa regenerate_video_script. Este 400 es s\xEDncrono y NO consume render.\n5. NUNCA inventes ni reescribas el guion por tu cuenta. Aplica los cambios del usuario al pie de la letra dentro de los l\xEDmites de palabras.\n6. MULTI-ANGLE: pasa el mismo `angleKey` que usaste en get_video_script. Si lo omites, el backend resuelve a "angle_0" literal \u2014 puedes aprobar un \xE1ngulo equivocado.\n\nDevuelve { status }. status="approved" confirma la aprobaci\xF3n; otro valor significa que la pausa sigue activa.',
15581
+ approveVideoScriptGuidance.summary + ' Tras llamarlo, el render arranca y consume cr\xE9ditos.\n\nREGLAS DURAS:\n1. INCLUYE EN scenes[] S\xD3LO las escenas que cambian. El backend mergea por `index` \u2014 las escenas no incluidas conservan su di\xE1logo original. scenes vac\xEDo es rechazado con 400, as\xED que si el usuario no quiere editar nada, manda AL MENOS UNA escena con su `dialogue` original (ser\xE1 un no-op sem\xE1ntico pero satisface el min(1)).\n2. Para cada entrada incluida cuyo `dialogue` difiera del original, el backend valida `minWords \u2264 words(dialogue) \u2264 maxWords` (los rangos vienen en get_video_script). Si te sales del rango, rechaza con 400 \u2014 NO recortes en silencio; p\xEDdele al usuario que ajuste.\n3. Escenas con LLM-original ya fuera de rango: el backend NO valida el rango POR-ESCENA si no las incluyes (la regla #2 s\xF3lo aplica a escenas editadas).\n4. TOPE GLOBAL: adem\xE1s del rango por-escena, el backend rechaza con 400 ("El guion no cabe en el tiempo del video. Ac\xF3rtalo y vuelve a aprobar.") cuando el TOTAL de palabras de TODO el guion (incluidas las escenas que no editaste) supera el cupo de la voz \u2014 porque ese guion se pasar\xEDa del tope de duraci\xF3n al renderizar. Si lo ves, acorta las escenas m\xE1s largas (la suma de `maxWords` de get_video_script es el techo total) y reintenta; o usa regenerate_video_script. Este 400 es s\xEDncrono y NO consume render.\n5. NUNCA inventes ni reescribas el guion por tu cuenta. Aplica los cambios del usuario al pie de la letra dentro de los l\xEDmites de palabras.\n6. MULTI-ANGLE: pasa el mismo `angleKey` que usaste en get_video_script. Si lo omites, el backend resuelve a "angle_0" literal \u2014 puedes aprobar un \xE1ngulo equivocado.\n\nDevuelve { status }. status="approved" confirma la aprobaci\xF3n; otro valor significa que la pausa sigue activa.',
14864
15582
  ApproveVideoScriptInput.shape,
14865
15583
  // Not CREATES_INTERNAL: approving the script is what STARTS the render, so
14866
15584
  // this call dispatches jobs to the video providers and burns credits. Its own
@@ -14887,8 +15605,13 @@ __name(registerScriptReviewTools, "registerScriptReviewTools");
14887
15605
 
14888
15606
  // src/tools/connections.ts
14889
15607
  import { z as z55 } from "zod";
15608
+ var listShopifyShopsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_shops");
15609
+ var listShopifyProductsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_products");
15610
+ var listMetaAdAccountsGuidance = getProductMakerCapabilityModelGuidance("list_meta_ad_accounts");
15611
+ var listMetaDestinationsGuidance = getProductMakerCapabilityModelGuidance("list_meta_destinations");
15612
+ var listTikTokAdvertisersGuidance = getProductMakerCapabilityModelGuidance("list_tiktok_advertisers");
14890
15613
  function registerConnectionTools(server, api) {
14891
- server.tool("list_shopify_shops", "Lista las tiendas Shopify conectadas por este usuario. Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.", z55.object({}).shape, READS_INTERNAL, async () => {
15614
+ server.tool("list_shopify_shops", listShopifyShopsGuidance.summary + " Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.", z55.object({}).shape, READS_INTERNAL, async () => {
14892
15615
  try {
14893
15616
  const r = await api.getJson("/v1/shopify/shops");
14894
15617
  return asText(r);
@@ -14896,7 +15619,7 @@ function registerConnectionTools(server, api) {
14896
15619
  throw translateError(e);
14897
15620
  }
14898
15621
  });
14899
- server.tool("list_shopify_products", 'Busca productos en una tienda Shopify conectada para elegir el target de una republicaci\xF3n. 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.', z55.object({
15622
+ server.tool("list_shopify_products", 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.', z55.object({
14900
15623
  shopDomain: z55.string().min(3).max(120),
14901
15624
  query: z55.string().trim().max(120).optional(),
14902
15625
  taskId: z55.string().uuid().optional()
@@ -14913,7 +15636,7 @@ function registerConnectionTools(server, api) {
14913
15636
  throw translateError(e);
14914
15637
  }
14915
15638
  });
14916
- server.tool("list_meta_ad_accounts", "Lista las cuentas publicitarias de Meta conectadas. 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.", z55.object({}).shape, READS_EXTERNAL, async () => {
15639
+ server.tool("list_meta_ad_accounts", 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.", z55.object({}).shape, READS_EXTERNAL, async () => {
14917
15640
  try {
14918
15641
  const r = await api.getJson("/v1/facebook/ad-accounts");
14919
15642
  return asText(r);
@@ -14922,7 +15645,7 @@ function registerConnectionTools(server, api) {
14922
15645
  }
14923
15646
  });
14924
15647
  server.registerTool("list_meta_destinations", {
14925
- description: "Lista los destinos de publicaci\xF3n de Meta del usuario: Business portfolios, cuentas publicitarias dentro de cada portfolio y Facebook Pages disponibles. 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.",
15648
+ 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.",
14926
15649
  inputSchema: z55.object({}).shape,
14927
15650
  annotations: READS_EXTERNAL,
14928
15651
  _meta: {
@@ -14936,7 +15659,7 @@ function registerConnectionTools(server, api) {
14936
15659
  throw translateError(e);
14937
15660
  }
14938
15661
  });
14939
- server.tool("list_tiktok_advertisers", "Lista las cuentas de TikTok Ads conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.", z55.object({}).shape, READS_EXTERNAL, async () => {
15662
+ server.tool("list_tiktok_advertisers", listTikTokAdvertisersGuidance.summary + " Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.", z55.object({}).shape, READS_EXTERNAL, async () => {
14940
15663
  try {
14941
15664
  const r = await api.getJson("/v1/tiktok/advertisers");
14942
15665
  return asText(r);
@@ -14949,8 +15672,9 @@ __name(registerConnectionTools, "registerConnectionTools");
14949
15672
 
14950
15673
  // src/tools/actors.ts
14951
15674
  import { z as z56 } from "zod";
15675
+ var listActorsGuidance = getProductMakerCapabilityModelGuidance("list_actors");
14952
15676
  function registerActorTools(server, api) {
14953
- server.tool("list_actors", "Lista los personajes guardados del usuario (personas creadas con IA que pueden protagonizar los videos e im\xE1genes). Devuelve id, nombre, descripci\xF3n, g\xE9nero y voz. 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.", z56.object({}).shape, READS_INTERNAL, async () => {
15677
+ server.tool("list_actors", listActorsGuidance.summary + " Devuelve id, nombre, descripci\xF3n, g\xE9nero y voz. 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.", z56.object({}).shape, READS_INTERNAL, async () => {
14954
15678
  try {
14955
15679
  const rows = await api.getJson("/v1/actors");
14956
15680
  const actors = (Array.isArray(rows) ? rows : []).filter((actor) => actor.status === "ready").map(({ id, name, description, gender, voiceId }) => ({
@@ -15023,6 +15747,10 @@ function deriveMetaPublishAttemptId(input) {
15023
15747
  __name(deriveMetaPublishAttemptId, "deriveMetaPublishAttemptId");
15024
15748
 
15025
15749
  // src/tools/publish.ts
15750
+ var publishToShopifyGuidance = getProductMakerCapabilityModelGuidance("publish_to_shopify");
15751
+ var publishToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_to_meta");
15752
+ var publishCreativesToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_creatives_to_meta");
15753
+ var publishToTikTokGuidance = getProductMakerCapabilityModelGuidance("publish_to_tiktok");
15026
15754
  var ShopifyInput = z57.object({
15027
15755
  taskId: z57.string().min(1),
15028
15756
  shopDomain: z57.string().min(1),
@@ -15312,7 +16040,7 @@ var TikTokInput = z57.object({
15312
16040
  });
15313
16041
  function registerPublishTools(server, api, opts = {}) {
15314
16042
  const transport = opts.transport ?? "http";
15315
- server.tool("publish_to_shopify", "Publica o actualiza la landing en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, OVERWRITES_EXTERNAL, async (input) => {
16043
+ server.tool("publish_to_shopify", publishToShopifyGuidance.summary + " Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, OVERWRITES_EXTERNAL, async (input) => {
15316
16044
  try {
15317
16045
  const parsed = parseShopifyInput(input);
15318
16046
  if (parsed.publicationMode === "existing" && !parsed.targetProductId) {
@@ -15350,7 +16078,7 @@ function registerPublishTools(server, api, opts = {}) {
15350
16078
  throw translateError(e);
15351
16079
  }
15352
16080
  });
15353
- server.tool("publish_to_meta", "Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.", MetaInput.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
16081
+ server.tool("publish_to_meta", publishToMetaGuidance.summary + " El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.", MetaInput.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
15354
16082
  try {
15355
16083
  const parsed = MetaInput.safeParse(rawInput);
15356
16084
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -15387,7 +16115,7 @@ function registerPublishTools(server, api, opts = {}) {
15387
16115
  throw translateError(e);
15388
16116
  }
15389
16117
  });
15390
- server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, conserva los assets confirmados, procesa s\xF3lo los pendientes y publica \xFAnicamente assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla antes de enviar una mutaci\xF3n a Meta, quedan durante el TTL. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.', PublishCreativesToMetaInputBase.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
16118
+ server.tool("publish_creatives_to_meta", publishCreativesToMetaGuidance.summary + ' Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, conserva los assets confirmados, procesa s\xF3lo los pendientes y publica \xFAnicamente assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla antes de enviar una mutaci\xF3n a Meta, quedan durante el TTL. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.', PublishCreativesToMetaInputBase.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
15391
16119
  try {
15392
16120
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
15393
16121
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -15483,7 +16211,7 @@ function registerPublishTools(server, api, opts = {}) {
15483
16211
  throw translateError(e);
15484
16212
  }
15485
16213
  });
15486
- server.tool("publish_to_tiktok", "Crea una campa\xF1a de TikTok Ads desde la tarea. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en TikTok Ads Manager. Use `list_tiktok_advertisers` antes para un advertiserId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 ej. [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario SIEMPRE en d\xF3lares, ej. 25 \u2014 nunca el n\xFAmero en moneda local), `targetCountries` (c\xF3digos ISO-2, primero se usa), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. Despu\xE9s de publicar usa `get_tiktok_campaign_status` para chequear el estado.", TikTokInput.shape, CREATES_EXTERNAL, async ({ taskId, campaignConfig, primaryText, ...rest }) => {
16214
+ server.tool("publish_to_tiktok", publishToTikTokGuidance.summary + " La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en TikTok Ads Manager. Use `list_tiktok_advertisers` antes para un advertiserId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 ej. [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario SIEMPRE en d\xF3lares, ej. 25 \u2014 nunca el n\xFAmero en moneda local), `targetCountries` (c\xF3digos ISO-2, primero se usa), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. Despu\xE9s de publicar usa `get_tiktok_campaign_status` para chequear el estado.", TikTokInput.shape, CREATES_EXTERNAL, async ({ taskId, campaignConfig, primaryText, ...rest }) => {
15487
16215
  try {
15488
16216
  const body = {
15489
16217
  ...rest
@@ -15574,6 +16302,7 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
15574
16302
  // src/tools/meta-upload.ts
15575
16303
  import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
15576
16304
  import { z as z58 } from "zod";
16305
+ var requestMediaUploadGuidance = getProductMakerCapabilityModelGuidance("request_media_upload");
15577
16306
  var UploadSessionInputBase = z58.object({
15578
16307
  mediaKind: z58.enum([
15579
16308
  "image",
@@ -15707,7 +16436,7 @@ __name(resolveEndpointForSession, "resolveEndpointForSession");
15707
16436
  function registerMediaUploadTools(server, api) {
15708
16437
  registerAppTool(server, "request_media_upload", {
15709
16438
  title: "Subir archivos",
15710
- description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Cuando la foto o el video sea el siguiente dato faltante, ll\xE1malo en esa misma respuesta: no te limites a pedir que lo adjunte en el chat y esperar otro turno. Ll\xE1malo tambi\xE9n autom\xE1ticamente y antes de preguntar por campos faltantes adicionales cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. El App NO te avisa cuando termina: en cuanto el usuario diga que ya subi\xF3 el archivo o que sigas, llama `check_media_upload` con este sessionId para obtener los assetId, y reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
16439
+ description: requestMediaUploadGuidance.summary + " Cuando la foto o el video sea el siguiente dato faltante, ll\xE1malo en esa misma respuesta: no te limites a pedir que lo adjunte en el chat y esperar otro turno. Ll\xE1malo tambi\xE9n autom\xE1ticamente y antes de preguntar por campos faltantes adicionales cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. El App NO te avisa cuando termina: en cuanto el usuario diga que ya subi\xF3 el archivo o que sigas, llama `check_media_upload` con este sessionId para obtener los assetId, y reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
15711
16440
  inputSchema: UploadSessionToolInputShape,
15712
16441
  annotations: CREATES_INTERNAL,
15713
16442
  outputSchema: UniversalUploadStructuredResponse.shape,
@@ -15767,6 +16496,8 @@ __name(registerMediaUploadTools, "registerMediaUploadTools");
15767
16496
 
15768
16497
  // src/tools/campaigns.ts
15769
16498
  import { z as z59 } from "zod";
16499
+ var getMetaCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_meta_campaign_status");
16500
+ var getTikTokCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_tiktok_campaign_status");
15770
16501
  var MetaStatusInput = z59.object({
15771
16502
  campaignId: z59.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
15772
16503
  });
@@ -15775,7 +16506,7 @@ var TikTokStatusInput = z59.object({
15775
16506
  advertiserId: z59.string().min(1).describe("The same advertiserId used to publish the campaign.")
15776
16507
  });
15777
16508
  function registerCampaignTools(server, api) {
15778
- server.tool("get_meta_campaign_status", "Consulta el estado actual + m\xE9tricas lifetime (spend, impressions, clicks, reach) de una campa\xF1a Meta publicada. Llama esto DESPU\xC9S de `publish_to_meta` para confirmar que la campa\xF1a se cre\xF3 correctamente y para reportar performance al usuario. Es read-only \u2014 no modifica nada en Meta Ads Manager.", MetaStatusInput.shape, READS_EXTERNAL, async (input) => {
16509
+ server.tool("get_meta_campaign_status", getMetaCampaignStatusGuidance.summary + " Llama esto DESPU\xC9S de `publish_to_meta` para confirmar que la campa\xF1a se cre\xF3 correctamente y para reportar performance al usuario. Es read-only \u2014 no modifica nada en Meta Ads Manager.", MetaStatusInput.shape, READS_EXTERNAL, async (input) => {
15779
16510
  try {
15780
16511
  const r = await api.getJson(`/v1/facebook/campaigns/${encodeURIComponent(input.campaignId)}`);
15781
16512
  return renderMetaStatusMarkdown(r);
@@ -15783,7 +16514,7 @@ function registerCampaignTools(server, api) {
15783
16514
  throw translateError(e);
15784
16515
  }
15785
16516
  });
15786
- server.tool("get_tiktok_campaign_status", "Consulta el estado actual + reporte lifetime (spend, impressions, clicks, reach) de una campa\xF1a TikTok publicada. Requiere `advertiserId` (el mismo con el que se cre\xF3). Llama esto DESPU\xC9S de `publish_to_tiktok` para confirmar creaci\xF3n correcta y reportar performance. Es read-only.", TikTokStatusInput.shape, READS_EXTERNAL, async (input) => {
16517
+ server.tool("get_tiktok_campaign_status", getTikTokCampaignStatusGuidance.summary + " Requiere `advertiserId` (el mismo con el que se cre\xF3). Llama esto DESPU\xC9S de `publish_to_tiktok` para confirmar creaci\xF3n correcta y reportar performance. Es read-only.", TikTokStatusInput.shape, READS_EXTERNAL, async (input) => {
15787
16518
  try {
15788
16519
  const r = await api.getJson(`/v1/tiktok/campaigns/${encodeURIComponent(input.campaignId)}?advertiserId=${encodeURIComponent(input.advertiserId)}`);
15789
16520
  return renderTikTokStatusMarkdown(r);
@@ -16219,6 +16950,7 @@ __name(calculateProductCosting, "calculateProductCosting");
16219
16950
 
16220
16951
  // src/tools/product-costing.ts
16221
16952
  import { z as z62 } from "zod";
16953
+ var costProductGuidance = getProductMakerCapabilityModelGuidance("cost_product");
16222
16954
  var optionalAmount = /* @__PURE__ */ __name((description) => z62.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
16223
16955
  var optionalPercent = /* @__PURE__ */ __name((description) => z62.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
16224
16956
  var ProductCostingToolInput = z62.object({
@@ -16236,7 +16968,7 @@ var ProductCostingToolInput = z62.object({
16236
16968
  desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar el valor gu\xEDa editable del pa\xEDs.")
16237
16969
  });
16238
16970
  function registerProductCostingTools(server) {
16239
- server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en cualquiera de los pa\xEDses disponibles en ProductMaker. Ll\xE1mala siempre que el usuario pida calcular cu\xE1nto cobrar, la rentabilidad o el margen de un producto concreto; no hagas el c\xE1lculo ni reconstruyas la f\xF3rmula por tu cuenta. Es READ-ONLY: no crea tareas ni publica nada. Pide country y el costo real del proveedor; los dem\xE1s campos pueden usar valores gu\xEDa editables del pa\xEDs. Devuelve `price` y `compareAtPrice` listos para `create_product_task`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, READS_INTERNAL, async (input) => {
16971
+ server.tool("cost_product", costProductGuidance.summary + " Ll\xE1mala siempre que el usuario pida calcular cu\xE1nto cobrar, la rentabilidad o el margen de un producto concreto; no hagas el c\xE1lculo ni reconstruyas la f\xF3rmula por tu cuenta. Es READ-ONLY: no crea tareas ni publica nada. Pide country y el costo real del proveedor; los dem\xE1s campos pueden usar valores gu\xEDa editables del pa\xEDs. Devuelve `price` y `compareAtPrice` listos para `create_product_task`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, READS_INTERNAL, async (input) => {
16240
16972
  if (input.country !== void 0 && input.country !== "CO" && [
16241
16973
  input.supplierCostCop,
16242
16974
  input.shippingCostCop,
@@ -16448,6 +17180,9 @@ function renderResearchReport(run) {
16448
17180
  __name(renderResearchReport, "renderResearchReport");
16449
17181
 
16450
17182
  // src/tools/product-research.ts
17183
+ var findWinningProductsGuidance = getProductMakerCapabilityModelGuidance("find_winning_products");
17184
+ var getProductResearchRunGuidance = getProductMakerCapabilityModelGuidance("get_product_research_run");
17185
+ var listProductResearchRunsGuidance = getProductMakerCapabilityModelGuidance("list_product_research_runs");
16451
17186
  var PRICE_CREDITS = 2e3;
16452
17187
  var TERMINAL2 = /* @__PURE__ */ new Set([
16453
17188
  "ready",
@@ -16491,7 +17226,7 @@ async function pollUntilTerminal(api, runId, waitSeconds) {
16491
17226
  }
16492
17227
  __name(pollUntilTerminal, "pollUntilTerminal");
16493
17228
  function registerProductResearchTools(server, api) {
16494
- server.tool("find_winning_products", `Busca productos ganadores de dropshipping para un pa\xEDs y categor\xEDa, y explica POR QU\xC9 la IA eligi\xF3 cada uno (razones, riesgos y evidencia real de anuncios en Meta Ads). CUESTA ${PRICE_CREDITS} CR\xC9DITOS por ejecuci\xF3n. ANTES de invocar, dile al usuario el costo y espera su aceptaci\xF3n expl\xEDcita; s\xF3lo entonces pasa confirmed: true. Consulta primero list_product_research_runs (GRATIS) \u2014 puede que el usuario ya tenga un reporte pagado para ese pa\xEDs/categor\xEDa y no necesites cobrar de nuevo. Pa\xEDses disponibles: CO, MX, PE, EC, CL. Tarda varios minutos: usa waitSeconds para esperar, y si expira consulta el resultado con get_product_research_run \u2014 el reporte NO se pierde.`, FindInput.shape, CREATES_EXTERNAL, async (input) => {
17229
+ server.tool("find_winning_products", `${findWinningProductsGuidance.summary} CUESTA ${PRICE_CREDITS} CR\xC9DITOS por ejecuci\xF3n. ANTES de invocar, dile al usuario el costo y espera su aceptaci\xF3n expl\xEDcita; s\xF3lo entonces pasa confirmed: true. Consulta primero list_product_research_runs (GRATIS) \u2014 puede que el usuario ya tenga un reporte pagado para ese pa\xEDs/categor\xEDa y no necesites cobrar de nuevo. Pa\xEDses disponibles: CO, MX, PE, EC, CL. Tarda varios minutos: usa waitSeconds para esperar, y si expira consulta el resultado con get_product_research_run \u2014 el reporte NO se pierde.`, FindInput.shape, CREATES_EXTERNAL, async (input) => {
16495
17230
  const parsed = FindInput.parse(input);
16496
17231
  if (!parsed.confirmed) {
16497
17232
  throw mcp(McpErrorCode.INVALID_INPUT, `This research run costs ${PRICE_CREDITS} credits. Tell the user the cost, get an explicit yes, then call again with confirmed: true.`);
@@ -16525,7 +17260,7 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
16525
17260
  throw translateError(error);
16526
17261
  }
16527
17262
  });
16528
- server.tool("get_product_research_run", "Devuelve el reporte completo de una investigaci\xF3n de productos ganadores: score, veredicto, confianza, razones, riesgos y evidencia de Meta Ads por producto. GRATIS y read-only. \xDAsalo cuando find_winning_products expir\xF3 la espera, o para releer un reporte anterior encontrado con list_product_research_runs.", GetInput.shape, READS_INTERNAL, async (input) => {
17263
+ server.tool("get_product_research_run", getProductResearchRunGuidance.summary + " GRATIS y read-only. \xDAsalo cuando find_winning_products expir\xF3 la espera, o para releer un reporte anterior encontrado con list_product_research_runs.", GetInput.shape, READS_INTERNAL, async (input) => {
16529
17264
  const parsed = GetInput.parse(input);
16530
17265
  try {
16531
17266
  const run = await api.getJson(`/v1/product-research/runs/${parsed.runId}`);
@@ -16545,7 +17280,7 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
16545
17280
  throw translateError(error);
16546
17281
  }
16547
17282
  });
16548
- server.tool("list_product_research_runs", "Lista las investigaciones de productos ganadores ya pagadas por el usuario, de la m\xE1s reciente a la m\xE1s antigua. GRATIS y read-only. Cons\xFAltalo ANTES de lanzar una nueva: un reporte ya pagado se relee gratis con get_product_research_run.", ListInput.shape, READS_INTERNAL, async (input) => {
17283
+ server.tool("list_product_research_runs", listProductResearchRunsGuidance.summary + " GRATIS y read-only. Cons\xFAltalo ANTES de lanzar una nueva: un reporte ya pagado se relee gratis con get_product_research_run.", ListInput.shape, READS_INTERNAL, async (input) => {
16549
17284
  const parsed = ListInput.parse(input);
16550
17285
  try {
16551
17286
  const runs = await api.getJson(`/v1/product-research/runs?limit=${parsed.limit}`);
@@ -16559,6 +17294,9 @@ __name(registerProductResearchTools, "registerProductResearchTools");
16559
17294
 
16560
17295
  // src/tools/dropshipping-projects.ts
16561
17296
  import { z as z64 } from "zod";
17297
+ var startDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("start_dropshipping_project");
17298
+ var costDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("cost_dropshipping_project");
17299
+ var guideDropshippingLaunchGuidance = getProductMakerCapabilityModelGuidance("guide_dropshipping_launch");
16562
17300
  var ProjectId = z64.string().uuid();
16563
17301
  var DropshippingProjectStatus = z64.enum([
16564
17302
  "active",
@@ -16788,7 +17526,7 @@ function projectText(raw) {
16788
17526
  }
16789
17527
  __name(projectText, "projectText");
16790
17528
  function registerDropshippingProjectTools(server, api) {
16791
- server.tool("start_dropshipping_project", "Inicia o reanuda un proyecto opcional de dropshipping. \xDAsalo cuando el usuario quiera seguir un producto hasta publicarlo; para una operaci\xF3n puntual usa el tool directo. La creaci\xF3n es idempotente y no cobra cr\xE9ditos.", StartProjectInput.shape, CREATES_INTERNAL, async (input) => {
17529
+ server.tool("start_dropshipping_project", startDropshippingProjectGuidance.summary + " \xDAsalo cuando el usuario quiera seguir un producto hasta publicarlo; para una operaci\xF3n puntual usa el tool directo. La creaci\xF3n es idempotente y no cobra cr\xE9ditos.", StartProjectInput.shape, CREATES_INTERNAL, async (input) => {
16792
17530
  try {
16793
17531
  return projectText(await api.postJson("/v1/dropshipping-projects", input));
16794
17532
  } catch (error) {
@@ -16797,7 +17535,7 @@ function registerDropshippingProjectTools(server, api) {
16797
17535
  });
16798
17536
  server.tool(
16799
17537
  "cost_dropshipping_project",
16800
- "Guarda el costeo de un proyecto guiado. Escribe solo inputs; el backend recalcula el resultado can\xF3nico y controla la versi\xF3n. Pide al usuario el costo real del proveedor: nunca lo inventes. No crea tarea ni publica nada.",
17538
+ costDropshippingProjectGuidance.summary + " Escribe solo inputs; el backend recalcula el resultado can\xF3nico y controla la versi\xF3n. Pide al usuario el costo real del proveedor: nunca lo inventes. No crea tarea ni publica nada.",
16801
17539
  CostProjectInput.shape,
16802
17540
  // Overwrites the costing inputs already saved on the project — the same
16803
17541
  // "previous values are gone" case as edit_task_draft, so destructive.
@@ -16819,7 +17557,7 @@ function registerDropshippingProjectTools(server, api) {
16819
17557
  }
16820
17558
  }
16821
17559
  );
16822
- server.tool("guide_dropshipping_launch", "Gu\xEDa read-only de un recorrido COMPLETO de dropshipping, desde producto/costeo hasta creativos, tienda y publicidad. No uses este tool para subir o publicar un creativo puntual en Meta: llama directamente `request_media_upload`, `list_meta_destinations` y `publish_creatives_to_meta`. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, READS_INTERNAL, async (input) => {
17560
+ server.tool("guide_dropshipping_launch", guideDropshippingLaunchGuidance.summary + " No uses este tool para subir o publicar un creativo puntual en Meta: llama directamente `request_media_upload`, `list_meta_destinations` y `publish_creatives_to_meta`. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, READS_INTERNAL, async (input) => {
16823
17561
  try {
16824
17562
  if (!input.projectId) {
16825
17563
  return asText({
@@ -16840,6 +17578,9 @@ __name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
16840
17578
 
16841
17579
  // src/tools/daily-control.ts
16842
17580
  import { z as z65 } from "zod";
17581
+ var getDailyControlGuidance = getProductMakerCapabilityModelGuidance("get_daily_control");
17582
+ var linkCampaignToProjectGuidance = getProductMakerCapabilityModelGuidance("link_campaign_to_project");
17583
+ var reportDailyReturnsGuidance = getProductMakerCapabilityModelGuidance("report_daily_returns");
16843
17584
  var ProviderCampaignIdentifier = z65.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
16844
17585
  var DateOnly = z65.string().regex(/^\d{4}-\d{2}-\d{2}$/);
16845
17586
  var DailyControlInput = z65.object({
@@ -16916,7 +17657,7 @@ __name(withWarnings, "withWarnings");
16916
17657
  function registerDailyControlTools(server, api) {
16917
17658
  server.tool(
16918
17659
  "get_daily_control",
16919
- 'Control diario de rentabilidad por producto: compara el costo real por venta contra lo que el producto puede aguantar, con sem\xE1foro (verde/\xE1mbar/rojo/ninguno) y veredicto (escalar, mantener, matar, muestra insuficiente). Es READ-ONLY sobre publicidad: nunca pausa, activa ni modifica ninguna campa\xF1a \u2014 esa decisi\xF3n es siempre del usuario. Si un producto vuelve con `status` distinto de "ok", NO tiene m\xE9tricas (`days` vac\xEDo, `verdict` null): explica el motivo (`statusMessage`) al usuario y no inventes ni estimes n\xFAmeros para reemplazarlo. Cuando un d\xEDa trae `returnedSource: "estimated"`, esa cifra de devoluciones NO fue medida \u2014 asume la tasa de devoluci\xF3n que el usuario configur\xF3 al costear el producto; dilo as\xED y ofrece registrar el dato real con report_daily_returns. `status: "no_campaign_linked"` significa que ese producto no tiene ninguna campa\xF1a vinculada: no hay gasto que leer, as\xED que NO existe costo por venta \u2014 ofrece link_campaign_to_project si el usuario est\xE1 pautando por fuera. Un d\xEDa con `cancelledSource: "shopify_settling"` est\xE1 medido pero todav\xEDa no cerrado (las cancelaciones de contra-entrega llegan d\xEDas despu\xE9s): ese resultado solo puede empeorar, no lo presentes como definitivo. Y `cancelledSource: "estimated"` es lo NORMAL en contra-entrega con fulfillment por fuera: la cancelaci\xF3n ocurre por WhatsApp o en la puerta y Shopify nunca se entera, as\xED que esa cifra asume la tasa de cancelaci\xF3n que el usuario configur\xF3 al costear \u2014 dilo as\xED y no la presentes como medida.',
17660
+ getDailyControlGuidance.summary + ' Compara el costo real por venta contra lo que el producto puede aguantar, con sem\xE1foro (verde/\xE1mbar/rojo/ninguno) y veredicto (escalar, mantener, matar, muestra insuficiente). Es READ-ONLY sobre publicidad: nunca pausa, activa ni modifica ninguna campa\xF1a \u2014 esa decisi\xF3n es siempre del usuario. Si un producto vuelve con `status` distinto de "ok", NO tiene m\xE9tricas (`days` vac\xEDo, `verdict` null): explica el motivo (`statusMessage`) al usuario y no inventes ni estimes n\xFAmeros para reemplazarlo. Cuando un d\xEDa trae `returnedSource: "estimated"`, esa cifra de devoluciones NO fue medida \u2014 asume la tasa de devoluci\xF3n que el usuario configur\xF3 al costear el producto; dilo as\xED y ofrece registrar el dato real con report_daily_returns. `status: "no_campaign_linked"` significa que ese producto no tiene ninguna campa\xF1a vinculada: no hay gasto que leer, as\xED que NO existe costo por venta \u2014 ofrece link_campaign_to_project si el usuario est\xE1 pautando por fuera. Un d\xEDa con `cancelledSource: "shopify_settling"` est\xE1 medido pero todav\xEDa no cerrado (las cancelaciones de contra-entrega llegan d\xEDas despu\xE9s): ese resultado solo puede empeorar, no lo presentes como definitivo. Y `cancelledSource: "estimated"` es lo NORMAL en contra-entrega con fulfillment por fuera: la cancelaci\xF3n ocurre por WhatsApp o en la puerta y Shopify nunca se entera, as\xED que esa cifra asume la tasa de cancelaci\xF3n que el usuario configur\xF3 al costear \u2014 dilo as\xED y no la presentes como medida.',
16920
17661
  DailyControlInput.shape,
16921
17662
  // Reads live spend from Meta/TikTok and orders from Shopify through our API;
16922
17663
  // it cannot modify anything on any of them.
@@ -16938,7 +17679,7 @@ function registerDailyControlTools(server, api) {
16938
17679
  );
16939
17680
  server.tool(
16940
17681
  "link_campaign_to_project",
16941
- "Ata una campa\xF1a publicitaria que el usuario lanz\xF3 por fuera de ProductMaker a un proyecto de dropshipping, para que su gasto entre al control diario (get_daily_control). Es la \xFAnica acci\xF3n de este grupo que escribe algo, pero solo vincula un identificador \u2014 nunca crea, pausa ni activa ninguna campa\xF1a real en Meta o TikTok. El servidor verifica con el token del usuario que la campa\xF1a vive en una de sus cuentas antes de vincularla; si no la ve, responde 404. Si la campa\xF1a ya est\xE1 vinculada a otro proyecto, responde 409 y no reasigna nada. Para Meta, el servidor deriva la cuenta publicitaria de la campa\xF1a e ignora el `accountId` recibido.",
17682
+ linkCampaignToProjectGuidance.summary + " Es la \xFAnica acci\xF3n de este grupo que escribe algo, pero solo vincula un identificador \u2014 nunca crea, pausa ni activa ninguna campa\xF1a real en Meta o TikTok. El servidor verifica con el token del usuario que la campa\xF1a vive en una de sus cuentas antes de vincularla; si no la ve, responde 404. Si la campa\xF1a ya est\xE1 vinculada a otro proyecto, responde 409 y no reasigna nada. Para Meta, el servidor deriva la cuenta publicitaria de la campa\xF1a e ignora el `accountId` recibido.",
16942
17683
  LinkCampaignInput.shape,
16943
17684
  // Creates one internal link row; never creates/pauses/activates a real
16944
17685
  // campaign, but the server verifies the campaign against the provider.
@@ -16954,7 +17695,7 @@ function registerDailyControlTools(server, api) {
16954
17695
  );
16955
17696
  server.tool(
16956
17697
  "report_daily_returns",
16957
- 'Registra cu\xE1ntos pedidos de un producto se devolvieron en un d\xEDa concreto. Es el \xFAnico dato del control diario que ProductMaker no puede ver por s\xED solo: quien maneja la log\xEDstica de contra-entrega es la transportadora, no la tienda. Sin este reporte, get_daily_control usa la tasa de devoluci\xF3n estimada del costeo (marcada `returnedSource: "estimated"`) en vez de un n\xFAmero medido. P\xEDdele el n\xFAmero exacto al usuario \u2014 nunca lo inventes ni lo deduzcas de otras m\xE9tricas.',
17698
+ reportDailyReturnsGuidance.summary + ' Es el \xFAnico dato del control diario que ProductMaker no puede ver por s\xED solo: quien maneja la log\xEDstica de contra-entrega es la transportadora, no la tienda. Sin este reporte, get_daily_control usa la tasa de devoluci\xF3n estimada del costeo (marcada `returnedSource: "estimated"`) en vez de un n\xFAmero medido. P\xEDdele el n\xFAmero exacto al usuario \u2014 nunca lo inventes ni lo deduzcas de otras m\xE9tricas.',
16958
17699
  ReportReturnsInput.shape,
16959
17700
  // PUT: replaces whatever number was reported for that product-day.
16960
17701
  OVERWRITES_INTERNAL,