@productmaker/mcp 2.4.0 → 2.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/stdio.js CHANGED
@@ -150,10 +150,60 @@ var ApiClient = class {
150
150
  };
151
151
 
152
152
  // src/index.ts
153
- var MCP_VERSION = "2.4.0";
153
+ var MCP_VERSION = "2.8.3";
154
+
155
+ // src/meta-upload-app.ts
156
+ import { readFileSync } from "fs";
157
+ import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
158
+ var META_UPLOAD_APP_RESOURCE_URI = "ui://meta-upload/v1.html";
159
+ var META_UPLOAD_APP_NAME = "Meta upload app";
160
+ var META_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
161
+ var META_UPLOAD_APP_CSP = {
162
+ connectDomains: [
163
+ "https://productmaker.app",
164
+ "https://www.productmaker.app",
165
+ "https://*.r2.cloudflarestorage.com",
166
+ "https://*.oaiusercontent.com"
167
+ ]
168
+ };
169
+ function readMetaUploadAppHtml(htmlUrl = META_UPLOAD_APP_HTML_URL) {
170
+ try {
171
+ return readFileSync(htmlUrl, "utf8");
172
+ } catch (error) {
173
+ const message = error instanceof Error ? error.message : String(error);
174
+ throw new Error(`Missing packaged Meta upload app HTML at ${htmlUrl.pathname}. Rebuild @productmaker/mcp so ${META_UPLOAD_APP_RESOURCE_URI} is bundled. Original error: ${message}`);
175
+ }
176
+ }
177
+ __name(readMetaUploadAppHtml, "readMetaUploadAppHtml");
178
+ function registerMetaUploadAppResource(server) {
179
+ const html = readMetaUploadAppHtml();
180
+ registerAppResource(server, META_UPLOAD_APP_NAME, META_UPLOAD_APP_RESOURCE_URI, {
181
+ title: "Meta upload app",
182
+ mimeType: RESOURCE_MIME_TYPE,
183
+ _meta: {
184
+ ui: {
185
+ csp: META_UPLOAD_APP_CSP
186
+ }
187
+ }
188
+ }, async () => ({
189
+ contents: [
190
+ {
191
+ uri: META_UPLOAD_APP_RESOURCE_URI,
192
+ mimeType: RESOURCE_MIME_TYPE,
193
+ text: html,
194
+ _meta: {
195
+ ui: {
196
+ csp: META_UPLOAD_APP_CSP
197
+ }
198
+ }
199
+ }
200
+ ]
201
+ }));
202
+ }
203
+ __name(registerMetaUploadAppResource, "registerMetaUploadAppResource");
154
204
 
155
205
  // src/tools/tasks.ts
156
- import { z as z44 } from "zod";
206
+ import { z as z45 } from "zod";
157
207
 
158
208
  // src/resolve-image.ts
159
209
  import { lookup as dnsLookup } from "dns/promises";
@@ -3717,6 +3767,25 @@ var ALLOWED_MEDIA_CONTENT_TYPES = [
3717
3767
  "audio/wav",
3718
3768
  "audio/x-wav"
3719
3769
  ];
