@productmaker/mcp 2.3.2 → 2.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/stdio.js CHANGED
@@ -150,10 +150,60 @@ var ApiClient = class {
150
150
  };
151
151
 
152
152
  // src/index.ts
153
- var MCP_VERSION = "2.3.2";
153
+ var MCP_VERSION = "2.8.3";
154
+
155
+ // src/meta-upload-app.ts
156
+ import { readFileSync } from "fs";
157
+ import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
158
+ var META_UPLOAD_APP_RESOURCE_URI = "ui://meta-upload/v1.html";
159
+ var META_UPLOAD_APP_NAME = "Meta upload app";
160
+ var META_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
161
+ var META_UPLOAD_APP_CSP = {
162
+ connectDomains: [
163
+ "https://productmaker.app",
164
+ "https://www.productmaker.app",
165
+ "https://*.r2.cloudflarestorage.com",
166
+ "https://*.oaiusercontent.com"
167
+ ]
168
+ };
169
+ function readMetaUploadAppHtml(htmlUrl = META_UPLOAD_APP_HTML_URL) {
170
+ try {
171
+ return readFileSync(htmlUrl, "utf8");
172
+ } catch (error) {
173
+ const message = error instanceof Error ? error.message : String(error);
174
+ throw new Error(`Missing packaged Meta upload app HTML at ${htmlUrl.pathname}. Rebuild @productmaker/mcp so ${META_UPLOAD_APP_RESOURCE_URI} is bundled. Original error: ${message}`);
175
+ }
176
+ }
177
+ __name(readMetaUploadAppHtml, "readMetaUploadAppHtml");
178
+ function registerMetaUploadAppResource(server) {
179
+ const html = readMetaUploadAppHtml();
180
+ registerAppResource(server, META_UPLOAD_APP_NAME, META_UPLOAD_APP_RESOURCE_URI, {
181
+ title: "Meta upload app",
182
+ mimeType: RESOURCE_MIME_TYPE,
183
+ _meta: {
184
+ ui: {
185
+ csp: META_UPLOAD_APP_CSP
186
+ }
187
+ }
188
+ }, async () => ({
189
+ contents: [
190
+ {
191
+ uri: META_UPLOAD_APP_RESOURCE_URI,
192
+ mimeType: RESOURCE_MIME_TYPE,
193
+ text: html,
194
+ _meta: {
195
+ ui: {
196
+ csp: META_UPLOAD_APP_CSP
197
+ }
198
+ }
199
+ }
200
+ ]
201
+ }));
202
+ }
203
+ __name(registerMetaUploadAppResource, "registerMetaUploadAppResource");
154
204
 
155
205
  // src/tools/tasks.ts
156
- import { z as z43 } from "zod";
206
+ import { z as z45 } from "zod";
157
207
 
158
208
  // src/resolve-image.ts
159
209
  import { lookup as dnsLookup } from "dns/promises";
