@productmaker/mcp 2.4.0 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/http.js CHANGED
@@ -153,10 +153,60 @@ var ApiClient = class {
153
153
  };
154
154
 
155
155
  // src/index.ts
156
- var MCP_VERSION = "2.4.0";
156
+ var MCP_VERSION = "2.9.1";
157
+
158
+ // src/meta-upload-app.ts
159
+ import { readFileSync } from "fs";
160
+ import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
161
+ var META_UPLOAD_APP_RESOURCE_URI = "ui://meta-upload/v1.html";
162
+ var META_UPLOAD_APP_NAME = "Meta upload app";
163
+ var META_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
164
+ var META_UPLOAD_APP_CSP = {
165
+ connectDomains: [
166
+ "https://productmaker.app",
167
+ "https://www.productmaker.app",
168
+ "https://*.r2.cloudflarestorage.com",
169
+ "https://*.oaiusercontent.com"
170
+ ]
171
+ };
172
+ function readMetaUploadAppHtml(htmlUrl = META_UPLOAD_APP_HTML_URL) {
173
+ try {
174
+ return readFileSync(htmlUrl, "utf8");
175
+ } catch (error) {
176
+ const message = error instanceof Error ? error.message : String(error);
177
+ 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}`);
178
+ }
179
+ }
180
+ __name(readMetaUploadAppHtml, "readMetaUploadAppHtml");
181
+ function registerMetaUploadAppResource(server) {
182
+ const html = readMetaUploadAppHtml();
183
+ registerAppResource(server, META_UPLOAD_APP_NAME, META_UPLOAD_APP_RESOURCE_URI, {
184
+ title: "Meta upload app",
185
+ mimeType: RESOURCE_MIME_TYPE,
186
+ _meta: {
187
+ ui: {
188
+ csp: META_UPLOAD_APP_CSP
189
+ }
190
+ }
191
+ }, async () => ({
192
+ contents: [
193
+ {
194
+ uri: META_UPLOAD_APP_RESOURCE_URI,
195
+ mimeType: RESOURCE_MIME_TYPE,
196
+ text: html,
197
+ _meta: {
198
+ ui: {
199
+ csp: META_UPLOAD_APP_CSP
200
+ }
201
+ }
202
+ }
203
+ ]
204
+ }));
205
+ }
206
+ __name(registerMetaUploadAppResource, "registerMetaUploadAppResource");
157
207
 
158
208
  // src/tools/tasks.ts
159
- import { z as z44 } from "zod";
209
+ import { z as z46 } from "zod";
160
210
 
161
211
  // src/resolve-image.ts
162
212
  import { lookup as dnsLookup } from "dns/promises";
@@ -2256,6 +2306,25 @@ var ShopifyPublicationStatus = z19.enum([
2256
2306
  "success",
2257
2307
  "error"
2258
2308
  ]);
2309
+ var ShopifyTrackingSyncStatus = z19.enum([
2310
+ "synced",
2311
+ "migrated",
2312
+ "not_detected"
2313
+ ]);
2314
+ var ShopifyTrackingSource = z19.enum([
2315
+ "theme",
2316
+ "legacy_layout",
2317
+ "theme_and_legacy",
2318
+ "none"
2319
+ ]);
2320
+ var ShopifyTrackingResult = z19.object({
2321
+ status: ShopifyTrackingSyncStatus,
2322
+ manualCodeDetected: z19.boolean(),
2323
+ /** True when code from a prior ProductMaker layout was preserved automatically. */
2324
+ legacyCodeMigrated: z19.boolean().default(false),
2325
+ source: ShopifyTrackingSource.default("none"),
2326
+ syncedAt: z19.string().datetime()
2327
+ });
2259
2328
  var ShopifyPublishResponse = z19.object({
2260
2329
  publicationId: z19.string().uuid(),
2261
2330
  taskId: z19.string().uuid(),
@@ -2271,6 +2340,8 @@ var ShopifyPublishResponse = z19.object({
2271
2340
  needsThemeSetup: z19.boolean().optional(),
2272
2341
  /** One-click theme-editor deep link to add the Code Landing block (when needsThemeSetup). */
2273
2342
  themeEditorDeepLink: z19.string().url().optional(),
2343
+ /** Tracking copied from the theme or migrated from a previous ProductMaker layout. */
2344
+ tracking: ShopifyTrackingResult.optional(),
2274
2345
  createdAt: z19.string().datetime().optional(),
2275
2346
  updatedAt: z19.string().datetime().optional()
2276
2347
  });
@@ -3169,7 +3240,9 @@ var ImageCreativeEstimationOptions = z24.object({
3169
3240
  });
3170
3241
  var RegenerateLandingRoleBody = z24.object({
3171
3242
  comment: z24.string().max(500).optional(),
3172
- sourceKey: z24.string().max(200).optional()
3243
+ sourceKey: z24.string().max(200).optional(),
3244
+ /** Client-generated key lets a lost response be retried without another charge. */
3245
+ clientRequestId: z24.string().uuid().optional()
3173
3246
  }).strict();
3174
3247
  var LandingRegenerateEstimationOptions = z24.object({
3175
3248
  /** KIE image endpoint has non-linear pricing by nVariants; for landing we default to 2. */
@@ -3720,6 +3793,25 @@ var ALLOWED_MEDIA_CONTENT_TYPES = [
3720
3793
  "audio/wav",
3721
3794
  "audio/x-wav"
3722
3795
  ];
3796
+ var META_STAGING_IMAGE_CONTENT_TYPES = /* @__PURE__ */ new Set([
3797
+ "image/jpeg",
3798
+ "image/png",
3799
+ "image/webp"
3800
+ ]);
3801
+ var META_STAGING_MEDIA_CONTENT_TYPES = /* @__PURE__ */ new Set([
3802
+ ...META_STAGING_IMAGE_CONTENT_TYPES,
3803
+ "video/mp4",
3804
+ "video/quicktime",
3805
+ "video/webm"
3806
+ ]);
3807
+ var META_STAGING_CONTENT_TYPES = [
3808
+ "image/jpeg",
3809
+ "image/png",
3810
+ "image/webp",
3811
+ "video/mp4",
3812
+ "video/quicktime",
3813
+ "video/webm"
3814
+ ];
3723
3815
  var PresignUploadRequest = z31.object({
3724
3816
  /** MIME type of the file to upload */
3725
3817
  contentType: z31.enum(ALLOWED_MEDIA_CONTENT_TYPES),
@@ -3735,8 +3827,51 @@ var PresignUploadRequest = z31.object({
3735
3827
  purpose: z31.enum([
3736
3828
  "ads",
3737
3829
  "service",
3738
- "actor"
3830
+ "actor",
3831
+ "meta_staging"
3832
+ ]).optional(),
3833
+ /** Required for purpose=meta_staging so media and thumbnails can be checked independently. */
3834
+ assetRole: z31.enum([
3835
+ "media",
3836
+ "thumbnail"
3739
3837
  ]).optional()
3838
+ }).superRefine((value, ctx) => {
3839
+ if (value.purpose === "meta_staging") {
3840
+ if (!value.assetRole) {
3841
+ ctx.addIssue({
3842
+ code: z31.ZodIssueCode.custom,
3843
+ path: [
3844
+ "assetRole"
3845
+ ],
3846
+ message: "assetRole is required for Meta staging uploads"
3847
+ });
3848
+ } else if (!META_STAGING_MEDIA_CONTENT_TYPES.has(value.contentType)) {
3849
+ ctx.addIssue({
3850
+ code: z31.ZodIssueCode.custom,
3851
+ path: [
3852
+ "contentType"
3853
+ ],
3854
+ message: "Meta staging accepts only image or video media"
3855
+ });
3856
+ } else if (value.assetRole === "thumbnail" && !META_STAGING_IMAGE_CONTENT_TYPES.has(value.contentType)) {
3857
+ ctx.addIssue({
3858
+ code: z31.ZodIssueCode.custom,
3859
+ path: [
3860
+ "contentType"
3861
+ ],
3862
+ message: "Meta staging thumbnails must be images"
3863
+ });
3864
+ }
3865
+ }
3866
+ if (value.purpose !== "meta_staging" && value.assetRole) {
3867
+ ctx.addIssue({
3868
+ code: z31.ZodIssueCode.custom,
3869
+ path: [
3870
+ "assetRole"
3871
+ ],
3872
+ message: "assetRole is only valid for Meta staging uploads"
3873
+ });
3874
+ }
3740
3875
  });
3741
3876
  var PresignUploadResponse = z31.object({
3742
3877
  /** Presigned S3 PUT URL — browser sends file directly here */
@@ -3746,8 +3881,23 @@ var PresignUploadResponse = z31.object({
3746
3881
  /** S3 key */
3747
3882
  key: z31.string(),
3748
3883
  /** Present only for purpose=service. */
3749
- assetToken: z31.string().min(1).optional()
3884
+ assetToken: z31.string().min(1).optional(),
3885
+ /** Present only for purpose=meta_staging. */
3886
+ stagingAssetId: z31.string().uuid().optional()
3750
3887
  });
3888
+ var MetaUploadMediaKind = z31.enum([
3889
+ "image",
3890
+ "video",
3891
+ "mixed"
3892
+ ]);
3893
+ var META_UPLOAD_MAX_FILES = CAMPAIGN_LIMITS.MAX_CREATIVES;
3894
+ var MetaUploadAssetSummary = z31.object({
3895
+ stagingAssetId: z31.string().uuid(),
3896
+ mediaKind: z31.enum([
3897
+ "image",
3898
+ "video"
3899
+ ])
3900
+ }).strict();
3751
3901
  var MetaCampaignType = z31.enum([
3752
3902
  "sales",
3753
3903
  "traffic",
@@ -3773,6 +3923,81 @@ var HttpUrl = z31.string().url().refine((value) => {
3773
3923
  return false;
3774
3924
  }
3775
3925
  }, "URL must use http or https");
3926
+ var MetaUploadSessionRequest = z31.object({
3927
+ /** The browser page will require a file of this media kind. */
3928
+ mediaKind: MetaUploadMediaKind,
3929
+ /** The staged object role; media is the normal creative input. */
3930
+ assetRole: z31.enum([
3931
+ "media",
3932
+ "thumbnail"
3933
+ ]).default("media"),
3934
+ /** Maximum number of files; the API chooses 10 for mixed and 1 otherwise when omitted. */
3935
+ maxFiles: z31.number().int().min(1).max(META_UPLOAD_MAX_FILES).optional()
3936
+ }).strict().superRefine((value, ctx) => {
3937
+ if (value.mediaKind === "mixed" && value.assetRole === "thumbnail") {
3938
+ ctx.addIssue({
3939
+ code: z31.ZodIssueCode.custom,
3940
+ path: [
3941
+ "mediaKind"
3942
+ ],
3943
+ message: "Mixed Meta upload sessions cannot be thumbnails"
3944
+ });
3945
+ }
3946
+ if (value.mediaKind !== "mixed" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
3947
+ ctx.addIssue({
3948
+ code: z31.ZodIssueCode.custom,
3949
+ path: [
3950
+ "maxFiles"
3951
+ ],
3952
+ message: "Non-mixed Meta upload sessions accept exactly one file"
3953
+ });
3954
+ }
3955
+ if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
3956
+ ctx.addIssue({
3957
+ code: z31.ZodIssueCode.custom,
3958
+ path: [
3959
+ "mediaKind"
3960
+ ],
3961
+ message: "Meta thumbnails must be images"
3962
+ });
3963
+ }
3964
+ });
3965
+ var ResolveMetaUploadSessionRequest = z31.object({
3966
+ /** Opaque signed token embedded in the one-click upload page URL. */
3967
+ token: z31.string().min(1).max(4096),
3968
+ /** Stable browser-generated id so failed PUT retries reuse the same slot. */
3969
+ uploadId: z31.string().uuid().optional(),
3970
+ /** Browser-selected metadata; the API checks it against the session kind/role. */
3971
+ contentType: z31.enum(META_STAGING_CONTENT_TYPES),
3972
+ fileName: z31.string().min(1).max(255),
3973
+ fileSize: z31.number().int().positive().max(CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES)
3974
+ }).strict();
3975
+ var MetaUploadSessionResponse = z31.object({
3976
+ sessionId: z31.string().uuid(),
3977
+ uploadPageUrl: HttpUrl,
3978
+ mediaKind: MetaUploadMediaKind,
3979
+ assetRole: z31.enum([
3980
+ "media",
3981
+ "thumbnail"
3982
+ ]),
3983
+ maxFiles: z31.number().int().min(1).max(META_UPLOAD_MAX_FILES),
3984
+ expiresAt: z31.string().datetime()
3985
+ }).strict();
3986
+ var ResolveMetaUploadSessionResponse = z31.object({
3987
+ uploadUrl: z31.string().url(),
3988
+ stagingAssetId: z31.string().uuid(),
3989
+ expiresAt: z31.string().datetime()
3990
+ }).strict();
3991
+ var MetaUploadSessionStatusResponse = z31.object({
3992
+ sessionId: z31.string().uuid(),
3993
+ status: z31.enum([
3994
+ "pending",
3995
+ "uploaded"
3996
+ ]),
3997
+ assets: z31.array(MetaUploadAssetSummary).max(META_UPLOAD_MAX_FILES),
3998
+ /** Legacy single-file response field retained for older callers. */
3999
+ stagingAssetId: z31.string().uuid().optional()
4000
+ }).strict();
3776
4001
  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), {
3777
4002
  message: 'adAccountId must look like "act_1234567890" (a bare numeric id is also accepted)'
3778
4003
  });
@@ -3859,18 +4084,26 @@ var UploadToMetaRequest = z31.object({
3859
4084
  title: z31.string().max(CAMPAIGN_LIMITS.MAX_TITLE_LENGTH),
3860
4085
  description: z31.string().max(CAMPAIGN_LIMITS.MAX_DESCRIPTION_LENGTH),
3861
4086
  cta: MetaCtaType,
3862
- /** Public URL of the uploaded media */
3863
- mediaUrl: HttpUrl,
4087
+ /** Legacy public URL; new Meta flows should use mediaAssetId. */
4088
+ mediaUrl: HttpUrl.optional(),
4089
+ /** Owner-scoped temporary asset created by the Meta staging upload flow. */
4090
+ mediaAssetId: z31.string().uuid().optional(),
3864
4091
  /**
3865
4092
  * Public URL of an extracted thumbnail. Required for video ads — Meta
3866
4093
  * rejects video ad creatives without `image_hash`/`image_url` in
3867
4094
  * `video_data` (subcode 1443226). Frontend extracts a frame from the
3868
4095
  * video and uploads it before submitting the request.
3869
4096
  */
3870
- thumbnailUrl: HttpUrl.optional()
4097
+ thumbnailUrl: HttpUrl.optional(),
4098
+ /** Owner-scoped temporary thumbnail created by the Meta staging upload flow. */
4099
+ thumbnailAssetId: z31.string().uuid().optional()
3871
4100
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3872
4101
  /** Meta Ad Account ID */
3873
4102
  adAccountId: MetaAdAccountId,
4103
+ /** Meta Business Portfolio selected by the user (optional for legacy callers). */
4104
+ portfolioId: z31.string().min(1).optional(),
4105
+ /** Facebook Page selected by the user (optional for legacy callers). */
4106
+ pageId: z31.string().min(1).optional(),
3874
4107
  /** Optional Meta Pixel ID */
3875
4108
  pixelId: z31.string().optional(),
3876
4109
  /** Landing page URL (required for landing destinations; omitted for whatsapp) */
@@ -3890,6 +4123,30 @@ var UploadToMetaRequest = z31.object({
3890
4123
  message: "landingUrl is required for landing destinations"
3891
4124
  });
3892
4125
  }
4126
+ value.creatives.forEach((creative, index) => {
4127
+ if (Boolean(creative.mediaUrl) === Boolean(creative.mediaAssetId)) {
4128
+ ctx.addIssue({
4129
+ code: z31.ZodIssueCode.custom,
4130
+ path: [
4131
+ "creatives",
4132
+ index,
4133
+ "mediaUrl"
4134
+ ],
4135
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4136
+ });
4137
+ }
4138
+ if (creative.thumbnailUrl && creative.thumbnailAssetId) {
4139
+ ctx.addIssue({
4140
+ code: z31.ZodIssueCode.custom,
4141
+ path: [
4142
+ "creatives",
4143
+ index,
4144
+ "thumbnailUrl"
4145
+ ],
4146
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4147
+ });
4148
+ }
4149
+ });
3893
4150
  });
3894
4151
  var CampaignLifetimeMetricsSchema = z31.object({
3895
4152
  spend: z31.number().nonnegative(),
@@ -3943,8 +4200,32 @@ var FacebookAdAccountSchema = z32.object({
3943
4200
  source: z32.enum([
3944
4201
  "business",
3945
4202
  "personal"
4203
+ ]),
4204
+ /** Meta Business Portfolio (or the synthetic `personal` group). */
4205
+ portfolioId: z32.string().min(1),
4206
+ portfolioName: z32.string().min(1),
4207
+ portfolioType: z32.enum([
4208
+ "business",
4209
+ "personal"
4210
+ ])
4211
+ });
4212
+ var FacebookMetaPortfolioSchema = z32.object({
4213
+ id: z32.string().min(1),
4214
+ name: z32.string().min(1),
4215
+ type: z32.enum([
4216
+ "business",
4217
+ "personal"
3946
4218
  ])
3947
4219
  });
4220
+ var FacebookMetaPageSchema = z32.object({
4221
+ id: z32.string().min(1),
4222
+ name: z32.string().min(1)
4223
+ });
4224
+ var FacebookMetaDestinationsResponseSchema = z32.object({
4225
+ portfolios: z32.array(FacebookMetaPortfolioSchema),
4226
+ adAccounts: z32.array(FacebookAdAccountSchema),
4227
+ pages: z32.array(FacebookMetaPageSchema)
4228
+ });
3948
4229
  var MetaCampaignOverridesSchema = z32.object({
3949
4230
  dailyBudgetUsd: z32.number().positive().optional(),
3950
4231
  targetCountries: z32.array(z32.string().length(2)).max(1).optional(),
@@ -3953,6 +4234,8 @@ var MetaCampaignOverridesSchema = z32.object({
3953
4234
  });
3954
4235
  var FacebookCreateCampaignBodySchema = z32.object({
3955
4236
  adAccountId: MetaAdAccountId,
4237
+ portfolioId: z32.string().min(1).optional(),
4238
+ pageId: z32.string().min(1).optional(),
3956
4239
  pixelId: z32.string().optional(),
3957
4240
  landingUrl: HttpUrl2.optional(),
3958
4241
  campaignConfig: MetaCampaignConfig.optional(),
@@ -3983,6 +4266,8 @@ var FacebookCampaignStatusResponseSchema = z32.object({
3983
4266
  var FacebookCreateCampaignResponseSchema = z32.object({
3984
4267
  campaignId: z32.string(),
3985
4268
  campaignName: z32.string(),
4269
+ portfolioId: z32.string().optional(),
4270
+ portfolioName: z32.string().optional(),
3986
4271
  pageId: z32.string().optional(),
3987
4272
  pageName: z32.string().optional(),
3988
4273
  // Backward-compatible single ad fields (optional)
@@ -4025,11 +4310,15 @@ var FacebookCreateCampaignFromCreativesBodySchema = z32.object({
4025
4310
  title: z32.string().max(120),
4026
4311
  description: z32.string().max(500),
4027
4312
  cta: MetaCtaType,
4028
- mediaUrl: HttpUrl2,
4313
+ mediaUrl: HttpUrl2.optional(),
4314
+ mediaAssetId: z32.string().uuid().optional(),
4029
4315
  thumbnailUrl: HttpUrl2.optional(),
4316
+ thumbnailAssetId: z32.string().uuid().optional(),
4030
4317
  creativeId: z32.string().min(1).optional()
4031
4318
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
4032
4319
  adAccountId: MetaAdAccountId,
4320
+ portfolioId: z32.string().min(1).optional(),
4321
+ pageId: z32.string().min(1).optional(),
4033
4322
  pixelId: z32.string().optional(),
4034
4323
  /** Required unless campaignConfig.destination is 'whatsapp' (see superRefine below). */
4035
4324
  landingUrl: HttpUrl2.optional(),
@@ -4049,6 +4338,30 @@ var FacebookCreateCampaignFromCreativesBodySchema = z32.object({
4049
4338
  message: "landingUrl is required unless destination is whatsapp"
4050
4339
  });
4051
4340
  }
4341
+ value.creatives.forEach((creative, index) => {
4342
+ if (Boolean(creative.mediaUrl) === Boolean(creative.mediaAssetId)) {
4343
+ ctx.addIssue({
4344
+ code: z32.ZodIssueCode.custom,
4345
+ path: [
4346
+ "creatives",
4347
+ index,
4348
+ "mediaUrl"
4349
+ ],
4350
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4351
+ });
4352
+ }
4353
+ if (creative.thumbnailUrl && creative.thumbnailAssetId) {
4354
+ ctx.addIssue({
4355
+ code: z32.ZodIssueCode.custom,
4356
+ path: [
4357
+ "creatives",
4358
+ index,
4359
+ "thumbnailUrl"
4360
+ ],
4361
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4362
+ });
4363
+ }
4364
+ });
4052
4365
  if (value.campaignConfig?.destination?.type === "landing" && value.landingUrl && value.campaignConfig.destination.landingUrl !== value.landingUrl) {
4053
4366
  ctx.addIssue({
4054
4367
  code: z32.ZodIssueCode.custom,
@@ -4126,8 +4439,10 @@ var FacebookTaskCreativeSourceSchema = z32.object({
4126
4439
  });
4127
4440
  var FacebookMediaCreativeSourceSchema = z32.object({
4128
4441
  type: z32.literal("media"),
4129
- mediaUrl: HttpUrl2,
4130
- thumbnailUrl: HttpUrl2.optional()
4442
+ mediaUrl: HttpUrl2.optional(),
4443
+ mediaAssetId: z32.string().uuid().optional(),
4444
+ thumbnailUrl: HttpUrl2.optional(),
4445
+ thumbnailAssetId: z32.string().uuid().optional()
4131
4446
  });
4132
4447
  var FacebookCreativeSourceSchema = z32.discriminatedUnion("type", [
4133
4448
  FacebookTaskCreativeSourceSchema,
@@ -4146,6 +4461,9 @@ var FacebookCreateCampaignFromTasksBodySchema = z32.object({
4146
4461
  creativeId: z32.string().min(1).optional()
4147
4462
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
4148
4463
  adAccountId: MetaAdAccountId,
4464
+ /** Explicit destination is required for MCP/remote creative publishing. */
4465
+ portfolioId: z32.string().min(1),
4466
+ pageId: z32.string().min(1),
4149
4467
  pixelId: z32.string().optional(),
4150
4468
  landingUrl: HttpUrl2.optional(),
4151
4469
  targetCountry: z32.string().length(2).default("US"),
@@ -4162,6 +4480,33 @@ var FacebookCreateCampaignFromTasksBodySchema = z32.object({
4162
4480
  message: "landingUrl is required unless destination is whatsapp"
4163
4481
  });
4164
4482
  }
4483
+ value.creatives.forEach((creative, index) => {
4484
+ if (creative.source.type !== "media") return;
4485
+ if (Boolean(creative.source.mediaUrl) === Boolean(creative.source.mediaAssetId)) {
4486
+ ctx.addIssue({
4487
+ code: z32.ZodIssueCode.custom,
4488
+ path: [
4489
+ "creatives",
4490
+ index,
4491
+ "source",
4492
+ "mediaUrl"
4493
+ ],
4494
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4495
+ });
4496
+ }
4497
+ if (creative.source.thumbnailUrl && creative.source.thumbnailAssetId) {
4498
+ ctx.addIssue({
4499
+ code: z32.ZodIssueCode.custom,
4500
+ path: [
4501
+ "creatives",
4502
+ index,
4503
+ "source",
4504
+ "thumbnailUrl"
4505
+ ],
4506
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4507
+ });
4508
+ }
4509
+ });
4165
4510
  });
4166
4511
 
4167
4512
  // ../../packages/types/src/tiktok.ts
@@ -5843,8 +6188,7 @@ var DEFAULT_PRICING = {
5843
6188
  userPriceCents: 0.04
5844
6189
  }
5845
6190
  };
5846
- var REGEN_I2V_CLIP_OUR_COST_CENTS = DEFAULT_PRICING["kie:video-generation:grok15-persec-480p"].ourCostCents * 3;
5847
- var REGEN_SINGLE_CALL_OUR_COST_CENTS = DEFAULT_PRICING["kie:video-generation:grok-15s-480p"].ourCostCents;
6191
+ var REGEN_SINGLE_CALL_OUR_COST_CENTS = DEFAULT_PRICING["kie:video-generation:grok-15s-720p"].ourCostCents;
5848
6192
  var SC_FRAME_COMPOSE_OUR_COST_CENTS = DEFAULT_PRICING["kie:image-generation:gpt-image-2:medium"].ourCostCents;
5849
6193
  var SC_FRAME_COMPOSE_USER_PRICE_CENTS = DEFAULT_PRICING["kie:image-generation:gpt-image-2:medium"].userPriceCents;
5850
6194
  var MS_FRAME_COMPOSE_OUR_COST_CENTS = DEFAULT_PRICING["kie:image-generation:nano-banana-2"].ourCostCents;
@@ -7121,10 +7465,35 @@ var ListLandingEditsResponse = z37.object({
7121
7465
  edits: z37.array(LandingEditDto)
7122
7466
  });
7123
7467
 
7124
- // ../../packages/types/src/video-edit.ts
7468
+ // ../../packages/types/src/landing-regeneration.ts
7125
7469
  import { z as z38 } from "zod";
7470
+ var LandingRegenerationStatus = z38.enum([
7471
+ "queued",
7472
+ "processing",
7473
+ "published",
7474
+ "failed"
7475
+ ]);
7476
+ var RegenerateLandingRoleResponse = z38.object({
7477
+ ok: z38.literal(true),
7478
+ role: LandingRole,
7479
+ promptIndex: z38.number().int().nonnegative(),
7480
+ operationId: z38.string().min(1)
7481
+ });
7482
+ var LandingRegenerationStatusResponse = z38.object({
7483
+ ok: z38.literal(true),
7484
+ operationId: z38.string().min(1),
7485
+ role: LandingRole,
7486
+ status: LandingRegenerationStatus,
7487
+ refunded: z38.boolean(),
7488
+ failureReason: z38.string().nullable(),
7489
+ publishedAssetKey: z38.string().nullable(),
7490
+ updatedAt: z38.string().datetime()
7491
+ });
7492
+
7493
+ // ../../packages/types/src/video-edit.ts
7494
+ import { z as z39 } from "zod";
7126
7495
  var MAX_SEGMENT_SECONDS = 3;
7127
- var VideoEditStatusSchema = z38.enum([
7496
+ var VideoEditStatusSchema = z39.enum([
7128
7497
  "pending",
7129
7498
  "processing",
7130
7499
  "assembling",
@@ -7133,9 +7502,9 @@ var VideoEditStatusSchema = z38.enum([
7133
7502
  "discarded",
7134
7503
  "failed"
7135
7504
  ]);
7136
- var VideoEditTimeSectionSchema = z38.object({
7137
- startSec: z38.number().min(0),
7138
- endSec: z38.number().positive()
7505
+ var VideoEditTimeSectionSchema = z39.object({
7506
+ startSec: z39.number().min(0),
7507
+ endSec: z39.number().positive()
7139
7508
  }).refine((v) => v.endSec > v.startSec, {
7140
7509
  message: "endSec must be after startSec",
7141
7510
  path: [
@@ -7147,35 +7516,35 @@ var VideoEditTimeSectionSchema = z38.object({
7147
7516
  "endSec"
7148
7517
  ]
7149
7518
  });
7150
- var VideoEditRequestSchema = z38.object({
7151
- comment: z38.string().trim().min(1).max(2e3),
7152
- sections: z38.array(VideoEditTimeSectionSchema).min(1).optional()
7519
+ var VideoEditRequestSchema = z39.object({
7520
+ comment: z39.string().trim().min(1).max(2e3),
7521
+ sections: z39.array(VideoEditTimeSectionSchema).min(1).optional()
7153
7522
  });
7154
- var VideoEditModeSchema = z38.enum([
7523
+ var VideoEditModeSchema = z39.enum([
7155
7524
  "wan_edit",
7156
7525
  "regenerate"
7157
7526
  ]);
7158
- var VideoRegenerationEngineSchema = z38.enum([
7527
+ var VideoRegenerationEngineSchema = z39.enum([
7159
7528
  "free_text",
7160
7529
  "free_image",
7161
7530
  "single_call",
7162
7531
  "multi_shot"
7163
7532
  ]);
7164
- var VideoRegenerationRouteFingerprintSchema = z38.string().trim().min(1).max(128);
7165
- var RegenerateEditRequestSchema = z38.object({
7166
- angleKey: z38.string().min(1),
7167
- comment: z38.string().min(1).max(2e3)
7533
+ var VideoRegenerationRouteFingerprintSchema = z39.string().trim().min(1).max(128);
7534
+ var RegenerateEditRequestSchema = z39.object({
7535
+ angleKey: z39.string().min(1),
7536
+ comment: z39.string().min(1).max(2e3)
7168
7537
  });
7169
7538
 
7170
7539
  // ../../packages/types/src/dropshipping-project.ts
7171
- import { z as z39 } from "zod";
7172
- var DropshippingProjectStatusSchema = z39.enum([
7540
+ import { z as z40 } from "zod";
7541
+ var DropshippingProjectStatusSchema = z40.enum([
7173
7542
  "active",
7174
7543
  "completed",
7175
7544
  "archived"
7176
7545
  ]);
7177
7546
  var DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN = "dropshipping-launch-v1";
7178
- var DropshippingRewardMilestoneSchema = z39.enum([
7547
+ var DropshippingRewardMilestoneSchema = z40.enum([
7179
7548
  "product_selected",
7180
7549
  "costing_saved",
7181
7550
  "product_media_ready",
@@ -7190,21 +7559,21 @@ var DROPSHIPPING_LAUNCH_REWARD_CREDITS = {
7190
7559
  campaign_created: 200
7191
7560
  };
7192
7561
  var DROPSHIPPING_LAUNCH_REWARD_TOTAL_CREDITS = Object.values(DROPSHIPPING_LAUNCH_REWARD_CREDITS).reduce((sum, credits) => sum + credits, 0);
7193
- var DropshippingProjectRewardMilestoneStateSchema = z39.object({
7562
+ var DropshippingProjectRewardMilestoneStateSchema = z40.object({
7194
7563
  milestone: DropshippingRewardMilestoneSchema,
7195
- credits: z39.number().int().positive(),
7196
- available: z39.boolean().optional(),
7197
- earned: z39.boolean(),
7198
- grantId: z39.string().uuid().nullable(),
7199
- grantedAt: z39.string().datetime().nullable()
7200
- });
7201
- var DropshippingProjectRewardProgressSchema = z39.object({
7202
- campaignKey: z39.literal(DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN),
7203
- totalCredits: z39.number().int().positive(),
7204
- earnedCredits: z39.number().int().nonnegative(),
7205
- milestones: z39.array(DropshippingProjectRewardMilestoneStateSchema).length(DropshippingRewardMilestoneSchema.options.length)
7206
- });
7207
- var DropshippingProjectPhaseSchema = z39.enum([
7564
+ credits: z40.number().int().positive(),
7565
+ available: z40.boolean().optional(),
7566
+ earned: z40.boolean(),
7567
+ grantId: z40.string().uuid().nullable(),
7568
+ grantedAt: z40.string().datetime().nullable()
7569
+ });
7570
+ var DropshippingProjectRewardProgressSchema = z40.object({
7571
+ campaignKey: z40.literal(DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN),
7572
+ totalCredits: z40.number().int().positive(),
7573
+ earnedCredits: z40.number().int().nonnegative(),
7574
+ milestones: z40.array(DropshippingProjectRewardMilestoneStateSchema).length(DropshippingRewardMilestoneSchema.options.length)
7575
+ });
7576
+ var DropshippingProjectPhaseSchema = z40.enum([
7208
7577
  "research",
7209
7578
  "costing",
7210
7579
  "media",
@@ -7213,18 +7582,18 @@ var DropshippingProjectPhaseSchema = z39.enum([
7213
7582
  "advertising",
7214
7583
  "complete"
7215
7584
  ]);
7216
- var DropshippingProjectCostingInputSchema = z39.object({
7217
- country: z39.string().min(2).max(3),
7218
- supplierCost: z39.number().finite().positive(),
7219
- shippingCost: z39.number().finite().nonnegative(),
7220
- returnRate: z39.number().finite().min(0).lt(1),
7221
- cancellationRate: z39.number().finite().min(0).lt(1),
7222
- adsManagerCpa: z39.number().finite().nonnegative(),
7223
- desiredMargin: z39.number().finite().min(0).lt(1)
7585
+ var DropshippingProjectCostingInputSchema = z40.object({
7586
+ country: z40.string().min(2).max(3),
7587
+ supplierCost: z40.number().finite().positive(),
7588
+ shippingCost: z40.number().finite().nonnegative(),
7589
+ returnRate: z40.number().finite().min(0).lt(1),
7590
+ cancellationRate: z40.number().finite().min(0).lt(1),
7591
+ adsManagerCpa: z40.number().finite().nonnegative(),
7592
+ desiredMargin: z40.number().finite().min(0).lt(1)
7224
7593
  }).superRefine(({ returnRate, cancellationRate }, context) => {
7225
7594
  if (returnRate + cancellationRate >= 1) {
7226
7595
  context.addIssue({
7227
- code: z39.ZodIssueCode.custom,
7596
+ code: z40.ZodIssueCode.custom,
7228
7597
  path: [
7229
7598
  "cancellationRate"
7230
7599
  ],
@@ -7232,24 +7601,24 @@ var DropshippingProjectCostingInputSchema = z39.object({
7232
7601
  });
7233
7602
  }
7234
7603
  });
7235
- var DropshippingProjectCostingResultSchema = z39.object({
7236
- country: z39.string().min(2).max(3),
7237
- deliveryRate: z39.number().finite(),
7238
- adjustedShippingCost: z39.number().finite(),
7239
- adjustedCpa: z39.number().finite(),
7240
- totalCost: z39.number().finite(),
7241
- suggestedPrice: z39.number().finite(),
7242
- baseCommercialPrice: z39.number().finite(),
7243
- minimumSafePrice: z39.number().finite(),
7244
- commercialPrice: z39.number().finite(),
7245
- comparisonPrice: z39.number().finite(),
7246
- estimatedProfit: z39.number().finite(),
7247
- actualMargin: z39.number().finite(),
7248
- breakEvenAdsManagerCpa: z39.number().finite(),
7249
- wasPriceAutoAdjusted: z39.boolean(),
7250
- profitabilityStatus: z39.literal("healthy")
7251
- });
7252
- var HttpUrlSchema = z39.string().url().max(2e3).refine((value) => {
7604
+ var DropshippingProjectCostingResultSchema = z40.object({
7605
+ country: z40.string().min(2).max(3),
7606
+ deliveryRate: z40.number().finite(),
7607
+ adjustedShippingCost: z40.number().finite(),
7608
+ adjustedCpa: z40.number().finite(),
7609
+ totalCost: z40.number().finite(),
7610
+ suggestedPrice: z40.number().finite(),
7611
+ baseCommercialPrice: z40.number().finite(),
7612
+ minimumSafePrice: z40.number().finite(),
7613
+ commercialPrice: z40.number().finite(),
7614
+ comparisonPrice: z40.number().finite(),
7615
+ estimatedProfit: z40.number().finite(),
7616
+ actualMargin: z40.number().finite(),
7617
+ breakEvenAdsManagerCpa: z40.number().finite(),
7618
+ wasPriceAutoAdjusted: z40.boolean(),
7619
+ profitabilityStatus: z40.literal("healthy")
7620
+ });
7621
+ var HttpUrlSchema = z40.string().url().max(2e3).refine((value) => {
7253
7622
  try {
7254
7623
  return [
7255
7624
  "http:",
@@ -7259,124 +7628,124 @@ var HttpUrlSchema = z39.string().url().max(2e3).refine((value) => {
7259
7628
  return false;
7260
7629
  }
7261
7630
  }, "Product URL must use HTTP(S)");
7262
- var ResearchCandidateOriginSchema = z39.object({
7263
- type: z39.literal("research_candidate"),
7264
- runId: z39.string().uuid(),
7265
- candidateSourceId: z39.string().min(1).max(160)
7266
- });
7267
- var TaskOriginSchema = z39.object({
7268
- type: z39.literal("task"),
7269
- taskId: z39.string().uuid()
7270
- });
7271
- var ManualOriginSchema = z39.object({
7272
- type: z39.literal("manual"),
7273
- clientRequestId: z39.string().uuid(),
7274
- country: z39.string().min(2).max(3).optional()
7275
- });
7276
- var CreateDropshippingProjectRequestSchema = z39.object({
7277
- origin: z39.discriminatedUnion("type", [
7631
+ var ResearchCandidateOriginSchema = z40.object({
7632
+ type: z40.literal("research_candidate"),
7633
+ runId: z40.string().uuid(),
7634
+ candidateSourceId: z40.string().min(1).max(160)
7635
+ });
7636
+ var TaskOriginSchema = z40.object({
7637
+ type: z40.literal("task"),
7638
+ taskId: z40.string().uuid()
7639
+ });
7640
+ var ManualOriginSchema = z40.object({
7641
+ type: z40.literal("manual"),
7642
+ clientRequestId: z40.string().uuid(),
7643
+ country: z40.string().min(2).max(3).optional()
7644
+ });
7645
+ var CreateDropshippingProjectRequestSchema = z40.object({
7646
+ origin: z40.discriminatedUnion("type", [
7278
7647
  ResearchCandidateOriginSchema,
7279
7648
  TaskOriginSchema,
7280
7649
  ManualOriginSchema
7281
7650
  ]),
7282
- productName: z39.string().trim().min(1).max(240).optional(),
7651
+ productName: z40.string().trim().min(1).max(240).optional(),
7283
7652
  productUrl: HttpUrlSchema.optional(),
7284
- country: z39.string().min(2).max(3).optional(),
7285
- languageCode: z39.string().min(2).max(16).optional()
7653
+ country: z40.string().min(2).max(3).optional(),
7654
+ languageCode: z40.string().min(2).max(16).optional()
7286
7655
  }).strict();
7287
- var SaveDropshippingProjectCostingRequestSchema = z39.object({
7656
+ var SaveDropshippingProjectCostingRequestSchema = z40.object({
7288
7657
  input: DropshippingProjectCostingInputSchema,
7289
- version: z39.number().int().positive()
7658
+ version: z40.number().int().positive()
7290
7659
  }).strict();
7291
- var DropshippingProjectNextActionSchema = z39.discriminatedUnion("kind", [
7292
- z39.object({
7293
- kind: z39.literal("cost_product"),
7294
- href: z39.string().min(1)
7660
+ var DropshippingProjectNextActionSchema = z40.discriminatedUnion("kind", [
7661
+ z40.object({
7662
+ kind: z40.literal("cost_product"),
7663
+ href: z40.string().min(1)
7295
7664
  }),
7296
- z39.object({
7297
- kind: z39.literal("provide_product_media"),
7298
- href: z39.string().min(1)
7665
+ z40.object({
7666
+ kind: z40.literal("provide_product_media"),
7667
+ href: z40.string().min(1)
7299
7668
  }),
7300
- z39.object({
7301
- kind: z39.literal("generate_creatives"),
7302
- href: z39.string().min(1)
7669
+ z40.object({
7670
+ kind: z40.literal("generate_creatives"),
7671
+ href: z40.string().min(1)
7303
7672
  }),
7304
- z39.object({
7305
- kind: z39.literal("wait_for_creatives"),
7306
- href: z39.string().min(1)
7673
+ z40.object({
7674
+ kind: z40.literal("wait_for_creatives"),
7675
+ href: z40.string().min(1)
7307
7676
  }),
7308
- z39.object({
7309
- kind: z39.literal("repair_creatives"),
7310
- href: z39.string().min(1)
7677
+ z40.object({
7678
+ kind: z40.literal("repair_creatives"),
7679
+ href: z40.string().min(1)
7311
7680
  }),
7312
- z39.object({
7313
- kind: z39.literal("complete_creative_set"),
7314
- href: z39.string().min(1)
7681
+ z40.object({
7682
+ kind: z40.literal("complete_creative_set"),
7683
+ href: z40.string().min(1)
7315
7684
  }),
7316
- z39.object({
7317
- kind: z39.literal("connect_shopify"),
7318
- href: z39.string().min(1)
7685
+ z40.object({
7686
+ kind: z40.literal("connect_shopify"),
7687
+ href: z40.string().min(1)
7319
7688
  }),
7320
- z39.object({
7321
- kind: z39.literal("publish_storefront"),
7322
- href: z39.string().min(1)
7689
+ z40.object({
7690
+ kind: z40.literal("publish_storefront"),
7691
+ href: z40.string().min(1)
7323
7692
  }),
7324
- z39.object({
7325
- kind: z39.literal("wait_for_storefront"),
7326
- href: z39.string().min(1)
7693
+ z40.object({
7694
+ kind: z40.literal("wait_for_storefront"),
7695
+ href: z40.string().min(1)
7327
7696
  }),
7328
- z39.object({
7329
- kind: z39.literal("connect_meta"),
7330
- href: z39.string().min(1)
7697
+ z40.object({
7698
+ kind: z40.literal("connect_meta"),
7699
+ href: z40.string().min(1)
7331
7700
  }),
7332
- z39.object({
7333
- kind: z39.literal("create_meta_campaign"),
7334
- href: z39.string().min(1)
7701
+ z40.object({
7702
+ kind: z40.literal("create_meta_campaign"),
7703
+ href: z40.string().min(1)
7335
7704
  }),
7336
- z39.object({
7337
- kind: z39.literal("repair_meta_campaign"),
7338
- href: z39.string().min(1)
7705
+ z40.object({
7706
+ kind: z40.literal("repair_meta_campaign"),
7707
+ href: z40.string().min(1)
7339
7708
  }),
7340
- z39.object({
7341
- kind: z39.literal("review_paused_campaign"),
7342
- href: z39.string().min(1)
7709
+ z40.object({
7710
+ kind: z40.literal("review_paused_campaign"),
7711
+ href: z40.string().min(1)
7343
7712
  })
7344
7713
  ]);
7345
- var DropshippingProjectResponseSchema = z39.object({
7346
- id: z39.string().uuid(),
7347
- taskId: z39.string().uuid().nullable(),
7714
+ var DropshippingProjectResponseSchema = z40.object({
7715
+ id: z40.string().uuid(),
7716
+ taskId: z40.string().uuid().nullable(),
7348
7717
  status: DropshippingProjectStatusSchema,
7349
- originKey: z39.string().min(1),
7350
- researchRunId: z39.string().uuid().nullable(),
7351
- candidateSourceId: z39.string().nullable(),
7352
- productName: z39.string().nullable(),
7718
+ originKey: z40.string().min(1),
7719
+ researchRunId: z40.string().uuid().nullable(),
7720
+ candidateSourceId: z40.string().nullable(),
7721
+ productName: z40.string().nullable(),
7353
7722
  productUrl: HttpUrlSchema.nullable(),
7354
- referenceImageUrl: z39.string().url().nullable(),
7355
- hasReferenceImage: z39.boolean(),
7356
- targetCountry: z39.string().nullable(),
7357
- languageCode: z39.string().nullable(),
7723
+ referenceImageUrl: z40.string().url().nullable(),
7724
+ hasReferenceImage: z40.boolean(),
7725
+ targetCountry: z40.string().nullable(),
7726
+ languageCode: z40.string().nullable(),
7358
7727
  costingInput: DropshippingProjectCostingInputSchema.nullable(),
7359
7728
  costingResult: DropshippingProjectCostingResultSchema.nullable(),
7360
- costingVersion: z39.number().int().positive().nullable(),
7361
- version: z39.number().int().positive(),
7729
+ costingVersion: z40.number().int().positive().nullable(),
7730
+ version: z40.number().int().positive(),
7362
7731
  phase: DropshippingProjectPhaseSchema,
7363
7732
  nextAction: DropshippingProjectNextActionSchema.nullable(),
7364
7733
  rewards: DropshippingProjectRewardProgressSchema,
7365
- createdAt: z39.string().datetime(),
7366
- updatedAt: z39.string().datetime()
7734
+ createdAt: z40.string().datetime(),
7735
+ updatedAt: z40.string().datetime()
7367
7736
  });
7368
7737
 
7369
7738
  // ../../packages/types/src/scene-subject-contract.ts
7370
- import { z as z40 } from "zod";
7371
- var SceneSubjectContractSchema = z40.object({
7372
- maxPeople: z40.number().int().min(1).max(10),
7373
- minPeople: z40.number().int().min(0).max(10),
7374
- minDistinctPeople: z40.number().int().min(0).max(10),
7375
- maxHands: z40.number().int().min(0).max(20)
7739
+ import { z as z41 } from "zod";
7740
+ var SceneSubjectContractSchema = z41.object({
7741
+ maxPeople: z41.number().int().min(1).max(10),
7742
+ minPeople: z41.number().int().min(0).max(10),
7743
+ minDistinctPeople: z41.number().int().min(0).max(10),
7744
+ maxHands: z41.number().int().min(0).max(20)
7376
7745
  }).superRefine((value, ctx) => {
7377
7746
  if (value.minPeople > value.maxPeople) {
7378
7747
  ctx.addIssue({
7379
- code: z40.ZodIssueCode.custom,
7748
+ code: z41.ZodIssueCode.custom,
7380
7749
  path: [
7381
7750
  "minPeople"
7382
7751
  ],
@@ -7385,7 +7754,7 @@ var SceneSubjectContractSchema = z40.object({
7385
7754
  }
7386
7755
  if (value.minDistinctPeople > value.maxPeople) {
7387
7756
  ctx.addIssue({
7388
- code: z40.ZodIssueCode.custom,
7757
+ code: z41.ZodIssueCode.custom,
7389
7758
  path: [
7390
7759
  "minDistinctPeople"
7391
7760
  ],
@@ -7394,7 +7763,7 @@ var SceneSubjectContractSchema = z40.object({
7394
7763
  }
7395
7764
  if (value.minDistinctPeople > value.minPeople) {
7396
7765
  ctx.addIssue({
7397
- code: z40.ZodIssueCode.custom,
7766
+ code: z41.ZodIssueCode.custom,
7398
7767
  path: [
7399
7768
  "minDistinctPeople"
7400
7769
  ],
@@ -7404,7 +7773,7 @@ var SceneSubjectContractSchema = z40.object({
7404
7773
  });
7405
7774
 
7406
7775
  // ../../packages/types/src/service-creative.ts
7407
- import { z as z41 } from "zod";
7776
+ import { z as z42 } from "zod";
7408
7777
  var SERVICE_CREATIVE_PUBLIC_PRESETS = [
7409
7778
  "auto",
7410
7779
  "ugc_dynamic",
@@ -7412,19 +7781,19 @@ var SERVICE_CREATIVE_PUBLIC_PRESETS = [
7412
7781
  "cinematic",
7413
7782
  "wild_card"
7414
7783
  ];
7415
- var ServiceCreativePresetSchema = z41.enum(SERVICE_CREATIVE_PUBLIC_PRESETS);
7784
+ var ServiceCreativePresetSchema = z42.enum(SERVICE_CREATIVE_PUBLIC_PRESETS);
7416
7785
  var SERVICE_REVIEW_MODES = [
7417
7786
  "automatic",
7418
7787
  "guided"
7419
7788
  ];
7420
- var ServiceReviewModeSchema = z41.enum(SERVICE_REVIEW_MODES);
7789
+ var ServiceReviewModeSchema = z42.enum(SERVICE_REVIEW_MODES);
7421
7790
  var SERVICE_HIGGSFIELD_MODES = [
7422
7791
  "product_review",
7423
7792
  "ugc_how_to",
7424
7793
  "tv_spot",
7425
7794
  "wild_card"
7426
7795
  ];
7427
- var ServiceHiggsfieldModeSchema = z41.enum(SERVICE_HIGGSFIELD_MODES);
7796
+ var ServiceHiggsfieldModeSchema = z42.enum(SERVICE_HIGGSFIELD_MODES);
7428
7797
  var SERVICE_MEDIA_ROLES = [
7429
7798
  "screen_demo",
7430
7799
  "ad_reference",
@@ -7432,20 +7801,20 @@ var SERVICE_MEDIA_ROLES = [
7432
7801
  "presenter_reference",
7433
7802
  "brand_asset"
7434
7803
  ];
7435
- var ServiceMediaRoleSchema = z41.enum(SERVICE_MEDIA_ROLES);
7436
- var ServiceMediaAssetSchema = z41.object({
7804
+ var ServiceMediaRoleSchema = z42.enum(SERVICE_MEDIA_ROLES);
7805
+ var ServiceMediaAssetSchema = z42.object({
7437
7806
  role: ServiceMediaRoleSchema,
7438
- s3Key: z41.string().min(1),
7807
+ s3Key: z42.string().min(1),
7439
7808
  /** HMAC ticket returned by the authenticated service-media presign route. */
7440
- uploadToken: z41.string().min(1).optional(),
7441
- mimeType: z41.string().min(1),
7442
- durationSec: z41.number().positive().max(30).optional(),
7443
- sourceName: z41.string().max(255).optional()
7809
+ uploadToken: z42.string().min(1).optional(),
7810
+ mimeType: z42.string().min(1),
7811
+ durationSec: z42.number().positive().max(30).optional(),
7812
+ sourceName: z42.string().max(255).optional()
7444
7813
  }).superRefine((asset, ctx) => {
7445
7814
  const isVideoRole = asset.role === "screen_demo" || asset.role === "ad_reference";
7446
7815
  if (isVideoRole && !asset.mimeType.startsWith("video/")) {
7447
7816
  ctx.addIssue({
7448
- code: z41.ZodIssueCode.custom,
7817
+ code: z42.ZodIssueCode.custom,
7449
7818
  message: `${asset.role} requires video media`,
7450
7819
  path: [
7451
7820
  "mimeType"
@@ -7454,7 +7823,7 @@ var ServiceMediaAssetSchema = z41.object({
7454
7823
  }
7455
7824
  if (isVideoRole && asset.durationSec === void 0) {
7456
7825
  ctx.addIssue({
7457
- code: z41.ZodIssueCode.custom,
7826
+ code: z42.ZodIssueCode.custom,
7458
7827
  message: `${asset.role} requires durationSec`,
7459
7828
  path: [
7460
7829
  "durationSec"
@@ -7463,7 +7832,7 @@ var ServiceMediaAssetSchema = z41.object({
7463
7832
  }
7464
7833
  if (!isVideoRole && !asset.mimeType.startsWith("image/")) {
7465
7834
  ctx.addIssue({
7466
- code: z41.ZodIssueCode.custom,
7835
+ code: z42.ZodIssueCode.custom,
7467
7836
  message: `${asset.role} requires image media`,
7468
7837
  path: [
7469
7838
  "mimeType"
@@ -7471,13 +7840,13 @@ var ServiceMediaAssetSchema = z41.object({
7471
7840
  });
7472
7841
  }
7473
7842
  });
7474
- var ServiceMediaBundleSchema = z41.object({
7475
- assets: z41.array(ServiceMediaAssetSchema).max(15).default([])
7843
+ var ServiceMediaBundleSchema = z42.object({
7844
+ assets: z42.array(ServiceMediaAssetSchema).max(15).default([])
7476
7845
  }).superRefine((bundle, ctx) => {
7477
7846
  const adReferences = bundle.assets.filter((asset) => asset.role === "ad_reference");
7478
7847
  if (adReferences.length > 5) {
7479
7848
  ctx.addIssue({
7480
- code: z41.ZodIssueCode.custom,
7849
+ code: z42.ZodIssueCode.custom,
7481
7850
  message: "A maximum of five ad_reference videos is allowed",
7482
7851
  path: [
7483
7852
  "assets"
@@ -7487,7 +7856,7 @@ var ServiceMediaBundleSchema = z41.object({
7487
7856
  const screenDemos = bundle.assets.filter((asset) => asset.role === "screen_demo");
7488
7857
  if (screenDemos.length > 3) {
7489
7858
  ctx.addIssue({
7490
- code: z41.ZodIssueCode.custom,
7859
+ code: z42.ZodIssueCode.custom,
7491
7860
  message: "A maximum of three screen_demo videos is allowed",
7492
7861
  path: [
7493
7862
  "assets"
@@ -7504,33 +7873,33 @@ var SERVICE_MEDIA_ANALYSIS_KINDS = [
7504
7873
  "product",
7505
7874
  "other"
7506
7875
  ];
7507
- var ServiceMediaAnalysisKindSchema = z41.enum(SERVICE_MEDIA_ANALYSIS_KINDS);
7508
- var ServiceMediaAnalysisItemSchema = z41.object({
7509
- assetIndex: z41.number().int().nonnegative(),
7510
- mediaType: z41.enum([
7876
+ var ServiceMediaAnalysisKindSchema = z42.enum(SERVICE_MEDIA_ANALYSIS_KINDS);
7877
+ var ServiceMediaAnalysisItemSchema = z42.object({
7878
+ assetIndex: z42.number().int().nonnegative(),
7879
+ mediaType: z42.enum([
7511
7880
  "image",
7512
7881
  "video"
7513
7882
  ]),
7514
7883
  kind: ServiceMediaAnalysisKindSchema,
7515
- description: z41.string().min(1).max(1200),
7516
- tags: z41.array(z41.string().min(1).max(80)).max(12),
7517
- suggestedRoles: z41.array(ServiceMediaRoleSchema).max(4),
7518
- visiblePeople: z41.number().int().nonnegative().max(20).optional(),
7519
- ocrText: z41.string().max(2e3).optional(),
7520
- speechSummary: z41.string().max(2e3).optional()
7521
- });
7522
- var ServiceMediaAnalysisSchema = z41.object({
7523
- version: z41.literal(1),
7524
- analyzedAt: z41.string().datetime(),
7525
- pricingAction: z41.literal("service-media-analysis"),
7526
- assetFingerprint: z41.string().regex(/^[a-f0-9]{64}$/),
7884
+ description: z42.string().min(1).max(1200),
7885
+ tags: z42.array(z42.string().min(1).max(80)).max(12),
7886
+ suggestedRoles: z42.array(ServiceMediaRoleSchema).max(4),
7887
+ visiblePeople: z42.number().int().nonnegative().max(20).optional(),
7888
+ ocrText: z42.string().max(2e3).optional(),
7889
+ speechSummary: z42.string().max(2e3).optional()
7890
+ });
7891
+ var ServiceMediaAnalysisSchema = z42.object({
7892
+ version: z42.literal(1),
7893
+ analyzedAt: z42.string().datetime(),
7894
+ pricingAction: z42.literal("service-media-analysis"),
7895
+ assetFingerprint: z42.string().regex(/^[a-f0-9]{64}$/),
7527
7896
  /** Language used for human-readable descriptions and tags. Optional for old checkpoints. */
7528
- language: z41.enum([
7897
+ language: z42.enum([
7529
7898
  "es",
7530
7899
  "en",
7531
7900
  "pt"
7532
7901
  ]).optional(),
7533
- items: z41.array(ServiceMediaAnalysisItemSchema).max(15)
7902
+ items: z42.array(ServiceMediaAnalysisItemSchema).max(15)
7534
7903
  });
7535
7904
 
7536
7905
  // ../../packages/types/src/productmaker-chat-capabilities.ts
@@ -7540,6 +7909,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7540
7909
  confirmation: "credit_consuming",
7541
7910
  schemaHash: "bab794a9700a8d3f42b97f444a776e7ed4dcd833038c52588f951b50970ff3fa"
7542
7911
  },
7912
+ {
7913
+ name: "check_meta_media_upload",
7914
+ confirmation: "none",
7915
+ schemaHash: "62b1af1d796d9466f8926afc5e6d18c4ddeccd5b0269ca7b21c2f6ebcfd2a945"
7916
+ },
7543
7917
  {
7544
7918
  name: "cost_dropshipping_project",
7545
7919
  confirmation: "state_change",
@@ -7558,7 +7932,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7558
7932
  {
7559
7933
  name: "create_product_task",
7560
7934
  confirmation: "credit_consuming",
7561
- schemaHash: "50845af40b29f479592ca5e545d9c1af1fe631998802827affccb4641fc90c18"
7935
+ schemaHash: "5c5fe82a365c62e44265e79a4100a06820dbbdb2e0ddc296cd066eb2255b1d62"
7562
7936
  },
7563
7937
  {
7564
7938
  name: "create_service_video",
@@ -7603,7 +7977,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7603
7977
  {
7604
7978
  name: "generate_video_creative",
7605
7979
  confirmation: "credit_consuming",
7606
- schemaHash: "c80208df75f2c7cbc459e9ae494a75a0a5f5bc4cf044b3fc9ef9c0e9af2c2452"
7980
+ schemaHash: "04e12ef523e4064b0c204d110baad35d414b605068087c625badf40c46429c8a"
7607
7981
  },
7608
7982
  {
7609
7983
  name: "get_meta_campaign_status",
@@ -7645,6 +8019,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7645
8019
  confirmation: "none",
7646
8020
  schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
7647
8021
  },
8022
+ {
8023
+ name: "list_meta_destinations",
8024
+ confirmation: "none",
8025
+ schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
8026
+ },
7648
8027
  {
7649
8028
  name: "list_product_research_runs",
7650
8029
  confirmation: "none",
@@ -7673,12 +8052,12 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7673
8052
  {
7674
8053
  name: "publish_creatives_to_meta",
7675
8054
  confirmation: "external_mutation",
7676
- schemaHash: "ddb45071d61746b443124346e7a82095315a2a80bd393c73a45f12b895939cf5"
8055
+ schemaHash: "59b3d6d5ce25812fee3382b0b1c0cf5e07beb1063048dd0a0d0633686cf29da5"
7677
8056
  },
7678
8057
  {
7679
8058
  name: "publish_to_meta",
7680
8059
  confirmation: "external_mutation",
7681
- schemaHash: "4b6daa1a25e739ccf208a6acede067ec1c22b1ea5509f332361a17dd46f6bb0b"
8060
+ schemaHash: "b507735a1fe13657355cf93163b6ead0db151984e6f2702dcdbf76b4037133ca"
7682
8061
  },
7683
8062
  {
7684
8063
  name: "publish_to_shopify",
@@ -7695,6 +8074,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7695
8074
  confirmation: "credit_consuming",
7696
8075
  schemaHash: "745687cb41f904e293105f93bbe6bc0471f63fe097ab8f85d3df8d9833084844"
7697
8076
  },
8077
+ {
8078
+ name: "request_meta_media_upload",
8079
+ confirmation: "none",
8080
+ schemaHash: "fad928b17b4658eb5831bb8b62d5e65bd5220ad94b04b90ace09e658e1895c74"
8081
+ },
7698
8082
  {
7699
8083
  name: "resolve_pending_action",
7700
8084
  confirmation: "credit_consuming",
@@ -7709,8 +8093,8 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7709
8093
  var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITIES.map((capability) => capability.name);
7710
8094
 
7711
8095
  // ../../packages/types/src/actor.ts
7712
- import { z as z42 } from "zod";
7713
- var ActorStatusSchema = z42.enum([
8096
+ import { z as z43 } from "zod";
8097
+ var ActorStatusSchema = z43.enum([
7714
8098
  "draft",
7715
8099
  "generating",
7716
8100
  "ready",
@@ -7719,18 +8103,18 @@ var ActorStatusSchema = z42.enum([
7719
8103
  var ACTOR_NAME_MAX_CHARS = 60;
7720
8104
  var ACTOR_DESCRIPTION_MAX_CHARS = 600;
7721
8105
  var ACTOR_CANDIDATE_COUNT = 4;
7722
- var ActorGenderSchema = z42.enum([
8106
+ var ActorGenderSchema = z43.enum([
7723
8107
  "female",
7724
8108
  "male",
7725
8109
  "non_binary"
7726
8110
  ]);
7727
- var ActorSchema = z42.object({
7728
- id: z42.string(),
7729
- name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS),
7730
- description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
8111
+ var ActorSchema = z43.object({
8112
+ id: z43.string(),
8113
+ name: z43.string().min(1).max(ACTOR_NAME_MAX_CHARS),
8114
+ description: z43.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
7731
8115
  gender: ActorGenderSchema,
7732
- voiceId: z42.string().nullable().optional(),
7733
- brandId: z42.string().nullable().optional(),
8116
+ voiceId: z43.string().nullable().optional(),
8117
+ brandId: z43.string().nullable().optional(),
7734
8118
  status: ActorStatusSchema,
7735
8119
  /**
7736
8120
  * A candidate-grid run is in flight RIGHT NOW.
@@ -7742,32 +8126,114 @@ var ActorSchema = z42.object({
7742
8126
  * timestamp with a TTL, so an orphaned run reads as `false` instead of
7743
8127
  * pinning the personaje forever.
7744
8128
  */
7745
- generating: z42.boolean().optional(),
7746
- photoUrl: z42.string().url().nullable().optional(),
7747
- avatarUrl: z42.string().url().nullable().optional(),
7748
- candidateUrls: z42.array(z42.string().url()).optional(),
8129
+ generating: z43.boolean().optional(),
8130
+ photoUrl: z43.string().url().nullable().optional(),
8131
+ avatarUrl: z43.string().url().nullable().optional(),
8132
+ candidateUrls: z43.array(z43.string().url()).optional(),
7749
8133
  /** Indices of candidateUrls that failed the anatomy check (Task 6). */
7750
- flaggedCandidateIndices: z42.array(z42.number().int()).optional()
8134
+ flaggedCandidateIndices: z43.array(z43.number().int()).optional()
7751
8135
  });
7752
- var CreateActorRequest = z42.object({
7753
- name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS),
7754
- description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
8136
+ var CreateActorRequest = z43.object({
8137
+ name: z43.string().min(1).max(ACTOR_NAME_MAX_CHARS),
8138
+ description: z43.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
7755
8139
  gender: ActorGenderSchema,
7756
- voiceId: z42.string().optional(),
7757
- brandId: z42.string().optional(),
7758
- sourcePhotoKey: z42.string().optional()
8140
+ voiceId: z43.string().optional(),
8141
+ brandId: z43.string().optional(),
8142
+ sourcePhotoKey: z43.string().optional()
7759
8143
  }).refine((v) => v.description.trim().length > 0 || !!v.sourcePhotoKey, {
7760
8144
  message: "Se necesita una descripci\xF3n o una foto"
7761
8145
  });
7762
- var UpdateActorRequest = z42.object({
7763
- name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
7764
- description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
8146
+ var UpdateActorRequest = z43.object({
8147
+ name: z43.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
8148
+ description: z43.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
7765
8149
  gender: ActorGenderSchema.optional(),
7766
- voiceId: z42.string().nullable().optional(),
7767
- brandId: z42.string().nullable().optional()
8150
+ voiceId: z43.string().nullable().optional(),
8151
+ brandId: z43.string().nullable().optional()
7768
8152
  });
7769
- var ApproveActorRequest = z42.object({
7770
- candidateIndex: z42.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
8153
+ var ApproveActorRequest = z43.object({
8154
+ candidateIndex: z43.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
8155
+ });
8156
+
8157
+ // ../../packages/types/src/referral-dashboard.ts
8158
+ import { z as z44 } from "zod";
8159
+ var REFERRAL_DASHBOARD_RAW_STATUSES = [
8160
+ "ACTIVE",
8161
+ "PENDING",
8162
+ "GRACE",
8163
+ "CANCELLED",
8164
+ "EXPIRED"
8165
+ ];
8166
+ var REFERRAL_DASHBOARD_STATES = [
8167
+ "active",
8168
+ "pending",
8169
+ "inactive",
8170
+ "no_plan"
8171
+ ];
8172
+ var REFERRAL_DASHBOARD_PLAN_KINDS = [
8173
+ "current",
8174
+ "last",
8175
+ "none"
8176
+ ];
8177
+ var ReferralDashboardRawStatusSchema = z44.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
8178
+ var ReferralDashboardStateSchema = z44.enum(REFERRAL_DASHBOARD_STATES);
8179
+ var ReferralDashboardPlanKindSchema = z44.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
8180
+ var ReferralDashboardStatusSchema = z44.enum([
8181
+ "all",
8182
+ "active",
8183
+ "pending",
8184
+ "inactive"
8185
+ ]).default("all");
8186
+ var ReferralDashboardPlanSchema = z44.string().trim().min(1).max(100).optional();
8187
+ var ReferralDashboardFiltersSchema = z44.object({
8188
+ status: ReferralDashboardStatusSchema,
8189
+ plan: ReferralDashboardPlanSchema
8190
+ });
8191
+ var ReferralDashboardSubscriptionSchema = z44.object({
8192
+ planName: z44.string().nullable(),
8193
+ planKind: ReferralDashboardPlanKindSchema,
8194
+ rawStatus: ReferralDashboardRawStatusSchema.nullable(),
8195
+ state: ReferralDashboardStateSchema,
8196
+ currentPeriodEnd: z44.string().datetime({
8197
+ offset: true
8198
+ }).nullable(),
8199
+ cancelAtPeriodEnd: z44.boolean()
8200
+ });
8201
+ var ReferralDashboardReferralSchema = z44.object({
8202
+ id: z44.string().min(1),
8203
+ registeredAt: z44.string().datetime({
8204
+ offset: true
8205
+ }),
8206
+ // Kept in the response for compatibility with existing referral API clients.
8207
+ // The referrer dashboard intentionally does not render these legacy fields.
8208
+ expiresAt: z44.string().datetime({
8209
+ offset: true
8210
+ }).nullable().optional(),
8211
+ status: z44.string().min(1).optional(),
8212
+ referredUser: z44.object({
8213
+ email: z44.string().nullable()
8214
+ }),
8215
+ subscription: ReferralDashboardSubscriptionSchema
8216
+ });
8217
+ var ReferralDashboardReferralsPageSchema = z44.object({
8218
+ referrals: z44.array(ReferralDashboardReferralSchema),
8219
+ total: z44.number().int().nonnegative()
8220
+ });
8221
+ var ReferralDashboardPlanBreakdownSchema = z44.object({
8222
+ planName: z44.string().nullable(),
8223
+ count: z44.number().int().nonnegative()
8224
+ });
8225
+ var ReferralDashboardMonthlyRegistrationSchema = z44.object({
8226
+ month: z44.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
8227
+ count: z44.number().int().nonnegative()
8228
+ });
8229
+ var ReferralDashboardOverviewSchema = z44.object({
8230
+ totalReferredUsers: z44.number().int().nonnegative(),
8231
+ activeUsers: z44.number().int().nonnegative(),
8232
+ pendingUsers: z44.number().int().nonnegative(),
8233
+ inactiveUsers: z44.number().int().nonnegative(),
8234
+ noPlanUsers: z44.number().int().nonnegative(),
8235
+ planBreakdown: z44.array(ReferralDashboardPlanBreakdownSchema),
8236
+ monthlyRegistrations: z44.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
7771
8237
  });
7772
8238
 
7773
8239
  // src/resolve-media.ts
@@ -8009,10 +8475,10 @@ async function resolveMediaRef(ref, opts) {
8009
8475
  __name(resolveMediaRef, "resolveMediaRef");
8010
8476
 
8011
8477
  // src/tools/shared.ts
8012
- import { z as z43 } from "zod";
8013
- var Lang = z43.string().min(2).max(10);
8014
- var Country = z43.string().min(2).max(3);
8015
- var LandingStrategyEnum = z43.enum([
8478
+ import { z as z45 } from "zod";
8479
+ var Lang = z45.string().min(2).max(10);
8480
+ var Country = z45.string().min(2).max(3);
8481
+ var LandingStrategyEnum = z45.enum([
8016
8482
  "auto",
8017
8483
  "classic",
8018
8484
  "pain_solution",
@@ -8021,12 +8487,12 @@ var LandingStrategyEnum = z43.enum([
8021
8487
  "offer_value",
8022
8488
  "lifestyle_identity"
8023
8489
  ]);
8024
- var LandingImageFormatEnum = z43.enum([
8490
+ var LandingImageFormatEnum = z45.enum([
8025
8491
  "9:16",
8026
8492
  "4:5",
8027
8493
  "1:1"
8028
8494
  ]);
8029
- var VideoPresetEnum = z43.enum([
8495
+ var VideoPresetEnum = z45.enum([
8030
8496
  "auto",
8031
8497
  "ugc_dynamic",
8032
8498
  "ugc_how_to",
@@ -8039,7 +8505,7 @@ var VideoPresetEnum = z43.enum([
8039
8505
  "pixar_problem",
8040
8506
  "pixar_sassy"
8041
8507
  ]);
8042
- var TaskCreativeVariantVideoPresetEnum = z43.enum([
8508
+ var TaskCreativeVariantVideoPresetEnum = z45.enum([
8043
8509
  "auto",
8044
8510
  "ugc_dynamic",
8045
8511
  "ugc_how_to",
@@ -8110,7 +8576,7 @@ function resolveVideoPreset(preset) {
8110
8576
  return VIDEO_PRESET_RESOLUTION[preset] ?? null;
8111
8577
  }
8112
8578
  __name(resolveVideoPreset, "resolveVideoPreset");
8113
- var VideoTierEnum = z43.enum([
8579
+ var VideoTierEnum = z45.enum([
8114
8580
  "basic"
8115
8581
  ]);
8116
8582
  function resolveVideoTier(_tier) {
@@ -8121,27 +8587,21 @@ function resolveWireVideoTier(tier, durationSeconds) {
8121
8587
  return durationSeconds === 30 ? "standard" : resolveVideoTier(tier);
8122
8588
  }
8123
8589
  __name(resolveWireVideoTier, "resolveWireVideoTier");
8124
- function assertVideoPresetDurationCompatibility(videoPreset, durationSeconds) {
8125
- if (videoPreset === "problem_solution" && durationSeconds !== 30) {
8126
- throw new Error("Problema y Soluci\xF3n solo est\xE1 disponible en videos Multiescena de 30 segundos.");
8127
- }
8128
- }
8129
- __name(assertVideoPresetDurationCompatibility, "assertVideoPresetDurationCompatibility");
8130
8590
  function resolveVideoFormatForDuration(durationSeconds) {
8131
8591
  return durationSeconds === 30 ? "double_30s" : "single_15s";
8132
8592
  }
8133
8593
  __name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
8134
- var VideoResolutionEnum = z43.enum([
8594
+ var VideoResolutionEnum = z45.enum([
8135
8595
  "480p",
8136
8596
  "720p"
8137
8597
  ]);
8138
- var VideoStyleEnum = z43.enum([
8598
+ var VideoStyleEnum = z45.enum([
8139
8599
  "ugc",
8140
8600
  "cartoon_3d",
8141
8601
  "product_only",
8142
8602
  "ai_dynamic"
8143
8603
  ]);
8144
- var NarrativeStyleEnum = z43.enum([
8604
+ var NarrativeStyleEnum = z45.enum([
8145
8605
  "review",
8146
8606
  "ugc_how_to",
8147
8607
  "problem_product_cta",
@@ -8158,7 +8618,7 @@ var NarrativeStyleEnum = z43.enum([
8158
8618
  "ugc_skeptic_confession",
8159
8619
  "ugc_demo_review"
8160
8620
  ]);
8161
- var PublicNarrativeStyleEnum = z43.enum([
8621
+ var PublicNarrativeStyleEnum = z45.enum([
8162
8622
  "review",
8163
8623
  "ugc_how_to",
8164
8624
  "ugc_unboxing_chaos",
@@ -8170,7 +8630,7 @@ var PublicNarrativeStyleEnum = z43.enum([
8170
8630
  "animated_problem_pixar",
8171
8631
  "sassy_product_pixar"
8172
8632
  ]);
8173
- var VideoModelEnum = z43.enum([
8633
+ var VideoModelEnum = z45.enum([
8174
8634
  "veo",
8175
8635
  "wan",
8176
8636
  "bytedance",
@@ -8178,25 +8638,25 @@ var VideoModelEnum = z43.enum([
8178
8638
  "grok",
8179
8639
  "kling"
8180
8640
  ]);
8181
- var ProductReferenceUserChoiceEnum = z43.enum([
8641
+ var ProductReferenceUserChoiceEnum = z45.enum([
8182
8642
  "same_product",
8183
8643
  "variants",
8184
8644
  "different_products",
8185
8645
  "combo_distinct"
8186
8646
  ]);
8187
- var ProductReferenceCandidateGroupingEnum = z43.enum([
8647
+ var ProductReferenceCandidateGroupingEnum = z45.enum([
8188
8648
  "same_product_same_variant",
8189
8649
  "same_product_different_variants",
8190
8650
  "different_products",
8191
8651
  "unclear"
8192
8652
  ]);
8193
- var GapDurationChoiceMirror = z43.object({
8194
- angleIndex: z43.number().int().nonnegative(),
8195
- recommendedSec: z43.union([
8196
- z43.literal(15),
8197
- z43.literal(30)
8653
+ var GapDurationChoiceMirror = z45.object({
8654
+ angleIndex: z45.number().int().nonnegative(),
8655
+ recommendedSec: z45.union([
8656
+ z45.literal(15),
8657
+ z45.literal(30)
8198
8658
  ]),
8199
- estimatedDeltaCredits: z43.number().int().nonnegative().optional()
8659
+ estimatedDeltaCredits: z45.number().int().nonnegative().optional()
8200
8660
  });
8201
8661
  function buildSelectedImageUrlsHash(urls) {
8202
8662
  const input = [
@@ -8210,13 +8670,13 @@ function buildSelectedImageUrlsHash(urls) {
8210
8670
  return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
8211
8671
  }
8212
8672
  __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
8213
- var ImageCreativeStyleEnum = z43.enum([
8673
+ var ImageCreativeStyleEnum = z45.enum([
8214
8674
  "studio",
8215
8675
  "floating",
8216
8676
  "ingredients",
8217
8677
  "in_use"
8218
8678
  ]);
8219
- var ImageCreativeArchetypeEnum = z43.enum([
8679
+ var ImageCreativeArchetypeEnum = z45.enum([
8220
8680
  "direct_response_product_ad",
8221
8681
  "hero_outcome",
8222
8682
  "offer_value_prop",
@@ -8225,23 +8685,23 @@ var ImageCreativeArchetypeEnum = z43.enum([
8225
8685
  "problem_solution",
8226
8686
  "lifestyle_identity"
8227
8687
  ]);
8228
- var ImageCreativeVisualTreatmentEnum = z43.enum([
8688
+ var ImageCreativeVisualTreatmentEnum = z45.enum([
8229
8689
  "performance_product_ad",
8230
8690
  "premium_dtc_carousel"
8231
8691
  ]);
8232
- var ImageCreativeAspectRatioEnum = z43.enum([
8692
+ var ImageCreativeAspectRatioEnum = z45.enum([
8233
8693
  "1:1",
8234
8694
  "4:5",
8235
8695
  "9:16",
8236
8696
  "16:9"
8237
8697
  ]);
8238
- var ImageRefSchema = z43.object({
8239
- url: z43.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.'),
8240
- path: z43.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`.')
8698
+ var ImageRefSchema = z45.object({
8699
+ url: z45.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.'),
8700
+ path: z45.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`.')
8241
8701
  }).refine((v) => Boolean(v.url) !== Boolean(v.path), {
8242
8702
  message: "each image entry must have exactly one of `url` or `path`"
8243
8703
  });
8244
- var ImagesInput = z43.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.');
8704
+ var ImagesInput = z45.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.');
8245
8705
  function asText(value) {
8246
8706
  return {
8247
8707
  content: [
@@ -8294,14 +8754,14 @@ async function submitCreative(api, opts) {
8294
8754
  });
8295
8755
  }
8296
8756
  __name(submitCreative, "submitCreative");
8297
- var ResearchTargetCountryEnum = z43.enum([
8757
+ var ResearchTargetCountryEnum = z45.enum([
8298
8758
  "CO",
8299
8759
  "MX",
8300
8760
  "PE",
8301
8761
  "EC",
8302
8762
  "CL"
8303
8763
  ]);
8304
- var ResearchCategoryEnum = z43.enum([
8764
+ var ResearchCategoryEnum = z45.enum([
8305
8765
  "auto",
8306
8766
  "automotive",
8307
8767
  "kitchen",
@@ -8374,6 +8834,15 @@ var McpErrorCode = {
8374
8834
  SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
8375
8835
  // Meta / Facebook Ads
8376
8836
  META_NO_FB_PAGE: "META_NO_FB_PAGE",
8837
+ META_PORTFOLIO_MISMATCH: "META_PORTFOLIO_MISMATCH",
8838
+ META_PAGE_SELECTION_REQUIRED: "META_PAGE_SELECTION_REQUIRED",
8839
+ META_INVALID_PAGE: "META_INVALID_PAGE",
8840
+ META_STAGED_ASSET_NOT_FOUND: "META_STAGED_ASSET_NOT_FOUND",
8841
+ META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
8842
+ META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
8843
+ META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
8844
+ META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
8845
+ META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
8377
8846
  META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
8378
8847
  META_NO_ASSETS_READY: "META_NO_ASSETS_READY",
8379
8848
  META_NO_THUMBNAIL_SOURCE: "META_NO_THUMBNAIL_SOURCE",
@@ -8426,6 +8895,15 @@ var CODE_MESSAGES = {
8426
8895
  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.",
8427
8896
  // Meta — high-frequency
8428
8897
  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).",
8898
+ 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.",
8899
+ 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`.",
8900
+ META_INVALID_PAGE: "The selected Facebook Page is not available for this Meta connection. Call `list_meta_destinations` again and choose an available `pageId`.",
8901
+ META_STAGED_ASSET_NOT_FOUND: "The temporary media upload was not found or expired. Re-upload the media and retry the Meta publication.",
8902
+ META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
8903
+ META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
8904
+ META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
8905
+ 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.",
8906
+ 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.",
8429
8907
  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.",
8430
8908
  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.",
8431
8909
  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.",
@@ -8539,6 +9017,12 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
8539
9017
  McpErrorCode.CAMPAIGN_MISSING_LANDING_URL,
8540
9018
  McpErrorCode.CAMPAIGN_RECONCILIATION_REQUIRED,
8541
9019
  McpErrorCode.META_NO_LANDING_URL,
9020
+ McpErrorCode.META_PORTFOLIO_MISMATCH,
9021
+ McpErrorCode.META_PAGE_SELECTION_REQUIRED,
9022
+ McpErrorCode.META_INVALID_PAGE,
9023
+ McpErrorCode.META_STAGED_ASSET_NOT_FOUND,
9024
+ McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
9025
+ McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
8542
9026
  McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
8543
9027
  McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
8544
9028
  McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
@@ -8560,6 +9044,15 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
8560
9044
  McpErrorCode.META_MISSING_PAYMENT,
8561
9045
  McpErrorCode.META_ACCOUNT_RESTRICTED,
8562
9046
  McpErrorCode.META_NO_FB_PAGE,
9047
+ McpErrorCode.META_PORTFOLIO_MISMATCH,
9048
+ McpErrorCode.META_PAGE_SELECTION_REQUIRED,
9049
+ McpErrorCode.META_INVALID_PAGE,
9050
+ McpErrorCode.META_STAGED_ASSET_NOT_FOUND,
9051
+ McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
9052
+ McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
9053
+ McpErrorCode.META_STAGED_ASSET_INVALID,
9054
+ McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
9055
+ McpErrorCode.META_PUBLICATION_IN_PROGRESS,
8563
9056
  McpErrorCode.TIKTOK_TOKEN_EXPIRED,
8564
9057
  McpErrorCode.TIKTOK_PERMISSION_DENIED,
8565
9058
  McpErrorCode.TIKTOK_MISSING_PAYMENT,
@@ -8954,53 +9447,53 @@ function renderOutputsMarkdown(outputs) {
8954
9447
  __name(renderOutputsMarkdown, "renderOutputsMarkdown");
8955
9448
 
8956
9449
  // src/tools/tasks.ts
8957
- var CreateProductTaskInput = z44.object({
9450
+ var CreateProductTaskInput = z46.object({
8958
9451
  images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
8959
- projectId: z44.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
8960
- productUrl: z44.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."),
9452
+ projectId: z46.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
9453
+ productUrl: z46.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."),
8961
9454
  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.'),
8962
9455
  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.'),
8963
- price: z44.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.'),
8964
- compareAtPrice: z44.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".'),
8965
- offer: z44.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.'),
8966
- paymentMethods: z44.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.'),
8967
- 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."),
8968
- productLabel: z44.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.'),
8969
- 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.'),
8970
- durationSeconds: z44.union([
8971
- z44.literal(15),
8972
- z44.literal(30)
9456
+ price: z46.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.'),
9457
+ compareAtPrice: z46.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".'),
9458
+ offer: z46.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.'),
9459
+ paymentMethods: z46.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.'),
9460
+ 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."),
9461
+ productLabel: z46.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.'),
9462
+ 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.'),
9463
+ durationSeconds: z46.union([
9464
+ z46.literal(15),
9465
+ z46.literal(30)
8973
9466
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
8974
- 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. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
9467
+ 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. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
8975
9468
  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.'),
8976
9469
  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).'),
8977
- multiAngleVideos: z44.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."),
9470
+ multiAngleVideos: z46.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."),
8978
9471
  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.'),
8979
- outputs: z44.array(z44.enum([
9472
+ outputs: z46.array(z46.enum([
8980
9473
  "landing",
8981
9474
  "video",
8982
9475
  "image"
8983
9476
  ])).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.'),
8984
- 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."),
8985
- 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 los outputs generados."),
8986
- actorId: z44.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."),
8987
- creativeDirection: z44.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.'),
8988
- headline: z44.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."),
8989
- 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."),
9477
+ 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."),
9478
+ 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 los outputs generados."),
9479
+ actorId: z46.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."),
9480
+ creativeDirection: z46.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.'),
9481
+ headline: z46.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."),
9482
+ 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."),
8990
9483
  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".'),
8991
9484
  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".')
8992
9485
  }).strict();
8993
- var CreateServiceVideoInput = z44.object({
8994
- landingUrl: z44.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
8995
- additionalContext: z44.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
9486
+ var CreateServiceVideoInput = z46.object({
9487
+ landingUrl: z46.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
9488
+ additionalContext: z46.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
8996
9489
  language: Lang.describe("Idioma del video: es, en o pt."),
8997
- voiceId: z44.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.'),
8998
- brandId: z44.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
8999
- reviewMode: z44.enum([
9490
+ voiceId: z46.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.'),
9491
+ brandId: z46.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
9492
+ reviewMode: z46.enum([
9000
9493
  "automatic",
9001
9494
  "guided"
9002
9495
  ]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
9003
- videoPreset: z44.enum([
9496
+ videoPreset: z46.enum([
9004
9497
  "auto",
9005
9498
  "ugc_dynamic",
9006
9499
  "ugc_how_to",
@@ -9008,35 +9501,35 @@ var CreateServiceVideoInput = z44.object({
9008
9501
  "wild_card"
9009
9502
  ]).default("auto")
9010
9503
  }).strict();
9011
- var GetTaskStatusInput = z44.object({
9012
- taskId: z44.string().min(1),
9013
- waitSeconds: z44.number().int().min(0).max(120).optional()
9014
- });
9015
- var ListTasksInput = z44.object({
9016
- limit: z44.number().int().min(1).max(50).optional(),
9017
- offset: z44.number().int().min(0).optional(),
9018
- search: z44.string().optional()
9019
- });
9020
- var EditTaskDraftInput = z44.object({
9021
- taskId: z44.string().min(1),
9022
- angleIndex: z44.number().int().min(0),
9023
- angle: z44.object({
9024
- angle: z44.string().min(1).max(480),
9025
- buyerPersona: z44.string().min(1).max(360),
9026
- actor: z44.string().min(1).max(360).optional(),
9027
- actorId: z44.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."),
9028
- gender: z44.enum([
9504
+ var GetTaskStatusInput = z46.object({
9505
+ taskId: z46.string().min(1),
9506
+ waitSeconds: z46.number().int().min(0).max(120).optional()
9507
+ });
9508
+ var ListTasksInput = z46.object({
9509
+ limit: z46.number().int().min(1).max(50).optional(),
9510
+ offset: z46.number().int().min(0).optional(),
9511
+ search: z46.string().optional()
9512
+ });
9513
+ var EditTaskDraftInput = z46.object({
9514
+ taskId: z46.string().min(1),
9515
+ angleIndex: z46.number().int().min(0),
9516
+ angle: z46.object({
9517
+ angle: z46.string().min(1).max(480),
9518
+ buyerPersona: z46.string().min(1).max(360),
9519
+ actor: z46.string().min(1).max(360).optional(),
9520
+ actorId: z46.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."),
9521
+ gender: z46.enum([
9029
9522
  "female",
9030
9523
  "male"
9031
9524
  ]).optional(),
9032
- scriptNotes: z44.string().max(2e3).optional()
9525
+ scriptNotes: z46.string().max(2e3).optional()
9033
9526
  })
9034
9527
  });
9035
- var PendingActionPayload = z44.discriminatedUnion("kind", [
9036
- z44.object({
9037
- kind: z44.literal("product_reference_confirmation"),
9528
+ var PendingActionPayload = z46.discriminatedUnion("kind", [
9529
+ z46.object({
9530
+ kind: z46.literal("product_reference_confirmation"),
9038
9531
  userChoice: ProductReferenceUserChoiceEnum,
9039
- confirmedProductNames: z44.array(z44.string().min(1).max(120)).min(2).max(8).optional(),
9532
+ confirmedProductNames: z46.array(z46.string().min(1).max(120)).min(2).max(8).optional(),
9040
9533
  // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
9041
9534
  // reachable via MCP. Valid ONLY together with userChoice='variants' (same
9042
9535
  // product in several colors): renders one shot per color and stitches them,
@@ -9047,50 +9540,50 @@ var PendingActionPayload = z44.discriminatedUnion("kind", [
9047
9540
  // safe because multi-scene IS the intended render for a color variant set,
9048
9541
  // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
9049
9542
  // confirm call; the backend re-gates it (variants + variant_set only).
9050
- useMultiSceneShowcase: z44.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".')
9543
+ useMultiSceneShowcase: z46.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".')
9051
9544
  }),
9052
- z44.object({
9053
- kind: z44.literal("angle_review_approval"),
9545
+ z46.object({
9546
+ kind: z46.literal("angle_review_approval"),
9054
9547
  // All edits are optional — empty action body just approves the
9055
9548
  // backend-generated draft as-is. Field shapes mirror the angle-review
9056
9549
  // request contracts in @packages/types/wizard.ts; the handler routes to
9057
9550
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
9058
- customAngle: z44.string().min(1).max(480).optional(),
9551
+ customAngle: z46.string().min(1).max(480).optional(),
9059
9552
  // Keep the shared MCP action within the strictest downstream contract:
9060
9553
  // autopilot/image resume endpoints cap these fields at 1500 characters.
9061
- painPoints: z44.string().max(1500).optional(),
9062
- benefits: z44.string().max(1500).optional(),
9063
- features: z44.array(z44.string().min(1).max(300)).max(10).optional(),
9064
- gapDurationSeconds: z44.union([
9065
- z44.literal(15),
9066
- z44.literal(30)
9554
+ painPoints: z46.string().max(1500).optional(),
9555
+ benefits: z46.string().max(1500).optional(),
9556
+ features: z46.array(z46.string().min(1).max(300)).max(10).optional(),
9557
+ gapDurationSeconds: z46.union([
9558
+ z46.literal(15),
9559
+ z46.literal(30)
9067
9560
  ]).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.")
9068
9561
  }),
9069
- z44.object({
9070
- kind: z44.literal("service_angle_approval"),
9071
- angleId: z44.string().uuid(),
9072
- content: z44.string().min(1).max(480).optional(),
9073
- creativeDirection: z44.string().min(1).max(1500).optional(),
9074
- actor: z44.string().min(1).max(360).optional()
9562
+ z46.object({
9563
+ kind: z46.literal("service_angle_approval"),
9564
+ angleId: z46.string().uuid(),
9565
+ content: z46.string().min(1).max(480).optional(),
9566
+ creativeDirection: z46.string().min(1).max(1500).optional(),
9567
+ actor: z46.string().min(1).max(360).optional()
9075
9568
  }),
9076
- z44.object({
9077
- kind: z44.literal("service_script_approval"),
9078
- voiceovers: z44.record(z44.string(), z44.string().min(1).max(600)).optional(),
9079
- visualDirections: z44.record(z44.string(), z44.string().min(1).max(1200)).optional()
9569
+ z46.object({
9570
+ kind: z46.literal("service_script_approval"),
9571
+ voiceovers: z46.record(z46.string(), z46.string().min(1).max(600)).optional(),
9572
+ visualDirections: z46.record(z46.string(), z46.string().min(1).max(1200)).optional()
9080
9573
  })
9081
9574
  ]);
9082
- var ResolvePendingActionInput = z44.object({
9083
- taskId: z44.string().min(1),
9575
+ var ResolvePendingActionInput = z46.object({
9576
+ taskId: z46.string().min(1),
9084
9577
  action: PendingActionPayload
9085
9578
  }).strict();
9086
9579
  function resolveVariantConfirmVideoTier(action) {
9087
9580
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
9088
9581
  }
9089
9582
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
9090
- var StatusForResolveSchema = z44.object({
9583
+ var StatusForResolveSchema = z46.object({
9091
9584
  // Metadata used to select the backend resume contract. Older API responses
9092
9585
  // may omit these fields, so the resolver keeps the generic fallback.
9093
- kind: z44.enum([
9586
+ kind: z46.enum([
9094
9587
  "autopilot",
9095
9588
  "video",
9096
9589
  "image",
@@ -9098,24 +9591,24 @@ var StatusForResolveSchema = z44.object({
9098
9591
  "mix",
9099
9592
  "service"
9100
9593
  ]).optional(),
9101
- selectedOutputs: z44.array(z44.enum([
9594
+ selectedOutputs: z46.array(z46.enum([
9102
9595
  "landing",
9103
9596
  "video",
9104
9597
  "image"
9105
9598
  ])).optional(),
9106
- videoAngleCount: z44.number().int().min(1).max(3).optional(),
9107
- ugcFormat: z44.enum([
9599
+ videoAngleCount: z46.number().int().min(1).max(3).optional(),
9600
+ ugcFormat: z46.enum([
9108
9601
  "single_8s",
9109
9602
  "single_15s",
9110
9603
  "double_30s"
9111
9604
  ]).optional(),
9112
- videoStyle: z44.enum([
9605
+ videoStyle: z46.enum([
9113
9606
  "ugc",
9114
9607
  "cartoon_3d",
9115
9608
  "product_only",
9116
9609
  "ai_dynamic"
9117
9610
  ]).optional(),
9118
- videoModel: z44.enum([
9611
+ videoModel: z46.enum([
9119
9612
  "veo",
9120
9613
  "wan",
9121
9614
  "bytedance",
@@ -9123,26 +9616,26 @@ var StatusForResolveSchema = z44.object({
9123
9616
  "grok",
9124
9617
  "kling"
9125
9618
  ]).optional(),
9126
- videoTier: z44.enum([
9619
+ videoTier: z46.enum([
9127
9620
  "legacy",
9128
9621
  "standard",
9129
9622
  "pro",
9130
9623
  "single_call"
9131
9624
  ]).optional(),
9132
- videoResolution: z44.enum([
9625
+ videoResolution: z46.enum([
9133
9626
  "480p",
9134
9627
  "720p"
9135
9628
  ]).optional(),
9136
9629
  narrativeStyle: NarrativeStyleEnum.optional(),
9137
- pendingUserAction: z44.object({
9138
- kind: z44.string(),
9139
- candidateGrouping: z44.string().optional(),
9140
- selectedImageUrls: z44.array(z44.string()).optional(),
9141
- proposedProductNames: z44.array(z44.string()).optional(),
9142
- customAngleDraft: z44.string().optional(),
9143
- painPointsDraft: z44.string().optional(),
9144
- benefitsDraft: z44.string().optional(),
9145
- featuresDraft: z44.array(z44.string()).optional(),
9630
+ pendingUserAction: z46.object({
9631
+ kind: z46.string(),
9632
+ candidateGrouping: z46.string().optional(),
9633
+ selectedImageUrls: z46.array(z46.string()).optional(),
9634
+ proposedProductNames: z46.array(z46.string()).optional(),
9635
+ customAngleDraft: z46.string().optional(),
9636
+ painPointsDraft: z46.string().optional(),
9637
+ benefitsDraft: z46.string().optional(),
9638
+ featuresDraft: z46.array(z46.string()).optional(),
9146
9639
  // Recommendation consumed only when the caller explicitly sends
9147
9640
  // gapDurationSeconds: 30 in the angle approval action.
9148
9641
  gapDurationChoice: GapDurationChoiceMirror.optional(),
@@ -9151,17 +9644,17 @@ var StatusForResolveSchema = z44.object({
9151
9644
  // keys. `actorId` (the saved custom actor the picker stored) was silently
9152
9645
  // dropped here, so the approval handler below could not echo it back on the
9153
9646
  // PATCH — the selection looked accepted and reached no render.
9154
- angles: z44.array(z44.object({
9155
- id: z44.string().uuid(),
9156
- content: z44.string(),
9157
- creativeDirection: z44.string(),
9158
- actor: z44.string(),
9159
- actorId: z44.string().nullable().optional()
9647
+ angles: z46.array(z46.object({
9648
+ id: z46.string().uuid(),
9649
+ content: z46.string(),
9650
+ creativeDirection: z46.string(),
9651
+ actor: z46.string(),
9652
+ actorId: z46.string().nullable().optional()
9160
9653
  }).passthrough()).optional(),
9161
- panels: z44.array(z44.object({
9162
- beatId: z44.string(),
9163
- voiceover: z44.string(),
9164
- visualDirection: z44.string()
9654
+ panels: z46.array(z46.object({
9655
+ beatId: z46.string(),
9656
+ voiceover: z46.string(),
9657
+ visualDirection: z46.string()
9165
9658
  }).passthrough()).optional()
9166
9659
  }).optional()
9167
9660
  }).passthrough();
@@ -9202,7 +9695,6 @@ __name(resolveStatusTaskKind, "resolveStatusTaskKind");
9202
9695
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
9203
9696
  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) => {
9204
9697
  try {
9205
- assertVideoPresetDurationCompatibility(input.videoPreset, input.durationSeconds);
9206
9698
  if (!input.images && !input.projectId) {
9207
9699
  throw new Error("images es requerido salvo que env\xEDes projectId.");
9208
9700
  }
@@ -9602,58 +10094,58 @@ __name(registerTaskTools, "registerTaskTools");
9602
10094
 
9603
10095
  // src/tools/creatives.ts
9604
10096
  import { randomUUID } from "crypto";
9605
- import { z as z45 } from "zod";
10097
+ import { z as z47 } from "zod";
9606
10098
  var CreativeRefs = {
9607
10099
  images: ImagesInput,
9608
10100
  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.'),
9609
10101
  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.'),
9610
- price: z45.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.'),
9611
- offer: z45.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.'),
9612
- compareAtPrice: z45.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
9613
- paymentMethods: z45.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.'),
9614
- 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."),
9615
- productLabel: z45.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.'),
9616
- buyerPersona: z45.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.'),
9617
- isCombo: z45.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
9618
- 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."),
9619
- 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 el creativo."),
9620
- projectId: z45.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
9621
- creativeDirection: z45.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.'),
9622
- headline: z45.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."),
9623
- 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.")
10102
+ price: z47.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.'),
10103
+ offer: z47.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.'),
10104
+ compareAtPrice: z47.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
10105
+ paymentMethods: z47.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.'),
10106
+ productContext: z47.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."),
10107
+ productLabel: z47.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.'),
10108
+ buyerPersona: z47.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.'),
10109
+ isCombo: z47.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
10110
+ competitorScanEnabled: z47.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."),
10111
+ brandId: z47.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."),
10112
+ projectId: z47.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
10113
+ creativeDirection: z47.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.'),
10114
+ headline: z47.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."),
10115
+ subheadline: z47.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.")
9624
10116
  };
9625
- var VideoInput = z45.object({
10117
+ var VideoInput = z47.object({
9626
10118
  images: CreativeRefs.images,
9627
- title: z45.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
9628
- primaryAngle: z45.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".'),
10119
+ title: z47.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
10120
+ primaryAngle: z47.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".'),
9629
10121
  language: CreativeRefs.language,
9630
10122
  country: CreativeRefs.country,
9631
- aspectRatio: z45.enum([
10123
+ aspectRatio: z47.enum([
9632
10124
  "9:16",
9633
10125
  "16:9",
9634
10126
  "Auto"
9635
10127
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
9636
- durationSeconds: z45.union([
9637
- z45.literal(15),
9638
- z45.literal(30)
10128
+ durationSeconds: z47.union([
10129
+ z47.literal(15),
10130
+ z47.literal(30)
9639
10131
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
9640
- 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).'),
10132
+ 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).'),
9641
10133
  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.'),
9642
10134
  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).'),
9643
- features: z45.array(z45.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
10135
+ features: z47.array(z47.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
9644
10136
  price: CreativeRefs.price,
9645
10137
  offer: CreativeRefs.offer,
9646
10138
  productContext: CreativeRefs.productContext,
9647
10139
  productLabel: CreativeRefs.productLabel,
9648
- cta: z45.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.'),
9649
- scriptNotes: z45.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.'),
9650
- pauseForVideoScriptReview: z45.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."),
9651
- 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. Default false."),
10140
+ cta: z47.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.'),
10141
+ scriptNotes: z47.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.'),
10142
+ pauseForVideoScriptReview: z47.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."),
10143
+ pauseForAngleReview: z47.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."),
9652
10144
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
9653
10145
  brandId: CreativeRefs.brandId,
9654
10146
  projectId: CreativeRefs.projectId
9655
10147
  }).strict();
9656
- var FreeVideoQualityEnum = z45.enum([
10148
+ var FreeVideoQualityEnum = z47.enum([
9657
10149
  "economical",
9658
10150
  "high_detail"
9659
10151
  ]);
@@ -9675,38 +10167,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
9675
10167
  15
9676
10168
  ]
9677
10169
  };
9678
- var FreeVideoInput = z45.object({
10170
+ var FreeVideoInput = z47.object({
9679
10171
  images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
9680
- rawPrompt: z45.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."),
10172
+ rawPrompt: z47.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."),
9681
10173
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
9682
- durationSeconds: z45.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."),
10174
+ durationSeconds: z47.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."),
9683
10175
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
9684
- generateAiMusic: z45.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
9685
- addSubtitles: z45.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
9686
- idempotencyKey: z45.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.")
10176
+ generateAiMusic: z47.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
10177
+ addSubtitles: z47.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
10178
+ idempotencyKey: z47.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.")
9687
10179
  }).strict();
9688
10180
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
9689
10181
  var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
9690
10182
  var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
9691
10183
  var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
9692
- var ImageInput = z45.object({
10184
+ var ImageInput = z47.object({
9693
10185
  images: CreativeRefs.images,
9694
- productTitle: z45.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
9695
- primaryAngle: z45.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
10186
+ productTitle: z47.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
10187
+ primaryAngle: z47.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
9696
10188
  buyerPersona: CreativeRefs.buyerPersona,
9697
10189
  language: CreativeRefs.language,
9698
10190
  country: CreativeRefs.country,
9699
- nVariants: z45.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
9700
- imageModel: z45.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
10191
+ nVariants: z47.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
10192
+ imageModel: z47.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
9701
10193
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
9702
10194
  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).`),
9703
10195
  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.`),
9704
- styleMix: z45.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
10196
+ styleMix: z47.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
9705
10197
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe(`Opcional. Relaci\xF3n de aspecto. Valores: ${aspectRatioList}. Default "4:5" (vertical Instagram feed).`),
9706
- generateAdCopy: z45.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
9707
- pauseForAngleReview: z45.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."),
9708
- features: z45.array(z45.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
9709
- brandColors: z45.array(z45.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
10198
+ generateAdCopy: z47.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
10199
+ pauseForAngleReview: z47.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."),
10200
+ features: z47.array(z47.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
10201
+ brandColors: z47.array(z47.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
9710
10202
  price: CreativeRefs.price,
9711
10203
  compareAtPrice: CreativeRefs.compareAtPrice,
9712
10204
  offer: CreativeRefs.offer,
@@ -9720,16 +10212,16 @@ var ImageInput = z45.object({
9720
10212
  headline: CreativeRefs.headline,
9721
10213
  subheadline: CreativeRefs.subheadline
9722
10214
  });
9723
- var EditImageCreativeInput = z45.object({
9724
- taskId: z45.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
9725
- variantIndex: z45.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
9726
- instruction: z45.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".')
10215
+ var EditImageCreativeInput = z47.object({
10216
+ taskId: z47.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
10217
+ variantIndex: z47.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
10218
+ instruction: z47.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".')
9727
10219
  });
9728
- var LandingInput = z45.object({
10220
+ var LandingInput = z47.object({
9729
10221
  images: CreativeRefs.images,
9730
10222
  language: CreativeRefs.language,
9731
10223
  country: CreativeRefs.country,
9732
- salesAngle: z45.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.'),
10224
+ salesAngle: z47.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.'),
9733
10225
  buyerPersona: CreativeRefs.buyerPersona,
9734
10226
  price: CreativeRefs.price,
9735
10227
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -9780,7 +10272,6 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
9780
10272
  });
9781
10273
  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) => {
9782
10274
  try {
9783
- assertVideoPresetDurationCompatibility(input.videoPreset, input.durationSeconds);
9784
10275
  const resolved = resolveVideoPreset(input.videoPreset);
9785
10276
  const wireTier = resolveWireVideoTier(input.videoTier, input.durationSeconds);
9786
10277
  const { videoTier: _publicTier, videoResolution: _vr, projectId, ...rest } = input;
@@ -9855,8 +10346,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
9855
10346
  __name(registerCreativeTools, "registerCreativeTools");
9856
10347
 
9857
10348
  // src/tools/creative-variants.ts
9858
- import { z as z46 } from "zod";
9859
- var LandingSectionKeyEnum = z46.enum([
10349
+ import { z as z48 } from "zod";
10350
+ var LandingSectionKeyEnum = z48.enum([
9860
10351
  "problem_hook",
9861
10352
  "problem_cost",
9862
10353
  "solution_mechanism",
@@ -9896,24 +10387,24 @@ var LandingSectionKeyEnum = z46.enum([
9896
10387
  "lifestyle_fit",
9897
10388
  "join_lifestyle"
9898
10389
  ]);
9899
- var CreativeVariantKindEnum = z46.enum([
10390
+ var CreativeVariantKindEnum = z48.enum([
9900
10391
  "video",
9901
10392
  "image",
9902
10393
  "landing"
9903
10394
  ]);
9904
- var EstimateCreativeVariantInputBase = z46.object({
9905
- sourceTaskId: z46.string().uuid().describe("ID de la tarea original completada."),
10395
+ var EstimateCreativeVariantInputBase = z48.object({
10396
+ sourceTaskId: z48.string().uuid().describe("ID de la tarea original completada."),
9906
10397
  kind: CreativeVariantKindEnum,
9907
10398
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
9908
10399
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
9909
- nVariants: z46.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
10400
+ nVariants: z48.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
9910
10401
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
9911
- instructions: z46.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.")
10402
+ instructions: z48.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.")
9912
10403
  }).strict();
9913
10404
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
9914
10405
  if (input.kind === "video" && input.nVariants !== void 0) {
9915
10406
  ctx.addIssue({
9916
- code: z46.ZodIssueCode.custom,
10407
+ code: z48.ZodIssueCode.custom,
9917
10408
  path: [
9918
10409
  "nVariants"
9919
10410
  ],
@@ -9923,7 +10414,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9923
10414
  if (input.kind !== "video") {
9924
10415
  if (input.videoPreset !== void 0) {
9925
10416
  ctx.addIssue({
9926
- code: z46.ZodIssueCode.custom,
10417
+ code: z48.ZodIssueCode.custom,
9927
10418
  path: [
9928
10419
  "videoPreset"
9929
10420
  ],
@@ -9932,7 +10423,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9932
10423
  }
9933
10424
  if (input.videoResolution !== void 0) {
9934
10425
  ctx.addIssue({
9935
- code: z46.ZodIssueCode.custom,
10426
+ code: z48.ZodIssueCode.custom,
9936
10427
  path: [
9937
10428
  "videoResolution"
9938
10429
  ],
@@ -9942,7 +10433,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9942
10433
  }
9943
10434
  if (input.kind !== "image" && input.nVariants !== void 0) {
9944
10435
  ctx.addIssue({
9945
- code: z46.ZodIssueCode.custom,
10436
+ code: z48.ZodIssueCode.custom,
9946
10437
  path: [
9947
10438
  "nVariants"
9948
10439
  ],
@@ -9952,7 +10443,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9952
10443
  if (input.kind !== "landing") {
9953
10444
  if (input.sectionKey !== void 0) {
9954
10445
  ctx.addIssue({
9955
- code: z46.ZodIssueCode.custom,
10446
+ code: z48.ZodIssueCode.custom,
9956
10447
  path: [
9957
10448
  "sectionKey"
9958
10449
  ],
@@ -9961,7 +10452,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9961
10452
  }
9962
10453
  if (input.instructions !== void 0) {
9963
10454
  ctx.addIssue({
9964
- code: z46.ZodIssueCode.custom,
10455
+ code: z48.ZodIssueCode.custom,
9965
10456
  path: [
9966
10457
  "instructions"
9967
10458
  ],
@@ -9970,31 +10461,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9970
10461
  }
9971
10462
  }
9972
10463
  });
9973
- var CreateCreativeVariantInputBase = z46.object({
9974
- sourceTaskId: z46.string().uuid().describe("ID de la tarea original completada."),
10464
+ var CreateCreativeVariantInputBase = z48.object({
10465
+ sourceTaskId: z48.string().uuid().describe("ID de la tarea original completada."),
9975
10466
  kind: CreativeVariantKindEnum,
9976
- confirm: z46.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
9977
- clientRequestId: z46.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."),
9978
- angle: z46.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
9979
- buyerPersona: z46.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
9980
- scriptNotes: z46.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
9981
- creativeDirection: z46.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
10467
+ confirm: z48.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
10468
+ clientRequestId: z48.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."),
10469
+ angle: z48.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
10470
+ buyerPersona: z48.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
10471
+ scriptNotes: z48.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
10472
+ creativeDirection: z48.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
9982
10473
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
9983
10474
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
9984
- generateAiMusic: z46.boolean().optional().describe("Opcional para video."),
9985
- nVariants: z46.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
10475
+ generateAiMusic: z48.boolean().optional().describe("Opcional para video."),
10476
+ nVariants: z48.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
9986
10477
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
9987
10478
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
9988
10479
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
9989
10480
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
9990
- styleMix: z46.boolean().optional().describe("Opcional para imagen."),
10481
+ styleMix: z48.boolean().optional().describe("Opcional para imagen."),
9991
10482
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
9992
- instructions: z46.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.")
10483
+ instructions: z48.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.")
9993
10484
  }).strict();
9994
10485
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
9995
10486
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
9996
10487
  ctx.addIssue({
9997
- code: z46.ZodIssueCode.custom,
10488
+ code: z48.ZodIssueCode.custom,
9998
10489
  path: [
9999
10490
  "angle"
10000
10491
  ],
@@ -10003,7 +10494,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
10003
10494
  }
10004
10495
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
10005
10496
  ctx.addIssue({
10006
- code: z46.ZodIssueCode.custom,
10497
+ code: z48.ZodIssueCode.custom,
10007
10498
  path: [
10008
10499
  "instructions"
10009
10500
  ],
@@ -10049,7 +10540,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
10049
10540
  for (const field of fieldsByKind[input.kind]) {
10050
10541
  if (input[field] !== void 0) {
10051
10542
  ctx.addIssue({
10052
- code: z46.ZodIssueCode.custom,
10543
+ code: z48.ZodIssueCode.custom,
10053
10544
  path: [
10054
10545
  field
10055
10546
  ],
@@ -10166,27 +10657,27 @@ function registerCreativeVariantTools(server, api) {
10166
10657
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
10167
10658
 
10168
10659
  // src/tools/script-review.ts
10169
- import { z as z47 } from "zod";
10170
- var GetVideoScriptInput = z47.object({
10171
- taskId: z47.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
10172
- angleKey: z47.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.')
10660
+ import { z as z49 } from "zod";
10661
+ var GetVideoScriptInput = z49.object({
10662
+ taskId: z49.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
10663
+ angleKey: z49.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.')
10173
10664
  }).strict();
10174
10665
  var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
10175
10666
  var VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2 = 2e3;
10176
- var ApproveSceneEdit = z47.object({
10177
- index: z47.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
10178
- dialogue: z47.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
10667
+ var ApproveSceneEdit = z49.object({
10668
+ index: z49.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
10669
+ dialogue: z49.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
10179
10670
  message: "dialogue no puede ser s\xF3lo espacios"
10180
10671
  }).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.")
10181
10672
  }).strict();
10182
- var ApproveVideoScriptInput = z47.object({
10183
- taskId: z47.string().min(1).describe("ID de la tarea."),
10184
- angleKey: z47.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.'),
10185
- scenes: z47.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.")
10673
+ var ApproveVideoScriptInput = z49.object({
10674
+ taskId: z49.string().min(1).describe("ID de la tarea."),
10675
+ angleKey: z49.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.'),
10676
+ scenes: z49.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.")
10186
10677
  }).strict();
10187
- var RegenerateVideoScriptInput = z47.object({
10188
- taskId: z47.string().min(1).describe("ID de la tarea."),
10189
- angleKey: z47.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.'),
10678
+ var RegenerateVideoScriptInput = z49.object({
10679
+ taskId: z49.string().min(1).describe("ID de la tarea."),
10680
+ angleKey: z49.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.'),
10190
10681
  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).')
10191
10682
  }).strict();
10192
10683
  function registerScriptReviewTools(server, api) {
@@ -10226,9 +10717,9 @@ function registerScriptReviewTools(server, api) {
10226
10717
  __name(registerScriptReviewTools, "registerScriptReviewTools");
10227
10718
 
10228
10719
  // src/tools/connections.ts
10229
- import { z as z48 } from "zod";
10720
+ import { z as z50 } from "zod";
10230
10721
  function registerConnectionTools(server, api) {
10231
- 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.", z48.object({}).shape, async () => {
10722
+ 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.", z50.object({}).shape, async () => {
10232
10723
  try {
10233
10724
  const r = await api.getJson("/v1/shopify/shops");
10234
10725
  return asText(r);
@@ -10236,10 +10727,10 @@ function registerConnectionTools(server, api) {
10236
10727
  throw translateError(e);
10237
10728
  }
10238
10729
  });
10239
- 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.', z48.object({
10240
- shopDomain: z48.string().min(3).max(120),
10241
- query: z48.string().trim().max(120).optional(),
10242
- taskId: z48.string().uuid().optional()
10730
+ 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.', z50.object({
10731
+ shopDomain: z50.string().min(3).max(120),
10732
+ query: z50.string().trim().max(120).optional(),
10733
+ taskId: z50.string().uuid().optional()
10243
10734
  }).shape, async ({ shopDomain, query, taskId }) => {
10244
10735
  try {
10245
10736
  const params = new URLSearchParams({
@@ -10253,7 +10744,7 @@ function registerConnectionTools(server, api) {
10253
10744
  throw translateError(e);
10254
10745
  }
10255
10746
  });
10256
- 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.", z48.object({}).shape, async () => {
10747
+ 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.", z50.object({}).shape, async () => {
10257
10748
  try {
10258
10749
  const r = await api.getJson("/v1/facebook/ad-accounts");
10259
10750
  return asText(r);
@@ -10261,7 +10752,21 @@ function registerConnectionTools(server, api) {
10261
10752
  throw translateError(e);
10262
10753
  }
10263
10754
  });
10264
- 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.", z48.object({}).shape, async () => {
10755
+ server.registerTool("list_meta_destinations", {
10756
+ description: "Lista los destinos de publicaci\xF3n de Meta del usuario: Business portfolios, cuentas publicitarias dentro de cada portfolio y Facebook Pages disponibles. Es obligatorio llamar esta herramienta antes de publicar, mostrar las opciones y enviar al publicador el `portfolioId`, `adAccountId` y `pageId` elegidos; nunca elijas silenciosamente la primera p\xE1gina o cuenta. No modifica Meta.",
10757
+ inputSchema: z50.object({}).shape,
10758
+ _meta: {
10759
+ "openai/widgetAccessible": true
10760
+ }
10761
+ }, async () => {
10762
+ try {
10763
+ const r = await api.getJson("/v1/facebook/destinations");
10764
+ return asText(r);
10765
+ } catch (e) {
10766
+ throw translateError(e);
10767
+ }
10768
+ });
10769
+ 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.", z50.object({}).shape, async () => {
10265
10770
  try {
10266
10771
  const r = await api.getJson("/v1/tiktok/advertisers");
10267
10772
  return asText(r);
@@ -10273,9 +10778,9 @@ function registerConnectionTools(server, api) {
10273
10778
  __name(registerConnectionTools, "registerConnectionTools");
10274
10779
 
10275
10780
  // src/tools/actors.ts
10276
- import { z as z49 } from "zod";
10781
+ import { z as z51 } from "zod";
10277
10782
  function registerActorTools(server, api) {
10278
- 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.", z49.object({}).shape, async () => {
10783
+ 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.", z51.object({}).shape, async () => {
10279
10784
  try {
10280
10785
  const rows = await api.getJson("/v1/actors");
10281
10786
  const actors = (Array.isArray(rows) ? rows : []).filter((actor) => actor.status === "ready").map(({ id, name, description, gender, voiceId }) => ({
@@ -10296,105 +10801,116 @@ function registerActorTools(server, api) {
10296
10801
  __name(registerActorTools, "registerActorTools");
10297
10802
 
10298
10803
  // src/tools/publish.ts
10299
- import { z as z50 } from "zod";
10300
- var ShopifyInput = z50.object({
10301
- taskId: z50.string().min(1),
10302
- shopDomain: z50.string().min(1),
10804
+ import { z as z52 } from "zod";
10805
+ var ShopifyInput = z52.object({
10806
+ taskId: z52.string().min(1),
10807
+ shopDomain: z52.string().min(1),
10303
10808
  /** Override: number of landing images to include. Default = ALL images the
10304
10809
  * task generated (the MCP fetches /v1/results first to count). Bounded
10305
10810
  * generously here; the backend validates each index against
10306
10811
  * landing.images.length and rejects out-of-range. */
10307
- numLandingImages: z50.number().int().min(1).max(20).optional(),
10308
- variantIndex: z50.number().int().min(0).optional(),
10812
+ numLandingImages: z52.number().int().min(1).max(20).optional(),
10813
+ variantIndex: z52.number().int().min(0).optional(),
10309
10814
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
10310
10815
  * When true, the landing renders variant-picker pills; when false, it ships
10311
10816
  * a single-option product page. Defaults to false. */
10312
- showVariantPicker: z50.boolean().optional(),
10817
+ showVariantPicker: z52.boolean().optional(),
10313
10818
  /** Default `auto` updates the product previously published for this task and
10314
10819
  * shop. Use `existing` with targetProductId to choose another product, or
10315
10820
  * `new` only when the user explicitly wants a duplicate/new listing. */
10316
- publicationMode: z50.enum([
10821
+ publicationMode: z52.enum([
10317
10822
  "auto",
10318
10823
  "existing",
10319
10824
  "new"
10320
10825
  ]).optional(),
10321
10826
  /** Shopify product GID, numeric id, or admin product URL. Required when
10322
10827
  * publicationMode is `existing`. */
10323
- targetProductId: z50.string().trim().min(1).max(300).optional()
10828
+ targetProductId: z52.string().trim().min(1).max(300).optional()
10324
10829
  });
10325
- var McpCampaignConfig = z50.object({
10830
+ var McpCampaignConfig = z52.object({
10326
10831
  /** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
10327
10832
  * converts to the account currency server-side. Do NOT pre-convert: on a COP
10328
10833
  * account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
10329
- dailyBudgetUsd: z50.number().positive().optional(),
10834
+ dailyBudgetUsd: z52.number().positive().optional(),
10330
10835
  /** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
10331
10836
  * ad set). TikTok takes only the first entry. */
10332
- targetCountries: z50.array(z50.string().length(2)).max(1).optional(),
10333
- campaignName: z50.string().trim().min(1).max(100).optional()
10837
+ targetCountries: z52.array(z52.string().length(2)).max(1).optional(),
10838
+ campaignName: z52.string().trim().min(1).max(100).optional()
10334
10839
  });
10335
10840
  var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
10336
- var MetaInput = z50.object({
10337
- taskId: z50.string().min(1),
10841
+ var MetaInput = z52.object({
10842
+ taskId: z52.string().min(1),
10843
+ /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
10844
+ portfolioId: z52.string().trim().min(1),
10845
+ /** Exact Facebook Page selected from `list_meta_destinations`. */
10846
+ pageId: z52.string().trim().min(1),
10338
10847
  adAccountId: McpAdAccountId,
10339
- pixelId: z50.string().min(1).optional(),
10340
- landingUrl: z50.string().url().optional(),
10848
+ pixelId: z52.string().min(1).optional(),
10849
+ landingUrl: z52.string().url().optional(),
10341
10850
  /** Select which angles to publish by 0-based index. Omit to publish all
10342
10851
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
10343
- angleIndexes: z50.array(z50.number().int().nonnegative()).optional(),
10852
+ angleIndexes: z52.array(z52.number().int().nonnegative()).optional(),
10344
10853
  campaignConfig: McpCampaignConfig.optional(),
10345
10854
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
10346
10855
  * uses the auto-generated copy attached to the task. Capped at 500 chars
10347
10856
  * (Meta's primary-text limit). */
10348
- primaryText: z50.string().trim().min(1).max(500).optional(),
10857
+ primaryText: z52.string().trim().min(1).max(500).optional(),
10349
10858
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
10350
10859
  * (wa.me) instead of a landing page. */
10351
- phoneNumber: z50.string().min(8).max(32).optional()
10860
+ phoneNumber: z52.string().min(8).max(32).optional()
10352
10861
  });
10353
- var GroupedTaskSource = z50.object({
10354
- type: z50.literal("task"),
10355
- taskId: z50.string().min(1),
10356
- angleIndex: z50.number().int().min(0).optional(),
10357
- videoVariant: z50.enum([
10862
+ var GroupedTaskSource = z52.object({
10863
+ type: z52.literal("task"),
10864
+ taskId: z52.string().min(1),
10865
+ angleIndex: z52.number().int().min(0).optional(),
10866
+ videoVariant: z52.enum([
10358
10867
  "with_subtitles",
10359
10868
  "without_subtitles"
10360
10869
  ]).optional(),
10361
- imageVariantIndex: z50.number().int().min(0).optional()
10362
- });
10363
- var GroupedMediaSource = z50.object({
10364
- type: z50.literal("media"),
10365
- url: z50.string().url().optional(),
10366
- path: z50.string().min(1).optional(),
10367
- thumbnailUrl: z50.string().url().optional(),
10368
- thumbnailPath: z50.string().min(1).optional()
10870
+ imageVariantIndex: z52.number().int().min(0).optional()
10871
+ });
10872
+ var GroupedMediaSource = z52.object({
10873
+ type: z52.literal("media"),
10874
+ url: z52.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."),
10875
+ path: z52.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".'),
10876
+ thumbnailUrl: z52.string().url().optional(),
10877
+ thumbnailPath: z52.string().min(1).optional()
10369
10878
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
10370
10879
  message: "media source needs exactly one of `url` or `path`"
10371
10880
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
10372
10881
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
10373
10882
  });
10374
- var GroupedCreative = z50.object({
10375
- type: z50.enum([
10883
+ var GroupedStagedMediaSource = z52.object({
10884
+ type: z52.literal("staged"),
10885
+ /** Returned by check_meta_media_upload after the user selects a local file. */
10886
+ stagingAssetId: z52.string().uuid(),
10887
+ thumbnailAssetId: z52.string().uuid().optional()
10888
+ });
10889
+ var GroupedCreative = z52.object({
10890
+ type: z52.enum([
10376
10891
  "image",
10377
10892
  "video"
10378
10893
  ]),
10379
- source: z50.union([
10894
+ source: z52.union([
10380
10895
  GroupedTaskSource,
10381
- GroupedMediaSource
10896
+ GroupedMediaSource,
10897
+ GroupedStagedMediaSource
10382
10898
  ]),
10383
- title: z50.string().max(120).optional(),
10384
- description: z50.string().max(500).optional(),
10899
+ title: z52.string().max(120).optional(),
10900
+ description: z52.string().max(500).optional(),
10385
10901
  cta: MetaCtaType.optional(),
10386
- creativeId: z50.string().min(1).optional()
10902
+ creativeId: z52.string().min(1).optional()
10387
10903
  });
10388
- var GroupedCampaignConfig = z50.object({
10904
+ var GroupedCampaignConfig = z52.object({
10389
10905
  campaignType: MetaCampaignType.default("sales"),
10390
10906
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
10391
10907
  budgetMode: MetaBudgetMode.default("campaign"),
10392
- campaignDailyBudget: z50.number().int().positive().optional(),
10393
- adSets: z50.array(z50.object({
10394
- tempId: z50.string().min(1).max(80),
10395
- name: z50.string().min(1).max(100),
10396
- dailyBudget: z50.number().int().positive().optional(),
10397
- creativeIds: z50.array(z50.string().min(1)).min(1)
10908
+ campaignDailyBudget: z52.number().int().positive().optional(),
10909
+ adSets: z52.array(z52.object({
10910
+ tempId: z52.string().min(1).max(80),
10911
+ name: z52.string().min(1).max(100),
10912
+ dailyBudget: z52.number().int().positive().optional(),
10913
+ creativeIds: z52.array(z52.string().min(1)).min(1)
10398
10914
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
10399
10915
  /**
10400
10916
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -10402,22 +10918,22 @@ var GroupedCampaignConfig = z50.object({
10402
10918
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
10403
10919
  * the API since #849 — unreachable from this tool.
10404
10920
  */
10405
- destination: z50.discriminatedUnion("type", [
10406
- z50.object({
10407
- type: z50.literal("landing"),
10408
- landingUrl: z50.string().url()
10921
+ destination: z52.discriminatedUnion("type", [
10922
+ z52.object({
10923
+ type: z52.literal("landing"),
10924
+ landingUrl: z52.string().url()
10409
10925
  }),
10410
- z50.object({
10411
- type: z50.literal("whatsapp"),
10412
- phoneNumber: z50.string().min(8).max(32)
10926
+ z52.object({
10927
+ type: z52.literal("whatsapp"),
10928
+ phoneNumber: z52.string().min(8).max(32)
10413
10929
  })
10414
10930
  ]),
10415
- saveAsDefault: z50.boolean().default(false),
10931
+ saveAsDefault: z52.boolean().default(false),
10416
10932
  budgetPolicy: MetaBudgetPolicy.optional()
10417
10933
  }).superRefine((value, ctx) => {
10418
10934
  if (value.budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
10419
10935
  ctx.addIssue({
10420
- code: z50.ZodIssueCode.custom,
10936
+ code: z52.ZodIssueCode.custom,
10421
10937
  path: [
10422
10938
  "campaignDailyBudget"
10423
10939
  ],
@@ -10428,7 +10944,7 @@ var GroupedCampaignConfig = z50.object({
10428
10944
  value.adSets.forEach((adSet, index) => {
10429
10945
  if (adSet.dailyBudget === void 0) {
10430
10946
  ctx.addIssue({
10431
- code: z50.ZodIssueCode.custom,
10947
+ code: z52.ZodIssueCode.custom,
10432
10948
  path: [
10433
10949
  "adSets",
10434
10950
  index,
@@ -10440,22 +10956,26 @@ var GroupedCampaignConfig = z50.object({
10440
10956
  });
10441
10957
  }
10442
10958
  });
10443
- var PublishCreativesToMetaInputBase = z50.object({
10444
- creatives: z50.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10959
+ var PublishCreativesToMetaInputBase = z52.object({
10960
+ creatives: z52.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10961
+ /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
10962
+ portfolioId: z52.string().trim().min(1),
10963
+ /** Exact Facebook Page selected from `list_meta_destinations`. */
10964
+ pageId: z52.string().trim().min(1),
10445
10965
  adAccountId: McpAdAccountId,
10446
- pixelId: z50.string().min(1).optional(),
10966
+ pixelId: z52.string().min(1).optional(),
10447
10967
  /** Required unless `campaignConfig.destination` is whatsapp — see the
10448
10968
  * superRefine below. Mirrors the API's `UploadToMetaRequest`. */
10449
- landingUrl: z50.string().url().optional(),
10450
- targetCountry: z50.string().length(2),
10451
- campaignName: z50.string().trim().min(1).max(100),
10969
+ landingUrl: z52.string().url().optional(),
10970
+ targetCountry: z52.string().length(2),
10971
+ campaignName: z52.string().trim().min(1).max(100),
10452
10972
  campaignConfig: GroupedCampaignConfig.optional()
10453
10973
  });
10454
10974
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
10455
10975
  const destination = value.campaignConfig?.destination;
10456
10976
  if (destination?.type !== "whatsapp" && !value.landingUrl) {
10457
10977
  ctx.addIssue({
10458
- code: z50.ZodIssueCode.custom,
10978
+ code: z52.ZodIssueCode.custom,
10459
10979
  path: [
10460
10980
  "landingUrl"
10461
10981
  ],
@@ -10464,7 +10984,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10464
10984
  }
10465
10985
  if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
10466
10986
  ctx.addIssue({
10467
- code: z50.ZodIssueCode.custom,
10987
+ code: z52.ZodIssueCode.custom,
10468
10988
  path: [
10469
10989
  "campaignConfig",
10470
10990
  "destination",
@@ -10474,46 +10994,50 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10474
10994
  });
10475
10995
  }
10476
10996
  });
10477
- var PresignUploadResponseSchema = z50.object({
10478
- uploadUrl: z50.string().url().refine((value) => {
10997
+ var PresignUploadResponseSchema = z52.object({
10998
+ uploadUrl: z52.string().url().refine((value) => {
10479
10999
  const url = new URL(value);
10480
11000
  return !url.username && !url.password && url.protocol === "https:";
10481
11001
  }, "presigned upload URL must be HTTPS without embedded credentials"),
10482
- publicUrl: z50.string().url().refine((value) => {
11002
+ publicUrl: z52.string().url().refine((value) => {
10483
11003
  const url = new URL(value);
10484
11004
  return !url.username && !url.password;
10485
- }, "public media URL must not contain embedded credentials")
11005
+ }, "public media URL must not contain embedded credentials"),
11006
+ stagingAssetId: z52.string().uuid()
10486
11007
  });
10487
- async function uploadResolvedMedia(api, media) {
10488
- if (media.url) return media.url;
11008
+ async function uploadResolvedMedia(api, media, assetRole) {
10489
11009
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
10490
11010
  const presigned = PresignUploadResponseSchema.parse(await api.postJson("/v1/ads/presign-upload", {
10491
11011
  contentType: media.mimetype,
10492
11012
  fileName: media.filename,
10493
- fileSize: media.buffer.byteLength
11013
+ fileSize: media.buffer.byteLength,
11014
+ purpose: "meta_staging",
11015
+ assetRole
10494
11016
  }));
10495
11017
  await api.putBytes(presigned.uploadUrl, media.buffer, media.mimetype);
10496
- return presigned.publicUrl;
11018
+ return {
11019
+ stagingAssetId: presigned.stagingAssetId
11020
+ };
10497
11021
  }
10498
11022
  __name(uploadResolvedMedia, "uploadResolvedMedia");
10499
- async function resolveAndUploadMedia(api, ref, expectedKind, transport) {
11023
+ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transport) {
10500
11024
  const media = await resolveMediaRef(ref, {
10501
11025
  transport,
10502
11026
  expectedKind
10503
11027
  });
10504
- return uploadResolvedMedia(api, media);
11028
+ return uploadResolvedMedia(api, media, assetRole);
10505
11029
  }
10506
11030
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
10507
- var TikTokInput = z50.object({
10508
- taskId: z50.string().min(1),
10509
- advertiserId: z50.string().min(1),
10510
- pixelId: z50.string().min(1).optional(),
10511
- landingUrl: z50.string().url().optional(),
10512
- angleIndexes: z50.array(z50.number().int().nonnegative()).optional(),
11031
+ var TikTokInput = z52.object({
11032
+ taskId: z52.string().min(1),
11033
+ advertiserId: z52.string().min(1),
11034
+ pixelId: z52.string().min(1).optional(),
11035
+ landingUrl: z52.string().url().optional(),
11036
+ angleIndexes: z52.array(z52.number().int().nonnegative()).optional(),
10513
11037
  campaignConfig: McpCampaignConfig.optional(),
10514
11038
  /** Override the ad's description (TikTok's body copy). If omitted,
10515
11039
  * ProductMaker uses the auto-generated copy attached to the task. */
10516
- primaryText: z50.string().trim().min(1).max(500).optional()
11040
+ primaryText: z52.string().trim().min(1).max(500).optional()
10517
11041
  });
10518
11042
  function registerPublishTools(server, api, opts = {}) {
10519
11043
  const transport = opts.transport ?? "http";
@@ -10552,7 +11076,7 @@ function registerPublishTools(server, api, opts = {}) {
10552
11076
  throw translateError(e);
10553
11077
  }
10554
11078
  });
10555
- 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 }) => {
11079
+ 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 }) => {
10556
11080
  try {
10557
11081
  const body = {
10558
11082
  ...rest
@@ -10580,7 +11104,7 @@ function registerPublishTools(server, api, opts = {}) {
10580
11104
  throw translateError(e);
10581
11105
  }
10582
11106
  });
10583
- 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) => {
11107
+ 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) => {
10584
11108
  try {
10585
11109
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
10586
11110
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -10606,25 +11130,44 @@ function registerPublishTools(server, api, opts = {}) {
10606
11130
  });
10607
11131
  continue;
10608
11132
  }
10609
- const mediaUrl = await resolveAndUploadMedia(api, {
11133
+ if (creative.source.type === "staged") {
11134
+ creatives.push({
11135
+ type: creative.type,
11136
+ source: {
11137
+ type: "media",
11138
+ mediaAssetId: creative.source.stagingAssetId,
11139
+ ...creative.source.thumbnailAssetId ? {
11140
+ thumbnailAssetId: creative.source.thumbnailAssetId
11141
+ } : {}
11142
+ },
11143
+ title: creative.title,
11144
+ description: creative.description,
11145
+ cta: creative.cta,
11146
+ creativeId
11147
+ });
11148
+ continue;
11149
+ }
11150
+ const stagedMedia = await resolveAndUploadMedia(api, {
10610
11151
  url: creative.source.url,
10611
11152
  path: creative.source.path
10612
- }, creative.type, transport);
10613
- let thumbnailUrl;
11153
+ }, creative.type, "media", transport);
11154
+ let stagedThumbnail;
10614
11155
  if (creative.type === "video") {
10615
11156
  if (creative.source.thumbnailUrl || creative.source.thumbnailPath) {
10616
- thumbnailUrl = await resolveAndUploadMedia(api, {
11157
+ stagedThumbnail = await resolveAndUploadMedia(api, {
10617
11158
  url: creative.source.thumbnailUrl,
10618
11159
  path: creative.source.thumbnailPath
10619
- }, "image", transport);
11160
+ }, "image", "thumbnail", transport);
10620
11161
  }
10621
11162
  }
10622
11163
  creatives.push({
10623
11164
  type: creative.type,
10624
11165
  source: {
10625
11166
  type: "media",
10626
- mediaUrl,
10627
- thumbnailUrl
11167
+ mediaAssetId: stagedMedia.stagingAssetId,
11168
+ ...stagedThumbnail ? {
11169
+ thumbnailAssetId: stagedThumbnail.stagingAssetId
11170
+ } : {}
10628
11171
  },
10629
11172
  title: creative.title,
10630
11173
  description: creative.description,
@@ -10634,6 +11177,8 @@ function registerPublishTools(server, api, opts = {}) {
10634
11177
  }
10635
11178
  const r = await api.postJson("/v1/facebook/campaigns/from-tasks", {
10636
11179
  creatives,
11180
+ portfolioId: input.portfolioId,
11181
+ pageId: input.pageId,
10637
11182
  adAccountId: input.adAccountId,
10638
11183
  pixelId: input.pixelId,
10639
11184
  landingUrl: input.landingUrl,
@@ -10680,6 +11225,17 @@ function renderShopifyPublishMarkdown(r) {
10680
11225
  if (r.productUrl) lines.push(`
10681
11226
  ### Producto
10682
11227
  [Ver producto](${r.productUrl})`);
11228
+ if (r.needsThemeSetup) {
11229
+ lines.push(r.themeEditorDeepLink ? `
11230
+ \u26A0\uFE0F La landing est\xE1 publicada, pero falta activar el bloque Code Landing en el tema. [Abrir editor de Shopify](${r.themeEditorDeepLink}) y guardar una vez.` : "\n\u26A0\uFE0F La landing est\xE1 publicada, pero falta activar el bloque Code Landing en el tema. Abre el editor de Shopify, agrega el bloque y guarda una vez.");
11231
+ }
11232
+ if (r.tracking?.status === "migrated") {
11233
+ lines.push("\nTracking: \u2705 configuraci\xF3n anterior migrada autom\xE1ticamente. No necesitas editar el tema.");
11234
+ } else if (r.tracking?.status === "synced") {
11235
+ lines.push("\nTracking: \u2705 c\xF3digo manual sincronizado desde `theme.liquid` al layout de ProductMaker.");
11236
+ } else if (r.tracking?.status === "not_detected") {
11237
+ lines.push("\nTracking: \u2705 ProductMaker dej\xF3 la landing compatible con el tracking nativo de Shopify; no se encontr\xF3 c\xF3digo manual para migrar.");
11238
+ }
10683
11239
  if (!r.pageUrl && !r.productUrl && r.status !== "success") {
10684
11240
  lines.push("\n_La publicaci\xF3n a\xFAn est\xE1 proces\xE1ndose. Usa `get_task_status` para verificar progreso._");
10685
11241
  }
@@ -10723,14 +11279,162 @@ function toTikTokCampaignConfig(cfg) {
10723
11279
  }
10724
11280
  __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
10725
11281
 
11282
+ // src/tools/meta-upload.ts
11283
+ import { z as z53 } from "zod";
11284
+ var MetaUploadSessionInputBase = z53.object({
11285
+ mediaKind: z53.enum([
11286
+ "image",
11287
+ "video",
11288
+ "mixed"
11289
+ ]).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."),
11290
+ assetRole: z53.enum([
11291
+ "media",
11292
+ "thumbnail"
11293
+ ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
11294
+ maxFiles: z53.number().int().min(1).max(10).optional().describe("Optional batch limit. Use 1 for a single creative; mixed media defaults to 10.")
11295
+ });
11296
+ var OpenAiFileReference = z53.object({
11297
+ download_url: z53.string().url(),
11298
+ file_id: z53.string().min(1),
11299
+ mime_type: z53.string().min(1).optional(),
11300
+ file_name: z53.string().min(1).optional()
11301
+ }).strict();
11302
+ var MetaUploadSessionToolInputShape = {
11303
+ ...MetaUploadSessionInputBase.shape,
11304
+ files: z53.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.")
11305
+ };
11306
+ var MetaUploadSessionToolInput = z53.object(MetaUploadSessionToolInputShape).superRefine((value, ctx) => {
11307
+ if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
11308
+ ctx.addIssue({
11309
+ code: z53.ZodIssueCode.custom,
11310
+ path: [
11311
+ "mediaKind"
11312
+ ],
11313
+ message: "Meta thumbnails must be images"
11314
+ });
11315
+ }
11316
+ if (value.mediaKind !== "mixed" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
11317
+ ctx.addIssue({
11318
+ code: z53.ZodIssueCode.custom,
11319
+ path: [
11320
+ "maxFiles"
11321
+ ],
11322
+ message: "Image and video sessions accept exactly one file"
11323
+ });
11324
+ }
11325
+ });
11326
+ var MetaUploadSessionResponse2 = z53.object({
11327
+ sessionId: z53.string().uuid(),
11328
+ uploadPageUrl: z53.string().url(),
11329
+ mediaKind: z53.enum([
11330
+ "image",
11331
+ "video",
11332
+ "mixed"
11333
+ ]),
11334
+ assetRole: z53.enum([
11335
+ "media",
11336
+ "thumbnail"
11337
+ ]),
11338
+ maxFiles: z53.number().int().positive(),
11339
+ expiresAt: z53.string().datetime()
11340
+ });
11341
+ var MetaUploadStatusInput = z53.object({
11342
+ sessionId: z53.string().uuid().describe("The sessionId returned by request_meta_media_upload.")
11343
+ });
11344
+ var MetaUploadStatusResponse = z53.object({
11345
+ sessionId: z53.string().uuid(),
11346
+ status: z53.enum([
11347
+ "pending",
11348
+ "uploaded"
11349
+ ]),
11350
+ assets: z53.array(z53.object({
11351
+ stagingAssetId: z53.string().uuid(),
11352
+ mediaKind: z53.enum([
11353
+ "image",
11354
+ "video"
11355
+ ])
11356
+ })).max(10).default([]),
11357
+ stagingAssetId: z53.string().uuid().optional()
11358
+ });
11359
+ function uploadSessionText(result) {
11360
+ const mediaLabel = result.mediaKind === "mixed" ? "archivos" : result.mediaKind === "video" ? "video" : "imagen";
11361
+ return {
11362
+ content: [
11363
+ {
11364
+ type: "text",
11365
+ text: [
11366
+ `### Prepara tu ${mediaLabel} en ProductMaker`,
11367
+ `Usa primero el App inline de este chat. Si tu host no soporta el bridge de archivos, usa este fallback: [Abrir carga segura](${result.uploadPageUrl})`,
11368
+ `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.`,
11369
+ `sessionId: \`${result.sessionId}\``,
11370
+ `Este enlace expira: ${result.expiresAt}`
11371
+ ].join("\n\n")
11372
+ }
11373
+ ]
11374
+ };
11375
+ }
11376
+ __name(uploadSessionText, "uploadSessionText");
11377
+ function registerMetaUploadTools(server, api) {
11378
+ server.registerTool("request_meta_media_upload", {
11379
+ title: "Solicitar carga segura de Meta",
11380
+ 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. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El flujo principal es el MCP App inline; el enlace externo de ProductMaker queda como fallback para hosts sin bridge de archivos. Despu\xE9s conserva el `sessionId` y llama `check_meta_media_upload`. Esta herramienta no publica ni consume cr\xE9ditos.",
11381
+ inputSchema: MetaUploadSessionToolInputShape,
11382
+ outputSchema: MetaUploadSessionResponse2,
11383
+ _meta: {
11384
+ ui: {
11385
+ resourceUri: META_UPLOAD_APP_RESOURCE_URI,
11386
+ visibility: [
11387
+ "model",
11388
+ "app"
11389
+ ]
11390
+ },
11391
+ "openai/outputTemplate": META_UPLOAD_APP_RESOURCE_URI,
11392
+ "openai/widgetAccessible": true,
11393
+ "openai/fileParams": [
11394
+ "files"
11395
+ ],
11396
+ "openai/toolInvocation/invoking": "Preparando carga segura\u2026",
11397
+ "openai/toolInvocation/invoked": "Carga segura lista"
11398
+ }
11399
+ }, async (rawInput) => {
11400
+ try {
11401
+ const input = MetaUploadSessionToolInput.parse(rawInput);
11402
+ const { files: _files, ...sessionInput } = input;
11403
+ const result = MetaUploadSessionResponse2.parse(await api.postJson("/v1/ads/meta-upload-sessions", sessionInput));
11404
+ return {
11405
+ structuredContent: result,
11406
+ ...uploadSessionText(result)
11407
+ };
11408
+ } catch (error) {
11409
+ throw translateError(error);
11410
+ }
11411
+ });
11412
+ server.registerTool("check_meta_media_upload", {
11413
+ description: '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.',
11414
+ inputSchema: MetaUploadStatusInput.shape,
11415
+ _meta: {
11416
+ "openai/widgetAccessible": true
11417
+ }
11418
+ }, async (rawInput) => {
11419
+ try {
11420
+ const input = MetaUploadStatusInput.parse(rawInput);
11421
+ const result = MetaUploadStatusResponse.parse(await api.getJson(`/v1/ads/meta-upload-sessions/${encodeURIComponent(input.sessionId)}`));
11422
+ return asText(result);
11423
+ } catch (error) {
11424
+ throw translateError(error);
11425
+ }
11426
+ });
11427
+ }
11428
+ __name(registerMetaUploadTools, "registerMetaUploadTools");
11429
+
10726
11430
  // src/tools/campaigns.ts
10727
- import { z as z51 } from "zod";
10728
- var MetaStatusInput = z51.object({
10729
- campaignId: z51.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
11431
+ import { z as z54 } from "zod";
11432
+ var MetaStatusInput = z54.object({
11433
+ campaignId: z54.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
10730
11434
  });
10731
- var TikTokStatusInput = z51.object({
10732
- campaignId: z51.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
10733
- advertiserId: z51.string().min(1).describe("The same advertiserId used to publish the campaign.")
11435
+ var TikTokStatusInput = z54.object({
11436
+ campaignId: z54.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
11437
+ advertiserId: z54.string().min(1).describe("The same advertiserId used to publish the campaign.")
10734
11438
  });
10735
11439
  function registerCampaignTools(server, api) {
10736
11440
  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) => {
@@ -10827,10 +11531,10 @@ function renderTikTokStatusMarkdown(r) {
10827
11531
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
10828
11532
 
10829
11533
  // ../../packages/product-economics/src/product-costing.ts
10830
- import { z as z53 } from "zod";
11534
+ import { z as z56 } from "zod";
10831
11535
 
10832
11536
  // ../../packages/product-economics/src/product-costing-countries.ts
10833
- import { z as z52 } from "zod";
11537
+ import { z as z55 } from "zod";
10834
11538
 
10835
11539
  // ../../packages/product-economics/src/product-costing-shipping-references.json
10836
11540
  var product_costing_shipping_references_default = {
@@ -10894,21 +11598,21 @@ var product_costing_shipping_references_default = {
10894
11598
  };
10895
11599
 
10896
11600
  // ../../packages/product-economics/src/product-costing-countries.ts
10897
- var ShippingReferenceSchema = z52.object({
10898
- reference: z52.number().positive(),
10899
- min: z52.number().nonnegative(),
10900
- max: z52.number().positive(),
10901
- updatedAt: z52.string().date(),
10902
- confidence: z52.enum([
11601
+ var ShippingReferenceSchema = z55.object({
11602
+ reference: z55.number().positive(),
11603
+ min: z55.number().nonnegative(),
11604
+ max: z55.number().positive(),
11605
+ updatedAt: z55.string().date(),
11606
+ confidence: z55.enum([
10903
11607
  "high",
10904
11608
  "medium",
10905
11609
  "medium-low"
10906
11610
  ]),
10907
- sources: z52.array(z52.string().url().startsWith("https://")).min(1).max(3)
11611
+ sources: z55.array(z55.string().url().startsWith("https://")).min(1).max(3)
10908
11612
  }).superRefine(({ reference, min, max }, context) => {
10909
11613
  if (min > reference || reference > max) {
10910
11614
  context.addIssue({
10911
- code: z52.ZodIssueCode.custom,
11615
+ code: z55.ZodIssueCode.custom,
10912
11616
  message: "shipping_reference_outside_range",
10913
11617
  path: [
10914
11618
  "reference"
@@ -10916,7 +11620,7 @@ var ShippingReferenceSchema = z52.object({
10916
11620
  });
10917
11621
  }
10918
11622
  });
10919
- var SHIPPING_REFERENCES = z52.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
11623
+ var SHIPPING_REFERENCES = z55.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
10920
11624
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
10921
11625
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
10922
11626
  "MX",
@@ -11098,18 +11802,18 @@ function getProductCostingCountryName(country) {
11098
11802
  __name(getProductCostingCountryName, "getProductCostingCountryName");
11099
11803
 
11100
11804
  // ../../packages/product-economics/src/product-costing.ts
11101
- var ProductCostingInputSchema = z53.object({
11102
- country: z53.string().refine(isProductCostingCountryCode, "unsupported_country"),
11103
- supplierCost: z53.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
11104
- shippingCost: z53.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
11105
- returnRate: z53.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11106
- cancellationRate: z53.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11107
- adsManagerCpa: z53.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
11108
- desiredMargin: z53.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
11805
+ var ProductCostingInputSchema = z56.object({
11806
+ country: z56.string().refine(isProductCostingCountryCode, "unsupported_country"),
11807
+ supplierCost: z56.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
11808
+ shippingCost: z56.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
11809
+ returnRate: z56.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11810
+ cancellationRate: z56.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11811
+ adsManagerCpa: z56.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
11812
+ desiredMargin: z56.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
11109
11813
  }).superRefine(({ returnRate, cancellationRate }, context) => {
11110
11814
  if (returnRate + cancellationRate >= 1) {
11111
11815
  context.addIssue({
11112
- code: z53.ZodIssueCode.custom,
11816
+ code: z56.ZodIssueCode.custom,
11113
11817
  message: "delivery_rate_positive",
11114
11818
  path: [
11115
11819
  "cancellationRate"
@@ -11176,14 +11880,14 @@ function calculateProductCosting(rawInput) {
11176
11880
  __name(calculateProductCosting, "calculateProductCosting");
11177
11881
 
11178
11882
  // src/tools/product-costing.ts
11179
- import { z as z54 } from "zod";
11180
- var optionalAmount = /* @__PURE__ */ __name((description) => z54.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
11181
- var optionalPercent = /* @__PURE__ */ __name((description) => z54.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
11182
- var ProductCostingToolInput = z54.object({
11183
- country: z54.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
11883
+ import { z as z57 } from "zod";
11884
+ var optionalAmount = /* @__PURE__ */ __name((description) => z57.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
11885
+ var optionalPercent = /* @__PURE__ */ __name((description) => z57.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
11886
+ var ProductCostingToolInput = z57.object({
11887
+ country: z57.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
11184
11888
  message: "Pa\xEDs no soportado por ProductMaker."
11185
11889
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
11186
- supplierCost: z54.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."),
11890
+ supplierCost: z57.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."),
11187
11891
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
11188
11892
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
11189
11893
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -11315,7 +12019,7 @@ function formatPercent(value) {
11315
12019
  __name(formatPercent, "formatPercent");
11316
12020
 
11317
12021
  // src/tools/product-research.ts
11318
- import { z as z55 } from "zod";
12022
+ import { z as z58 } from "zod";
11319
12023
 
11320
12024
  // src/tools/product-research-idempotency.ts
11321
12025
  import { createHash as createHash2 } from "crypto";
@@ -11431,17 +12135,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
11431
12135
  ]);
11432
12136
  var POLL_INTERVAL_MS = 5e3;
11433
12137
  var MAX_WAIT_SECONDS = 120;
11434
- var FindInput = z55.object({
12138
+ var FindInput = z58.object({
11435
12139
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
11436
12140
  categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
11437
- confirmed: z55.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
11438
- waitSeconds: z55.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
12141
+ confirmed: z58.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
12142
+ waitSeconds: z58.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
11439
12143
  });
11440
- var GetInput = z55.object({
11441
- runId: z55.string().uuid().describe("ID del run devuelto por find_winning_products.")
12144
+ var GetInput = z58.object({
12145
+ runId: z58.string().uuid().describe("ID del run devuelto por find_winning_products.")
11442
12146
  });
11443
- var ListInput = z55.object({
11444
- limit: z55.number().int().min(1).max(20).default(10)
12147
+ var ListInput = z58.object({
12148
+ limit: z58.number().int().min(1).max(20).default(10)
11445
12149
  });
11446
12150
  function textResult(text) {
11447
12151
  return {
@@ -11533,14 +12237,14 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
11533
12237
  __name(registerProductResearchTools, "registerProductResearchTools");
11534
12238
 
11535
12239
  // src/tools/dropshipping-projects.ts
11536
- import { z as z56 } from "zod";
11537
- var ProjectId = z56.string().uuid();
11538
- var DropshippingProjectStatus = z56.enum([
12240
+ import { z as z59 } from "zod";
12241
+ var ProjectId = z59.string().uuid();
12242
+ var DropshippingProjectStatus = z59.enum([
11539
12243
  "active",
11540
12244
  "completed",
11541
12245
  "archived"
11542
12246
  ]);
11543
- var DropshippingProjectPhase = z56.enum([
12247
+ var DropshippingProjectPhase = z59.enum([
11544
12248
  "research",
11545
12249
  "costing",
11546
12250
  "media",
@@ -11549,21 +12253,21 @@ var DropshippingProjectPhase = z56.enum([
11549
12253
  "advertising",
11550
12254
  "complete"
11551
12255
  ]);
11552
- var DropshippingRewardMilestone = z56.enum([
12256
+ var DropshippingRewardMilestone = z59.enum([
11553
12257
  "product_selected",
11554
12258
  "costing_saved",
11555
12259
  "product_media_ready",
11556
12260
  "creatives_completed",
11557
12261
  "campaign_created"
11558
12262
  ]);
11559
- var TargetCountry = z56.enum([
12263
+ var TargetCountry = z59.enum([
11560
12264
  "CO",
11561
12265
  "MX",
11562
12266
  "PE",
11563
12267
  "EC",
11564
12268
  "CL"
11565
12269
  ]);
11566
- var ResearchCategory = z56.enum([
12270
+ var ResearchCategory = z59.enum([
11567
12271
  "auto",
11568
12272
  "automotive",
11569
12273
  "kitchen",
@@ -11578,7 +12282,7 @@ var ResearchCategory = z56.enum([
11578
12282
  "garden",
11579
12283
  "cleaning"
11580
12284
  ]);
11581
- var HttpUrl3 = z56.string().url().max(2e3).refine((value) => {
12285
+ var HttpUrl3 = z59.string().url().max(2e3).refine((value) => {
11582
12286
  try {
11583
12287
  return [
11584
12288
  "http:",
@@ -11588,46 +12292,46 @@ var HttpUrl3 = z56.string().url().max(2e3).refine((value) => {
11588
12292
  return false;
11589
12293
  }
11590
12294
  }, "URL must use HTTP(S)");
11591
- var ResearchCandidateOrigin = z56.object({
11592
- type: z56.literal("research_candidate"),
12295
+ var ResearchCandidateOrigin = z59.object({
12296
+ type: z59.literal("research_candidate"),
11593
12297
  runId: ProjectId,
11594
- candidateSourceId: z56.string().min(1).max(160)
12298
+ candidateSourceId: z59.string().min(1).max(160)
11595
12299
  });
11596
- var TaskOrigin = z56.object({
11597
- type: z56.literal("task"),
12300
+ var TaskOrigin = z59.object({
12301
+ type: z59.literal("task"),
11598
12302
  taskId: ProjectId
11599
12303
  });
11600
- var ManualOrigin = z56.object({
11601
- type: z56.literal("manual"),
12304
+ var ManualOrigin = z59.object({
12305
+ type: z59.literal("manual"),
11602
12306
  clientRequestId: ProjectId,
11603
- country: z56.string().min(2).max(3).optional()
12307
+ country: z59.string().min(2).max(3).optional()
11604
12308
  });
11605
- var StartProjectInput = z56.object({
11606
- origin: z56.discriminatedUnion("type", [
12309
+ var StartProjectInput = z59.object({
12310
+ origin: z59.discriminatedUnion("type", [
11607
12311
  ResearchCandidateOrigin,
11608
12312
  TaskOrigin,
11609
12313
  ManualOrigin
11610
12314
  ]),
11611
- productName: z56.string().trim().min(1).max(240).optional(),
12315
+ productName: z59.string().trim().min(1).max(240).optional(),
11612
12316
  productUrl: HttpUrl3.optional(),
11613
- country: z56.string().min(2).max(3).optional(),
11614
- languageCode: z56.string().min(2).max(16).optional()
12317
+ country: z59.string().min(2).max(3).optional(),
12318
+ languageCode: z59.string().min(2).max(16).optional()
11615
12319
  }).strict();
11616
- var CostProjectInput = z56.object({
12320
+ var CostProjectInput = z59.object({
11617
12321
  projectId: ProjectId,
11618
- version: z56.number().int().positive(),
11619
- country: z56.string().trim().min(2).max(3),
11620
- supplierCost: z56.number().finite().positive(),
11621
- shippingCost: z56.number().finite().nonnegative(),
11622
- returnRate: z56.number().finite().min(0).lt(1),
11623
- cancellationRate: z56.number().finite().min(0).lt(1),
11624
- adsManagerCpa: z56.number().finite().nonnegative(),
11625
- desiredMargin: z56.number().finite().min(0).lt(1)
11626
- });
11627
- var GuideInput = z56.object({
12322
+ version: z59.number().int().positive(),
12323
+ country: z59.string().trim().min(2).max(3),
12324
+ supplierCost: z59.number().finite().positive(),
12325
+ shippingCost: z59.number().finite().nonnegative(),
12326
+ returnRate: z59.number().finite().min(0).lt(1),
12327
+ cancellationRate: z59.number().finite().min(0).lt(1),
12328
+ adsManagerCpa: z59.number().finite().nonnegative(),
12329
+ desiredMargin: z59.number().finite().min(0).lt(1)
12330
+ });
12331
+ var GuideInput = z59.object({
11628
12332
  projectId: ProjectId.optional()
11629
12333
  }).strict();
11630
- var DropshippingProjectNextAction = z56.discriminatedUnion("kind", [
12334
+ var DropshippingProjectNextAction = z59.discriminatedUnion("kind", [
11631
12335
  "cost_product",
11632
12336
  "provide_product_media",
11633
12337
  "generate_creatives",
@@ -11641,78 +12345,78 @@ var DropshippingProjectNextAction = z56.discriminatedUnion("kind", [
11641
12345
  "create_meta_campaign",
11642
12346
  "repair_meta_campaign",
11643
12347
  "review_paused_campaign"
11644
- ].map((kind) => z56.object({
11645
- kind: z56.literal(kind),
11646
- href: z56.string().min(1)
12348
+ ].map((kind) => z59.object({
12349
+ kind: z59.literal(kind),
12350
+ href: z59.string().min(1)
11647
12351
  })));
11648
- var CostingInputSchema = z56.object({
11649
- country: z56.string().min(2).max(3),
11650
- supplierCost: z56.number().finite().positive(),
11651
- shippingCost: z56.number().finite().nonnegative(),
11652
- returnRate: z56.number().finite().min(0).lt(1),
11653
- cancellationRate: z56.number().finite().min(0).lt(1),
11654
- adsManagerCpa: z56.number().finite().nonnegative(),
11655
- desiredMargin: z56.number().finite().min(0).lt(1)
12352
+ var CostingInputSchema = z59.object({
12353
+ country: z59.string().min(2).max(3),
12354
+ supplierCost: z59.number().finite().positive(),
12355
+ shippingCost: z59.number().finite().nonnegative(),
12356
+ returnRate: z59.number().finite().min(0).lt(1),
12357
+ cancellationRate: z59.number().finite().min(0).lt(1),
12358
+ adsManagerCpa: z59.number().finite().nonnegative(),
12359
+ desiredMargin: z59.number().finite().min(0).lt(1)
11656
12360
  }).superRefine(({ returnRate, cancellationRate }, context) => {
11657
12361
  if (returnRate + cancellationRate >= 1) context.addIssue({
11658
- code: z56.ZodIssueCode.custom,
12362
+ code: z59.ZodIssueCode.custom,
11659
12363
  path: [
11660
12364
  "cancellationRate"
11661
12365
  ]
11662
12366
  });
11663
12367
  });
11664
- var CostingResultSchema = z56.object({
11665
- country: z56.string().min(2).max(3),
11666
- deliveryRate: z56.number().finite(),
11667
- adjustedShippingCost: z56.number().finite(),
11668
- adjustedCpa: z56.number().finite(),
11669
- totalCost: z56.number().finite(),
11670
- suggestedPrice: z56.number().finite(),
11671
- baseCommercialPrice: z56.number().finite(),
11672
- minimumSafePrice: z56.number().finite(),
11673
- commercialPrice: z56.number().finite(),
11674
- comparisonPrice: z56.number().finite(),
11675
- estimatedProfit: z56.number().finite(),
11676
- actualMargin: z56.number().finite(),
11677
- breakEvenAdsManagerCpa: z56.number().finite(),
11678
- wasPriceAutoAdjusted: z56.boolean(),
11679
- profitabilityStatus: z56.literal("healthy")
11680
- });
11681
- var RewardProgressSchema = z56.object({
11682
- campaignKey: z56.literal("dropshipping-launch-v1"),
11683
- totalCredits: z56.number().int().positive(),
11684
- earnedCredits: z56.number().int().nonnegative(),
11685
- milestones: z56.array(z56.object({
12368
+ var CostingResultSchema = z59.object({
12369
+ country: z59.string().min(2).max(3),
12370
+ deliveryRate: z59.number().finite(),
12371
+ adjustedShippingCost: z59.number().finite(),
12372
+ adjustedCpa: z59.number().finite(),
12373
+ totalCost: z59.number().finite(),
12374
+ suggestedPrice: z59.number().finite(),
12375
+ baseCommercialPrice: z59.number().finite(),
12376
+ minimumSafePrice: z59.number().finite(),
12377
+ commercialPrice: z59.number().finite(),
12378
+ comparisonPrice: z59.number().finite(),
12379
+ estimatedProfit: z59.number().finite(),
12380
+ actualMargin: z59.number().finite(),
12381
+ breakEvenAdsManagerCpa: z59.number().finite(),
12382
+ wasPriceAutoAdjusted: z59.boolean(),
12383
+ profitabilityStatus: z59.literal("healthy")
12384
+ });
12385
+ var RewardProgressSchema = z59.object({
12386
+ campaignKey: z59.literal("dropshipping-launch-v1"),
12387
+ totalCredits: z59.number().int().positive(),
12388
+ earnedCredits: z59.number().int().nonnegative(),
12389
+ milestones: z59.array(z59.object({
11686
12390
  milestone: DropshippingRewardMilestone,
11687
- credits: z56.number().int().positive(),
11688
- available: z56.boolean().optional(),
11689
- earned: z56.boolean(),
12391
+ credits: z59.number().int().positive(),
12392
+ available: z59.boolean().optional(),
12393
+ earned: z59.boolean(),
11690
12394
  grantId: ProjectId.nullable(),
11691
- grantedAt: z56.string().datetime().nullable()
12395
+ grantedAt: z59.string().datetime().nullable()
11692
12396
  })).length(DropshippingRewardMilestone.options.length)
11693
12397
  });
11694
- var ProjectResponseSchema = z56.object({
12398
+ var ProjectResponseSchema = z59.object({
11695
12399
  id: ProjectId,
11696
12400
  taskId: ProjectId.nullable(),
11697
12401
  status: DropshippingProjectStatus,
11698
- originKey: z56.string().min(1),
12402
+ originKey: z59.string().min(1),
11699
12403
  researchRunId: ProjectId.nullable(),
11700
- candidateSourceId: z56.string().nullable(),
11701
- productName: z56.string().nullable(),
12404
+ candidateSourceId: z59.string().nullable(),
12405
+ productName: z59.string().nullable(),
11702
12406
  productUrl: HttpUrl3.nullable(),
11703
12407
  referenceImageUrl: HttpUrl3.nullable(),
11704
- targetCountry: z56.string().nullable(),
11705
- languageCode: z56.string().nullable(),
11706
- hasReferenceImage: z56.boolean(),
12408
+ targetCountry: z59.string().nullable(),
12409
+ languageCode: z59.string().nullable(),
12410
+ hasReferenceImage: z59.boolean(),
11707
12411
  costingInput: CostingInputSchema.nullable(),
11708
12412
  costingResult: CostingResultSchema.nullable(),
11709
- costingVersion: z56.number().int().positive().nullable(),
11710
- version: z56.number().int().positive(),
12413
+ costingVersion: z59.number().int().positive().nullable(),
12414
+ version: z59.number().int().positive(),
11711
12415
  phase: DropshippingProjectPhase,
11712
12416
  nextAction: DropshippingProjectNextAction.nullable(),
11713
12417
  rewards: RewardProgressSchema,
11714
- createdAt: z56.string().datetime(),
11715
- updatedAt: z56.string().datetime()
12418
+ createdAt: z59.string().datetime(),
12419
+ updatedAt: z59.string().datetime()
11716
12420
  }).passthrough();
11717
12421
  function projectText(raw) {
11718
12422
  const project = ProjectResponseSchema.parse(raw);
@@ -11797,6 +12501,7 @@ function createServer(opts) {
11797
12501
  }
11798
12502
  });
11799
12503
  const api = new ApiClient(apiBaseUrl, apiKey);
12504
+ registerMetaUploadAppResource(server);
11800
12505
  registerProductCostingTools(server);
11801
12506
  registerProductResearchTools(server, api);
11802
12507
  registerDropshippingProjectTools(server, api);
@@ -11814,6 +12519,7 @@ function createServer(opts) {
11814
12519
  registerPublishTools(server, api, {
11815
12520
  transport
11816
12521
  });
12522
+ registerMetaUploadTools(server, api);
11817
12523
  registerCampaignTools(server, api);
11818
12524
  return server;
11819
12525
  }