3770
+ var META_STAGING_IMAGE_CONTENT_TYPES = /* @__PURE__ */ new Set([
3771
+ "image/jpeg",
3772
+ "image/png",
3773
+ "image/webp"
3774
+ ]);
3775
+ var META_STAGING_MEDIA_CONTENT_TYPES = /* @__PURE__ */ new Set([
3776
+ ...META_STAGING_IMAGE_CONTENT_TYPES,
3777
+ "video/mp4",
3778
+ "video/quicktime",
3779
+ "video/webm"
3780
+ ]);
3781
+ var META_STAGING_CONTENT_TYPES = [
3782
+ "image/jpeg",
3783
+ "image/png",
3784
+ "image/webp",
3785
+ "video/mp4",
3786
+ "video/quicktime",
3787
+ "video/webm"
3788
+ ];
3720
3789
  var PresignUploadRequest = z31.object({
3721
3790
  /** MIME type of the file to upload */
3722
3791
  contentType: z31.enum(ALLOWED_MEDIA_CONTENT_TYPES),
@@ -3732,8 +3801,51 @@ var PresignUploadRequest = z31.object({
3732
3801
  purpose: z31.enum([
3733
3802
  "ads",
3734
3803
  "service",
3735
- "actor"
3804
+ "actor",
3805
+ "meta_staging"
3806
+ ]).optional(),
3807
+ /** Required for purpose=meta_staging so media and thumbnails can be checked independently. */
3808
+ assetRole: z31.enum([
3809
+ "media",
3810
+ "thumbnail"
3736
3811
  ]).optional()
3812
+ }).superRefine((value, ctx) => {
3813
+ if (value.purpose === "meta_staging") {
3814
+ if (!value.assetRole) {
3815
+ ctx.addIssue({
3816
+ code: z31.ZodIssueCode.custom,
3817
+ path: [
3818
+ "assetRole"
3819
+ ],
3820
+ message: "assetRole is required for Meta staging uploads"
3821
+ });
3822
+ } else if (!META_STAGING_MEDIA_CONTENT_TYPES.has(value.contentType)) {
3823
+ ctx.addIssue({
3824
+ code: z31.ZodIssueCode.custom,
3825
+ path: [
3826
+ "contentType"
3827
+ ],
3828
+ message: "Meta staging accepts only image or video media"
3829
+ });
3830
+ } else if (value.assetRole === "thumbnail" && !META_STAGING_IMAGE_CONTENT_TYPES.has(value.contentType)) {
3831
+ ctx.addIssue({
3832
+ code: z31.ZodIssueCode.custom,
3833
+ path: [
3834
+ "contentType"
3835
+ ],
3836
+ message: "Meta staging thumbnails must be images"
3837
+ });
3838
+ }
3839
+ }
3840
+ if (value.purpose !== "meta_staging" && value.assetRole) {
3841
+ ctx.addIssue({
3842
+ code: z31.ZodIssueCode.custom,
3843
+ path: [
3844
+ "assetRole"
3845
+ ],
3846
+ message: "assetRole is only valid for Meta staging uploads"
3847
+ });
3848
+ }
3737
3849
  });
3738
3850
  var PresignUploadResponse = z31.object({
3739
3851
  /** Presigned S3 PUT URL — browser sends file directly here */
@@ -3743,8 +3855,23 @@ var PresignUploadResponse = z31.object({
3743
3855
  /** S3 key */
3744
3856
  key: z31.string(),
3745
3857
  /** Present only for purpose=service. */
3746
- assetToken: z31.string().min(1).optional()
3858
+ assetToken: z31.string().min(1).optional(),
3859
+ /** Present only for purpose=meta_staging. */
3860
+ stagingAssetId: z31.string().uuid().optional()
3747
3861
  });
3862
+ var MetaUploadMediaKind = z31.enum([
3863
+ "image",
3864
+ "video",
3865
+ "mixed"
3866
+ ]);
3867
+ var META_UPLOAD_MAX_FILES = CAMPAIGN_LIMITS.MAX_CREATIVES;
3868
+ var MetaUploadAssetSummary = z31.object({
3869
+ stagingAssetId: z31.string().uuid(),
3870
+ mediaKind: z31.enum([
3871
+ "image",
3872
+ "video"
3873
+ ])
3874
+ }).strict();
3748
3875
  var MetaCampaignType = z31.enum([
3749
3876
  "sales",
3750
3877
  "traffic",
@@ -3770,6 +3897,81 @@ var HttpUrl = z31.string().url().refine((value) => {
3770
3897
  return false;
3771
3898
  }
3772
3899
  }, "URL must use http or https");
3900
+ var MetaUploadSessionRequest = z31.object({
3901
+ /** The browser page will require a file of this media kind. */
3902
+ mediaKind: MetaUploadMediaKind,
3903
+ /** The staged object role; media is the normal creative input. */
3904
+ assetRole: z31.enum([
3905
+ "media",
3906
+ "thumbnail"
3907
+ ]).default("media"),
3908
+ /** Maximum number of files; the API chooses 10 for mixed and 1 otherwise when omitted. */
3909
+ maxFiles: z31.number().int().min(1).max(META_UPLOAD_MAX_FILES).optional()
3910
+ }).strict().superRefine((value, ctx) => {
3911
+ if (value.mediaKind === "mixed" && value.assetRole === "thumbnail") {
3912
+ ctx.addIssue({
3913
+ code: z31.ZodIssueCode.custom,
3914
+ path: [
3915
+ "mediaKind"
3916
+ ],
3917
+ message: "Mixed Meta upload sessions cannot be thumbnails"
3918
+ });
3919
+ }
3920
+ if (value.mediaKind !== "mixed" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
3921
+ ctx.addIssue({
3922
+ code: z31.ZodIssueCode.custom,
3923
+ path: [
3924
+ "maxFiles"
3925
+ ],
3926
+ message: "Non-mixed Meta upload sessions accept exactly one file"
3927
+ });
3928
+ }
3929
+ if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
3930
+ ctx.addIssue({
3931
+ code: z31.ZodIssueCode.custom,
3932
+ path: [
3933
+ "mediaKind"
3934
+ ],
3935
+ message: "Meta thumbnails must be images"
3936
+ });
3937
+ }
3938
+ });
3939
+ var ResolveMetaUploadSessionRequest = z31.object({
3940
+ /** Opaque signed token embedded in the one-click upload page URL. */
3941
+ token: z31.string().min(1).max(4096),
3942
+ /** Stable browser-generated id so failed PUT retries reuse the same slot. */
3943
+ uploadId: z31.string().uuid().optional(),
3944
+ /** Browser-selected metadata; the API checks it against the session kind/role. */
3945
+ contentType: z31.enum(META_STAGING_CONTENT_TYPES),
3946
+ fileName: z31.string().min(1).max(255),
3947
+ fileSize: z31.number().int().positive().max(CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES)
3948
+ }).strict();
3949
+ var MetaUploadSessionResponse = z31.object({
3950
+ sessionId: z31.string().uuid(),
3951
+ uploadPageUrl: HttpUrl,
3952
+ mediaKind: MetaUploadMediaKind,
3953
+ assetRole: z31.enum([
3954
+ "media",
3955
+ "thumbnail"
3956
+ ]),
3957
+ maxFiles: z31.number().int().min(1).max(META_UPLOAD_MAX_FILES),
3958
+ expiresAt: z31.string().datetime()
3959
+ }).strict();
3960
+ var ResolveMetaUploadSessionResponse = z31.object({
3961
+ uploadUrl: z31.string().url(),
3962
+ stagingAssetId: z31.string().uuid(),
3963
+ expiresAt: z31.string().datetime()
3964
+ }).strict();
3965
+ var MetaUploadSessionStatusResponse = z31.object({
3966
+ sessionId: z31.string().uuid(),
3967
+ status: z31.enum([
3968
+ "pending",
3969
+ "uploaded"
3970
+ ]),
3971
+ assets: z31.array(MetaUploadAssetSummary).max(META_UPLOAD_MAX_FILES),
3972
+ /** Legacy single-file response field retained for older callers. */
3973
+ stagingAssetId: z31.string().uuid().optional()
3974
+ }).strict();
3773
3975
  var MetaAdAccountId = z31.string().trim().min(1, "Debes seleccionar una cuenta publicitaria").transform((value) => /^\d+$/.test(value) ? `act_${value}` : value).refine((value) => /^act_\d+$/.test(value), {
3774
3976
  message: 'adAccountId must look like "act_1234567890" (a bare numeric id is also accepted)'
3775
3977
  });
@@ -3856,18 +4058,26 @@ var UploadToMetaRequest = z31.object({
3856
4058
  title: z31.string().max(CAMPAIGN_LIMITS.MAX_TITLE_LENGTH),
3857
4059
  description: z31.string().max(CAMPAIGN_LIMITS.MAX_DESCRIPTION_LENGTH),
3858
4060
  cta: MetaCtaType,
3859
- /** Public URL of the uploaded media */
3860
- mediaUrl: HttpUrl,
4061
+ /** Legacy public URL; new Meta flows should use mediaAssetId. */
4062
+ mediaUrl: HttpUrl.optional(),
4063
+ /** Owner-scoped temporary asset created by the Meta staging upload flow. */
4064
+ mediaAssetId: z31.string().uuid().optional(),
3861
4065
  /**
3862
4066
  * Public URL of an extracted thumbnail. Required for video ads — Meta
3863
4067
  * rejects video ad creatives without `image_hash`/`image_url` in
3864
4068
  * `video_data` (subcode 1443226). Frontend extracts a frame from the
3865
4069
  * video and uploads it before submitting the request.
3866
4070
  */
3867
- thumbnailUrl: HttpUrl.optional()
4071
+ thumbnailUrl: HttpUrl.optional(),
4072
+ /** Owner-scoped temporary thumbnail created by the Meta staging upload flow. */
4073
+ thumbnailAssetId: z31.string().uuid().optional()
3868
4074
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3869
4075
  /** Meta Ad Account ID */
3870
4076
  adAccountId: MetaAdAccountId,
4077
+ /** Meta Business Portfolio selected by the user (optional for legacy callers). */
4078
+ portfolioId: z31.string().min(1).optional(),
4079
+ /** Facebook Page selected by the user (optional for legacy callers). */
4080
+ pageId: z31.string().min(1).optional(),
3871
4081
  /** Optional Meta Pixel ID */
3872
4082
  pixelId: z31.string().optional(),
3873
4083
  /** Landing page URL (required for landing destinations; omitted for whatsapp) */
@@ -3887,6 +4097,30 @@ var UploadToMetaRequest = z31.object({
3887
4097
  message: "landingUrl is required for landing destinations"
3888
4098
  });
3889
4099
  }
4100
+ value.creatives.forEach((creative, index) => {
4101
+ if (Boolean(creative.mediaUrl) === Boolean(creative.mediaAssetId)) {
4102
+ ctx.addIssue({
4103
+ code: z31.ZodIssueCode.custom,
4104
+ path: [
4105
+ "creatives",
4106
+ index,
4107
+ "mediaUrl"
4108
+ ],
4109
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4110
+ });
4111
+ }
4112
+ if (creative.thumbnailUrl && creative.thumbnailAssetId) {
4113
+ ctx.addIssue({
4114
+ code: z31.ZodIssueCode.custom,
4115
+ path: [
4116
+ "creatives",
4117
+ index,
4118
+ "thumbnailUrl"
4119
+ ],
4120
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4121
+ });
4122
+ }
4123
+ });
3890
4124
  });
3891
4125
  var CampaignLifetimeMetricsSchema = z31.object({
3892
4126
  spend: z31.number().nonnegative(),
@@ -3940,8 +4174,32 @@ var FacebookAdAccountSchema = z32.object({
3940
4174
  source: z32.enum([
3941
4175
  "business",
3942
4176
  "personal"
4177
+ ]),
4178
+ /** Meta Business Portfolio (or the synthetic `personal` group). */
4179
+ portfolioId: z32.string().min(1),
4180
+ portfolioName: z32.string().min(1),
4181
+ portfolioType: z32.enum([
4182
+ "business",
4183
+ "personal"
3943
4184
  ])
3944
4185
  });
4186
+ var FacebookMetaPortfolioSchema = z32.object({
4187
+ id: z32.string().min(1),
4188
+ name: z32.string().min(1),
4189
+ type: z32.enum([
4190
+ "business",
4191
+ "personal"
4192
+ ])
4193
+ });
4194
+ var FacebookMetaPageSchema = z32.object({
4195
+ id: z32.string().min(1),
4196
+ name: z32.string().min(1)
4197
+ });
4198
+ var FacebookMetaDestinationsResponseSchema = z32.object({
4199
+ portfolios: z32.array(FacebookMetaPortfolioSchema),
4200
+ adAccounts: z32.array(FacebookAdAccountSchema),
4201
+ pages: z32.array(FacebookMetaPageSchema)
4202
+ });
3945
4203
  var MetaCampaignOverridesSchema = z32.object({
3946
4204
  dailyBudgetUsd: z32.number().positive().optional(),
3947
4205
  targetCountries: z32.array(z32.string().length(2)).max(1).optional(),
@@ -3950,6 +4208,8 @@ var MetaCampaignOverridesSchema = z32.object({
3950
4208
  });
3951
4209
  var FacebookCreateCampaignBodySchema = z32.object({
3952
4210
  adAccountId: MetaAdAccountId,
4211
+ portfolioId: z32.string().min(1).optional(),
4212
+ pageId: z32.string().min(1).optional(),
3953
4213
  pixelId: z32.string().optional(),
3954
4214
  landingUrl: HttpUrl2.optional(),
3955
4215
  campaignConfig: MetaCampaignConfig.optional(),
@@ -3980,6 +4240,8 @@ var FacebookCampaignStatusResponseSchema = z32.object({
3980
4240
  var FacebookCreateCampaignResponseSchema = z32.object({
3981
4241
  campaignId: z32.string(),
3982
4242
  campaignName: z32.string(),
4243
+ portfolioId: z32.string().optional(),
4244
+ portfolioName: z32.string().optional(),
3983
4245
  pageId: z32.string().optional(),
3984
4246
  pageName: z32.string().optional(),
3985
4247
  // Backward-compatible single ad fields (optional)
@@ -4022,11 +4284,15 @@ var FacebookCreateCampaignFromCreativesBodySchema = z32.object({
4022
4284
  title: z32.string().max(120),
4023
4285
  description: z32.string().max(500),
4024
4286
  cta: MetaCtaType,
4025
- mediaUrl: HttpUrl2,
4287
+ mediaUrl: HttpUrl2.optional(),
4288
+ mediaAssetId: z32.string().uuid().optional(),
4026
4289
  thumbnailUrl: HttpUrl2.optional(),
4290
+ thumbnailAssetId: z32.string().uuid().optional(),
4027
4291
  creativeId: z32.string().min(1).optional()
4028
4292
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
4029
4293
  adAccountId: MetaAdAccountId,
4294
+ portfolioId: z32.string().min(1).optional(),
4295
+ pageId: z32.string().min(1).optional(),
4030
4296
  pixelId: z32.string().optional(),
4031
4297
  /** Required unless campaignConfig.destination is 'whatsapp' (see superRefine below). */
4032
4298
  landingUrl: HttpUrl2.optional(),
@@ -4046,6 +4312,30 @@ var FacebookCreateCampaignFromCreativesBodySchema = z32.object({
4046
4312
  message: "landingUrl is required unless destination is whatsapp"
4047
4313
  });
4048
4314
  }
4315
+ value.creatives.forEach((creative, index) => {
4316
+ if (Boolean(creative.mediaUrl) === Boolean(creative.mediaAssetId)) {
4317
+ ctx.addIssue({
4318
+ code: z32.ZodIssueCode.custom,
4319
+ path: [
4320
+ "creatives",
4321
+ index,
4322
+ "mediaUrl"
4323
+ ],
4324
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4325
+ });
4326
+ }
4327
+ if (creative.thumbnailUrl && creative.thumbnailAssetId) {
4328
+ ctx.addIssue({
4329
+ code: z32.ZodIssueCode.custom,
4330
+ path: [
4331
+ "creatives",
4332
+ index,
4333
+ "thumbnailUrl"
4334
+ ],
4335
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4336
+ });
4337
+ }
4338
+ });
4049
4339
  if (value.campaignConfig?.destination?.type === "landing" && value.landingUrl && value.campaignConfig.destination.landingUrl !== value.landingUrl) {
4050
4340
  ctx.addIssue({
4051
4341
  code: z32.ZodIssueCode.custom,
@@ -4123,8 +4413,10 @@ var FacebookTaskCreativeSourceSchema = z32.object({
4123
4413
  });
4124
4414
  var FacebookMediaCreativeSourceSchema = z32.object({
4125
4415
  type: z32.literal("media"),
4126
- mediaUrl: HttpUrl2,
4127
- thumbnailUrl: HttpUrl2.optional()
4416
+ mediaUrl: HttpUrl2.optional(),
4417
+ mediaAssetId: z32.string().uuid().optional(),
4418
+ thumbnailUrl: HttpUrl2.optional(),
4419
+ thumbnailAssetId: z32.string().uuid().optional()
4128
4420
  });
4129
4421
  var FacebookCreativeSourceSchema = z32.discriminatedUnion("type", [
4130
4422
  FacebookTaskCreativeSourceSchema,
@@ -4143,6 +4435,9 @@ var FacebookCreateCampaignFromTasksBodySchema = z32.object({
4143
4435
  creativeId: z32.string().min(1).optional()
4144
4436
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
4145
4437
  adAccountId: MetaAdAccountId,
4438
+ /** Explicit destination is required for MCP/remote creative publishing. */
4439
+ portfolioId: z32.string().min(1),
4440
+ pageId: z32.string().min(1),
4146
4441
  pixelId: z32.string().optional(),
4147
4442
  landingUrl: HttpUrl2.optional(),
4148
4443
  targetCountry: z32.string().length(2).default("US"),
@@ -4159,6 +4454,33 @@ var FacebookCreateCampaignFromTasksBodySchema = z32.object({
4159
4454
  message: "landingUrl is required unless destination is whatsapp"
4160
4455
  });
4161
4456
  }
4457
+ value.creatives.forEach((creative, index) => {
4458
+ if (creative.source.type !== "media") return;
4459
+ if (Boolean(creative.source.mediaUrl) === Boolean(creative.source.mediaAssetId)) {
4460
+ ctx.addIssue({
4461
+ code: z32.ZodIssueCode.custom,
4462
+ path: [
4463
+ "creatives",
4464
+ index,
4465
+ "source",
4466
+ "mediaUrl"
4467
+ ],
4468
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4469
+ });
4470
+ }
4471
+ if (creative.source.thumbnailUrl && creative.source.thumbnailAssetId) {
4472
+ ctx.addIssue({
4473
+ code: z32.ZodIssueCode.custom,
4474
+ path: [
4475
+ "creatives",
4476
+ index,
4477
+ "source",
4478
+ "thumbnailUrl"
4479
+ ],
4480
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4481
+ });
4482
+ }
4483
+ });
4162
4484
  });
4163
4485
 
4164
4486
  // ../../packages/types/src/tiktok.ts
@@ -7537,6 +7859,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7537
7859
  confirmation: "credit_consuming",
7538
7860
  schemaHash: "bab794a9700a8d3f42b97f444a776e7ed4dcd833038c52588f951b50970ff3fa"
7539
7861
  },
7862
+ {
7863
+ name: "check_meta_media_upload",
7864
+ confirmation: "none",
7865
+ schemaHash: "62b1af1d796d9466f8926afc5e6d18c4ddeccd5b0269ca7b21c2f6ebcfd2a945"
7866
+ },
7540
7867
  {
7541
7868
  name: "cost_dropshipping_project",
7542
7869
  confirmation: "state_change",
@@ -7555,7 +7882,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7555
7882
  {
7556
7883
  name: "create_product_task",
7557
7884
  confirmation: "credit_consuming",
7558
- schemaHash: "50845af40b29f479592ca5e545d9c1af1fe631998802827affccb4641fc90c18"
7885
+ schemaHash: "5c5fe82a365c62e44265e79a4100a06820dbbdb2e0ddc296cd066eb2255b1d62"
7559
7886
  },
7560
7887
  {
7561
7888
  name: "create_service_video",
@@ -7600,7 +7927,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7600
7927
  {
7601
7928
  name: "generate_video_creative",
7602
7929
  confirmation: "credit_consuming",
7603
- schemaHash: "c80208df75f2c7cbc459e9ae494a75a0a5f5bc4cf044b3fc9ef9c0e9af2c2452"
7930
+ schemaHash: "04e12ef523e4064b0c204d110baad35d414b605068087c625badf40c46429c8a"
7604
7931
  },
7605
7932
  {
7606
7933
  name: "get_meta_campaign_status",
@@ -7642,6 +7969,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7642
7969
  confirmation: "none",
7643
7970
  schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
7644
7971
  },
7972
+ {
7973
+ name: "list_meta_destinations",
7974
+ confirmation: "none",
7975
+ schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
7976
+ },
7645
7977
  {
7646
7978
  name: "list_product_research_runs",
7647
7979
  confirmation: "none",
@@ -7670,12 +8002,12 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7670
8002
  {
7671
8003
  name: "publish_creatives_to_meta",
7672
8004
  confirmation: "external_mutation",
7673
- schemaHash: "ddb45071d61746b443124346e7a82095315a2a80bd393c73a45f12b895939cf5"
8005
+ schemaHash: "59b3d6d5ce25812fee3382b0b1c0cf5e07beb1063048dd0a0d0633686cf29da5"
7674
8006
  },
7675
8007
  {
7676
8008
  name: "publish_to_meta",
7677
8009
  confirmation: "external_mutation",
7678
- schemaHash: "4b6daa1a25e739ccf208a6acede067ec1c22b1ea5509f332361a17dd46f6bb0b"
8010
+ schemaHash: "b507735a1fe13657355cf93163b6ead0db151984e6f2702dcdbf76b4037133ca"
7679
8011
  },
7680
8012
  {
7681
8013
  name: "publish_to_shopify",
@@ -7692,6 +8024,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7692
8024
  confirmation: "credit_consuming",
7693
8025
  schemaHash: "745687cb41f904e293105f93bbe6bc0471f63fe097ab8f85d3df8d9833084844"
7694
8026
  },
8027
+ {
8028
+ name: "request_meta_media_upload",
8029
+ confirmation: "none",
8030
+ schemaHash: "32203d205f414c386ee6ab33e265eb2708c108fca4169d4c12e99d7a4109a132"
8031
+ },
7695
8032
  {
7696
8033
  name: "resolve_pending_action",
7697
8034
  confirmation: "credit_consuming",
@@ -7767,6 +8104,88 @@ var ApproveActorRequest = z42.object({
7767
8104
  candidateIndex: z42.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
7768
8105
  });
7769
8106
 
8107
+ // ../../packages/types/src/referral-dashboard.ts
8108
+ import { z as z43 } from "zod";
8109
+ var REFERRAL_DASHBOARD_RAW_STATUSES = [
8110
+ "ACTIVE",
8111
+ "PENDING",
8112
+ "GRACE",
8113
+ "CANCELLED",
8114
+ "EXPIRED"
8115
+ ];
8116
+ var REFERRAL_DASHBOARD_STATES = [
8117
+ "active",
8118
+ "pending",
8119
+ "inactive",
8120
+ "no_plan"
8121
+ ];
8122
+ var REFERRAL_DASHBOARD_PLAN_KINDS = [
8123
+ "current",
8124
+ "last",
8125
+ "none"
8126
+ ];
8127
+ var ReferralDashboardRawStatusSchema = z43.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
8128
+ var ReferralDashboardStateSchema = z43.enum(REFERRAL_DASHBOARD_STATES);
8129
+ var ReferralDashboardPlanKindSchema = z43.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
8130
+ var ReferralDashboardStatusSchema = z43.enum([
8131
+ "all",
8132
+ "active",
8133
+ "pending",
8134
+ "inactive"
8135
+ ]).default("all");
8136
+ var ReferralDashboardPlanSchema = z43.string().trim().min(1).max(100).optional();
8137
+ var ReferralDashboardFiltersSchema = z43.object({
8138
+ status: ReferralDashboardStatusSchema,
8139
+ plan: ReferralDashboardPlanSchema
8140
+ });
8141
+ var ReferralDashboardSubscriptionSchema = z43.object({
8142
+ planName: z43.string().nullable(),
8143
+ planKind: ReferralDashboardPlanKindSchema,
8144
+ rawStatus: ReferralDashboardRawStatusSchema.nullable(),
8145
+ state: ReferralDashboardStateSchema,
8146
+ currentPeriodEnd: z43.string().datetime({
8147
+ offset: true
8148
+ }).nullable(),
8149
+ cancelAtPeriodEnd: z43.boolean()
8150
+ });
8151
+ var ReferralDashboardReferralSchema = z43.object({
8152
+ id: z43.string().min(1),
8153
+ registeredAt: z43.string().datetime({
8154
+ offset: true
8155
+ }),
8156
+ // Kept in the response for compatibility with existing referral API clients.
8157
+ // The referrer dashboard intentionally does not render these legacy fields.
8158
+ expiresAt: z43.string().datetime({
8159
+ offset: true
8160
+ }).nullable().optional(),
8161
+ status: z43.string().min(1).optional(),
8162
+ referredUser: z43.object({
8163
+ email: z43.string().nullable()
8164
+ }),
8165
+ subscription: ReferralDashboardSubscriptionSchema
8166
+ });
8167
+ var ReferralDashboardReferralsPageSchema = z43.object({
8168
+ referrals: z43.array(ReferralDashboardReferralSchema),
8169
+ total: z43.number().int().nonnegative()
8170
+ });
8171
+ var ReferralDashboardPlanBreakdownSchema = z43.object({
8172
+ planName: z43.string().nullable(),
8173
+ count: z43.number().int().nonnegative()
8174
+ });
8175
+ var ReferralDashboardMonthlyRegistrationSchema = z43.object({
8176
+ month: z43.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
8177
+ count: z43.number().int().nonnegative()
8178
+ });
8179
+ var ReferralDashboardOverviewSchema = z43.object({
8180
+ totalReferredUsers: z43.number().int().nonnegative(),
8181
+ activeUsers: z43.number().int().nonnegative(),
8182
+ pendingUsers: z43.number().int().nonnegative(),
8183
+ inactiveUsers: z43.number().int().nonnegative(),
8184
+ noPlanUsers: z43.number().int().nonnegative(),
8185
+ planBreakdown: z43.array(ReferralDashboardPlanBreakdownSchema),
8186
+ monthlyRegistrations: z43.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
8187
+ });
8188
+
7770
8189
  // src/resolve-media.ts
7771
8190
  var MAX_MEDIA_BYTES = CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES;
7772
8191
  var MEDIA_FORMATS = [
@@ -8006,10 +8425,10 @@ async function resolveMediaRef(ref, opts) {
8006
8425
  __name(resolveMediaRef, "resolveMediaRef");
8007
8426
 
8008
8427
  // 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([
8428
+ import { z as z44 } from "zod";
8429
+ var Lang = z44.string().min(2).max(10);
8430
+ var Country = z44.string().min(2).max(3);
8431
+ var LandingStrategyEnum = z44.enum([
8013
8432
  "auto",
8014
8433
  "classic",
8015
8434
  "pain_solution",
@@ -8018,12 +8437,12 @@ var LandingStrategyEnum = z43.enum([
8018
8437
  "offer_value",
8019
8438
  "lifestyle_identity"
8020
8439
  ]);
8021
- var LandingImageFormatEnum = z43.enum([
8440
+ var LandingImageFormatEnum = z44.enum([
8022
8441
  "9:16",
8023
8442
  "4:5",
8024
8443
  "1:1"
8025
8444
  ]);
8026
- var VideoPresetEnum = z43.enum([
8445
+ var VideoPresetEnum = z44.enum([
8027
8446
  "auto",
8028
8447
  "ugc_dynamic",
8029
8448
  "ugc_how_to",
@@ -8036,7 +8455,7 @@ var VideoPresetEnum = z43.enum([
8036
8455
  "pixar_problem",
8037
8456
  "pixar_sassy"
8038
8457
  ]);
8039
- var TaskCreativeVariantVideoPresetEnum = z43.enum([
8458
+ var TaskCreativeVariantVideoPresetEnum = z44.enum([
8040
8459
  "auto",
8041
8460
  "ugc_dynamic",
8042
8461
  "ugc_how_to",
@@ -8107,7 +8526,7 @@ function resolveVideoPreset(preset) {
8107
8526
  return VIDEO_PRESET_RESOLUTION[preset] ?? null;
8108
8527
  }
8109
8528
  __name(resolveVideoPreset, "resolveVideoPreset");
8110
- var VideoTierEnum = z43.enum([
8529
+ var VideoTierEnum = z44.enum([
8111
8530
  "basic"
8112
8531
  ]);
8113
8532
  function resolveVideoTier(_tier) {
@@ -8118,27 +8537,21 @@ function resolveWireVideoTier(tier, durationSeconds) {
8118
8537
  return durationSeconds === 30 ? "standard" : resolveVideoTier(tier);
8119
8538
  }
8120
8539
  __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
8540
  function resolveVideoFormatForDuration(durationSeconds) {
8128
8541
  return durationSeconds === 30 ? "double_30s" : "single_15s";
8129
8542
  }
8130
8543
  __name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
8131
- var VideoResolutionEnum = z43.enum([
8544
+ var VideoResolutionEnum = z44.enum([
8132
8545
  "480p",
8133
8546
  "720p"
8134
8547
  ]);
8135
- var VideoStyleEnum = z43.enum([
8548
+ var VideoStyleEnum = z44.enum([
8136
8549
  "ugc",
8137
8550
  "cartoon_3d",
8138
8551
  "product_only",
8139
8552
  "ai_dynamic"
8140
8553
  ]);
8141
- var NarrativeStyleEnum = z43.enum([
8554
+ var NarrativeStyleEnum = z44.enum([
8142
8555
  "review",
8143
8556
  "ugc_how_to",
8144
8557
  "problem_product_cta",
@@ -8155,7 +8568,7 @@ var NarrativeStyleEnum = z43.enum([
8155
8568
  "ugc_skeptic_confession",
8156
8569
  "ugc_demo_review"
8157
8570
  ]);
8158
- var PublicNarrativeStyleEnum = z43.enum([
8571
+ var PublicNarrativeStyleEnum = z44.enum([
8159
8572
  "review",
8160
8573
  "ugc_how_to",
8161
8574
  "ugc_unboxing_chaos",
@@ -8167,7 +8580,7 @@ var PublicNarrativeStyleEnum = z43.enum([
8167
8580
  "animated_problem_pixar",
8168
8581
  "sassy_product_pixar"
8169
8582
  ]);
8170
- var VideoModelEnum = z43.enum([
8583
+ var VideoModelEnum = z44.enum([
8171
8584
  "veo",
8172
8585
  "wan",
8173
8586
  "bytedance",
@@ -8175,25 +8588,25 @@ var VideoModelEnum = z43.enum([
8175
8588
  "grok",
8176
8589
  "kling"
8177
8590
  ]);
8178
- var ProductReferenceUserChoiceEnum = z43.enum([
8591
+ var ProductReferenceUserChoiceEnum = z44.enum([
8179
8592
  "same_product",
8180
8593
  "variants",
8181
8594
  "different_products",
8182
8595
  "combo_distinct"
8183
8596
  ]);
8184
- var ProductReferenceCandidateGroupingEnum = z43.enum([
8597
+ var ProductReferenceCandidateGroupingEnum = z44.enum([
8185
8598
  "same_product_same_variant",
8186
8599
  "same_product_different_variants",
8187
8600
  "different_products",
8188
8601
  "unclear"
8189
8602
  ]);
8190
- var GapDurationChoiceMirror = z43.object({
8191
- angleIndex: z43.number().int().nonnegative(),
8192
- recommendedSec: z43.union([
8193
- z43.literal(15),
8194
- z43.literal(30)
8603
+ var GapDurationChoiceMirror = z44.object({
8604
+ angleIndex: z44.number().int().nonnegative(),
8605
+ recommendedSec: z44.union([
8606
+ z44.literal(15),
8607
+ z44.literal(30)
8195
8608
  ]),
8196
- estimatedDeltaCredits: z43.number().int().nonnegative().optional()
8609
+ estimatedDeltaCredits: z44.number().int().nonnegative().optional()
8197
8610
  });
8198
8611
  function buildSelectedImageUrlsHash(urls) {
8199
8612
  const input = [
@@ -8207,13 +8620,13 @@ function buildSelectedImageUrlsHash(urls) {
8207
8620
  return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
8208
8621
  }
8209
8622
  __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
8210
- var ImageCreativeStyleEnum = z43.enum([
8623
+ var ImageCreativeStyleEnum = z44.enum([
8211
8624
  "studio",
8212
8625
  "floating",
8213
8626
  "ingredients",
8214
8627
  "in_use"
8215
8628
  ]);
8216
- var ImageCreativeArchetypeEnum = z43.enum([
8629
+ var ImageCreativeArchetypeEnum = z44.enum([
8217
8630
  "direct_response_product_ad",
8218
8631
  "hero_outcome",
8219
8632
  "offer_value_prop",
@@ -8222,23 +8635,23 @@ var ImageCreativeArchetypeEnum = z43.enum([
8222
8635
  "problem_solution",
8223
8636
  "lifestyle_identity"
8224
8637
  ]);
8225
- var ImageCreativeVisualTreatmentEnum = z43.enum([
8638
+ var ImageCreativeVisualTreatmentEnum = z44.enum([
8226
8639
  "performance_product_ad",
8227
8640
  "premium_dtc_carousel"
8228
8641
  ]);
8229
- var ImageCreativeAspectRatioEnum = z43.enum([
8642
+ var ImageCreativeAspectRatioEnum = z44.enum([
8230
8643
  "1:1",
8231
8644
  "4:5",
8232
8645
  "9:16",
8233
8646
  "16:9"
8234
8647
  ]);
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`.')
8648
+ var ImageRefSchema = z44.object({
8649
+ url: z44.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
8650
+ path: z44.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.')
8238
8651
  }).refine((v) => Boolean(v.url) !== Boolean(v.path), {
8239
8652
  message: "each image entry must have exactly one of `url` or `path`"
8240
8653
  });
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.');
8654
+ var ImagesInput = z44.array(ImageRefSchema).min(1).max(5).describe('1 a 5 fotos del MISMO producto. La PRIMERA es la principal (hero); las dem\xE1s son \xE1ngulos adicionales. NO mezcles productos distintos en una sola llamada.\n\nCada entrada tiene EXACTAMENTE uno de:\n\u2022 `url` \u2014 URL p\xFAblica HTTPS (Shopify CDN, Cloudinary). Prefi\xE9rela si la foto ya est\xE1 hospedada.\n\u2022 `path` \u2014 Ruta del archivo en el computador del usuario (ej. "/Users/maria/Desktop/foto.jpg", "~/Downloads/camiseta.png"). \xDAsala cuando el usuario te dijo d\xF3nde guard\xF3 la foto. Solo MCP local \u2014 el cliente web rechaza paths.\n\nSi el usuario te peg\xF3 la foto en el chat pero no te dio ni URL ni ruta: PREG\xDANTALE d\xF3nde la tiene guardada (Escritorio, Descargas, etc.). Si el `path` falla con IMAGE_PATH_NOT_FOUND, prueba 1-2 ubicaciones obvias alternativas (~/Desktop, ~/Downloads) antes de molestar al usuario. Tras 3 intentos fallidos, p\xEDdele la ruta completa.');
8242
8655
  function asText(value) {
8243
8656
  return {
8244
8657
  content: [
@@ -8291,14 +8704,14 @@ async function submitCreative(api, opts) {
8291
8704
  });
8292
8705
  }
8293
8706
  __name(submitCreative, "submitCreative");
8294
- var ResearchTargetCountryEnum = z43.enum([
8707
+ var ResearchTargetCountryEnum = z44.enum([
8295
8708
  "CO",
8296
8709
  "MX",
8297
8710
  "PE",
8298
8711
  "EC",
8299
8712
  "CL"
8300
8713
  ]);
8301
- var ResearchCategoryEnum = z43.enum([
8714
+ var ResearchCategoryEnum = z44.enum([
8302
8715
  "auto",
8303
8716
  "automotive",
8304
8717
  "kitchen",
@@ -8371,6 +8784,15 @@ var McpErrorCode = {
8371
8784
  SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
8372
8785
  // Meta / Facebook Ads
8373
8786
  META_NO_FB_PAGE: "META_NO_FB_PAGE",
8787
+ META_PORTFOLIO_MISMATCH: "META_PORTFOLIO_MISMATCH",
8788
+ META_PAGE_SELECTION_REQUIRED: "META_PAGE_SELECTION_REQUIRED",
8789
+ META_INVALID_PAGE: "META_INVALID_PAGE",
8790
+ META_STAGED_ASSET_NOT_FOUND: "META_STAGED_ASSET_NOT_FOUND",
8791
+ META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
8792
+ META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
8793
+ META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
8794
+ META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
8795
+ META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
8374
8796
  META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
8375
8797
  META_NO_ASSETS_READY: "META_NO_ASSETS_READY",
8376
8798
  META_NO_THUMBNAIL_SOURCE: "META_NO_THUMBNAIL_SOURCE",
@@ -8423,6 +8845,15 @@ var CODE_MESSAGES = {
8423
8845
  SHOPIFY_PUBLISH_FAILED: "Shopify rejected the publish. Check that the product/landing does not already exist on the store and that your Shopify plan allows additional products.",
8424
8846
  // Meta — high-frequency
8425
8847
  META_NO_FB_PAGE: "No Facebook Page is associated with this account. Create one at https://www.facebook.com/pages/create before publishing (Meta requires a Page to run ads).",
8848
+ META_PORTFOLIO_MISMATCH: "The selected Meta portfolio does not contain the selected ad account. Call `list_meta_destinations` again and choose an account from that portfolio.",
8849
+ META_PAGE_SELECTION_REQUIRED: "Meta has multiple Facebook Pages available. Ask the user to choose one with `list_meta_destinations` and retry with its exact `pageId`.",
8850
+ META_INVALID_PAGE: "The selected Facebook Page is not available for this Meta connection. Call `list_meta_destinations` again and choose an available `pageId`.",
8851
+ META_STAGED_ASSET_NOT_FOUND: "The temporary media upload was not found or expired. Re-upload the media and retry the Meta publication.",
8852
+ META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
8853
+ META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
8854
+ META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
8855
+ META_PUBLICATION_RECONCILIATION_REQUIRED: "A previous Meta publication with these exact inputs may already exist. Check Meta before retrying so you do not create a duplicate campaign.",
8856
+ META_PUBLICATION_IN_PROGRESS: "A Meta publication with these exact inputs is already in progress. Wait for it to finish, then check the campaign status.",
8426
8857
  META_NO_ADS_CREATED: "No ads could be created. Most likely: the Meta Ads account has no payment method, no Facebook Page is linked, or the Facebook app lost permissions. Verify all three at https://business.facebook.com.",
8427
8858
  META_NO_ASSETS_READY: "The task has no videos or ad creative ready to publish yet. Wait for generation to finish or regenerate the assets.",
8428
8859
  META_NO_THUMBNAIL_SOURCE: "No image is available to use as the video thumbnail. Upload a product image or generate the ad creative before publishing.",
@@ -8536,6 +8967,12 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
8536
8967
  McpErrorCode.CAMPAIGN_MISSING_LANDING_URL,
8537
8968
  McpErrorCode.CAMPAIGN_RECONCILIATION_REQUIRED,
8538
8969
  McpErrorCode.META_NO_LANDING_URL,
8970
+ McpErrorCode.META_PORTFOLIO_MISMATCH,
8971
+ McpErrorCode.META_PAGE_SELECTION_REQUIRED,
8972
+ McpErrorCode.META_INVALID_PAGE,
8973
+ McpErrorCode.META_STAGED_ASSET_NOT_FOUND,
8974
+ McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
8975
+ McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
8539
8976
  McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
8540
8977
  McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
8541
8978
  McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
@@ -8557,6 +8994,15 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
8557
8994
  McpErrorCode.META_MISSING_PAYMENT,
8558
8995
  McpErrorCode.META_ACCOUNT_RESTRICTED,
8559
8996
  McpErrorCode.META_NO_FB_PAGE,
8997
+ McpErrorCode.META_PORTFOLIO_MISMATCH,
8998
+ McpErrorCode.META_PAGE_SELECTION_REQUIRED,
8999
+ McpErrorCode.META_INVALID_PAGE,
9000
+ McpErrorCode.META_STAGED_ASSET_NOT_FOUND,
9001
+ McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
9002
+ McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
9003
+ McpErrorCode.META_STAGED_ASSET_INVALID,
9004
+ McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
9005
+ McpErrorCode.META_PUBLICATION_IN_PROGRESS,
8560
9006
  McpErrorCode.TIKTOK_TOKEN_EXPIRED,
8561
9007
  McpErrorCode.TIKTOK_PERMISSION_DENIED,
8562
9008
  McpErrorCode.TIKTOK_MISSING_PAYMENT,
@@ -8910,53 +9356,53 @@ function renderOutputsMarkdown(outputs) {
8910
9356
  __name(renderOutputsMarkdown, "renderOutputsMarkdown");
8911
9357
 
8912
9358
  // src/tools/tasks.ts
8913
- var CreateProductTaskInput = z44.object({
9359
+ var CreateProductTaskInput = z45.object({
8914
9360
  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."),
9361
+ projectId: z45.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
9362
+ productUrl: z45.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
8917
9363
  language: Lang.describe('C\xF3digo de idioma para el contenido generado. Ejemplos: "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas "es" por default.'),
8918
9364
  country: Country.describe('C\xF3digo ISO del pa\xEDs objetivo. Ejemplos: "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago locales y tono. REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat.'),
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)
9365
+ price: z45.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
9366
+ compareAtPrice: z45.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
9367
+ offer: z45.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
9368
+ paymentMethods: z45.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
9369
+ productContext: z45.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
9370
+ productLabel: z45.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
9371
+ videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s (single_call) y 30s (Multiescena). Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla). Si el usuario pide algo fuera de esta lista, p\xEDdele una opci\xF3n v\xE1lida.'),
9372
+ durationSeconds: z45.union([
9373
+ z45.literal(15),
9374
+ z45.literal(30)
8929
9375
  ]).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."),
9376
+ pauseForAngleReview: z45.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
8931
9377
  videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista" para 15s; al pedir durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
8932
9378
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez, entre 3% y 15% m\xE1s caro seg\xFAn formato y tier. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
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."),
9379
+ multiAngleVideos: z45.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
8934
9380
  angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "ugc_how_to", "ugc_unboxing_chaos", "problem_product_cta" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase", "wearable_showcase", "cinematic", "wild_card", "animated_problem_pixar", "sassy_product_pixar". angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para varios \xE1ngulos.'),
8935
- outputs: z44.array(z44.enum([
9381
+ outputs: z45.array(z45.enum([
8936
9382
  "landing",
8937
9383
  "video",
8938
9384
  "image"
8939
9385
  ])).min(2).max(3).optional().describe('Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). Para 1 solo output, usa generate_landing / generate_video_creative / generate_image_creatives.'),
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."),
9386
+ competitorScanEnabled: z45.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
9387
+ brandId: z45.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
9388
+ actorId: z45.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
9389
+ creativeDirection: z45.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
9390
+ headline: z45.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar. Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. Se aplica a las IM\xC1GENES y a la portada de la landing de esta task (el video no lo usa; para el video usa scriptNotes). \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
9391
+ subheadline: z45.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres."),
8946
9392
  landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution", "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity", "offer_value" o "lifestyle_identity". Default "classic".'),
8947
9393
  landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".')
8948
9394
  }).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.'),
9395
+ var CreateServiceVideoInput = z45.object({
9396
+ landingUrl: z45.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
9397
+ additionalContext: z45.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
8952
9398
  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([
9399
+ voiceId: z45.string().trim().min(1).optional().describe('Opcional. Voz de ElevenLabs para toda la narraci\xF3n. Usa "auto" para que la IA la alinee con el presentador o env\xEDa un ID concreto de la lista de voces de la web.'),
9400
+ brandId: z45.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
9401
+ reviewMode: z45.enum([
8956
9402
  "automatic",
8957
9403
  "guided"
8958
9404
  ]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
8959
- videoPreset: z44.enum([
9405
+ videoPreset: z45.enum([
8960
9406
  "auto",
8961
9407
  "ugc_dynamic",
8962
9408
  "ugc_how_to",
@@ -8964,35 +9410,35 @@ var CreateServiceVideoInput = z44.object({
8964
9410
  "wild_card"
8965
9411
  ]).default("auto")
8966
9412
  }).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([
9413
+ var GetTaskStatusInput = z45.object({
9414
+ taskId: z45.string().min(1),
9415
+ waitSeconds: z45.number().int().min(0).max(120).optional()
9416
+ });
9417
+ var ListTasksInput = z45.object({
9418
+ limit: z45.number().int().min(1).max(50).optional(),
9419
+ offset: z45.number().int().min(0).optional(),
9420
+ search: z45.string().optional()
9421
+ });
9422
+ var EditTaskDraftInput = z45.object({
9423
+ taskId: z45.string().min(1),
9424
+ angleIndex: z45.number().int().min(0),
9425
+ angle: z45.object({
9426
+ angle: z45.string().min(1).max(480),
9427
+ buyerPersona: z45.string().min(1).max(360),
9428
+ actor: z45.string().min(1).max(360).optional(),
9429
+ actorId: z45.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
9430
+ gender: z45.enum([
8985
9431
  "female",
8986
9432
  "male"
8987
9433
  ]).optional(),
8988
- scriptNotes: z44.string().max(2e3).optional()
9434
+ scriptNotes: z45.string().max(2e3).optional()
8989
9435
  })
8990
9436
  });
8991
- var PendingActionPayload = z44.discriminatedUnion("kind", [
8992
- z44.object({
8993
- kind: z44.literal("product_reference_confirmation"),
9437
+ var PendingActionPayload = z45.discriminatedUnion("kind", [
9438
+ z45.object({
9439
+ kind: z45.literal("product_reference_confirmation"),
8994
9440
  userChoice: ProductReferenceUserChoiceEnum,
8995
- confirmedProductNames: z44.array(z44.string().min(1).max(120)).min(2).max(8).optional(),
9441
+ confirmedProductNames: z45.array(z45.string().min(1).max(120)).min(2).max(8).optional(),
8996
9442
  // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
8997
9443
  // reachable via MCP. Valid ONLY together with userChoice='variants' (same
8998
9444
  // product in several colors): renders one shot per color and stitches them,
@@ -9003,50 +9449,50 @@ var PendingActionPayload = z44.discriminatedUnion("kind", [
9003
9449
  // safe because multi-scene IS the intended render for a color variant set,
9004
9450
  // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
9005
9451
  // 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".')
9452
+ useMultiSceneShowcase: z45.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
9007
9453
  }),
9008
- z44.object({
9009
- kind: z44.literal("angle_review_approval"),
9454
+ z45.object({
9455
+ kind: z45.literal("angle_review_approval"),
9010
9456
  // All edits are optional — empty action body just approves the
9011
9457
  // backend-generated draft as-is. Field shapes mirror the angle-review
9012
9458
  // request contracts in @packages/types/wizard.ts; the handler routes to
9013
9459
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
9014
- customAngle: z44.string().min(1).max(480).optional(),
9460
+ customAngle: z45.string().min(1).max(480).optional(),
9015
9461
  // Keep the shared MCP action within the strictest downstream contract:
9016
9462
  // 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)
9463
+ painPoints: z45.string().max(1500).optional(),
9464
+ benefits: z45.string().max(1500).optional(),
9465
+ features: z45.array(z45.string().min(1).max(300)).max(10).optional(),
9466
+ gapDurationSeconds: z45.union([
9467
+ z45.literal(15),
9468
+ z45.literal(30)
9023
9469
  ]).optional().describe("Opcional. Solo para aceptar expl\xEDcitamente la recomendaci\xF3n gapDurationChoice del status. 30 env\xEDa el override double_30s para el \xE1ngulo recomendado; 15 u omitido conserva la duraci\xF3n cotizada.")
9024
9470
  }),
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()
9471
+ z45.object({
9472
+ kind: z45.literal("service_angle_approval"),
9473
+ angleId: z45.string().uuid(),
9474
+ content: z45.string().min(1).max(480).optional(),
9475
+ creativeDirection: z45.string().min(1).max(1500).optional(),
9476
+ actor: z45.string().min(1).max(360).optional()
9031
9477
  }),
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()
9478
+ z45.object({
9479
+ kind: z45.literal("service_script_approval"),
9480
+ voiceovers: z45.record(z45.string(), z45.string().min(1).max(600)).optional(),
9481
+ visualDirections: z45.record(z45.string(), z45.string().min(1).max(1200)).optional()
9036
9482
  })
9037
9483
  ]);
9038
- var ResolvePendingActionInput = z44.object({
9039
- taskId: z44.string().min(1),
9484
+ var ResolvePendingActionInput = z45.object({
9485
+ taskId: z45.string().min(1),
9040
9486
  action: PendingActionPayload
9041
9487
  }).strict();
9042
9488
  function resolveVariantConfirmVideoTier(action) {
9043
9489
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
9044
9490
  }
9045
9491
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
9046
- var StatusForResolveSchema = z44.object({
9492
+ var StatusForResolveSchema = z45.object({
9047
9493
  // Metadata used to select the backend resume contract. Older API responses
9048
9494
  // may omit these fields, so the resolver keeps the generic fallback.
9049
- kind: z44.enum([
9495
+ kind: z45.enum([
9050
9496
  "autopilot",
9051
9497
  "video",
9052
9498
  "image",
@@ -9054,24 +9500,24 @@ var StatusForResolveSchema = z44.object({
9054
9500
  "mix",
9055
9501
  "service"
9056
9502
  ]).optional(),
9057
- selectedOutputs: z44.array(z44.enum([
9503
+ selectedOutputs: z45.array(z45.enum([
9058
9504
  "landing",
9059
9505
  "video",
9060
9506
  "image"
9061
9507
  ])).optional(),
9062
- videoAngleCount: z44.number().int().min(1).max(3).optional(),
9063
- ugcFormat: z44.enum([
9508
+ videoAngleCount: z45.number().int().min(1).max(3).optional(),
9509
+ ugcFormat: z45.enum([
9064
9510
  "single_8s",
9065
9511
  "single_15s",
9066
9512
  "double_30s"
9067
9513
  ]).optional(),
9068
- videoStyle: z44.enum([
9514
+ videoStyle: z45.enum([
9069
9515
  "ugc",
9070
9516
  "cartoon_3d",
9071
9517
  "product_only",
9072
9518
  "ai_dynamic"
9073
9519
  ]).optional(),
9074
- videoModel: z44.enum([
9520
+ videoModel: z45.enum([
9075
9521
  "veo",
9076
9522
  "wan",
9077
9523
  "bytedance",
@@ -9079,26 +9525,26 @@ var StatusForResolveSchema = z44.object({
9079
9525
  "grok",
9080
9526
  "kling"
9081
9527
  ]).optional(),
9082
- videoTier: z44.enum([
9528
+ videoTier: z45.enum([
9083
9529
  "legacy",
9084
9530
  "standard",
9085
9531
  "pro",
9086
9532
  "single_call"
9087
9533
  ]).optional(),
9088
- videoResolution: z44.enum([
9534
+ videoResolution: z45.enum([
9089
9535
  "480p",
9090
9536
  "720p"
9091
9537
  ]).optional(),
9092
9538
  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(),
9539
+ pendingUserAction: z45.object({
9540
+ kind: z45.string(),
9541
+ candidateGrouping: z45.string().optional(),
9542
+ selectedImageUrls: z45.array(z45.string()).optional(),
9543
+ proposedProductNames: z45.array(z45.string()).optional(),
9544
+ customAngleDraft: z45.string().optional(),
9545
+ painPointsDraft: z45.string().optional(),
9546
+ benefitsDraft: z45.string().optional(),
9547
+ featuresDraft: z45.array(z45.string()).optional(),
9102
9548
  // Recommendation consumed only when the caller explicitly sends
9103
9549
  // gapDurationSeconds: 30 in the angle approval action.
9104
9550
  gapDurationChoice: GapDurationChoiceMirror.optional(),
@@ -9107,17 +9553,17 @@ var StatusForResolveSchema = z44.object({
9107
9553
  // keys. `actorId` (the saved custom actor the picker stored) was silently
9108
9554
  // dropped here, so the approval handler below could not echo it back on the
9109
9555
  // 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()
9556
+ angles: z45.array(z45.object({
9557
+ id: z45.string().uuid(),
9558
+ content: z45.string(),
9559
+ creativeDirection: z45.string(),
9560
+ actor: z45.string(),
9561
+ actorId: z45.string().nullable().optional()
9116
9562
  }).passthrough()).optional(),
9117
- panels: z44.array(z44.object({
9118
- beatId: z44.string(),
9119
- voiceover: z44.string(),
9120
- visualDirection: z44.string()
9563
+ panels: z45.array(z45.object({
9564
+ beatId: z45.string(),
9565
+ voiceover: z45.string(),
9566
+ visualDirection: z45.string()
9121
9567
  }).passthrough()).optional()
9122
9568
  }).optional()
9123
9569
  }).passthrough();
@@ -9158,7 +9604,6 @@ __name(resolveStatusTaskKind, "resolveStatusTaskKind");
9158
9604
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
9159
9605
  server.tool("create_product_task", "Crea una tarea de producto desde fotos o desde un proyecto guiado propio. Por defecto genera los 3 outputs (landing + video + image creatives) EN UN MISMO TASK. Devuelve un taskId.\n\nINPUTS REQUERIDOS: images (1-5 fotos del MISMO producto; la primera es la hero) O projectId si el proyecto ya tiene una imagen guardada; adem\xE1s language y country.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son inputs de alto impacto en la landing y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto del chat: preg\xFAntale expl\xEDcitamente, incluso si est\xE1 en LATAM.\n\nSi env\xEDas projectId, debe ser el proyecto propio que el usuario acaba de costear; el backend resolver\xE1 la imagen privada y conservar\xE1 todos los controles de cr\xE9ditos/confirmaci\xF3n. Si el usuario adjunta images junto con projectId, esas fotos reemplazan la referencia solo para este task.\n\nANTES de invocar, DILE CLARAMENTE qu\xE9 outputs vas a generar y que `create_product_task` los genera juntos en un mismo task.\n\nUsa SOLO valores declarados en los enums.", CreateProductTaskInput.shape, async (input) => {
9160
9606
  try {
9161
- assertVideoPresetDurationCompatibility(input.videoPreset, input.durationSeconds);
9162
9607
  if (!input.images && !input.projectId) {
9163
9608
  throw new Error("images es requerido salvo que env\xEDes projectId.");
9164
9609
  }
@@ -9558,58 +10003,58 @@ __name(registerTaskTools, "registerTaskTools");
9558
10003
 
9559
10004
  // src/tools/creatives.ts
9560
10005
  import { randomUUID } from "crypto";
9561
- import { z as z45 } from "zod";
10006
+ import { z as z46 } from "zod";
9562
10007
  var CreativeRefs = {
9563
10008
  images: ImagesInput,
9564
10009
  language: Lang.describe('Idioma del contenido. Ej. "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
9565
10010
  country: Country.describe('Pa\xEDs objetivo ISO. Ej. "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago y tono. REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
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.")
10011
+ price: z46.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
10012
+ offer: z46.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
10013
+ compareAtPrice: z46.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
10014
+ paymentMethods: z46.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago a mostrar. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
10015
+ productContext: z46.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
10016
+ productLabel: z46.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
10017
+ buyerPersona: z46.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
10018
+ isCombo: z46.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
10019
+ competitorScanEnabled: z46.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
10020
+ brandId: z46.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer el creativo."),
10021
+ projectId: z46.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
10022
+ creativeDirection: z46.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Aplica SOLO a im\xE1genes y landing. Ej. im\xE1genes: "amarillo, azul y rojo, minimalista"; landing: "elegante, paleta fr\xEDa". Para video usa scriptNotes (ambiente/escenario). M\xE1x 500 caracteres.'),
10023
+ headline: z46.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar en la imagen (o en la portada de la landing). Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
10024
+ subheadline: z46.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres.")
9580
10025
  };
9581
- var VideoInput = z45.object({
10026
+ var VideoInput = z46.object({
9582
10027
  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".'),
10028
+ title: z46.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
10029
+ primaryAngle: z46.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
9585
10030
  language: CreativeRefs.language,
9586
10031
  country: CreativeRefs.country,
9587
- aspectRatio: z45.enum([
10032
+ aspectRatio: z46.enum([
9588
10033
  "9:16",
9589
10034
  "16:9",
9590
10035
  "Auto"
9591
10036
  ]).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)
10037
+ durationSeconds: z46.union([
10038
+ z46.literal(15),
10039
+ z46.literal(30)
9595
10040
  ]).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).'),
10041
+ videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s (single_call) y 30s (Multiescena). Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla).'),
9597
10042
  videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista"; para durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
9598
10043
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez, entre 3% y 15% m\xE1s caro seg\xFAn formato y tier. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
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"].'),
10044
+ features: z46.array(z46.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
9600
10045
  price: CreativeRefs.price,
9601
10046
  offer: CreativeRefs.offer,
9602
10047
  productContext: CreativeRefs.productContext,
9603
10048
  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."),
10049
+ cta: z46.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
10050
+ scriptNotes: z46.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
10051
+ pauseForVideoScriptReview: z46.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
10052
+ pauseForAngleReview: z46.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. Default false."),
9608
10053
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
9609
10054
  brandId: CreativeRefs.brandId,
9610
10055
  projectId: CreativeRefs.projectId
9611
10056
  }).strict();
9612
- var FreeVideoQualityEnum = z45.enum([
10057
+ var FreeVideoQualityEnum = z46.enum([
9613
10058
  "economical",
9614
10059
  "high_detail"
9615
10060
  ]);
@@ -9631,38 +10076,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
9631
10076
  15
9632
10077
  ]
9633
10078
  };
9634
- var FreeVideoInput = z45.object({
10079
+ var FreeVideoInput = z46.object({
9635
10080
  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."),
10081
+ rawPrompt: z46.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
9637
10082
  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."),
10083
+ durationSeconds: z46.number().int().min(1).max(15).describe("Duraci\xF3n exacta. Econ\xF3mico permite 6, 8, 10 o 15s; Alto detalle permite 4, 8, 12 o 15s."),
9639
10084
  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.")
10085
+ generateAiMusic: z46.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
10086
+ addSubtitles: z46.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
10087
+ idempotencyKey: z46.string().uuid().optional().describe("Opcional. UUID para reintentar exactamente esta misma solicitud sin crear ni cobrar otro video. Reutiliza el mismo valor si el cliente no recibi\xF3 la respuesta.")
9643
10088
  }).strict();
9644
10089
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
9645
10090
  var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
9646
10091
  var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
9647
10092
  var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
9648
- var ImageInput = z45.object({
10093
+ var ImageInput = z46.object({
9649
10094
  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".'),
10095
+ productTitle: z46.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
10096
+ primaryAngle: z46.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
9652
10097
  buyerPersona: CreativeRefs.buyerPersona,
9653
10098
  language: CreativeRefs.language,
9654
10099
  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.'),
10100
+ nVariants: z46.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
10101
+ imageModel: z46.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
9657
10102
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
9658
10103
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe(`Opcional. Arquetipo CRO de la composici\xF3n. Valores: ${archetypeList}. Si se omite, la IA escoge seg\xFAn \xE1ngulo+oferta (ej. "offer_value_prop" cuando hay descuento, "social_proof_trust" cuando el \xE1ngulo es testimonial).`),
9659
10104
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe(`Opcional. Tratamiento gr\xE1fico. Valores: ${visualTreatmentList} ("performance_product_ad" = dealer-style alto CTR para Meta/TikTok ads; "premium_dtc_carousel" = fotograf\xEDa limpia estilo Allbirds/Glossier). Si se omite, la IA escoge.`),
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."),
10105
+ styleMix: z46.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
9661
10106
  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.'),
10107
+ generateAdCopy: z46.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
10108
+ pauseForAngleReview: z46.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo y antes de las im\xE1genes para que el usuario edite v\xEDa edit_task_draft. Default false."),
10109
+ features: z46.array(z46.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
10110
+ brandColors: z46.array(z46.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
9666
10111
  price: CreativeRefs.price,
9667
10112
  compareAtPrice: CreativeRefs.compareAtPrice,
9668
10113
  offer: CreativeRefs.offer,
@@ -9676,16 +10121,16 @@ var ImageInput = z45.object({
9676
10121
  headline: CreativeRefs.headline,
9677
10122
  subheadline: CreativeRefs.subheadline
9678
10123
  });
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".')
10124
+ var EditImageCreativeInput = z46.object({
10125
+ taskId: z46.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
10126
+ variantIndex: z46.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
10127
+ instruction: z46.string().min(1).max(1e3).describe('Qu\xE9 cambiar en esa imagen. Ej. "quita el badge de env\xEDo gratis", "cambia el fondo a blanco".')
9683
10128
  });
9684
- var LandingInput = z45.object({
10129
+ var LandingInput = z46.object({
9685
10130
  images: CreativeRefs.images,
9686
10131
  language: CreativeRefs.language,
9687
10132
  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.'),
10133
+ salesAngle: z46.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
9689
10134
  buyerPersona: CreativeRefs.buyerPersona,
9690
10135
  price: CreativeRefs.price,
9691
10136
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -9736,7 +10181,6 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
9736
10181
  });
9737
10182
  server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (15 o 30), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nCOMPETENCIA Y GAP REPLICATION: con videoPreset="auto" (default) y competitorScanEnabled=true (default), el sistema analiza anuncios de competencia. Si encuentra un winner de video coherente, ancla el primaryAngle y genera autom\xE1ticamente la narrativa `gap_replication` para copiar su estructura audiovisual. No la selecciones manualmente: es un resultado del an\xE1lisis. Si competitorScanEnabled=false, esta b\xFAsqueda se omite.\n\nESTILO: 15s usa "Realista" (single_call); 30s usa "Multiescena" (multi-toma, aproximadamente 30s). Multiescena se factura por escena, as\xED que avisa el costo antes de activarlo.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes. Para revisar el \xE1ngulo y decidir duraci\xF3n tras el an\xE1lisis de competencia, pasa `pauseForAngleReview: true` y luego resuelve `angle_review_approval` con `gapDurationSeconds`.', VideoInput.shape, async (input) => {
9738
10183
  try {
9739
- assertVideoPresetDurationCompatibility(input.videoPreset, input.durationSeconds);
9740
10184
  const resolved = resolveVideoPreset(input.videoPreset);
9741
10185
  const wireTier = resolveWireVideoTier(input.videoTier, input.durationSeconds);
9742
10186
  const { videoTier: _publicTier, videoResolution: _vr, projectId, ...rest } = input;
@@ -9811,8 +10255,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
9811
10255
  __name(registerCreativeTools, "registerCreativeTools");
9812
10256
 
9813
10257
  // src/tools/creative-variants.ts
9814
- import { z as z46 } from "zod";
9815
- var LandingSectionKeyEnum = z46.enum([
10258
+ import { z as z47 } from "zod";
10259
+ var LandingSectionKeyEnum = z47.enum([
9816
10260
  "problem_hook",
9817
10261
  "problem_cost",
9818
10262
  "solution_mechanism",
@@ -9852,24 +10296,24 @@ var LandingSectionKeyEnum = z46.enum([
9852
10296
  "lifestyle_fit",
9853
10297
  "join_lifestyle"
9854
10298
  ]);
9855
- var CreativeVariantKindEnum = z46.enum([
10299
+ var CreativeVariantKindEnum = z47.enum([
9856
10300
  "video",
9857
10301
  "image",
9858
10302
  "landing"
9859
10303
  ]);
9860
- var EstimateCreativeVariantInputBase = z46.object({
9861
- sourceTaskId: z46.string().uuid().describe("ID de la tarea original completada."),
10304
+ var EstimateCreativeVariantInputBase = z47.object({
10305
+ sourceTaskId: z47.string().uuid().describe("ID de la tarea original completada."),
9862
10306
  kind: CreativeVariantKindEnum,
9863
10307
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
9864
10308
  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)."),
10309
+ nVariants: z47.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
9866
10310
  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.")
10311
+ instructions: z47.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
9868
10312
  }).strict();
9869
10313
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
9870
10314
  if (input.kind === "video" && input.nVariants !== void 0) {
9871
10315
  ctx.addIssue({
9872
- code: z46.ZodIssueCode.custom,
10316
+ code: z47.ZodIssueCode.custom,
9873
10317
  path: [
9874
10318
  "nVariants"
9875
10319
  ],
@@ -9879,7 +10323,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9879
10323
  if (input.kind !== "video") {
9880
10324
  if (input.videoPreset !== void 0) {
9881
10325
  ctx.addIssue({
9882
- code: z46.ZodIssueCode.custom,
10326
+ code: z47.ZodIssueCode.custom,
9883
10327
  path: [
9884
10328
  "videoPreset"
9885
10329
  ],
@@ -9888,7 +10332,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9888
10332
  }
9889
10333
  if (input.videoResolution !== void 0) {
9890
10334
  ctx.addIssue({
9891
- code: z46.ZodIssueCode.custom,
10335
+ code: z47.ZodIssueCode.custom,
9892
10336
  path: [
9893
10337
  "videoResolution"
9894
10338
  ],
@@ -9898,7 +10342,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9898
10342
  }
9899
10343
  if (input.kind !== "image" && input.nVariants !== void 0) {
9900
10344
  ctx.addIssue({
9901
- code: z46.ZodIssueCode.custom,
10345
+ code: z47.ZodIssueCode.custom,
9902
10346
  path: [
9903
10347
  "nVariants"
9904
10348
  ],
@@ -9908,7 +10352,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9908
10352
  if (input.kind !== "landing") {
9909
10353
  if (input.sectionKey !== void 0) {
9910
10354
  ctx.addIssue({
9911
- code: z46.ZodIssueCode.custom,
10355
+ code: z47.ZodIssueCode.custom,
9912
10356
  path: [
9913
10357
  "sectionKey"
9914
10358
  ],
@@ -9917,7 +10361,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9917
10361
  }
9918
10362
  if (input.instructions !== void 0) {
9919
10363
  ctx.addIssue({
9920
- code: z46.ZodIssueCode.custom,
10364
+ code: z47.ZodIssueCode.custom,
9921
10365
  path: [
9922
10366
  "instructions"
9923
10367
  ],
@@ -9926,31 +10370,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9926
10370
  }
9927
10371
  }
9928
10372
  });
9929
- var CreateCreativeVariantInputBase = z46.object({
9930
- sourceTaskId: z46.string().uuid().describe("ID de la tarea original completada."),
10373
+ var CreateCreativeVariantInputBase = z47.object({
10374
+ sourceTaskId: z47.string().uuid().describe("ID de la tarea original completada."),
9931
10375
  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."),
10376
+ confirm: z47.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
10377
+ clientRequestId: z47.string().uuid().describe("UUID estable y obligatorio para esta intenci\xF3n. Reutil\xEDzalo ante timeouts o transporte ambiguo. Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n y un UUID nuevo."),
10378
+ angle: z47.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
10379
+ buyerPersona: z47.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
10380
+ scriptNotes: z47.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
10381
+ creativeDirection: z47.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
9938
10382
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
9939
10383
  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)."),
10384
+ generateAiMusic: z47.boolean().optional().describe("Opcional para video."),
10385
+ nVariants: z47.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
9942
10386
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
9943
10387
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
9944
10388
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
9945
10389
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
9946
- styleMix: z46.boolean().optional().describe("Opcional para imagen."),
10390
+ styleMix: z47.boolean().optional().describe("Opcional para imagen."),
9947
10391
  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.")
10392
+ instructions: z47.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
9949
10393
  }).strict();
9950
10394
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
9951
10395
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
9952
10396
  ctx.addIssue({
9953
- code: z46.ZodIssueCode.custom,
10397
+ code: z47.ZodIssueCode.custom,
9954
10398
  path: [
9955
10399
  "angle"
9956
10400
  ],
@@ -9959,7 +10403,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
9959
10403
  }
9960
10404
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
9961
10405
  ctx.addIssue({
9962
- code: z46.ZodIssueCode.custom,
10406
+ code: z47.ZodIssueCode.custom,
9963
10407
  path: [
9964
10408
  "instructions"
9965
10409
  ],
@@ -10005,7 +10449,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
10005
10449
  for (const field of fieldsByKind[input.kind]) {
10006
10450
  if (input[field] !== void 0) {
10007
10451
  ctx.addIssue({
10008
- code: z46.ZodIssueCode.custom,
10452
+ code: z47.ZodIssueCode.custom,
10009
10453
  path: [
10010
10454
  field
10011
10455
  ],
@@ -10122,27 +10566,27 @@ function registerCreativeVariantTools(server, api) {
10122
10566
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
10123
10567
 
10124
10568
  // 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.')
10569
+ import { z as z48 } from "zod";
10570
+ var GetVideoScriptInput = z48.object({
10571
+ taskId: z48.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
10572
+ angleKey: z48.string().min(1).optional().describe('Clave del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). REQUERIDO para tareas multi-angle: si se omite, el backend resuelve a "angle_0" LITERAL (no "el primer pendiente"), as\xED que en multi-angle donde angle_0 ya est\xE9 aprobado/saltado obtendr\xEDas status="approved" y nunca alcanzar\xEDas el \xE1ngulo realmente pendiente. Para single-angle puede omitirse.')
10129
10573
  }).strict();
10130
10574
  var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
10131
10575
  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, {
10576
+ var ApproveSceneEdit = z48.object({
10577
+ index: z48.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
10578
+ dialogue: z48.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
10135
10579
  message: "dialogue no puede ser s\xF3lo espacios"
10136
10580
  }).describe("Di\xE1logo final de la escena. NO puede estar vac\xEDo ni ser s\xF3lo espacios. Puede ser el original sin cambios o la edici\xF3n del usuario.")
10137
10581
  }).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.")
10582
+ var ApproveVideoScriptInput = z48.object({
10583
+ taskId: z48.string().min(1).describe("ID de la tarea."),
10584
+ angleKey: z48.string().trim().min(1).max(120).optional().describe('Mismo angleKey usado en get_video_script. REQUERIDO en multi-angle (si se omite el backend resuelve a "angle_0" literal). En single-angle puede omitirse.'),
10585
+ scenes: z48.array(ApproveSceneEdit).min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_SCENES2).describe("Escenas con los di\xE1logos finales. El backend mergea por `index` \u2014 las escenas NO incluidas conservan el di\xE1logo original. Debe haber entre 1 y 50 entradas. Para cada entrada que mandes con un dialogue distinto del original, el backend valida minWords \u2264 words \u2264 maxWords y rechaza con 400 si te sales del rango.")
10142
10586
  }).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.'),
10587
+ var RegenerateVideoScriptInput = z48.object({
10588
+ taskId: z48.string().min(1).describe("ID de la tarea."),
10589
+ angleKey: z48.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
10146
10590
  narrativeStyle: NarrativeStyleEnum.describe('Narrativa para el nuevo guion. Incluye valores hist\xF3ricos porque esta herramienta tambi\xE9n regenera tareas antiguas. REQUERIDO \u2014 el backend NO acepta "mantener la actual"; siempre tienes que elegir una. Si el usuario quiere re-rolear con la misma narrativa, pasa la que ya tiene el \xE1ngulo (la ves en get_video_script o en get_task_status).')
10147
10591
  }).strict();
10148
10592
  function registerScriptReviewTools(server, api) {
@@ -10182,9 +10626,9 @@ function registerScriptReviewTools(server, api) {
10182
10626
  __name(registerScriptReviewTools, "registerScriptReviewTools");
10183
10627
 
10184
10628
  // src/tools/connections.ts
10185
- import { z as z48 } from "zod";
10629
+ import { z as z49 } from "zod";
10186
10630
  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 () => {
10631
+ server.tool("list_shopify_shops", "Lista las tiendas Shopify conectadas por este usuario. Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.", z49.object({}).shape, async () => {
10188
10632
  try {
10189
10633
  const r = await api.getJson("/v1/shopify/shops");
10190
10634
  return asText(r);
@@ -10192,10 +10636,10 @@ function registerConnectionTools(server, api) {
10192
10636
  throw translateError(e);
10193
10637
  }
10194
10638
  });
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()
10639
+ server.tool("list_shopify_products", 'Busca productos en una tienda Shopify conectada para elegir el target de una republicaci\xF3n. Usa el `id` devuelto junto con `publicationMode="existing"` en `publish_to_shopify`. Si `taskId` se proporciona, la respuesta tambi\xE9n indica el producto publicado actualmente para esa tarea y tienda. No modifica Shopify.', z49.object({
10640
+ shopDomain: z49.string().min(3).max(120),
10641
+ query: z49.string().trim().max(120).optional(),
10642
+ taskId: z49.string().uuid().optional()
10199
10643
  }).shape, async ({ shopDomain, query, taskId }) => {
10200
10644
  try {
10201
10645
  const params = new URLSearchParams({
@@ -10209,7 +10653,7 @@ function registerConnectionTools(server, api) {
10209
10653
  throw translateError(e);
10210
10654
  }
10211
10655
  });
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 () => {
10656
+ server.tool("list_meta_ad_accounts", "Lista las cuentas publicitarias de Meta conectadas. Para publicar, prefiere `list_meta_destinations`, que tambi\xE9n devuelve el portfolio y las p\xE1ginas disponibles. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar Meta en productmaker.app/integrations.", z49.object({}).shape, async () => {
10213
10657
  try {
10214
10658
  const r = await api.getJson("/v1/facebook/ad-accounts");
10215
10659
  return asText(r);
@@ -10217,7 +10661,15 @@ function registerConnectionTools(server, api) {
10217
10661
  throw translateError(e);
10218
10662
  }
10219
10663
  });
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 () => {
10664
+ server.tool("list_meta_destinations", "Lista los destinos de publicaci\xF3n de Meta del usuario: Business portfolios, cuentas publicitarias dentro de cada portfolio y Facebook Pages disponibles. Es obligatorio llamar esta herramienta antes de publicar, mostrar las opciones y enviar al publicador el `portfolioId`, `adAccountId` y `pageId` elegidos; nunca elijas silenciosamente la primera p\xE1gina o cuenta. No modifica Meta.", z49.object({}).shape, async () => {
10665
+ try {
10666
+ const r = await api.getJson("/v1/facebook/destinations");
10667
+ return asText(r);
10668
+ } catch (e) {
10669
+ throw translateError(e);
10670
+ }
10671
+ });
10672
+ server.tool("list_tiktok_advertisers", "Lista las cuentas de TikTok Ads conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.", z49.object({}).shape, async () => {
10221
10673
  try {
10222
10674
  const r = await api.getJson("/v1/tiktok/advertisers");
10223
10675
  return asText(r);
@@ -10229,9 +10681,9 @@ function registerConnectionTools(server, api) {
10229
10681
  __name(registerConnectionTools, "registerConnectionTools");
10230
10682
 
10231
10683
  // src/tools/actors.ts
10232
- import { z as z49 } from "zod";
10684
+ import { z as z50 } from "zod";
10233
10685
  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 () => {
10686
+ server.tool("list_actors", "Lista los personajes guardados del usuario (personas creadas con IA que pueden protagonizar los videos e im\xE1genes). Devuelve id, nombre, descripci\xF3n, g\xE9nero y voz. Usa el `id` como `actorId` al crear o editar una tarea. Solo aparecen los personajes listos para usar; si la lista est\xE1 vac\xEDa, dile al usuario que cree uno en productmaker.app antes de intentar asignarlo. No modifica nada.", z50.object({}).shape, async () => {
10235
10687
  try {
10236
10688
  const rows = await api.getJson("/v1/actors");
10237
10689
  const actors = (Array.isArray(rows) ? rows : []).filter((actor) => actor.status === "ready").map(({ id, name, description, gender, voiceId }) => ({
@@ -10252,105 +10704,116 @@ function registerActorTools(server, api) {
10252
10704
  __name(registerActorTools, "registerActorTools");
10253
10705
 
10254
10706
  // 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),
10707
+ import { z as z51 } from "zod";
10708
+ var ShopifyInput = z51.object({
10709
+ taskId: z51.string().min(1),
10710
+ shopDomain: z51.string().min(1),
10259
10711
  /** Override: number of landing images to include. Default = ALL images the
10260
10712
  * task generated (the MCP fetches /v1/results first to count). Bounded
10261
10713
  * generously here; the backend validates each index against
10262
10714
  * 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(),
10715
+ numLandingImages: z51.number().int().min(1).max(20).optional(),
10716
+ variantIndex: z51.number().int().min(0).optional(),
10265
10717
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
10266
10718
  * When true, the landing renders variant-picker pills; when false, it ships
10267
10719
  * a single-option product page. Defaults to false. */
10268
- showVariantPicker: z50.boolean().optional(),
10720
+ showVariantPicker: z51.boolean().optional(),
10269
10721
  /** Default `auto` updates the product previously published for this task and
10270
10722
  * shop. Use `existing` with targetProductId to choose another product, or
10271
10723
  * `new` only when the user explicitly wants a duplicate/new listing. */
10272
- publicationMode: z50.enum([
10724
+ publicationMode: z51.enum([
10273
10725
  "auto",
10274
10726
  "existing",
10275
10727
  "new"
10276
10728
  ]).optional(),
10277
10729
  /** Shopify product GID, numeric id, or admin product URL. Required when
10278
10730
  * publicationMode is `existing`. */
10279
- targetProductId: z50.string().trim().min(1).max(300).optional()
10731
+ targetProductId: z51.string().trim().min(1).max(300).optional()
10280
10732
  });
10281
- var McpCampaignConfig = z50.object({
10733
+ var McpCampaignConfig = z51.object({
10282
10734
  /** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
10283
10735
  * converts to the account currency server-side. Do NOT pre-convert: on a COP
10284
10736
  * account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
10285
- dailyBudgetUsd: z50.number().positive().optional(),
10737
+ dailyBudgetUsd: z51.number().positive().optional(),
10286
10738
  /** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
10287
10739
  * 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()
10740
+ targetCountries: z51.array(z51.string().length(2)).max(1).optional(),
10741
+ campaignName: z51.string().trim().min(1).max(100).optional()
10290
10742
  });
10291
10743
  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),
10744
+ var MetaInput = z51.object({
10745
+ taskId: z51.string().min(1),
10746
+ /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
10747
+ portfolioId: z51.string().trim().min(1),
10748
+ /** Exact Facebook Page selected from `list_meta_destinations`. */
10749
+ pageId: z51.string().trim().min(1),
10294
10750
  adAccountId: McpAdAccountId,
10295
- pixelId: z50.string().min(1).optional(),
10296
- landingUrl: z50.string().url().optional(),
10751
+ pixelId: z51.string().min(1).optional(),
10752
+ landingUrl: z51.string().url().optional(),
10297
10753
  /** Select which angles to publish by 0-based index. Omit to publish all
10298
10754
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
10299
- angleIndexes: z50.array(z50.number().int().nonnegative()).optional(),
10755
+ angleIndexes: z51.array(z51.number().int().nonnegative()).optional(),
10300
10756
  campaignConfig: McpCampaignConfig.optional(),
10301
10757
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
10302
10758
  * uses the auto-generated copy attached to the task. Capped at 500 chars
10303
10759
  * (Meta's primary-text limit). */
10304
- primaryText: z50.string().trim().min(1).max(500).optional(),
10760
+ primaryText: z51.string().trim().min(1).max(500).optional(),
10305
10761
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
10306
10762
  * (wa.me) instead of a landing page. */
10307
- phoneNumber: z50.string().min(8).max(32).optional()
10763
+ phoneNumber: z51.string().min(8).max(32).optional()
10308
10764
  });
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([
10765
+ var GroupedTaskSource = z51.object({
10766
+ type: z51.literal("task"),
10767
+ taskId: z51.string().min(1),
10768
+ angleIndex: z51.number().int().min(0).optional(),
10769
+ videoVariant: z51.enum([
10314
10770
  "with_subtitles",
10315
10771
  "without_subtitles"
10316
10772
  ]).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()
10773
+ imageVariantIndex: z51.number().int().min(0).optional()
10774
+ });
10775
+ var GroupedMediaSource = z51.object({
10776
+ type: z51.literal("media"),
10777
+ url: z51.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_meta_media_upload on the hosted MCP instead."),
10778
+ path: z51.string().min(1).optional().describe('Local file path only when this MCP runs over stdio. On the hosted MCP, call request_meta_media_upload and then use source.type="staged".'),
10779
+ thumbnailUrl: z51.string().url().optional(),
10780
+ thumbnailPath: z51.string().min(1).optional()
10325
10781
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
10326
10782
  message: "media source needs exactly one of `url` or `path`"
10327
10783
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
10328
10784
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
10329
10785
  });
10330
- var GroupedCreative = z50.object({
10331
- type: z50.enum([
10786
+ var GroupedStagedMediaSource = z51.object({
10787
+ type: z51.literal("staged"),
10788
+ /** Returned by check_meta_media_upload after the user selects a local file. */
10789
+ stagingAssetId: z51.string().uuid(),
10790
+ thumbnailAssetId: z51.string().uuid().optional()
10791
+ });
10792
+ var GroupedCreative = z51.object({
10793
+ type: z51.enum([
10332
10794
  "image",
10333
10795
  "video"
10334
10796
  ]),
10335
- source: z50.union([
10797
+ source: z51.union([
10336
10798
  GroupedTaskSource,
10337
- GroupedMediaSource
10799
+ GroupedMediaSource,
10800
+ GroupedStagedMediaSource
10338
10801
  ]),
10339
- title: z50.string().max(120).optional(),
10340
- description: z50.string().max(500).optional(),
10802
+ title: z51.string().max(120).optional(),
10803
+ description: z51.string().max(500).optional(),
10341
10804
  cta: MetaCtaType.optional(),
10342
- creativeId: z50.string().min(1).optional()
10805
+ creativeId: z51.string().min(1).optional()
10343
10806
  });
10344
- var GroupedCampaignConfig = z50.object({
10807
+ var GroupedCampaignConfig = z51.object({
10345
10808
  campaignType: MetaCampaignType.default("sales"),
10346
10809
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
10347
10810
  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)
10811
+ campaignDailyBudget: z51.number().int().positive().optional(),
10812
+ adSets: z51.array(z51.object({
10813
+ tempId: z51.string().min(1).max(80),
10814
+ name: z51.string().min(1).max(100),
10815
+ dailyBudget: z51.number().int().positive().optional(),
10816
+ creativeIds: z51.array(z51.string().min(1)).min(1)
10354
10817
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
10355
10818
  /**
10356
10819
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -10358,22 +10821,22 @@ var GroupedCampaignConfig = z50.object({
10358
10821
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
10359
10822
  * the API since #849 — unreachable from this tool.
10360
10823
  */
10361
- destination: z50.discriminatedUnion("type", [
10362
- z50.object({
10363
- type: z50.literal("landing"),
10364
- landingUrl: z50.string().url()
10824
+ destination: z51.discriminatedUnion("type", [
10825
+ z51.object({
10826
+ type: z51.literal("landing"),
10827
+ landingUrl: z51.string().url()
10365
10828
  }),
10366
- z50.object({
10367
- type: z50.literal("whatsapp"),
10368
- phoneNumber: z50.string().min(8).max(32)
10829
+ z51.object({
10830
+ type: z51.literal("whatsapp"),
10831
+ phoneNumber: z51.string().min(8).max(32)
10369
10832
  })
10370
10833
  ]),
10371
- saveAsDefault: z50.boolean().default(false),
10834
+ saveAsDefault: z51.boolean().default(false),
10372
10835
  budgetPolicy: MetaBudgetPolicy.optional()
10373
10836
  }).superRefine((value, ctx) => {
10374
10837
  if (value.budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
10375
10838
  ctx.addIssue({
10376
- code: z50.ZodIssueCode.custom,
10839
+ code: z51.ZodIssueCode.custom,
10377
10840
  path: [
10378
10841
  "campaignDailyBudget"
10379
10842
  ],
@@ -10384,7 +10847,7 @@ var GroupedCampaignConfig = z50.object({
10384
10847
  value.adSets.forEach((adSet, index) => {
10385
10848
  if (adSet.dailyBudget === void 0) {
10386
10849
  ctx.addIssue({
10387
- code: z50.ZodIssueCode.custom,
10850
+ code: z51.ZodIssueCode.custom,
10388
10851
  path: [
10389
10852
  "adSets",
10390
10853
  index,
@@ -10396,22 +10859,26 @@ var GroupedCampaignConfig = z50.object({
10396
10859
  });
10397
10860
  }
10398
10861
  });
10399
- var PublishCreativesToMetaInputBase = z50.object({
10400
- creatives: z50.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10862
+ var PublishCreativesToMetaInputBase = z51.object({
10863
+ creatives: z51.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10864
+ /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
10865
+ portfolioId: z51.string().trim().min(1),
10866
+ /** Exact Facebook Page selected from `list_meta_destinations`. */
10867
+ pageId: z51.string().trim().min(1),
10401
10868
  adAccountId: McpAdAccountId,
10402
- pixelId: z50.string().min(1).optional(),
10869
+ pixelId: z51.string().min(1).optional(),
10403
10870
  /** Required unless `campaignConfig.destination` is whatsapp — see the
10404
10871
  * 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),
10872
+ landingUrl: z51.string().url().optional(),
10873
+ targetCountry: z51.string().length(2),
10874
+ campaignName: z51.string().trim().min(1).max(100),
10408
10875
  campaignConfig: GroupedCampaignConfig.optional()
10409
10876
  });
10410
10877
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
10411
10878
  const destination = value.campaignConfig?.destination;
10412
10879
  if (destination?.type !== "whatsapp" && !value.landingUrl) {
10413
10880
  ctx.addIssue({
10414
- code: z50.ZodIssueCode.custom,
10881
+ code: z51.ZodIssueCode.custom,
10415
10882
  path: [
10416
10883
  "landingUrl"
10417
10884
  ],
@@ -10420,7 +10887,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10420
10887
  }
10421
10888
  if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
10422
10889
  ctx.addIssue({
10423
- code: z50.ZodIssueCode.custom,
10890
+ code: z51.ZodIssueCode.custom,
10424
10891
  path: [
10425
10892
  "campaignConfig",
10426
10893
  "destination",
@@ -10430,46 +10897,50 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10430
10897
  });
10431
10898
  }
10432
10899
  });
10433
- var PresignUploadResponseSchema = z50.object({
10434
- uploadUrl: z50.string().url().refine((value) => {
10900
+ var PresignUploadResponseSchema = z51.object({
10901
+ uploadUrl: z51.string().url().refine((value) => {
10435
10902
  const url = new URL(value);
10436
10903
  return !url.username && !url.password && url.protocol === "https:";
10437
10904
  }, "presigned upload URL must be HTTPS without embedded credentials"),
10438
- publicUrl: z50.string().url().refine((value) => {
10905
+ publicUrl: z51.string().url().refine((value) => {
10439
10906
  const url = new URL(value);
10440
10907
  return !url.username && !url.password;
10441
- }, "public media URL must not contain embedded credentials")
10908
+ }, "public media URL must not contain embedded credentials"),
10909
+ stagingAssetId: z51.string().uuid()
10442
10910
  });
10443
- async function uploadResolvedMedia(api, media) {
10444
- if (media.url) return media.url;
10911
+ async function uploadResolvedMedia(api, media, assetRole) {
10445
10912
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
10446
10913
  const presigned = PresignUploadResponseSchema.parse(await api.postJson("/v1/ads/presign-upload", {
10447
10914
  contentType: media.mimetype,
10448
10915
  fileName: media.filename,
10449
- fileSize: media.buffer.byteLength
10916
+ fileSize: media.buffer.byteLength,
10917
+ purpose: "meta_staging",
10918
+ assetRole
10450
10919
  }));
10451
10920
  await api.putBytes(presigned.uploadUrl, media.buffer, media.mimetype);
10452
- return presigned.publicUrl;
10921
+ return {
10922
+ stagingAssetId: presigned.stagingAssetId
10923
+ };
10453
10924
  }
10454
10925
  __name(uploadResolvedMedia, "uploadResolvedMedia");
10455
- async function resolveAndUploadMedia(api, ref, expectedKind, transport) {
10926
+ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transport) {
10456
10927
  const media = await resolveMediaRef(ref, {
10457
10928
  transport,
10458
10929
  expectedKind
10459
10930
  });
10460
- return uploadResolvedMedia(api, media);
10931
+ return uploadResolvedMedia(api, media, assetRole);
10461
10932
  }
10462
10933
  __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(),
10934
+ var TikTokInput = z51.object({
10935
+ taskId: z51.string().min(1),
10936
+ advertiserId: z51.string().min(1),
10937
+ pixelId: z51.string().min(1).optional(),
10938
+ landingUrl: z51.string().url().optional(),
10939
+ angleIndexes: z51.array(z51.number().int().nonnegative()).optional(),
10469
10940
  campaignConfig: McpCampaignConfig.optional(),
10470
10941
  /** Override the ad's description (TikTok's body copy). If omitted,
10471
10942
  * ProductMaker uses the auto-generated copy attached to the task. */
10472
- primaryText: z50.string().trim().min(1).max(500).optional()
10943
+ primaryText: z51.string().trim().min(1).max(500).optional()
10473
10944
  });
10474
10945
  function registerPublishTools(server, api, opts = {}) {
10475
10946
  const transport = opts.transport ?? "http";
@@ -10508,7 +10979,7 @@ function registerPublishTools(server, api, opts = {}) {
10508
10979
  throw translateError(e);
10509
10980
  }
10510
10981
  });
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 }) => {
10982
+ server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
10512
10983
  try {
10513
10984
  const body = {
10514
10985
  ...rest
@@ -10536,7 +11007,7 @@ function registerPublishTools(server, api, opts = {}) {
10536
11007
  throw translateError(e);
10537
11008
  }
10538
11009
  });
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) => {
11010
+ server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo para reintento y el TTL los limpia. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
10540
11011
  try {
10541
11012
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
10542
11013
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -10562,25 +11033,44 @@ function registerPublishTools(server, api, opts = {}) {
10562
11033
  });
10563
11034
  continue;
10564
11035
  }
10565
- const mediaUrl = await resolveAndUploadMedia(api, {
11036
+ if (creative.source.type === "staged") {
11037
+ creatives.push({
11038
+ type: creative.type,
11039
+ source: {
11040
+ type: "media",
11041
+ mediaAssetId: creative.source.stagingAssetId,
11042
+ ...creative.source.thumbnailAssetId ? {
11043
+ thumbnailAssetId: creative.source.thumbnailAssetId
11044
+ } : {}
11045
+ },
11046
+ title: creative.title,
11047
+ description: creative.description,
11048
+ cta: creative.cta,
11049
+ creativeId
11050
+ });
11051
+ continue;
11052
+ }
11053
+ const stagedMedia = await resolveAndUploadMedia(api, {
10566
11054
  url: creative.source.url,
10567
11055
  path: creative.source.path
10568
- }, creative.type, transport);
10569
- let thumbnailUrl;
11056
+ }, creative.type, "media", transport);
11057
+ let stagedThumbnail;
10570
11058
  if (creative.type === "video") {
10571
11059
  if (creative.source.thumbnailUrl || creative.source.thumbnailPath) {
10572
- thumbnailUrl = await resolveAndUploadMedia(api, {
11060
+ stagedThumbnail = await resolveAndUploadMedia(api, {
10573
11061
  url: creative.source.thumbnailUrl,
10574
11062
  path: creative.source.thumbnailPath
10575
- }, "image", transport);
11063
+ }, "image", "thumbnail", transport);
10576
11064
  }
10577
11065
  }
10578
11066
  creatives.push({
10579
11067
  type: creative.type,
10580
11068
  source: {
10581
11069
  type: "media",
10582
- mediaUrl,
10583
- thumbnailUrl
11070
+ mediaAssetId: stagedMedia.stagingAssetId,
11071
+ ...stagedThumbnail ? {
11072
+ thumbnailAssetId: stagedThumbnail.stagingAssetId
11073
+ } : {}
10584
11074
  },
10585
11075
  title: creative.title,
10586
11076
  description: creative.description,
@@ -10590,6 +11080,8 @@ function registerPublishTools(server, api, opts = {}) {
10590
11080
  }
10591
11081
  const r = await api.postJson("/v1/facebook/campaigns/from-tasks", {
10592
11082
  creatives,
11083
+ portfolioId: input.portfolioId,
11084
+ pageId: input.pageId,
10593
11085
  adAccountId: input.adAccountId,
10594
11086
  pixelId: input.pixelId,
10595
11087
  landingUrl: input.landingUrl,
@@ -10679,14 +11171,146 @@ function toTikTokCampaignConfig(cfg) {
10679
11171
  }
10680
11172
  __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
10681
11173
 
11174
+ // src/tools/meta-upload.ts
11175
+ import { z as z52 } from "zod";
11176
+ var MetaUploadSessionInputBase = z52.object({
11177
+ mediaKind: z52.enum([
11178
+ "image",
11179
+ "video",
11180
+ "mixed"
11181
+ ]).default("mixed").describe("The kind of file the user will select in the upload page. Use mixed for a normal batch of image and video creatives."),
11182
+ assetRole: z52.enum([
11183
+ "media",
11184
+ "thumbnail"
11185
+ ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail.")
11186
+ });
11187
+ var OpenAiFileReference = z52.object({
11188
+ download_url: z52.string().url(),
11189
+ file_id: z52.string().min(1),
11190
+ mime_type: z52.string().min(1).optional(),
11191
+ file_name: z52.string().min(1).optional()
11192
+ }).strict();
11193
+ var MetaUploadSessionToolInputShape = {
11194
+ ...MetaUploadSessionInputBase.shape,
11195
+ files: z52.array(OpenAiFileReference).max(10).optional().describe("Optional OpenAI-hosted file references provided by ChatGPT. They are only metadata for the UI bridge and are never forwarded to ProductMaker.")
11196
+ };
11197
+ var MetaUploadSessionToolInput = z52.object(MetaUploadSessionToolInputShape).superRefine((value, ctx) => {
11198
+ if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
11199
+ ctx.addIssue({
11200
+ code: z52.ZodIssueCode.custom,
11201
+ path: [
11202
+ "mediaKind"
11203
+ ],
11204
+ message: "Meta thumbnails must be images"
11205
+ });
11206
+ }
11207
+ });
11208
+ var MetaUploadSessionResponse2 = z52.object({
11209
+ sessionId: z52.string().uuid(),
11210
+ uploadPageUrl: z52.string().url(),
11211
+ mediaKind: z52.enum([
11212
+ "image",
11213
+ "video",
11214
+ "mixed"
11215
+ ]),
11216
+ assetRole: z52.enum([
11217
+ "media",
11218
+ "thumbnail"
11219
+ ]),
11220
+ maxFiles: z52.number().int().positive(),
11221
+ expiresAt: z52.string().datetime()
11222
+ });
11223
+ var MetaUploadStatusInput = z52.object({
11224
+ sessionId: z52.string().uuid().describe("The sessionId returned by request_meta_media_upload.")
11225
+ });
11226
+ var MetaUploadStatusResponse = z52.object({
11227
+ sessionId: z52.string().uuid(),
11228
+ status: z52.enum([
11229
+ "pending",
11230
+ "uploaded"
11231
+ ]),
11232
+ assets: z52.array(z52.object({
11233
+ stagingAssetId: z52.string().uuid(),
11234
+ mediaKind: z52.enum([
11235
+ "image",
11236
+ "video"
11237
+ ])
11238
+ })).max(10).default([]),
11239
+ stagingAssetId: z52.string().uuid().optional()
11240
+ });
11241
+ function uploadSessionText(result) {
11242
+ const mediaLabel = result.mediaKind === "mixed" ? "archivos" : result.mediaKind === "video" ? "video" : "imagen";
11243
+ return {
11244
+ content: [
11245
+ {
11246
+ type: "text",
11247
+ text: [
11248
+ `### Sube tu ${mediaLabel} a ProductMaker`,
11249
+ `[Abrir carga segura](${result.uploadPageUrl})`,
11250
+ `Puedes seleccionar hasta ${result.maxFiles} ${result.mediaKind === "mixed" ? "archivos" : "archivo"}. Cuando termine, escribe **\u201Clisto\u201D** en este chat para que ProductMaker contin\xFAe con Meta.`,
11251
+ `sessionId: \`${result.sessionId}\``,
11252
+ `Este enlace expira: ${result.expiresAt}`
11253
+ ].join("\n\n")
11254
+ }
11255
+ ]
11256
+ };
11257
+ }
11258
+ __name(uploadSessionText, "uploadSessionText");
11259
+ function registerMetaUploadTools(server, api) {
11260
+ server.registerTool("request_meta_media_upload", {
11261
+ title: "Solicitar carga segura de Meta",
11262
+ description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. En ChatGPT puede recibir referencias opcionales de `files`, pero NO las descarga ni las reenv\xEDa: siempre devuelve un enlace de ProductMaker donde el usuario selecciona los archivos. No pidas URL CDN, ruta local, bytes ni base64. Despu\xE9s conserva el `sessionId` y llama `check_meta_media_upload`. Esta herramienta no publica ni consume cr\xE9ditos.",
11263
+ inputSchema: MetaUploadSessionToolInputShape,
11264
+ outputSchema: MetaUploadSessionResponse2,
11265
+ _meta: {
11266
+ ui: {
11267
+ resourceUri: META_UPLOAD_APP_RESOURCE_URI,
11268
+ visibility: [
11269
+ "model",
11270
+ "app"
11271
+ ]
11272
+ },
11273
+ "openai/outputTemplate": META_UPLOAD_APP_RESOURCE_URI,
11274
+ "openai/widgetAccessible": true,
11275
+ "openai/fileParams": [
11276
+ "files"
11277
+ ],
11278
+ "openai/toolInvocation/invoking": "Preparando carga segura\u2026",
11279
+ "openai/toolInvocation/invoked": "Carga segura lista"
11280
+ }
11281
+ }, async (rawInput) => {
11282
+ try {
11283
+ const input = MetaUploadSessionToolInput.parse(rawInput);
11284
+ const { files: _files, ...sessionInput } = input;
11285
+ const result = MetaUploadSessionResponse2.parse(await api.postJson("/v1/ads/meta-upload-sessions", sessionInput));
11286
+ return {
11287
+ structuredContent: result,
11288
+ ...uploadSessionText(result)
11289
+ };
11290
+ } catch (error) {
11291
+ throw translateError(error);
11292
+ }
11293
+ });
11294
+ server.tool("check_meta_media_upload", 'Comprueba si el usuario ya termin\xF3 de subir desde el enlace de ProductMaker. Si status=uploaded, devuelve `assets[]`; crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como el tipo del creativo, hasta diez en el batch. Si la carga fue parcial, publica \xFAnicamente los assets que `assets[]` reporta como subidos. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.', MetaUploadStatusInput.shape, async (rawInput) => {
11295
+ try {
11296
+ const input = MetaUploadStatusInput.parse(rawInput);
11297
+ const result = MetaUploadStatusResponse.parse(await api.getJson(`/v1/ads/meta-upload-sessions/${encodeURIComponent(input.sessionId)}`));
11298
+ return asText(result);
11299
+ } catch (error) {
11300
+ throw translateError(error);
11301
+ }
11302
+ });
11303
+ }
11304
+ __name(registerMetaUploadTools, "registerMetaUploadTools");
11305
+
10682
11306
  // 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.")
11307
+ import { z as z53 } from "zod";
11308
+ var MetaStatusInput = z53.object({
11309
+ campaignId: z53.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
10686
11310
  });
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.")
11311
+ var TikTokStatusInput = z53.object({
11312
+ campaignId: z53.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
11313
+ advertiserId: z53.string().min(1).describe("The same advertiserId used to publish the campaign.")
10690
11314
  });
10691
11315
  function registerCampaignTools(server, api) {
10692
11316
  server.tool("get_meta_campaign_status", "Consulta el estado actual + m\xE9tricas lifetime (spend, impressions, clicks, reach) de una campa\xF1a Meta publicada. Llama esto DESPU\xC9S de `publish_to_meta` para confirmar que la campa\xF1a se cre\xF3 correctamente y para reportar performance al usuario. Es read-only \u2014 no modifica nada en Meta Ads Manager.", MetaStatusInput.shape, async (input) => {
@@ -10783,10 +11407,10 @@ function renderTikTokStatusMarkdown(r) {
10783
11407
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
10784
11408
 
10785
11409
  // ../../packages/product-economics/src/product-costing.ts
10786
- import { z as z53 } from "zod";
11410
+ import { z as z55 } from "zod";
10787
11411
 
10788
11412
  // ../../packages/product-economics/src/product-costing-countries.ts
10789
- import { z as z52 } from "zod";
11413
+ import { z as z54 } from "zod";
10790
11414
 
10791
11415
  // ../../packages/product-economics/src/product-costing-shipping-references.json
10792
11416
  var product_costing_shipping_references_default = {
@@ -10850,21 +11474,21 @@ var product_costing_shipping_references_default = {
10850
11474
  };
10851
11475
 
10852
11476
  // ../../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([
11477
+ var ShippingReferenceSchema = z54.object({
11478
+ reference: z54.number().positive(),
11479
+ min: z54.number().nonnegative(),
11480
+ max: z54.number().positive(),
11481
+ updatedAt: z54.string().date(),
11482
+ confidence: z54.enum([
10859
11483
  "high",
10860
11484
  "medium",
10861
11485
  "medium-low"
10862
11486
  ]),
10863
- sources: z52.array(z52.string().url().startsWith("https://")).min(1).max(3)
11487
+ sources: z54.array(z54.string().url().startsWith("https://")).min(1).max(3)
10864
11488
  }).superRefine(({ reference, min, max }, context) => {
10865
11489
  if (min > reference || reference > max) {
10866
11490
  context.addIssue({
10867
- code: z52.ZodIssueCode.custom,
11491
+ code: z54.ZodIssueCode.custom,
10868
11492
  message: "shipping_reference_outside_range",
10869
11493
  path: [
10870
11494
  "reference"
@@ -10872,7 +11496,7 @@ var ShippingReferenceSchema = z52.object({
10872
11496
  });
10873
11497
  }
10874
11498
  });
10875
- var SHIPPING_REFERENCES = z52.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
11499
+ var SHIPPING_REFERENCES = z54.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
10876
11500
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
10877
11501
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
10878
11502
  "MX",
@@ -11054,18 +11678,18 @@ function getProductCostingCountryName(country) {
11054
11678
  __name(getProductCostingCountryName, "getProductCostingCountryName");
11055
11679
 
11056
11680
  // ../../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")
11681
+ var ProductCostingInputSchema = z55.object({
11682
+ country: z55.string().refine(isProductCostingCountryCode, "unsupported_country"),
11683
+ supplierCost: z55.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
11684
+ shippingCost: z55.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
11685
+ returnRate: z55.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11686
+ cancellationRate: z55.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11687
+ adsManagerCpa: z55.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
11688
+ desiredMargin: z55.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
11065
11689
  }).superRefine(({ returnRate, cancellationRate }, context) => {
11066
11690
  if (returnRate + cancellationRate >= 1) {
11067
11691
  context.addIssue({
11068
- code: z53.ZodIssueCode.custom,
11692
+ code: z55.ZodIssueCode.custom,
11069
11693
  message: "delivery_rate_positive",
11070
11694
  path: [
11071
11695
  "cancellationRate"
@@ -11132,14 +11756,14 @@ function calculateProductCosting(rawInput) {
11132
11756
  __name(calculateProductCosting, "calculateProductCosting");
11133
11757
 
11134
11758
  // 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, {
11759
+ import { z as z56 } from "zod";
11760
+ var optionalAmount = /* @__PURE__ */ __name((description) => z56.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
11761
+ var optionalPercent = /* @__PURE__ */ __name((description) => z56.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
11762
+ var ProductCostingToolInput = z56.object({
11763
+ country: z56.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
11140
11764
  message: "Pa\xEDs no soportado por ProductMaker."
11141
11765
  }).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."),
11766
+ supplierCost: z56.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
11143
11767
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
11144
11768
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
11145
11769
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -11271,7 +11895,7 @@ function formatPercent(value) {
11271
11895
  __name(formatPercent, "formatPercent");
11272
11896
 
11273
11897
  // src/tools/product-research.ts
11274
- import { z as z55 } from "zod";
11898
+ import { z as z57 } from "zod";
11275
11899
 
11276
11900
  // src/tools/product-research-idempotency.ts
11277
11901
  import { createHash as createHash2 } from "crypto";
@@ -11387,17 +12011,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
11387
12011
  ]);
11388
12012
  var POLL_INTERVAL_MS = 5e3;
11389
12013
  var MAX_WAIT_SECONDS = 120;
11390
- var FindInput = z55.object({
12014
+ var FindInput = z57.object({
11391
12015
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
11392
12016
  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.")
12017
+ confirmed: z57.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
12018
+ waitSeconds: z57.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
11395
12019
  });
11396
- var GetInput = z55.object({
11397
- runId: z55.string().uuid().describe("ID del run devuelto por find_winning_products.")
12020
+ var GetInput = z57.object({
12021
+ runId: z57.string().uuid().describe("ID del run devuelto por find_winning_products.")
11398
12022
  });
11399
- var ListInput = z55.object({
11400
- limit: z55.number().int().min(1).max(20).default(10)
12023
+ var ListInput = z57.object({
12024
+ limit: z57.number().int().min(1).max(20).default(10)
11401
12025
  });
11402
12026
  function textResult(text) {
11403
12027
  return {
@@ -11489,14 +12113,14 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
11489
12113
  __name(registerProductResearchTools, "registerProductResearchTools");
11490
12114
 
11491
12115
  // src/tools/dropshipping-projects.ts
11492
- import { z as z56 } from "zod";
11493
- var ProjectId = z56.string().uuid();
11494
- var DropshippingProjectStatus = z56.enum([
12116
+ import { z as z58 } from "zod";
12117
+ var ProjectId = z58.string().uuid();
12118
+ var DropshippingProjectStatus = z58.enum([
11495
12119
  "active",
11496
12120
  "completed",
11497
12121
  "archived"
11498
12122
  ]);
11499
- var DropshippingProjectPhase = z56.enum([
12123
+ var DropshippingProjectPhase = z58.enum([
11500
12124
  "research",
11501
12125
  "costing",
11502
12126
  "media",
@@ -11505,21 +12129,21 @@ var DropshippingProjectPhase = z56.enum([
11505
12129
  "advertising",
11506
12130
  "complete"
11507
12131
  ]);
11508
- var DropshippingRewardMilestone = z56.enum([
12132
+ var DropshippingRewardMilestone = z58.enum([
11509
12133
  "product_selected",
11510
12134
  "costing_saved",
11511
12135
  "product_media_ready",
11512
12136
  "creatives_completed",
11513
12137
  "campaign_created"
11514
12138
  ]);
11515
- var TargetCountry = z56.enum([
12139
+ var TargetCountry = z58.enum([
11516
12140
  "CO",
11517
12141
  "MX",
11518
12142
  "PE",
11519
12143
  "EC",
11520
12144
  "CL"
11521
12145
  ]);
11522
- var ResearchCategory = z56.enum([
12146
+ var ResearchCategory = z58.enum([
11523
12147
  "auto",
11524
12148
  "automotive",
11525
12149
  "kitchen",
@@ -11534,7 +12158,7 @@ var ResearchCategory = z56.enum([
11534
12158
  "garden",
11535
12159
  "cleaning"
11536
12160
  ]);
11537
- var HttpUrl3 = z56.string().url().max(2e3).refine((value) => {
12161
+ var HttpUrl3 = z58.string().url().max(2e3).refine((value) => {
11538
12162
  try {
11539
12163
  return [
11540
12164
  "http:",
@@ -11544,46 +12168,46 @@ var HttpUrl3 = z56.string().url().max(2e3).refine((value) => {
11544
12168
  return false;
11545
12169
  }
11546
12170
  }, "URL must use HTTP(S)");
11547
- var ResearchCandidateOrigin = z56.object({
11548
- type: z56.literal("research_candidate"),
12171
+ var ResearchCandidateOrigin = z58.object({
12172
+ type: z58.literal("research_candidate"),
11549
12173
  runId: ProjectId,
11550
- candidateSourceId: z56.string().min(1).max(160)
12174
+ candidateSourceId: z58.string().min(1).max(160)
11551
12175
  });
11552
- var TaskOrigin = z56.object({
11553
- type: z56.literal("task"),
12176
+ var TaskOrigin = z58.object({
12177
+ type: z58.literal("task"),
11554
12178
  taskId: ProjectId
11555
12179
  });
11556
- var ManualOrigin = z56.object({
11557
- type: z56.literal("manual"),
12180
+ var ManualOrigin = z58.object({
12181
+ type: z58.literal("manual"),
11558
12182
  clientRequestId: ProjectId,
11559
- country: z56.string().min(2).max(3).optional()
12183
+ country: z58.string().min(2).max(3).optional()
11560
12184
  });
11561
- var StartProjectInput = z56.object({
11562
- origin: z56.discriminatedUnion("type", [
12185
+ var StartProjectInput = z58.object({
12186
+ origin: z58.discriminatedUnion("type", [
11563
12187
  ResearchCandidateOrigin,
11564
12188
  TaskOrigin,
11565
12189
  ManualOrigin
11566
12190
  ]),
11567
- productName: z56.string().trim().min(1).max(240).optional(),
12191
+ productName: z58.string().trim().min(1).max(240).optional(),
11568
12192
  productUrl: HttpUrl3.optional(),
11569
- country: z56.string().min(2).max(3).optional(),
11570
- languageCode: z56.string().min(2).max(16).optional()
12193
+ country: z58.string().min(2).max(3).optional(),
12194
+ languageCode: z58.string().min(2).max(16).optional()
11571
12195
  }).strict();
11572
- var CostProjectInput = z56.object({
12196
+ var CostProjectInput = z58.object({
11573
12197
  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({
12198
+ version: z58.number().int().positive(),
12199
+ country: z58.string().trim().min(2).max(3),
12200
+ supplierCost: z58.number().finite().positive(),
12201
+ shippingCost: z58.number().finite().nonnegative(),
12202
+ returnRate: z58.number().finite().min(0).lt(1),
12203
+ cancellationRate: z58.number().finite().min(0).lt(1),
12204
+ adsManagerCpa: z58.number().finite().nonnegative(),
12205
+ desiredMargin: z58.number().finite().min(0).lt(1)
12206
+ });
12207
+ var GuideInput = z58.object({
11584
12208
  projectId: ProjectId.optional()
11585
12209
  }).strict();
11586
- var DropshippingProjectNextAction = z56.discriminatedUnion("kind", [
12210
+ var DropshippingProjectNextAction = z58.discriminatedUnion("kind", [
11587
12211
  "cost_product",
11588
12212
  "provide_product_media",
11589
12213
  "generate_creatives",
@@ -11597,78 +12221,78 @@ var DropshippingProjectNextAction = z56.discriminatedUnion("kind", [
11597
12221
  "create_meta_campaign",
11598
12222
  "repair_meta_campaign",
11599
12223
  "review_paused_campaign"
11600
- ].map((kind) => z56.object({
11601
- kind: z56.literal(kind),
11602
- href: z56.string().min(1)
12224
+ ].map((kind) => z58.object({
12225
+ kind: z58.literal(kind),
12226
+ href: z58.string().min(1)
11603
12227
  })));
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)
12228
+ var CostingInputSchema = z58.object({
12229
+ country: z58.string().min(2).max(3),
12230
+ supplierCost: z58.number().finite().positive(),
12231
+ shippingCost: z58.number().finite().nonnegative(),
12232
+ returnRate: z58.number().finite().min(0).lt(1),
12233
+ cancellationRate: z58.number().finite().min(0).lt(1),
12234
+ adsManagerCpa: z58.number().finite().nonnegative(),
12235
+ desiredMargin: z58.number().finite().min(0).lt(1)
11612
12236
  }).superRefine(({ returnRate, cancellationRate }, context) => {
11613
12237
  if (returnRate + cancellationRate >= 1) context.addIssue({
11614
- code: z56.ZodIssueCode.custom,
12238
+ code: z58.ZodIssueCode.custom,
11615
12239
  path: [
11616
12240
  "cancellationRate"
11617
12241
  ]
11618
12242
  });
11619
12243
  });
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({
12244
+ var CostingResultSchema = z58.object({
12245
+ country: z58.string().min(2).max(3),
12246
+ deliveryRate: z58.number().finite(),
12247
+ adjustedShippingCost: z58.number().finite(),
12248
+ adjustedCpa: z58.number().finite(),
12249
+ totalCost: z58.number().finite(),
12250
+ suggestedPrice: z58.number().finite(),
12251
+ baseCommercialPrice: z58.number().finite(),
12252
+ minimumSafePrice: z58.number().finite(),
12253
+ commercialPrice: z58.number().finite(),
12254
+ comparisonPrice: z58.number().finite(),
12255
+ estimatedProfit: z58.number().finite(),
12256
+ actualMargin: z58.number().finite(),
12257
+ breakEvenAdsManagerCpa: z58.number().finite(),
12258
+ wasPriceAutoAdjusted: z58.boolean(),
12259
+ profitabilityStatus: z58.literal("healthy")
12260
+ });
12261
+ var RewardProgressSchema = z58.object({
12262
+ campaignKey: z58.literal("dropshipping-launch-v1"),
12263
+ totalCredits: z58.number().int().positive(),
12264
+ earnedCredits: z58.number().int().nonnegative(),
12265
+ milestones: z58.array(z58.object({
11642
12266
  milestone: DropshippingRewardMilestone,
11643
- credits: z56.number().int().positive(),
11644
- available: z56.boolean().optional(),
11645
- earned: z56.boolean(),
12267
+ credits: z58.number().int().positive(),
12268
+ available: z58.boolean().optional(),
12269
+ earned: z58.boolean(),
11646
12270
  grantId: ProjectId.nullable(),
11647
- grantedAt: z56.string().datetime().nullable()
12271
+ grantedAt: z58.string().datetime().nullable()
11648
12272
  })).length(DropshippingRewardMilestone.options.length)
11649
12273
  });
11650
- var ProjectResponseSchema = z56.object({
12274
+ var ProjectResponseSchema = z58.object({
11651
12275
  id: ProjectId,
11652
12276
  taskId: ProjectId.nullable(),
11653
12277
  status: DropshippingProjectStatus,
11654
- originKey: z56.string().min(1),
12278
+ originKey: z58.string().min(1),
11655
12279
  researchRunId: ProjectId.nullable(),
11656
- candidateSourceId: z56.string().nullable(),
11657
- productName: z56.string().nullable(),
12280
+ candidateSourceId: z58.string().nullable(),
12281
+ productName: z58.string().nullable(),
11658
12282
  productUrl: HttpUrl3.nullable(),
11659
12283
  referenceImageUrl: HttpUrl3.nullable(),
11660
- targetCountry: z56.string().nullable(),
11661
- languageCode: z56.string().nullable(),
11662
- hasReferenceImage: z56.boolean(),
12284
+ targetCountry: z58.string().nullable(),
12285
+ languageCode: z58.string().nullable(),
12286
+ hasReferenceImage: z58.boolean(),
11663
12287
  costingInput: CostingInputSchema.nullable(),
11664
12288
  costingResult: CostingResultSchema.nullable(),
11665
- costingVersion: z56.number().int().positive().nullable(),
11666
- version: z56.number().int().positive(),
12289
+ costingVersion: z58.number().int().positive().nullable(),
12290
+ version: z58.number().int().positive(),
11667
12291
  phase: DropshippingProjectPhase,
11668
12292
  nextAction: DropshippingProjectNextAction.nullable(),
11669
12293
  rewards: RewardProgressSchema,
11670
- createdAt: z56.string().datetime(),
11671
- updatedAt: z56.string().datetime()
12294
+ createdAt: z58.string().datetime(),
12295
+ updatedAt: z58.string().datetime()
11672
12296
  }).passthrough();
11673
12297
  function projectText(raw) {
11674
12298
  const project = ProjectResponseSchema.parse(raw);
@@ -11753,6 +12377,7 @@ function createServer(opts) {
11753
12377
  }
11754
12378
  });
11755
12379
  const api = new ApiClient(apiBaseUrl, apiKey);
12380
+ registerMetaUploadAppResource(server);
11756
12381
  registerProductCostingTools(server);
11757
12382
  registerProductResearchTools(server, api);
11758
12383
  registerDropshippingProjectTools(server, api);
@@ -11770,6 +12395,7 @@ function createServer(opts) {
11770
12395
  registerPublishTools(server, api, {
11771
12396
  transport
11772
12397
  });
12398
+ registerMetaUploadTools(server, api);
11773
12399
  registerCampaignTools(server, api);
11774
12400
  return server;
11775
12401
  }