@@ -1042,6 +1092,8 @@ var Angle = z6.object({
1042
1092
  buyerPersona: z6.string().min(1).max(360),
1043
1093
  /** Character description for video (role + appearance + attitude). Drives voice selection and Frame0Spec. */
1044
1094
  actor: z6.string().min(1).max(360).optional(),
1095
+ /** Saved custom actor (Actor.id) resolved for this angle. Its approved photo anchors the identity. */
1096
+ actorId: z6.string().optional(),
1045
1097
  // Binary only: voice catalog has no non-binary voices.
1046
1098
  // See packages/angles/src/index.ts + getAngles coercion in angle.repository.ts.
1047
1099
  gender: z6.enum([
@@ -1632,6 +1684,8 @@ var IngestOptions = z14.object({
1632
1684
  generateAdCopy: z14.boolean().default(true),
1633
1685
  generateAiMusic: z14.boolean().optional(),
1634
1686
  voiceId: VoiceId.optional(),
1687
+ /** Global saved custom actor (Actor.id). Default for all angles. */
1688
+ actorId: z14.string().optional(),
1635
1689
  isCombo: z14.boolean().default(false),
1636
1690
  productContext: z14.string().transform((value) => value.trim()).pipe(z14.string().max(PRODUCT_CONTEXT_MAX_CHARS)).optional(),
1637
1691
  creativeDirection: z14.string().transform((value) => value.trim()).pipe(z14.string().max(500)).optional(),
@@ -1646,7 +1700,7 @@ var IngestOptions = z14.object({
1646
1700
  "landing",
1647
1701
  "video",
1648
1702
  "image"
1649
- ])).min(2).max(3).optional(),
1703
+ ])).min(1).max(3).optional(),
1650
1704
  // Landing page output: 'classic' = image-based (default), 'code' = structured
1651
1705
  // code landing rendered by the theme app extension. Absent ⇒ classic.
1652
1706
  landingMode: LandingModeSchema.optional(),
@@ -1697,7 +1751,13 @@ var VideoOptionsOverride = z14.object({
1697
1751
  videoResolution: VideoResolution.optional(),
1698
1752
  voiceId: VoiceId.optional(),
1699
1753
  /** Actor/character description (role + appearance + attitude). Drives voice and video character. */
1700
- actor: z14.string().max(360).optional()
1754
+ actor: z14.string().max(360).optional(),
1755
+ /** Saved custom actor (Actor.id). Its approved photo anchors the identity.
1756
+ * `null` is an explicit REMOVAL (the user cleared the picker) and must be
1757
+ * sent as such: an omitted key means "not mentioned", which every writer
1758
+ * reads as "leave the stored selection alone". Same contract as
1759
+ * service-angles.controller.ts. */
1760
+ actorId: z14.string().nullable().optional()
1701
1761
  });
1702
1762
  var AngleVideoOptions = VideoOptionsOverride.extend({
1703
1763
  angleIndex: z14.number().int().nonnegative(),
@@ -1708,7 +1768,11 @@ var AngleVideoOptions = VideoOptionsOverride.extend({
1708
1768
  /** Per-angle buyer persona override (describes the target audience) */
1709
1769
  buyerPersona: z14.string().max(360).optional(),
1710
1770
  /** Per-angle actor/character description (role + appearance + attitude). Drives voice and video character. */
1711
- actor: z14.string().max(360).optional()
1771
+ actor: z14.string().max(360).optional(),
1772
+ /** Per-angle saved custom actor (Actor.id). Overrides the global actorId.
1773
+ * `null` = the user removed the personaje on this angle; it must NOT fall
1774
+ * back to the global default. Omitted = not mentioned. */
1775
+ actorId: z14.string().nullable().optional()
1712
1776
  });
1713
1777
  var RapidScriptBlueprintShotForApproval = z14.object({
1714
1778
  id: z14.string().min(1),
@@ -1771,6 +1835,8 @@ var IngestResumePayload = z14.object({
1771
1835
  voiceId: VoiceId.optional(),
1772
1836
  /** Global actor/character description (role + appearance + attitude). Default for all angles. */
1773
1837
  actor: z14.string().max(360).optional(),
1838
+ /** Global saved custom actor (Actor.id). Default for all angles. */
1839
+ actorId: z14.string().optional(),
1774
1840
  /** Custom angle override for landing/creative (uses this instead of first angle) */
1775
1841
  customAngleOverride: z14.string().min(1).max(480).optional(),
1776
1842
  /** Pain points to emphasize in landings, creatives, and videos */
@@ -2469,6 +2535,8 @@ var RefineAngleResponse = z20.object({
2469
2535
  painPoints: z20.array(z20.string()).optional(),
2470
2536
  benefits: z20.array(z20.string()).optional(),
2471
2537
  actor: z20.string().optional(),
2538
+ /** Saved custom actor (Actor.id) carried through the refine round-trip. */
2539
+ actorId: z20.string().optional(),
2472
2540
  buyerPersona: z20.string().optional()
2473
2541
  });
2474
2542
 
@@ -2848,7 +2916,9 @@ var PendingUserActionSchema = z23.discriminatedUnion("kind", [
2848
2916
  id: z23.string().uuid(),
2849
2917
  content: z23.string(),
2850
2918
  creativeDirection: z23.string(),
2851
- actor: z23.string()
2919
+ actor: z23.string(),
2920
+ /** Saved custom actor (Actor.id) backing this angle, when one was chosen. */
2921
+ actorId: z23.string().optional()
2852
2922
  })).min(1).max(3)
2853
2923
  }),
2854
2924
  z23.object({
@@ -3001,7 +3071,8 @@ var CostEstimationType = z24.enum([
3001
3071
  "landing-regenerate",
3002
3072
  "landing-creative",
3003
3073
  "campaign-copy",
3004
- "service-creative"
3074
+ "service-creative",
3075
+ "actor-grid"
3005
3076
  ]);
3006
3077
  var IngestEstimationOptions = z24.object({
3007
3078
  /** Number of product references that the extract stage will clean. */
@@ -3102,6 +3173,8 @@ var VideoCreativeEstimationOptions = z24.object({
3102
3173
  requiresAngles: z24.boolean().optional(),
3103
3174
  /** When false, omit the Meta/Whisper competitor-scan ceiling from the estimate. */
3104
3175
  competitorScanEnabled: z24.boolean().optional(),
3176
+ /** Quote the competitor scan even when product extraction is skipped (direct video auto path). */
3177
+ requiresCompetitorScan: z24.boolean().optional(),
3105
3178
  generateAiMusic: z24.boolean().optional(),
3106
3179
  /** Free mode burns one Whisper pass over the final clip when enabled. */
3107
3180
  addSubtitles: z24.boolean().optional()
@@ -3170,6 +3243,7 @@ var ServiceCreativeEstimationOptions = z24.object({
3170
3243
  mediaAssetCount: z24.number().int().min(0).max(8).optional(),
3171
3244
  uploadedVideoDurationSeconds: z24.number().min(0).max(240).optional()
3172
3245
  });
3246
+ var ActorGridEstimationOptions = z24.object({}).strict();
3173
3247
  var CostEstimationRequest = z24.discriminatedUnion("type", [
3174
3248
  z24.object({
3175
3249
  type: z24.literal("ingest"),
@@ -3206,6 +3280,10 @@ var CostEstimationRequest = z24.discriminatedUnion("type", [
3206
3280
  z24.object({
3207
3281
  type: z24.literal("service-creative"),
3208
3282
  options: ServiceCreativeEstimationOptions
3283
+ }),
3284
+ z24.object({
3285
+ type: z24.literal("actor-grid"),
3286
+ options: ActorGridEstimationOptions
3209
3287
  })
3210
3288
  ]);
3211
3289
  var CostEstimationResponse = z24.object({
@@ -3689,6 +3767,25 @@ var ALLOWED_MEDIA_CONTENT_TYPES = [
3689
3767
  "audio/wav",
3690
3768
  "audio/x-wav"
3691
3769
  ];
3770
+ var META_STAGING_IMAGE_CONTENT_TYPES = /* @__PURE__ */ new Set([
3771
+ "image/jpeg",
3772
+ "image/png",
3773
+ "image/webp"
3774
+ ]);
3775
+ var META_STAGING_MEDIA_CONTENT_TYPES = /* @__PURE__ */ new Set([
3776
+ ...META_STAGING_IMAGE_CONTENT_TYPES,
3777
+ "video/mp4",
3778
+ "video/quicktime",
3779
+ "video/webm"
3780
+ ]);
3781
+ var META_STAGING_CONTENT_TYPES = [
3782
+ "image/jpeg",
3783
+ "image/png",
3784
+ "image/webp",
3785
+ "video/mp4",
3786
+ "video/quicktime",
3787
+ "video/webm"
3788
+ ];
3692
3789
  var PresignUploadRequest = z31.object({
3693
3790
  /** MIME type of the file to upload */
3694
3791
  contentType: z31.enum(ALLOWED_MEDIA_CONTENT_TYPES),
@@ -3696,11 +3793,59 @@ var PresignUploadRequest = z31.object({
3696
3793
  fileName: z31.string().min(1).max(255),
3697
3794
  /** File size in bytes (validated against MAX_FILE_SIZE_BYTES) */
3698
3795
  fileSize: z31.number().int().positive().max(CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES),
3699
- /** Service uploads receive a user-bound asset ticket; ads keep the legacy path. */
3796
+ /**
3797
+ * Service uploads receive a user-bound asset ticket; ads keep the legacy path.
3798
+ * `actor` uploads the identity photo of a custom actor and is stored under
3799
+ * `actors/<userId>/…` so `AssetAccessGuard` can resolve its owner.
3800
+ */
3700
3801
  purpose: z31.enum([
3701
3802
  "ads",
3702
- "service"
3803
+ "service",
3804
+ "actor",
3805
+ "meta_staging"
3806
+ ]).optional(),
3807
+ /** Required for purpose=meta_staging so media and thumbnails can be checked independently. */
3808
+ assetRole: z31.enum([
3809
+ "media",
3810
+ "thumbnail"
3703
3811
  ]).optional()
3812
+ }).superRefine((value, ctx) => {
3813
+ if (value.purpose === "meta_staging") {
3814
+ if (!value.assetRole) {
3815
+ ctx.addIssue({
3816
+ code: z31.ZodIssueCode.custom,
3817
+ path: [
3818
+ "assetRole"
3819
+ ],
3820
+ message: "assetRole is required for Meta staging uploads"
3821
+ });
3822
+ } else if (!META_STAGING_MEDIA_CONTENT_TYPES.has(value.contentType)) {
3823
+ ctx.addIssue({
3824
+ code: z31.ZodIssueCode.custom,
3825
+ path: [
3826
+ "contentType"
3827
+ ],
3828
+ message: "Meta staging accepts only image or video media"
3829
+ });
3830
+ } else if (value.assetRole === "thumbnail" && !META_STAGING_IMAGE_CONTENT_TYPES.has(value.contentType)) {
3831
+ ctx.addIssue({
3832
+ code: z31.ZodIssueCode.custom,
3833
+ path: [
3834
+ "contentType"
3835
+ ],
3836
+ message: "Meta staging thumbnails must be images"
3837
+ });
3838
+ }
3839
+ }
3840
+ if (value.purpose !== "meta_staging" && value.assetRole) {
3841
+ ctx.addIssue({
3842
+ code: z31.ZodIssueCode.custom,
3843
+ path: [
3844
+ "assetRole"
3845
+ ],
3846
+ message: "assetRole is only valid for Meta staging uploads"
3847
+ });
3848
+ }
3704
3849
  });
3705
3850
  var PresignUploadResponse = z31.object({
3706
3851
  /** Presigned S3 PUT URL — browser sends file directly here */
@@ -3710,8 +3855,23 @@ var PresignUploadResponse = z31.object({
3710
3855
  /** S3 key */
3711
3856
  key: z31.string(),
3712
3857
  /** Present only for purpose=service. */
3713
- assetToken: z31.string().min(1).optional()
3858
+ assetToken: z31.string().min(1).optional(),
3859
+ /** Present only for purpose=meta_staging. */
3860
+ stagingAssetId: z31.string().uuid().optional()
3714
3861
  });
3862
+ var MetaUploadMediaKind = z31.enum([
3863
+ "image",
3864
+ "video",
3865
+ "mixed"
3866
+ ]);
3867
+ var META_UPLOAD_MAX_FILES = CAMPAIGN_LIMITS.MAX_CREATIVES;
3868
+ var MetaUploadAssetSummary = z31.object({
3869
+ stagingAssetId: z31.string().uuid(),
3870
+ mediaKind: z31.enum([
3871
+ "image",
3872
+ "video"
3873
+ ])
3874
+ }).strict();
3715
3875
  var MetaCampaignType = z31.enum([
3716
3876
  "sales",
3717
3877
  "traffic",
@@ -3737,6 +3897,81 @@ var HttpUrl = z31.string().url().refine((value) => {
3737
3897
  return false;
3738
3898
  }
3739
3899
  }, "URL must use http or https");
3900
+ var MetaUploadSessionRequest = z31.object({
3901
+ /** The browser page will require a file of this media kind. */
3902
+ mediaKind: MetaUploadMediaKind,
3903
+ /** The staged object role; media is the normal creative input. */
3904
+ assetRole: z31.enum([
3905
+ "media",
3906
+ "thumbnail"
3907
+ ]).default("media"),
3908
+ /** Maximum number of files; the API chooses 10 for mixed and 1 otherwise when omitted. */
3909
+ maxFiles: z31.number().int().min(1).max(META_UPLOAD_MAX_FILES).optional()
3910
+ }).strict().superRefine((value, ctx) => {
3911
+ if (value.mediaKind === "mixed" && value.assetRole === "thumbnail") {
3912
+ ctx.addIssue({
3913
+ code: z31.ZodIssueCode.custom,
3914
+ path: [
3915
+ "mediaKind"
3916
+ ],
3917
+ message: "Mixed Meta upload sessions cannot be thumbnails"
3918
+ });
3919
+ }
3920
+ if (value.mediaKind !== "mixed" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
3921
+ ctx.addIssue({
3922
+ code: z31.ZodIssueCode.custom,
3923
+ path: [
3924
+ "maxFiles"
3925
+ ],
3926
+ message: "Non-mixed Meta upload sessions accept exactly one file"
3927
+ });
3928
+ }
3929
+ if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
3930
+ ctx.addIssue({
3931
+ code: z31.ZodIssueCode.custom,
3932
+ path: [
3933
+ "mediaKind"
3934
+ ],
3935
+ message: "Meta thumbnails must be images"
3936
+ });
3937
+ }
3938
+ });
3939
+ var ResolveMetaUploadSessionRequest = z31.object({
3940
+ /** Opaque signed token embedded in the one-click upload page URL. */
3941
+ token: z31.string().min(1).max(4096),
3942
+ /** Stable browser-generated id so failed PUT retries reuse the same slot. */
3943
+ uploadId: z31.string().uuid().optional(),
3944
+ /** Browser-selected metadata; the API checks it against the session kind/role. */
3945
+ contentType: z31.enum(META_STAGING_CONTENT_TYPES),
3946
+ fileName: z31.string().min(1).max(255),
3947
+ fileSize: z31.number().int().positive().max(CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES)
3948
+ }).strict();
3949
+ var MetaUploadSessionResponse = z31.object({
3950
+ sessionId: z31.string().uuid(),
3951
+ uploadPageUrl: HttpUrl,
3952
+ mediaKind: MetaUploadMediaKind,
3953
+ assetRole: z31.enum([
3954
+ "media",
3955
+ "thumbnail"
3956
+ ]),
3957
+ maxFiles: z31.number().int().min(1).max(META_UPLOAD_MAX_FILES),
3958
+ expiresAt: z31.string().datetime()
3959
+ }).strict();
3960
+ var ResolveMetaUploadSessionResponse = z31.object({
3961
+ uploadUrl: z31.string().url(),
3962
+ stagingAssetId: z31.string().uuid(),
3963
+ expiresAt: z31.string().datetime()
3964
+ }).strict();
3965
+ var MetaUploadSessionStatusResponse = z31.object({
3966
+ sessionId: z31.string().uuid(),
3967
+ status: z31.enum([
3968
+ "pending",
3969
+ "uploaded"
3970
+ ]),
3971
+ assets: z31.array(MetaUploadAssetSummary).max(META_UPLOAD_MAX_FILES),
3972
+ /** Legacy single-file response field retained for older callers. */
3973
+ stagingAssetId: z31.string().uuid().optional()
3974
+ }).strict();
3740
3975
  var MetaAdAccountId = z31.string().trim().min(1, "Debes seleccionar una cuenta publicitaria").transform((value) => /^\d+$/.test(value) ? `act_${value}` : value).refine((value) => /^act_\d+$/.test(value), {
3741
3976
  message: 'adAccountId must look like "act_1234567890" (a bare numeric id is also accepted)'
3742
3977
  });
@@ -3823,18 +4058,26 @@ var UploadToMetaRequest = z31.object({
3823
4058
  title: z31.string().max(CAMPAIGN_LIMITS.MAX_TITLE_LENGTH),
3824
4059
  description: z31.string().max(CAMPAIGN_LIMITS.MAX_DESCRIPTION_LENGTH),
3825
4060
  cta: MetaCtaType,
3826
- /** Public URL of the uploaded media */
3827
- mediaUrl: HttpUrl,
4061
+ /** Legacy public URL; new Meta flows should use mediaAssetId. */
4062
+ mediaUrl: HttpUrl.optional(),
4063
+ /** Owner-scoped temporary asset created by the Meta staging upload flow. */
4064
+ mediaAssetId: z31.string().uuid().optional(),
3828
4065
  /**
3829
4066
  * Public URL of an extracted thumbnail. Required for video ads — Meta
3830
4067
  * rejects video ad creatives without `image_hash`/`image_url` in
3831
4068
  * `video_data` (subcode 1443226). Frontend extracts a frame from the
3832
4069
  * video and uploads it before submitting the request.
3833
4070
  */
3834
- thumbnailUrl: HttpUrl.optional()
4071
+ thumbnailUrl: HttpUrl.optional(),
4072
+ /** Owner-scoped temporary thumbnail created by the Meta staging upload flow. */
4073
+ thumbnailAssetId: z31.string().uuid().optional()
3835
4074
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3836
4075
  /** Meta Ad Account ID */
3837
4076
  adAccountId: MetaAdAccountId,
4077
+ /** Meta Business Portfolio selected by the user (optional for legacy callers). */
4078
+ portfolioId: z31.string().min(1).optional(),
4079
+ /** Facebook Page selected by the user (optional for legacy callers). */
4080
+ pageId: z31.string().min(1).optional(),
3838
4081
  /** Optional Meta Pixel ID */
3839
4082
  pixelId: z31.string().optional(),
3840
4083
  /** Landing page URL (required for landing destinations; omitted for whatsapp) */
@@ -3854,6 +4097,30 @@ var UploadToMetaRequest = z31.object({
3854
4097
  message: "landingUrl is required for landing destinations"
3855
4098
  });
3856
4099
  }
4100
+ value.creatives.forEach((creative, index) => {
4101
+ if (Boolean(creative.mediaUrl) === Boolean(creative.mediaAssetId)) {
4102
+ ctx.addIssue({
4103
+ code: z31.ZodIssueCode.custom,
4104
+ path: [
4105
+ "creatives",
4106
+ index,
4107
+ "mediaUrl"
4108
+ ],
4109
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4110
+ });
4111
+ }
4112
+ if (creative.thumbnailUrl && creative.thumbnailAssetId) {
4113
+ ctx.addIssue({
4114
+ code: z31.ZodIssueCode.custom,
4115
+ path: [
4116
+ "creatives",
4117
+ index,
4118
+ "thumbnailUrl"
4119
+ ],
4120
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4121
+ });
4122
+ }
4123
+ });
3857
4124
  });
3858
4125
  var CampaignLifetimeMetricsSchema = z31.object({
3859
4126
  spend: z31.number().nonnegative(),
@@ -3907,8 +4174,32 @@ var FacebookAdAccountSchema = z32.object({
3907
4174
  source: z32.enum([
3908
4175
  "business",
3909
4176
  "personal"
4177
+ ]),
4178
+ /** Meta Business Portfolio (or the synthetic `personal` group). */
4179
+ portfolioId: z32.string().min(1),
4180
+ portfolioName: z32.string().min(1),
4181
+ portfolioType: z32.enum([
4182
+ "business",
4183
+ "personal"
3910
4184
  ])
3911
4185
  });
4186
+ var FacebookMetaPortfolioSchema = z32.object({
4187
+ id: z32.string().min(1),
4188
+ name: z32.string().min(1),
4189
+ type: z32.enum([
4190
+ "business",
4191
+ "personal"
4192
+ ])
4193
+ });
4194
+ var FacebookMetaPageSchema = z32.object({
4195
+ id: z32.string().min(1),
4196
+ name: z32.string().min(1)
4197
+ });
4198
+ var FacebookMetaDestinationsResponseSchema = z32.object({
4199
+ portfolios: z32.array(FacebookMetaPortfolioSchema),
4200
+ adAccounts: z32.array(FacebookAdAccountSchema),
4201
+ pages: z32.array(FacebookMetaPageSchema)
4202
+ });
3912
4203
  var MetaCampaignOverridesSchema = z32.object({
3913
4204
  dailyBudgetUsd: z32.number().positive().optional(),
3914
4205
  targetCountries: z32.array(z32.string().length(2)).max(1).optional(),
@@ -3917,6 +4208,8 @@ var MetaCampaignOverridesSchema = z32.object({
3917
4208
  });
3918
4209
  var FacebookCreateCampaignBodySchema = z32.object({
3919
4210
  adAccountId: MetaAdAccountId,
4211
+ portfolioId: z32.string().min(1).optional(),
4212
+ pageId: z32.string().min(1).optional(),
3920
4213
  pixelId: z32.string().optional(),
3921
4214
  landingUrl: HttpUrl2.optional(),
3922
4215
  campaignConfig: MetaCampaignConfig.optional(),
@@ -3947,6 +4240,8 @@ var FacebookCampaignStatusResponseSchema = z32.object({
3947
4240
  var FacebookCreateCampaignResponseSchema = z32.object({
3948
4241
  campaignId: z32.string(),
3949
4242
  campaignName: z32.string(),
4243
+ portfolioId: z32.string().optional(),
4244
+ portfolioName: z32.string().optional(),
3950
4245
  pageId: z32.string().optional(),
3951
4246
  pageName: z32.string().optional(),
3952
4247
  // Backward-compatible single ad fields (optional)
@@ -3989,11 +4284,15 @@ var FacebookCreateCampaignFromCreativesBodySchema = z32.object({
3989
4284
  title: z32.string().max(120),
3990
4285
  description: z32.string().max(500),
3991
4286
  cta: MetaCtaType,
3992
- mediaUrl: HttpUrl2,
4287
+ mediaUrl: HttpUrl2.optional(),
4288
+ mediaAssetId: z32.string().uuid().optional(),
3993
4289
  thumbnailUrl: HttpUrl2.optional(),
4290
+ thumbnailAssetId: z32.string().uuid().optional(),
3994
4291
  creativeId: z32.string().min(1).optional()
3995
4292
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3996
4293
  adAccountId: MetaAdAccountId,
4294
+ portfolioId: z32.string().min(1).optional(),
4295
+ pageId: z32.string().min(1).optional(),
3997
4296
  pixelId: z32.string().optional(),
3998
4297
  /** Required unless campaignConfig.destination is 'whatsapp' (see superRefine below). */
3999
4298
  landingUrl: HttpUrl2.optional(),
@@ -4013,6 +4312,30 @@ var FacebookCreateCampaignFromCreativesBodySchema = z32.object({
4013
4312
  message: "landingUrl is required unless destination is whatsapp"
4014
4313
  });
4015
4314
  }
4315
+ value.creatives.forEach((creative, index) => {
4316
+ if (Boolean(creative.mediaUrl) === Boolean(creative.mediaAssetId)) {
4317
+ ctx.addIssue({
4318
+ code: z32.ZodIssueCode.custom,
4319
+ path: [
4320
+ "creatives",
4321
+ index,
4322
+ "mediaUrl"
4323
+ ],
4324
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4325
+ });
4326
+ }
4327
+ if (creative.thumbnailUrl && creative.thumbnailAssetId) {
4328
+ ctx.addIssue({
4329
+ code: z32.ZodIssueCode.custom,
4330
+ path: [
4331
+ "creatives",
4332
+ index,
4333
+ "thumbnailUrl"
4334
+ ],
4335
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4336
+ });
4337
+ }
4338
+ });
4016
4339
  if (value.campaignConfig?.destination?.type === "landing" && value.landingUrl && value.campaignConfig.destination.landingUrl !== value.landingUrl) {
4017
4340
  ctx.addIssue({
4018
4341
  code: z32.ZodIssueCode.custom,
@@ -4090,8 +4413,10 @@ var FacebookTaskCreativeSourceSchema = z32.object({
4090
4413
  });
4091
4414
  var FacebookMediaCreativeSourceSchema = z32.object({
4092
4415
  type: z32.literal("media"),
4093
- mediaUrl: HttpUrl2,
4094
- thumbnailUrl: HttpUrl2.optional()
4416
+ mediaUrl: HttpUrl2.optional(),
4417
+ mediaAssetId: z32.string().uuid().optional(),
4418
+ thumbnailUrl: HttpUrl2.optional(),
4419
+ thumbnailAssetId: z32.string().uuid().optional()
4095
4420
  });
4096
4421
  var FacebookCreativeSourceSchema = z32.discriminatedUnion("type", [
4097
4422
  FacebookTaskCreativeSourceSchema,
@@ -4110,6 +4435,9 @@ var FacebookCreateCampaignFromTasksBodySchema = z32.object({
4110
4435
  creativeId: z32.string().min(1).optional()
4111
4436
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
4112
4437
  adAccountId: MetaAdAccountId,
4438
+ /** Explicit destination is required for MCP/remote creative publishing. */
4439
+ portfolioId: z32.string().min(1),
4440
+ pageId: z32.string().min(1),
4113
4441
  pixelId: z32.string().optional(),
4114
4442
  landingUrl: HttpUrl2.optional(),
4115
4443
  targetCountry: z32.string().length(2).default("US"),
@@ -4126,6 +4454,33 @@ var FacebookCreateCampaignFromTasksBodySchema = z32.object({
4126
4454
  message: "landingUrl is required unless destination is whatsapp"
4127
4455
  });
4128
4456
  }
4457
+ value.creatives.forEach((creative, index) => {
4458
+ if (creative.source.type !== "media") return;
4459
+ if (Boolean(creative.source.mediaUrl) === Boolean(creative.source.mediaAssetId)) {
4460
+ ctx.addIssue({
4461
+ code: z32.ZodIssueCode.custom,
4462
+ path: [
4463
+ "creatives",
4464
+ index,
4465
+ "source",
4466
+ "mediaUrl"
4467
+ ],
4468
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4469
+ });
4470
+ }
4471
+ if (creative.source.thumbnailUrl && creative.source.thumbnailAssetId) {
4472
+ ctx.addIssue({
4473
+ code: z32.ZodIssueCode.custom,
4474
+ path: [
4475
+ "creatives",
4476
+ index,
4477
+ "source",
4478
+ "thumbnailUrl"
4479
+ ],
4480
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4481
+ });
4482
+ }
4483
+ });
4129
4484
  });
4130
4485
 
4131
4486
  // ../../packages/types/src/tiktok.ts
@@ -4692,6 +5047,13 @@ var DEFAULT_PRICING = {
4692
5047
  ourCostCents: 3,
4693
5048
  userPriceCents: 6
4694
5049
  },
5050
+ // Custom actor identity photos (gpt-image-2 @ 1K, 4 candidates per attempt).
5051
+ // Recorded once per PRODUCED candidate. Without this row billingService.record
5052
+ // logs "No pricing rule found; skipping billing record" and the whole grid is free.
5053
+ "kie:image-generation:actor": {
5054
+ ourCostCents: 3,
5055
+ userPriceCents: 6
5056
+ },
4695
5057
  "kie:image-generation:seedream-5-lite-text-to-image": {
4696
5058
  ourCostCents: 3,
4697
5059
  userPriceCents: 6
@@ -5628,6 +5990,32 @@ var DEFAULT_PRICING = {
5628
5990
  ourCostCents: 1.5,
5629
5991
  userPriceCents: 3
5630
5992
  },
5993
+ // Frame0 per-person anatomy gate. Priced PER PANEL, like the inventory above and for the same
5994
+ // reason: it is one vision call per rendered panel, so quoting it per VIDEO would under-quote
5995
+ // by the panel count (~10x). Same single-image, short-answer shape as the inventory, so it
5996
+ // carries the same rate until measured otherwise.
5997
+ "openrouter:rapid-panel-anatomy-gate": {
5998
+ ourCostCents: 1.5,
5999
+ userPriceCents: 3
6000
+ },
6001
+ // Plan hand-budget audit: one TEXT call per shot (no image), so materially cheaper than the
6002
+ // vision passes above. Billed at the call site, therefore it MUST be quoted — an unquoted paid
6003
+ // call is the failure this repo already has on record.
6004
+ "openrouter:plan-hand-budget-audit": {
6005
+ ourCostCents: 0.3,
6006
+ userPriceCents: 0.6
6007
+ },
6008
+ // Hand-budget REPAIR: the rewrite that fires only on a shot whose brief demands more hand jobs
6009
+ // than its cast has hands. One TEXT call, same shape as the audit above but with the brief on
6010
+ // both sides (prompt in, prose out), so twice the rate rather than the audit's.
6011
+ //
6012
+ // Kept on its OWN key rather than folded into the audit for the reason the frame-inventory delta
6013
+ // is: the audit runs on every shot and the repair on a minority, so one blended rate would price
6014
+ // both wrong and neither line would be auditable against the ledger.
6015
+ "openrouter:rapid-hand-budget-repair": {
6016
+ ourCostCents: 0.6,
6017
+ userPriceCents: 1.2
6018
+ },
5631
6019
  // Unit = one shot-action flatten, once per video, quoted ONLY when frame inventories are on.
5632
6020
  // Carrying ~10 inventories as text raised that call from a 1.7078¢ production median to a
5633
6021
  // measured 3.1481¢ / 3.4551¢. This is the difference, kept on its own key so the base rate above
@@ -7058,6 +7446,7 @@ var MAX_SEGMENT_SECONDS = 3;
7058
7446
  var VideoEditStatusSchema = z38.enum([
7059
7447
  "pending",
7060
7448
  "processing",
7449
+ "assembling",
7061
7450
  "ready",
7062
7451
  "accepted",
7063
7452
  "discarded",
@@ -7085,6 +7474,13 @@ var VideoEditModeSchema = z38.enum([
7085
7474
  "wan_edit",
7086
7475
  "regenerate"
7087
7476
  ]);
7477
+ var VideoRegenerationEngineSchema = z38.enum([
7478
+ "free_text",
7479
+ "free_image",
7480
+ "single_call",
7481
+ "multi_shot"
7482
+ ]);
7483
+ var VideoRegenerationRouteFingerprintSchema = z38.string().trim().min(1).max(128);
7088
7484
  var RegenerateEditRequestSchema = z38.object({
7089
7485
  angleKey: z38.string().min(1),
7090
7486
  comment: z38.string().min(1).max(2e3)
@@ -7463,6 +7859,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7463
7859
  confirmation: "credit_consuming",
7464
7860
  schemaHash: "bab794a9700a8d3f42b97f444a776e7ed4dcd833038c52588f951b50970ff3fa"
7465
7861
  },
7862
+ {
7863
+ name: "check_meta_media_upload",
7864
+ confirmation: "none",
7865
+ schemaHash: "62b1af1d796d9466f8926afc5e6d18c4ddeccd5b0269ca7b21c2f6ebcfd2a945"
7866
+ },
7466
7867
  {
7467
7868
  name: "cost_dropshipping_project",
7468
7869
  confirmation: "state_change",
@@ -7481,7 +7882,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7481
7882
  {
7482
7883
  name: "create_product_task",
7483
7884
  confirmation: "credit_consuming",
7484
- schemaHash: "3f03c4886f65a030e03a531c02e0ef879aaf3f5820e90ab3ca04a02350e693e6"
7885
+ schemaHash: "5c5fe82a365c62e44265e79a4100a06820dbbdb2e0ddc296cd066eb2255b1d62"
7485
7886
  },
7486
7887
  {
7487
7888
  name: "create_service_video",
@@ -7496,7 +7897,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7496
7897
  {
7497
7898
  name: "edit_task_draft",
7498
7899
  confirmation: "state_change",
7499
- schemaHash: "f1763564083dcaac2d1f4d5e6c4625a2d4ec0ea3eea273aadc0fb9d4ab6c7d47"
7900
+ schemaHash: "1c9343bdce51aac36ec6254557c8a904037746523168527f012b4c5b6361e98e"
7500
7901
  },
7501
7902
  {
7502
7903
  name: "estimate_creative_variant",
@@ -7526,7 +7927,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7526
7927
  {
7527
7928
  name: "generate_video_creative",
7528
7929
  confirmation: "credit_consuming",
7529
- schemaHash: "c80208df75f2c7cbc459e9ae494a75a0a5f5bc4cf044b3fc9ef9c0e9af2c2452"
7930
+ schemaHash: "04e12ef523e4064b0c204d110baad35d414b605068087c625badf40c46429c8a"
7530
7931
  },
7531
7932
  {
7532
7933
  name: "get_meta_campaign_status",
@@ -7558,11 +7959,21 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7558
7959
  confirmation: "none",
7559
7960
  schemaHash: "0e7a1cb5ad5c6681473df5689336edcd7a63b8d68853359982e44bfd98a65db8"
7560
7961
  },
7962
+ {
7963
+ name: "list_actors",
7964
+ confirmation: "none",
7965
+ schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
7966
+ },
7561
7967
  {
7562
7968
  name: "list_meta_ad_accounts",
7563
7969
  confirmation: "none",
7564
7970
  schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
7565
7971
  },
7972
+ {
7973
+ name: "list_meta_destinations",
7974
+ confirmation: "none",
7975
+ schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
7976
+ },
7566
7977
  {
7567
7978
  name: "list_product_research_runs",
7568
7979
  confirmation: "none",
@@ -7591,12 +8002,12 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7591
8002
  {
7592
8003
  name: "publish_creatives_to_meta",
7593
8004
  confirmation: "external_mutation",
7594
- schemaHash: "ddb45071d61746b443124346e7a82095315a2a80bd393c73a45f12b895939cf5"
8005
+ schemaHash: "59b3d6d5ce25812fee3382b0b1c0cf5e07beb1063048dd0a0d0633686cf29da5"
7595
8006
  },
7596
8007
  {
7597
8008
  name: "publish_to_meta",
7598
8009
  confirmation: "external_mutation",
7599
- schemaHash: "4b6daa1a25e739ccf208a6acede067ec1c22b1ea5509f332361a17dd46f6bb0b"
8010
+ schemaHash: "b507735a1fe13657355cf93163b6ead0db151984e6f2702dcdbf76b4037133ca"
7600
8011
  },
7601
8012
  {
7602
8013
  name: "publish_to_shopify",
@@ -7613,6 +8024,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7613
8024
  confirmation: "credit_consuming",
7614
8025
  schemaHash: "745687cb41f904e293105f93bbe6bc0471f63fe097ab8f85d3df8d9833084844"
7615
8026
  },
8027
+ {
8028
+ name: "request_meta_media_upload",
8029
+ confirmation: "none",
8030
+ schemaHash: "32203d205f414c386ee6ab33e265eb2708c108fca4169d4c12e99d7a4109a132"
8031
+ },
7616
8032
  {
7617
8033
  name: "resolve_pending_action",
7618
8034
  confirmation: "credit_consuming",
@@ -7626,6 +8042,150 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7626
8042
  ];
7627
8043
  var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITIES.map((capability) => capability.name);
7628
8044
 
8045
+ // ../../packages/types/src/actor.ts
8046
+ import { z as z42 } from "zod";
8047
+ var ActorStatusSchema = z42.enum([
8048
+ "draft",
8049
+ "generating",
8050
+ "ready",
8051
+ "failed"
8052
+ ]);
8053
+ var ACTOR_NAME_MAX_CHARS = 60;
8054
+ var ACTOR_DESCRIPTION_MAX_CHARS = 600;
8055
+ var ACTOR_CANDIDATE_COUNT = 4;
8056
+ var ActorGenderSchema = z42.enum([
8057
+ "female",
8058
+ "male",
8059
+ "non_binary"
8060
+ ]);
8061
+ var ActorSchema = z42.object({
8062
+ id: z42.string(),
8063
+ name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS),
8064
+ description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
8065
+ gender: ActorGenderSchema,
8066
+ voiceId: z42.string().nullable().optional(),
8067
+ brandId: z42.string().nullable().optional(),
8068
+ status: ActorStatusSchema,
8069
+ /**
8070
+ * A candidate-grid run is in flight RIGHT NOW.
8071
+ *
8072
+ * Separate from `status` because the two answer different questions:
8073
+ * `status` is the PUBLISHED state (a personaje being regenerated stays
8074
+ * `ready`, so it keeps serving the renders that already reference it), and
8075
+ * this flag is the live one the wizard polls. It is derived from a lock
8076
+ * timestamp with a TTL, so an orphaned run reads as `false` instead of
8077
+ * pinning the personaje forever.
8078
+ */
8079
+ generating: z42.boolean().optional(),
8080
+ photoUrl: z42.string().url().nullable().optional(),
8081
+ avatarUrl: z42.string().url().nullable().optional(),
8082
+ candidateUrls: z42.array(z42.string().url()).optional(),
8083
+ /** Indices of candidateUrls that failed the anatomy check (Task 6). */
8084
+ flaggedCandidateIndices: z42.array(z42.number().int()).optional()
8085
+ });
8086
+ var CreateActorRequest = z42.object({
8087
+ name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS),
8088
+ description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
8089
+ gender: ActorGenderSchema,
8090
+ voiceId: z42.string().optional(),
8091
+ brandId: z42.string().optional(),
8092
+ sourcePhotoKey: z42.string().optional()
8093
+ }).refine((v) => v.description.trim().length > 0 || !!v.sourcePhotoKey, {
8094
+ message: "Se necesita una descripci\xF3n o una foto"
8095
+ });
8096
+ var UpdateActorRequest = z42.object({
8097
+ name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
8098
+ description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
8099
+ gender: ActorGenderSchema.optional(),
8100
+ voiceId: z42.string().nullable().optional(),
8101
+ brandId: z42.string().nullable().optional()
8102
+ });
8103
+ var ApproveActorRequest = z42.object({
8104
+ candidateIndex: z42.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
8105
+ });
8106
+
8107
+ // ../../packages/types/src/referral-dashboard.ts
8108
+ import { z as z43 } from "zod";
8109
+ var REFERRAL_DASHBOARD_RAW_STATUSES = [
8110
+ "ACTIVE",
8111
+ "PENDING",
8112
+ "GRACE",
8113
+ "CANCELLED",
8114
+ "EXPIRED"
8115
+ ];
8116
+ var REFERRAL_DASHBOARD_STATES = [
8117
+ "active",
8118
+ "pending",
8119
+ "inactive",
8120
+ "no_plan"
8121
+ ];
8122
+ var REFERRAL_DASHBOARD_PLAN_KINDS = [
8123
+ "current",
8124
+ "last",
8125
+ "none"
8126
+ ];
8127
+ var ReferralDashboardRawStatusSchema = z43.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
8128
+ var ReferralDashboardStateSchema = z43.enum(REFERRAL_DASHBOARD_STATES);
8129
+ var ReferralDashboardPlanKindSchema = z43.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
8130
+ var ReferralDashboardStatusSchema = z43.enum([
8131
+ "all",
8132
+ "active",
8133
+ "pending",
8134
+ "inactive"
8135
+ ]).default("all");
8136
+ var ReferralDashboardPlanSchema = z43.string().trim().min(1).max(100).optional();
8137
+ var ReferralDashboardFiltersSchema = z43.object({
8138
+ status: ReferralDashboardStatusSchema,
8139
+ plan: ReferralDashboardPlanSchema
8140
+ });
8141
+ var ReferralDashboardSubscriptionSchema = z43.object({
8142
+ planName: z43.string().nullable(),
8143
+ planKind: ReferralDashboardPlanKindSchema,
8144
+ rawStatus: ReferralDashboardRawStatusSchema.nullable(),
8145
+ state: ReferralDashboardStateSchema,
8146
+ currentPeriodEnd: z43.string().datetime({
8147
+ offset: true
8148
+ }).nullable(),
8149
+ cancelAtPeriodEnd: z43.boolean()
8150
+ });
8151
+ var ReferralDashboardReferralSchema = z43.object({
8152
+ id: z43.string().min(1),
8153
+ registeredAt: z43.string().datetime({
8154
+ offset: true
8155
+ }),
8156
+ // Kept in the response for compatibility with existing referral API clients.
8157
+ // The referrer dashboard intentionally does not render these legacy fields.
8158
+ expiresAt: z43.string().datetime({
8159
+ offset: true
8160
+ }).nullable().optional(),
8161
+ status: z43.string().min(1).optional(),
8162
+ referredUser: z43.object({
8163
+ email: z43.string().nullable()
8164
+ }),
8165
+ subscription: ReferralDashboardSubscriptionSchema
8166
+ });
8167
+ var ReferralDashboardReferralsPageSchema = z43.object({
8168
+ referrals: z43.array(ReferralDashboardReferralSchema),
8169
+ total: z43.number().int().nonnegative()
8170
+ });
8171
+ var ReferralDashboardPlanBreakdownSchema = z43.object({
8172
+ planName: z43.string().nullable(),
8173
+ count: z43.number().int().nonnegative()
8174
+ });
8175
+ var ReferralDashboardMonthlyRegistrationSchema = z43.object({
8176
+ month: z43.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
8177
+ count: z43.number().int().nonnegative()
8178
+ });
8179
+ var ReferralDashboardOverviewSchema = z43.object({
8180
+ totalReferredUsers: z43.number().int().nonnegative(),
8181
+ activeUsers: z43.number().int().nonnegative(),
8182
+ pendingUsers: z43.number().int().nonnegative(),
8183
+ inactiveUsers: z43.number().int().nonnegative(),
8184
+ noPlanUsers: z43.number().int().nonnegative(),
8185
+ planBreakdown: z43.array(ReferralDashboardPlanBreakdownSchema),
8186
+ monthlyRegistrations: z43.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
8187
+ });
8188
+
7629
8189
  // src/resolve-media.ts
7630
8190
  var MAX_MEDIA_BYTES = CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES;
7631
8191
  var MEDIA_FORMATS = [
@@ -7865,10 +8425,10 @@ async function resolveMediaRef(ref, opts) {
7865
8425
  __name(resolveMediaRef, "resolveMediaRef");
7866
8426
 
7867
8427
  // src/tools/shared.ts
7868
- import { z as z42 } from "zod";
7869
- var Lang = z42.string().min(2).max(10);
7870
- var Country = z42.string().min(2).max(3);
7871
- var LandingStrategyEnum = z42.enum([
8428
+ import { z as z44 } from "zod";
8429
+ var Lang = z44.string().min(2).max(10);
8430
+ var Country = z44.string().min(2).max(3);
8431
+ var LandingStrategyEnum = z44.enum([
7872
8432
  "auto",
7873
8433
  "classic",
7874
8434
  "pain_solution",
@@ -7877,12 +8437,12 @@ var LandingStrategyEnum = z42.enum([
7877
8437
  "offer_value",
7878
8438
  "lifestyle_identity"
7879
8439
  ]);
7880
- var LandingImageFormatEnum = z42.enum([
8440
+ var LandingImageFormatEnum = z44.enum([
7881
8441
  "9:16",
7882
8442
  "4:5",
7883
8443
  "1:1"
7884
8444
  ]);
7885
- var VideoPresetEnum = z42.enum([
8445
+ var VideoPresetEnum = z44.enum([
7886
8446
  "auto",
7887
8447
  "ugc_dynamic",
7888
8448
  "ugc_how_to",
@@ -7895,7 +8455,7 @@ var VideoPresetEnum = z42.enum([
7895
8455
  "pixar_problem",
7896
8456
  "pixar_sassy"
7897
8457
  ]);
7898
- var TaskCreativeVariantVideoPresetEnum = z42.enum([
8458
+ var TaskCreativeVariantVideoPresetEnum = z44.enum([
7899
8459
  "auto",
7900
8460
  "ugc_dynamic",
7901
8461
  "ugc_how_to",
@@ -7966,7 +8526,7 @@ function resolveVideoPreset(preset) {
7966
8526
  return VIDEO_PRESET_RESOLUTION[preset] ?? null;
7967
8527
  }
7968
8528
  __name(resolveVideoPreset, "resolveVideoPreset");
7969
- var VideoTierEnum = z42.enum([
8529
+ var VideoTierEnum = z44.enum([
7970
8530
  "basic"
7971
8531
  ]);
7972
8532
  function resolveVideoTier(_tier) {
@@ -7977,27 +8537,21 @@ function resolveWireVideoTier(tier, durationSeconds) {
7977
8537
  return durationSeconds === 30 ? "standard" : resolveVideoTier(tier);
7978
8538
  }
7979
8539
  __name(resolveWireVideoTier, "resolveWireVideoTier");
7980
- function assertVideoPresetDurationCompatibility(videoPreset, durationSeconds) {
7981
- if (videoPreset === "problem_solution" && durationSeconds !== 30) {
7982
- throw new Error("Problema y Soluci\xF3n solo est\xE1 disponible en videos Multiescena de 30 segundos.");
7983
- }
7984
- }
7985
- __name(assertVideoPresetDurationCompatibility, "assertVideoPresetDurationCompatibility");
7986
8540
  function resolveVideoFormatForDuration(durationSeconds) {
7987
8541
  return durationSeconds === 30 ? "double_30s" : "single_15s";
7988
8542
  }
7989
8543
  __name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
7990
- var VideoResolutionEnum = z42.enum([
8544
+ var VideoResolutionEnum = z44.enum([
7991
8545
  "480p",
7992
8546
  "720p"
7993
8547
  ]);
7994
- var VideoStyleEnum = z42.enum([
8548
+ var VideoStyleEnum = z44.enum([
7995
8549
  "ugc",
7996
8550
  "cartoon_3d",
7997
8551
  "product_only",
7998
8552
  "ai_dynamic"
7999
8553
  ]);
8000
- var NarrativeStyleEnum = z42.enum([
8554
+ var NarrativeStyleEnum = z44.enum([
8001
8555
  "review",
8002
8556
  "ugc_how_to",
8003
8557
  "problem_product_cta",
@@ -8014,7 +8568,7 @@ var NarrativeStyleEnum = z42.enum([
8014
8568
  "ugc_skeptic_confession",
8015
8569
  "ugc_demo_review"
8016
8570
  ]);
8017
- var PublicNarrativeStyleEnum = z42.enum([
8571
+ var PublicNarrativeStyleEnum = z44.enum([
8018
8572
  "review",
8019
8573
  "ugc_how_to",
8020
8574
  "ugc_unboxing_chaos",
@@ -8026,7 +8580,7 @@ var PublicNarrativeStyleEnum = z42.enum([
8026
8580
  "animated_problem_pixar",
8027
8581
  "sassy_product_pixar"
8028
8582
  ]);
8029
- var VideoModelEnum = z42.enum([
8583
+ var VideoModelEnum = z44.enum([
8030
8584
  "veo",
8031
8585
  "wan",
8032
8586
  "bytedance",
@@ -8034,25 +8588,25 @@ var VideoModelEnum = z42.enum([
8034
8588
  "grok",
8035
8589
  "kling"
8036
8590
  ]);
8037
- var ProductReferenceUserChoiceEnum = z42.enum([
8591
+ var ProductReferenceUserChoiceEnum = z44.enum([
8038
8592
  "same_product",
8039
8593
  "variants",
8040
8594
  "different_products",
8041
8595
  "combo_distinct"
8042
8596
  ]);
8043
- var ProductReferenceCandidateGroupingEnum = z42.enum([
8597
+ var ProductReferenceCandidateGroupingEnum = z44.enum([
8044
8598
  "same_product_same_variant",
8045
8599
  "same_product_different_variants",
8046
8600
  "different_products",
8047
8601
  "unclear"
8048
8602
  ]);
8049
- var GapDurationChoiceMirror = z42.object({
8050
- angleIndex: z42.number().int().nonnegative(),
8051
- recommendedSec: z42.union([
8052
- z42.literal(15),
8053
- z42.literal(30)
8603
+ var GapDurationChoiceMirror = z44.object({
8604
+ angleIndex: z44.number().int().nonnegative(),
8605
+ recommendedSec: z44.union([
8606
+ z44.literal(15),
8607
+ z44.literal(30)
8054
8608
  ]),
8055
- estimatedDeltaCredits: z42.number().int().nonnegative().optional()
8609
+ estimatedDeltaCredits: z44.number().int().nonnegative().optional()
8056
8610
  });
8057
8611
  function buildSelectedImageUrlsHash(urls) {
8058
8612
  const input = [
@@ -8066,13 +8620,13 @@ function buildSelectedImageUrlsHash(urls) {
8066
8620
  return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
8067
8621
  }
8068
8622
  __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
8069
- var ImageCreativeStyleEnum = z42.enum([
8623
+ var ImageCreativeStyleEnum = z44.enum([
8070
8624
  "studio",
8071
8625
  "floating",
8072
8626
  "ingredients",
8073
8627
  "in_use"
8074
8628
  ]);
8075
- var ImageCreativeArchetypeEnum = z42.enum([
8629
+ var ImageCreativeArchetypeEnum = z44.enum([
8076
8630
  "direct_response_product_ad",
8077
8631
  "hero_outcome",
8078
8632
  "offer_value_prop",
@@ -8081,23 +8635,23 @@ var ImageCreativeArchetypeEnum = z42.enum([
8081
8635
  "problem_solution",
8082
8636
  "lifestyle_identity"
8083
8637
  ]);
8084
- var ImageCreativeVisualTreatmentEnum = z42.enum([
8638
+ var ImageCreativeVisualTreatmentEnum = z44.enum([
8085
8639
  "performance_product_ad",
8086
8640
  "premium_dtc_carousel"
8087
8641
  ]);
8088
- var ImageCreativeAspectRatioEnum = z42.enum([
8642
+ var ImageCreativeAspectRatioEnum = z44.enum([
8089
8643
  "1:1",
8090
8644
  "4:5",
8091
8645
  "9:16",
8092
8646
  "16:9"
8093
8647
  ]);
8094
- var ImageRefSchema = z42.object({
8095
- url: z42.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
8096
- path: z42.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.')
8648
+ var ImageRefSchema = z44.object({
8649
+ url: z44.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
8650
+ path: z44.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.')
8097
8651
  }).refine((v) => Boolean(v.url) !== Boolean(v.path), {
8098
8652
  message: "each image entry must have exactly one of `url` or `path`"
8099
8653
  });
8100
- var ImagesInput = z42.array(ImageRefSchema).min(1).max(5).describe('1 a 5 fotos del MISMO producto. La PRIMERA es la principal (hero); las dem\xE1s son \xE1ngulos adicionales. NO mezcles productos distintos en una sola llamada.\n\nCada entrada tiene EXACTAMENTE uno de:\n\u2022 `url` \u2014 URL p\xFAblica HTTPS (Shopify CDN, Cloudinary). Prefi\xE9rela si la foto ya est\xE1 hospedada.\n\u2022 `path` \u2014 Ruta del archivo en el computador del usuario (ej. "/Users/maria/Desktop/foto.jpg", "~/Downloads/camiseta.png"). \xDAsala cuando el usuario te dijo d\xF3nde guard\xF3 la foto. Solo MCP local \u2014 el cliente web rechaza paths.\n\nSi el usuario te peg\xF3 la foto en el chat pero no te dio ni URL ni ruta: PREG\xDANTALE d\xF3nde la tiene guardada (Escritorio, Descargas, etc.). Si el `path` falla con IMAGE_PATH_NOT_FOUND, prueba 1-2 ubicaciones obvias alternativas (~/Desktop, ~/Downloads) antes de molestar al usuario. Tras 3 intentos fallidos, p\xEDdele la ruta completa.');
8654
+ var ImagesInput = z44.array(ImageRefSchema).min(1).max(5).describe('1 a 5 fotos del MISMO producto. La PRIMERA es la principal (hero); las dem\xE1s son \xE1ngulos adicionales. NO mezcles productos distintos en una sola llamada.\n\nCada entrada tiene EXACTAMENTE uno de:\n\u2022 `url` \u2014 URL p\xFAblica HTTPS (Shopify CDN, Cloudinary). Prefi\xE9rela si la foto ya est\xE1 hospedada.\n\u2022 `path` \u2014 Ruta del archivo en el computador del usuario (ej. "/Users/maria/Desktop/foto.jpg", "~/Downloads/camiseta.png"). \xDAsala cuando el usuario te dijo d\xF3nde guard\xF3 la foto. Solo MCP local \u2014 el cliente web rechaza paths.\n\nSi el usuario te peg\xF3 la foto en el chat pero no te dio ni URL ni ruta: PREG\xDANTALE d\xF3nde la tiene guardada (Escritorio, Descargas, etc.). Si el `path` falla con IMAGE_PATH_NOT_FOUND, prueba 1-2 ubicaciones obvias alternativas (~/Desktop, ~/Downloads) antes de molestar al usuario. Tras 3 intentos fallidos, p\xEDdele la ruta completa.');
8101
8655
  function asText(value) {
8102
8656
  return {
8103
8657
  content: [
@@ -8150,14 +8704,14 @@ async function submitCreative(api, opts) {
8150
8704
  });
8151
8705
  }
8152
8706
  __name(submitCreative, "submitCreative");
8153
- var ResearchTargetCountryEnum = z42.enum([
8707
+ var ResearchTargetCountryEnum = z44.enum([
8154
8708
  "CO",
8155
8709
  "MX",
8156
8710
  "PE",
8157
8711
  "EC",
8158
8712
  "CL"
8159
8713
  ]);
8160
- var ResearchCategoryEnum = z42.enum([
8714
+ var ResearchCategoryEnum = z44.enum([
8161
8715
  "auto",
8162
8716
  "automotive",
8163
8717
  "kitchen",
@@ -8230,6 +8784,15 @@ var McpErrorCode = {
8230
8784
  SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
8231
8785
  // Meta / Facebook Ads
8232
8786
  META_NO_FB_PAGE: "META_NO_FB_PAGE",
8787
+ META_PORTFOLIO_MISMATCH: "META_PORTFOLIO_MISMATCH",
8788
+ META_PAGE_SELECTION_REQUIRED: "META_PAGE_SELECTION_REQUIRED",
8789
+ META_INVALID_PAGE: "META_INVALID_PAGE",
8790
+ META_STAGED_ASSET_NOT_FOUND: "META_STAGED_ASSET_NOT_FOUND",
8791
+ META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
8792
+ META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
8793
+ META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
8794
+ META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
8795
+ META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
8233
8796
  META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
8234
8797
  META_NO_ASSETS_READY: "META_NO_ASSETS_READY",
8235
8798
  META_NO_THUMBNAIL_SOURCE: "META_NO_THUMBNAIL_SOURCE",
@@ -8282,6 +8845,15 @@ var CODE_MESSAGES = {
8282
8845
  SHOPIFY_PUBLISH_FAILED: "Shopify rejected the publish. Check that the product/landing does not already exist on the store and that your Shopify plan allows additional products.",
8283
8846
  // Meta — high-frequency
8284
8847
  META_NO_FB_PAGE: "No Facebook Page is associated with this account. Create one at https://www.facebook.com/pages/create before publishing (Meta requires a Page to run ads).",
8848
+ META_PORTFOLIO_MISMATCH: "The selected Meta portfolio does not contain the selected ad account. Call `list_meta_destinations` again and choose an account from that portfolio.",
8849
+ META_PAGE_SELECTION_REQUIRED: "Meta has multiple Facebook Pages available. Ask the user to choose one with `list_meta_destinations` and retry with its exact `pageId`.",
8850
+ META_INVALID_PAGE: "The selected Facebook Page is not available for this Meta connection. Call `list_meta_destinations` again and choose an available `pageId`.",
8851
+ META_STAGED_ASSET_NOT_FOUND: "The temporary media upload was not found or expired. Re-upload the media and retry the Meta publication.",
8852
+ META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
8853
+ META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
8854
+ META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
8855
+ META_PUBLICATION_RECONCILIATION_REQUIRED: "A previous Meta publication with these exact inputs may already exist. Check Meta before retrying so you do not create a duplicate campaign.",
8856
+ META_PUBLICATION_IN_PROGRESS: "A Meta publication with these exact inputs is already in progress. Wait for it to finish, then check the campaign status.",
8285
8857
  META_NO_ADS_CREATED: "No ads could be created. Most likely: the Meta Ads account has no payment method, no Facebook Page is linked, or the Facebook app lost permissions. Verify all three at https://business.facebook.com.",
8286
8858
  META_NO_ASSETS_READY: "The task has no videos or ad creative ready to publish yet. Wait for generation to finish or regenerate the assets.",
8287
8859
  META_NO_THUMBNAIL_SOURCE: "No image is available to use as the video thumbnail. Upload a product image or generate the ad creative before publishing.",
@@ -8395,6 +8967,12 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
8395
8967
  McpErrorCode.CAMPAIGN_MISSING_LANDING_URL,
8396
8968
  McpErrorCode.CAMPAIGN_RECONCILIATION_REQUIRED,
8397
8969
  McpErrorCode.META_NO_LANDING_URL,
8970
+ McpErrorCode.META_PORTFOLIO_MISMATCH,
8971
+ McpErrorCode.META_PAGE_SELECTION_REQUIRED,
8972
+ McpErrorCode.META_INVALID_PAGE,
8973
+ McpErrorCode.META_STAGED_ASSET_NOT_FOUND,
8974
+ McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
8975
+ McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
8398
8976
  McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
8399
8977
  McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
8400
8978
  McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
@@ -8416,6 +8994,15 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
8416
8994
  McpErrorCode.META_MISSING_PAYMENT,
8417
8995
  McpErrorCode.META_ACCOUNT_RESTRICTED,
8418
8996
  McpErrorCode.META_NO_FB_PAGE,
8997
+ McpErrorCode.META_PORTFOLIO_MISMATCH,
8998
+ McpErrorCode.META_PAGE_SELECTION_REQUIRED,
8999
+ McpErrorCode.META_INVALID_PAGE,
9000
+ McpErrorCode.META_STAGED_ASSET_NOT_FOUND,
9001
+ McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
9002
+ McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
9003
+ McpErrorCode.META_STAGED_ASSET_INVALID,
9004
+ McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
9005
+ McpErrorCode.META_PUBLICATION_IN_PROGRESS,
8419
9006
  McpErrorCode.TIKTOK_TOKEN_EXPIRED,
8420
9007
  McpErrorCode.TIKTOK_PERMISSION_DENIED,
8421
9008
  McpErrorCode.TIKTOK_MISSING_PAYMENT,
@@ -8769,52 +9356,53 @@ function renderOutputsMarkdown(outputs) {
8769
9356
  __name(renderOutputsMarkdown, "renderOutputsMarkdown");
8770
9357
 
8771
9358
  // src/tools/tasks.ts
8772
- var CreateProductTaskInput = z43.object({
9359
+ var CreateProductTaskInput = z45.object({
8773
9360
  images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
8774
- projectId: z43.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
8775
- productUrl: z43.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
9361
+ projectId: z45.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
9362
+ productUrl: z45.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
8776
9363
  language: Lang.describe('C\xF3digo de idioma para el contenido generado. Ejemplos: "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas "es" por default.'),
8777
9364
  country: Country.describe('C\xF3digo ISO del pa\xEDs objetivo. Ejemplos: "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago locales y tono. REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat.'),
8778
- price: z43.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
8779
- compareAtPrice: z43.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
8780
- offer: z43.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
8781
- paymentMethods: z43.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
8782
- productContext: z43.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
8783
- productLabel: z43.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
8784
- videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s y 30s, excepto "problem_solution", que requiere 30s Multiescena. Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla). Si el usuario pide algo fuera de esta lista, p\xEDdele una opci\xF3n v\xE1lida.'),
8785
- durationSeconds: z43.union([
8786
- z43.literal(15),
8787
- z43.literal(30)
9365
+ price: z45.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
9366
+ compareAtPrice: z45.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
9367
+ offer: z45.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
9368
+ paymentMethods: z45.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
9369
+ productContext: z45.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
9370
+ productLabel: z45.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
9371
+ videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s (single_call) y 30s (Multiescena). Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla). Si el usuario pide algo fuera de esta lista, p\xEDdele una opci\xF3n v\xE1lida.'),
9372
+ durationSeconds: z45.union([
9373
+ z45.literal(15),
9374
+ z45.literal(30)
8788
9375
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
8789
- pauseForAngleReview: z43.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
9376
+ pauseForAngleReview: z45.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
8790
9377
  videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista" para 15s; al pedir durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
8791
9378
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez, entre 3% y 15% m\xE1s caro seg\xFAn formato y tier. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
8792
- multiAngleVideos: z43.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
9379
+ multiAngleVideos: z45.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
8793
9380
  angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "ugc_how_to", "ugc_unboxing_chaos", "problem_product_cta" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase", "wearable_showcase", "cinematic", "wild_card", "animated_problem_pixar", "sassy_product_pixar". angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para varios \xE1ngulos.'),
8794
- outputs: z43.array(z43.enum([
9381
+ outputs: z45.array(z45.enum([
8795
9382
  "landing",
8796
9383
  "video",
8797
9384
  "image"
8798
9385
  ])).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.'),
8799
- competitorScanEnabled: z43.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."),
8800
- brandId: z43.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."),
8801
- creativeDirection: z43.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.'),
8802
- headline: z43.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar. Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. Se aplica a las IM\xC1GENES y a la portada de la landing de esta task (el video no lo usa; para el video usa scriptNotes). \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
8803
- subheadline: z43.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres."),
9386
+ competitorScanEnabled: z45.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."),
9387
+ brandId: z45.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."),
9388
+ actorId: z45.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."),
9389
+ creativeDirection: z45.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.'),
9390
+ headline: z45.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar. Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. Se aplica a las IM\xC1GENES y a la portada de la landing de esta task (el video no lo usa; para el video usa scriptNotes). \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
9391
+ subheadline: z45.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres."),
8804
9392
  landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution", "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity", "offer_value" o "lifestyle_identity". Default "classic".'),
8805
9393
  landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".')
8806
9394
  }).strict();
8807
- var CreateServiceVideoInput = z43.object({
8808
- landingUrl: z43.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
8809
- additionalContext: z43.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
9395
+ var CreateServiceVideoInput = z45.object({
9396
+ landingUrl: z45.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
9397
+ additionalContext: z45.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
8810
9398
  language: Lang.describe("Idioma del video: es, en o pt."),
8811
- voiceId: z43.string().trim().min(1).optional().describe('Opcional. Voz de ElevenLabs para toda la narraci\xF3n. Usa "auto" para que la IA la alinee con el presentador o env\xEDa un ID concreto de la lista de voces de la web.'),
8812
- brandId: z43.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
8813
- reviewMode: z43.enum([
9399
+ voiceId: z45.string().trim().min(1).optional().describe('Opcional. Voz de ElevenLabs para toda la narraci\xF3n. Usa "auto" para que la IA la alinee con el presentador o env\xEDa un ID concreto de la lista de voces de la web.'),
9400
+ brandId: z45.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
9401
+ reviewMode: z45.enum([
8814
9402
  "automatic",
8815
9403
  "guided"
8816
9404
  ]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
8817
- videoPreset: z43.enum([
9405
+ videoPreset: z45.enum([
8818
9406
  "auto",
8819
9407
  "ugc_dynamic",
8820
9408
  "ugc_how_to",
@@ -8822,34 +9410,35 @@ var CreateServiceVideoInput = z43.object({
8822
9410
  "wild_card"
8823
9411
  ]).default("auto")
8824
9412
  }).strict();
8825
- var GetTaskStatusInput = z43.object({
8826
- taskId: z43.string().min(1),
8827
- waitSeconds: z43.number().int().min(0).max(120).optional()
8828
- });
8829
- var ListTasksInput = z43.object({
8830
- limit: z43.number().int().min(1).max(50).optional(),
8831
- offset: z43.number().int().min(0).optional(),
8832
- search: z43.string().optional()
8833
- });
8834
- var EditTaskDraftInput = z43.object({
8835
- taskId: z43.string().min(1),
8836
- angleIndex: z43.number().int().min(0),
8837
- angle: z43.object({
8838
- angle: z43.string().min(1).max(480),
8839
- buyerPersona: z43.string().min(1).max(360),
8840
- actor: z43.string().min(1).max(360).optional(),
8841
- gender: z43.enum([
9413
+ var GetTaskStatusInput = z45.object({
9414
+ taskId: z45.string().min(1),
9415
+ waitSeconds: z45.number().int().min(0).max(120).optional()
9416
+ });
9417
+ var ListTasksInput = z45.object({
9418
+ limit: z45.number().int().min(1).max(50).optional(),
9419
+ offset: z45.number().int().min(0).optional(),
9420
+ search: z45.string().optional()
9421
+ });
9422
+ var EditTaskDraftInput = z45.object({
9423
+ taskId: z45.string().min(1),
9424
+ angleIndex: z45.number().int().min(0),
9425
+ angle: z45.object({
9426
+ angle: z45.string().min(1).max(480),
9427
+ buyerPersona: z45.string().min(1).max(360),
9428
+ actor: z45.string().min(1).max(360).optional(),
9429
+ actorId: z45.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."),
9430
+ gender: z45.enum([
8842
9431
  "female",
8843
9432
  "male"
8844
9433
  ]).optional(),
8845
- scriptNotes: z43.string().max(2e3).optional()
9434
+ scriptNotes: z45.string().max(2e3).optional()
8846
9435
  })
8847
9436
  });
8848
- var PendingActionPayload = z43.discriminatedUnion("kind", [
8849
- z43.object({
8850
- kind: z43.literal("product_reference_confirmation"),
9437
+ var PendingActionPayload = z45.discriminatedUnion("kind", [
9438
+ z45.object({
9439
+ kind: z45.literal("product_reference_confirmation"),
8851
9440
  userChoice: ProductReferenceUserChoiceEnum,
8852
- confirmedProductNames: z43.array(z43.string().min(1).max(120)).min(2).max(8).optional(),
9441
+ confirmedProductNames: z45.array(z45.string().min(1).max(120)).min(2).max(8).optional(),
8853
9442
  // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
8854
9443
  // reachable via MCP. Valid ONLY together with userChoice='variants' (same
8855
9444
  // product in several colors): renders one shot per color and stitches them,
@@ -8860,50 +9449,50 @@ var PendingActionPayload = z43.discriminatedUnion("kind", [
8860
9449
  // safe because multi-scene IS the intended render for a color variant set,
8861
9450
  // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
8862
9451
  // confirm call; the backend re-gates it (variants + variant_set only).
8863
- useMultiSceneShowcase: z43.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
9452
+ useMultiSceneShowcase: z45.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
8864
9453
  }),
8865
- z43.object({
8866
- kind: z43.literal("angle_review_approval"),
9454
+ z45.object({
9455
+ kind: z45.literal("angle_review_approval"),
8867
9456
  // All edits are optional — empty action body just approves the
8868
9457
  // backend-generated draft as-is. Field shapes mirror the angle-review
8869
9458
  // request contracts in @packages/types/wizard.ts; the handler routes to
8870
9459
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
8871
- customAngle: z43.string().min(1).max(480).optional(),
9460
+ customAngle: z45.string().min(1).max(480).optional(),
8872
9461
  // Keep the shared MCP action within the strictest downstream contract:
8873
9462
  // autopilot/image resume endpoints cap these fields at 1500 characters.
8874
- painPoints: z43.string().max(1500).optional(),
8875
- benefits: z43.string().max(1500).optional(),
8876
- features: z43.array(z43.string().min(1).max(300)).max(10).optional(),
8877
- gapDurationSeconds: z43.union([
8878
- z43.literal(15),
8879
- z43.literal(30)
9463
+ painPoints: z45.string().max(1500).optional(),
9464
+ benefits: z45.string().max(1500).optional(),
9465
+ features: z45.array(z45.string().min(1).max(300)).max(10).optional(),
9466
+ gapDurationSeconds: z45.union([
9467
+ z45.literal(15),
9468
+ z45.literal(30)
8880
9469
  ]).optional().describe("Opcional. Solo para aceptar expl\xEDcitamente la recomendaci\xF3n gapDurationChoice del status. 30 env\xEDa el override double_30s para el \xE1ngulo recomendado; 15 u omitido conserva la duraci\xF3n cotizada.")
8881
9470
  }),
8882
- z43.object({
8883
- kind: z43.literal("service_angle_approval"),
8884
- angleId: z43.string().uuid(),
8885
- content: z43.string().min(1).max(480).optional(),
8886
- creativeDirection: z43.string().min(1).max(1500).optional(),
8887
- actor: z43.string().min(1).max(360).optional()
9471
+ z45.object({
9472
+ kind: z45.literal("service_angle_approval"),
9473
+ angleId: z45.string().uuid(),
9474
+ content: z45.string().min(1).max(480).optional(),
9475
+ creativeDirection: z45.string().min(1).max(1500).optional(),
9476
+ actor: z45.string().min(1).max(360).optional()
8888
9477
  }),
8889
- z43.object({
8890
- kind: z43.literal("service_script_approval"),
8891
- voiceovers: z43.record(z43.string(), z43.string().min(1).max(600)).optional(),
8892
- visualDirections: z43.record(z43.string(), z43.string().min(1).max(1200)).optional()
9478
+ z45.object({
9479
+ kind: z45.literal("service_script_approval"),
9480
+ voiceovers: z45.record(z45.string(), z45.string().min(1).max(600)).optional(),
9481
+ visualDirections: z45.record(z45.string(), z45.string().min(1).max(1200)).optional()
8893
9482
  })
8894
9483
  ]);
8895
- var ResolvePendingActionInput = z43.object({
8896
- taskId: z43.string().min(1),
9484
+ var ResolvePendingActionInput = z45.object({
9485
+ taskId: z45.string().min(1),
8897
9486
  action: PendingActionPayload
8898
9487
  }).strict();
8899
9488
  function resolveVariantConfirmVideoTier(action) {
8900
9489
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
8901
9490
  }
8902
9491
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
8903
- var StatusForResolveSchema = z43.object({
9492
+ var StatusForResolveSchema = z45.object({
8904
9493
  // Metadata used to select the backend resume contract. Older API responses
8905
9494
  // may omit these fields, so the resolver keeps the generic fallback.
8906
- kind: z43.enum([
9495
+ kind: z45.enum([
8907
9496
  "autopilot",
8908
9497
  "video",
8909
9498
  "image",
@@ -8911,24 +9500,24 @@ var StatusForResolveSchema = z43.object({
8911
9500
  "mix",
8912
9501
  "service"
8913
9502
  ]).optional(),
8914
- selectedOutputs: z43.array(z43.enum([
9503
+ selectedOutputs: z45.array(z45.enum([
8915
9504
  "landing",
8916
9505
  "video",
8917
9506
  "image"
8918
9507
  ])).optional(),
8919
- videoAngleCount: z43.number().int().min(1).max(3).optional(),
8920
- ugcFormat: z43.enum([
9508
+ videoAngleCount: z45.number().int().min(1).max(3).optional(),
9509
+ ugcFormat: z45.enum([
8921
9510
  "single_8s",
8922
9511
  "single_15s",
8923
9512
  "double_30s"
8924
9513
  ]).optional(),
8925
- videoStyle: z43.enum([
9514
+ videoStyle: z45.enum([
8926
9515
  "ugc",
8927
9516
  "cartoon_3d",
8928
9517
  "product_only",
8929
9518
  "ai_dynamic"
8930
9519
  ]).optional(),
8931
- videoModel: z43.enum([
9520
+ videoModel: z45.enum([
8932
9521
  "veo",
8933
9522
  "wan",
8934
9523
  "bytedance",
@@ -8936,39 +9525,45 @@ var StatusForResolveSchema = z43.object({
8936
9525
  "grok",
8937
9526
  "kling"
8938
9527
  ]).optional(),
8939
- videoTier: z43.enum([
9528
+ videoTier: z45.enum([
8940
9529
  "legacy",
8941
9530
  "standard",
8942
9531
  "pro",
8943
9532
  "single_call"
8944
9533
  ]).optional(),
8945
- videoResolution: z43.enum([
9534
+ videoResolution: z45.enum([
8946
9535
  "480p",
8947
9536
  "720p"
8948
9537
  ]).optional(),
8949
9538
  narrativeStyle: NarrativeStyleEnum.optional(),
8950
- pendingUserAction: z43.object({
8951
- kind: z43.string(),
8952
- candidateGrouping: z43.string().optional(),
8953
- selectedImageUrls: z43.array(z43.string()).optional(),
8954
- proposedProductNames: z43.array(z43.string()).optional(),
8955
- customAngleDraft: z43.string().optional(),
8956
- painPointsDraft: z43.string().optional(),
8957
- benefitsDraft: z43.string().optional(),
8958
- featuresDraft: z43.array(z43.string()).optional(),
9539
+ pendingUserAction: z45.object({
9540
+ kind: z45.string(),
9541
+ candidateGrouping: z45.string().optional(),
9542
+ selectedImageUrls: z45.array(z45.string()).optional(),
9543
+ proposedProductNames: z45.array(z45.string()).optional(),
9544
+ customAngleDraft: z45.string().optional(),
9545
+ painPointsDraft: z45.string().optional(),
9546
+ benefitsDraft: z45.string().optional(),
9547
+ featuresDraft: z45.array(z45.string()).optional(),
8959
9548
  // Recommendation consumed only when the caller explicitly sends
8960
9549
  // gapDurationSeconds: 30 in the angle approval action.
8961
9550
  gapDurationChoice: GapDurationChoiceMirror.optional(),
8962
- angles: z43.array(z43.object({
8963
- id: z43.string().uuid(),
8964
- content: z43.string(),
8965
- creativeDirection: z43.string(),
8966
- actor: z43.string()
8967
- })).optional(),
8968
- panels: z43.array(z43.object({
8969
- beatId: z43.string(),
8970
- voiceover: z43.string(),
8971
- visualDirection: z43.string()
9551
+ // `.passthrough()` + an explicit `actorId`: this object is a MIRROR of the
9552
+ // backend's pending service angle, and a bare z.object() STRIPS unknown
9553
+ // keys. `actorId` (the saved custom actor the picker stored) was silently
9554
+ // dropped here, so the approval handler below could not echo it back on the
9555
+ // PATCH — the selection looked accepted and reached no render.
9556
+ angles: z45.array(z45.object({
9557
+ id: z45.string().uuid(),
9558
+ content: z45.string(),
9559
+ creativeDirection: z45.string(),
9560
+ actor: z45.string(),
9561
+ actorId: z45.string().nullable().optional()
9562
+ }).passthrough()).optional(),
9563
+ panels: z45.array(z45.object({
9564
+ beatId: z45.string(),
9565
+ voiceover: z45.string(),
9566
+ visualDirection: z45.string()
8972
9567
  }).passthrough()).optional()
8973
9568
  }).optional()
8974
9569
  }).passthrough();
@@ -9009,7 +9604,6 @@ __name(resolveStatusTaskKind, "resolveStatusTaskKind");
9009
9604
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
9010
9605
  server.tool("create_product_task", "Crea una tarea de producto desde fotos o desde un proyecto guiado propio. Por defecto genera los 3 outputs (landing + video + image creatives) EN UN MISMO TASK. Devuelve un taskId.\n\nINPUTS REQUERIDOS: images (1-5 fotos del MISMO producto; la primera es la hero) O projectId si el proyecto ya tiene una imagen guardada; adem\xE1s language y country.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son inputs de alto impacto en la landing y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto del chat: preg\xFAntale expl\xEDcitamente, incluso si est\xE1 en LATAM.\n\nSi env\xEDas projectId, debe ser el proyecto propio que el usuario acaba de costear; el backend resolver\xE1 la imagen privada y conservar\xE1 todos los controles de cr\xE9ditos/confirmaci\xF3n. Si el usuario adjunta images junto con projectId, esas fotos reemplazan la referencia solo para este task.\n\nANTES de invocar, DILE CLARAMENTE qu\xE9 outputs vas a generar y que `create_product_task` los genera juntos en un mismo task.\n\nUsa SOLO valores declarados en los enums.", CreateProductTaskInput.shape, async (input) => {
9011
9606
  try {
9012
- assertVideoPresetDurationCompatibility(input.videoPreset, input.durationSeconds);
9013
9607
  if (!input.images && !input.projectId) {
9014
9608
  throw new Error("images es requerido salvo que env\xEDes projectId.");
9015
9609
  }
@@ -9044,6 +9638,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
9044
9638
  "options.selectedOutputs": input.outputs,
9045
9639
  "options.competitorScanEnabled": input.competitorScanEnabled,
9046
9640
  "options.brandId": input.brandId,
9641
+ "options.actorId": input.actorId,
9047
9642
  "options.creativeDirection": input.creativeDirection,
9048
9643
  "options.headline": input.headline,
9049
9644
  "options.subheadline": input.subheadline,
@@ -9185,7 +9780,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
9185
9780
  throw translateError(e);
9186
9781
  }
9187
9782
  });
9188
- 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), gender (female|male), scriptNotes. Use this only before generation completes. Editing already-generated images or videos is not supported.", EditTaskDraftInput.shape, async (input) => {
9783
+ 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, async (input) => {
9189
9784
  try {
9190
9785
  const angle = await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/angles/${input.angleIndex}`, input.angle ?? {});
9191
9786
  return asText({
@@ -9361,7 +9956,13 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
9361
9956
  const edits = {
9362
9957
  content: action.content ?? candidate.content,
9363
9958
  creativeDirection: action.creativeDirection ?? candidate.creativeDirection,
9364
- actor: action.actor ?? candidate.actor
9959
+ actor: action.actor ?? candidate.actor,
9960
+ // Echo the persisted saved actor. Omitted when absent so the PATCH
9961
+ // never writes a null over a selection made elsewhere — the
9962
+ // backend only updates the keys it receives.
9963
+ ...candidate.actorId ? {
9964
+ actorId: candidate.actorId
9965
+ } : {}
9365
9966
  };
9366
9967
  await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/${encodeURIComponent(candidate.id)}`, edits);
9367
9968
  const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/select`, {
@@ -9402,58 +10003,58 @@ __name(registerTaskTools, "registerTaskTools");
9402
10003
 
9403
10004
  // src/tools/creatives.ts
9404
10005
  import { randomUUID } from "crypto";
9405
- import { z as z44 } from "zod";
10006
+ import { z as z46 } from "zod";
9406
10007
  var CreativeRefs = {
9407
10008
  images: ImagesInput,
9408
10009
  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.'),
9409
10010
  country: Country.describe('Pa\xEDs objetivo ISO. Ej. "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago y tono. REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
9410
- price: z44.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
9411
- offer: z44.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
9412
- compareAtPrice: z44.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
9413
- paymentMethods: z44.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago a mostrar. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
9414
- productContext: z44.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
9415
- productLabel: z44.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.'),
9416
- buyerPersona: z44.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.'),
9417
- isCombo: z44.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
9418
- competitorScanEnabled: z44.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."),
9419
- brandId: z44.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."),
9420
- projectId: z44.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
9421
- creativeDirection: z44.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Aplica SOLO a im\xE1genes y landing. Ej. im\xE1genes: "amarillo, azul y rojo, minimalista"; landing: "elegante, paleta fr\xEDa". Para video usa scriptNotes (ambiente/escenario). M\xE1x 500 caracteres.'),
9422
- headline: z44.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar en la imagen (o en la portada de la landing). Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
9423
- subheadline: z44.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres.")
10011
+ price: z46.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
10012
+ offer: z46.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
10013
+ compareAtPrice: z46.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
10014
+ paymentMethods: z46.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago a mostrar. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
10015
+ productContext: z46.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
10016
+ productLabel: z46.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.'),
10017
+ buyerPersona: z46.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.'),
10018
+ isCombo: z46.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
10019
+ competitorScanEnabled: z46.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."),
10020
+ brandId: z46.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."),
10021
+ projectId: z46.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
10022
+ creativeDirection: z46.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Aplica SOLO a im\xE1genes y landing. Ej. im\xE1genes: "amarillo, azul y rojo, minimalista"; landing: "elegante, paleta fr\xEDa". Para video usa scriptNotes (ambiente/escenario). M\xE1x 500 caracteres.'),
10023
+ headline: z46.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar en la imagen (o en la portada de la landing). Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
10024
+ subheadline: z46.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres.")
9424
10025
  };
9425
- var VideoInput = z44.object({
10026
+ var VideoInput = z46.object({
9426
10027
  images: CreativeRefs.images,
9427
- title: z44.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
9428
- primaryAngle: z44.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
10028
+ title: z46.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
10029
+ primaryAngle: z46.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
9429
10030
  language: CreativeRefs.language,
9430
10031
  country: CreativeRefs.country,
9431
- aspectRatio: z44.enum([
10032
+ aspectRatio: z46.enum([
9432
10033
  "9:16",
9433
10034
  "16:9",
9434
10035
  "Auto"
9435
10036
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
9436
- durationSeconds: z44.union([
9437
- z44.literal(15),
9438
- z44.literal(30)
10037
+ durationSeconds: z46.union([
10038
+ z46.literal(15),
10039
+ z46.literal(30)
9439
10040
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
9440
- videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s y 30s, excepto "problem_solution", que requiere 30s Multiescena. Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla).'),
10041
+ videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s (single_call) y 30s (Multiescena). Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla).'),
9441
10042
  videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista"; para durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
9442
10043
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez, entre 3% y 15% m\xE1s caro seg\xFAn formato y tier. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
9443
- features: z44.array(z44.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
10044
+ features: z46.array(z46.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
9444
10045
  price: CreativeRefs.price,
9445
10046
  offer: CreativeRefs.offer,
9446
10047
  productContext: CreativeRefs.productContext,
9447
10048
  productLabel: CreativeRefs.productLabel,
9448
- cta: z44.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.'),
9449
- scriptNotes: z44.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 \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
9450
- pauseForVideoScriptReview: z44.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
9451
- pauseForAngleReview: z44.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. Default false."),
10049
+ cta: z46.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.'),
10050
+ scriptNotes: z46.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 \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
10051
+ pauseForVideoScriptReview: z46.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
10052
+ pauseForAngleReview: z46.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. Default false."),
9452
10053
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
9453
10054
  brandId: CreativeRefs.brandId,
9454
10055
  projectId: CreativeRefs.projectId
9455
10056
  }).strict();
9456
- var FreeVideoQualityEnum = z44.enum([
10057
+ var FreeVideoQualityEnum = z46.enum([
9457
10058
  "economical",
9458
10059
  "high_detail"
9459
10060
  ]);
@@ -9475,38 +10076,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
9475
10076
  15
9476
10077
  ]
9477
10078
  };
9478
- var FreeVideoInput = z44.object({
10079
+ var FreeVideoInput = z46.object({
9479
10080
  images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
9480
- rawPrompt: z44.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
10081
+ rawPrompt: z46.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
9481
10082
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
9482
- durationSeconds: z44.number().int().min(1).max(15).describe("Duraci\xF3n exacta. Econ\xF3mico permite 6, 8, 10 o 15s; Alto detalle permite 4, 8, 12 o 15s."),
10083
+ durationSeconds: z46.number().int().min(1).max(15).describe("Duraci\xF3n exacta. Econ\xF3mico permite 6, 8, 10 o 15s; Alto detalle permite 4, 8, 12 o 15s."),
9483
10084
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
9484
- generateAiMusic: z44.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
9485
- addSubtitles: z44.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
9486
- idempotencyKey: z44.string().uuid().optional().describe("Opcional. UUID para reintentar exactamente esta misma solicitud sin crear ni cobrar otro video. Reutiliza el mismo valor si el cliente no recibi\xF3 la respuesta.")
10085
+ generateAiMusic: z46.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
10086
+ addSubtitles: z46.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
10087
+ idempotencyKey: z46.string().uuid().optional().describe("Opcional. UUID para reintentar exactamente esta misma solicitud sin crear ni cobrar otro video. Reutiliza el mismo valor si el cliente no recibi\xF3 la respuesta.")
9487
10088
  }).strict();
9488
10089
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
9489
10090
  var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
9490
10091
  var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
9491
10092
  var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
9492
- var ImageInput = z44.object({
10093
+ var ImageInput = z46.object({
9493
10094
  images: CreativeRefs.images,
9494
- productTitle: z44.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
9495
- primaryAngle: z44.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
10095
+ productTitle: z46.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
10096
+ primaryAngle: z46.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
9496
10097
  buyerPersona: CreativeRefs.buyerPersona,
9497
10098
  language: CreativeRefs.language,
9498
10099
  country: CreativeRefs.country,
9499
- nVariants: z44.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
9500
- imageModel: z44.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
10100
+ nVariants: z46.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
10101
+ imageModel: z46.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
9501
10102
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
9502
10103
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe(`Opcional. Arquetipo CRO de la composici\xF3n. Valores: ${archetypeList}. Si se omite, la IA escoge seg\xFAn \xE1ngulo+oferta (ej. "offer_value_prop" cuando hay descuento, "social_proof_trust" cuando el \xE1ngulo es testimonial).`),
9503
10104
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe(`Opcional. Tratamiento gr\xE1fico. Valores: ${visualTreatmentList} ("performance_product_ad" = dealer-style alto CTR para Meta/TikTok ads; "premium_dtc_carousel" = fotograf\xEDa limpia estilo Allbirds/Glossier). Si se omite, la IA escoge.`),
9504
- styleMix: z44.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
10105
+ styleMix: z46.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
9505
10106
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe(`Opcional. Relaci\xF3n de aspecto. Valores: ${aspectRatioList}. Default "4:5" (vertical Instagram feed).`),
9506
- generateAdCopy: z44.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
9507
- pauseForAngleReview: z44.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo y antes de las im\xE1genes para que el usuario edite v\xEDa edit_task_draft. Default false."),
9508
- features: z44.array(z44.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
9509
- brandColors: z44.array(z44.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
10107
+ generateAdCopy: z46.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
10108
+ pauseForAngleReview: z46.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo y antes de las im\xE1genes para que el usuario edite v\xEDa edit_task_draft. Default false."),
10109
+ features: z46.array(z46.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
10110
+ brandColors: z46.array(z46.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
9510
10111
  price: CreativeRefs.price,
9511
10112
  compareAtPrice: CreativeRefs.compareAtPrice,
9512
10113
  offer: CreativeRefs.offer,
@@ -9520,16 +10121,16 @@ var ImageInput = z44.object({
9520
10121
  headline: CreativeRefs.headline,
9521
10122
  subheadline: CreativeRefs.subheadline
9522
10123
  });
9523
- var EditImageCreativeInput = z44.object({
9524
- taskId: z44.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
9525
- variantIndex: z44.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
9526
- instruction: z44.string().min(1).max(1e3).describe('Qu\xE9 cambiar en esa imagen. Ej. "quita el badge de env\xEDo gratis", "cambia el fondo a blanco".')
10124
+ var EditImageCreativeInput = z46.object({
10125
+ taskId: z46.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
10126
+ variantIndex: z46.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
10127
+ instruction: z46.string().min(1).max(1e3).describe('Qu\xE9 cambiar en esa imagen. Ej. "quita el badge de env\xEDo gratis", "cambia el fondo a blanco".')
9527
10128
  });
9528
- var LandingInput = z44.object({
10129
+ var LandingInput = z46.object({
9529
10130
  images: CreativeRefs.images,
9530
10131
  language: CreativeRefs.language,
9531
10132
  country: CreativeRefs.country,
9532
- salesAngle: z44.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
10133
+ salesAngle: z46.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
9533
10134
  buyerPersona: CreativeRefs.buyerPersona,
9534
10135
  price: CreativeRefs.price,
9535
10136
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -9578,9 +10179,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
9578
10179
  throw translateError(e);
9579
10180
  }
9580
10181
  });
9581
- server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (15 o 30), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nESTILO: 15s usa "Realista" (single_call); 30s usa "Multiescena" (multi-toma, aproximadamente 30s). Multiescena se factura por escena, as\xED que avisa el costo antes de activarlo.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes. Para revisar el \xE1ngulo y decidir duraci\xF3n tras el an\xE1lisis de competencia, pasa `pauseForAngleReview: true` y luego resuelve `angle_review_approval` con `gapDurationSeconds`.', VideoInput.shape, async (input) => {
10182
+ server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (15 o 30), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nCOMPETENCIA Y GAP REPLICATION: con videoPreset="auto" (default) y competitorScanEnabled=true (default), el sistema analiza anuncios de competencia. Si encuentra un winner de video coherente, ancla el primaryAngle y genera autom\xE1ticamente la narrativa `gap_replication` para copiar su estructura audiovisual. No la selecciones manualmente: es un resultado del an\xE1lisis. Si competitorScanEnabled=false, esta b\xFAsqueda se omite.\n\nESTILO: 15s usa "Realista" (single_call); 30s usa "Multiescena" (multi-toma, aproximadamente 30s). Multiescena se factura por escena, as\xED que avisa el costo antes de activarlo.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes. Para revisar el \xE1ngulo y decidir duraci\xF3n tras el an\xE1lisis de competencia, pasa `pauseForAngleReview: true` y luego resuelve `angle_review_approval` con `gapDurationSeconds`.', VideoInput.shape, async (input) => {
9582
10183
  try {
9583
- assertVideoPresetDurationCompatibility(input.videoPreset, input.durationSeconds);
9584
10184
  const resolved = resolveVideoPreset(input.videoPreset);
9585
10185
  const wireTier = resolveWireVideoTier(input.videoTier, input.durationSeconds);
9586
10186
  const { videoTier: _publicTier, videoResolution: _vr, projectId, ...rest } = input;
@@ -9655,8 +10255,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
9655
10255
  __name(registerCreativeTools, "registerCreativeTools");
9656
10256
 
9657
10257
  // src/tools/creative-variants.ts
9658
- import { z as z45 } from "zod";
9659
- var LandingSectionKeyEnum = z45.enum([
10258
+ import { z as z47 } from "zod";
10259
+ var LandingSectionKeyEnum = z47.enum([
9660
10260
  "problem_hook",
9661
10261
  "problem_cost",
9662
10262
  "solution_mechanism",
@@ -9696,24 +10296,24 @@ var LandingSectionKeyEnum = z45.enum([
9696
10296
  "lifestyle_fit",
9697
10297
  "join_lifestyle"
9698
10298
  ]);
9699
- var CreativeVariantKindEnum = z45.enum([
10299
+ var CreativeVariantKindEnum = z47.enum([
9700
10300
  "video",
9701
10301
  "image",
9702
10302
  "landing"
9703
10303
  ]);
9704
- var EstimateCreativeVariantInputBase = z45.object({
9705
- sourceTaskId: z45.string().uuid().describe("ID de la tarea original completada."),
10304
+ var EstimateCreativeVariantInputBase = z47.object({
10305
+ sourceTaskId: z47.string().uuid().describe("ID de la tarea original completada."),
9706
10306
  kind: CreativeVariantKindEnum,
9707
10307
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
9708
10308
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
9709
- nVariants: z45.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
10309
+ nVariants: z47.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
9710
10310
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
9711
- instructions: z45.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
10311
+ instructions: z47.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
9712
10312
  }).strict();
9713
10313
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
9714
10314
  if (input.kind === "video" && input.nVariants !== void 0) {
9715
10315
  ctx.addIssue({
9716
- code: z45.ZodIssueCode.custom,
10316
+ code: z47.ZodIssueCode.custom,
9717
10317
  path: [
9718
10318
  "nVariants"
9719
10319
  ],
@@ -9723,7 +10323,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9723
10323
  if (input.kind !== "video") {
9724
10324
  if (input.videoPreset !== void 0) {
9725
10325
  ctx.addIssue({
9726
- code: z45.ZodIssueCode.custom,
10326
+ code: z47.ZodIssueCode.custom,
9727
10327
  path: [
9728
10328
  "videoPreset"
9729
10329
  ],
@@ -9732,7 +10332,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9732
10332
  }
9733
10333
  if (input.videoResolution !== void 0) {
9734
10334
  ctx.addIssue({
9735
- code: z45.ZodIssueCode.custom,
10335
+ code: z47.ZodIssueCode.custom,
9736
10336
  path: [
9737
10337
  "videoResolution"
9738
10338
  ],
@@ -9742,7 +10342,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9742
10342
  }
9743
10343
  if (input.kind !== "image" && input.nVariants !== void 0) {
9744
10344
  ctx.addIssue({
9745
- code: z45.ZodIssueCode.custom,
10345
+ code: z47.ZodIssueCode.custom,
9746
10346
  path: [
9747
10347
  "nVariants"
9748
10348
  ],
@@ -9752,7 +10352,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9752
10352
  if (input.kind !== "landing") {
9753
10353
  if (input.sectionKey !== void 0) {
9754
10354
  ctx.addIssue({
9755
- code: z45.ZodIssueCode.custom,
10355
+ code: z47.ZodIssueCode.custom,
9756
10356
  path: [
9757
10357
  "sectionKey"
9758
10358
  ],
@@ -9761,7 +10361,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9761
10361
  }
9762
10362
  if (input.instructions !== void 0) {
9763
10363
  ctx.addIssue({
9764
- code: z45.ZodIssueCode.custom,
10364
+ code: z47.ZodIssueCode.custom,
9765
10365
  path: [
9766
10366
  "instructions"
9767
10367
  ],
@@ -9770,31 +10370,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9770
10370
  }
9771
10371
  }
9772
10372
  });
9773
- var CreateCreativeVariantInputBase = z45.object({
9774
- sourceTaskId: z45.string().uuid().describe("ID de la tarea original completada."),
10373
+ var CreateCreativeVariantInputBase = z47.object({
10374
+ sourceTaskId: z47.string().uuid().describe("ID de la tarea original completada."),
9775
10375
  kind: CreativeVariantKindEnum,
9776
- confirm: z45.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
9777
- clientRequestId: z45.string().uuid().describe("UUID estable y obligatorio para esta intenci\xF3n. Reutil\xEDzalo ante timeouts o transporte ambiguo. Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n y un UUID nuevo."),
9778
- angle: z45.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
9779
- buyerPersona: z45.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
9780
- scriptNotes: z45.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
9781
- creativeDirection: z45.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
10376
+ confirm: z47.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
10377
+ clientRequestId: z47.string().uuid().describe("UUID estable y obligatorio para esta intenci\xF3n. Reutil\xEDzalo ante timeouts o transporte ambiguo. Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n y un UUID nuevo."),
10378
+ angle: z47.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
10379
+ buyerPersona: z47.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
10380
+ scriptNotes: z47.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
10381
+ creativeDirection: z47.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
9782
10382
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
9783
10383
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
9784
- generateAiMusic: z45.boolean().optional().describe("Opcional para video."),
9785
- nVariants: z45.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
10384
+ generateAiMusic: z47.boolean().optional().describe("Opcional para video."),
10385
+ nVariants: z47.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
9786
10386
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
9787
10387
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
9788
10388
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
9789
10389
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
9790
- styleMix: z45.boolean().optional().describe("Opcional para imagen."),
10390
+ styleMix: z47.boolean().optional().describe("Opcional para imagen."),
9791
10391
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
9792
- instructions: z45.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
10392
+ instructions: z47.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
9793
10393
  }).strict();
9794
10394
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
9795
10395
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
9796
10396
  ctx.addIssue({
9797
- code: z45.ZodIssueCode.custom,
10397
+ code: z47.ZodIssueCode.custom,
9798
10398
  path: [
9799
10399
  "angle"
9800
10400
  ],
@@ -9803,7 +10403,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
9803
10403
  }
9804
10404
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
9805
10405
  ctx.addIssue({
9806
- code: z45.ZodIssueCode.custom,
10406
+ code: z47.ZodIssueCode.custom,
9807
10407
  path: [
9808
10408
  "instructions"
9809
10409
  ],
@@ -9849,7 +10449,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
9849
10449
  for (const field of fieldsByKind[input.kind]) {
9850
10450
  if (input[field] !== void 0) {
9851
10451
  ctx.addIssue({
9852
- code: z45.ZodIssueCode.custom,
10452
+ code: z47.ZodIssueCode.custom,
9853
10453
  path: [
9854
10454
  field
9855
10455
  ],
@@ -9966,27 +10566,27 @@ function registerCreativeVariantTools(server, api) {
9966
10566
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
9967
10567
 
9968
10568
  // src/tools/script-review.ts
9969
- import { z as z46 } from "zod";
9970
- var GetVideoScriptInput = z46.object({
9971
- taskId: z46.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
9972
- angleKey: z46.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.')
10569
+ import { z as z48 } from "zod";
10570
+ var GetVideoScriptInput = z48.object({
10571
+ taskId: z48.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
10572
+ angleKey: z48.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.')
9973
10573
  }).strict();
9974
10574
  var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
9975
10575
  var VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2 = 2e3;
9976
- var ApproveSceneEdit = z46.object({
9977
- index: z46.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
9978
- dialogue: z46.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
10576
+ var ApproveSceneEdit = z48.object({
10577
+ index: z48.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
10578
+ dialogue: z48.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
9979
10579
  message: "dialogue no puede ser s\xF3lo espacios"
9980
10580
  }).describe("Di\xE1logo final de la escena. NO puede estar vac\xEDo ni ser s\xF3lo espacios. Puede ser el original sin cambios o la edici\xF3n del usuario.")
9981
10581
  }).strict();
9982
- var ApproveVideoScriptInput = z46.object({
9983
- taskId: z46.string().min(1).describe("ID de la tarea."),
9984
- angleKey: z46.string().trim().min(1).max(120).optional().describe('Mismo angleKey usado en get_video_script. REQUERIDO en multi-angle (si se omite el backend resuelve a "angle_0" literal). En single-angle puede omitirse.'),
9985
- scenes: z46.array(ApproveSceneEdit).min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_SCENES2).describe("Escenas con los di\xE1logos finales. El backend mergea por `index` \u2014 las escenas NO incluidas conservan el di\xE1logo original. Debe haber entre 1 y 50 entradas. Para cada entrada que mandes con un dialogue distinto del original, el backend valida minWords \u2264 words \u2264 maxWords y rechaza con 400 si te sales del rango.")
10582
+ var ApproveVideoScriptInput = z48.object({
10583
+ taskId: z48.string().min(1).describe("ID de la tarea."),
10584
+ angleKey: z48.string().trim().min(1).max(120).optional().describe('Mismo angleKey usado en get_video_script. REQUERIDO en multi-angle (si se omite el backend resuelve a "angle_0" literal). En single-angle puede omitirse.'),
10585
+ scenes: z48.array(ApproveSceneEdit).min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_SCENES2).describe("Escenas con los di\xE1logos finales. El backend mergea por `index` \u2014 las escenas NO incluidas conservan el di\xE1logo original. Debe haber entre 1 y 50 entradas. Para cada entrada que mandes con un dialogue distinto del original, el backend valida minWords \u2264 words \u2264 maxWords y rechaza con 400 si te sales del rango.")
9986
10586
  }).strict();
9987
- var RegenerateVideoScriptInput = z46.object({
9988
- taskId: z46.string().min(1).describe("ID de la tarea."),
9989
- angleKey: z46.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
10587
+ var RegenerateVideoScriptInput = z48.object({
10588
+ taskId: z48.string().min(1).describe("ID de la tarea."),
10589
+ angleKey: z48.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
9990
10590
  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).')
9991
10591
  }).strict();
9992
10592
  function registerScriptReviewTools(server, api) {
@@ -10026,9 +10626,9 @@ function registerScriptReviewTools(server, api) {
10026
10626
  __name(registerScriptReviewTools, "registerScriptReviewTools");
10027
10627
 
10028
10628
  // src/tools/connections.ts
10029
- import { z as z47 } from "zod";
10629
+ import { z as z49 } from "zod";
10030
10630
  function registerConnectionTools(server, api) {
10031
- 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.", z47.object({}).shape, async () => {
10631
+ 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.", z49.object({}).shape, async () => {
10032
10632
  try {
10033
10633
  const r = await api.getJson("/v1/shopify/shops");
10034
10634
  return asText(r);
@@ -10036,10 +10636,10 @@ function registerConnectionTools(server, api) {
10036
10636
  throw translateError(e);
10037
10637
  }
10038
10638
  });
10039
- 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.', z47.object({
10040
- shopDomain: z47.string().min(3).max(120),
10041
- query: z47.string().trim().max(120).optional(),
10042
- taskId: z47.string().uuid().optional()
10639
+ 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.', z49.object({
10640
+ shopDomain: z49.string().min(3).max(120),
10641
+ query: z49.string().trim().max(120).optional(),
10642
+ taskId: z49.string().uuid().optional()
10043
10643
  }).shape, async ({ shopDomain, query, taskId }) => {
10044
10644
  try {
10045
10645
  const params = new URLSearchParams({
@@ -10053,7 +10653,7 @@ function registerConnectionTools(server, api) {
10053
10653
  throw translateError(e);
10054
10654
  }
10055
10655
  });
10056
- server.tool("list_meta_ad_accounts", "Lista las cuentas publicitarias de Meta conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar Meta en productmaker.app/integrations.", z47.object({}).shape, async () => {
10656
+ 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.", z49.object({}).shape, async () => {
10057
10657
  try {
10058
10658
  const r = await api.getJson("/v1/facebook/ad-accounts");
10059
10659
  return asText(r);
@@ -10061,7 +10661,15 @@ function registerConnectionTools(server, api) {
10061
10661
  throw translateError(e);
10062
10662
  }
10063
10663
  });
10064
- 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.", z47.object({}).shape, async () => {
10664
+ server.tool("list_meta_destinations", "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.", z49.object({}).shape, async () => {
10665
+ try {
10666
+ const r = await api.getJson("/v1/facebook/destinations");
10667
+ return asText(r);
10668
+ } catch (e) {
10669
+ throw translateError(e);
10670
+ }
10671
+ });
10672
+ 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.", z49.object({}).shape, async () => {
10065
10673
  try {
10066
10674
  const r = await api.getJson("/v1/tiktok/advertisers");
10067
10675
  return asText(r);
@@ -10072,106 +10680,140 @@ function registerConnectionTools(server, api) {
10072
10680
  }
10073
10681
  __name(registerConnectionTools, "registerConnectionTools");
10074
10682
 
10683
+ // src/tools/actors.ts
10684
+ import { z as z50 } from "zod";
10685
+ function registerActorTools(server, api) {
10686
+ 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.", z50.object({}).shape, async () => {
10687
+ try {
10688
+ const rows = await api.getJson("/v1/actors");
10689
+ const actors = (Array.isArray(rows) ? rows : []).filter((actor) => actor.status === "ready").map(({ id, name, description, gender, voiceId }) => ({
10690
+ id,
10691
+ name,
10692
+ description,
10693
+ gender,
10694
+ voiceId: voiceId ?? null
10695
+ }));
10696
+ return asText({
10697
+ actors
10698
+ });
10699
+ } catch (e) {
10700
+ throw translateError(e);
10701
+ }
10702
+ });
10703
+ }
10704
+ __name(registerActorTools, "registerActorTools");
10705
+
10075
10706
  // src/tools/publish.ts
10076
- import { z as z48 } from "zod";
10077
- var ShopifyInput = z48.object({
10078
- taskId: z48.string().min(1),
10079
- shopDomain: z48.string().min(1),
10707
+ import { z as z51 } from "zod";
10708
+ var ShopifyInput = z51.object({
10709
+ taskId: z51.string().min(1),
10710
+ shopDomain: z51.string().min(1),
10080
10711
  /** Override: number of landing images to include. Default = ALL images the
10081
10712
  * task generated (the MCP fetches /v1/results first to count). Bounded
10082
10713
  * generously here; the backend validates each index against
10083
10714
  * landing.images.length and rejects out-of-range. */
10084
- numLandingImages: z48.number().int().min(1).max(20).optional(),
10085
- variantIndex: z48.number().int().min(0).optional(),
10715
+ numLandingImages: z51.number().int().min(1).max(20).optional(),
10716
+ variantIndex: z51.number().int().min(0).optional(),
10086
10717
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
10087
10718
  * When true, the landing renders variant-picker pills; when false, it ships
10088
10719
  * a single-option product page. Defaults to false. */
10089
- showVariantPicker: z48.boolean().optional(),
10720
+ showVariantPicker: z51.boolean().optional(),
10090
10721
  /** Default `auto` updates the product previously published for this task and
10091
10722
  * shop. Use `existing` with targetProductId to choose another product, or
10092
10723
  * `new` only when the user explicitly wants a duplicate/new listing. */
10093
- publicationMode: z48.enum([
10724
+ publicationMode: z51.enum([
10094
10725
  "auto",
10095
10726
  "existing",
10096
10727
  "new"
10097
10728
  ]).optional(),
10098
10729
  /** Shopify product GID, numeric id, or admin product URL. Required when
10099
10730
  * publicationMode is `existing`. */
10100
- targetProductId: z48.string().trim().min(1).max(300).optional()
10731
+ targetProductId: z51.string().trim().min(1).max(300).optional()
10101
10732
  });
10102
- var McpCampaignConfig = z48.object({
10733
+ var McpCampaignConfig = z51.object({
10103
10734
  /** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
10104
10735
  * converts to the account currency server-side. Do NOT pre-convert: on a COP
10105
10736
  * account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
10106
- dailyBudgetUsd: z48.number().positive().optional(),
10737
+ dailyBudgetUsd: z51.number().positive().optional(),
10107
10738
  /** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
10108
10739
  * ad set). TikTok takes only the first entry. */
10109
- targetCountries: z48.array(z48.string().length(2)).max(1).optional(),
10110
- campaignName: z48.string().trim().min(1).max(100).optional()
10740
+ targetCountries: z51.array(z51.string().length(2)).max(1).optional(),
10741
+ campaignName: z51.string().trim().min(1).max(100).optional()
10111
10742
  });
10112
10743
  var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
10113
- var MetaInput = z48.object({
10114
- taskId: z48.string().min(1),
10744
+ var MetaInput = z51.object({
10745
+ taskId: z51.string().min(1),
10746
+ /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
10747
+ portfolioId: z51.string().trim().min(1),
10748
+ /** Exact Facebook Page selected from `list_meta_destinations`. */
10749
+ pageId: z51.string().trim().min(1),
10115
10750
  adAccountId: McpAdAccountId,
10116
- pixelId: z48.string().min(1).optional(),
10117
- landingUrl: z48.string().url().optional(),
10751
+ pixelId: z51.string().min(1).optional(),
10752
+ landingUrl: z51.string().url().optional(),
10118
10753
  /** Select which angles to publish by 0-based index. Omit to publish all
10119
10754
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
10120
- angleIndexes: z48.array(z48.number().int().nonnegative()).optional(),
10755
+ angleIndexes: z51.array(z51.number().int().nonnegative()).optional(),
10121
10756
  campaignConfig: McpCampaignConfig.optional(),
10122
10757
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
10123
10758
  * uses the auto-generated copy attached to the task. Capped at 500 chars
10124
10759
  * (Meta's primary-text limit). */
10125
- primaryText: z48.string().trim().min(1).max(500).optional(),
10760
+ primaryText: z51.string().trim().min(1).max(500).optional(),
10126
10761
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
10127
10762
  * (wa.me) instead of a landing page. */
10128
- phoneNumber: z48.string().min(8).max(32).optional()
10763
+ phoneNumber: z51.string().min(8).max(32).optional()
10129
10764
  });
10130
- var GroupedTaskSource = z48.object({
10131
- type: z48.literal("task"),
10132
- taskId: z48.string().min(1),
10133
- angleIndex: z48.number().int().min(0).optional(),
10134
- videoVariant: z48.enum([
10765
+ var GroupedTaskSource = z51.object({
10766
+ type: z51.literal("task"),
10767
+ taskId: z51.string().min(1),
10768
+ angleIndex: z51.number().int().min(0).optional(),
10769
+ videoVariant: z51.enum([
10135
10770
  "with_subtitles",
10136
10771
  "without_subtitles"
10137
10772
  ]).optional(),
10138
- imageVariantIndex: z48.number().int().min(0).optional()
10139
- });
10140
- var GroupedMediaSource = z48.object({
10141
- type: z48.literal("media"),
10142
- url: z48.string().url().optional(),
10143
- path: z48.string().min(1).optional(),
10144
- thumbnailUrl: z48.string().url().optional(),
10145
- thumbnailPath: z48.string().min(1).optional()
10773
+ imageVariantIndex: z51.number().int().min(0).optional()
10774
+ });
10775
+ var GroupedMediaSource = z51.object({
10776
+ type: z51.literal("media"),
10777
+ url: z51.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_meta_media_upload on the hosted MCP instead."),
10778
+ path: z51.string().min(1).optional().describe('Local file path only when this MCP runs over stdio. On the hosted MCP, call request_meta_media_upload and then use source.type="staged".'),
10779
+ thumbnailUrl: z51.string().url().optional(),
10780
+ thumbnailPath: z51.string().min(1).optional()
10146
10781
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
10147
10782
  message: "media source needs exactly one of `url` or `path`"
10148
10783
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
10149
10784
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
10150
10785
  });
10151
- var GroupedCreative = z48.object({
10152
- type: z48.enum([
10786
+ var GroupedStagedMediaSource = z51.object({
10787
+ type: z51.literal("staged"),
10788
+ /** Returned by check_meta_media_upload after the user selects a local file. */
10789
+ stagingAssetId: z51.string().uuid(),
10790
+ thumbnailAssetId: z51.string().uuid().optional()
10791
+ });
10792
+ var GroupedCreative = z51.object({
10793
+ type: z51.enum([
10153
10794
  "image",
10154
10795
  "video"
10155
10796
  ]),
10156
- source: z48.union([
10797
+ source: z51.union([
10157
10798
  GroupedTaskSource,
10158
- GroupedMediaSource
10799
+ GroupedMediaSource,
10800
+ GroupedStagedMediaSource
10159
10801
  ]),
10160
- title: z48.string().max(120).optional(),
10161
- description: z48.string().max(500).optional(),
10802
+ title: z51.string().max(120).optional(),
10803
+ description: z51.string().max(500).optional(),
10162
10804
  cta: MetaCtaType.optional(),
10163
- creativeId: z48.string().min(1).optional()
10805
+ creativeId: z51.string().min(1).optional()
10164
10806
  });
10165
- var GroupedCampaignConfig = z48.object({
10807
+ var GroupedCampaignConfig = z51.object({
10166
10808
  campaignType: MetaCampaignType.default("sales"),
10167
10809
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
10168
10810
  budgetMode: MetaBudgetMode.default("campaign"),
10169
- campaignDailyBudget: z48.number().int().positive().optional(),
10170
- adSets: z48.array(z48.object({
10171
- tempId: z48.string().min(1).max(80),
10172
- name: z48.string().min(1).max(100),
10173
- dailyBudget: z48.number().int().positive().optional(),
10174
- creativeIds: z48.array(z48.string().min(1)).min(1)
10811
+ campaignDailyBudget: z51.number().int().positive().optional(),
10812
+ adSets: z51.array(z51.object({
10813
+ tempId: z51.string().min(1).max(80),
10814
+ name: z51.string().min(1).max(100),
10815
+ dailyBudget: z51.number().int().positive().optional(),
10816
+ creativeIds: z51.array(z51.string().min(1)).min(1)
10175
10817
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
10176
10818
  /**
10177
10819
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -10179,22 +10821,22 @@ var GroupedCampaignConfig = z48.object({
10179
10821
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
10180
10822
  * the API since #849 — unreachable from this tool.
10181
10823
  */
10182
- destination: z48.discriminatedUnion("type", [
10183
- z48.object({
10184
- type: z48.literal("landing"),
10185
- landingUrl: z48.string().url()
10824
+ destination: z51.discriminatedUnion("type", [
10825
+ z51.object({
10826
+ type: z51.literal("landing"),
10827
+ landingUrl: z51.string().url()
10186
10828
  }),
10187
- z48.object({
10188
- type: z48.literal("whatsapp"),
10189
- phoneNumber: z48.string().min(8).max(32)
10829
+ z51.object({
10830
+ type: z51.literal("whatsapp"),
10831
+ phoneNumber: z51.string().min(8).max(32)
10190
10832
  })
10191
10833
  ]),
10192
- saveAsDefault: z48.boolean().default(false),
10834
+ saveAsDefault: z51.boolean().default(false),
10193
10835
  budgetPolicy: MetaBudgetPolicy.optional()
10194
10836
  }).superRefine((value, ctx) => {
10195
10837
  if (value.budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
10196
10838
  ctx.addIssue({
10197
- code: z48.ZodIssueCode.custom,
10839
+ code: z51.ZodIssueCode.custom,
10198
10840
  path: [
10199
10841
  "campaignDailyBudget"
10200
10842
  ],
@@ -10205,7 +10847,7 @@ var GroupedCampaignConfig = z48.object({
10205
10847
  value.adSets.forEach((adSet, index) => {
10206
10848
  if (adSet.dailyBudget === void 0) {
10207
10849
  ctx.addIssue({
10208
- code: z48.ZodIssueCode.custom,
10850
+ code: z51.ZodIssueCode.custom,
10209
10851
  path: [
10210
10852
  "adSets",
10211
10853
  index,
@@ -10217,22 +10859,26 @@ var GroupedCampaignConfig = z48.object({
10217
10859
  });
10218
10860
  }
10219
10861
  });
10220
- var PublishCreativesToMetaInputBase = z48.object({
10221
- creatives: z48.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10862
+ var PublishCreativesToMetaInputBase = z51.object({
10863
+ creatives: z51.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10864
+ /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
10865
+ portfolioId: z51.string().trim().min(1),
10866
+ /** Exact Facebook Page selected from `list_meta_destinations`. */
10867
+ pageId: z51.string().trim().min(1),
10222
10868
  adAccountId: McpAdAccountId,
10223
- pixelId: z48.string().min(1).optional(),
10869
+ pixelId: z51.string().min(1).optional(),
10224
10870
  /** Required unless `campaignConfig.destination` is whatsapp — see the
10225
10871
  * superRefine below. Mirrors the API's `UploadToMetaRequest`. */
10226
- landingUrl: z48.string().url().optional(),
10227
- targetCountry: z48.string().length(2),
10228
- campaignName: z48.string().trim().min(1).max(100),
10872
+ landingUrl: z51.string().url().optional(),
10873
+ targetCountry: z51.string().length(2),
10874
+ campaignName: z51.string().trim().min(1).max(100),
10229
10875
  campaignConfig: GroupedCampaignConfig.optional()
10230
10876
  });
10231
10877
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
10232
10878
  const destination = value.campaignConfig?.destination;
10233
10879
  if (destination?.type !== "whatsapp" && !value.landingUrl) {
10234
10880
  ctx.addIssue({
10235
- code: z48.ZodIssueCode.custom,
10881
+ code: z51.ZodIssueCode.custom,
10236
10882
  path: [
10237
10883
  "landingUrl"
10238
10884
  ],
@@ -10241,7 +10887,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10241
10887
  }
10242
10888
  if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
10243
10889
  ctx.addIssue({
10244
- code: z48.ZodIssueCode.custom,
10890
+ code: z51.ZodIssueCode.custom,
10245
10891
  path: [
10246
10892
  "campaignConfig",
10247
10893
  "destination",
@@ -10251,46 +10897,50 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10251
10897
  });
10252
10898
  }
10253
10899
  });
10254
- var PresignUploadResponseSchema = z48.object({
10255
- uploadUrl: z48.string().url().refine((value) => {
10900
+ var PresignUploadResponseSchema = z51.object({
10901
+ uploadUrl: z51.string().url().refine((value) => {
10256
10902
  const url = new URL(value);
10257
10903
  return !url.username && !url.password && url.protocol === "https:";
10258
10904
  }, "presigned upload URL must be HTTPS without embedded credentials"),
10259
- publicUrl: z48.string().url().refine((value) => {
10905
+ publicUrl: z51.string().url().refine((value) => {
10260
10906
  const url = new URL(value);
10261
10907
  return !url.username && !url.password;
10262
- }, "public media URL must not contain embedded credentials")
10908
+ }, "public media URL must not contain embedded credentials"),
10909
+ stagingAssetId: z51.string().uuid()
10263
10910
  });
10264
- async function uploadResolvedMedia(api, media) {
10265
- if (media.url) return media.url;
10911
+ async function uploadResolvedMedia(api, media, assetRole) {
10266
10912
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
10267
10913
  const presigned = PresignUploadResponseSchema.parse(await api.postJson("/v1/ads/presign-upload", {
10268
10914
  contentType: media.mimetype,
10269
10915
  fileName: media.filename,
10270
- fileSize: media.buffer.byteLength
10916
+ fileSize: media.buffer.byteLength,
10917
+ purpose: "meta_staging",
10918
+ assetRole
10271
10919
  }));
10272
10920
  await api.putBytes(presigned.uploadUrl, media.buffer, media.mimetype);
10273
- return presigned.publicUrl;
10921
+ return {
10922
+ stagingAssetId: presigned.stagingAssetId
10923
+ };
10274
10924
  }
10275
10925
  __name(uploadResolvedMedia, "uploadResolvedMedia");
10276
- async function resolveAndUploadMedia(api, ref, expectedKind, transport) {
10926
+ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transport) {
10277
10927
  const media = await resolveMediaRef(ref, {
10278
10928
  transport,
10279
10929
  expectedKind
10280
10930
  });
10281
- return uploadResolvedMedia(api, media);
10931
+ return uploadResolvedMedia(api, media, assetRole);
10282
10932
  }
10283
10933
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
10284
- var TikTokInput = z48.object({
10285
- taskId: z48.string().min(1),
10286
- advertiserId: z48.string().min(1),
10287
- pixelId: z48.string().min(1).optional(),
10288
- landingUrl: z48.string().url().optional(),
10289
- angleIndexes: z48.array(z48.number().int().nonnegative()).optional(),
10934
+ var TikTokInput = z51.object({
10935
+ taskId: z51.string().min(1),
10936
+ advertiserId: z51.string().min(1),
10937
+ pixelId: z51.string().min(1).optional(),
10938
+ landingUrl: z51.string().url().optional(),
10939
+ angleIndexes: z51.array(z51.number().int().nonnegative()).optional(),
10290
10940
  campaignConfig: McpCampaignConfig.optional(),
10291
10941
  /** Override the ad's description (TikTok's body copy). If omitted,
10292
10942
  * ProductMaker uses the auto-generated copy attached to the task. */
10293
- primaryText: z48.string().trim().min(1).max(500).optional()
10943
+ primaryText: z51.string().trim().min(1).max(500).optional()
10294
10944
  });
10295
10945
  function registerPublishTools(server, api, opts = {}) {
10296
10946
  const transport = opts.transport ?? "http";
@@ -10329,7 +10979,7 @@ function registerPublishTools(server, api, opts = {}) {
10329
10979
  throw translateError(e);
10330
10980
  }
10331
10981
  });
10332
- server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Si quieres juntar creativos de VARIAS tareas en una misma campa\xF1a o en un mismo ad set, usa `publish_creatives_to_meta` \u2014 esta herramienta crea una campa\xF1a por tarea. `adAccountId` acepta tanto 'act_1234567890' como el id num\xE9rico pelado '1234567890'. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en Meta Ads Manager. Use `list_meta_ad_accounts` antes para un adAccountId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 si la tarea gener\xF3 3 \xE1ngulos y el usuario solo quiere los #0 y #2, pasa [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario SIEMPRE en d\xF3lares, ej. 25 \u2014 ProductMaker lo convierte a la moneda de la cuenta; si el usuario te dice '60.000 COP al d\xEDa' pasa el equivalente en USD, NUNCA el n\xFAmero en moneda local), `targetCountries` (c\xF3digos ISO-2, m\xE1x 1 para Meta), `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. `phoneNumber` (opcional): si el usuario quiere que el bot\xF3n del anuncio abra un chat de WhatsApp en vez de una landing, pasa el n\xFAmero (con c\xF3digo de pa\xEDs, ej. +573001234567) \u2014 no hace falta `landingUrl` en ese caso. Despu\xE9s de publicar usa `get_meta_campaign_status` para chequear el estado.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
10982
+ server.tool("publish_to_meta", "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`.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
10333
10983
  try {
10334
10984
  const body = {
10335
10985
  ...rest
@@ -10357,7 +11007,7 @@ function registerPublishTools(server, api, opts = {}) {
10357
11007
  throw translateError(e);
10358
11008
  }
10359
11009
  });
10360
- server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads en estado PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + " creativos (de tareas distintas si hace falta) en el mismo flujo, repartidos en hasta " + CAMPAIGN_LIMITS.MAX_AD_SETS + ' ad sets. Cada creativo puede venir de una tarea (source.type="task") \u2014 usa angleIndex, videoVariant (with_subtitles/without_subtitles) o imageVariantIndex \u2014 o de media (source.type="media") con URL p\xFAblica o ruta local (stdio). Para videos media, thumbnailUrl/thumbnailPath son OPCIONALES en ambos casos: ProductMaker sube el archivo y genera la miniatura con FFmpeg server-side, tanto si diste una URL como una ruta local. La herramienta valida extensi\xF3n, MIME, magic bytes y tama\xF1o antes de crear la campa\xF1a. `adAccountId` acepta tanto "act_1234567890" como el id num\xE9rico pelado "1234567890". `campaignConfig.destination` acepta exactamente dos valores: {type:"landing", landingUrl} o {type:"whatsapp", phoneNumber} \u2014 NO existe "website". campaignConfig permite definir el ad set y budgetPolicy: exact respeta el presupuesto solicitado y falla expl\xEDcitamente si Meta exige un m\xEDnimo; recommended_floor aplica el piso recomendado. La campa\xF1a queda PAUSED; reporta campaignId, adSetId, IDs de ads, requestedBudget, appliedBudget y el enlace de Ads Manager. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
11010
+ server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo para reintento y el TTL los limpia. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
10361
11011
  try {
10362
11012
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
10363
11013
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -10383,25 +11033,44 @@ function registerPublishTools(server, api, opts = {}) {
10383
11033
  });
10384
11034
  continue;
10385
11035
  }
10386
- const mediaUrl = await resolveAndUploadMedia(api, {
11036
+ if (creative.source.type === "staged") {
11037
+ creatives.push({
11038
+ type: creative.type,
11039
+ source: {
11040
+ type: "media",
11041
+ mediaAssetId: creative.source.stagingAssetId,
11042
+ ...creative.source.thumbnailAssetId ? {
11043
+ thumbnailAssetId: creative.source.thumbnailAssetId
11044
+ } : {}
11045
+ },
11046
+ title: creative.title,
11047
+ description: creative.description,
11048
+ cta: creative.cta,
11049
+ creativeId
11050
+ });
11051
+ continue;
11052
+ }
11053
+ const stagedMedia = await resolveAndUploadMedia(api, {
10387
11054
  url: creative.source.url,
10388
11055
  path: creative.source.path
10389
- }, creative.type, transport);
10390
- let thumbnailUrl;
11056
+ }, creative.type, "media", transport);
11057
+ let stagedThumbnail;
10391
11058
  if (creative.type === "video") {
10392
11059
  if (creative.source.thumbnailUrl || creative.source.thumbnailPath) {
10393
- thumbnailUrl = await resolveAndUploadMedia(api, {
11060
+ stagedThumbnail = await resolveAndUploadMedia(api, {
10394
11061
  url: creative.source.thumbnailUrl,
10395
11062
  path: creative.source.thumbnailPath
10396
- }, "image", transport);
11063
+ }, "image", "thumbnail", transport);
10397
11064
  }
10398
11065
  }
10399
11066
  creatives.push({
10400
11067
  type: creative.type,
10401
11068
  source: {
10402
11069
  type: "media",
10403
- mediaUrl,
10404
- thumbnailUrl
11070
+ mediaAssetId: stagedMedia.stagingAssetId,
11071
+ ...stagedThumbnail ? {
11072
+ thumbnailAssetId: stagedThumbnail.stagingAssetId
11073
+ } : {}
10405
11074
  },
10406
11075
  title: creative.title,
10407
11076
  description: creative.description,
@@ -10411,6 +11080,8 @@ function registerPublishTools(server, api, opts = {}) {
10411
11080
  }
10412
11081
  const r = await api.postJson("/v1/facebook/campaigns/from-tasks", {
10413
11082
  creatives,
11083
+ portfolioId: input.portfolioId,
11084
+ pageId: input.pageId,
10414
11085
  adAccountId: input.adAccountId,
10415
11086
  pixelId: input.pixelId,
10416
11087
  landingUrl: input.landingUrl,
@@ -10500,14 +11171,146 @@ function toTikTokCampaignConfig(cfg) {
10500
11171
  }
10501
11172
  __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
10502
11173
 
11174
+ // src/tools/meta-upload.ts
11175
+ import { z as z52 } from "zod";
11176
+ var MetaUploadSessionInputBase = z52.object({
11177
+ mediaKind: z52.enum([
11178
+ "image",
11179
+ "video",
11180
+ "mixed"
11181
+ ]).default("mixed").describe("The kind of file the user will select in the upload page. Use mixed for a normal batch of image and video creatives."),
11182
+ assetRole: z52.enum([
11183
+ "media",
11184
+ "thumbnail"
11185
+ ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail.")
11186
+ });
11187
+ var OpenAiFileReference = z52.object({
11188
+ download_url: z52.string().url(),
11189
+ file_id: z52.string().min(1),
11190
+ mime_type: z52.string().min(1).optional(),
11191
+ file_name: z52.string().min(1).optional()
11192
+ }).strict();
11193
+ var MetaUploadSessionToolInputShape = {
11194
+ ...MetaUploadSessionInputBase.shape,
11195
+ files: z52.array(OpenAiFileReference).max(10).optional().describe("Optional OpenAI-hosted file references provided by ChatGPT. They are only metadata for the UI bridge and are never forwarded to ProductMaker.")
11196
+ };
11197
+ var MetaUploadSessionToolInput = z52.object(MetaUploadSessionToolInputShape).superRefine((value, ctx) => {
11198
+ if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
11199
+ ctx.addIssue({
11200
+ code: z52.ZodIssueCode.custom,
11201
+ path: [
11202
+ "mediaKind"
11203
+ ],
11204
+ message: "Meta thumbnails must be images"
11205
+ });
11206
+ }
11207
+ });
11208
+ var MetaUploadSessionResponse2 = z52.object({
11209
+ sessionId: z52.string().uuid(),
11210
+ uploadPageUrl: z52.string().url(),
11211
+ mediaKind: z52.enum([
11212
+ "image",
11213
+ "video",
11214
+ "mixed"
11215
+ ]),
11216
+ assetRole: z52.enum([
11217
+ "media",
11218
+ "thumbnail"
11219
+ ]),
11220
+ maxFiles: z52.number().int().positive(),
11221
+ expiresAt: z52.string().datetime()
11222
+ });
11223
+ var MetaUploadStatusInput = z52.object({
11224
+ sessionId: z52.string().uuid().describe("The sessionId returned by request_meta_media_upload.")
11225
+ });
11226
+ var MetaUploadStatusResponse = z52.object({
11227
+ sessionId: z52.string().uuid(),
11228
+ status: z52.enum([
11229
+ "pending",
11230
+ "uploaded"
11231
+ ]),
11232
+ assets: z52.array(z52.object({
11233
+ stagingAssetId: z52.string().uuid(),
11234
+ mediaKind: z52.enum([
11235
+ "image",
11236
+ "video"
11237
+ ])
11238
+ })).max(10).default([]),
11239
+ stagingAssetId: z52.string().uuid().optional()
11240
+ });
11241
+ function uploadSessionText(result) {
11242
+ const mediaLabel = result.mediaKind === "mixed" ? "archivos" : result.mediaKind === "video" ? "video" : "imagen";
11243
+ return {
11244
+ content: [
11245
+ {
11246
+ type: "text",
11247
+ text: [
11248
+ `### Sube tu ${mediaLabel} a ProductMaker`,
11249
+ `[Abrir carga segura](${result.uploadPageUrl})`,
11250
+ `Puedes seleccionar hasta ${result.maxFiles} ${result.mediaKind === "mixed" ? "archivos" : "archivo"}. Cuando termine, escribe **\u201Clisto\u201D** en este chat para que ProductMaker contin\xFAe con Meta.`,
11251
+ `sessionId: \`${result.sessionId}\``,
11252
+ `Este enlace expira: ${result.expiresAt}`
11253
+ ].join("\n\n")
11254
+ }
11255
+ ]
11256
+ };
11257
+ }
11258
+ __name(uploadSessionText, "uploadSessionText");
11259
+ function registerMetaUploadTools(server, api) {
11260
+ server.registerTool("request_meta_media_upload", {
11261
+ title: "Solicitar carga segura de Meta",
11262
+ description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. En ChatGPT puede recibir referencias opcionales de `files`, pero NO las descarga ni las reenv\xEDa: siempre devuelve un enlace de ProductMaker donde el usuario selecciona los archivos. No pidas URL CDN, ruta local, bytes ni base64. Despu\xE9s conserva el `sessionId` y llama `check_meta_media_upload`. Esta herramienta no publica ni consume cr\xE9ditos.",
11263
+ inputSchema: MetaUploadSessionToolInputShape,
11264
+ outputSchema: MetaUploadSessionResponse2,
11265
+ _meta: {
11266
+ ui: {
11267
+ resourceUri: META_UPLOAD_APP_RESOURCE_URI,
11268
+ visibility: [
11269
+ "model",
11270
+ "app"
11271
+ ]
11272
+ },
11273
+ "openai/outputTemplate": META_UPLOAD_APP_RESOURCE_URI,
11274
+ "openai/widgetAccessible": true,
11275
+ "openai/fileParams": [
11276
+ "files"
11277
+ ],
11278
+ "openai/toolInvocation/invoking": "Preparando carga segura\u2026",
11279
+ "openai/toolInvocation/invoked": "Carga segura lista"
11280
+ }
11281
+ }, async (rawInput) => {
11282
+ try {
11283
+ const input = MetaUploadSessionToolInput.parse(rawInput);
11284
+ const { files: _files, ...sessionInput } = input;
11285
+ const result = MetaUploadSessionResponse2.parse(await api.postJson("/v1/ads/meta-upload-sessions", sessionInput));
11286
+ return {
11287
+ structuredContent: result,
11288
+ ...uploadSessionText(result)
11289
+ };
11290
+ } catch (error) {
11291
+ throw translateError(error);
11292
+ }
11293
+ });
11294
+ server.tool("check_meta_media_upload", 'Comprueba si el usuario ya termin\xF3 de subir desde el enlace de ProductMaker. Si status=uploaded, devuelve `assets[]`; crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como el tipo del creativo, hasta diez en el batch. Si la carga fue parcial, publica \xFAnicamente los assets que `assets[]` reporta como subidos. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.', MetaUploadStatusInput.shape, async (rawInput) => {
11295
+ try {
11296
+ const input = MetaUploadStatusInput.parse(rawInput);
11297
+ const result = MetaUploadStatusResponse.parse(await api.getJson(`/v1/ads/meta-upload-sessions/${encodeURIComponent(input.sessionId)}`));
11298
+ return asText(result);
11299
+ } catch (error) {
11300
+ throw translateError(error);
11301
+ }
11302
+ });
11303
+ }
11304
+ __name(registerMetaUploadTools, "registerMetaUploadTools");
11305
+
10503
11306
  // src/tools/campaigns.ts
10504
- import { z as z49 } from "zod";
10505
- var MetaStatusInput = z49.object({
10506
- campaignId: z49.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
11307
+ import { z as z53 } from "zod";
11308
+ var MetaStatusInput = z53.object({
11309
+ campaignId: z53.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
10507
11310
  });
10508
- var TikTokStatusInput = z49.object({
10509
- campaignId: z49.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
10510
- advertiserId: z49.string().min(1).describe("The same advertiserId used to publish the campaign.")
11311
+ var TikTokStatusInput = z53.object({
11312
+ campaignId: z53.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
11313
+ advertiserId: z53.string().min(1).describe("The same advertiserId used to publish the campaign.")
10511
11314
  });
10512
11315
  function registerCampaignTools(server, api) {
10513
11316
  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, async (input) => {
@@ -10604,10 +11407,10 @@ function renderTikTokStatusMarkdown(r) {
10604
11407
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
10605
11408
 
10606
11409
  // ../../packages/product-economics/src/product-costing.ts
10607
- import { z as z51 } from "zod";
11410
+ import { z as z55 } from "zod";
10608
11411
 
10609
11412
  // ../../packages/product-economics/src/product-costing-countries.ts
10610
- import { z as z50 } from "zod";
11413
+ import { z as z54 } from "zod";
10611
11414
 
10612
11415
  // ../../packages/product-economics/src/product-costing-shipping-references.json
10613
11416
  var product_costing_shipping_references_default = {
@@ -10671,21 +11474,21 @@ var product_costing_shipping_references_default = {
10671
11474
  };
10672
11475
 
10673
11476
  // ../../packages/product-economics/src/product-costing-countries.ts
10674
- var ShippingReferenceSchema = z50.object({
10675
- reference: z50.number().positive(),
10676
- min: z50.number().nonnegative(),
10677
- max: z50.number().positive(),
10678
- updatedAt: z50.string().date(),
10679
- confidence: z50.enum([
11477
+ var ShippingReferenceSchema = z54.object({
11478
+ reference: z54.number().positive(),
11479
+ min: z54.number().nonnegative(),
11480
+ max: z54.number().positive(),
11481
+ updatedAt: z54.string().date(),
11482
+ confidence: z54.enum([
10680
11483
  "high",
10681
11484
  "medium",
10682
11485
  "medium-low"
10683
11486
  ]),
10684
- sources: z50.array(z50.string().url().startsWith("https://")).min(1).max(3)
11487
+ sources: z54.array(z54.string().url().startsWith("https://")).min(1).max(3)
10685
11488
  }).superRefine(({ reference, min, max }, context) => {
10686
11489
  if (min > reference || reference > max) {
10687
11490
  context.addIssue({
10688
- code: z50.ZodIssueCode.custom,
11491
+ code: z54.ZodIssueCode.custom,
10689
11492
  message: "shipping_reference_outside_range",
10690
11493
  path: [
10691
11494
  "reference"
@@ -10693,7 +11496,7 @@ var ShippingReferenceSchema = z50.object({
10693
11496
  });
10694
11497
  }
10695
11498
  });
10696
- var SHIPPING_REFERENCES = z50.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
11499
+ var SHIPPING_REFERENCES = z54.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
10697
11500
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
10698
11501
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
10699
11502
  "MX",
@@ -10875,18 +11678,18 @@ function getProductCostingCountryName(country) {
10875
11678
  __name(getProductCostingCountryName, "getProductCostingCountryName");
10876
11679
 
10877
11680
  // ../../packages/product-economics/src/product-costing.ts
10878
- var ProductCostingInputSchema = z51.object({
10879
- country: z51.string().refine(isProductCostingCountryCode, "unsupported_country"),
10880
- supplierCost: z51.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
10881
- shippingCost: z51.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
10882
- returnRate: z51.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
10883
- cancellationRate: z51.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
10884
- adsManagerCpa: z51.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
10885
- desiredMargin: z51.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
11681
+ var ProductCostingInputSchema = z55.object({
11682
+ country: z55.string().refine(isProductCostingCountryCode, "unsupported_country"),
11683
+ supplierCost: z55.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
11684
+ shippingCost: z55.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
11685
+ returnRate: z55.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11686
+ cancellationRate: z55.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11687
+ adsManagerCpa: z55.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
11688
+ desiredMargin: z55.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
10886
11689
  }).superRefine(({ returnRate, cancellationRate }, context) => {
10887
11690
  if (returnRate + cancellationRate >= 1) {
10888
11691
  context.addIssue({
10889
- code: z51.ZodIssueCode.custom,
11692
+ code: z55.ZodIssueCode.custom,
10890
11693
  message: "delivery_rate_positive",
10891
11694
  path: [
10892
11695
  "cancellationRate"
@@ -10953,14 +11756,14 @@ function calculateProductCosting(rawInput) {
10953
11756
  __name(calculateProductCosting, "calculateProductCosting");
10954
11757
 
10955
11758
  // src/tools/product-costing.ts
10956
- import { z as z52 } from "zod";
10957
- var optionalAmount = /* @__PURE__ */ __name((description) => z52.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
10958
- var optionalPercent = /* @__PURE__ */ __name((description) => z52.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
10959
- var ProductCostingToolInput = z52.object({
10960
- country: z52.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
11759
+ import { z as z56 } from "zod";
11760
+ var optionalAmount = /* @__PURE__ */ __name((description) => z56.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
11761
+ var optionalPercent = /* @__PURE__ */ __name((description) => z56.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
11762
+ var ProductCostingToolInput = z56.object({
11763
+ country: z56.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
10961
11764
  message: "Pa\xEDs no soportado por ProductMaker."
10962
11765
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
10963
- supplierCost: z52.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
11766
+ supplierCost: z56.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
10964
11767
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
10965
11768
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
10966
11769
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -11092,7 +11895,7 @@ function formatPercent(value) {
11092
11895
  __name(formatPercent, "formatPercent");
11093
11896
 
11094
11897
  // src/tools/product-research.ts
11095
- import { z as z53 } from "zod";
11898
+ import { z as z57 } from "zod";
11096
11899
 
11097
11900
  // src/tools/product-research-idempotency.ts
11098
11901
  import { createHash as createHash2 } from "crypto";
@@ -11208,17 +12011,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
11208
12011
  ]);
11209
12012
  var POLL_INTERVAL_MS = 5e3;
11210
12013
  var MAX_WAIT_SECONDS = 120;
11211
- var FindInput = z53.object({
12014
+ var FindInput = z57.object({
11212
12015
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
11213
12016
  categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
11214
- confirmed: z53.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
11215
- waitSeconds: z53.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
12017
+ confirmed: z57.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
12018
+ waitSeconds: z57.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
11216
12019
  });
11217
- var GetInput = z53.object({
11218
- runId: z53.string().uuid().describe("ID del run devuelto por find_winning_products.")
12020
+ var GetInput = z57.object({
12021
+ runId: z57.string().uuid().describe("ID del run devuelto por find_winning_products.")
11219
12022
  });
11220
- var ListInput = z53.object({
11221
- limit: z53.number().int().min(1).max(20).default(10)
12023
+ var ListInput = z57.object({
12024
+ limit: z57.number().int().min(1).max(20).default(10)
11222
12025
  });
11223
12026
  function textResult(text) {
11224
12027
  return {
@@ -11310,14 +12113,14 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
11310
12113
  __name(registerProductResearchTools, "registerProductResearchTools");
11311
12114
 
11312
12115
  // src/tools/dropshipping-projects.ts
11313
- import { z as z54 } from "zod";
11314
- var ProjectId = z54.string().uuid();
11315
- var DropshippingProjectStatus = z54.enum([
12116
+ import { z as z58 } from "zod";
12117
+ var ProjectId = z58.string().uuid();
12118
+ var DropshippingProjectStatus = z58.enum([
11316
12119
  "active",
11317
12120
  "completed",
11318
12121
  "archived"
11319
12122
  ]);
11320
- var DropshippingProjectPhase = z54.enum([
12123
+ var DropshippingProjectPhase = z58.enum([
11321
12124
  "research",
11322
12125
  "costing",
11323
12126
  "media",
@@ -11326,21 +12129,21 @@ var DropshippingProjectPhase = z54.enum([
11326
12129
  "advertising",
11327
12130
  "complete"
11328
12131
  ]);
11329
- var DropshippingRewardMilestone = z54.enum([
12132
+ var DropshippingRewardMilestone = z58.enum([
11330
12133
  "product_selected",
11331
12134
  "costing_saved",
11332
12135
  "product_media_ready",
11333
12136
  "creatives_completed",
11334
12137
  "campaign_created"
11335
12138
  ]);
11336
- var TargetCountry = z54.enum([
12139
+ var TargetCountry = z58.enum([
11337
12140
  "CO",
11338
12141
  "MX",
11339
12142
  "PE",
11340
12143
  "EC",
11341
12144
  "CL"
11342
12145
  ]);
11343
- var ResearchCategory = z54.enum([
12146
+ var ResearchCategory = z58.enum([
11344
12147
  "auto",
11345
12148
  "automotive",
11346
12149
  "kitchen",
@@ -11355,7 +12158,7 @@ var ResearchCategory = z54.enum([
11355
12158
  "garden",
11356
12159
  "cleaning"
11357
12160
  ]);
11358
- var HttpUrl3 = z54.string().url().max(2e3).refine((value) => {
12161
+ var HttpUrl3 = z58.string().url().max(2e3).refine((value) => {
11359
12162
  try {
11360
12163
  return [
11361
12164
  "http:",
@@ -11365,46 +12168,46 @@ var HttpUrl3 = z54.string().url().max(2e3).refine((value) => {
11365
12168
  return false;
11366
12169
  }
11367
12170
  }, "URL must use HTTP(S)");
11368
- var ResearchCandidateOrigin = z54.object({
11369
- type: z54.literal("research_candidate"),
12171
+ var ResearchCandidateOrigin = z58.object({
12172
+ type: z58.literal("research_candidate"),
11370
12173
  runId: ProjectId,
11371
- candidateSourceId: z54.string().min(1).max(160)
12174
+ candidateSourceId: z58.string().min(1).max(160)
11372
12175
  });
11373
- var TaskOrigin = z54.object({
11374
- type: z54.literal("task"),
12176
+ var TaskOrigin = z58.object({
12177
+ type: z58.literal("task"),
11375
12178
  taskId: ProjectId
11376
12179
  });
11377
- var ManualOrigin = z54.object({
11378
- type: z54.literal("manual"),
12180
+ var ManualOrigin = z58.object({
12181
+ type: z58.literal("manual"),
11379
12182
  clientRequestId: ProjectId,
11380
- country: z54.string().min(2).max(3).optional()
12183
+ country: z58.string().min(2).max(3).optional()
11381
12184
  });
11382
- var StartProjectInput = z54.object({
11383
- origin: z54.discriminatedUnion("type", [
12185
+ var StartProjectInput = z58.object({
12186
+ origin: z58.discriminatedUnion("type", [
11384
12187
  ResearchCandidateOrigin,
11385
12188
  TaskOrigin,
11386
12189
  ManualOrigin
11387
12190
  ]),
11388
- productName: z54.string().trim().min(1).max(240).optional(),
12191
+ productName: z58.string().trim().min(1).max(240).optional(),
11389
12192
  productUrl: HttpUrl3.optional(),
11390
- country: z54.string().min(2).max(3).optional(),
11391
- languageCode: z54.string().min(2).max(16).optional()
12193
+ country: z58.string().min(2).max(3).optional(),
12194
+ languageCode: z58.string().min(2).max(16).optional()
11392
12195
  }).strict();
11393
- var CostProjectInput = z54.object({
12196
+ var CostProjectInput = z58.object({
11394
12197
  projectId: ProjectId,
11395
- version: z54.number().int().positive(),
11396
- country: z54.string().trim().min(2).max(3),
11397
- supplierCost: z54.number().finite().positive(),
11398
- shippingCost: z54.number().finite().nonnegative(),
11399
- returnRate: z54.number().finite().min(0).lt(1),
11400
- cancellationRate: z54.number().finite().min(0).lt(1),
11401
- adsManagerCpa: z54.number().finite().nonnegative(),
11402
- desiredMargin: z54.number().finite().min(0).lt(1)
11403
- });
11404
- var GuideInput = z54.object({
12198
+ version: z58.number().int().positive(),
12199
+ country: z58.string().trim().min(2).max(3),
12200
+ supplierCost: z58.number().finite().positive(),
12201
+ shippingCost: z58.number().finite().nonnegative(),
12202
+ returnRate: z58.number().finite().min(0).lt(1),
12203
+ cancellationRate: z58.number().finite().min(0).lt(1),
12204
+ adsManagerCpa: z58.number().finite().nonnegative(),
12205
+ desiredMargin: z58.number().finite().min(0).lt(1)
12206
+ });
12207
+ var GuideInput = z58.object({
11405
12208
  projectId: ProjectId.optional()
11406
12209
  }).strict();
11407
- var DropshippingProjectNextAction = z54.discriminatedUnion("kind", [
12210
+ var DropshippingProjectNextAction = z58.discriminatedUnion("kind", [
11408
12211
  "cost_product",
11409
12212
  "provide_product_media",
11410
12213
  "generate_creatives",
@@ -11418,78 +12221,78 @@ var DropshippingProjectNextAction = z54.discriminatedUnion("kind", [
11418
12221
  "create_meta_campaign",
11419
12222
  "repair_meta_campaign",
11420
12223
  "review_paused_campaign"
11421
- ].map((kind) => z54.object({
11422
- kind: z54.literal(kind),
11423
- href: z54.string().min(1)
12224
+ ].map((kind) => z58.object({
12225
+ kind: z58.literal(kind),
12226
+ href: z58.string().min(1)
11424
12227
  })));
11425
- var CostingInputSchema = z54.object({
11426
- country: z54.string().min(2).max(3),
11427
- supplierCost: z54.number().finite().positive(),
11428
- shippingCost: z54.number().finite().nonnegative(),
11429
- returnRate: z54.number().finite().min(0).lt(1),
11430
- cancellationRate: z54.number().finite().min(0).lt(1),
11431
- adsManagerCpa: z54.number().finite().nonnegative(),
11432
- desiredMargin: z54.number().finite().min(0).lt(1)
12228
+ var CostingInputSchema = z58.object({
12229
+ country: z58.string().min(2).max(3),
12230
+ supplierCost: z58.number().finite().positive(),
12231
+ shippingCost: z58.number().finite().nonnegative(),
12232
+ returnRate: z58.number().finite().min(0).lt(1),
12233
+ cancellationRate: z58.number().finite().min(0).lt(1),
12234
+ adsManagerCpa: z58.number().finite().nonnegative(),
12235
+ desiredMargin: z58.number().finite().min(0).lt(1)
11433
12236
  }).superRefine(({ returnRate, cancellationRate }, context) => {
11434
12237
  if (returnRate + cancellationRate >= 1) context.addIssue({
11435
- code: z54.ZodIssueCode.custom,
12238
+ code: z58.ZodIssueCode.custom,
11436
12239
  path: [
11437
12240
  "cancellationRate"
11438
12241
  ]
11439
12242
  });
11440
12243
  });
11441
- var CostingResultSchema = z54.object({
11442
- country: z54.string().min(2).max(3),
11443
- deliveryRate: z54.number().finite(),
11444
- adjustedShippingCost: z54.number().finite(),
11445
- adjustedCpa: z54.number().finite(),
11446
- totalCost: z54.number().finite(),
11447
- suggestedPrice: z54.number().finite(),
11448
- baseCommercialPrice: z54.number().finite(),
11449
- minimumSafePrice: z54.number().finite(),
11450
- commercialPrice: z54.number().finite(),
11451
- comparisonPrice: z54.number().finite(),
11452
- estimatedProfit: z54.number().finite(),
11453
- actualMargin: z54.number().finite(),
11454
- breakEvenAdsManagerCpa: z54.number().finite(),
11455
- wasPriceAutoAdjusted: z54.boolean(),
11456
- profitabilityStatus: z54.literal("healthy")
11457
- });
11458
- var RewardProgressSchema = z54.object({
11459
- campaignKey: z54.literal("dropshipping-launch-v1"),
11460
- totalCredits: z54.number().int().positive(),
11461
- earnedCredits: z54.number().int().nonnegative(),
11462
- milestones: z54.array(z54.object({
12244
+ var CostingResultSchema = z58.object({
12245
+ country: z58.string().min(2).max(3),
12246
+ deliveryRate: z58.number().finite(),
12247
+ adjustedShippingCost: z58.number().finite(),
12248
+ adjustedCpa: z58.number().finite(),
12249
+ totalCost: z58.number().finite(),
12250
+ suggestedPrice: z58.number().finite(),
12251
+ baseCommercialPrice: z58.number().finite(),
12252
+ minimumSafePrice: z58.number().finite(),
12253
+ commercialPrice: z58.number().finite(),
12254
+ comparisonPrice: z58.number().finite(),
12255
+ estimatedProfit: z58.number().finite(),
12256
+ actualMargin: z58.number().finite(),
12257
+ breakEvenAdsManagerCpa: z58.number().finite(),
12258
+ wasPriceAutoAdjusted: z58.boolean(),
12259
+ profitabilityStatus: z58.literal("healthy")
12260
+ });
12261
+ var RewardProgressSchema = z58.object({
12262
+ campaignKey: z58.literal("dropshipping-launch-v1"),
12263
+ totalCredits: z58.number().int().positive(),
12264
+ earnedCredits: z58.number().int().nonnegative(),
12265
+ milestones: z58.array(z58.object({
11463
12266
  milestone: DropshippingRewardMilestone,
11464
- credits: z54.number().int().positive(),
11465
- available: z54.boolean().optional(),
11466
- earned: z54.boolean(),
12267
+ credits: z58.number().int().positive(),
12268
+ available: z58.boolean().optional(),
12269
+ earned: z58.boolean(),
11467
12270
  grantId: ProjectId.nullable(),
11468
- grantedAt: z54.string().datetime().nullable()
12271
+ grantedAt: z58.string().datetime().nullable()
11469
12272
  })).length(DropshippingRewardMilestone.options.length)
11470
12273
  });
11471
- var ProjectResponseSchema = z54.object({
12274
+ var ProjectResponseSchema = z58.object({
11472
12275
  id: ProjectId,
11473
12276
  taskId: ProjectId.nullable(),
11474
12277
  status: DropshippingProjectStatus,
11475
- originKey: z54.string().min(1),
12278
+ originKey: z58.string().min(1),
11476
12279
  researchRunId: ProjectId.nullable(),
11477
- candidateSourceId: z54.string().nullable(),
11478
- productName: z54.string().nullable(),
12280
+ candidateSourceId: z58.string().nullable(),
12281
+ productName: z58.string().nullable(),
11479
12282
  productUrl: HttpUrl3.nullable(),
11480
12283
  referenceImageUrl: HttpUrl3.nullable(),
11481
- targetCountry: z54.string().nullable(),
11482
- languageCode: z54.string().nullable(),
11483
- hasReferenceImage: z54.boolean(),
12284
+ targetCountry: z58.string().nullable(),
12285
+ languageCode: z58.string().nullable(),
12286
+ hasReferenceImage: z58.boolean(),
11484
12287
  costingInput: CostingInputSchema.nullable(),
11485
12288
  costingResult: CostingResultSchema.nullable(),
11486
- costingVersion: z54.number().int().positive().nullable(),
11487
- version: z54.number().int().positive(),
12289
+ costingVersion: z58.number().int().positive().nullable(),
12290
+ version: z58.number().int().positive(),
11488
12291
  phase: DropshippingProjectPhase,
11489
12292
  nextAction: DropshippingProjectNextAction.nullable(),
11490
12293
  rewards: RewardProgressSchema,
11491
- createdAt: z54.string().datetime(),
11492
- updatedAt: z54.string().datetime()
12294
+ createdAt: z58.string().datetime(),
12295
+ updatedAt: z58.string().datetime()
11493
12296
  }).passthrough();
11494
12297
  function projectText(raw) {
11495
12298
  const project = ProjectResponseSchema.parse(raw);
@@ -11574,6 +12377,7 @@ function createServer(opts) {
11574
12377
  }
11575
12378
  });
11576
12379
  const api = new ApiClient(apiBaseUrl, apiKey);
12380
+ registerMetaUploadAppResource(server);
11577
12381
  registerProductCostingTools(server);
11578
12382
  registerProductResearchTools(server, api);
11579
12383
  registerDropshippingProjectTools(server, api);
@@ -11587,9 +12391,11 @@ function createServer(opts) {
11587
12391
  registerCreativeVariantTools(server, api);
11588
12392
  registerScriptReviewTools(server, api);
11589
12393
  registerConnectionTools(server, api);
12394
+ registerActorTools(server, api);
11590
12395
  registerPublishTools(server, api, {
11591
12396
  transport
11592
12397
  });
12398
+ registerMetaUploadTools(server, api);
11593
12399
  registerCampaignTools(server, api);
11594
12400
  return server;
11595
12401
  }