@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/stdio.js CHANGED
@@ -150,10 +150,60 @@ var ApiClient = class {
150
150
  };
151
151
 
152
152
  // src/index.ts
153
- var MCP_VERSION = "2.4.0";
153
+ var MCP_VERSION = "2.9.1";
154
+
155
+ // src/meta-upload-app.ts
156
+ import { readFileSync } from "fs";
157
+ import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
158
+ var META_UPLOAD_APP_RESOURCE_URI = "ui://meta-upload/v1.html";
159
+ var META_UPLOAD_APP_NAME = "Meta upload app";
160
+ var META_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
161
+ var META_UPLOAD_APP_CSP = {
162
+ connectDomains: [
163
+ "https://productmaker.app",
164
+ "https://www.productmaker.app",
165
+ "https://*.r2.cloudflarestorage.com",
166
+ "https://*.oaiusercontent.com"
167
+ ]
168
+ };
169
+ function readMetaUploadAppHtml(htmlUrl = META_UPLOAD_APP_HTML_URL) {
170
+ try {
171
+ return readFileSync(htmlUrl, "utf8");
172
+ } catch (error) {
173
+ const message = error instanceof Error ? error.message : String(error);
174
+ throw new Error(`Missing packaged Meta upload app HTML at ${htmlUrl.pathname}. Rebuild @productmaker/mcp so ${META_UPLOAD_APP_RESOURCE_URI} is bundled. Original error: ${message}`);
175
+ }
176
+ }
177
+ __name(readMetaUploadAppHtml, "readMetaUploadAppHtml");
178
+ function registerMetaUploadAppResource(server) {
179
+ const html = readMetaUploadAppHtml();
180
+ registerAppResource(server, META_UPLOAD_APP_NAME, META_UPLOAD_APP_RESOURCE_URI, {
181
+ title: "Meta upload app",
182
+ mimeType: RESOURCE_MIME_TYPE,
183
+ _meta: {
184
+ ui: {
185
+ csp: META_UPLOAD_APP_CSP
186
+ }
187
+ }
188
+ }, async () => ({
189
+ contents: [
190
+ {
191
+ uri: META_UPLOAD_APP_RESOURCE_URI,
192
+ mimeType: RESOURCE_MIME_TYPE,
193
+ text: html,
194
+ _meta: {
195
+ ui: {
196
+ csp: META_UPLOAD_APP_CSP
197
+ }
198
+ }
199
+ }
200
+ ]
201
+ }));
202
+ }
203
+ __name(registerMetaUploadAppResource, "registerMetaUploadAppResource");
154
204
 
155
205
  // src/tools/tasks.ts
156
- import { z as z44 } from "zod";
206
+ import { z as z46 } from "zod";
157
207
 
158
208
  // src/resolve-image.ts
159
209
  import { lookup as dnsLookup } from "dns/promises";
@@ -2253,6 +2303,25 @@ var ShopifyPublicationStatus = z19.enum([
2253
2303
  "success",
2254
2304
  "error"
2255
2305
  ]);
2306
+ var ShopifyTrackingSyncStatus = z19.enum([
2307
+ "synced",
2308
+ "migrated",
2309
+ "not_detected"
2310
+ ]);
2311
+ var ShopifyTrackingSource = z19.enum([
2312
+ "theme",
2313
+ "legacy_layout",
2314
+ "theme_and_legacy",
2315
+ "none"
2316
+ ]);
2317
+ var ShopifyTrackingResult = z19.object({
2318
+ status: ShopifyTrackingSyncStatus,
2319
+ manualCodeDetected: z19.boolean(),
2320
+ /** True when code from a prior ProductMaker layout was preserved automatically. */
2321
+ legacyCodeMigrated: z19.boolean().default(false),
2322
+ source: ShopifyTrackingSource.default("none"),
2323
+ syncedAt: z19.string().datetime()
2324
+ });
2256
2325
  var ShopifyPublishResponse = z19.object({
2257
2326
  publicationId: z19.string().uuid(),
2258
2327
  taskId: z19.string().uuid(),
@@ -2268,6 +2337,8 @@ var ShopifyPublishResponse = z19.object({
2268
2337
  needsThemeSetup: z19.boolean().optional(),
2269
2338
  /** One-click theme-editor deep link to add the Code Landing block (when needsThemeSetup). */
2270
2339
  themeEditorDeepLink: z19.string().url().optional(),
2340
+ /** Tracking copied from the theme or migrated from a previous ProductMaker layout. */
2341
+ tracking: ShopifyTrackingResult.optional(),
2271
2342
  createdAt: z19.string().datetime().optional(),
2272
2343
  updatedAt: z19.string().datetime().optional()
2273
2344
  });
@@ -3166,7 +3237,9 @@ var ImageCreativeEstimationOptions = z24.object({
3166
3237
  });
3167
3238
  var RegenerateLandingRoleBody = z24.object({
3168
3239
  comment: z24.string().max(500).optional(),
3169
- sourceKey: z24.string().max(200).optional()
3240
+ sourceKey: z24.string().max(200).optional(),
3241
+ /** Client-generated key lets a lost response be retried without another charge. */
3242
+ clientRequestId: z24.string().uuid().optional()
3170
3243
  }).strict();
3171
3244
  var LandingRegenerateEstimationOptions = z24.object({
3172
3245
  /** KIE image endpoint has non-linear pricing by nVariants; for landing we default to 2. */
@@ -3717,6 +3790,25 @@ var ALLOWED_MEDIA_CONTENT_TYPES = [
3717
3790
  "audio/wav",
3718
3791
  "audio/x-wav"
3719
3792
  ];
3793
+ var META_STAGING_IMAGE_CONTENT_TYPES = /* @__PURE__ */ new Set([
3794
+ "image/jpeg",
3795
+ "image/png",
3796
+ "image/webp"
3797
+ ]);
3798
+ var META_STAGING_MEDIA_CONTENT_TYPES = /* @__PURE__ */ new Set([
3799
+ ...META_STAGING_IMAGE_CONTENT_TYPES,
3800
+ "video/mp4",
3801
+ "video/quicktime",
3802
+ "video/webm"
3803
+ ]);
3804
+ var META_STAGING_CONTENT_TYPES = [
3805
+ "image/jpeg",
3806
+ "image/png",
3807
+ "image/webp",
3808
+ "video/mp4",
3809
+ "video/quicktime",
3810
+ "video/webm"
3811
+ ];
3720
3812
  var PresignUploadRequest = z31.object({
3721
3813
  /** MIME type of the file to upload */
3722
3814
  contentType: z31.enum(ALLOWED_MEDIA_CONTENT_TYPES),
@@ -3732,8 +3824,51 @@ var PresignUploadRequest = z31.object({
3732
3824
  purpose: z31.enum([
3733
3825
  "ads",
3734
3826
  "service",
3735
- "actor"
3827
+ "actor",
3828
+ "meta_staging"
3829
+ ]).optional(),
3830
+ /** Required for purpose=meta_staging so media and thumbnails can be checked independently. */
3831
+ assetRole: z31.enum([
3832
+ "media",
3833
+ "thumbnail"
3736
3834
  ]).optional()
3835
+ }).superRefine((value, ctx) => {
3836
+ if (value.purpose === "meta_staging") {
3837
+ if (!value.assetRole) {
3838
+ ctx.addIssue({
3839
+ code: z31.ZodIssueCode.custom,
3840
+ path: [
3841
+ "assetRole"
3842
+ ],
3843
+ message: "assetRole is required for Meta staging uploads"
3844
+ });
3845
+ } else if (!META_STAGING_MEDIA_CONTENT_TYPES.has(value.contentType)) {
3846
+ ctx.addIssue({
3847
+ code: z31.ZodIssueCode.custom,
3848
+ path: [
3849
+ "contentType"
3850
+ ],
3851
+ message: "Meta staging accepts only image or video media"
3852
+ });
3853
+ } else if (value.assetRole === "thumbnail" && !META_STAGING_IMAGE_CONTENT_TYPES.has(value.contentType)) {
3854
+ ctx.addIssue({
3855
+ code: z31.ZodIssueCode.custom,
3856
+ path: [
3857
+ "contentType"
3858
+ ],
3859
+ message: "Meta staging thumbnails must be images"
3860
+ });
3861
+ }
3862
+ }
3863
+ if (value.purpose !== "meta_staging" && value.assetRole) {
3864
+ ctx.addIssue({
3865
+ code: z31.ZodIssueCode.custom,
3866
+ path: [
3867
+ "assetRole"
3868
+ ],
3869
+ message: "assetRole is only valid for Meta staging uploads"
3870
+ });
3871
+ }
3737
3872
  });
3738
3873
  var PresignUploadResponse = z31.object({
3739
3874
  /** Presigned S3 PUT URL — browser sends file directly here */
@@ -3743,8 +3878,23 @@ var PresignUploadResponse = z31.object({
3743
3878
  /** S3 key */
3744
3879
  key: z31.string(),
3745
3880
  /** Present only for purpose=service. */
3746
- assetToken: z31.string().min(1).optional()
3881
+ assetToken: z31.string().min(1).optional(),
3882
+ /** Present only for purpose=meta_staging. */
3883
+ stagingAssetId: z31.string().uuid().optional()
3747
3884
  });
3885
+ var MetaUploadMediaKind = z31.enum([
3886
+ "image",
3887
+ "video",
3888
+ "mixed"
3889
+ ]);
3890
+ var META_UPLOAD_MAX_FILES = CAMPAIGN_LIMITS.MAX_CREATIVES;
3891
+ var MetaUploadAssetSummary = z31.object({
3892
+ stagingAssetId: z31.string().uuid(),
3893
+ mediaKind: z31.enum([
3894
+ "image",
3895
+ "video"
3896
+ ])
3897
+ }).strict();
3748
3898
  var MetaCampaignType = z31.enum([
3749
3899
  "sales",
3750
3900
  "traffic",
@@ -3770,6 +3920,81 @@ var HttpUrl = z31.string().url().refine((value) => {
3770
3920
  return false;
3771
3921
  }
3772
3922
  }, "URL must use http or https");
3923
+ var MetaUploadSessionRequest = z31.object({
3924
+ /** The browser page will require a file of this media kind. */
3925
+ mediaKind: MetaUploadMediaKind,
3926
+ /** The staged object role; media is the normal creative input. */
3927
+ assetRole: z31.enum([
3928
+ "media",
3929
+ "thumbnail"
3930
+ ]).default("media"),
3931
+ /** Maximum number of files; the API chooses 10 for mixed and 1 otherwise when omitted. */
3932
+ maxFiles: z31.number().int().min(1).max(META_UPLOAD_MAX_FILES).optional()
3933
+ }).strict().superRefine((value, ctx) => {
3934
+ if (value.mediaKind === "mixed" && value.assetRole === "thumbnail") {
3935
+ ctx.addIssue({
3936
+ code: z31.ZodIssueCode.custom,
3937
+ path: [
3938
+ "mediaKind"
3939
+ ],
3940
+ message: "Mixed Meta upload sessions cannot be thumbnails"
3941
+ });
3942
+ }
3943
+ if (value.mediaKind !== "mixed" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
3944
+ ctx.addIssue({
3945
+ code: z31.ZodIssueCode.custom,
3946
+ path: [
3947
+ "maxFiles"
3948
+ ],
3949
+ message: "Non-mixed Meta upload sessions accept exactly one file"
3950
+ });
3951
+ }
3952
+ if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
3953
+ ctx.addIssue({
3954
+ code: z31.ZodIssueCode.custom,
3955
+ path: [
3956
+ "mediaKind"
3957
+ ],
3958
+ message: "Meta thumbnails must be images"
3959
+ });
3960
+ }
3961
+ });
3962
+ var ResolveMetaUploadSessionRequest = z31.object({
3963
+ /** Opaque signed token embedded in the one-click upload page URL. */
3964
+ token: z31.string().min(1).max(4096),
3965
+ /** Stable browser-generated id so failed PUT retries reuse the same slot. */
3966
+ uploadId: z31.string().uuid().optional(),
3967
+ /** Browser-selected metadata; the API checks it against the session kind/role. */
3968
+ contentType: z31.enum(META_STAGING_CONTENT_TYPES),
3969
+ fileName: z31.string().min(1).max(255),
3970
+ fileSize: z31.number().int().positive().max(CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES)
3971
+ }).strict();
3972
+ var MetaUploadSessionResponse = z31.object({
3973
+ sessionId: z31.string().uuid(),
3974
+ uploadPageUrl: HttpUrl,
3975
+ mediaKind: MetaUploadMediaKind,
3976
+ assetRole: z31.enum([
3977
+ "media",
3978
+ "thumbnail"
3979
+ ]),
3980
+ maxFiles: z31.number().int().min(1).max(META_UPLOAD_MAX_FILES),
3981
+ expiresAt: z31.string().datetime()
3982
+ }).strict();
3983
+ var ResolveMetaUploadSessionResponse = z31.object({
3984
+ uploadUrl: z31.string().url(),
3985
+ stagingAssetId: z31.string().uuid(),
3986
+ expiresAt: z31.string().datetime()
3987
+ }).strict();
3988
+ var MetaUploadSessionStatusResponse = z31.object({
3989
+ sessionId: z31.string().uuid(),
3990
+ status: z31.enum([
3991
+ "pending",
3992
+ "uploaded"
3993
+ ]),
3994
+ assets: z31.array(MetaUploadAssetSummary).max(META_UPLOAD_MAX_FILES),
3995
+ /** Legacy single-file response field retained for older callers. */
3996
+ stagingAssetId: z31.string().uuid().optional()
3997
+ }).strict();
3773
3998
  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), {
3774
3999
  message: 'adAccountId must look like "act_1234567890" (a bare numeric id is also accepted)'
3775
4000
  });
@@ -3856,18 +4081,26 @@ var UploadToMetaRequest = z31.object({
3856
4081
  title: z31.string().max(CAMPAIGN_LIMITS.MAX_TITLE_LENGTH),
3857
4082
  description: z31.string().max(CAMPAIGN_LIMITS.MAX_DESCRIPTION_LENGTH),
3858
4083
  cta: MetaCtaType,
3859
- /** Public URL of the uploaded media */
3860
- mediaUrl: HttpUrl,
4084
+ /** Legacy public URL; new Meta flows should use mediaAssetId. */
4085
+ mediaUrl: HttpUrl.optional(),
4086
+ /** Owner-scoped temporary asset created by the Meta staging upload flow. */
4087
+ mediaAssetId: z31.string().uuid().optional(),
3861
4088
  /**
3862
4089
  * Public URL of an extracted thumbnail. Required for video ads — Meta
3863
4090
  * rejects video ad creatives without `image_hash`/`image_url` in
3864
4091
  * `video_data` (subcode 1443226). Frontend extracts a frame from the
3865
4092
  * video and uploads it before submitting the request.
3866
4093
  */
3867
- thumbnailUrl: HttpUrl.optional()
4094
+ thumbnailUrl: HttpUrl.optional(),
4095
+ /** Owner-scoped temporary thumbnail created by the Meta staging upload flow. */
4096
+ thumbnailAssetId: z31.string().uuid().optional()
3868
4097
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3869
4098
  /** Meta Ad Account ID */
3870
4099
  adAccountId: MetaAdAccountId,
4100
+ /** Meta Business Portfolio selected by the user (optional for legacy callers). */
4101
+ portfolioId: z31.string().min(1).optional(),
4102
+ /** Facebook Page selected by the user (optional for legacy callers). */
4103
+ pageId: z31.string().min(1).optional(),
3871
4104
  /** Optional Meta Pixel ID */
3872
4105
  pixelId: z31.string().optional(),
3873
4106
  /** Landing page URL (required for landing destinations; omitted for whatsapp) */
@@ -3887,6 +4120,30 @@ var UploadToMetaRequest = z31.object({
3887
4120
  message: "landingUrl is required for landing destinations"
3888
4121
  });
3889
4122
  }
4123
+ value.creatives.forEach((creative, index) => {
4124
+ if (Boolean(creative.mediaUrl) === Boolean(creative.mediaAssetId)) {
4125
+ ctx.addIssue({
4126
+ code: z31.ZodIssueCode.custom,
4127
+ path: [
4128
+ "creatives",
4129
+ index,
4130
+ "mediaUrl"
4131
+ ],
4132
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4133
+ });
4134
+ }
4135
+ if (creative.thumbnailUrl && creative.thumbnailAssetId) {
4136
+ ctx.addIssue({
4137
+ code: z31.ZodIssueCode.custom,
4138
+ path: [
4139
+ "creatives",
4140
+ index,
4141
+ "thumbnailUrl"
4142
+ ],
4143
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4144
+ });
4145
+ }
4146
+ });
3890
4147
  });
3891
4148
  var CampaignLifetimeMetricsSchema = z31.object({
3892
4149
  spend: z31.number().nonnegative(),
@@ -3940,8 +4197,32 @@ var FacebookAdAccountSchema = z32.object({
3940
4197
  source: z32.enum([
3941
4198
  "business",
3942
4199
  "personal"
4200
+ ]),
4201
+ /** Meta Business Portfolio (or the synthetic `personal` group). */
4202
+ portfolioId: z32.string().min(1),
4203
+ portfolioName: z32.string().min(1),
4204
+ portfolioType: z32.enum([
4205
+ "business",
4206
+ "personal"
3943
4207
  ])
3944
4208
  });
4209
+ var FacebookMetaPortfolioSchema = z32.object({
4210
+ id: z32.string().min(1),
4211
+ name: z32.string().min(1),
4212
+ type: z32.enum([
4213
+ "business",
4214
+ "personal"
4215
+ ])
4216
+ });
4217
+ var FacebookMetaPageSchema = z32.object({
4218
+ id: z32.string().min(1),
4219
+ name: z32.string().min(1)
4220
+ });
4221
+ var FacebookMetaDestinationsResponseSchema = z32.object({
4222
+ portfolios: z32.array(FacebookMetaPortfolioSchema),
4223
+ adAccounts: z32.array(FacebookAdAccountSchema),
4224
+ pages: z32.array(FacebookMetaPageSchema)
4225
+ });
3945
4226
  var MetaCampaignOverridesSchema = z32.object({
3946
4227
  dailyBudgetUsd: z32.number().positive().optional(),
3947
4228
  targetCountries: z32.array(z32.string().length(2)).max(1).optional(),
@@ -3950,6 +4231,8 @@ var MetaCampaignOverridesSchema = z32.object({
3950
4231
  });
3951
4232
  var FacebookCreateCampaignBodySchema = z32.object({
3952
4233
  adAccountId: MetaAdAccountId,
4234
+ portfolioId: z32.string().min(1).optional(),
4235
+ pageId: z32.string().min(1).optional(),
3953
4236
  pixelId: z32.string().optional(),
3954
4237
  landingUrl: HttpUrl2.optional(),
3955
4238
  campaignConfig: MetaCampaignConfig.optional(),
@@ -3980,6 +4263,8 @@ var FacebookCampaignStatusResponseSchema = z32.object({
3980
4263
  var FacebookCreateCampaignResponseSchema = z32.object({
3981
4264
  campaignId: z32.string(),
3982
4265
  campaignName: z32.string(),
4266
+ portfolioId: z32.string().optional(),
4267
+ portfolioName: z32.string().optional(),
3983
4268
  pageId: z32.string().optional(),
3984
4269
  pageName: z32.string().optional(),
3985
4270
  // Backward-compatible single ad fields (optional)
@@ -4022,11 +4307,15 @@ var FacebookCreateCampaignFromCreativesBodySchema = z32.object({
4022
4307
  title: z32.string().max(120),
4023
4308
  description: z32.string().max(500),
4024
4309
  cta: MetaCtaType,
4025
- mediaUrl: HttpUrl2,
4310
+ mediaUrl: HttpUrl2.optional(),
4311
+ mediaAssetId: z32.string().uuid().optional(),
4026
4312
  thumbnailUrl: HttpUrl2.optional(),
4313
+ thumbnailAssetId: z32.string().uuid().optional(),
4027
4314
  creativeId: z32.string().min(1).optional()
4028
4315
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
4029
4316
  adAccountId: MetaAdAccountId,
4317
+ portfolioId: z32.string().min(1).optional(),
4318
+ pageId: z32.string().min(1).optional(),
4030
4319
  pixelId: z32.string().optional(),
4031
4320
  /** Required unless campaignConfig.destination is 'whatsapp' (see superRefine below). */
4032
4321
  landingUrl: HttpUrl2.optional(),
@@ -4046,6 +4335,30 @@ var FacebookCreateCampaignFromCreativesBodySchema = z32.object({
4046
4335
  message: "landingUrl is required unless destination is whatsapp"
4047
4336
  });
4048
4337
  }
4338
+ value.creatives.forEach((creative, index) => {
4339
+ if (Boolean(creative.mediaUrl) === Boolean(creative.mediaAssetId)) {
4340
+ ctx.addIssue({
4341
+ code: z32.ZodIssueCode.custom,
4342
+ path: [
4343
+ "creatives",
4344
+ index,
4345
+ "mediaUrl"
4346
+ ],
4347
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4348
+ });
4349
+ }
4350
+ if (creative.thumbnailUrl && creative.thumbnailAssetId) {
4351
+ ctx.addIssue({
4352
+ code: z32.ZodIssueCode.custom,
4353
+ path: [
4354
+ "creatives",
4355
+ index,
4356
+ "thumbnailUrl"
4357
+ ],
4358
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4359
+ });
4360
+ }
4361
+ });
4049
4362
  if (value.campaignConfig?.destination?.type === "landing" && value.landingUrl && value.campaignConfig.destination.landingUrl !== value.landingUrl) {
4050
4363
  ctx.addIssue({
4051
4364
  code: z32.ZodIssueCode.custom,
@@ -4123,8 +4436,10 @@ var FacebookTaskCreativeSourceSchema = z32.object({
4123
4436
  });
4124
4437
  var FacebookMediaCreativeSourceSchema = z32.object({
4125
4438
  type: z32.literal("media"),
4126
- mediaUrl: HttpUrl2,
4127
- thumbnailUrl: HttpUrl2.optional()
4439
+ mediaUrl: HttpUrl2.optional(),
4440
+ mediaAssetId: z32.string().uuid().optional(),
4441
+ thumbnailUrl: HttpUrl2.optional(),
4442
+ thumbnailAssetId: z32.string().uuid().optional()
4128
4443
  });
4129
4444
  var FacebookCreativeSourceSchema = z32.discriminatedUnion("type", [
4130
4445
  FacebookTaskCreativeSourceSchema,
@@ -4143,6 +4458,9 @@ var FacebookCreateCampaignFromTasksBodySchema = z32.object({
4143
4458
  creativeId: z32.string().min(1).optional()
4144
4459
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
4145
4460
  adAccountId: MetaAdAccountId,
4461
+ /** Explicit destination is required for MCP/remote creative publishing. */
4462
+ portfolioId: z32.string().min(1),
4463
+ pageId: z32.string().min(1),
4146
4464
  pixelId: z32.string().optional(),
4147
4465
  landingUrl: HttpUrl2.optional(),
4148
4466
  targetCountry: z32.string().length(2).default("US"),
@@ -4159,6 +4477,33 @@ var FacebookCreateCampaignFromTasksBodySchema = z32.object({
4159
4477
  message: "landingUrl is required unless destination is whatsapp"
4160
4478
  });
4161
4479
  }
4480
+ value.creatives.forEach((creative, index) => {
4481
+ if (creative.source.type !== "media") return;
4482
+ if (Boolean(creative.source.mediaUrl) === Boolean(creative.source.mediaAssetId)) {
4483
+ ctx.addIssue({
4484
+ code: z32.ZodIssueCode.custom,
4485
+ path: [
4486
+ "creatives",
4487
+ index,
4488
+ "source",
4489
+ "mediaUrl"
4490
+ ],
4491
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4492
+ });
4493
+ }
4494
+ if (creative.source.thumbnailUrl && creative.source.thumbnailAssetId) {
4495
+ ctx.addIssue({
4496
+ code: z32.ZodIssueCode.custom,
4497
+ path: [
4498
+ "creatives",
4499
+ index,
4500
+ "source",
4501
+ "thumbnailUrl"
4502
+ ],
4503
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4504
+ });
4505
+ }
4506
+ });
4162
4507
  });
4163
4508
 
4164
4509
  // ../../packages/types/src/tiktok.ts
@@ -5840,8 +6185,7 @@ var DEFAULT_PRICING = {
5840
6185
  userPriceCents: 0.04
5841
6186
  }
5842
6187
  };
5843
- var REGEN_I2V_CLIP_OUR_COST_CENTS = DEFAULT_PRICING["kie:video-generation:grok15-persec-480p"].ourCostCents * 3;
5844
- var REGEN_SINGLE_CALL_OUR_COST_CENTS = DEFAULT_PRICING["kie:video-generation:grok-15s-480p"].ourCostCents;
6188
+ var REGEN_SINGLE_CALL_OUR_COST_CENTS = DEFAULT_PRICING["kie:video-generation:grok-15s-720p"].ourCostCents;
5845
6189
  var SC_FRAME_COMPOSE_OUR_COST_CENTS = DEFAULT_PRICING["kie:image-generation:gpt-image-2:medium"].ourCostCents;
5846
6190
  var SC_FRAME_COMPOSE_USER_PRICE_CENTS = DEFAULT_PRICING["kie:image-generation:gpt-image-2:medium"].userPriceCents;
5847
6191
  var MS_FRAME_COMPOSE_OUR_COST_CENTS = DEFAULT_PRICING["kie:image-generation:nano-banana-2"].ourCostCents;
@@ -7118,10 +7462,35 @@ var ListLandingEditsResponse = z37.object({
7118
7462
  edits: z37.array(LandingEditDto)
7119
7463
  });
7120
7464
 
7121
- // ../../packages/types/src/video-edit.ts
7465
+ // ../../packages/types/src/landing-regeneration.ts
7122
7466
  import { z as z38 } from "zod";
7467
+ var LandingRegenerationStatus = z38.enum([
7468
+ "queued",
7469
+ "processing",
7470
+ "published",
7471
+ "failed"
7472
+ ]);
7473
+ var RegenerateLandingRoleResponse = z38.object({
7474
+ ok: z38.literal(true),
7475
+ role: LandingRole,
7476
+ promptIndex: z38.number().int().nonnegative(),
7477
+ operationId: z38.string().min(1)
7478
+ });
7479
+ var LandingRegenerationStatusResponse = z38.object({
7480
+ ok: z38.literal(true),
7481
+ operationId: z38.string().min(1),
7482
+ role: LandingRole,
7483
+ status: LandingRegenerationStatus,
7484
+ refunded: z38.boolean(),
7485
+ failureReason: z38.string().nullable(),
7486
+ publishedAssetKey: z38.string().nullable(),
7487
+ updatedAt: z38.string().datetime()
7488
+ });
7489
+
7490
+ // ../../packages/types/src/video-edit.ts
7491
+ import { z as z39 } from "zod";
7123
7492
  var MAX_SEGMENT_SECONDS = 3;
7124
- var VideoEditStatusSchema = z38.enum([
7493
+ var VideoEditStatusSchema = z39.enum([
7125
7494
  "pending",
7126
7495
  "processing",
7127
7496
  "assembling",
@@ -7130,9 +7499,9 @@ var VideoEditStatusSchema = z38.enum([
7130
7499
  "discarded",
7131
7500
  "failed"
7132
7501
  ]);
7133
- var VideoEditTimeSectionSchema = z38.object({
7134
- startSec: z38.number().min(0),
7135
- endSec: z38.number().positive()
7502
+ var VideoEditTimeSectionSchema = z39.object({
7503
+ startSec: z39.number().min(0),
7504
+ endSec: z39.number().positive()
7136
7505
  }).refine((v) => v.endSec > v.startSec, {
7137
7506
  message: "endSec must be after startSec",
7138
7507
  path: [
@@ -7144,35 +7513,35 @@ var VideoEditTimeSectionSchema = z38.object({
7144
7513
  "endSec"
7145
7514
  ]
7146
7515
  });
7147
- var VideoEditRequestSchema = z38.object({
7148
- comment: z38.string().trim().min(1).max(2e3),
7149
- sections: z38.array(VideoEditTimeSectionSchema).min(1).optional()
7516
+ var VideoEditRequestSchema = z39.object({
7517
+ comment: z39.string().trim().min(1).max(2e3),
7518
+ sections: z39.array(VideoEditTimeSectionSchema).min(1).optional()
7150
7519
  });
7151
- var VideoEditModeSchema = z38.enum([
7520
+ var VideoEditModeSchema = z39.enum([
7152
7521
  "wan_edit",
7153
7522
  "regenerate"
7154
7523
  ]);
7155
- var VideoRegenerationEngineSchema = z38.enum([
7524
+ var VideoRegenerationEngineSchema = z39.enum([
7156
7525
  "free_text",
7157
7526
  "free_image",
7158
7527
  "single_call",
7159
7528
  "multi_shot"
7160
7529
  ]);
7161
- var VideoRegenerationRouteFingerprintSchema = z38.string().trim().min(1).max(128);
7162
- var RegenerateEditRequestSchema = z38.object({
7163
- angleKey: z38.string().min(1),
7164
- comment: z38.string().min(1).max(2e3)
7530
+ var VideoRegenerationRouteFingerprintSchema = z39.string().trim().min(1).max(128);
7531
+ var RegenerateEditRequestSchema = z39.object({
7532
+ angleKey: z39.string().min(1),
7533
+ comment: z39.string().min(1).max(2e3)
7165
7534
  });
7166
7535
 
7167
7536
  // ../../packages/types/src/dropshipping-project.ts
7168
- import { z as z39 } from "zod";
7169
- var DropshippingProjectStatusSchema = z39.enum([
7537
+ import { z as z40 } from "zod";
7538
+ var DropshippingProjectStatusSchema = z40.enum([
7170
7539
  "active",
7171
7540
  "completed",
7172
7541
  "archived"
7173
7542
  ]);
7174
7543
  var DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN = "dropshipping-launch-v1";
7175
- var DropshippingRewardMilestoneSchema = z39.enum([
7544
+ var DropshippingRewardMilestoneSchema = z40.enum([
7176
7545
  "product_selected",
7177
7546
  "costing_saved",
7178
7547
  "product_media_ready",
@@ -7187,21 +7556,21 @@ var DROPSHIPPING_LAUNCH_REWARD_CREDITS = {
7187
7556
  campaign_created: 200
7188
7557
  };
7189
7558
  var DROPSHIPPING_LAUNCH_REWARD_TOTAL_CREDITS = Object.values(DROPSHIPPING_LAUNCH_REWARD_CREDITS).reduce((sum, credits) => sum + credits, 0);
7190
- var DropshippingProjectRewardMilestoneStateSchema = z39.object({
7559
+ var DropshippingProjectRewardMilestoneStateSchema = z40.object({
7191
7560
  milestone: DropshippingRewardMilestoneSchema,
7192
- credits: z39.number().int().positive(),
7193
- available: z39.boolean().optional(),
7194
- earned: z39.boolean(),
7195
- grantId: z39.string().uuid().nullable(),
7196
- grantedAt: z39.string().datetime().nullable()
7197
- });
7198
- var DropshippingProjectRewardProgressSchema = z39.object({
7199
- campaignKey: z39.literal(DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN),
7200
- totalCredits: z39.number().int().positive(),
7201
- earnedCredits: z39.number().int().nonnegative(),
7202
- milestones: z39.array(DropshippingProjectRewardMilestoneStateSchema).length(DropshippingRewardMilestoneSchema.options.length)
7203
- });
7204
- var DropshippingProjectPhaseSchema = z39.enum([
7561
+ credits: z40.number().int().positive(),
7562
+ available: z40.boolean().optional(),
7563
+ earned: z40.boolean(),
7564
+ grantId: z40.string().uuid().nullable(),
7565
+ grantedAt: z40.string().datetime().nullable()
7566
+ });
7567
+ var DropshippingProjectRewardProgressSchema = z40.object({
7568
+ campaignKey: z40.literal(DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN),
7569
+ totalCredits: z40.number().int().positive(),
7570
+ earnedCredits: z40.number().int().nonnegative(),
7571
+ milestones: z40.array(DropshippingProjectRewardMilestoneStateSchema).length(DropshippingRewardMilestoneSchema.options.length)
7572
+ });
7573
+ var DropshippingProjectPhaseSchema = z40.enum([
7205
7574
  "research",
7206
7575
  "costing",
7207
7576
  "media",
@@ -7210,18 +7579,18 @@ var DropshippingProjectPhaseSchema = z39.enum([
7210
7579
  "advertising",
7211
7580
  "complete"
7212
7581
  ]);
7213
- var DropshippingProjectCostingInputSchema = z39.object({
7214
- country: z39.string().min(2).max(3),
7215
- supplierCost: z39.number().finite().positive(),
7216
- shippingCost: z39.number().finite().nonnegative(),
7217
- returnRate: z39.number().finite().min(0).lt(1),
7218
- cancellationRate: z39.number().finite().min(0).lt(1),
7219
- adsManagerCpa: z39.number().finite().nonnegative(),
7220
- desiredMargin: z39.number().finite().min(0).lt(1)
7582
+ var DropshippingProjectCostingInputSchema = z40.object({
7583
+ country: z40.string().min(2).max(3),
7584
+ supplierCost: z40.number().finite().positive(),
7585
+ shippingCost: z40.number().finite().nonnegative(),
7586
+ returnRate: z40.number().finite().min(0).lt(1),
7587
+ cancellationRate: z40.number().finite().min(0).lt(1),
7588
+ adsManagerCpa: z40.number().finite().nonnegative(),
7589
+ desiredMargin: z40.number().finite().min(0).lt(1)
7221
7590
  }).superRefine(({ returnRate, cancellationRate }, context) => {
7222
7591
  if (returnRate + cancellationRate >= 1) {
7223
7592
  context.addIssue({
7224
- code: z39.ZodIssueCode.custom,
7593
+ code: z40.ZodIssueCode.custom,
7225
7594
  path: [
7226
7595
  "cancellationRate"
7227
7596
  ],
@@ -7229,24 +7598,24 @@ var DropshippingProjectCostingInputSchema = z39.object({
7229
7598
  });
7230
7599
  }
7231
7600
  });
7232
- var DropshippingProjectCostingResultSchema = z39.object({
7233
- country: z39.string().min(2).max(3),
7234
- deliveryRate: z39.number().finite(),
7235
- adjustedShippingCost: z39.number().finite(),
7236
- adjustedCpa: z39.number().finite(),
7237
- totalCost: z39.number().finite(),
7238
- suggestedPrice: z39.number().finite(),
7239
- baseCommercialPrice: z39.number().finite(),
7240
- minimumSafePrice: z39.number().finite(),
7241
- commercialPrice: z39.number().finite(),
7242
- comparisonPrice: z39.number().finite(),
7243
- estimatedProfit: z39.number().finite(),
7244
- actualMargin: z39.number().finite(),
7245
- breakEvenAdsManagerCpa: z39.number().finite(),
7246
- wasPriceAutoAdjusted: z39.boolean(),
7247
- profitabilityStatus: z39.literal("healthy")
7248
- });
7249
- var HttpUrlSchema = z39.string().url().max(2e3).refine((value) => {
7601
+ var DropshippingProjectCostingResultSchema = z40.object({
7602
+ country: z40.string().min(2).max(3),
7603
+ deliveryRate: z40.number().finite(),
7604
+ adjustedShippingCost: z40.number().finite(),
7605
+ adjustedCpa: z40.number().finite(),
7606
+ totalCost: z40.number().finite(),
7607
+ suggestedPrice: z40.number().finite(),
7608
+ baseCommercialPrice: z40.number().finite(),
7609
+ minimumSafePrice: z40.number().finite(),
7610
+ commercialPrice: z40.number().finite(),
7611
+ comparisonPrice: z40.number().finite(),
7612
+ estimatedProfit: z40.number().finite(),
7613
+ actualMargin: z40.number().finite(),
7614
+ breakEvenAdsManagerCpa: z40.number().finite(),
7615
+ wasPriceAutoAdjusted: z40.boolean(),
7616
+ profitabilityStatus: z40.literal("healthy")
7617
+ });
7618
+ var HttpUrlSchema = z40.string().url().max(2e3).refine((value) => {
7250
7619
  try {
7251
7620
  return [
7252
7621
  "http:",
@@ -7256,124 +7625,124 @@ var HttpUrlSchema = z39.string().url().max(2e3).refine((value) => {
7256
7625
  return false;
7257
7626
  }
7258
7627
  }, "Product URL must use HTTP(S)");
7259
- var ResearchCandidateOriginSchema = z39.object({
7260
- type: z39.literal("research_candidate"),
7261
- runId: z39.string().uuid(),
7262
- candidateSourceId: z39.string().min(1).max(160)
7263
- });
7264
- var TaskOriginSchema = z39.object({
7265
- type: z39.literal("task"),
7266
- taskId: z39.string().uuid()
7267
- });
7268
- var ManualOriginSchema = z39.object({
7269
- type: z39.literal("manual"),
7270
- clientRequestId: z39.string().uuid(),
7271
- country: z39.string().min(2).max(3).optional()
7272
- });
7273
- var CreateDropshippingProjectRequestSchema = z39.object({
7274
- origin: z39.discriminatedUnion("type", [
7628
+ var ResearchCandidateOriginSchema = z40.object({
7629
+ type: z40.literal("research_candidate"),
7630
+ runId: z40.string().uuid(),
7631
+ candidateSourceId: z40.string().min(1).max(160)
7632
+ });
7633
+ var TaskOriginSchema = z40.object({
7634
+ type: z40.literal("task"),
7635
+ taskId: z40.string().uuid()
7636
+ });
7637
+ var ManualOriginSchema = z40.object({
7638
+ type: z40.literal("manual"),
7639
+ clientRequestId: z40.string().uuid(),
7640
+ country: z40.string().min(2).max(3).optional()
7641
+ });
7642
+ var CreateDropshippingProjectRequestSchema = z40.object({
7643
+ origin: z40.discriminatedUnion("type", [
7275
7644
  ResearchCandidateOriginSchema,
7276
7645
  TaskOriginSchema,
7277
7646
  ManualOriginSchema
7278
7647
  ]),
7279
- productName: z39.string().trim().min(1).max(240).optional(),
7648
+ productName: z40.string().trim().min(1).max(240).optional(),
7280
7649
  productUrl: HttpUrlSchema.optional(),
7281
- country: z39.string().min(2).max(3).optional(),
7282
- languageCode: z39.string().min(2).max(16).optional()
7650
+ country: z40.string().min(2).max(3).optional(),
7651
+ languageCode: z40.string().min(2).max(16).optional()
7283
7652
  }).strict();
7284
- var SaveDropshippingProjectCostingRequestSchema = z39.object({
7653
+ var SaveDropshippingProjectCostingRequestSchema = z40.object({
7285
7654
  input: DropshippingProjectCostingInputSchema,
7286
- version: z39.number().int().positive()
7655
+ version: z40.number().int().positive()
7287
7656
  }).strict();
7288
- var DropshippingProjectNextActionSchema = z39.discriminatedUnion("kind", [
7289
- z39.object({
7290
- kind: z39.literal("cost_product"),
7291
- href: z39.string().min(1)
7657
+ var DropshippingProjectNextActionSchema = z40.discriminatedUnion("kind", [
7658
+ z40.object({
7659
+ kind: z40.literal("cost_product"),
7660
+ href: z40.string().min(1)
7292
7661
  }),
7293
- z39.object({
7294
- kind: z39.literal("provide_product_media"),
7295
- href: z39.string().min(1)
7662
+ z40.object({
7663
+ kind: z40.literal("provide_product_media"),
7664
+ href: z40.string().min(1)
7296
7665
  }),
7297
- z39.object({
7298
- kind: z39.literal("generate_creatives"),
7299
- href: z39.string().min(1)
7666
+ z40.object({
7667
+ kind: z40.literal("generate_creatives"),
7668
+ href: z40.string().min(1)
7300
7669
  }),
7301
- z39.object({
7302
- kind: z39.literal("wait_for_creatives"),
7303
- href: z39.string().min(1)
7670
+ z40.object({
7671
+ kind: z40.literal("wait_for_creatives"),
7672
+ href: z40.string().min(1)
7304
7673
  }),
7305
- z39.object({
7306
- kind: z39.literal("repair_creatives"),
7307
- href: z39.string().min(1)
7674
+ z40.object({
7675
+ kind: z40.literal("repair_creatives"),
7676
+ href: z40.string().min(1)
7308
7677
  }),
7309
- z39.object({
7310
- kind: z39.literal("complete_creative_set"),
7311
- href: z39.string().min(1)
7678
+ z40.object({
7679
+ kind: z40.literal("complete_creative_set"),
7680
+ href: z40.string().min(1)
7312
7681
  }),
7313
- z39.object({
7314
- kind: z39.literal("connect_shopify"),
7315
- href: z39.string().min(1)
7682
+ z40.object({
7683
+ kind: z40.literal("connect_shopify"),
7684
+ href: z40.string().min(1)
7316
7685
  }),
7317
- z39.object({
7318
- kind: z39.literal("publish_storefront"),
7319
- href: z39.string().min(1)
7686
+ z40.object({
7687
+ kind: z40.literal("publish_storefront"),
7688
+ href: z40.string().min(1)
7320
7689
  }),
7321
- z39.object({
7322
- kind: z39.literal("wait_for_storefront"),
7323
- href: z39.string().min(1)
7690
+ z40.object({
7691
+ kind: z40.literal("wait_for_storefront"),
7692
+ href: z40.string().min(1)
7324
7693
  }),
7325
- z39.object({
7326
- kind: z39.literal("connect_meta"),
7327
- href: z39.string().min(1)
7694
+ z40.object({
7695
+ kind: z40.literal("connect_meta"),
7696
+ href: z40.string().min(1)
7328
7697
  }),
7329
- z39.object({
7330
- kind: z39.literal("create_meta_campaign"),
7331
- href: z39.string().min(1)
7698
+ z40.object({
7699
+ kind: z40.literal("create_meta_campaign"),
7700
+ href: z40.string().min(1)
7332
7701
  }),
7333
- z39.object({
7334
- kind: z39.literal("repair_meta_campaign"),
7335
- href: z39.string().min(1)
7702
+ z40.object({
7703
+ kind: z40.literal("repair_meta_campaign"),
7704
+ href: z40.string().min(1)
7336
7705
  }),
7337
- z39.object({
7338
- kind: z39.literal("review_paused_campaign"),
7339
- href: z39.string().min(1)
7706
+ z40.object({
7707
+ kind: z40.literal("review_paused_campaign"),
7708
+ href: z40.string().min(1)
7340
7709
  })
7341
7710
  ]);
7342
- var DropshippingProjectResponseSchema = z39.object({
7343
- id: z39.string().uuid(),
7344
- taskId: z39.string().uuid().nullable(),
7711
+ var DropshippingProjectResponseSchema = z40.object({
7712
+ id: z40.string().uuid(),
7713
+ taskId: z40.string().uuid().nullable(),
7345
7714
  status: DropshippingProjectStatusSchema,
7346
- originKey: z39.string().min(1),
7347
- researchRunId: z39.string().uuid().nullable(),
7348
- candidateSourceId: z39.string().nullable(),
7349
- productName: z39.string().nullable(),
7715
+ originKey: z40.string().min(1),
7716
+ researchRunId: z40.string().uuid().nullable(),
7717
+ candidateSourceId: z40.string().nullable(),
7718
+ productName: z40.string().nullable(),
7350
7719
  productUrl: HttpUrlSchema.nullable(),
7351
- referenceImageUrl: z39.string().url().nullable(),
7352
- hasReferenceImage: z39.boolean(),
7353
- targetCountry: z39.string().nullable(),
7354
- languageCode: z39.string().nullable(),
7720
+ referenceImageUrl: z40.string().url().nullable(),
7721
+ hasReferenceImage: z40.boolean(),
7722
+ targetCountry: z40.string().nullable(),
7723
+ languageCode: z40.string().nullable(),
7355
7724
  costingInput: DropshippingProjectCostingInputSchema.nullable(),
7356
7725
  costingResult: DropshippingProjectCostingResultSchema.nullable(),
7357
- costingVersion: z39.number().int().positive().nullable(),
7358
- version: z39.number().int().positive(),
7726
+ costingVersion: z40.number().int().positive().nullable(),
7727
+ version: z40.number().int().positive(),
7359
7728
  phase: DropshippingProjectPhaseSchema,
7360
7729
  nextAction: DropshippingProjectNextActionSchema.nullable(),
7361
7730
  rewards: DropshippingProjectRewardProgressSchema,
7362
- createdAt: z39.string().datetime(),
7363
- updatedAt: z39.string().datetime()
7731
+ createdAt: z40.string().datetime(),
7732
+ updatedAt: z40.string().datetime()
7364
7733
  });
7365
7734
 
7366
7735
  // ../../packages/types/src/scene-subject-contract.ts
7367
- import { z as z40 } from "zod";
7368
- var SceneSubjectContractSchema = z40.object({
7369
- maxPeople: z40.number().int().min(1).max(10),
7370
- minPeople: z40.number().int().min(0).max(10),
7371
- minDistinctPeople: z40.number().int().min(0).max(10),
7372
- maxHands: z40.number().int().min(0).max(20)
7736
+ import { z as z41 } from "zod";
7737
+ var SceneSubjectContractSchema = z41.object({
7738
+ maxPeople: z41.number().int().min(1).max(10),
7739
+ minPeople: z41.number().int().min(0).max(10),
7740
+ minDistinctPeople: z41.number().int().min(0).max(10),
7741
+ maxHands: z41.number().int().min(0).max(20)
7373
7742
  }).superRefine((value, ctx) => {
7374
7743
  if (value.minPeople > value.maxPeople) {
7375
7744
  ctx.addIssue({
7376
- code: z40.ZodIssueCode.custom,
7745
+ code: z41.ZodIssueCode.custom,
7377
7746
  path: [
7378
7747
  "minPeople"
7379
7748
  ],
@@ -7382,7 +7751,7 @@ var SceneSubjectContractSchema = z40.object({
7382
7751
  }
7383
7752
  if (value.minDistinctPeople > value.maxPeople) {
7384
7753
  ctx.addIssue({
7385
- code: z40.ZodIssueCode.custom,
7754
+ code: z41.ZodIssueCode.custom,
7386
7755
  path: [
7387
7756
  "minDistinctPeople"
7388
7757
  ],
@@ -7391,7 +7760,7 @@ var SceneSubjectContractSchema = z40.object({
7391
7760
  }
7392
7761
  if (value.minDistinctPeople > value.minPeople) {
7393
7762
  ctx.addIssue({
7394
- code: z40.ZodIssueCode.custom,
7763
+ code: z41.ZodIssueCode.custom,
7395
7764
  path: [
7396
7765
  "minDistinctPeople"
7397
7766
  ],
@@ -7401,7 +7770,7 @@ var SceneSubjectContractSchema = z40.object({
7401
7770
  });
7402
7771
 
7403
7772
  // ../../packages/types/src/service-creative.ts
7404
- import { z as z41 } from "zod";
7773
+ import { z as z42 } from "zod";
7405
7774
  var SERVICE_CREATIVE_PUBLIC_PRESETS = [
7406
7775
  "auto",
7407
7776
  "ugc_dynamic",
@@ -7409,19 +7778,19 @@ var SERVICE_CREATIVE_PUBLIC_PRESETS = [
7409
7778
  "cinematic",
7410
7779
  "wild_card"
7411
7780
  ];
7412
- var ServiceCreativePresetSchema = z41.enum(SERVICE_CREATIVE_PUBLIC_PRESETS);
7781
+ var ServiceCreativePresetSchema = z42.enum(SERVICE_CREATIVE_PUBLIC_PRESETS);
7413
7782
  var SERVICE_REVIEW_MODES = [
7414
7783
  "automatic",
7415
7784
  "guided"
7416
7785
  ];
7417
- var ServiceReviewModeSchema = z41.enum(SERVICE_REVIEW_MODES);
7786
+ var ServiceReviewModeSchema = z42.enum(SERVICE_REVIEW_MODES);
7418
7787
  var SERVICE_HIGGSFIELD_MODES = [
7419
7788
  "product_review",
7420
7789
  "ugc_how_to",
7421
7790
  "tv_spot",
7422
7791
  "wild_card"
7423
7792
  ];
7424
- var ServiceHiggsfieldModeSchema = z41.enum(SERVICE_HIGGSFIELD_MODES);
7793
+ var ServiceHiggsfieldModeSchema = z42.enum(SERVICE_HIGGSFIELD_MODES);
7425
7794
  var SERVICE_MEDIA_ROLES = [
7426
7795
  "screen_demo",
7427
7796
  "ad_reference",
@@ -7429,20 +7798,20 @@ var SERVICE_MEDIA_ROLES = [
7429
7798
  "presenter_reference",
7430
7799
  "brand_asset"
7431
7800
  ];
7432
- var ServiceMediaRoleSchema = z41.enum(SERVICE_MEDIA_ROLES);
7433
- var ServiceMediaAssetSchema = z41.object({
7801
+ var ServiceMediaRoleSchema = z42.enum(SERVICE_MEDIA_ROLES);
7802
+ var ServiceMediaAssetSchema = z42.object({
7434
7803
  role: ServiceMediaRoleSchema,
7435
- s3Key: z41.string().min(1),
7804
+ s3Key: z42.string().min(1),
7436
7805
  /** HMAC ticket returned by the authenticated service-media presign route. */
7437
- uploadToken: z41.string().min(1).optional(),
7438
- mimeType: z41.string().min(1),
7439
- durationSec: z41.number().positive().max(30).optional(),
7440
- sourceName: z41.string().max(255).optional()
7806
+ uploadToken: z42.string().min(1).optional(),
7807
+ mimeType: z42.string().min(1),
7808
+ durationSec: z42.number().positive().max(30).optional(),
7809
+ sourceName: z42.string().max(255).optional()
7441
7810
  }).superRefine((asset, ctx) => {
7442
7811
  const isVideoRole = asset.role === "screen_demo" || asset.role === "ad_reference";
7443
7812
  if (isVideoRole && !asset.mimeType.startsWith("video/")) {
7444
7813
  ctx.addIssue({
7445
- code: z41.ZodIssueCode.custom,
7814
+ code: z42.ZodIssueCode.custom,
7446
7815
  message: `${asset.role} requires video media`,
7447
7816
  path: [
7448
7817
  "mimeType"
@@ -7451,7 +7820,7 @@ var ServiceMediaAssetSchema = z41.object({
7451
7820
  }
7452
7821
  if (isVideoRole && asset.durationSec === void 0) {
7453
7822
  ctx.addIssue({
7454
- code: z41.ZodIssueCode.custom,
7823
+ code: z42.ZodIssueCode.custom,
7455
7824
  message: `${asset.role} requires durationSec`,
7456
7825
  path: [
7457
7826
  "durationSec"
@@ -7460,7 +7829,7 @@ var ServiceMediaAssetSchema = z41.object({
7460
7829
  }
7461
7830
  if (!isVideoRole && !asset.mimeType.startsWith("image/")) {
7462
7831
  ctx.addIssue({
7463
- code: z41.ZodIssueCode.custom,
7832
+ code: z42.ZodIssueCode.custom,
7464
7833
  message: `${asset.role} requires image media`,
7465
7834
  path: [
7466
7835
  "mimeType"
@@ -7468,13 +7837,13 @@ var ServiceMediaAssetSchema = z41.object({
7468
7837
  });
7469
7838
  }
7470
7839
  });
7471
- var ServiceMediaBundleSchema = z41.object({
7472
- assets: z41.array(ServiceMediaAssetSchema).max(15).default([])
7840
+ var ServiceMediaBundleSchema = z42.object({
7841
+ assets: z42.array(ServiceMediaAssetSchema).max(15).default([])
7473
7842
  }).superRefine((bundle, ctx) => {
7474
7843
  const adReferences = bundle.assets.filter((asset) => asset.role === "ad_reference");
7475
7844
  if (adReferences.length > 5) {
7476
7845
  ctx.addIssue({
7477
- code: z41.ZodIssueCode.custom,
7846
+ code: z42.ZodIssueCode.custom,
7478
7847
  message: "A maximum of five ad_reference videos is allowed",
7479
7848
  path: [
7480
7849
  "assets"
@@ -7484,7 +7853,7 @@ var ServiceMediaBundleSchema = z41.object({
7484
7853
  const screenDemos = bundle.assets.filter((asset) => asset.role === "screen_demo");
7485
7854
  if (screenDemos.length > 3) {
7486
7855
  ctx.addIssue({
7487
- code: z41.ZodIssueCode.custom,
7856
+ code: z42.ZodIssueCode.custom,
7488
7857
  message: "A maximum of three screen_demo videos is allowed",
7489
7858
  path: [
7490
7859
  "assets"
@@ -7501,33 +7870,33 @@ var SERVICE_MEDIA_ANALYSIS_KINDS = [
7501
7870
  "product",
7502
7871
  "other"
7503
7872
  ];
7504
- var ServiceMediaAnalysisKindSchema = z41.enum(SERVICE_MEDIA_ANALYSIS_KINDS);
7505
- var ServiceMediaAnalysisItemSchema = z41.object({
7506
- assetIndex: z41.number().int().nonnegative(),
7507
- mediaType: z41.enum([
7873
+ var ServiceMediaAnalysisKindSchema = z42.enum(SERVICE_MEDIA_ANALYSIS_KINDS);
7874
+ var ServiceMediaAnalysisItemSchema = z42.object({
7875
+ assetIndex: z42.number().int().nonnegative(),
7876
+ mediaType: z42.enum([
7508
7877
  "image",
7509
7878
  "video"
7510
7879
  ]),
7511
7880
  kind: ServiceMediaAnalysisKindSchema,
7512
- description: z41.string().min(1).max(1200),
7513
- tags: z41.array(z41.string().min(1).max(80)).max(12),
7514
- suggestedRoles: z41.array(ServiceMediaRoleSchema).max(4),
7515
- visiblePeople: z41.number().int().nonnegative().max(20).optional(),
7516
- ocrText: z41.string().max(2e3).optional(),
7517
- speechSummary: z41.string().max(2e3).optional()
7518
- });
7519
- var ServiceMediaAnalysisSchema = z41.object({
7520
- version: z41.literal(1),
7521
- analyzedAt: z41.string().datetime(),
7522
- pricingAction: z41.literal("service-media-analysis"),
7523
- assetFingerprint: z41.string().regex(/^[a-f0-9]{64}$/),
7881
+ description: z42.string().min(1).max(1200),
7882
+ tags: z42.array(z42.string().min(1).max(80)).max(12),
7883
+ suggestedRoles: z42.array(ServiceMediaRoleSchema).max(4),
7884
+ visiblePeople: z42.number().int().nonnegative().max(20).optional(),
7885
+ ocrText: z42.string().max(2e3).optional(),
7886
+ speechSummary: z42.string().max(2e3).optional()
7887
+ });
7888
+ var ServiceMediaAnalysisSchema = z42.object({
7889
+ version: z42.literal(1),
7890
+ analyzedAt: z42.string().datetime(),
7891
+ pricingAction: z42.literal("service-media-analysis"),
7892
+ assetFingerprint: z42.string().regex(/^[a-f0-9]{64}$/),
7524
7893
  /** Language used for human-readable descriptions and tags. Optional for old checkpoints. */
7525
- language: z41.enum([
7894
+ language: z42.enum([
7526
7895
  "es",
7527
7896
  "en",
7528
7897
  "pt"
7529
7898
  ]).optional(),
7530
- items: z41.array(ServiceMediaAnalysisItemSchema).max(15)
7899
+ items: z42.array(ServiceMediaAnalysisItemSchema).max(15)
7531
7900
  });
7532
7901
 
7533
7902
  // ../../packages/types/src/productmaker-chat-capabilities.ts
@@ -7537,6 +7906,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7537
7906
  confirmation: "credit_consuming",
7538
7907
  schemaHash: "bab794a9700a8d3f42b97f444a776e7ed4dcd833038c52588f951b50970ff3fa"
7539
7908
  },
7909
+ {
7910
+ name: "check_meta_media_upload",
7911
+ confirmation: "none",
7912
+ schemaHash: "62b1af1d796d9466f8926afc5e6d18c4ddeccd5b0269ca7b21c2f6ebcfd2a945"
7913
+ },
7540
7914
  {
7541
7915
  name: "cost_dropshipping_project",
7542
7916
  confirmation: "state_change",
@@ -7555,7 +7929,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7555
7929
  {
7556
7930
  name: "create_product_task",
7557
7931
  confirmation: "credit_consuming",
7558
- schemaHash: "50845af40b29f479592ca5e545d9c1af1fe631998802827affccb4641fc90c18"
7932
+ schemaHash: "5c5fe82a365c62e44265e79a4100a06820dbbdb2e0ddc296cd066eb2255b1d62"
7559
7933
  },
7560
7934
  {
7561
7935
  name: "create_service_video",
@@ -7600,7 +7974,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7600
7974
  {
7601
7975
  name: "generate_video_creative",
7602
7976
  confirmation: "credit_consuming",
7603
- schemaHash: "c80208df75f2c7cbc459e9ae494a75a0a5f5bc4cf044b3fc9ef9c0e9af2c2452"
7977
+ schemaHash: "04e12ef523e4064b0c204d110baad35d414b605068087c625badf40c46429c8a"
7604
7978
  },
7605
7979
  {
7606
7980
  name: "get_meta_campaign_status",
@@ -7642,6 +8016,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7642
8016
  confirmation: "none",
7643
8017
  schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
7644
8018
  },
8019
+ {
8020
+ name: "list_meta_destinations",
8021
+ confirmation: "none",
8022
+ schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
8023
+ },
7645
8024
  {
7646
8025
  name: "list_product_research_runs",
7647
8026
  confirmation: "none",
@@ -7670,12 +8049,12 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7670
8049
  {
7671
8050
  name: "publish_creatives_to_meta",
7672
8051
  confirmation: "external_mutation",
7673
- schemaHash: "ddb45071d61746b443124346e7a82095315a2a80bd393c73a45f12b895939cf5"
8052
+ schemaHash: "59b3d6d5ce25812fee3382b0b1c0cf5e07beb1063048dd0a0d0633686cf29da5"
7674
8053
  },
7675
8054
  {
7676
8055
  name: "publish_to_meta",
7677
8056
  confirmation: "external_mutation",
7678
- schemaHash: "4b6daa1a25e739ccf208a6acede067ec1c22b1ea5509f332361a17dd46f6bb0b"
8057
+ schemaHash: "b507735a1fe13657355cf93163b6ead0db151984e6f2702dcdbf76b4037133ca"
7679
8058
  },
7680
8059
  {
7681
8060
  name: "publish_to_shopify",
@@ -7692,6 +8071,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7692
8071
  confirmation: "credit_consuming",
7693
8072
  schemaHash: "745687cb41f904e293105f93bbe6bc0471f63fe097ab8f85d3df8d9833084844"
7694
8073
  },
8074
+ {
8075
+ name: "request_meta_media_upload",
8076
+ confirmation: "none",
8077
+ schemaHash: "fad928b17b4658eb5831bb8b62d5e65bd5220ad94b04b90ace09e658e1895c74"
8078
+ },
7695
8079
  {
7696
8080
  name: "resolve_pending_action",
7697
8081
  confirmation: "credit_consuming",
@@ -7706,8 +8090,8 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7706
8090
  var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITIES.map((capability) => capability.name);
7707
8091
 
7708
8092
  // ../../packages/types/src/actor.ts
7709
- import { z as z42 } from "zod";
7710
- var ActorStatusSchema = z42.enum([
8093
+ import { z as z43 } from "zod";
8094
+ var ActorStatusSchema = z43.enum([
7711
8095
  "draft",
7712
8096
  "generating",
7713
8097
  "ready",
@@ -7716,18 +8100,18 @@ var ActorStatusSchema = z42.enum([
7716
8100
  var ACTOR_NAME_MAX_CHARS = 60;
7717
8101
  var ACTOR_DESCRIPTION_MAX_CHARS = 600;
7718
8102
  var ACTOR_CANDIDATE_COUNT = 4;
7719
- var ActorGenderSchema = z42.enum([
8103
+ var ActorGenderSchema = z43.enum([
7720
8104
  "female",
7721
8105
  "male",
7722
8106
  "non_binary"
7723
8107
  ]);
7724
- var ActorSchema = z42.object({
7725
- id: z42.string(),
7726
- name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS),
7727
- description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
8108
+ var ActorSchema = z43.object({
8109
+ id: z43.string(),
8110
+ name: z43.string().min(1).max(ACTOR_NAME_MAX_CHARS),
8111
+ description: z43.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
7728
8112
  gender: ActorGenderSchema,
7729
- voiceId: z42.string().nullable().optional(),
7730
- brandId: z42.string().nullable().optional(),
8113
+ voiceId: z43.string().nullable().optional(),
8114
+ brandId: z43.string().nullable().optional(),
7731
8115
  status: ActorStatusSchema,
7732
8116
  /**
7733
8117
  * A candidate-grid run is in flight RIGHT NOW.
@@ -7739,32 +8123,114 @@ var ActorSchema = z42.object({
7739
8123
  * timestamp with a TTL, so an orphaned run reads as `false` instead of
7740
8124
  * pinning the personaje forever.
7741
8125
  */
7742
- generating: z42.boolean().optional(),
7743
- photoUrl: z42.string().url().nullable().optional(),
7744
- avatarUrl: z42.string().url().nullable().optional(),
7745
- candidateUrls: z42.array(z42.string().url()).optional(),
8126
+ generating: z43.boolean().optional(),
8127
+ photoUrl: z43.string().url().nullable().optional(),
8128
+ avatarUrl: z43.string().url().nullable().optional(),
8129
+ candidateUrls: z43.array(z43.string().url()).optional(),
7746
8130
  /** Indices of candidateUrls that failed the anatomy check (Task 6). */
7747
- flaggedCandidateIndices: z42.array(z42.number().int()).optional()
8131
+ flaggedCandidateIndices: z43.array(z43.number().int()).optional()
7748
8132
  });
7749
- var CreateActorRequest = z42.object({
7750
- name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS),
7751
- description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
8133
+ var CreateActorRequest = z43.object({
8134
+ name: z43.string().min(1).max(ACTOR_NAME_MAX_CHARS),
8135
+ description: z43.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
7752
8136
  gender: ActorGenderSchema,
7753
- voiceId: z42.string().optional(),
7754
- brandId: z42.string().optional(),
7755
- sourcePhotoKey: z42.string().optional()
8137
+ voiceId: z43.string().optional(),
8138
+ brandId: z43.string().optional(),
8139
+ sourcePhotoKey: z43.string().optional()
7756
8140
  }).refine((v) => v.description.trim().length > 0 || !!v.sourcePhotoKey, {
7757
8141
  message: "Se necesita una descripci\xF3n o una foto"
7758
8142
  });
7759
- var UpdateActorRequest = z42.object({
7760
- name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
7761
- description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
8143
+ var UpdateActorRequest = z43.object({
8144
+ name: z43.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
8145
+ description: z43.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
7762
8146
  gender: ActorGenderSchema.optional(),
7763
- voiceId: z42.string().nullable().optional(),
7764
- brandId: z42.string().nullable().optional()
8147
+ voiceId: z43.string().nullable().optional(),
8148
+ brandId: z43.string().nullable().optional()
8149
+ });
8150
+ var ApproveActorRequest = z43.object({
8151
+ candidateIndex: z43.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
8152
+ });
8153
+
8154
+ // ../../packages/types/src/referral-dashboard.ts
8155
+ import { z as z44 } from "zod";
8156
+ var REFERRAL_DASHBOARD_RAW_STATUSES = [
8157
+ "ACTIVE",
8158
+ "PENDING",
8159
+ "GRACE",
8160
+ "CANCELLED",
8161
+ "EXPIRED"
8162
+ ];
8163
+ var REFERRAL_DASHBOARD_STATES = [
8164
+ "active",
8165
+ "pending",
8166
+ "inactive",
8167
+ "no_plan"
8168
+ ];
8169
+ var REFERRAL_DASHBOARD_PLAN_KINDS = [
8170
+ "current",
8171
+ "last",
8172
+ "none"
8173
+ ];
8174
+ var ReferralDashboardRawStatusSchema = z44.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
8175
+ var ReferralDashboardStateSchema = z44.enum(REFERRAL_DASHBOARD_STATES);
8176
+ var ReferralDashboardPlanKindSchema = z44.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
8177
+ var ReferralDashboardStatusSchema = z44.enum([
8178
+ "all",
8179
+ "active",
8180
+ "pending",
8181
+ "inactive"
8182
+ ]).default("all");
8183
+ var ReferralDashboardPlanSchema = z44.string().trim().min(1).max(100).optional();
8184
+ var ReferralDashboardFiltersSchema = z44.object({
8185
+ status: ReferralDashboardStatusSchema,
8186
+ plan: ReferralDashboardPlanSchema
8187
+ });
8188
+ var ReferralDashboardSubscriptionSchema = z44.object({
8189
+ planName: z44.string().nullable(),
8190
+ planKind: ReferralDashboardPlanKindSchema,
8191
+ rawStatus: ReferralDashboardRawStatusSchema.nullable(),
8192
+ state: ReferralDashboardStateSchema,
8193
+ currentPeriodEnd: z44.string().datetime({
8194
+ offset: true
8195
+ }).nullable(),
8196
+ cancelAtPeriodEnd: z44.boolean()
8197
+ });
8198
+ var ReferralDashboardReferralSchema = z44.object({
8199
+ id: z44.string().min(1),
8200
+ registeredAt: z44.string().datetime({
8201
+ offset: true
8202
+ }),
8203
+ // Kept in the response for compatibility with existing referral API clients.
8204
+ // The referrer dashboard intentionally does not render these legacy fields.
8205
+ expiresAt: z44.string().datetime({
8206
+ offset: true
8207
+ }).nullable().optional(),
8208
+ status: z44.string().min(1).optional(),
8209
+ referredUser: z44.object({
8210
+ email: z44.string().nullable()
8211
+ }),
8212
+ subscription: ReferralDashboardSubscriptionSchema
8213
+ });
8214
+ var ReferralDashboardReferralsPageSchema = z44.object({
8215
+ referrals: z44.array(ReferralDashboardReferralSchema),
8216
+ total: z44.number().int().nonnegative()
8217
+ });
8218
+ var ReferralDashboardPlanBreakdownSchema = z44.object({
8219
+ planName: z44.string().nullable(),
8220
+ count: z44.number().int().nonnegative()
8221
+ });
8222
+ var ReferralDashboardMonthlyRegistrationSchema = z44.object({
8223
+ month: z44.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
8224
+ count: z44.number().int().nonnegative()
7765
8225
  });
7766
- var ApproveActorRequest = z42.object({
7767
- candidateIndex: z42.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
8226
+ var ReferralDashboardOverviewSchema = z44.object({
8227
+ totalReferredUsers: z44.number().int().nonnegative(),
8228
+ activeUsers: z44.number().int().nonnegative(),
8229
+ pendingUsers: z44.number().int().nonnegative(),
8230
+ inactiveUsers: z44.number().int().nonnegative(),
8231
+ noPlanUsers: z44.number().int().nonnegative(),
8232
+ planBreakdown: z44.array(ReferralDashboardPlanBreakdownSchema),
8233
+ monthlyRegistrations: z44.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
7768
8234
  });
7769
8235
 
7770
8236
  // src/resolve-media.ts
@@ -8006,10 +8472,10 @@ async function resolveMediaRef(ref, opts) {
8006
8472
  __name(resolveMediaRef, "resolveMediaRef");
8007
8473
 
8008
8474
  // src/tools/shared.ts
8009
- import { z as z43 } from "zod";
8010
- var Lang = z43.string().min(2).max(10);
8011
- var Country = z43.string().min(2).max(3);
8012
- var LandingStrategyEnum = z43.enum([
8475
+ import { z as z45 } from "zod";
8476
+ var Lang = z45.string().min(2).max(10);
8477
+ var Country = z45.string().min(2).max(3);
8478
+ var LandingStrategyEnum = z45.enum([
8013
8479
  "auto",
8014
8480
  "classic",
8015
8481
  "pain_solution",
@@ -8018,12 +8484,12 @@ var LandingStrategyEnum = z43.enum([
8018
8484
  "offer_value",
8019
8485
  "lifestyle_identity"
8020
8486
  ]);
8021
- var LandingImageFormatEnum = z43.enum([
8487
+ var LandingImageFormatEnum = z45.enum([
8022
8488
  "9:16",
8023
8489
  "4:5",
8024
8490
  "1:1"
8025
8491
  ]);
8026
- var VideoPresetEnum = z43.enum([
8492
+ var VideoPresetEnum = z45.enum([
8027
8493
  "auto",
8028
8494
  "ugc_dynamic",
8029
8495
  "ugc_how_to",
@@ -8036,7 +8502,7 @@ var VideoPresetEnum = z43.enum([
8036
8502
  "pixar_problem",
8037
8503
  "pixar_sassy"
8038
8504
  ]);
8039
- var TaskCreativeVariantVideoPresetEnum = z43.enum([
8505
+ var TaskCreativeVariantVideoPresetEnum = z45.enum([
8040
8506
  "auto",
8041
8507
  "ugc_dynamic",
8042
8508
  "ugc_how_to",
@@ -8107,7 +8573,7 @@ function resolveVideoPreset(preset) {
8107
8573
  return VIDEO_PRESET_RESOLUTION[preset] ?? null;
8108
8574
  }
8109
8575
  __name(resolveVideoPreset, "resolveVideoPreset");
8110
- var VideoTierEnum = z43.enum([
8576
+ var VideoTierEnum = z45.enum([
8111
8577
  "basic"
8112
8578
  ]);
8113
8579
  function resolveVideoTier(_tier) {
@@ -8118,27 +8584,21 @@ function resolveWireVideoTier(tier, durationSeconds) {
8118
8584
  return durationSeconds === 30 ? "standard" : resolveVideoTier(tier);
8119
8585
  }
8120
8586
  __name(resolveWireVideoTier, "resolveWireVideoTier");
8121
- function assertVideoPresetDurationCompatibility(videoPreset, durationSeconds) {
8122
- if (videoPreset === "problem_solution" && durationSeconds !== 30) {
8123
- throw new Error("Problema y Soluci\xF3n solo est\xE1 disponible en videos Multiescena de 30 segundos.");
8124
- }
8125
- }
8126
- __name(assertVideoPresetDurationCompatibility, "assertVideoPresetDurationCompatibility");
8127
8587
  function resolveVideoFormatForDuration(durationSeconds) {
8128
8588
  return durationSeconds === 30 ? "double_30s" : "single_15s";
8129
8589
  }
8130
8590
  __name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
8131
- var VideoResolutionEnum = z43.enum([
8591
+ var VideoResolutionEnum = z45.enum([
8132
8592
  "480p",
8133
8593
  "720p"
8134
8594
  ]);
8135
- var VideoStyleEnum = z43.enum([
8595
+ var VideoStyleEnum = z45.enum([
8136
8596
  "ugc",
8137
8597
  "cartoon_3d",
8138
8598
  "product_only",
8139
8599
  "ai_dynamic"
8140
8600
  ]);
8141
- var NarrativeStyleEnum = z43.enum([
8601
+ var NarrativeStyleEnum = z45.enum([
8142
8602
  "review",
8143
8603
  "ugc_how_to",
8144
8604
  "problem_product_cta",
@@ -8155,7 +8615,7 @@ var NarrativeStyleEnum = z43.enum([
8155
8615
  "ugc_skeptic_confession",
8156
8616
  "ugc_demo_review"
8157
8617
  ]);
8158
- var PublicNarrativeStyleEnum = z43.enum([
8618
+ var PublicNarrativeStyleEnum = z45.enum([
8159
8619
  "review",
8160
8620
  "ugc_how_to",
8161
8621
  "ugc_unboxing_chaos",
@@ -8167,7 +8627,7 @@ var PublicNarrativeStyleEnum = z43.enum([
8167
8627
  "animated_problem_pixar",
8168
8628
  "sassy_product_pixar"
8169
8629
  ]);
8170
- var VideoModelEnum = z43.enum([
8630
+ var VideoModelEnum = z45.enum([
8171
8631
  "veo",
8172
8632
  "wan",
8173
8633
  "bytedance",
@@ -8175,25 +8635,25 @@ var VideoModelEnum = z43.enum([
8175
8635
  "grok",
8176
8636
  "kling"
8177
8637
  ]);
8178
- var ProductReferenceUserChoiceEnum = z43.enum([
8638
+ var ProductReferenceUserChoiceEnum = z45.enum([
8179
8639
  "same_product",
8180
8640
  "variants",
8181
8641
  "different_products",
8182
8642
  "combo_distinct"
8183
8643
  ]);
8184
- var ProductReferenceCandidateGroupingEnum = z43.enum([
8644
+ var ProductReferenceCandidateGroupingEnum = z45.enum([
8185
8645
  "same_product_same_variant",
8186
8646
  "same_product_different_variants",
8187
8647
  "different_products",
8188
8648
  "unclear"
8189
8649
  ]);
8190
- var GapDurationChoiceMirror = z43.object({
8191
- angleIndex: z43.number().int().nonnegative(),
8192
- recommendedSec: z43.union([
8193
- z43.literal(15),
8194
- z43.literal(30)
8650
+ var GapDurationChoiceMirror = z45.object({
8651
+ angleIndex: z45.number().int().nonnegative(),
8652
+ recommendedSec: z45.union([
8653
+ z45.literal(15),
8654
+ z45.literal(30)
8195
8655
  ]),
8196
- estimatedDeltaCredits: z43.number().int().nonnegative().optional()
8656
+ estimatedDeltaCredits: z45.number().int().nonnegative().optional()
8197
8657
  });
8198
8658
  function buildSelectedImageUrlsHash(urls) {
8199
8659
  const input = [
@@ -8207,13 +8667,13 @@ function buildSelectedImageUrlsHash(urls) {
8207
8667
  return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
8208
8668
  }
8209
8669
  __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
8210
- var ImageCreativeStyleEnum = z43.enum([
8670
+ var ImageCreativeStyleEnum = z45.enum([
8211
8671
  "studio",
8212
8672
  "floating",
8213
8673
  "ingredients",
8214
8674
  "in_use"
8215
8675
  ]);
8216
- var ImageCreativeArchetypeEnum = z43.enum([
8676
+ var ImageCreativeArchetypeEnum = z45.enum([
8217
8677
  "direct_response_product_ad",
8218
8678
  "hero_outcome",
8219
8679
  "offer_value_prop",
@@ -8222,23 +8682,23 @@ var ImageCreativeArchetypeEnum = z43.enum([
8222
8682
  "problem_solution",
8223
8683
  "lifestyle_identity"
8224
8684
  ]);
8225
- var ImageCreativeVisualTreatmentEnum = z43.enum([
8685
+ var ImageCreativeVisualTreatmentEnum = z45.enum([
8226
8686
  "performance_product_ad",
8227
8687
  "premium_dtc_carousel"
8228
8688
  ]);
8229
- var ImageCreativeAspectRatioEnum = z43.enum([
8689
+ var ImageCreativeAspectRatioEnum = z45.enum([
8230
8690
  "1:1",
8231
8691
  "4:5",
8232
8692
  "9:16",
8233
8693
  "16:9"
8234
8694
  ]);
8235
- var ImageRefSchema = z43.object({
8236
- 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.'),
8237
- 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`.')
8695
+ var ImageRefSchema = z45.object({
8696
+ 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.'),
8697
+ 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`.')
8238
8698
  }).refine((v) => Boolean(v.url) !== Boolean(v.path), {
8239
8699
  message: "each image entry must have exactly one of `url` or `path`"
8240
8700
  });
8241
- 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.');
8701
+ 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.');
8242
8702
  function asText(value) {
8243
8703
  return {
8244
8704
  content: [
@@ -8291,14 +8751,14 @@ async function submitCreative(api, opts) {
8291
8751
  });
8292
8752
  }
8293
8753
  __name(submitCreative, "submitCreative");
8294
- var ResearchTargetCountryEnum = z43.enum([
8754
+ var ResearchTargetCountryEnum = z45.enum([
8295
8755
  "CO",
8296
8756
  "MX",
8297
8757
  "PE",
8298
8758
  "EC",
8299
8759
  "CL"
8300
8760
  ]);
8301
- var ResearchCategoryEnum = z43.enum([
8761
+ var ResearchCategoryEnum = z45.enum([
8302
8762
  "auto",
8303
8763
  "automotive",
8304
8764
  "kitchen",
@@ -8371,6 +8831,15 @@ var McpErrorCode = {
8371
8831
  SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
8372
8832
  // Meta / Facebook Ads
8373
8833
  META_NO_FB_PAGE: "META_NO_FB_PAGE",
8834
+ META_PORTFOLIO_MISMATCH: "META_PORTFOLIO_MISMATCH",
8835
+ META_PAGE_SELECTION_REQUIRED: "META_PAGE_SELECTION_REQUIRED",
8836
+ META_INVALID_PAGE: "META_INVALID_PAGE",
8837
+ META_STAGED_ASSET_NOT_FOUND: "META_STAGED_ASSET_NOT_FOUND",
8838
+ META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
8839
+ META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
8840
+ META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
8841
+ META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
8842
+ META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
8374
8843
  META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
8375
8844
  META_NO_ASSETS_READY: "META_NO_ASSETS_READY",
8376
8845
  META_NO_THUMBNAIL_SOURCE: "META_NO_THUMBNAIL_SOURCE",
@@ -8423,6 +8892,15 @@ var CODE_MESSAGES = {
8423
8892
  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.",
8424
8893
  // Meta — high-frequency
8425
8894
  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).",
8895
+ 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.",
8896
+ 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`.",
8897
+ META_INVALID_PAGE: "The selected Facebook Page is not available for this Meta connection. Call `list_meta_destinations` again and choose an available `pageId`.",
8898
+ META_STAGED_ASSET_NOT_FOUND: "The temporary media upload was not found or expired. Re-upload the media and retry the Meta publication.",
8899
+ META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
8900
+ META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
8901
+ META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
8902
+ 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.",
8903
+ 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.",
8426
8904
  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.",
8427
8905
  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.",
8428
8906
  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.",
@@ -8536,6 +9014,12 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
8536
9014
  McpErrorCode.CAMPAIGN_MISSING_LANDING_URL,
8537
9015
  McpErrorCode.CAMPAIGN_RECONCILIATION_REQUIRED,
8538
9016
  McpErrorCode.META_NO_LANDING_URL,
9017
+ McpErrorCode.META_PORTFOLIO_MISMATCH,
9018
+ McpErrorCode.META_PAGE_SELECTION_REQUIRED,
9019
+ McpErrorCode.META_INVALID_PAGE,
9020
+ McpErrorCode.META_STAGED_ASSET_NOT_FOUND,
9021
+ McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
9022
+ McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
8539
9023
  McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
8540
9024
  McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
8541
9025
  McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
@@ -8557,6 +9041,15 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
8557
9041
  McpErrorCode.META_MISSING_PAYMENT,
8558
9042
  McpErrorCode.META_ACCOUNT_RESTRICTED,
8559
9043
  McpErrorCode.META_NO_FB_PAGE,
9044
+ McpErrorCode.META_PORTFOLIO_MISMATCH,
9045
+ McpErrorCode.META_PAGE_SELECTION_REQUIRED,
9046
+ McpErrorCode.META_INVALID_PAGE,
9047
+ McpErrorCode.META_STAGED_ASSET_NOT_FOUND,
9048
+ McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
9049
+ McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
9050
+ McpErrorCode.META_STAGED_ASSET_INVALID,
9051
+ McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
9052
+ McpErrorCode.META_PUBLICATION_IN_PROGRESS,
8560
9053
  McpErrorCode.TIKTOK_TOKEN_EXPIRED,
8561
9054
  McpErrorCode.TIKTOK_PERMISSION_DENIED,
8562
9055
  McpErrorCode.TIKTOK_MISSING_PAYMENT,
@@ -8910,53 +9403,53 @@ function renderOutputsMarkdown(outputs) {
8910
9403
  __name(renderOutputsMarkdown, "renderOutputsMarkdown");
8911
9404
 
8912
9405
  // src/tools/tasks.ts
8913
- var CreateProductTaskInput = z44.object({
9406
+ var CreateProductTaskInput = z46.object({
8914
9407
  images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
8915
- 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."),
8916
- 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."),
9408
+ 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."),
9409
+ 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."),
8917
9410
  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.'),
8918
9411
  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.'),
8919
- 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.'),
8920
- 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".'),
8921
- 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.'),
8922
- 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.'),
8923
- 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."),
8924
- 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.'),
8925
- 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.'),
8926
- durationSeconds: z44.union([
8927
- z44.literal(15),
8928
- z44.literal(30)
9412
+ 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.'),
9413
+ 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".'),
9414
+ 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.'),
9415
+ 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.'),
9416
+ 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."),
9417
+ 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.'),
9418
+ 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.'),
9419
+ durationSeconds: z46.union([
9420
+ z46.literal(15),
9421
+ z46.literal(30)
8929
9422
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
8930
- 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."),
9423
+ 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."),
8931
9424
  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.'),
8932
9425
  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).'),
8933
- 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."),
9426
+ 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."),
8934
9427
  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.'),
8935
- outputs: z44.array(z44.enum([
9428
+ outputs: z46.array(z46.enum([
8936
9429
  "landing",
8937
9430
  "video",
8938
9431
  "image"
8939
9432
  ])).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.'),
8940
- 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."),
8941
- 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."),
8942
- 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."),
8943
- 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.'),
8944
- 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."),
8945
- 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."),
9433
+ 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."),
9434
+ 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."),
9435
+ 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."),
9436
+ 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.'),
9437
+ 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."),
9438
+ 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."),
8946
9439
  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".'),
8947
9440
  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".')
8948
9441
  }).strict();
8949
- var CreateServiceVideoInput = z44.object({
8950
- 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."),
8951
- 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.'),
9442
+ var CreateServiceVideoInput = z46.object({
9443
+ 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."),
9444
+ 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.'),
8952
9445
  language: Lang.describe("Idioma del video: es, en o pt."),
8953
- 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.'),
8954
- brandId: z44.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
8955
- reviewMode: z44.enum([
9446
+ 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.'),
9447
+ brandId: z46.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
9448
+ reviewMode: z46.enum([
8956
9449
  "automatic",
8957
9450
  "guided"
8958
9451
  ]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
8959
- videoPreset: z44.enum([
9452
+ videoPreset: z46.enum([
8960
9453
  "auto",
8961
9454
  "ugc_dynamic",
8962
9455
  "ugc_how_to",
@@ -8964,35 +9457,35 @@ var CreateServiceVideoInput = z44.object({
8964
9457
  "wild_card"
8965
9458
  ]).default("auto")
8966
9459
  }).strict();
8967
- var GetTaskStatusInput = z44.object({
8968
- taskId: z44.string().min(1),
8969
- waitSeconds: z44.number().int().min(0).max(120).optional()
8970
- });
8971
- var ListTasksInput = z44.object({
8972
- limit: z44.number().int().min(1).max(50).optional(),
8973
- offset: z44.number().int().min(0).optional(),
8974
- search: z44.string().optional()
8975
- });
8976
- var EditTaskDraftInput = z44.object({
8977
- taskId: z44.string().min(1),
8978
- angleIndex: z44.number().int().min(0),
8979
- angle: z44.object({
8980
- angle: z44.string().min(1).max(480),
8981
- buyerPersona: z44.string().min(1).max(360),
8982
- actor: z44.string().min(1).max(360).optional(),
8983
- 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."),
8984
- gender: z44.enum([
9460
+ var GetTaskStatusInput = z46.object({
9461
+ taskId: z46.string().min(1),
9462
+ waitSeconds: z46.number().int().min(0).max(120).optional()
9463
+ });
9464
+ var ListTasksInput = z46.object({
9465
+ limit: z46.number().int().min(1).max(50).optional(),
9466
+ offset: z46.number().int().min(0).optional(),
9467
+ search: z46.string().optional()
9468
+ });
9469
+ var EditTaskDraftInput = z46.object({
9470
+ taskId: z46.string().min(1),
9471
+ angleIndex: z46.number().int().min(0),
9472
+ angle: z46.object({
9473
+ angle: z46.string().min(1).max(480),
9474
+ buyerPersona: z46.string().min(1).max(360),
9475
+ actor: z46.string().min(1).max(360).optional(),
9476
+ 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."),
9477
+ gender: z46.enum([
8985
9478
  "female",
8986
9479
  "male"
8987
9480
  ]).optional(),
8988
- scriptNotes: z44.string().max(2e3).optional()
9481
+ scriptNotes: z46.string().max(2e3).optional()
8989
9482
  })
8990
9483
  });
8991
- var PendingActionPayload = z44.discriminatedUnion("kind", [
8992
- z44.object({
8993
- kind: z44.literal("product_reference_confirmation"),
9484
+ var PendingActionPayload = z46.discriminatedUnion("kind", [
9485
+ z46.object({
9486
+ kind: z46.literal("product_reference_confirmation"),
8994
9487
  userChoice: ProductReferenceUserChoiceEnum,
8995
- confirmedProductNames: z44.array(z44.string().min(1).max(120)).min(2).max(8).optional(),
9488
+ confirmedProductNames: z46.array(z46.string().min(1).max(120)).min(2).max(8).optional(),
8996
9489
  // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
8997
9490
  // reachable via MCP. Valid ONLY together with userChoice='variants' (same
8998
9491
  // product in several colors): renders one shot per color and stitches them,
@@ -9003,50 +9496,50 @@ var PendingActionPayload = z44.discriminatedUnion("kind", [
9003
9496
  // safe because multi-scene IS the intended render for a color variant set,
9004
9497
  // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
9005
9498
  // confirm call; the backend re-gates it (variants + variant_set only).
9006
- 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".')
9499
+ 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".')
9007
9500
  }),
9008
- z44.object({
9009
- kind: z44.literal("angle_review_approval"),
9501
+ z46.object({
9502
+ kind: z46.literal("angle_review_approval"),
9010
9503
  // All edits are optional — empty action body just approves the
9011
9504
  // backend-generated draft as-is. Field shapes mirror the angle-review
9012
9505
  // request contracts in @packages/types/wizard.ts; the handler routes to
9013
9506
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
9014
- customAngle: z44.string().min(1).max(480).optional(),
9507
+ customAngle: z46.string().min(1).max(480).optional(),
9015
9508
  // Keep the shared MCP action within the strictest downstream contract:
9016
9509
  // autopilot/image resume endpoints cap these fields at 1500 characters.
9017
- painPoints: z44.string().max(1500).optional(),
9018
- benefits: z44.string().max(1500).optional(),
9019
- features: z44.array(z44.string().min(1).max(300)).max(10).optional(),
9020
- gapDurationSeconds: z44.union([
9021
- z44.literal(15),
9022
- z44.literal(30)
9510
+ painPoints: z46.string().max(1500).optional(),
9511
+ benefits: z46.string().max(1500).optional(),
9512
+ features: z46.array(z46.string().min(1).max(300)).max(10).optional(),
9513
+ gapDurationSeconds: z46.union([
9514
+ z46.literal(15),
9515
+ z46.literal(30)
9023
9516
  ]).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.")
9024
9517
  }),
9025
- z44.object({
9026
- kind: z44.literal("service_angle_approval"),
9027
- angleId: z44.string().uuid(),
9028
- content: z44.string().min(1).max(480).optional(),
9029
- creativeDirection: z44.string().min(1).max(1500).optional(),
9030
- actor: z44.string().min(1).max(360).optional()
9518
+ z46.object({
9519
+ kind: z46.literal("service_angle_approval"),
9520
+ angleId: z46.string().uuid(),
9521
+ content: z46.string().min(1).max(480).optional(),
9522
+ creativeDirection: z46.string().min(1).max(1500).optional(),
9523
+ actor: z46.string().min(1).max(360).optional()
9031
9524
  }),
9032
- z44.object({
9033
- kind: z44.literal("service_script_approval"),
9034
- voiceovers: z44.record(z44.string(), z44.string().min(1).max(600)).optional(),
9035
- visualDirections: z44.record(z44.string(), z44.string().min(1).max(1200)).optional()
9525
+ z46.object({
9526
+ kind: z46.literal("service_script_approval"),
9527
+ voiceovers: z46.record(z46.string(), z46.string().min(1).max(600)).optional(),
9528
+ visualDirections: z46.record(z46.string(), z46.string().min(1).max(1200)).optional()
9036
9529
  })
9037
9530
  ]);
9038
- var ResolvePendingActionInput = z44.object({
9039
- taskId: z44.string().min(1),
9531
+ var ResolvePendingActionInput = z46.object({
9532
+ taskId: z46.string().min(1),
9040
9533
  action: PendingActionPayload
9041
9534
  }).strict();
9042
9535
  function resolveVariantConfirmVideoTier(action) {
9043
9536
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
9044
9537
  }
9045
9538
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
9046
- var StatusForResolveSchema = z44.object({
9539
+ var StatusForResolveSchema = z46.object({
9047
9540
  // Metadata used to select the backend resume contract. Older API responses
9048
9541
  // may omit these fields, so the resolver keeps the generic fallback.
9049
- kind: z44.enum([
9542
+ kind: z46.enum([
9050
9543
  "autopilot",
9051
9544
  "video",
9052
9545
  "image",
@@ -9054,24 +9547,24 @@ var StatusForResolveSchema = z44.object({
9054
9547
  "mix",
9055
9548
  "service"
9056
9549
  ]).optional(),
9057
- selectedOutputs: z44.array(z44.enum([
9550
+ selectedOutputs: z46.array(z46.enum([
9058
9551
  "landing",
9059
9552
  "video",
9060
9553
  "image"
9061
9554
  ])).optional(),
9062
- videoAngleCount: z44.number().int().min(1).max(3).optional(),
9063
- ugcFormat: z44.enum([
9555
+ videoAngleCount: z46.number().int().min(1).max(3).optional(),
9556
+ ugcFormat: z46.enum([
9064
9557
  "single_8s",
9065
9558
  "single_15s",
9066
9559
  "double_30s"
9067
9560
  ]).optional(),
9068
- videoStyle: z44.enum([
9561
+ videoStyle: z46.enum([
9069
9562
  "ugc",
9070
9563
  "cartoon_3d",
9071
9564
  "product_only",
9072
9565
  "ai_dynamic"
9073
9566
  ]).optional(),
9074
- videoModel: z44.enum([
9567
+ videoModel: z46.enum([
9075
9568
  "veo",
9076
9569
  "wan",
9077
9570
  "bytedance",
@@ -9079,26 +9572,26 @@ var StatusForResolveSchema = z44.object({
9079
9572
  "grok",
9080
9573
  "kling"
9081
9574
  ]).optional(),
9082
- videoTier: z44.enum([
9575
+ videoTier: z46.enum([
9083
9576
  "legacy",
9084
9577
  "standard",
9085
9578
  "pro",
9086
9579
  "single_call"
9087
9580
  ]).optional(),
9088
- videoResolution: z44.enum([
9581
+ videoResolution: z46.enum([
9089
9582
  "480p",
9090
9583
  "720p"
9091
9584
  ]).optional(),
9092
9585
  narrativeStyle: NarrativeStyleEnum.optional(),
9093
- pendingUserAction: z44.object({
9094
- kind: z44.string(),
9095
- candidateGrouping: z44.string().optional(),
9096
- selectedImageUrls: z44.array(z44.string()).optional(),
9097
- proposedProductNames: z44.array(z44.string()).optional(),
9098
- customAngleDraft: z44.string().optional(),
9099
- painPointsDraft: z44.string().optional(),
9100
- benefitsDraft: z44.string().optional(),
9101
- featuresDraft: z44.array(z44.string()).optional(),
9586
+ pendingUserAction: z46.object({
9587
+ kind: z46.string(),
9588
+ candidateGrouping: z46.string().optional(),
9589
+ selectedImageUrls: z46.array(z46.string()).optional(),
9590
+ proposedProductNames: z46.array(z46.string()).optional(),
9591
+ customAngleDraft: z46.string().optional(),
9592
+ painPointsDraft: z46.string().optional(),
9593
+ benefitsDraft: z46.string().optional(),
9594
+ featuresDraft: z46.array(z46.string()).optional(),
9102
9595
  // Recommendation consumed only when the caller explicitly sends
9103
9596
  // gapDurationSeconds: 30 in the angle approval action.
9104
9597
  gapDurationChoice: GapDurationChoiceMirror.optional(),
@@ -9107,17 +9600,17 @@ var StatusForResolveSchema = z44.object({
9107
9600
  // keys. `actorId` (the saved custom actor the picker stored) was silently
9108
9601
  // dropped here, so the approval handler below could not echo it back on the
9109
9602
  // PATCH — the selection looked accepted and reached no render.
9110
- angles: z44.array(z44.object({
9111
- id: z44.string().uuid(),
9112
- content: z44.string(),
9113
- creativeDirection: z44.string(),
9114
- actor: z44.string(),
9115
- actorId: z44.string().nullable().optional()
9603
+ angles: z46.array(z46.object({
9604
+ id: z46.string().uuid(),
9605
+ content: z46.string(),
9606
+ creativeDirection: z46.string(),
9607
+ actor: z46.string(),
9608
+ actorId: z46.string().nullable().optional()
9116
9609
  }).passthrough()).optional(),
9117
- panels: z44.array(z44.object({
9118
- beatId: z44.string(),
9119
- voiceover: z44.string(),
9120
- visualDirection: z44.string()
9610
+ panels: z46.array(z46.object({
9611
+ beatId: z46.string(),
9612
+ voiceover: z46.string(),
9613
+ visualDirection: z46.string()
9121
9614
  }).passthrough()).optional()
9122
9615
  }).optional()
9123
9616
  }).passthrough();
@@ -9158,7 +9651,6 @@ __name(resolveStatusTaskKind, "resolveStatusTaskKind");
9158
9651
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
9159
9652
  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) => {
9160
9653
  try {
9161
- assertVideoPresetDurationCompatibility(input.videoPreset, input.durationSeconds);
9162
9654
  if (!input.images && !input.projectId) {
9163
9655
  throw new Error("images es requerido salvo que env\xEDes projectId.");
9164
9656
  }
@@ -9558,58 +10050,58 @@ __name(registerTaskTools, "registerTaskTools");
9558
10050
 
9559
10051
  // src/tools/creatives.ts
9560
10052
  import { randomUUID } from "crypto";
9561
- import { z as z45 } from "zod";
10053
+ import { z as z47 } from "zod";
9562
10054
  var CreativeRefs = {
9563
10055
  images: ImagesInput,
9564
10056
  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.'),
9565
10057
  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.'),
9566
- 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.'),
9567
- 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.'),
9568
- 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".'),
9569
- 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.'),
9570
- 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."),
9571
- 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.'),
9572
- 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.'),
9573
- isCombo: z45.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
9574
- 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."),
9575
- 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."),
9576
- 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."),
9577
- 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.'),
9578
- 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."),
9579
- 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.")
10058
+ 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.'),
10059
+ 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.'),
10060
+ 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".'),
10061
+ 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.'),
10062
+ 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."),
10063
+ 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.'),
10064
+ 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.'),
10065
+ isCombo: z47.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
10066
+ 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."),
10067
+ 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."),
10068
+ 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."),
10069
+ 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.'),
10070
+ 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."),
10071
+ 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.")
9580
10072
  };
9581
- var VideoInput = z45.object({
10073
+ var VideoInput = z47.object({
9582
10074
  images: CreativeRefs.images,
9583
- title: z45.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
9584
- 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".'),
10075
+ title: z47.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
10076
+ 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".'),
9585
10077
  language: CreativeRefs.language,
9586
10078
  country: CreativeRefs.country,
9587
- aspectRatio: z45.enum([
10079
+ aspectRatio: z47.enum([
9588
10080
  "9:16",
9589
10081
  "16:9",
9590
10082
  "Auto"
9591
10083
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
9592
- durationSeconds: z45.union([
9593
- z45.literal(15),
9594
- z45.literal(30)
10084
+ durationSeconds: z47.union([
10085
+ z47.literal(15),
10086
+ z47.literal(30)
9595
10087
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
9596
- 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).'),
10088
+ 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).'),
9597
10089
  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.'),
9598
10090
  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).'),
9599
- 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"].'),
10091
+ 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"].'),
9600
10092
  price: CreativeRefs.price,
9601
10093
  offer: CreativeRefs.offer,
9602
10094
  productContext: CreativeRefs.productContext,
9603
10095
  productLabel: CreativeRefs.productLabel,
9604
- 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.'),
9605
- 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.'),
9606
- 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."),
9607
- 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."),
10096
+ 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.'),
10097
+ 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.'),
10098
+ 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."),
10099
+ 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."),
9608
10100
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
9609
10101
  brandId: CreativeRefs.brandId,
9610
10102
  projectId: CreativeRefs.projectId
9611
10103
  }).strict();
9612
- var FreeVideoQualityEnum = z45.enum([
10104
+ var FreeVideoQualityEnum = z47.enum([
9613
10105
  "economical",
9614
10106
  "high_detail"
9615
10107
  ]);
@@ -9631,38 +10123,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
9631
10123
  15
9632
10124
  ]
9633
10125
  };
9634
- var FreeVideoInput = z45.object({
10126
+ var FreeVideoInput = z47.object({
9635
10127
  images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
9636
- 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."),
10128
+ 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."),
9637
10129
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
9638
- 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."),
10130
+ 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."),
9639
10131
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
9640
- generateAiMusic: z45.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
9641
- addSubtitles: z45.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
9642
- 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.")
10132
+ generateAiMusic: z47.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
10133
+ addSubtitles: z47.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
10134
+ 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.")
9643
10135
  }).strict();
9644
10136
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
9645
10137
  var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
9646
10138
  var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
9647
10139
  var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
9648
- var ImageInput = z45.object({
10140
+ var ImageInput = z47.object({
9649
10141
  images: CreativeRefs.images,
9650
- productTitle: z45.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
9651
- primaryAngle: z45.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
10142
+ productTitle: z47.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
10143
+ primaryAngle: z47.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
9652
10144
  buyerPersona: CreativeRefs.buyerPersona,
9653
10145
  language: CreativeRefs.language,
9654
10146
  country: CreativeRefs.country,
9655
- nVariants: z45.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
9656
- 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.'),
10147
+ nVariants: z47.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
10148
+ 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.'),
9657
10149
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
9658
10150
  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).`),
9659
10151
  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.`),
9660
- 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."),
10152
+ 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."),
9661
10153
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe(`Opcional. Relaci\xF3n de aspecto. Valores: ${aspectRatioList}. Default "4:5" (vertical Instagram feed).`),
9662
- generateAdCopy: z45.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
9663
- 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."),
9664
- features: z45.array(z45.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
9665
- 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.'),
10154
+ generateAdCopy: z47.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
10155
+ 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."),
10156
+ features: z47.array(z47.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
10157
+ 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.'),
9666
10158
  price: CreativeRefs.price,
9667
10159
  compareAtPrice: CreativeRefs.compareAtPrice,
9668
10160
  offer: CreativeRefs.offer,
@@ -9676,16 +10168,16 @@ var ImageInput = z45.object({
9676
10168
  headline: CreativeRefs.headline,
9677
10169
  subheadline: CreativeRefs.subheadline
9678
10170
  });
9679
- var EditImageCreativeInput = z45.object({
9680
- taskId: z45.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
9681
- variantIndex: z45.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
9682
- 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".')
10171
+ var EditImageCreativeInput = z47.object({
10172
+ taskId: z47.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
10173
+ variantIndex: z47.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
10174
+ 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".')
9683
10175
  });
9684
- var LandingInput = z45.object({
10176
+ var LandingInput = z47.object({
9685
10177
  images: CreativeRefs.images,
9686
10178
  language: CreativeRefs.language,
9687
10179
  country: CreativeRefs.country,
9688
- 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.'),
10180
+ 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.'),
9689
10181
  buyerPersona: CreativeRefs.buyerPersona,
9690
10182
  price: CreativeRefs.price,
9691
10183
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -9736,7 +10228,6 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
9736
10228
  });
9737
10229
  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) => {
9738
10230
  try {
9739
- assertVideoPresetDurationCompatibility(input.videoPreset, input.durationSeconds);
9740
10231
  const resolved = resolveVideoPreset(input.videoPreset);
9741
10232
  const wireTier = resolveWireVideoTier(input.videoTier, input.durationSeconds);
9742
10233
  const { videoTier: _publicTier, videoResolution: _vr, projectId, ...rest } = input;
@@ -9811,8 +10302,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
9811
10302
  __name(registerCreativeTools, "registerCreativeTools");
9812
10303
 
9813
10304
  // src/tools/creative-variants.ts
9814
- import { z as z46 } from "zod";
9815
- var LandingSectionKeyEnum = z46.enum([
10305
+ import { z as z48 } from "zod";
10306
+ var LandingSectionKeyEnum = z48.enum([
9816
10307
  "problem_hook",
9817
10308
  "problem_cost",
9818
10309
  "solution_mechanism",
@@ -9852,24 +10343,24 @@ var LandingSectionKeyEnum = z46.enum([
9852
10343
  "lifestyle_fit",
9853
10344
  "join_lifestyle"
9854
10345
  ]);
9855
- var CreativeVariantKindEnum = z46.enum([
10346
+ var CreativeVariantKindEnum = z48.enum([
9856
10347
  "video",
9857
10348
  "image",
9858
10349
  "landing"
9859
10350
  ]);
9860
- var EstimateCreativeVariantInputBase = z46.object({
9861
- sourceTaskId: z46.string().uuid().describe("ID de la tarea original completada."),
10351
+ var EstimateCreativeVariantInputBase = z48.object({
10352
+ sourceTaskId: z48.string().uuid().describe("ID de la tarea original completada."),
9862
10353
  kind: CreativeVariantKindEnum,
9863
10354
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
9864
10355
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
9865
- nVariants: z46.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
10356
+ nVariants: z48.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
9866
10357
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
9867
- 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.")
10358
+ 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.")
9868
10359
  }).strict();
9869
10360
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
9870
10361
  if (input.kind === "video" && input.nVariants !== void 0) {
9871
10362
  ctx.addIssue({
9872
- code: z46.ZodIssueCode.custom,
10363
+ code: z48.ZodIssueCode.custom,
9873
10364
  path: [
9874
10365
  "nVariants"
9875
10366
  ],
@@ -9879,7 +10370,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9879
10370
  if (input.kind !== "video") {
9880
10371
  if (input.videoPreset !== void 0) {
9881
10372
  ctx.addIssue({
9882
- code: z46.ZodIssueCode.custom,
10373
+ code: z48.ZodIssueCode.custom,
9883
10374
  path: [
9884
10375
  "videoPreset"
9885
10376
  ],
@@ -9888,7 +10379,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9888
10379
  }
9889
10380
  if (input.videoResolution !== void 0) {
9890
10381
  ctx.addIssue({
9891
- code: z46.ZodIssueCode.custom,
10382
+ code: z48.ZodIssueCode.custom,
9892
10383
  path: [
9893
10384
  "videoResolution"
9894
10385
  ],
@@ -9898,7 +10389,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9898
10389
  }
9899
10390
  if (input.kind !== "image" && input.nVariants !== void 0) {
9900
10391
  ctx.addIssue({
9901
- code: z46.ZodIssueCode.custom,
10392
+ code: z48.ZodIssueCode.custom,
9902
10393
  path: [
9903
10394
  "nVariants"
9904
10395
  ],
@@ -9908,7 +10399,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9908
10399
  if (input.kind !== "landing") {
9909
10400
  if (input.sectionKey !== void 0) {
9910
10401
  ctx.addIssue({
9911
- code: z46.ZodIssueCode.custom,
10402
+ code: z48.ZodIssueCode.custom,
9912
10403
  path: [
9913
10404
  "sectionKey"
9914
10405
  ],
@@ -9917,7 +10408,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9917
10408
  }
9918
10409
  if (input.instructions !== void 0) {
9919
10410
  ctx.addIssue({
9920
- code: z46.ZodIssueCode.custom,
10411
+ code: z48.ZodIssueCode.custom,
9921
10412
  path: [
9922
10413
  "instructions"
9923
10414
  ],
@@ -9926,31 +10417,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9926
10417
  }
9927
10418
  }
9928
10419
  });
9929
- var CreateCreativeVariantInputBase = z46.object({
9930
- sourceTaskId: z46.string().uuid().describe("ID de la tarea original completada."),
10420
+ var CreateCreativeVariantInputBase = z48.object({
10421
+ sourceTaskId: z48.string().uuid().describe("ID de la tarea original completada."),
9931
10422
  kind: CreativeVariantKindEnum,
9932
- confirm: z46.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
9933
- 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."),
9934
- angle: z46.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
9935
- buyerPersona: z46.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
9936
- scriptNotes: z46.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
9937
- creativeDirection: z46.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
10423
+ confirm: z48.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
10424
+ 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."),
10425
+ angle: z48.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
10426
+ buyerPersona: z48.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
10427
+ scriptNotes: z48.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
10428
+ creativeDirection: z48.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
9938
10429
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
9939
10430
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
9940
- generateAiMusic: z46.boolean().optional().describe("Opcional para video."),
9941
- nVariants: z46.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
10431
+ generateAiMusic: z48.boolean().optional().describe("Opcional para video."),
10432
+ nVariants: z48.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
9942
10433
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
9943
10434
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
9944
10435
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
9945
10436
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
9946
- styleMix: z46.boolean().optional().describe("Opcional para imagen."),
10437
+ styleMix: z48.boolean().optional().describe("Opcional para imagen."),
9947
10438
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
9948
- 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.")
10439
+ 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.")
9949
10440
  }).strict();
9950
10441
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
9951
10442
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
9952
10443
  ctx.addIssue({
9953
- code: z46.ZodIssueCode.custom,
10444
+ code: z48.ZodIssueCode.custom,
9954
10445
  path: [
9955
10446
  "angle"
9956
10447
  ],
@@ -9959,7 +10450,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
9959
10450
  }
9960
10451
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
9961
10452
  ctx.addIssue({
9962
- code: z46.ZodIssueCode.custom,
10453
+ code: z48.ZodIssueCode.custom,
9963
10454
  path: [
9964
10455
  "instructions"
9965
10456
  ],
@@ -10005,7 +10496,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
10005
10496
  for (const field of fieldsByKind[input.kind]) {
10006
10497
  if (input[field] !== void 0) {
10007
10498
  ctx.addIssue({
10008
- code: z46.ZodIssueCode.custom,
10499
+ code: z48.ZodIssueCode.custom,
10009
10500
  path: [
10010
10501
  field
10011
10502
  ],
@@ -10122,27 +10613,27 @@ function registerCreativeVariantTools(server, api) {
10122
10613
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
10123
10614
 
10124
10615
  // src/tools/script-review.ts
10125
- import { z as z47 } from "zod";
10126
- var GetVideoScriptInput = z47.object({
10127
- taskId: z47.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
10128
- 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.')
10616
+ import { z as z49 } from "zod";
10617
+ var GetVideoScriptInput = z49.object({
10618
+ taskId: z49.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
10619
+ 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.')
10129
10620
  }).strict();
10130
10621
  var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
10131
10622
  var VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2 = 2e3;
10132
- var ApproveSceneEdit = z47.object({
10133
- index: z47.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
10134
- dialogue: z47.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
10623
+ var ApproveSceneEdit = z49.object({
10624
+ index: z49.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
10625
+ dialogue: z49.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
10135
10626
  message: "dialogue no puede ser s\xF3lo espacios"
10136
10627
  }).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.")
10137
10628
  }).strict();
10138
- var ApproveVideoScriptInput = z47.object({
10139
- taskId: z47.string().min(1).describe("ID de la tarea."),
10140
- 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.'),
10141
- 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.")
10629
+ var ApproveVideoScriptInput = z49.object({
10630
+ taskId: z49.string().min(1).describe("ID de la tarea."),
10631
+ 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.'),
10632
+ 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.")
10142
10633
  }).strict();
10143
- var RegenerateVideoScriptInput = z47.object({
10144
- taskId: z47.string().min(1).describe("ID de la tarea."),
10145
- 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.'),
10634
+ var RegenerateVideoScriptInput = z49.object({
10635
+ taskId: z49.string().min(1).describe("ID de la tarea."),
10636
+ 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.'),
10146
10637
  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).')
10147
10638
  }).strict();
10148
10639
  function registerScriptReviewTools(server, api) {
@@ -10182,9 +10673,9 @@ function registerScriptReviewTools(server, api) {
10182
10673
  __name(registerScriptReviewTools, "registerScriptReviewTools");
10183
10674
 
10184
10675
  // src/tools/connections.ts
10185
- import { z as z48 } from "zod";
10676
+ import { z as z50 } from "zod";
10186
10677
  function registerConnectionTools(server, api) {
10187
- 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 () => {
10678
+ 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 () => {
10188
10679
  try {
10189
10680
  const r = await api.getJson("/v1/shopify/shops");
10190
10681
  return asText(r);
@@ -10192,10 +10683,10 @@ function registerConnectionTools(server, api) {
10192
10683
  throw translateError(e);
10193
10684
  }
10194
10685
  });
10195
- 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({
10196
- shopDomain: z48.string().min(3).max(120),
10197
- query: z48.string().trim().max(120).optional(),
10198
- taskId: z48.string().uuid().optional()
10686
+ 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({
10687
+ shopDomain: z50.string().min(3).max(120),
10688
+ query: z50.string().trim().max(120).optional(),
10689
+ taskId: z50.string().uuid().optional()
10199
10690
  }).shape, async ({ shopDomain, query, taskId }) => {
10200
10691
  try {
10201
10692
  const params = new URLSearchParams({
@@ -10209,7 +10700,7 @@ function registerConnectionTools(server, api) {
10209
10700
  throw translateError(e);
10210
10701
  }
10211
10702
  });
10212
- 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 () => {
10703
+ 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 () => {
10213
10704
  try {
10214
10705
  const r = await api.getJson("/v1/facebook/ad-accounts");
10215
10706
  return asText(r);
@@ -10217,7 +10708,21 @@ function registerConnectionTools(server, api) {
10217
10708
  throw translateError(e);
10218
10709
  }
10219
10710
  });
10220
- 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 () => {
10711
+ server.registerTool("list_meta_destinations", {
10712
+ 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.",
10713
+ inputSchema: z50.object({}).shape,
10714
+ _meta: {
10715
+ "openai/widgetAccessible": true
10716
+ }
10717
+ }, async () => {
10718
+ try {
10719
+ const r = await api.getJson("/v1/facebook/destinations");
10720
+ return asText(r);
10721
+ } catch (e) {
10722
+ throw translateError(e);
10723
+ }
10724
+ });
10725
+ 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 () => {
10221
10726
  try {
10222
10727
  const r = await api.getJson("/v1/tiktok/advertisers");
10223
10728
  return asText(r);
@@ -10229,9 +10734,9 @@ function registerConnectionTools(server, api) {
10229
10734
  __name(registerConnectionTools, "registerConnectionTools");
10230
10735
 
10231
10736
  // src/tools/actors.ts
10232
- import { z as z49 } from "zod";
10737
+ import { z as z51 } from "zod";
10233
10738
  function registerActorTools(server, api) {
10234
- 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 () => {
10739
+ 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 () => {
10235
10740
  try {
10236
10741
  const rows = await api.getJson("/v1/actors");
10237
10742
  const actors = (Array.isArray(rows) ? rows : []).filter((actor) => actor.status === "ready").map(({ id, name, description, gender, voiceId }) => ({
@@ -10252,105 +10757,116 @@ function registerActorTools(server, api) {
10252
10757
  __name(registerActorTools, "registerActorTools");
10253
10758
 
10254
10759
  // src/tools/publish.ts
10255
- import { z as z50 } from "zod";
10256
- var ShopifyInput = z50.object({
10257
- taskId: z50.string().min(1),
10258
- shopDomain: z50.string().min(1),
10760
+ import { z as z52 } from "zod";
10761
+ var ShopifyInput = z52.object({
10762
+ taskId: z52.string().min(1),
10763
+ shopDomain: z52.string().min(1),
10259
10764
  /** Override: number of landing images to include. Default = ALL images the
10260
10765
  * task generated (the MCP fetches /v1/results first to count). Bounded
10261
10766
  * generously here; the backend validates each index against
10262
10767
  * landing.images.length and rejects out-of-range. */
10263
- numLandingImages: z50.number().int().min(1).max(20).optional(),
10264
- variantIndex: z50.number().int().min(0).optional(),
10768
+ numLandingImages: z52.number().int().min(1).max(20).optional(),
10769
+ variantIndex: z52.number().int().min(0).optional(),
10265
10770
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
10266
10771
  * When true, the landing renders variant-picker pills; when false, it ships
10267
10772
  * a single-option product page. Defaults to false. */
10268
- showVariantPicker: z50.boolean().optional(),
10773
+ showVariantPicker: z52.boolean().optional(),
10269
10774
  /** Default `auto` updates the product previously published for this task and
10270
10775
  * shop. Use `existing` with targetProductId to choose another product, or
10271
10776
  * `new` only when the user explicitly wants a duplicate/new listing. */
10272
- publicationMode: z50.enum([
10777
+ publicationMode: z52.enum([
10273
10778
  "auto",
10274
10779
  "existing",
10275
10780
  "new"
10276
10781
  ]).optional(),
10277
10782
  /** Shopify product GID, numeric id, or admin product URL. Required when
10278
10783
  * publicationMode is `existing`. */
10279
- targetProductId: z50.string().trim().min(1).max(300).optional()
10784
+ targetProductId: z52.string().trim().min(1).max(300).optional()
10280
10785
  });
10281
- var McpCampaignConfig = z50.object({
10786
+ var McpCampaignConfig = z52.object({
10282
10787
  /** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
10283
10788
  * converts to the account currency server-side. Do NOT pre-convert: on a COP
10284
10789
  * account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
10285
- dailyBudgetUsd: z50.number().positive().optional(),
10790
+ dailyBudgetUsd: z52.number().positive().optional(),
10286
10791
  /** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
10287
10792
  * ad set). TikTok takes only the first entry. */
10288
- targetCountries: z50.array(z50.string().length(2)).max(1).optional(),
10289
- campaignName: z50.string().trim().min(1).max(100).optional()
10793
+ targetCountries: z52.array(z52.string().length(2)).max(1).optional(),
10794
+ campaignName: z52.string().trim().min(1).max(100).optional()
10290
10795
  });
10291
10796
  var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
10292
- var MetaInput = z50.object({
10293
- taskId: z50.string().min(1),
10797
+ var MetaInput = z52.object({
10798
+ taskId: z52.string().min(1),
10799
+ /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
10800
+ portfolioId: z52.string().trim().min(1),
10801
+ /** Exact Facebook Page selected from `list_meta_destinations`. */
10802
+ pageId: z52.string().trim().min(1),
10294
10803
  adAccountId: McpAdAccountId,
10295
- pixelId: z50.string().min(1).optional(),
10296
- landingUrl: z50.string().url().optional(),
10804
+ pixelId: z52.string().min(1).optional(),
10805
+ landingUrl: z52.string().url().optional(),
10297
10806
  /** Select which angles to publish by 0-based index. Omit to publish all
10298
10807
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
10299
- angleIndexes: z50.array(z50.number().int().nonnegative()).optional(),
10808
+ angleIndexes: z52.array(z52.number().int().nonnegative()).optional(),
10300
10809
  campaignConfig: McpCampaignConfig.optional(),
10301
10810
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
10302
10811
  * uses the auto-generated copy attached to the task. Capped at 500 chars
10303
10812
  * (Meta's primary-text limit). */
10304
- primaryText: z50.string().trim().min(1).max(500).optional(),
10813
+ primaryText: z52.string().trim().min(1).max(500).optional(),
10305
10814
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
10306
10815
  * (wa.me) instead of a landing page. */
10307
- phoneNumber: z50.string().min(8).max(32).optional()
10816
+ phoneNumber: z52.string().min(8).max(32).optional()
10308
10817
  });
10309
- var GroupedTaskSource = z50.object({
10310
- type: z50.literal("task"),
10311
- taskId: z50.string().min(1),
10312
- angleIndex: z50.number().int().min(0).optional(),
10313
- videoVariant: z50.enum([
10818
+ var GroupedTaskSource = z52.object({
10819
+ type: z52.literal("task"),
10820
+ taskId: z52.string().min(1),
10821
+ angleIndex: z52.number().int().min(0).optional(),
10822
+ videoVariant: z52.enum([
10314
10823
  "with_subtitles",
10315
10824
  "without_subtitles"
10316
10825
  ]).optional(),
10317
- imageVariantIndex: z50.number().int().min(0).optional()
10318
- });
10319
- var GroupedMediaSource = z50.object({
10320
- type: z50.literal("media"),
10321
- url: z50.string().url().optional(),
10322
- path: z50.string().min(1).optional(),
10323
- thumbnailUrl: z50.string().url().optional(),
10324
- thumbnailPath: z50.string().min(1).optional()
10826
+ imageVariantIndex: z52.number().int().min(0).optional()
10827
+ });
10828
+ var GroupedMediaSource = z52.object({
10829
+ type: z52.literal("media"),
10830
+ 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."),
10831
+ 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".'),
10832
+ thumbnailUrl: z52.string().url().optional(),
10833
+ thumbnailPath: z52.string().min(1).optional()
10325
10834
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
10326
10835
  message: "media source needs exactly one of `url` or `path`"
10327
10836
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
10328
10837
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
10329
10838
  });
10330
- var GroupedCreative = z50.object({
10331
- type: z50.enum([
10839
+ var GroupedStagedMediaSource = z52.object({
10840
+ type: z52.literal("staged"),
10841
+ /** Returned by check_meta_media_upload after the user selects a local file. */
10842
+ stagingAssetId: z52.string().uuid(),
10843
+ thumbnailAssetId: z52.string().uuid().optional()
10844
+ });
10845
+ var GroupedCreative = z52.object({
10846
+ type: z52.enum([
10332
10847
  "image",
10333
10848
  "video"
10334
10849
  ]),
10335
- source: z50.union([
10850
+ source: z52.union([
10336
10851
  GroupedTaskSource,
10337
- GroupedMediaSource
10852
+ GroupedMediaSource,
10853
+ GroupedStagedMediaSource
10338
10854
  ]),
10339
- title: z50.string().max(120).optional(),
10340
- description: z50.string().max(500).optional(),
10855
+ title: z52.string().max(120).optional(),
10856
+ description: z52.string().max(500).optional(),
10341
10857
  cta: MetaCtaType.optional(),
10342
- creativeId: z50.string().min(1).optional()
10858
+ creativeId: z52.string().min(1).optional()
10343
10859
  });
10344
- var GroupedCampaignConfig = z50.object({
10860
+ var GroupedCampaignConfig = z52.object({
10345
10861
  campaignType: MetaCampaignType.default("sales"),
10346
10862
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
10347
10863
  budgetMode: MetaBudgetMode.default("campaign"),
10348
- campaignDailyBudget: z50.number().int().positive().optional(),
10349
- adSets: z50.array(z50.object({
10350
- tempId: z50.string().min(1).max(80),
10351
- name: z50.string().min(1).max(100),
10352
- dailyBudget: z50.number().int().positive().optional(),
10353
- creativeIds: z50.array(z50.string().min(1)).min(1)
10864
+ campaignDailyBudget: z52.number().int().positive().optional(),
10865
+ adSets: z52.array(z52.object({
10866
+ tempId: z52.string().min(1).max(80),
10867
+ name: z52.string().min(1).max(100),
10868
+ dailyBudget: z52.number().int().positive().optional(),
10869
+ creativeIds: z52.array(z52.string().min(1)).min(1)
10354
10870
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
10355
10871
  /**
10356
10872
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -10358,22 +10874,22 @@ var GroupedCampaignConfig = z50.object({
10358
10874
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
10359
10875
  * the API since #849 — unreachable from this tool.
10360
10876
  */
10361
- destination: z50.discriminatedUnion("type", [
10362
- z50.object({
10363
- type: z50.literal("landing"),
10364
- landingUrl: z50.string().url()
10877
+ destination: z52.discriminatedUnion("type", [
10878
+ z52.object({
10879
+ type: z52.literal("landing"),
10880
+ landingUrl: z52.string().url()
10365
10881
  }),
10366
- z50.object({
10367
- type: z50.literal("whatsapp"),
10368
- phoneNumber: z50.string().min(8).max(32)
10882
+ z52.object({
10883
+ type: z52.literal("whatsapp"),
10884
+ phoneNumber: z52.string().min(8).max(32)
10369
10885
  })
10370
10886
  ]),
10371
- saveAsDefault: z50.boolean().default(false),
10887
+ saveAsDefault: z52.boolean().default(false),
10372
10888
  budgetPolicy: MetaBudgetPolicy.optional()
10373
10889
  }).superRefine((value, ctx) => {
10374
10890
  if (value.budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
10375
10891
  ctx.addIssue({
10376
- code: z50.ZodIssueCode.custom,
10892
+ code: z52.ZodIssueCode.custom,
10377
10893
  path: [
10378
10894
  "campaignDailyBudget"
10379
10895
  ],
@@ -10384,7 +10900,7 @@ var GroupedCampaignConfig = z50.object({
10384
10900
  value.adSets.forEach((adSet, index) => {
10385
10901
  if (adSet.dailyBudget === void 0) {
10386
10902
  ctx.addIssue({
10387
- code: z50.ZodIssueCode.custom,
10903
+ code: z52.ZodIssueCode.custom,
10388
10904
  path: [
10389
10905
  "adSets",
10390
10906
  index,
@@ -10396,22 +10912,26 @@ var GroupedCampaignConfig = z50.object({
10396
10912
  });
10397
10913
  }
10398
10914
  });
10399
- var PublishCreativesToMetaInputBase = z50.object({
10400
- creatives: z50.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10915
+ var PublishCreativesToMetaInputBase = z52.object({
10916
+ creatives: z52.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10917
+ /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
10918
+ portfolioId: z52.string().trim().min(1),
10919
+ /** Exact Facebook Page selected from `list_meta_destinations`. */
10920
+ pageId: z52.string().trim().min(1),
10401
10921
  adAccountId: McpAdAccountId,
10402
- pixelId: z50.string().min(1).optional(),
10922
+ pixelId: z52.string().min(1).optional(),
10403
10923
  /** Required unless `campaignConfig.destination` is whatsapp — see the
10404
10924
  * superRefine below. Mirrors the API's `UploadToMetaRequest`. */
10405
- landingUrl: z50.string().url().optional(),
10406
- targetCountry: z50.string().length(2),
10407
- campaignName: z50.string().trim().min(1).max(100),
10925
+ landingUrl: z52.string().url().optional(),
10926
+ targetCountry: z52.string().length(2),
10927
+ campaignName: z52.string().trim().min(1).max(100),
10408
10928
  campaignConfig: GroupedCampaignConfig.optional()
10409
10929
  });
10410
10930
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
10411
10931
  const destination = value.campaignConfig?.destination;
10412
10932
  if (destination?.type !== "whatsapp" && !value.landingUrl) {
10413
10933
  ctx.addIssue({
10414
- code: z50.ZodIssueCode.custom,
10934
+ code: z52.ZodIssueCode.custom,
10415
10935
  path: [
10416
10936
  "landingUrl"
10417
10937
  ],
@@ -10420,7 +10940,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10420
10940
  }
10421
10941
  if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
10422
10942
  ctx.addIssue({
10423
- code: z50.ZodIssueCode.custom,
10943
+ code: z52.ZodIssueCode.custom,
10424
10944
  path: [
10425
10945
  "campaignConfig",
10426
10946
  "destination",
@@ -10430,46 +10950,50 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10430
10950
  });
10431
10951
  }
10432
10952
  });
10433
- var PresignUploadResponseSchema = z50.object({
10434
- uploadUrl: z50.string().url().refine((value) => {
10953
+ var PresignUploadResponseSchema = z52.object({
10954
+ uploadUrl: z52.string().url().refine((value) => {
10435
10955
  const url = new URL(value);
10436
10956
  return !url.username && !url.password && url.protocol === "https:";
10437
10957
  }, "presigned upload URL must be HTTPS without embedded credentials"),
10438
- publicUrl: z50.string().url().refine((value) => {
10958
+ publicUrl: z52.string().url().refine((value) => {
10439
10959
  const url = new URL(value);
10440
10960
  return !url.username && !url.password;
10441
- }, "public media URL must not contain embedded credentials")
10961
+ }, "public media URL must not contain embedded credentials"),
10962
+ stagingAssetId: z52.string().uuid()
10442
10963
  });
10443
- async function uploadResolvedMedia(api, media) {
10444
- if (media.url) return media.url;
10964
+ async function uploadResolvedMedia(api, media, assetRole) {
10445
10965
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
10446
10966
  const presigned = PresignUploadResponseSchema.parse(await api.postJson("/v1/ads/presign-upload", {
10447
10967
  contentType: media.mimetype,
10448
10968
  fileName: media.filename,
10449
- fileSize: media.buffer.byteLength
10969
+ fileSize: media.buffer.byteLength,
10970
+ purpose: "meta_staging",
10971
+ assetRole
10450
10972
  }));
10451
10973
  await api.putBytes(presigned.uploadUrl, media.buffer, media.mimetype);
10452
- return presigned.publicUrl;
10974
+ return {
10975
+ stagingAssetId: presigned.stagingAssetId
10976
+ };
10453
10977
  }
10454
10978
  __name(uploadResolvedMedia, "uploadResolvedMedia");
10455
- async function resolveAndUploadMedia(api, ref, expectedKind, transport) {
10979
+ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transport) {
10456
10980
  const media = await resolveMediaRef(ref, {
10457
10981
  transport,
10458
10982
  expectedKind
10459
10983
  });
10460
- return uploadResolvedMedia(api, media);
10984
+ return uploadResolvedMedia(api, media, assetRole);
10461
10985
  }
10462
10986
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
10463
- var TikTokInput = z50.object({
10464
- taskId: z50.string().min(1),
10465
- advertiserId: z50.string().min(1),
10466
- pixelId: z50.string().min(1).optional(),
10467
- landingUrl: z50.string().url().optional(),
10468
- angleIndexes: z50.array(z50.number().int().nonnegative()).optional(),
10987
+ var TikTokInput = z52.object({
10988
+ taskId: z52.string().min(1),
10989
+ advertiserId: z52.string().min(1),
10990
+ pixelId: z52.string().min(1).optional(),
10991
+ landingUrl: z52.string().url().optional(),
10992
+ angleIndexes: z52.array(z52.number().int().nonnegative()).optional(),
10469
10993
  campaignConfig: McpCampaignConfig.optional(),
10470
10994
  /** Override the ad's description (TikTok's body copy). If omitted,
10471
10995
  * ProductMaker uses the auto-generated copy attached to the task. */
10472
- primaryText: z50.string().trim().min(1).max(500).optional()
10996
+ primaryText: z52.string().trim().min(1).max(500).optional()
10473
10997
  });
10474
10998
  function registerPublishTools(server, api, opts = {}) {
10475
10999
  const transport = opts.transport ?? "http";
@@ -10508,7 +11032,7 @@ function registerPublishTools(server, api, opts = {}) {
10508
11032
  throw translateError(e);
10509
11033
  }
10510
11034
  });
10511
- 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 }) => {
11035
+ 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 }) => {
10512
11036
  try {
10513
11037
  const body = {
10514
11038
  ...rest
@@ -10536,7 +11060,7 @@ function registerPublishTools(server, api, opts = {}) {
10536
11060
  throw translateError(e);
10537
11061
  }
10538
11062
  });
10539
- 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) => {
11063
+ 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) => {
10540
11064
  try {
10541
11065
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
10542
11066
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -10562,25 +11086,44 @@ function registerPublishTools(server, api, opts = {}) {
10562
11086
  });
10563
11087
  continue;
10564
11088
  }
10565
- const mediaUrl = await resolveAndUploadMedia(api, {
11089
+ if (creative.source.type === "staged") {
11090
+ creatives.push({
11091
+ type: creative.type,
11092
+ source: {
11093
+ type: "media",
11094
+ mediaAssetId: creative.source.stagingAssetId,
11095
+ ...creative.source.thumbnailAssetId ? {
11096
+ thumbnailAssetId: creative.source.thumbnailAssetId
11097
+ } : {}
11098
+ },
11099
+ title: creative.title,
11100
+ description: creative.description,
11101
+ cta: creative.cta,
11102
+ creativeId
11103
+ });
11104
+ continue;
11105
+ }
11106
+ const stagedMedia = await resolveAndUploadMedia(api, {
10566
11107
  url: creative.source.url,
10567
11108
  path: creative.source.path
10568
- }, creative.type, transport);
10569
- let thumbnailUrl;
11109
+ }, creative.type, "media", transport);
11110
+ let stagedThumbnail;
10570
11111
  if (creative.type === "video") {
10571
11112
  if (creative.source.thumbnailUrl || creative.source.thumbnailPath) {
10572
- thumbnailUrl = await resolveAndUploadMedia(api, {
11113
+ stagedThumbnail = await resolveAndUploadMedia(api, {
10573
11114
  url: creative.source.thumbnailUrl,
10574
11115
  path: creative.source.thumbnailPath
10575
- }, "image", transport);
11116
+ }, "image", "thumbnail", transport);
10576
11117
  }
10577
11118
  }
10578
11119
  creatives.push({
10579
11120
  type: creative.type,
10580
11121
  source: {
10581
11122
  type: "media",
10582
- mediaUrl,
10583
- thumbnailUrl
11123
+ mediaAssetId: stagedMedia.stagingAssetId,
11124
+ ...stagedThumbnail ? {
11125
+ thumbnailAssetId: stagedThumbnail.stagingAssetId
11126
+ } : {}
10584
11127
  },
10585
11128
  title: creative.title,
10586
11129
  description: creative.description,
@@ -10590,6 +11133,8 @@ function registerPublishTools(server, api, opts = {}) {
10590
11133
  }
10591
11134
  const r = await api.postJson("/v1/facebook/campaigns/from-tasks", {
10592
11135
  creatives,
11136
+ portfolioId: input.portfolioId,
11137
+ pageId: input.pageId,
10593
11138
  adAccountId: input.adAccountId,
10594
11139
  pixelId: input.pixelId,
10595
11140
  landingUrl: input.landingUrl,
@@ -10636,6 +11181,17 @@ function renderShopifyPublishMarkdown(r) {
10636
11181
  if (r.productUrl) lines.push(`
10637
11182
  ### Producto
10638
11183
  [Ver producto](${r.productUrl})`);
11184
+ if (r.needsThemeSetup) {
11185
+ lines.push(r.themeEditorDeepLink ? `
11186
+ \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.");
11187
+ }
11188
+ if (r.tracking?.status === "migrated") {
11189
+ lines.push("\nTracking: \u2705 configuraci\xF3n anterior migrada autom\xE1ticamente. No necesitas editar el tema.");
11190
+ } else if (r.tracking?.status === "synced") {
11191
+ lines.push("\nTracking: \u2705 c\xF3digo manual sincronizado desde `theme.liquid` al layout de ProductMaker.");
11192
+ } else if (r.tracking?.status === "not_detected") {
11193
+ 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.");
11194
+ }
10639
11195
  if (!r.pageUrl && !r.productUrl && r.status !== "success") {
10640
11196
  lines.push("\n_La publicaci\xF3n a\xFAn est\xE1 proces\xE1ndose. Usa `get_task_status` para verificar progreso._");
10641
11197
  }
@@ -10679,14 +11235,162 @@ function toTikTokCampaignConfig(cfg) {
10679
11235
  }
10680
11236
  __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
10681
11237
 
11238
+ // src/tools/meta-upload.ts
11239
+ import { z as z53 } from "zod";
11240
+ var MetaUploadSessionInputBase = z53.object({
11241
+ mediaKind: z53.enum([
11242
+ "image",
11243
+ "video",
11244
+ "mixed"
11245
+ ]).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."),
11246
+ assetRole: z53.enum([
11247
+ "media",
11248
+ "thumbnail"
11249
+ ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
11250
+ maxFiles: z53.number().int().min(1).max(10).optional().describe("Optional batch limit. Use 1 for a single creative; mixed media defaults to 10.")
11251
+ });
11252
+ var OpenAiFileReference = z53.object({
11253
+ download_url: z53.string().url(),
11254
+ file_id: z53.string().min(1),
11255
+ mime_type: z53.string().min(1).optional(),
11256
+ file_name: z53.string().min(1).optional()
11257
+ }).strict();
11258
+ var MetaUploadSessionToolInputShape = {
11259
+ ...MetaUploadSessionInputBase.shape,
11260
+ 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.")
11261
+ };
11262
+ var MetaUploadSessionToolInput = z53.object(MetaUploadSessionToolInputShape).superRefine((value, ctx) => {
11263
+ if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
11264
+ ctx.addIssue({
11265
+ code: z53.ZodIssueCode.custom,
11266
+ path: [
11267
+ "mediaKind"
11268
+ ],
11269
+ message: "Meta thumbnails must be images"
11270
+ });
11271
+ }
11272
+ if (value.mediaKind !== "mixed" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
11273
+ ctx.addIssue({
11274
+ code: z53.ZodIssueCode.custom,
11275
+ path: [
11276
+ "maxFiles"
11277
+ ],
11278
+ message: "Image and video sessions accept exactly one file"
11279
+ });
11280
+ }
11281
+ });
11282
+ var MetaUploadSessionResponse2 = z53.object({
11283
+ sessionId: z53.string().uuid(),
11284
+ uploadPageUrl: z53.string().url(),
11285
+ mediaKind: z53.enum([
11286
+ "image",
11287
+ "video",
11288
+ "mixed"
11289
+ ]),
11290
+ assetRole: z53.enum([
11291
+ "media",
11292
+ "thumbnail"
11293
+ ]),
11294
+ maxFiles: z53.number().int().positive(),
11295
+ expiresAt: z53.string().datetime()
11296
+ });
11297
+ var MetaUploadStatusInput = z53.object({
11298
+ sessionId: z53.string().uuid().describe("The sessionId returned by request_meta_media_upload.")
11299
+ });
11300
+ var MetaUploadStatusResponse = z53.object({
11301
+ sessionId: z53.string().uuid(),
11302
+ status: z53.enum([
11303
+ "pending",
11304
+ "uploaded"
11305
+ ]),
11306
+ assets: z53.array(z53.object({
11307
+ stagingAssetId: z53.string().uuid(),
11308
+ mediaKind: z53.enum([
11309
+ "image",
11310
+ "video"
11311
+ ])
11312
+ })).max(10).default([]),
11313
+ stagingAssetId: z53.string().uuid().optional()
11314
+ });
11315
+ function uploadSessionText(result) {
11316
+ const mediaLabel = result.mediaKind === "mixed" ? "archivos" : result.mediaKind === "video" ? "video" : "imagen";
11317
+ return {
11318
+ content: [
11319
+ {
11320
+ type: "text",
11321
+ text: [
11322
+ `### Prepara tu ${mediaLabel} en ProductMaker`,
11323
+ `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})`,
11324
+ `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.`,
11325
+ `sessionId: \`${result.sessionId}\``,
11326
+ `Este enlace expira: ${result.expiresAt}`
11327
+ ].join("\n\n")
11328
+ }
11329
+ ]
11330
+ };
11331
+ }
11332
+ __name(uploadSessionText, "uploadSessionText");
11333
+ function registerMetaUploadTools(server, api) {
11334
+ server.registerTool("request_meta_media_upload", {
11335
+ title: "Solicitar carga segura de Meta",
11336
+ 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.",
11337
+ inputSchema: MetaUploadSessionToolInputShape,
11338
+ outputSchema: MetaUploadSessionResponse2,
11339
+ _meta: {
11340
+ ui: {
11341
+ resourceUri: META_UPLOAD_APP_RESOURCE_URI,
11342
+ visibility: [
11343
+ "model",
11344
+ "app"
11345
+ ]
11346
+ },
11347
+ "openai/outputTemplate": META_UPLOAD_APP_RESOURCE_URI,
11348
+ "openai/widgetAccessible": true,
11349
+ "openai/fileParams": [
11350
+ "files"
11351
+ ],
11352
+ "openai/toolInvocation/invoking": "Preparando carga segura\u2026",
11353
+ "openai/toolInvocation/invoked": "Carga segura lista"
11354
+ }
11355
+ }, async (rawInput) => {
11356
+ try {
11357
+ const input = MetaUploadSessionToolInput.parse(rawInput);
11358
+ const { files: _files, ...sessionInput } = input;
11359
+ const result = MetaUploadSessionResponse2.parse(await api.postJson("/v1/ads/meta-upload-sessions", sessionInput));
11360
+ return {
11361
+ structuredContent: result,
11362
+ ...uploadSessionText(result)
11363
+ };
11364
+ } catch (error) {
11365
+ throw translateError(error);
11366
+ }
11367
+ });
11368
+ server.registerTool("check_meta_media_upload", {
11369
+ 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.',
11370
+ inputSchema: MetaUploadStatusInput.shape,
11371
+ _meta: {
11372
+ "openai/widgetAccessible": true
11373
+ }
11374
+ }, async (rawInput) => {
11375
+ try {
11376
+ const input = MetaUploadStatusInput.parse(rawInput);
11377
+ const result = MetaUploadStatusResponse.parse(await api.getJson(`/v1/ads/meta-upload-sessions/${encodeURIComponent(input.sessionId)}`));
11378
+ return asText(result);
11379
+ } catch (error) {
11380
+ throw translateError(error);
11381
+ }
11382
+ });
11383
+ }
11384
+ __name(registerMetaUploadTools, "registerMetaUploadTools");
11385
+
10682
11386
  // src/tools/campaigns.ts
10683
- import { z as z51 } from "zod";
10684
- var MetaStatusInput = z51.object({
10685
- campaignId: z51.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
11387
+ import { z as z54 } from "zod";
11388
+ var MetaStatusInput = z54.object({
11389
+ campaignId: z54.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
10686
11390
  });
10687
- var TikTokStatusInput = z51.object({
10688
- campaignId: z51.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
10689
- advertiserId: z51.string().min(1).describe("The same advertiserId used to publish the campaign.")
11391
+ var TikTokStatusInput = z54.object({
11392
+ campaignId: z54.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
11393
+ advertiserId: z54.string().min(1).describe("The same advertiserId used to publish the campaign.")
10690
11394
  });
10691
11395
  function registerCampaignTools(server, api) {
10692
11396
  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) => {
@@ -10783,10 +11487,10 @@ function renderTikTokStatusMarkdown(r) {
10783
11487
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
10784
11488
 
10785
11489
  // ../../packages/product-economics/src/product-costing.ts
10786
- import { z as z53 } from "zod";
11490
+ import { z as z56 } from "zod";
10787
11491
 
10788
11492
  // ../../packages/product-economics/src/product-costing-countries.ts
10789
- import { z as z52 } from "zod";
11493
+ import { z as z55 } from "zod";
10790
11494
 
10791
11495
  // ../../packages/product-economics/src/product-costing-shipping-references.json
10792
11496
  var product_costing_shipping_references_default = {
@@ -10850,21 +11554,21 @@ var product_costing_shipping_references_default = {
10850
11554
  };
10851
11555
 
10852
11556
  // ../../packages/product-economics/src/product-costing-countries.ts
10853
- var ShippingReferenceSchema = z52.object({
10854
- reference: z52.number().positive(),
10855
- min: z52.number().nonnegative(),
10856
- max: z52.number().positive(),
10857
- updatedAt: z52.string().date(),
10858
- confidence: z52.enum([
11557
+ var ShippingReferenceSchema = z55.object({
11558
+ reference: z55.number().positive(),
11559
+ min: z55.number().nonnegative(),
11560
+ max: z55.number().positive(),
11561
+ updatedAt: z55.string().date(),
11562
+ confidence: z55.enum([
10859
11563
  "high",
10860
11564
  "medium",
10861
11565
  "medium-low"
10862
11566
  ]),
10863
- sources: z52.array(z52.string().url().startsWith("https://")).min(1).max(3)
11567
+ sources: z55.array(z55.string().url().startsWith("https://")).min(1).max(3)
10864
11568
  }).superRefine(({ reference, min, max }, context) => {
10865
11569
  if (min > reference || reference > max) {
10866
11570
  context.addIssue({
10867
- code: z52.ZodIssueCode.custom,
11571
+ code: z55.ZodIssueCode.custom,
10868
11572
  message: "shipping_reference_outside_range",
10869
11573
  path: [
10870
11574
  "reference"
@@ -10872,7 +11576,7 @@ var ShippingReferenceSchema = z52.object({
10872
11576
  });
10873
11577
  }
10874
11578
  });
10875
- var SHIPPING_REFERENCES = z52.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
11579
+ var SHIPPING_REFERENCES = z55.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
10876
11580
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
10877
11581
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
10878
11582
  "MX",
@@ -11054,18 +11758,18 @@ function getProductCostingCountryName(country) {
11054
11758
  __name(getProductCostingCountryName, "getProductCostingCountryName");
11055
11759
 
11056
11760
  // ../../packages/product-economics/src/product-costing.ts
11057
- var ProductCostingInputSchema = z53.object({
11058
- country: z53.string().refine(isProductCostingCountryCode, "unsupported_country"),
11059
- supplierCost: z53.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
11060
- shippingCost: z53.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
11061
- returnRate: z53.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11062
- cancellationRate: z53.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11063
- adsManagerCpa: z53.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
11064
- desiredMargin: z53.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
11761
+ var ProductCostingInputSchema = z56.object({
11762
+ country: z56.string().refine(isProductCostingCountryCode, "unsupported_country"),
11763
+ supplierCost: z56.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
11764
+ shippingCost: z56.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
11765
+ returnRate: z56.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11766
+ cancellationRate: z56.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11767
+ adsManagerCpa: z56.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
11768
+ desiredMargin: z56.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
11065
11769
  }).superRefine(({ returnRate, cancellationRate }, context) => {
11066
11770
  if (returnRate + cancellationRate >= 1) {
11067
11771
  context.addIssue({
11068
- code: z53.ZodIssueCode.custom,
11772
+ code: z56.ZodIssueCode.custom,
11069
11773
  message: "delivery_rate_positive",
11070
11774
  path: [
11071
11775
  "cancellationRate"
@@ -11132,14 +11836,14 @@ function calculateProductCosting(rawInput) {
11132
11836
  __name(calculateProductCosting, "calculateProductCosting");
11133
11837
 
11134
11838
  // src/tools/product-costing.ts
11135
- import { z as z54 } from "zod";
11136
- var optionalAmount = /* @__PURE__ */ __name((description) => z54.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
11137
- var optionalPercent = /* @__PURE__ */ __name((description) => z54.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
11138
- var ProductCostingToolInput = z54.object({
11139
- country: z54.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
11839
+ import { z as z57 } from "zod";
11840
+ var optionalAmount = /* @__PURE__ */ __name((description) => z57.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
11841
+ var optionalPercent = /* @__PURE__ */ __name((description) => z57.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
11842
+ var ProductCostingToolInput = z57.object({
11843
+ country: z57.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
11140
11844
  message: "Pa\xEDs no soportado por ProductMaker."
11141
11845
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
11142
- 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."),
11846
+ 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."),
11143
11847
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
11144
11848
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
11145
11849
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -11271,7 +11975,7 @@ function formatPercent(value) {
11271
11975
  __name(formatPercent, "formatPercent");
11272
11976
 
11273
11977
  // src/tools/product-research.ts
11274
- import { z as z55 } from "zod";
11978
+ import { z as z58 } from "zod";
11275
11979
 
11276
11980
  // src/tools/product-research-idempotency.ts
11277
11981
  import { createHash as createHash2 } from "crypto";
@@ -11387,17 +12091,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
11387
12091
  ]);
11388
12092
  var POLL_INTERVAL_MS = 5e3;
11389
12093
  var MAX_WAIT_SECONDS = 120;
11390
- var FindInput = z55.object({
12094
+ var FindInput = z58.object({
11391
12095
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
11392
12096
  categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
11393
- 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.`),
11394
- 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.")
12097
+ 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.`),
12098
+ 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.")
11395
12099
  });
11396
- var GetInput = z55.object({
11397
- runId: z55.string().uuid().describe("ID del run devuelto por find_winning_products.")
12100
+ var GetInput = z58.object({
12101
+ runId: z58.string().uuid().describe("ID del run devuelto por find_winning_products.")
11398
12102
  });
11399
- var ListInput = z55.object({
11400
- limit: z55.number().int().min(1).max(20).default(10)
12103
+ var ListInput = z58.object({
12104
+ limit: z58.number().int().min(1).max(20).default(10)
11401
12105
  });
11402
12106
  function textResult(text) {
11403
12107
  return {
@@ -11489,14 +12193,14 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
11489
12193
  __name(registerProductResearchTools, "registerProductResearchTools");
11490
12194
 
11491
12195
  // src/tools/dropshipping-projects.ts
11492
- import { z as z56 } from "zod";
11493
- var ProjectId = z56.string().uuid();
11494
- var DropshippingProjectStatus = z56.enum([
12196
+ import { z as z59 } from "zod";
12197
+ var ProjectId = z59.string().uuid();
12198
+ var DropshippingProjectStatus = z59.enum([
11495
12199
  "active",
11496
12200
  "completed",
11497
12201
  "archived"
11498
12202
  ]);
11499
- var DropshippingProjectPhase = z56.enum([
12203
+ var DropshippingProjectPhase = z59.enum([
11500
12204
  "research",
11501
12205
  "costing",
11502
12206
  "media",
@@ -11505,21 +12209,21 @@ var DropshippingProjectPhase = z56.enum([
11505
12209
  "advertising",
11506
12210
  "complete"
11507
12211
  ]);
11508
- var DropshippingRewardMilestone = z56.enum([
12212
+ var DropshippingRewardMilestone = z59.enum([
11509
12213
  "product_selected",
11510
12214
  "costing_saved",
11511
12215
  "product_media_ready",
11512
12216
  "creatives_completed",
11513
12217
  "campaign_created"
11514
12218
  ]);
11515
- var TargetCountry = z56.enum([
12219
+ var TargetCountry = z59.enum([
11516
12220
  "CO",
11517
12221
  "MX",
11518
12222
  "PE",
11519
12223
  "EC",
11520
12224
  "CL"
11521
12225
  ]);
11522
- var ResearchCategory = z56.enum([
12226
+ var ResearchCategory = z59.enum([
11523
12227
  "auto",
11524
12228
  "automotive",
11525
12229
  "kitchen",
@@ -11534,7 +12238,7 @@ var ResearchCategory = z56.enum([
11534
12238
  "garden",
11535
12239
  "cleaning"
11536
12240
  ]);
11537
- var HttpUrl3 = z56.string().url().max(2e3).refine((value) => {
12241
+ var HttpUrl3 = z59.string().url().max(2e3).refine((value) => {
11538
12242
  try {
11539
12243
  return [
11540
12244
  "http:",
@@ -11544,46 +12248,46 @@ var HttpUrl3 = z56.string().url().max(2e3).refine((value) => {
11544
12248
  return false;
11545
12249
  }
11546
12250
  }, "URL must use HTTP(S)");
11547
- var ResearchCandidateOrigin = z56.object({
11548
- type: z56.literal("research_candidate"),
12251
+ var ResearchCandidateOrigin = z59.object({
12252
+ type: z59.literal("research_candidate"),
11549
12253
  runId: ProjectId,
11550
- candidateSourceId: z56.string().min(1).max(160)
12254
+ candidateSourceId: z59.string().min(1).max(160)
11551
12255
  });
11552
- var TaskOrigin = z56.object({
11553
- type: z56.literal("task"),
12256
+ var TaskOrigin = z59.object({
12257
+ type: z59.literal("task"),
11554
12258
  taskId: ProjectId
11555
12259
  });
11556
- var ManualOrigin = z56.object({
11557
- type: z56.literal("manual"),
12260
+ var ManualOrigin = z59.object({
12261
+ type: z59.literal("manual"),
11558
12262
  clientRequestId: ProjectId,
11559
- country: z56.string().min(2).max(3).optional()
12263
+ country: z59.string().min(2).max(3).optional()
11560
12264
  });
11561
- var StartProjectInput = z56.object({
11562
- origin: z56.discriminatedUnion("type", [
12265
+ var StartProjectInput = z59.object({
12266
+ origin: z59.discriminatedUnion("type", [
11563
12267
  ResearchCandidateOrigin,
11564
12268
  TaskOrigin,
11565
12269
  ManualOrigin
11566
12270
  ]),
11567
- productName: z56.string().trim().min(1).max(240).optional(),
12271
+ productName: z59.string().trim().min(1).max(240).optional(),
11568
12272
  productUrl: HttpUrl3.optional(),
11569
- country: z56.string().min(2).max(3).optional(),
11570
- languageCode: z56.string().min(2).max(16).optional()
12273
+ country: z59.string().min(2).max(3).optional(),
12274
+ languageCode: z59.string().min(2).max(16).optional()
11571
12275
  }).strict();
11572
- var CostProjectInput = z56.object({
12276
+ var CostProjectInput = z59.object({
11573
12277
  projectId: ProjectId,
11574
- version: z56.number().int().positive(),
11575
- country: z56.string().trim().min(2).max(3),
11576
- supplierCost: z56.number().finite().positive(),
11577
- shippingCost: z56.number().finite().nonnegative(),
11578
- returnRate: z56.number().finite().min(0).lt(1),
11579
- cancellationRate: z56.number().finite().min(0).lt(1),
11580
- adsManagerCpa: z56.number().finite().nonnegative(),
11581
- desiredMargin: z56.number().finite().min(0).lt(1)
11582
- });
11583
- var GuideInput = z56.object({
12278
+ version: z59.number().int().positive(),
12279
+ country: z59.string().trim().min(2).max(3),
12280
+ supplierCost: z59.number().finite().positive(),
12281
+ shippingCost: z59.number().finite().nonnegative(),
12282
+ returnRate: z59.number().finite().min(0).lt(1),
12283
+ cancellationRate: z59.number().finite().min(0).lt(1),
12284
+ adsManagerCpa: z59.number().finite().nonnegative(),
12285
+ desiredMargin: z59.number().finite().min(0).lt(1)
12286
+ });
12287
+ var GuideInput = z59.object({
11584
12288
  projectId: ProjectId.optional()
11585
12289
  }).strict();
11586
- var DropshippingProjectNextAction = z56.discriminatedUnion("kind", [
12290
+ var DropshippingProjectNextAction = z59.discriminatedUnion("kind", [
11587
12291
  "cost_product",
11588
12292
  "provide_product_media",
11589
12293
  "generate_creatives",
@@ -11597,78 +12301,78 @@ var DropshippingProjectNextAction = z56.discriminatedUnion("kind", [
11597
12301
  "create_meta_campaign",
11598
12302
  "repair_meta_campaign",
11599
12303
  "review_paused_campaign"
11600
- ].map((kind) => z56.object({
11601
- kind: z56.literal(kind),
11602
- href: z56.string().min(1)
12304
+ ].map((kind) => z59.object({
12305
+ kind: z59.literal(kind),
12306
+ href: z59.string().min(1)
11603
12307
  })));
11604
- var CostingInputSchema = z56.object({
11605
- country: z56.string().min(2).max(3),
11606
- supplierCost: z56.number().finite().positive(),
11607
- shippingCost: z56.number().finite().nonnegative(),
11608
- returnRate: z56.number().finite().min(0).lt(1),
11609
- cancellationRate: z56.number().finite().min(0).lt(1),
11610
- adsManagerCpa: z56.number().finite().nonnegative(),
11611
- desiredMargin: z56.number().finite().min(0).lt(1)
12308
+ var CostingInputSchema = z59.object({
12309
+ country: z59.string().min(2).max(3),
12310
+ supplierCost: z59.number().finite().positive(),
12311
+ shippingCost: z59.number().finite().nonnegative(),
12312
+ returnRate: z59.number().finite().min(0).lt(1),
12313
+ cancellationRate: z59.number().finite().min(0).lt(1),
12314
+ adsManagerCpa: z59.number().finite().nonnegative(),
12315
+ desiredMargin: z59.number().finite().min(0).lt(1)
11612
12316
  }).superRefine(({ returnRate, cancellationRate }, context) => {
11613
12317
  if (returnRate + cancellationRate >= 1) context.addIssue({
11614
- code: z56.ZodIssueCode.custom,
12318
+ code: z59.ZodIssueCode.custom,
11615
12319
  path: [
11616
12320
  "cancellationRate"
11617
12321
  ]
11618
12322
  });
11619
12323
  });
11620
- var CostingResultSchema = z56.object({
11621
- country: z56.string().min(2).max(3),
11622
- deliveryRate: z56.number().finite(),
11623
- adjustedShippingCost: z56.number().finite(),
11624
- adjustedCpa: z56.number().finite(),
11625
- totalCost: z56.number().finite(),
11626
- suggestedPrice: z56.number().finite(),
11627
- baseCommercialPrice: z56.number().finite(),
11628
- minimumSafePrice: z56.number().finite(),
11629
- commercialPrice: z56.number().finite(),
11630
- comparisonPrice: z56.number().finite(),
11631
- estimatedProfit: z56.number().finite(),
11632
- actualMargin: z56.number().finite(),
11633
- breakEvenAdsManagerCpa: z56.number().finite(),
11634
- wasPriceAutoAdjusted: z56.boolean(),
11635
- profitabilityStatus: z56.literal("healthy")
11636
- });
11637
- var RewardProgressSchema = z56.object({
11638
- campaignKey: z56.literal("dropshipping-launch-v1"),
11639
- totalCredits: z56.number().int().positive(),
11640
- earnedCredits: z56.number().int().nonnegative(),
11641
- milestones: z56.array(z56.object({
12324
+ var CostingResultSchema = z59.object({
12325
+ country: z59.string().min(2).max(3),
12326
+ deliveryRate: z59.number().finite(),
12327
+ adjustedShippingCost: z59.number().finite(),
12328
+ adjustedCpa: z59.number().finite(),
12329
+ totalCost: z59.number().finite(),
12330
+ suggestedPrice: z59.number().finite(),
12331
+ baseCommercialPrice: z59.number().finite(),
12332
+ minimumSafePrice: z59.number().finite(),
12333
+ commercialPrice: z59.number().finite(),
12334
+ comparisonPrice: z59.number().finite(),
12335
+ estimatedProfit: z59.number().finite(),
12336
+ actualMargin: z59.number().finite(),
12337
+ breakEvenAdsManagerCpa: z59.number().finite(),
12338
+ wasPriceAutoAdjusted: z59.boolean(),
12339
+ profitabilityStatus: z59.literal("healthy")
12340
+ });
12341
+ var RewardProgressSchema = z59.object({
12342
+ campaignKey: z59.literal("dropshipping-launch-v1"),
12343
+ totalCredits: z59.number().int().positive(),
12344
+ earnedCredits: z59.number().int().nonnegative(),
12345
+ milestones: z59.array(z59.object({
11642
12346
  milestone: DropshippingRewardMilestone,
11643
- credits: z56.number().int().positive(),
11644
- available: z56.boolean().optional(),
11645
- earned: z56.boolean(),
12347
+ credits: z59.number().int().positive(),
12348
+ available: z59.boolean().optional(),
12349
+ earned: z59.boolean(),
11646
12350
  grantId: ProjectId.nullable(),
11647
- grantedAt: z56.string().datetime().nullable()
12351
+ grantedAt: z59.string().datetime().nullable()
11648
12352
  })).length(DropshippingRewardMilestone.options.length)
11649
12353
  });
11650
- var ProjectResponseSchema = z56.object({
12354
+ var ProjectResponseSchema = z59.object({
11651
12355
  id: ProjectId,
11652
12356
  taskId: ProjectId.nullable(),
11653
12357
  status: DropshippingProjectStatus,
11654
- originKey: z56.string().min(1),
12358
+ originKey: z59.string().min(1),
11655
12359
  researchRunId: ProjectId.nullable(),
11656
- candidateSourceId: z56.string().nullable(),
11657
- productName: z56.string().nullable(),
12360
+ candidateSourceId: z59.string().nullable(),
12361
+ productName: z59.string().nullable(),
11658
12362
  productUrl: HttpUrl3.nullable(),
11659
12363
  referenceImageUrl: HttpUrl3.nullable(),
11660
- targetCountry: z56.string().nullable(),
11661
- languageCode: z56.string().nullable(),
11662
- hasReferenceImage: z56.boolean(),
12364
+ targetCountry: z59.string().nullable(),
12365
+ languageCode: z59.string().nullable(),
12366
+ hasReferenceImage: z59.boolean(),
11663
12367
  costingInput: CostingInputSchema.nullable(),
11664
12368
  costingResult: CostingResultSchema.nullable(),
11665
- costingVersion: z56.number().int().positive().nullable(),
11666
- version: z56.number().int().positive(),
12369
+ costingVersion: z59.number().int().positive().nullable(),
12370
+ version: z59.number().int().positive(),
11667
12371
  phase: DropshippingProjectPhase,
11668
12372
  nextAction: DropshippingProjectNextAction.nullable(),
11669
12373
  rewards: RewardProgressSchema,
11670
- createdAt: z56.string().datetime(),
11671
- updatedAt: z56.string().datetime()
12374
+ createdAt: z59.string().datetime(),
12375
+ updatedAt: z59.string().datetime()
11672
12376
  }).passthrough();
11673
12377
  function projectText(raw) {
11674
12378
  const project = ProjectResponseSchema.parse(raw);
@@ -11753,6 +12457,7 @@ function createServer(opts) {
11753
12457
  }
11754
12458
  });
11755
12459
  const api = new ApiClient(apiBaseUrl, apiKey);
12460
+ registerMetaUploadAppResource(server);
11756
12461
  registerProductCostingTools(server);
11757
12462
  registerProductResearchTools(server, api);
11758
12463
  registerDropshippingProjectTools(server, api);
@@ -11770,6 +12475,7 @@ function createServer(opts) {
11770
12475
  registerPublishTools(server, api, {
11771
12476
  transport
11772
12477
  });
12478
+ registerMetaUploadTools(server, api);
11773
12479
  registerCampaignTools(server, api);
11774
12480
  return server;
11775
12481
  }