@productmaker/mcp 2.3.2 → 2.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/http.js CHANGED
@@ -153,10 +153,60 @@ var ApiClient = class {
153
153
  };
154
154
 
155
155
  // src/index.ts
156
- var MCP_VERSION = "2.3.2";
156
+ var MCP_VERSION = "2.8.3";
157
+
158
+ // src/meta-upload-app.ts
159
+ import { readFileSync } from "fs";
160
+ import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
161
+ var META_UPLOAD_APP_RESOURCE_URI = "ui://meta-upload/v1.html";
162
+ var META_UPLOAD_APP_NAME = "Meta upload app";
163
+ var META_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
164
+ var META_UPLOAD_APP_CSP = {
165
+ connectDomains: [
166
+ "https://productmaker.app",
167
+ "https://www.productmaker.app",
168
+ "https://*.r2.cloudflarestorage.com",
169
+ "https://*.oaiusercontent.com"
170
+ ]
171
+ };
172
+ function readMetaUploadAppHtml(htmlUrl = META_UPLOAD_APP_HTML_URL) {
173
+ try {
174
+ return readFileSync(htmlUrl, "utf8");
175
+ } catch (error) {
176
+ const message = error instanceof Error ? error.message : String(error);
177
+ throw new Error(`Missing packaged Meta upload app HTML at ${htmlUrl.pathname}. Rebuild @productmaker/mcp so ${META_UPLOAD_APP_RESOURCE_URI} is bundled. Original error: ${message}`);
178
+ }
179
+ }
180
+ __name(readMetaUploadAppHtml, "readMetaUploadAppHtml");
181
+ function registerMetaUploadAppResource(server) {
182
+ const html = readMetaUploadAppHtml();
183
+ registerAppResource(server, META_UPLOAD_APP_NAME, META_UPLOAD_APP_RESOURCE_URI, {
184
+ title: "Meta upload app",
185
+ mimeType: RESOURCE_MIME_TYPE,
186
+ _meta: {
187
+ ui: {
188
+ csp: META_UPLOAD_APP_CSP
189
+ }
190
+ }
191
+ }, async () => ({
192
+ contents: [
193
+ {
194
+ uri: META_UPLOAD_APP_RESOURCE_URI,
195
+ mimeType: RESOURCE_MIME_TYPE,
196
+ text: html,
197
+ _meta: {
198
+ ui: {
199
+ csp: META_UPLOAD_APP_CSP
200
+ }
201
+ }
202
+ }
203
+ ]
204
+ }));
205
+ }
206
+ __name(registerMetaUploadAppResource, "registerMetaUploadAppResource");
157
207
 
158
208
  // src/tools/tasks.ts
159
- import { z as z43 } from "zod";
209
+ import { z as z45 } from "zod";
160
210
 
161
211
  // src/resolve-image.ts
162
212
  import { lookup as dnsLookup } from "dns/promises";
@@ -1045,6 +1095,8 @@ var Angle = z6.object({
1045
1095
  buyerPersona: z6.string().min(1).max(360),
1046
1096
  /** Character description for video (role + appearance + attitude). Drives voice selection and Frame0Spec. */
1047
1097
  actor: z6.string().min(1).max(360).optional(),
1098
+ /** Saved custom actor (Actor.id) resolved for this angle. Its approved photo anchors the identity. */
1099
+ actorId: z6.string().optional(),
1048
1100
  // Binary only: voice catalog has no non-binary voices.
1049
1101
  // See packages/angles/src/index.ts + getAngles coercion in angle.repository.ts.
1050
1102
  gender: z6.enum([
@@ -1635,6 +1687,8 @@ var IngestOptions = z14.object({
1635
1687
  generateAdCopy: z14.boolean().default(true),
1636
1688
  generateAiMusic: z14.boolean().optional(),
1637
1689
  voiceId: VoiceId.optional(),
1690
+ /** Global saved custom actor (Actor.id). Default for all angles. */
1691
+ actorId: z14.string().optional(),
1638
1692
  isCombo: z14.boolean().default(false),
1639
1693
  productContext: z14.string().transform((value) => value.trim()).pipe(z14.string().max(PRODUCT_CONTEXT_MAX_CHARS)).optional(),
1640
1694
  creativeDirection: z14.string().transform((value) => value.trim()).pipe(z14.string().max(500)).optional(),
@@ -1649,7 +1703,7 @@ var IngestOptions = z14.object({
1649
1703
  "landing",
1650
1704
  "video",
1651
1705
  "image"
1652
- ])).min(2).max(3).optional(),
1706
+ ])).min(1).max(3).optional(),
1653
1707
  // Landing page output: 'classic' = image-based (default), 'code' = structured
1654
1708
  // code landing rendered by the theme app extension. Absent ⇒ classic.
1655
1709
  landingMode: LandingModeSchema.optional(),
@@ -1700,7 +1754,13 @@ var VideoOptionsOverride = z14.object({
1700
1754
  videoResolution: VideoResolution.optional(),
1701
1755
  voiceId: VoiceId.optional(),
1702
1756
  /** Actor/character description (role + appearance + attitude). Drives voice and video character. */
1703
- actor: z14.string().max(360).optional()
1757
+ actor: z14.string().max(360).optional(),
1758
+ /** Saved custom actor (Actor.id). Its approved photo anchors the identity.
1759
+ * `null` is an explicit REMOVAL (the user cleared the picker) and must be
1760
+ * sent as such: an omitted key means "not mentioned", which every writer
1761
+ * reads as "leave the stored selection alone". Same contract as
1762
+ * service-angles.controller.ts. */
1763
+ actorId: z14.string().nullable().optional()
1704
1764
  });
1705
1765
  var AngleVideoOptions = VideoOptionsOverride.extend({
1706
1766
  angleIndex: z14.number().int().nonnegative(),
@@ -1711,7 +1771,11 @@ var AngleVideoOptions = VideoOptionsOverride.extend({
1711
1771
  /** Per-angle buyer persona override (describes the target audience) */
1712
1772
  buyerPersona: z14.string().max(360).optional(),
1713
1773
  /** Per-angle actor/character description (role + appearance + attitude). Drives voice and video character. */
1714
- actor: z14.string().max(360).optional()
1774
+ actor: z14.string().max(360).optional(),
1775
+ /** Per-angle saved custom actor (Actor.id). Overrides the global actorId.
1776
+ * `null` = the user removed the personaje on this angle; it must NOT fall
1777
+ * back to the global default. Omitted = not mentioned. */
1778
+ actorId: z14.string().nullable().optional()
1715
1779
  });
1716
1780
  var RapidScriptBlueprintShotForApproval = z14.object({
1717
1781
  id: z14.string().min(1),
@@ -1774,6 +1838,8 @@ var IngestResumePayload = z14.object({
1774
1838
  voiceId: VoiceId.optional(),
1775
1839
  /** Global actor/character description (role + appearance + attitude). Default for all angles. */
1776
1840
  actor: z14.string().max(360).optional(),
1841
+ /** Global saved custom actor (Actor.id). Default for all angles. */
1842
+ actorId: z14.string().optional(),
1777
1843
  /** Custom angle override for landing/creative (uses this instead of first angle) */
1778
1844
  customAngleOverride: z14.string().min(1).max(480).optional(),
1779
1845
  /** Pain points to emphasize in landings, creatives, and videos */
@@ -2472,6 +2538,8 @@ var RefineAngleResponse = z20.object({
2472
2538
  painPoints: z20.array(z20.string()).optional(),
2473
2539
  benefits: z20.array(z20.string()).optional(),
2474
2540
  actor: z20.string().optional(),
2541
+ /** Saved custom actor (Actor.id) carried through the refine round-trip. */
2542
+ actorId: z20.string().optional(),
2475
2543
  buyerPersona: z20.string().optional()
2476
2544
  });
2477
2545
 
@@ -2851,7 +2919,9 @@ var PendingUserActionSchema = z23.discriminatedUnion("kind", [
2851
2919
  id: z23.string().uuid(),
2852
2920
  content: z23.string(),
2853
2921
  creativeDirection: z23.string(),
2854
- actor: z23.string()
2922
+ actor: z23.string(),
2923
+ /** Saved custom actor (Actor.id) backing this angle, when one was chosen. */
2924
+ actorId: z23.string().optional()
2855
2925
  })).min(1).max(3)
2856
2926
  }),
2857
2927
  z23.object({
@@ -3004,7 +3074,8 @@ var CostEstimationType = z24.enum([
3004
3074
  "landing-regenerate",
3005
3075
  "landing-creative",
3006
3076
  "campaign-copy",
3007
- "service-creative"
3077
+ "service-creative",
3078
+ "actor-grid"
3008
3079
  ]);
3009
3080
  var IngestEstimationOptions = z24.object({
3010
3081
  /** Number of product references that the extract stage will clean. */
@@ -3105,6 +3176,8 @@ var VideoCreativeEstimationOptions = z24.object({
3105
3176
  requiresAngles: z24.boolean().optional(),
3106
3177
  /** When false, omit the Meta/Whisper competitor-scan ceiling from the estimate. */
3107
3178
  competitorScanEnabled: z24.boolean().optional(),
3179
+ /** Quote the competitor scan even when product extraction is skipped (direct video auto path). */
3180
+ requiresCompetitorScan: z24.boolean().optional(),
3108
3181
  generateAiMusic: z24.boolean().optional(),
3109
3182
  /** Free mode burns one Whisper pass over the final clip when enabled. */
3110
3183
  addSubtitles: z24.boolean().optional()
@@ -3173,6 +3246,7 @@ var ServiceCreativeEstimationOptions = z24.object({
3173
3246
  mediaAssetCount: z24.number().int().min(0).max(8).optional(),
3174
3247
  uploadedVideoDurationSeconds: z24.number().min(0).max(240).optional()
3175
3248
  });
3249
+ var ActorGridEstimationOptions = z24.object({}).strict();
3176
3250
  var CostEstimationRequest = z24.discriminatedUnion("type", [
3177
3251
  z24.object({
3178
3252
  type: z24.literal("ingest"),
@@ -3209,6 +3283,10 @@ var CostEstimationRequest = z24.discriminatedUnion("type", [
3209
3283
  z24.object({
3210
3284
  type: z24.literal("service-creative"),
3211
3285
  options: ServiceCreativeEstimationOptions
3286
+ }),
3287
+ z24.object({
3288
+ type: z24.literal("actor-grid"),
3289
+ options: ActorGridEstimationOptions
3212
3290
  })
3213
3291
  ]);
3214
3292
  var CostEstimationResponse = z24.object({
@@ -3692,6 +3770,25 @@ var ALLOWED_MEDIA_CONTENT_TYPES = [
3692
3770
  "audio/wav",
3693
3771
  "audio/x-wav"
3694
3772
  ];
3773
+ var META_STAGING_IMAGE_CONTENT_TYPES = /* @__PURE__ */ new Set([
3774
+ "image/jpeg",
3775
+ "image/png",
3776
+ "image/webp"
3777
+ ]);
3778
+ var META_STAGING_MEDIA_CONTENT_TYPES = /* @__PURE__ */ new Set([
3779
+ ...META_STAGING_IMAGE_CONTENT_TYPES,
3780
+ "video/mp4",
3781
+ "video/quicktime",
3782
+ "video/webm"
3783
+ ]);
3784
+ var META_STAGING_CONTENT_TYPES = [
3785
+ "image/jpeg",
3786
+ "image/png",
3787
+ "image/webp",
3788
+ "video/mp4",
3789
+ "video/quicktime",
3790
+ "video/webm"
3791
+ ];
3695
3792
  var PresignUploadRequest = z31.object({
3696
3793
  /** MIME type of the file to upload */
3697
3794
  contentType: z31.enum(ALLOWED_MEDIA_CONTENT_TYPES),
@@ -3699,11 +3796,59 @@ var PresignUploadRequest = z31.object({
3699
3796
  fileName: z31.string().min(1).max(255),
3700
3797
  /** File size in bytes (validated against MAX_FILE_SIZE_BYTES) */
3701
3798
  fileSize: z31.number().int().positive().max(CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES),
3702
- /** Service uploads receive a user-bound asset ticket; ads keep the legacy path. */
3799
+ /**
3800
+ * Service uploads receive a user-bound asset ticket; ads keep the legacy path.
3801
+ * `actor` uploads the identity photo of a custom actor and is stored under
3802
+ * `actors/<userId>/…` so `AssetAccessGuard` can resolve its owner.
3803
+ */
3703
3804
  purpose: z31.enum([
3704
3805
  "ads",
3705
- "service"
3806
+ "service",
3807
+ "actor",
3808
+ "meta_staging"
3809
+ ]).optional(),
3810
+ /** Required for purpose=meta_staging so media and thumbnails can be checked independently. */
3811
+ assetRole: z31.enum([
3812
+ "media",
3813
+ "thumbnail"
3706
3814
  ]).optional()
3815
+ }).superRefine((value, ctx) => {
3816
+ if (value.purpose === "meta_staging") {
3817
+ if (!value.assetRole) {
3818
+ ctx.addIssue({
3819
+ code: z31.ZodIssueCode.custom,
3820
+ path: [
3821
+ "assetRole"
3822
+ ],
3823
+ message: "assetRole is required for Meta staging uploads"
3824
+ });
3825
+ } else if (!META_STAGING_MEDIA_CONTENT_TYPES.has(value.contentType)) {
3826
+ ctx.addIssue({
3827
+ code: z31.ZodIssueCode.custom,
3828
+ path: [
3829
+ "contentType"
3830
+ ],
3831
+ message: "Meta staging accepts only image or video media"
3832
+ });
3833
+ } else if (value.assetRole === "thumbnail" && !META_STAGING_IMAGE_CONTENT_TYPES.has(value.contentType)) {
3834
+ ctx.addIssue({
3835
+ code: z31.ZodIssueCode.custom,
3836
+ path: [
3837
+ "contentType"
3838
+ ],
3839
+ message: "Meta staging thumbnails must be images"
3840
+ });
3841
+ }
3842
+ }
3843
+ if (value.purpose !== "meta_staging" && value.assetRole) {
3844
+ ctx.addIssue({
3845
+ code: z31.ZodIssueCode.custom,
3846
+ path: [
3847
+ "assetRole"
3848
+ ],
3849
+ message: "assetRole is only valid for Meta staging uploads"
3850
+ });
3851
+ }
3707
3852
  });
3708
3853
  var PresignUploadResponse = z31.object({
3709
3854
  /** Presigned S3 PUT URL — browser sends file directly here */
@@ -3713,8 +3858,23 @@ var PresignUploadResponse = z31.object({
3713
3858
  /** S3 key */
3714
3859
  key: z31.string(),
3715
3860
  /** Present only for purpose=service. */
3716
- assetToken: z31.string().min(1).optional()
3861
+ assetToken: z31.string().min(1).optional(),
3862
+ /** Present only for purpose=meta_staging. */
3863
+ stagingAssetId: z31.string().uuid().optional()
3717
3864
  });
3865
+ var MetaUploadMediaKind = z31.enum([
3866
+ "image",
3867
+ "video",
3868
+ "mixed"
3869
+ ]);
3870
+ var META_UPLOAD_MAX_FILES = CAMPAIGN_LIMITS.MAX_CREATIVES;
3871
+ var MetaUploadAssetSummary = z31.object({
3872
+ stagingAssetId: z31.string().uuid(),
3873
+ mediaKind: z31.enum([
3874
+ "image",
3875
+ "video"
3876
+ ])
3877
+ }).strict();
3718
3878
  var MetaCampaignType = z31.enum([
3719
3879
  "sales",
3720
3880
  "traffic",
@@ -3740,6 +3900,81 @@ var HttpUrl = z31.string().url().refine((value) => {
3740
3900
  return false;
3741
3901
  }
3742
3902
  }, "URL must use http or https");
3903
+ var MetaUploadSessionRequest = z31.object({
3904
+ /** The browser page will require a file of this media kind. */
3905
+ mediaKind: MetaUploadMediaKind,
3906
+ /** The staged object role; media is the normal creative input. */
3907
+ assetRole: z31.enum([
3908
+ "media",
3909
+ "thumbnail"
3910
+ ]).default("media"),
3911
+ /** Maximum number of files; the API chooses 10 for mixed and 1 otherwise when omitted. */
3912
+ maxFiles: z31.number().int().min(1).max(META_UPLOAD_MAX_FILES).optional()
3913
+ }).strict().superRefine((value, ctx) => {
3914
+ if (value.mediaKind === "mixed" && value.assetRole === "thumbnail") {
3915
+ ctx.addIssue({
3916
+ code: z31.ZodIssueCode.custom,
3917
+ path: [
3918
+ "mediaKind"
3919
+ ],
3920
+ message: "Mixed Meta upload sessions cannot be thumbnails"
3921
+ });
3922
+ }
3923
+ if (value.mediaKind !== "mixed" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
3924
+ ctx.addIssue({
3925
+ code: z31.ZodIssueCode.custom,
3926
+ path: [
3927
+ "maxFiles"
3928
+ ],
3929
+ message: "Non-mixed Meta upload sessions accept exactly one file"
3930
+ });
3931
+ }
3932
+ if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
3933
+ ctx.addIssue({
3934
+ code: z31.ZodIssueCode.custom,
3935
+ path: [
3936
+ "mediaKind"
3937
+ ],
3938
+ message: "Meta thumbnails must be images"
3939
+ });
3940
+ }
3941
+ });
3942
+ var ResolveMetaUploadSessionRequest = z31.object({
3943
+ /** Opaque signed token embedded in the one-click upload page URL. */
3944
+ token: z31.string().min(1).max(4096),
3945
+ /** Stable browser-generated id so failed PUT retries reuse the same slot. */
3946
+ uploadId: z31.string().uuid().optional(),
3947
+ /** Browser-selected metadata; the API checks it against the session kind/role. */
3948
+ contentType: z31.enum(META_STAGING_CONTENT_TYPES),
3949
+ fileName: z31.string().min(1).max(255),
3950
+ fileSize: z31.number().int().positive().max(CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES)
3951
+ }).strict();
3952
+ var MetaUploadSessionResponse = z31.object({
3953
+ sessionId: z31.string().uuid(),
3954
+ uploadPageUrl: HttpUrl,
3955
+ mediaKind: MetaUploadMediaKind,
3956
+ assetRole: z31.enum([
3957
+ "media",
3958
+ "thumbnail"
3959
+ ]),
3960
+ maxFiles: z31.number().int().min(1).max(META_UPLOAD_MAX_FILES),
3961
+ expiresAt: z31.string().datetime()
3962
+ }).strict();
3963
+ var ResolveMetaUploadSessionResponse = z31.object({
3964
+ uploadUrl: z31.string().url(),
3965
+ stagingAssetId: z31.string().uuid(),
3966
+ expiresAt: z31.string().datetime()
3967
+ }).strict();
3968
+ var MetaUploadSessionStatusResponse = z31.object({
3969
+ sessionId: z31.string().uuid(),
3970
+ status: z31.enum([
3971
+ "pending",
3972
+ "uploaded"
3973
+ ]),
3974
+ assets: z31.array(MetaUploadAssetSummary).max(META_UPLOAD_MAX_FILES),
3975
+ /** Legacy single-file response field retained for older callers. */
3976
+ stagingAssetId: z31.string().uuid().optional()
3977
+ }).strict();
3743
3978
  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), {
3744
3979
  message: 'adAccountId must look like "act_1234567890" (a bare numeric id is also accepted)'
3745
3980
  });
@@ -3826,18 +4061,26 @@ var UploadToMetaRequest = z31.object({
3826
4061
  title: z31.string().max(CAMPAIGN_LIMITS.MAX_TITLE_LENGTH),
3827
4062
  description: z31.string().max(CAMPAIGN_LIMITS.MAX_DESCRIPTION_LENGTH),
3828
4063
  cta: MetaCtaType,
3829
- /** Public URL of the uploaded media */
3830
- mediaUrl: HttpUrl,
4064
+ /** Legacy public URL; new Meta flows should use mediaAssetId. */
4065
+ mediaUrl: HttpUrl.optional(),
4066
+ /** Owner-scoped temporary asset created by the Meta staging upload flow. */
4067
+ mediaAssetId: z31.string().uuid().optional(),
3831
4068
  /**
3832
4069
  * Public URL of an extracted thumbnail. Required for video ads — Meta
3833
4070
  * rejects video ad creatives without `image_hash`/`image_url` in
3834
4071
  * `video_data` (subcode 1443226). Frontend extracts a frame from the
3835
4072
  * video and uploads it before submitting the request.
3836
4073
  */
3837
- thumbnailUrl: HttpUrl.optional()
4074
+ thumbnailUrl: HttpUrl.optional(),
4075
+ /** Owner-scoped temporary thumbnail created by the Meta staging upload flow. */
4076
+ thumbnailAssetId: z31.string().uuid().optional()
3838
4077
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3839
4078
  /** Meta Ad Account ID */
3840
4079
  adAccountId: MetaAdAccountId,
4080
+ /** Meta Business Portfolio selected by the user (optional for legacy callers). */
4081
+ portfolioId: z31.string().min(1).optional(),
4082
+ /** Facebook Page selected by the user (optional for legacy callers). */
4083
+ pageId: z31.string().min(1).optional(),
3841
4084
  /** Optional Meta Pixel ID */
3842
4085
  pixelId: z31.string().optional(),
3843
4086
  /** Landing page URL (required for landing destinations; omitted for whatsapp) */
@@ -3857,6 +4100,30 @@ var UploadToMetaRequest = z31.object({
3857
4100
  message: "landingUrl is required for landing destinations"
3858
4101
  });
3859
4102
  }
4103
+ value.creatives.forEach((creative, index) => {
4104
+ if (Boolean(creative.mediaUrl) === Boolean(creative.mediaAssetId)) {
4105
+ ctx.addIssue({
4106
+ code: z31.ZodIssueCode.custom,
4107
+ path: [
4108
+ "creatives",
4109
+ index,
4110
+ "mediaUrl"
4111
+ ],
4112
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4113
+ });
4114
+ }
4115
+ if (creative.thumbnailUrl && creative.thumbnailAssetId) {
4116
+ ctx.addIssue({
4117
+ code: z31.ZodIssueCode.custom,
4118
+ path: [
4119
+ "creatives",
4120
+ index,
4121
+ "thumbnailUrl"
4122
+ ],
4123
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4124
+ });
4125
+ }
4126
+ });
3860
4127
  });
3861
4128
  var CampaignLifetimeMetricsSchema = z31.object({
3862
4129
  spend: z31.number().nonnegative(),
@@ -3910,8 +4177,32 @@ var FacebookAdAccountSchema = z32.object({
3910
4177
  source: z32.enum([
3911
4178
  "business",
3912
4179
  "personal"
4180
+ ]),
4181
+ /** Meta Business Portfolio (or the synthetic `personal` group). */
4182
+ portfolioId: z32.string().min(1),
4183
+ portfolioName: z32.string().min(1),
4184
+ portfolioType: z32.enum([
4185
+ "business",
4186
+ "personal"
3913
4187
  ])
3914
4188
  });
4189
+ var FacebookMetaPortfolioSchema = z32.object({
4190
+ id: z32.string().min(1),
4191
+ name: z32.string().min(1),
4192
+ type: z32.enum([
4193
+ "business",
4194
+ "personal"
4195
+ ])
4196
+ });
4197
+ var FacebookMetaPageSchema = z32.object({
4198
+ id: z32.string().min(1),
4199
+ name: z32.string().min(1)
4200
+ });
4201
+ var FacebookMetaDestinationsResponseSchema = z32.object({
4202
+ portfolios: z32.array(FacebookMetaPortfolioSchema),
4203
+ adAccounts: z32.array(FacebookAdAccountSchema),
4204
+ pages: z32.array(FacebookMetaPageSchema)
4205
+ });
3915
4206
  var MetaCampaignOverridesSchema = z32.object({
3916
4207
  dailyBudgetUsd: z32.number().positive().optional(),
3917
4208
  targetCountries: z32.array(z32.string().length(2)).max(1).optional(),
@@ -3920,6 +4211,8 @@ var MetaCampaignOverridesSchema = z32.object({
3920
4211
  });
3921
4212
  var FacebookCreateCampaignBodySchema = z32.object({
3922
4213
  adAccountId: MetaAdAccountId,
4214
+ portfolioId: z32.string().min(1).optional(),
4215
+ pageId: z32.string().min(1).optional(),
3923
4216
  pixelId: z32.string().optional(),
3924
4217
  landingUrl: HttpUrl2.optional(),
3925
4218
  campaignConfig: MetaCampaignConfig.optional(),
@@ -3950,6 +4243,8 @@ var FacebookCampaignStatusResponseSchema = z32.object({
3950
4243
  var FacebookCreateCampaignResponseSchema = z32.object({
3951
4244
  campaignId: z32.string(),
3952
4245
  campaignName: z32.string(),
4246
+ portfolioId: z32.string().optional(),
4247
+ portfolioName: z32.string().optional(),
3953
4248
  pageId: z32.string().optional(),
3954
4249
  pageName: z32.string().optional(),
3955
4250
  // Backward-compatible single ad fields (optional)
@@ -3992,11 +4287,15 @@ var FacebookCreateCampaignFromCreativesBodySchema = z32.object({
3992
4287
  title: z32.string().max(120),
3993
4288
  description: z32.string().max(500),
3994
4289
  cta: MetaCtaType,
3995
- mediaUrl: HttpUrl2,
4290
+ mediaUrl: HttpUrl2.optional(),
4291
+ mediaAssetId: z32.string().uuid().optional(),
3996
4292
  thumbnailUrl: HttpUrl2.optional(),
4293
+ thumbnailAssetId: z32.string().uuid().optional(),
3997
4294
  creativeId: z32.string().min(1).optional()
3998
4295
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3999
4296
  adAccountId: MetaAdAccountId,
4297
+ portfolioId: z32.string().min(1).optional(),
4298
+ pageId: z32.string().min(1).optional(),
4000
4299
  pixelId: z32.string().optional(),
4001
4300
  /** Required unless campaignConfig.destination is 'whatsapp' (see superRefine below). */
4002
4301
  landingUrl: HttpUrl2.optional(),
@@ -4016,6 +4315,30 @@ var FacebookCreateCampaignFromCreativesBodySchema = z32.object({
4016
4315
  message: "landingUrl is required unless destination is whatsapp"
4017
4316
  });
4018
4317
  }
4318
+ value.creatives.forEach((creative, index) => {
4319
+ if (Boolean(creative.mediaUrl) === Boolean(creative.mediaAssetId)) {
4320
+ ctx.addIssue({
4321
+ code: z32.ZodIssueCode.custom,
4322
+ path: [
4323
+ "creatives",
4324
+ index,
4325
+ "mediaUrl"
4326
+ ],
4327
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4328
+ });
4329
+ }
4330
+ if (creative.thumbnailUrl && creative.thumbnailAssetId) {
4331
+ ctx.addIssue({
4332
+ code: z32.ZodIssueCode.custom,
4333
+ path: [
4334
+ "creatives",
4335
+ index,
4336
+ "thumbnailUrl"
4337
+ ],
4338
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4339
+ });
4340
+ }
4341
+ });
4019
4342
  if (value.campaignConfig?.destination?.type === "landing" && value.landingUrl && value.campaignConfig.destination.landingUrl !== value.landingUrl) {
4020
4343
  ctx.addIssue({
4021
4344
  code: z32.ZodIssueCode.custom,
@@ -4093,8 +4416,10 @@ var FacebookTaskCreativeSourceSchema = z32.object({
4093
4416
  });
4094
4417
  var FacebookMediaCreativeSourceSchema = z32.object({
4095
4418
  type: z32.literal("media"),
4096
- mediaUrl: HttpUrl2,
4097
- thumbnailUrl: HttpUrl2.optional()
4419
+ mediaUrl: HttpUrl2.optional(),
4420
+ mediaAssetId: z32.string().uuid().optional(),
4421
+ thumbnailUrl: HttpUrl2.optional(),
4422
+ thumbnailAssetId: z32.string().uuid().optional()
4098
4423
  });
4099
4424
  var FacebookCreativeSourceSchema = z32.discriminatedUnion("type", [
4100
4425
  FacebookTaskCreativeSourceSchema,
@@ -4113,6 +4438,9 @@ var FacebookCreateCampaignFromTasksBodySchema = z32.object({
4113
4438
  creativeId: z32.string().min(1).optional()
4114
4439
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
4115
4440
  adAccountId: MetaAdAccountId,
4441
+ /** Explicit destination is required for MCP/remote creative publishing. */
4442
+ portfolioId: z32.string().min(1),
4443
+ pageId: z32.string().min(1),
4116
4444
  pixelId: z32.string().optional(),
4117
4445
  landingUrl: HttpUrl2.optional(),
4118
4446
  targetCountry: z32.string().length(2).default("US"),
@@ -4129,6 +4457,33 @@ var FacebookCreateCampaignFromTasksBodySchema = z32.object({
4129
4457
  message: "landingUrl is required unless destination is whatsapp"
4130
4458
  });
4131
4459
  }
4460
+ value.creatives.forEach((creative, index) => {
4461
+ if (creative.source.type !== "media") return;
4462
+ if (Boolean(creative.source.mediaUrl) === Boolean(creative.source.mediaAssetId)) {
4463
+ ctx.addIssue({
4464
+ code: z32.ZodIssueCode.custom,
4465
+ path: [
4466
+ "creatives",
4467
+ index,
4468
+ "source",
4469
+ "mediaUrl"
4470
+ ],
4471
+ message: "Provide exactly one of mediaUrl or mediaAssetId"
4472
+ });
4473
+ }
4474
+ if (creative.source.thumbnailUrl && creative.source.thumbnailAssetId) {
4475
+ ctx.addIssue({
4476
+ code: z32.ZodIssueCode.custom,
4477
+ path: [
4478
+ "creatives",
4479
+ index,
4480
+ "source",
4481
+ "thumbnailUrl"
4482
+ ],
4483
+ message: "Provide at most one of thumbnailUrl or thumbnailAssetId"
4484
+ });
4485
+ }
4486
+ });
4132
4487
  });
4133
4488
 
4134
4489
  // ../../packages/types/src/tiktok.ts
@@ -4695,6 +5050,13 @@ var DEFAULT_PRICING = {
4695
5050
  ourCostCents: 3,
4696
5051
  userPriceCents: 6
4697
5052
  },
5053
+ // Custom actor identity photos (gpt-image-2 @ 1K, 4 candidates per attempt).
5054
+ // Recorded once per PRODUCED candidate. Without this row billingService.record
5055
+ // logs "No pricing rule found; skipping billing record" and the whole grid is free.
5056
+ "kie:image-generation:actor": {
5057
+ ourCostCents: 3,
5058
+ userPriceCents: 6
5059
+ },
4698
5060
  "kie:image-generation:seedream-5-lite-text-to-image": {
4699
5061
  ourCostCents: 3,
4700
5062
  userPriceCents: 6
@@ -5631,6 +5993,32 @@ var DEFAULT_PRICING = {
5631
5993
  ourCostCents: 1.5,
5632
5994
  userPriceCents: 3
5633
5995
  },
5996
+ // Frame0 per-person anatomy gate. Priced PER PANEL, like the inventory above and for the same
5997
+ // reason: it is one vision call per rendered panel, so quoting it per VIDEO would under-quote
5998
+ // by the panel count (~10x). Same single-image, short-answer shape as the inventory, so it
5999
+ // carries the same rate until measured otherwise.
6000
+ "openrouter:rapid-panel-anatomy-gate": {
6001
+ ourCostCents: 1.5,
6002
+ userPriceCents: 3
6003
+ },
6004
+ // Plan hand-budget audit: one TEXT call per shot (no image), so materially cheaper than the
6005
+ // vision passes above. Billed at the call site, therefore it MUST be quoted — an unquoted paid
6006
+ // call is the failure this repo already has on record.
6007
+ "openrouter:plan-hand-budget-audit": {
6008
+ ourCostCents: 0.3,
6009
+ userPriceCents: 0.6
6010
+ },
6011
+ // Hand-budget REPAIR: the rewrite that fires only on a shot whose brief demands more hand jobs
6012
+ // than its cast has hands. One TEXT call, same shape as the audit above but with the brief on
6013
+ // both sides (prompt in, prose out), so twice the rate rather than the audit's.
6014
+ //
6015
+ // Kept on its OWN key rather than folded into the audit for the reason the frame-inventory delta
6016
+ // is: the audit runs on every shot and the repair on a minority, so one blended rate would price
6017
+ // both wrong and neither line would be auditable against the ledger.
6018
+ "openrouter:rapid-hand-budget-repair": {
6019
+ ourCostCents: 0.6,
6020
+ userPriceCents: 1.2
6021
+ },
5634
6022
  // Unit = one shot-action flatten, once per video, quoted ONLY when frame inventories are on.
5635
6023
  // Carrying ~10 inventories as text raised that call from a 1.7078¢ production median to a
5636
6024
  // measured 3.1481¢ / 3.4551¢. This is the difference, kept on its own key so the base rate above
@@ -7061,6 +7449,7 @@ var MAX_SEGMENT_SECONDS = 3;
7061
7449
  var VideoEditStatusSchema = z38.enum([
7062
7450
  "pending",
7063
7451
  "processing",
7452
+ "assembling",
7064
7453
  "ready",
7065
7454
  "accepted",
7066
7455
  "discarded",
@@ -7088,6 +7477,13 @@ var VideoEditModeSchema = z38.enum([
7088
7477
  "wan_edit",
7089
7478
  "regenerate"
7090
7479
  ]);
7480
+ var VideoRegenerationEngineSchema = z38.enum([
7481
+ "free_text",
7482
+ "free_image",
7483
+ "single_call",
7484
+ "multi_shot"
7485
+ ]);
7486
+ var VideoRegenerationRouteFingerprintSchema = z38.string().trim().min(1).max(128);
7091
7487
  var RegenerateEditRequestSchema = z38.object({
7092
7488
  angleKey: z38.string().min(1),
7093
7489
  comment: z38.string().min(1).max(2e3)
@@ -7466,6 +7862,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7466
7862
  confirmation: "credit_consuming",
7467
7863
  schemaHash: "bab794a9700a8d3f42b97f444a776e7ed4dcd833038c52588f951b50970ff3fa"
7468
7864
  },
7865
+ {
7866
+ name: "check_meta_media_upload",
7867
+ confirmation: "none",
7868
+ schemaHash: "62b1af1d796d9466f8926afc5e6d18c4ddeccd5b0269ca7b21c2f6ebcfd2a945"
7869
+ },
7469
7870
  {
7470
7871
  name: "cost_dropshipping_project",
7471
7872
  confirmation: "state_change",
@@ -7484,7 +7885,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7484
7885
  {
7485
7886
  name: "create_product_task",
7486
7887
  confirmation: "credit_consuming",
7487
- schemaHash: "3f03c4886f65a030e03a531c02e0ef879aaf3f5820e90ab3ca04a02350e693e6"
7888
+ schemaHash: "5c5fe82a365c62e44265e79a4100a06820dbbdb2e0ddc296cd066eb2255b1d62"
7488
7889
  },
7489
7890
  {
7490
7891
  name: "create_service_video",
@@ -7499,7 +7900,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7499
7900
  {
7500
7901
  name: "edit_task_draft",
7501
7902
  confirmation: "state_change",
7502
- schemaHash: "f1763564083dcaac2d1f4d5e6c4625a2d4ec0ea3eea273aadc0fb9d4ab6c7d47"
7903
+ schemaHash: "1c9343bdce51aac36ec6254557c8a904037746523168527f012b4c5b6361e98e"
7503
7904
  },
7504
7905
  {
7505
7906
  name: "estimate_creative_variant",
@@ -7529,7 +7930,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7529
7930
  {
7530
7931
  name: "generate_video_creative",
7531
7932
  confirmation: "credit_consuming",
7532
- schemaHash: "c80208df75f2c7cbc459e9ae494a75a0a5f5bc4cf044b3fc9ef9c0e9af2c2452"
7933
+ schemaHash: "04e12ef523e4064b0c204d110baad35d414b605068087c625badf40c46429c8a"
7533
7934
  },
7534
7935
  {
7535
7936
  name: "get_meta_campaign_status",
@@ -7561,11 +7962,21 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7561
7962
  confirmation: "none",
7562
7963
  schemaHash: "0e7a1cb5ad5c6681473df5689336edcd7a63b8d68853359982e44bfd98a65db8"
7563
7964
  },
7965
+ {
7966
+ name: "list_actors",
7967
+ confirmation: "none",
7968
+ schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
7969
+ },
7564
7970
  {
7565
7971
  name: "list_meta_ad_accounts",
7566
7972
  confirmation: "none",
7567
7973
  schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
7568
7974
  },
7975
+ {
7976
+ name: "list_meta_destinations",
7977
+ confirmation: "none",
7978
+ schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
7979
+ },
7569
7980
  {
7570
7981
  name: "list_product_research_runs",
7571
7982
  confirmation: "none",
@@ -7594,12 +8005,12 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7594
8005
  {
7595
8006
  name: "publish_creatives_to_meta",
7596
8007
  confirmation: "external_mutation",
7597
- schemaHash: "ddb45071d61746b443124346e7a82095315a2a80bd393c73a45f12b895939cf5"
8008
+ schemaHash: "59b3d6d5ce25812fee3382b0b1c0cf5e07beb1063048dd0a0d0633686cf29da5"
7598
8009
  },
7599
8010
  {
7600
8011
  name: "publish_to_meta",
7601
8012
  confirmation: "external_mutation",
7602
- schemaHash: "4b6daa1a25e739ccf208a6acede067ec1c22b1ea5509f332361a17dd46f6bb0b"
8013
+ schemaHash: "b507735a1fe13657355cf93163b6ead0db151984e6f2702dcdbf76b4037133ca"
7603
8014
  },
7604
8015
  {
7605
8016
  name: "publish_to_shopify",
@@ -7616,6 +8027,11 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7616
8027
  confirmation: "credit_consuming",
7617
8028
  schemaHash: "745687cb41f904e293105f93bbe6bc0471f63fe097ab8f85d3df8d9833084844"
7618
8029
  },
8030
+ {
8031
+ name: "request_meta_media_upload",
8032
+ confirmation: "none",
8033
+ schemaHash: "32203d205f414c386ee6ab33e265eb2708c108fca4169d4c12e99d7a4109a132"
8034
+ },
7619
8035
  {
7620
8036
  name: "resolve_pending_action",
7621
8037
  confirmation: "credit_consuming",
@@ -7629,6 +8045,150 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
7629
8045
  ];
7630
8046
  var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITIES.map((capability) => capability.name);
7631
8047
 
8048
+ // ../../packages/types/src/actor.ts
8049
+ import { z as z42 } from "zod";
8050
+ var ActorStatusSchema = z42.enum([
8051
+ "draft",
8052
+ "generating",
8053
+ "ready",
8054
+ "failed"
8055
+ ]);
8056
+ var ACTOR_NAME_MAX_CHARS = 60;
8057
+ var ACTOR_DESCRIPTION_MAX_CHARS = 600;
8058
+ var ACTOR_CANDIDATE_COUNT = 4;
8059
+ var ActorGenderSchema = z42.enum([
8060
+ "female",
8061
+ "male",
8062
+ "non_binary"
8063
+ ]);
8064
+ var ActorSchema = z42.object({
8065
+ id: z42.string(),
8066
+ name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS),
8067
+ description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
8068
+ gender: ActorGenderSchema,
8069
+ voiceId: z42.string().nullable().optional(),
8070
+ brandId: z42.string().nullable().optional(),
8071
+ status: ActorStatusSchema,
8072
+ /**
8073
+ * A candidate-grid run is in flight RIGHT NOW.
8074
+ *
8075
+ * Separate from `status` because the two answer different questions:
8076
+ * `status` is the PUBLISHED state (a personaje being regenerated stays
8077
+ * `ready`, so it keeps serving the renders that already reference it), and
8078
+ * this flag is the live one the wizard polls. It is derived from a lock
8079
+ * timestamp with a TTL, so an orphaned run reads as `false` instead of
8080
+ * pinning the personaje forever.
8081
+ */
8082
+ generating: z42.boolean().optional(),
8083
+ photoUrl: z42.string().url().nullable().optional(),
8084
+ avatarUrl: z42.string().url().nullable().optional(),
8085
+ candidateUrls: z42.array(z42.string().url()).optional(),
8086
+ /** Indices of candidateUrls that failed the anatomy check (Task 6). */
8087
+ flaggedCandidateIndices: z42.array(z42.number().int()).optional()
8088
+ });
8089
+ var CreateActorRequest = z42.object({
8090
+ name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS),
8091
+ description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
8092
+ gender: ActorGenderSchema,
8093
+ voiceId: z42.string().optional(),
8094
+ brandId: z42.string().optional(),
8095
+ sourcePhotoKey: z42.string().optional()
8096
+ }).refine((v) => v.description.trim().length > 0 || !!v.sourcePhotoKey, {
8097
+ message: "Se necesita una descripci\xF3n o una foto"
8098
+ });
8099
+ var UpdateActorRequest = z42.object({
8100
+ name: z42.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
8101
+ description: z42.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
8102
+ gender: ActorGenderSchema.optional(),
8103
+ voiceId: z42.string().nullable().optional(),
8104
+ brandId: z42.string().nullable().optional()
8105
+ });
8106
+ var ApproveActorRequest = z42.object({
8107
+ candidateIndex: z42.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
8108
+ });
8109
+
8110
+ // ../../packages/types/src/referral-dashboard.ts
8111
+ import { z as z43 } from "zod";
8112
+ var REFERRAL_DASHBOARD_RAW_STATUSES = [
8113
+ "ACTIVE",
8114
+ "PENDING",
8115
+ "GRACE",
8116
+ "CANCELLED",
8117
+ "EXPIRED"
8118
+ ];
8119
+ var REFERRAL_DASHBOARD_STATES = [
8120
+ "active",
8121
+ "pending",
8122
+ "inactive",
8123
+ "no_plan"
8124
+ ];
8125
+ var REFERRAL_DASHBOARD_PLAN_KINDS = [
8126
+ "current",
8127
+ "last",
8128
+ "none"
8129
+ ];
8130
+ var ReferralDashboardRawStatusSchema = z43.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
8131
+ var ReferralDashboardStateSchema = z43.enum(REFERRAL_DASHBOARD_STATES);
8132
+ var ReferralDashboardPlanKindSchema = z43.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
8133
+ var ReferralDashboardStatusSchema = z43.enum([
8134
+ "all",
8135
+ "active",
8136
+ "pending",
8137
+ "inactive"
8138
+ ]).default("all");
8139
+ var ReferralDashboardPlanSchema = z43.string().trim().min(1).max(100).optional();
8140
+ var ReferralDashboardFiltersSchema = z43.object({
8141
+ status: ReferralDashboardStatusSchema,
8142
+ plan: ReferralDashboardPlanSchema
8143
+ });
8144
+ var ReferralDashboardSubscriptionSchema = z43.object({
8145
+ planName: z43.string().nullable(),
8146
+ planKind: ReferralDashboardPlanKindSchema,
8147
+ rawStatus: ReferralDashboardRawStatusSchema.nullable(),
8148
+ state: ReferralDashboardStateSchema,
8149
+ currentPeriodEnd: z43.string().datetime({
8150
+ offset: true
8151
+ }).nullable(),
8152
+ cancelAtPeriodEnd: z43.boolean()
8153
+ });
8154
+ var ReferralDashboardReferralSchema = z43.object({
8155
+ id: z43.string().min(1),
8156
+ registeredAt: z43.string().datetime({
8157
+ offset: true
8158
+ }),
8159
+ // Kept in the response for compatibility with existing referral API clients.
8160
+ // The referrer dashboard intentionally does not render these legacy fields.
8161
+ expiresAt: z43.string().datetime({
8162
+ offset: true
8163
+ }).nullable().optional(),
8164
+ status: z43.string().min(1).optional(),
8165
+ referredUser: z43.object({
8166
+ email: z43.string().nullable()
8167
+ }),
8168
+ subscription: ReferralDashboardSubscriptionSchema
8169
+ });
8170
+ var ReferralDashboardReferralsPageSchema = z43.object({
8171
+ referrals: z43.array(ReferralDashboardReferralSchema),
8172
+ total: z43.number().int().nonnegative()
8173
+ });
8174
+ var ReferralDashboardPlanBreakdownSchema = z43.object({
8175
+ planName: z43.string().nullable(),
8176
+ count: z43.number().int().nonnegative()
8177
+ });
8178
+ var ReferralDashboardMonthlyRegistrationSchema = z43.object({
8179
+ month: z43.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
8180
+ count: z43.number().int().nonnegative()
8181
+ });
8182
+ var ReferralDashboardOverviewSchema = z43.object({
8183
+ totalReferredUsers: z43.number().int().nonnegative(),
8184
+ activeUsers: z43.number().int().nonnegative(),
8185
+ pendingUsers: z43.number().int().nonnegative(),
8186
+ inactiveUsers: z43.number().int().nonnegative(),
8187
+ noPlanUsers: z43.number().int().nonnegative(),
8188
+ planBreakdown: z43.array(ReferralDashboardPlanBreakdownSchema),
8189
+ monthlyRegistrations: z43.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
8190
+ });
8191
+
7632
8192
  // src/resolve-media.ts
7633
8193
  var MAX_MEDIA_BYTES = CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES;
7634
8194
  var MEDIA_FORMATS = [
@@ -7868,10 +8428,10 @@ async function resolveMediaRef(ref, opts) {
7868
8428
  __name(resolveMediaRef, "resolveMediaRef");
7869
8429
 
7870
8430
  // src/tools/shared.ts
7871
- import { z as z42 } from "zod";
7872
- var Lang = z42.string().min(2).max(10);
7873
- var Country = z42.string().min(2).max(3);
7874
- var LandingStrategyEnum = z42.enum([
8431
+ import { z as z44 } from "zod";
8432
+ var Lang = z44.string().min(2).max(10);
8433
+ var Country = z44.string().min(2).max(3);
8434
+ var LandingStrategyEnum = z44.enum([
7875
8435
  "auto",
7876
8436
  "classic",
7877
8437
  "pain_solution",
@@ -7880,12 +8440,12 @@ var LandingStrategyEnum = z42.enum([
7880
8440
  "offer_value",
7881
8441
  "lifestyle_identity"
7882
8442
  ]);
7883
- var LandingImageFormatEnum = z42.enum([
8443
+ var LandingImageFormatEnum = z44.enum([
7884
8444
  "9:16",
7885
8445
  "4:5",
7886
8446
  "1:1"
7887
8447
  ]);
7888
- var VideoPresetEnum = z42.enum([
8448
+ var VideoPresetEnum = z44.enum([
7889
8449
  "auto",
7890
8450
  "ugc_dynamic",
7891
8451
  "ugc_how_to",
@@ -7898,7 +8458,7 @@ var VideoPresetEnum = z42.enum([
7898
8458
  "pixar_problem",
7899
8459
  "pixar_sassy"
7900
8460
  ]);
7901
- var TaskCreativeVariantVideoPresetEnum = z42.enum([
8461
+ var TaskCreativeVariantVideoPresetEnum = z44.enum([
7902
8462
  "auto",
7903
8463
  "ugc_dynamic",
7904
8464
  "ugc_how_to",
@@ -7969,7 +8529,7 @@ function resolveVideoPreset(preset) {
7969
8529
  return VIDEO_PRESET_RESOLUTION[preset] ?? null;
7970
8530
  }
7971
8531
  __name(resolveVideoPreset, "resolveVideoPreset");
7972
- var VideoTierEnum = z42.enum([
8532
+ var VideoTierEnum = z44.enum([
7973
8533
  "basic"
7974
8534
  ]);
7975
8535
  function resolveVideoTier(_tier) {
@@ -7980,27 +8540,21 @@ function resolveWireVideoTier(tier, durationSeconds) {
7980
8540
  return durationSeconds === 30 ? "standard" : resolveVideoTier(tier);
7981
8541
  }
7982
8542
  __name(resolveWireVideoTier, "resolveWireVideoTier");
7983
- function assertVideoPresetDurationCompatibility(videoPreset, durationSeconds) {
7984
- if (videoPreset === "problem_solution" && durationSeconds !== 30) {
7985
- throw new Error("Problema y Soluci\xF3n solo est\xE1 disponible en videos Multiescena de 30 segundos.");
7986
- }
7987
- }
7988
- __name(assertVideoPresetDurationCompatibility, "assertVideoPresetDurationCompatibility");
7989
8543
  function resolveVideoFormatForDuration(durationSeconds) {
7990
8544
  return durationSeconds === 30 ? "double_30s" : "single_15s";
7991
8545
  }
7992
8546
  __name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
7993
- var VideoResolutionEnum = z42.enum([
8547
+ var VideoResolutionEnum = z44.enum([
7994
8548
  "480p",
7995
8549
  "720p"
7996
8550
  ]);
7997
- var VideoStyleEnum = z42.enum([
8551
+ var VideoStyleEnum = z44.enum([
7998
8552
  "ugc",
7999
8553
  "cartoon_3d",
8000
8554
  "product_only",
8001
8555
  "ai_dynamic"
8002
8556
  ]);
8003
- var NarrativeStyleEnum = z42.enum([
8557
+ var NarrativeStyleEnum = z44.enum([
8004
8558
  "review",
8005
8559
  "ugc_how_to",
8006
8560
  "problem_product_cta",
@@ -8017,7 +8571,7 @@ var NarrativeStyleEnum = z42.enum([
8017
8571
  "ugc_skeptic_confession",
8018
8572
  "ugc_demo_review"
8019
8573
  ]);
8020
- var PublicNarrativeStyleEnum = z42.enum([
8574
+ var PublicNarrativeStyleEnum = z44.enum([
8021
8575
  "review",
8022
8576
  "ugc_how_to",
8023
8577
  "ugc_unboxing_chaos",
@@ -8029,7 +8583,7 @@ var PublicNarrativeStyleEnum = z42.enum([
8029
8583
  "animated_problem_pixar",
8030
8584
  "sassy_product_pixar"
8031
8585
  ]);
8032
- var VideoModelEnum = z42.enum([
8586
+ var VideoModelEnum = z44.enum([
8033
8587
  "veo",
8034
8588
  "wan",
8035
8589
  "bytedance",
@@ -8037,25 +8591,25 @@ var VideoModelEnum = z42.enum([
8037
8591
  "grok",
8038
8592
  "kling"
8039
8593
  ]);
8040
- var ProductReferenceUserChoiceEnum = z42.enum([
8594
+ var ProductReferenceUserChoiceEnum = z44.enum([
8041
8595
  "same_product",
8042
8596
  "variants",
8043
8597
  "different_products",
8044
8598
  "combo_distinct"
8045
8599
  ]);
8046
- var ProductReferenceCandidateGroupingEnum = z42.enum([
8600
+ var ProductReferenceCandidateGroupingEnum = z44.enum([
8047
8601
  "same_product_same_variant",
8048
8602
  "same_product_different_variants",
8049
8603
  "different_products",
8050
8604
  "unclear"
8051
8605
  ]);
8052
- var GapDurationChoiceMirror = z42.object({
8053
- angleIndex: z42.number().int().nonnegative(),
8054
- recommendedSec: z42.union([
8055
- z42.literal(15),
8056
- z42.literal(30)
8606
+ var GapDurationChoiceMirror = z44.object({
8607
+ angleIndex: z44.number().int().nonnegative(),
8608
+ recommendedSec: z44.union([
8609
+ z44.literal(15),
8610
+ z44.literal(30)
8057
8611
  ]),
8058
- estimatedDeltaCredits: z42.number().int().nonnegative().optional()
8612
+ estimatedDeltaCredits: z44.number().int().nonnegative().optional()
8059
8613
  });
8060
8614
  function buildSelectedImageUrlsHash(urls) {
8061
8615
  const input = [
@@ -8069,13 +8623,13 @@ function buildSelectedImageUrlsHash(urls) {
8069
8623
  return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
8070
8624
  }
8071
8625
  __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
8072
- var ImageCreativeStyleEnum = z42.enum([
8626
+ var ImageCreativeStyleEnum = z44.enum([
8073
8627
  "studio",
8074
8628
  "floating",
8075
8629
  "ingredients",
8076
8630
  "in_use"
8077
8631
  ]);
8078
- var ImageCreativeArchetypeEnum = z42.enum([
8632
+ var ImageCreativeArchetypeEnum = z44.enum([
8079
8633
  "direct_response_product_ad",
8080
8634
  "hero_outcome",
8081
8635
  "offer_value_prop",
@@ -8084,23 +8638,23 @@ var ImageCreativeArchetypeEnum = z42.enum([
8084
8638
  "problem_solution",
8085
8639
  "lifestyle_identity"
8086
8640
  ]);
8087
- var ImageCreativeVisualTreatmentEnum = z42.enum([
8641
+ var ImageCreativeVisualTreatmentEnum = z44.enum([
8088
8642
  "performance_product_ad",
8089
8643
  "premium_dtc_carousel"
8090
8644
  ]);
8091
- var ImageCreativeAspectRatioEnum = z42.enum([
8645
+ var ImageCreativeAspectRatioEnum = z44.enum([
8092
8646
  "1:1",
8093
8647
  "4:5",
8094
8648
  "9:16",
8095
8649
  "16:9"
8096
8650
  ]);
8097
- var ImageRefSchema = z42.object({
8098
- url: z42.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
8099
- path: z42.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.')
8651
+ var ImageRefSchema = z44.object({
8652
+ 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.'),
8653
+ 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`.')
8100
8654
  }).refine((v) => Boolean(v.url) !== Boolean(v.path), {
8101
8655
  message: "each image entry must have exactly one of `url` or `path`"
8102
8656
  });
8103
- var ImagesInput = z42.array(ImageRefSchema).min(1).max(5).describe('1 a 5 fotos del MISMO producto. La PRIMERA es la principal (hero); las dem\xE1s son \xE1ngulos adicionales. NO mezcles productos distintos en una sola llamada.\n\nCada entrada tiene EXACTAMENTE uno de:\n\u2022 `url` \u2014 URL p\xFAblica HTTPS (Shopify CDN, Cloudinary). Prefi\xE9rela si la foto ya est\xE1 hospedada.\n\u2022 `path` \u2014 Ruta del archivo en el computador del usuario (ej. "/Users/maria/Desktop/foto.jpg", "~/Downloads/camiseta.png"). \xDAsala cuando el usuario te dijo d\xF3nde guard\xF3 la foto. Solo MCP local \u2014 el cliente web rechaza paths.\n\nSi el usuario te peg\xF3 la foto en el chat pero no te dio ni URL ni ruta: PREG\xDANTALE d\xF3nde la tiene guardada (Escritorio, Descargas, etc.). Si el `path` falla con IMAGE_PATH_NOT_FOUND, prueba 1-2 ubicaciones obvias alternativas (~/Desktop, ~/Downloads) antes de molestar al usuario. Tras 3 intentos fallidos, p\xEDdele la ruta completa.');
8657
+ 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.');
8104
8658
  function asText(value) {
8105
8659
  return {
8106
8660
  content: [
@@ -8153,14 +8707,14 @@ async function submitCreative(api, opts) {
8153
8707
  });
8154
8708
  }
8155
8709
  __name(submitCreative, "submitCreative");
8156
- var ResearchTargetCountryEnum = z42.enum([
8710
+ var ResearchTargetCountryEnum = z44.enum([
8157
8711
  "CO",
8158
8712
  "MX",
8159
8713
  "PE",
8160
8714
  "EC",
8161
8715
  "CL"
8162
8716
  ]);
8163
- var ResearchCategoryEnum = z42.enum([
8717
+ var ResearchCategoryEnum = z44.enum([
8164
8718
  "auto",
8165
8719
  "automotive",
8166
8720
  "kitchen",
@@ -8233,6 +8787,15 @@ var McpErrorCode = {
8233
8787
  SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
8234
8788
  // Meta / Facebook Ads
8235
8789
  META_NO_FB_PAGE: "META_NO_FB_PAGE",
8790
+ META_PORTFOLIO_MISMATCH: "META_PORTFOLIO_MISMATCH",
8791
+ META_PAGE_SELECTION_REQUIRED: "META_PAGE_SELECTION_REQUIRED",
8792
+ META_INVALID_PAGE: "META_INVALID_PAGE",
8793
+ META_STAGED_ASSET_NOT_FOUND: "META_STAGED_ASSET_NOT_FOUND",
8794
+ META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
8795
+ META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
8796
+ META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
8797
+ META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
8798
+ META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
8236
8799
  META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
8237
8800
  META_NO_ASSETS_READY: "META_NO_ASSETS_READY",
8238
8801
  META_NO_THUMBNAIL_SOURCE: "META_NO_THUMBNAIL_SOURCE",
@@ -8285,6 +8848,15 @@ var CODE_MESSAGES = {
8285
8848
  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.",
8286
8849
  // Meta — high-frequency
8287
8850
  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).",
8851
+ 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.",
8852
+ 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`.",
8853
+ META_INVALID_PAGE: "The selected Facebook Page is not available for this Meta connection. Call `list_meta_destinations` again and choose an available `pageId`.",
8854
+ META_STAGED_ASSET_NOT_FOUND: "The temporary media upload was not found or expired. Re-upload the media and retry the Meta publication.",
8855
+ META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
8856
+ META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
8857
+ META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
8858
+ 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.",
8859
+ 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.",
8288
8860
  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.",
8289
8861
  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.",
8290
8862
  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.",
@@ -8398,6 +8970,12 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
8398
8970
  McpErrorCode.CAMPAIGN_MISSING_LANDING_URL,
8399
8971
  McpErrorCode.CAMPAIGN_RECONCILIATION_REQUIRED,
8400
8972
  McpErrorCode.META_NO_LANDING_URL,
8973
+ McpErrorCode.META_PORTFOLIO_MISMATCH,
8974
+ McpErrorCode.META_PAGE_SELECTION_REQUIRED,
8975
+ McpErrorCode.META_INVALID_PAGE,
8976
+ McpErrorCode.META_STAGED_ASSET_NOT_FOUND,
8977
+ McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
8978
+ McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
8401
8979
  McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
8402
8980
  McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
8403
8981
  McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
@@ -8419,6 +8997,15 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
8419
8997
  McpErrorCode.META_MISSING_PAYMENT,
8420
8998
  McpErrorCode.META_ACCOUNT_RESTRICTED,
8421
8999
  McpErrorCode.META_NO_FB_PAGE,
9000
+ McpErrorCode.META_PORTFOLIO_MISMATCH,
9001
+ McpErrorCode.META_PAGE_SELECTION_REQUIRED,
9002
+ McpErrorCode.META_INVALID_PAGE,
9003
+ McpErrorCode.META_STAGED_ASSET_NOT_FOUND,
9004
+ McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
9005
+ McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
9006
+ McpErrorCode.META_STAGED_ASSET_INVALID,
9007
+ McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
9008
+ McpErrorCode.META_PUBLICATION_IN_PROGRESS,
8422
9009
  McpErrorCode.TIKTOK_TOKEN_EXPIRED,
8423
9010
  McpErrorCode.TIKTOK_PERMISSION_DENIED,
8424
9011
  McpErrorCode.TIKTOK_MISSING_PAYMENT,
@@ -8813,52 +9400,53 @@ function renderOutputsMarkdown(outputs) {
8813
9400
  __name(renderOutputsMarkdown, "renderOutputsMarkdown");
8814
9401
 
8815
9402
  // src/tools/tasks.ts
8816
- var CreateProductTaskInput = z43.object({
9403
+ var CreateProductTaskInput = z45.object({
8817
9404
  images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
8818
- projectId: z43.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
8819
- productUrl: z43.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
9405
+ 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."),
9406
+ 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."),
8820
9407
  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.'),
8821
9408
  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.'),
8822
- price: z43.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
8823
- compareAtPrice: z43.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
8824
- offer: z43.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
8825
- paymentMethods: z43.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
8826
- productContext: z43.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
8827
- productLabel: z43.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
8828
- 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.'),
8829
- durationSeconds: z43.union([
8830
- z43.literal(15),
8831
- z43.literal(30)
9409
+ 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.'),
9410
+ 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".'),
9411
+ 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.'),
9412
+ 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.'),
9413
+ 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."),
9414
+ 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.'),
9415
+ 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.'),
9416
+ durationSeconds: z45.union([
9417
+ z45.literal(15),
9418
+ z45.literal(30)
8832
9419
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
8833
- pauseForAngleReview: z43.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
9420
+ 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."),
8834
9421
  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.'),
8835
9422
  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).'),
8836
- multiAngleVideos: z43.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
9423
+ 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."),
8837
9424
  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.'),
8838
- outputs: z43.array(z43.enum([
9425
+ outputs: z45.array(z45.enum([
8839
9426
  "landing",
8840
9427
  "video",
8841
9428
  "image"
8842
9429
  ])).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.'),
8843
- competitorScanEnabled: z43.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
8844
- brandId: z43.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
8845
- creativeDirection: z43.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
8846
- headline: z43.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar. Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. Se aplica a las IM\xC1GENES y a la portada de la landing de esta task (el video no lo usa; para el video usa scriptNotes). \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
8847
- subheadline: z43.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres."),
9430
+ 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."),
9431
+ 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."),
9432
+ 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."),
9433
+ 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.'),
9434
+ 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."),
9435
+ 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."),
8848
9436
  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".'),
8849
9437
  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".')
8850
9438
  }).strict();
8851
- var CreateServiceVideoInput = z43.object({
8852
- landingUrl: z43.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
8853
- additionalContext: z43.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
9439
+ var CreateServiceVideoInput = z45.object({
9440
+ 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."),
9441
+ 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.'),
8854
9442
  language: Lang.describe("Idioma del video: es, en o pt."),
8855
- voiceId: z43.string().trim().min(1).optional().describe('Opcional. Voz de ElevenLabs para toda la narraci\xF3n. Usa "auto" para que la IA la alinee con el presentador o env\xEDa un ID concreto de la lista de voces de la web.'),
8856
- brandId: z43.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
8857
- reviewMode: z43.enum([
9443
+ 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.'),
9444
+ brandId: z45.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
9445
+ reviewMode: z45.enum([
8858
9446
  "automatic",
8859
9447
  "guided"
8860
9448
  ]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
8861
- videoPreset: z43.enum([
9449
+ videoPreset: z45.enum([
8862
9450
  "auto",
8863
9451
  "ugc_dynamic",
8864
9452
  "ugc_how_to",
@@ -8866,34 +9454,35 @@ var CreateServiceVideoInput = z43.object({
8866
9454
  "wild_card"
8867
9455
  ]).default("auto")
8868
9456
  }).strict();
8869
- var GetTaskStatusInput = z43.object({
8870
- taskId: z43.string().min(1),
8871
- waitSeconds: z43.number().int().min(0).max(120).optional()
8872
- });
8873
- var ListTasksInput = z43.object({
8874
- limit: z43.number().int().min(1).max(50).optional(),
8875
- offset: z43.number().int().min(0).optional(),
8876
- search: z43.string().optional()
8877
- });
8878
- var EditTaskDraftInput = z43.object({
8879
- taskId: z43.string().min(1),
8880
- angleIndex: z43.number().int().min(0),
8881
- angle: z43.object({
8882
- angle: z43.string().min(1).max(480),
8883
- buyerPersona: z43.string().min(1).max(360),
8884
- actor: z43.string().min(1).max(360).optional(),
8885
- gender: z43.enum([
9457
+ var GetTaskStatusInput = z45.object({
9458
+ taskId: z45.string().min(1),
9459
+ waitSeconds: z45.number().int().min(0).max(120).optional()
9460
+ });
9461
+ var ListTasksInput = z45.object({
9462
+ limit: z45.number().int().min(1).max(50).optional(),
9463
+ offset: z45.number().int().min(0).optional(),
9464
+ search: z45.string().optional()
9465
+ });
9466
+ var EditTaskDraftInput = z45.object({
9467
+ taskId: z45.string().min(1),
9468
+ angleIndex: z45.number().int().min(0),
9469
+ angle: z45.object({
9470
+ angle: z45.string().min(1).max(480),
9471
+ buyerPersona: z45.string().min(1).max(360),
9472
+ actor: z45.string().min(1).max(360).optional(),
9473
+ 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."),
9474
+ gender: z45.enum([
8886
9475
  "female",
8887
9476
  "male"
8888
9477
  ]).optional(),
8889
- scriptNotes: z43.string().max(2e3).optional()
9478
+ scriptNotes: z45.string().max(2e3).optional()
8890
9479
  })
8891
9480
  });
8892
- var PendingActionPayload = z43.discriminatedUnion("kind", [
8893
- z43.object({
8894
- kind: z43.literal("product_reference_confirmation"),
9481
+ var PendingActionPayload = z45.discriminatedUnion("kind", [
9482
+ z45.object({
9483
+ kind: z45.literal("product_reference_confirmation"),
8895
9484
  userChoice: ProductReferenceUserChoiceEnum,
8896
- confirmedProductNames: z43.array(z43.string().min(1).max(120)).min(2).max(8).optional(),
9485
+ confirmedProductNames: z45.array(z45.string().min(1).max(120)).min(2).max(8).optional(),
8897
9486
  // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
8898
9487
  // reachable via MCP. Valid ONLY together with userChoice='variants' (same
8899
9488
  // product in several colors): renders one shot per color and stitches them,
@@ -8904,50 +9493,50 @@ var PendingActionPayload = z43.discriminatedUnion("kind", [
8904
9493
  // safe because multi-scene IS the intended render for a color variant set,
8905
9494
  // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
8906
9495
  // confirm call; the backend re-gates it (variants + variant_set only).
8907
- useMultiSceneShowcase: z43.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
9496
+ 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".')
8908
9497
  }),
8909
- z43.object({
8910
- kind: z43.literal("angle_review_approval"),
9498
+ z45.object({
9499
+ kind: z45.literal("angle_review_approval"),
8911
9500
  // All edits are optional — empty action body just approves the
8912
9501
  // backend-generated draft as-is. Field shapes mirror the angle-review
8913
9502
  // request contracts in @packages/types/wizard.ts; the handler routes to
8914
9503
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
8915
- customAngle: z43.string().min(1).max(480).optional(),
9504
+ customAngle: z45.string().min(1).max(480).optional(),
8916
9505
  // Keep the shared MCP action within the strictest downstream contract:
8917
9506
  // autopilot/image resume endpoints cap these fields at 1500 characters.
8918
- painPoints: z43.string().max(1500).optional(),
8919
- benefits: z43.string().max(1500).optional(),
8920
- features: z43.array(z43.string().min(1).max(300)).max(10).optional(),
8921
- gapDurationSeconds: z43.union([
8922
- z43.literal(15),
8923
- z43.literal(30)
9507
+ painPoints: z45.string().max(1500).optional(),
9508
+ benefits: z45.string().max(1500).optional(),
9509
+ features: z45.array(z45.string().min(1).max(300)).max(10).optional(),
9510
+ gapDurationSeconds: z45.union([
9511
+ z45.literal(15),
9512
+ z45.literal(30)
8924
9513
  ]).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.")
8925
9514
  }),
8926
- z43.object({
8927
- kind: z43.literal("service_angle_approval"),
8928
- angleId: z43.string().uuid(),
8929
- content: z43.string().min(1).max(480).optional(),
8930
- creativeDirection: z43.string().min(1).max(1500).optional(),
8931
- actor: z43.string().min(1).max(360).optional()
9515
+ z45.object({
9516
+ kind: z45.literal("service_angle_approval"),
9517
+ angleId: z45.string().uuid(),
9518
+ content: z45.string().min(1).max(480).optional(),
9519
+ creativeDirection: z45.string().min(1).max(1500).optional(),
9520
+ actor: z45.string().min(1).max(360).optional()
8932
9521
  }),
8933
- z43.object({
8934
- kind: z43.literal("service_script_approval"),
8935
- voiceovers: z43.record(z43.string(), z43.string().min(1).max(600)).optional(),
8936
- visualDirections: z43.record(z43.string(), z43.string().min(1).max(1200)).optional()
9522
+ z45.object({
9523
+ kind: z45.literal("service_script_approval"),
9524
+ voiceovers: z45.record(z45.string(), z45.string().min(1).max(600)).optional(),
9525
+ visualDirections: z45.record(z45.string(), z45.string().min(1).max(1200)).optional()
8937
9526
  })
8938
9527
  ]);
8939
- var ResolvePendingActionInput = z43.object({
8940
- taskId: z43.string().min(1),
9528
+ var ResolvePendingActionInput = z45.object({
9529
+ taskId: z45.string().min(1),
8941
9530
  action: PendingActionPayload
8942
9531
  }).strict();
8943
9532
  function resolveVariantConfirmVideoTier(action) {
8944
9533
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
8945
9534
  }
8946
9535
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
8947
- var StatusForResolveSchema = z43.object({
9536
+ var StatusForResolveSchema = z45.object({
8948
9537
  // Metadata used to select the backend resume contract. Older API responses
8949
9538
  // may omit these fields, so the resolver keeps the generic fallback.
8950
- kind: z43.enum([
9539
+ kind: z45.enum([
8951
9540
  "autopilot",
8952
9541
  "video",
8953
9542
  "image",
@@ -8955,24 +9544,24 @@ var StatusForResolveSchema = z43.object({
8955
9544
  "mix",
8956
9545
  "service"
8957
9546
  ]).optional(),
8958
- selectedOutputs: z43.array(z43.enum([
9547
+ selectedOutputs: z45.array(z45.enum([
8959
9548
  "landing",
8960
9549
  "video",
8961
9550
  "image"
8962
9551
  ])).optional(),
8963
- videoAngleCount: z43.number().int().min(1).max(3).optional(),
8964
- ugcFormat: z43.enum([
9552
+ videoAngleCount: z45.number().int().min(1).max(3).optional(),
9553
+ ugcFormat: z45.enum([
8965
9554
  "single_8s",
8966
9555
  "single_15s",
8967
9556
  "double_30s"
8968
9557
  ]).optional(),
8969
- videoStyle: z43.enum([
9558
+ videoStyle: z45.enum([
8970
9559
  "ugc",
8971
9560
  "cartoon_3d",
8972
9561
  "product_only",
8973
9562
  "ai_dynamic"
8974
9563
  ]).optional(),
8975
- videoModel: z43.enum([
9564
+ videoModel: z45.enum([
8976
9565
  "veo",
8977
9566
  "wan",
8978
9567
  "bytedance",
@@ -8980,39 +9569,45 @@ var StatusForResolveSchema = z43.object({
8980
9569
  "grok",
8981
9570
  "kling"
8982
9571
  ]).optional(),
8983
- videoTier: z43.enum([
9572
+ videoTier: z45.enum([
8984
9573
  "legacy",
8985
9574
  "standard",
8986
9575
  "pro",
8987
9576
  "single_call"
8988
9577
  ]).optional(),
8989
- videoResolution: z43.enum([
9578
+ videoResolution: z45.enum([
8990
9579
  "480p",
8991
9580
  "720p"
8992
9581
  ]).optional(),
8993
9582
  narrativeStyle: NarrativeStyleEnum.optional(),
8994
- pendingUserAction: z43.object({
8995
- kind: z43.string(),
8996
- candidateGrouping: z43.string().optional(),
8997
- selectedImageUrls: z43.array(z43.string()).optional(),
8998
- proposedProductNames: z43.array(z43.string()).optional(),
8999
- customAngleDraft: z43.string().optional(),
9000
- painPointsDraft: z43.string().optional(),
9001
- benefitsDraft: z43.string().optional(),
9002
- featuresDraft: z43.array(z43.string()).optional(),
9583
+ pendingUserAction: z45.object({
9584
+ kind: z45.string(),
9585
+ candidateGrouping: z45.string().optional(),
9586
+ selectedImageUrls: z45.array(z45.string()).optional(),
9587
+ proposedProductNames: z45.array(z45.string()).optional(),
9588
+ customAngleDraft: z45.string().optional(),
9589
+ painPointsDraft: z45.string().optional(),
9590
+ benefitsDraft: z45.string().optional(),
9591
+ featuresDraft: z45.array(z45.string()).optional(),
9003
9592
  // Recommendation consumed only when the caller explicitly sends
9004
9593
  // gapDurationSeconds: 30 in the angle approval action.
9005
9594
  gapDurationChoice: GapDurationChoiceMirror.optional(),
9006
- angles: z43.array(z43.object({
9007
- id: z43.string().uuid(),
9008
- content: z43.string(),
9009
- creativeDirection: z43.string(),
9010
- actor: z43.string()
9011
- })).optional(),
9012
- panels: z43.array(z43.object({
9013
- beatId: z43.string(),
9014
- voiceover: z43.string(),
9015
- visualDirection: z43.string()
9595
+ // `.passthrough()` + an explicit `actorId`: this object is a MIRROR of the
9596
+ // backend's pending service angle, and a bare z.object() STRIPS unknown
9597
+ // keys. `actorId` (the saved custom actor the picker stored) was silently
9598
+ // dropped here, so the approval handler below could not echo it back on the
9599
+ // PATCH — the selection looked accepted and reached no render.
9600
+ angles: z45.array(z45.object({
9601
+ id: z45.string().uuid(),
9602
+ content: z45.string(),
9603
+ creativeDirection: z45.string(),
9604
+ actor: z45.string(),
9605
+ actorId: z45.string().nullable().optional()
9606
+ }).passthrough()).optional(),
9607
+ panels: z45.array(z45.object({
9608
+ beatId: z45.string(),
9609
+ voiceover: z45.string(),
9610
+ visualDirection: z45.string()
9016
9611
  }).passthrough()).optional()
9017
9612
  }).optional()
9018
9613
  }).passthrough();
@@ -9053,7 +9648,6 @@ __name(resolveStatusTaskKind, "resolveStatusTaskKind");
9053
9648
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
9054
9649
  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) => {
9055
9650
  try {
9056
- assertVideoPresetDurationCompatibility(input.videoPreset, input.durationSeconds);
9057
9651
  if (!input.images && !input.projectId) {
9058
9652
  throw new Error("images es requerido salvo que env\xEDes projectId.");
9059
9653
  }
@@ -9088,6 +9682,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
9088
9682
  "options.selectedOutputs": input.outputs,
9089
9683
  "options.competitorScanEnabled": input.competitorScanEnabled,
9090
9684
  "options.brandId": input.brandId,
9685
+ "options.actorId": input.actorId,
9091
9686
  "options.creativeDirection": input.creativeDirection,
9092
9687
  "options.headline": input.headline,
9093
9688
  "options.subheadline": input.subheadline,
@@ -9229,7 +9824,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
9229
9824
  throw translateError(e);
9230
9825
  }
9231
9826
  });
9232
- server.tool("edit_task_draft", "Edita el draft de un angle de marketing antes de la generaci\xF3n. Campos editables: angle (texto del angle), buyerPersona, actor (descripci\xF3n del personaje), gender (female|male), scriptNotes. Use this only before generation completes. Editing already-generated images or videos is not supported.", EditTaskDraftInput.shape, async (input) => {
9827
+ server.tool("edit_task_draft", "Edita el draft de un angle de marketing antes de la generaci\xF3n. Campos editables: angle (texto del angle), buyerPersona, actor (descripci\xF3n del personaje), actorId (ID de un personaje guardado; usa list_actors), gender (female|male), scriptNotes. Use this only before generation completes. Editing already-generated images or videos is not supported.", EditTaskDraftInput.shape, async (input) => {
9233
9828
  try {
9234
9829
  const angle = await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/angles/${input.angleIndex}`, input.angle ?? {});
9235
9830
  return asText({
@@ -9405,7 +10000,13 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
9405
10000
  const edits = {
9406
10001
  content: action.content ?? candidate.content,
9407
10002
  creativeDirection: action.creativeDirection ?? candidate.creativeDirection,
9408
- actor: action.actor ?? candidate.actor
10003
+ actor: action.actor ?? candidate.actor,
10004
+ // Echo the persisted saved actor. Omitted when absent so the PATCH
10005
+ // never writes a null over a selection made elsewhere — the
10006
+ // backend only updates the keys it receives.
10007
+ ...candidate.actorId ? {
10008
+ actorId: candidate.actorId
10009
+ } : {}
9409
10010
  };
9410
10011
  await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/${encodeURIComponent(candidate.id)}`, edits);
9411
10012
  const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/select`, {
@@ -9446,58 +10047,58 @@ __name(registerTaskTools, "registerTaskTools");
9446
10047
 
9447
10048
  // src/tools/creatives.ts
9448
10049
  import { randomUUID } from "crypto";
9449
- import { z as z44 } from "zod";
10050
+ import { z as z46 } from "zod";
9450
10051
  var CreativeRefs = {
9451
10052
  images: ImagesInput,
9452
10053
  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.'),
9453
10054
  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.'),
9454
- price: z44.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
9455
- offer: z44.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
9456
- compareAtPrice: z44.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
9457
- paymentMethods: z44.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago a mostrar. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
9458
- 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."),
9459
- productLabel: z44.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
9460
- buyerPersona: z44.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
9461
- isCombo: z44.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
9462
- 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."),
9463
- brandId: z44.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer el creativo."),
9464
- projectId: z44.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
9465
- creativeDirection: z44.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Aplica SOLO a im\xE1genes y landing. Ej. im\xE1genes: "amarillo, azul y rojo, minimalista"; landing: "elegante, paleta fr\xEDa". Para video usa scriptNotes (ambiente/escenario). M\xE1x 500 caracteres.'),
9466
- headline: z44.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar en la imagen (o en la portada de la landing). Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
9467
- 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.")
10055
+ 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.'),
10056
+ 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.'),
10057
+ 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".'),
10058
+ 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.'),
10059
+ 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."),
10060
+ 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.'),
10061
+ 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.'),
10062
+ isCombo: z46.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
10063
+ 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."),
10064
+ 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."),
10065
+ 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."),
10066
+ 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.'),
10067
+ 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."),
10068
+ 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.")
9468
10069
  };
9469
- var VideoInput = z44.object({
10070
+ var VideoInput = z46.object({
9470
10071
  images: CreativeRefs.images,
9471
- title: z44.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
9472
- primaryAngle: z44.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
10072
+ title: z46.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
10073
+ 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".'),
9473
10074
  language: CreativeRefs.language,
9474
10075
  country: CreativeRefs.country,
9475
- aspectRatio: z44.enum([
10076
+ aspectRatio: z46.enum([
9476
10077
  "9:16",
9477
10078
  "16:9",
9478
10079
  "Auto"
9479
10080
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
9480
- durationSeconds: z44.union([
9481
- z44.literal(15),
9482
- z44.literal(30)
10081
+ durationSeconds: z46.union([
10082
+ z46.literal(15),
10083
+ z46.literal(30)
9483
10084
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
9484
- 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).'),
10085
+ 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).'),
9485
10086
  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.'),
9486
10087
  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).'),
9487
- features: z44.array(z44.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
10088
+ 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"].'),
9488
10089
  price: CreativeRefs.price,
9489
10090
  offer: CreativeRefs.offer,
9490
10091
  productContext: CreativeRefs.productContext,
9491
10092
  productLabel: CreativeRefs.productLabel,
9492
- cta: z44.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
9493
- scriptNotes: z44.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
9494
- pauseForVideoScriptReview: z44.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
9495
- pauseForAngleReview: z44.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. Default false."),
10093
+ 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.'),
10094
+ 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.'),
10095
+ 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."),
10096
+ 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."),
9496
10097
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
9497
10098
  brandId: CreativeRefs.brandId,
9498
10099
  projectId: CreativeRefs.projectId
9499
10100
  }).strict();
9500
- var FreeVideoQualityEnum = z44.enum([
10101
+ var FreeVideoQualityEnum = z46.enum([
9501
10102
  "economical",
9502
10103
  "high_detail"
9503
10104
  ]);
@@ -9519,38 +10120,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
9519
10120
  15
9520
10121
  ]
9521
10122
  };
9522
- var FreeVideoInput = z44.object({
10123
+ var FreeVideoInput = z46.object({
9523
10124
  images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
9524
- rawPrompt: z44.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
10125
+ 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."),
9525
10126
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
9526
- durationSeconds: z44.number().int().min(1).max(15).describe("Duraci\xF3n exacta. Econ\xF3mico permite 6, 8, 10 o 15s; Alto detalle permite 4, 8, 12 o 15s."),
10127
+ 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."),
9527
10128
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
9528
- generateAiMusic: z44.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
9529
- addSubtitles: z44.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
9530
- idempotencyKey: z44.string().uuid().optional().describe("Opcional. UUID para reintentar exactamente esta misma solicitud sin crear ni cobrar otro video. Reutiliza el mismo valor si el cliente no recibi\xF3 la respuesta.")
10129
+ generateAiMusic: z46.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
10130
+ addSubtitles: z46.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
10131
+ 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.")
9531
10132
  }).strict();
9532
10133
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
9533
10134
  var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
9534
10135
  var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
9535
10136
  var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
9536
- var ImageInput = z44.object({
10137
+ var ImageInput = z46.object({
9537
10138
  images: CreativeRefs.images,
9538
- productTitle: z44.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
9539
- primaryAngle: z44.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
10139
+ productTitle: z46.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
10140
+ primaryAngle: z46.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
9540
10141
  buyerPersona: CreativeRefs.buyerPersona,
9541
10142
  language: CreativeRefs.language,
9542
10143
  country: CreativeRefs.country,
9543
- nVariants: z44.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
9544
- imageModel: z44.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
10144
+ nVariants: z46.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
10145
+ 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.'),
9545
10146
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
9546
10147
  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).`),
9547
10148
  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.`),
9548
- styleMix: z44.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
10149
+ 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."),
9549
10150
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe(`Opcional. Relaci\xF3n de aspecto. Valores: ${aspectRatioList}. Default "4:5" (vertical Instagram feed).`),
9550
- generateAdCopy: z44.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
9551
- pauseForAngleReview: z44.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo y antes de las im\xE1genes para que el usuario edite v\xEDa edit_task_draft. Default false."),
9552
- features: z44.array(z44.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
9553
- brandColors: z44.array(z44.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
10151
+ generateAdCopy: z46.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
10152
+ 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."),
10153
+ features: z46.array(z46.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
10154
+ 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.'),
9554
10155
  price: CreativeRefs.price,
9555
10156
  compareAtPrice: CreativeRefs.compareAtPrice,
9556
10157
  offer: CreativeRefs.offer,
@@ -9564,16 +10165,16 @@ var ImageInput = z44.object({
9564
10165
  headline: CreativeRefs.headline,
9565
10166
  subheadline: CreativeRefs.subheadline
9566
10167
  });
9567
- var EditImageCreativeInput = z44.object({
9568
- taskId: z44.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
9569
- variantIndex: z44.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
9570
- instruction: z44.string().min(1).max(1e3).describe('Qu\xE9 cambiar en esa imagen. Ej. "quita el badge de env\xEDo gratis", "cambia el fondo a blanco".')
10168
+ var EditImageCreativeInput = z46.object({
10169
+ taskId: z46.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
10170
+ variantIndex: z46.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
10171
+ 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".')
9571
10172
  });
9572
- var LandingInput = z44.object({
10173
+ var LandingInput = z46.object({
9573
10174
  images: CreativeRefs.images,
9574
10175
  language: CreativeRefs.language,
9575
10176
  country: CreativeRefs.country,
9576
- salesAngle: z44.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
10177
+ 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.'),
9577
10178
  buyerPersona: CreativeRefs.buyerPersona,
9578
10179
  price: CreativeRefs.price,
9579
10180
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -9622,9 +10223,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
9622
10223
  throw translateError(e);
9623
10224
  }
9624
10225
  });
9625
- server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (15 o 30), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nESTILO: 15s usa "Realista" (single_call); 30s usa "Multiescena" (multi-toma, aproximadamente 30s). Multiescena se factura por escena, as\xED que avisa el costo antes de activarlo.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes. Para revisar el \xE1ngulo y decidir duraci\xF3n tras el an\xE1lisis de competencia, pasa `pauseForAngleReview: true` y luego resuelve `angle_review_approval` con `gapDurationSeconds`.', VideoInput.shape, async (input) => {
10226
+ 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) => {
9626
10227
  try {
9627
- assertVideoPresetDurationCompatibility(input.videoPreset, input.durationSeconds);
9628
10228
  const resolved = resolveVideoPreset(input.videoPreset);
9629
10229
  const wireTier = resolveWireVideoTier(input.videoTier, input.durationSeconds);
9630
10230
  const { videoTier: _publicTier, videoResolution: _vr, projectId, ...rest } = input;
@@ -9699,8 +10299,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
9699
10299
  __name(registerCreativeTools, "registerCreativeTools");
9700
10300
 
9701
10301
  // src/tools/creative-variants.ts
9702
- import { z as z45 } from "zod";
9703
- var LandingSectionKeyEnum = z45.enum([
10302
+ import { z as z47 } from "zod";
10303
+ var LandingSectionKeyEnum = z47.enum([
9704
10304
  "problem_hook",
9705
10305
  "problem_cost",
9706
10306
  "solution_mechanism",
@@ -9740,24 +10340,24 @@ var LandingSectionKeyEnum = z45.enum([
9740
10340
  "lifestyle_fit",
9741
10341
  "join_lifestyle"
9742
10342
  ]);
9743
- var CreativeVariantKindEnum = z45.enum([
10343
+ var CreativeVariantKindEnum = z47.enum([
9744
10344
  "video",
9745
10345
  "image",
9746
10346
  "landing"
9747
10347
  ]);
9748
- var EstimateCreativeVariantInputBase = z45.object({
9749
- sourceTaskId: z45.string().uuid().describe("ID de la tarea original completada."),
10348
+ var EstimateCreativeVariantInputBase = z47.object({
10349
+ sourceTaskId: z47.string().uuid().describe("ID de la tarea original completada."),
9750
10350
  kind: CreativeVariantKindEnum,
9751
10351
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
9752
10352
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
9753
- nVariants: z45.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
10353
+ nVariants: z47.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
9754
10354
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
9755
- instructions: z45.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
10355
+ 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.")
9756
10356
  }).strict();
9757
10357
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
9758
10358
  if (input.kind === "video" && input.nVariants !== void 0) {
9759
10359
  ctx.addIssue({
9760
- code: z45.ZodIssueCode.custom,
10360
+ code: z47.ZodIssueCode.custom,
9761
10361
  path: [
9762
10362
  "nVariants"
9763
10363
  ],
@@ -9767,7 +10367,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9767
10367
  if (input.kind !== "video") {
9768
10368
  if (input.videoPreset !== void 0) {
9769
10369
  ctx.addIssue({
9770
- code: z45.ZodIssueCode.custom,
10370
+ code: z47.ZodIssueCode.custom,
9771
10371
  path: [
9772
10372
  "videoPreset"
9773
10373
  ],
@@ -9776,7 +10376,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9776
10376
  }
9777
10377
  if (input.videoResolution !== void 0) {
9778
10378
  ctx.addIssue({
9779
- code: z45.ZodIssueCode.custom,
10379
+ code: z47.ZodIssueCode.custom,
9780
10380
  path: [
9781
10381
  "videoResolution"
9782
10382
  ],
@@ -9786,7 +10386,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9786
10386
  }
9787
10387
  if (input.kind !== "image" && input.nVariants !== void 0) {
9788
10388
  ctx.addIssue({
9789
- code: z45.ZodIssueCode.custom,
10389
+ code: z47.ZodIssueCode.custom,
9790
10390
  path: [
9791
10391
  "nVariants"
9792
10392
  ],
@@ -9796,7 +10396,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9796
10396
  if (input.kind !== "landing") {
9797
10397
  if (input.sectionKey !== void 0) {
9798
10398
  ctx.addIssue({
9799
- code: z45.ZodIssueCode.custom,
10399
+ code: z47.ZodIssueCode.custom,
9800
10400
  path: [
9801
10401
  "sectionKey"
9802
10402
  ],
@@ -9805,7 +10405,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9805
10405
  }
9806
10406
  if (input.instructions !== void 0) {
9807
10407
  ctx.addIssue({
9808
- code: z45.ZodIssueCode.custom,
10408
+ code: z47.ZodIssueCode.custom,
9809
10409
  path: [
9810
10410
  "instructions"
9811
10411
  ],
@@ -9814,31 +10414,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
9814
10414
  }
9815
10415
  }
9816
10416
  });
9817
- var CreateCreativeVariantInputBase = z45.object({
9818
- sourceTaskId: z45.string().uuid().describe("ID de la tarea original completada."),
10417
+ var CreateCreativeVariantInputBase = z47.object({
10418
+ sourceTaskId: z47.string().uuid().describe("ID de la tarea original completada."),
9819
10419
  kind: CreativeVariantKindEnum,
9820
- confirm: z45.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
9821
- clientRequestId: z45.string().uuid().describe("UUID estable y obligatorio para esta intenci\xF3n. Reutil\xEDzalo ante timeouts o transporte ambiguo. Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n y un UUID nuevo."),
9822
- angle: z45.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
9823
- buyerPersona: z45.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
9824
- scriptNotes: z45.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
9825
- creativeDirection: z45.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
10420
+ confirm: z47.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
10421
+ 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."),
10422
+ angle: z47.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
10423
+ buyerPersona: z47.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
10424
+ scriptNotes: z47.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
10425
+ creativeDirection: z47.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
9826
10426
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
9827
10427
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
9828
- generateAiMusic: z45.boolean().optional().describe("Opcional para video."),
9829
- nVariants: z45.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
10428
+ generateAiMusic: z47.boolean().optional().describe("Opcional para video."),
10429
+ nVariants: z47.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
9830
10430
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
9831
10431
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
9832
10432
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
9833
10433
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
9834
- styleMix: z45.boolean().optional().describe("Opcional para imagen."),
10434
+ styleMix: z47.boolean().optional().describe("Opcional para imagen."),
9835
10435
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
9836
- instructions: z45.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
10436
+ 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.")
9837
10437
  }).strict();
9838
10438
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
9839
10439
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
9840
10440
  ctx.addIssue({
9841
- code: z45.ZodIssueCode.custom,
10441
+ code: z47.ZodIssueCode.custom,
9842
10442
  path: [
9843
10443
  "angle"
9844
10444
  ],
@@ -9847,7 +10447,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
9847
10447
  }
9848
10448
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
9849
10449
  ctx.addIssue({
9850
- code: z45.ZodIssueCode.custom,
10450
+ code: z47.ZodIssueCode.custom,
9851
10451
  path: [
9852
10452
  "instructions"
9853
10453
  ],
@@ -9893,7 +10493,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
9893
10493
  for (const field of fieldsByKind[input.kind]) {
9894
10494
  if (input[field] !== void 0) {
9895
10495
  ctx.addIssue({
9896
- code: z45.ZodIssueCode.custom,
10496
+ code: z47.ZodIssueCode.custom,
9897
10497
  path: [
9898
10498
  field
9899
10499
  ],
@@ -10010,27 +10610,27 @@ function registerCreativeVariantTools(server, api) {
10010
10610
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
10011
10611
 
10012
10612
  // src/tools/script-review.ts
10013
- import { z as z46 } from "zod";
10014
- var GetVideoScriptInput = z46.object({
10015
- taskId: z46.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
10016
- angleKey: z46.string().min(1).optional().describe('Clave del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). REQUERIDO para tareas multi-angle: si se omite, el backend resuelve a "angle_0" LITERAL (no "el primer pendiente"), as\xED que en multi-angle donde angle_0 ya est\xE9 aprobado/saltado obtendr\xEDas status="approved" y nunca alcanzar\xEDas el \xE1ngulo realmente pendiente. Para single-angle puede omitirse.')
10613
+ import { z as z48 } from "zod";
10614
+ var GetVideoScriptInput = z48.object({
10615
+ taskId: z48.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
10616
+ 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.')
10017
10617
  }).strict();
10018
10618
  var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
10019
10619
  var VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2 = 2e3;
10020
- var ApproveSceneEdit = z46.object({
10021
- index: z46.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
10022
- dialogue: z46.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
10620
+ var ApproveSceneEdit = z48.object({
10621
+ index: z48.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
10622
+ dialogue: z48.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
10023
10623
  message: "dialogue no puede ser s\xF3lo espacios"
10024
10624
  }).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.")
10025
10625
  }).strict();
10026
- var ApproveVideoScriptInput = z46.object({
10027
- taskId: z46.string().min(1).describe("ID de la tarea."),
10028
- angleKey: z46.string().trim().min(1).max(120).optional().describe('Mismo angleKey usado en get_video_script. REQUERIDO en multi-angle (si se omite el backend resuelve a "angle_0" literal). En single-angle puede omitirse.'),
10029
- scenes: z46.array(ApproveSceneEdit).min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_SCENES2).describe("Escenas con los di\xE1logos finales. El backend mergea por `index` \u2014 las escenas NO incluidas conservan el di\xE1logo original. Debe haber entre 1 y 50 entradas. Para cada entrada que mandes con un dialogue distinto del original, el backend valida minWords \u2264 words \u2264 maxWords y rechaza con 400 si te sales del rango.")
10626
+ var ApproveVideoScriptInput = z48.object({
10627
+ taskId: z48.string().min(1).describe("ID de la tarea."),
10628
+ 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.'),
10629
+ 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.")
10030
10630
  }).strict();
10031
- var RegenerateVideoScriptInput = z46.object({
10032
- taskId: z46.string().min(1).describe("ID de la tarea."),
10033
- angleKey: z46.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
10631
+ var RegenerateVideoScriptInput = z48.object({
10632
+ taskId: z48.string().min(1).describe("ID de la tarea."),
10633
+ 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.'),
10034
10634
  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).')
10035
10635
  }).strict();
10036
10636
  function registerScriptReviewTools(server, api) {
@@ -10070,9 +10670,9 @@ function registerScriptReviewTools(server, api) {
10070
10670
  __name(registerScriptReviewTools, "registerScriptReviewTools");
10071
10671
 
10072
10672
  // src/tools/connections.ts
10073
- import { z as z47 } from "zod";
10673
+ import { z as z49 } from "zod";
10074
10674
  function registerConnectionTools(server, api) {
10075
- server.tool("list_shopify_shops", "Lista las tiendas Shopify conectadas por este usuario. Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.", z47.object({}).shape, async () => {
10675
+ 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 () => {
10076
10676
  try {
10077
10677
  const r = await api.getJson("/v1/shopify/shops");
10078
10678
  return asText(r);
@@ -10080,10 +10680,10 @@ function registerConnectionTools(server, api) {
10080
10680
  throw translateError(e);
10081
10681
  }
10082
10682
  });
10083
- server.tool("list_shopify_products", 'Busca productos en una tienda Shopify conectada para elegir el target de una republicaci\xF3n. Usa el `id` devuelto junto con `publicationMode="existing"` en `publish_to_shopify`. Si `taskId` se proporciona, la respuesta tambi\xE9n indica el producto publicado actualmente para esa tarea y tienda. No modifica Shopify.', z47.object({
10084
- shopDomain: z47.string().min(3).max(120),
10085
- query: z47.string().trim().max(120).optional(),
10086
- taskId: z47.string().uuid().optional()
10683
+ 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({
10684
+ shopDomain: z49.string().min(3).max(120),
10685
+ query: z49.string().trim().max(120).optional(),
10686
+ taskId: z49.string().uuid().optional()
10087
10687
  }).shape, async ({ shopDomain, query, taskId }) => {
10088
10688
  try {
10089
10689
  const params = new URLSearchParams({
@@ -10097,7 +10697,7 @@ function registerConnectionTools(server, api) {
10097
10697
  throw translateError(e);
10098
10698
  }
10099
10699
  });
10100
- server.tool("list_meta_ad_accounts", "Lista las cuentas publicitarias de Meta conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar Meta en productmaker.app/integrations.", z47.object({}).shape, async () => {
10700
+ 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 () => {
10101
10701
  try {
10102
10702
  const r = await api.getJson("/v1/facebook/ad-accounts");
10103
10703
  return asText(r);
@@ -10105,7 +10705,15 @@ function registerConnectionTools(server, api) {
10105
10705
  throw translateError(e);
10106
10706
  }
10107
10707
  });
10108
- server.tool("list_tiktok_advertisers", "Lista las cuentas de TikTok Ads conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.", z47.object({}).shape, async () => {
10708
+ 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 () => {
10709
+ try {
10710
+ const r = await api.getJson("/v1/facebook/destinations");
10711
+ return asText(r);
10712
+ } catch (e) {
10713
+ throw translateError(e);
10714
+ }
10715
+ });
10716
+ 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 () => {
10109
10717
  try {
10110
10718
  const r = await api.getJson("/v1/tiktok/advertisers");
10111
10719
  return asText(r);
@@ -10116,106 +10724,140 @@ function registerConnectionTools(server, api) {
10116
10724
  }
10117
10725
  __name(registerConnectionTools, "registerConnectionTools");
10118
10726
 
10727
+ // src/tools/actors.ts
10728
+ import { z as z50 } from "zod";
10729
+ function registerActorTools(server, api) {
10730
+ 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 () => {
10731
+ try {
10732
+ const rows = await api.getJson("/v1/actors");
10733
+ const actors = (Array.isArray(rows) ? rows : []).filter((actor) => actor.status === "ready").map(({ id, name, description, gender, voiceId }) => ({
10734
+ id,
10735
+ name,
10736
+ description,
10737
+ gender,
10738
+ voiceId: voiceId ?? null
10739
+ }));
10740
+ return asText({
10741
+ actors
10742
+ });
10743
+ } catch (e) {
10744
+ throw translateError(e);
10745
+ }
10746
+ });
10747
+ }
10748
+ __name(registerActorTools, "registerActorTools");
10749
+
10119
10750
  // src/tools/publish.ts
10120
- import { z as z48 } from "zod";
10121
- var ShopifyInput = z48.object({
10122
- taskId: z48.string().min(1),
10123
- shopDomain: z48.string().min(1),
10751
+ import { z as z51 } from "zod";
10752
+ var ShopifyInput = z51.object({
10753
+ taskId: z51.string().min(1),
10754
+ shopDomain: z51.string().min(1),
10124
10755
  /** Override: number of landing images to include. Default = ALL images the
10125
10756
  * task generated (the MCP fetches /v1/results first to count). Bounded
10126
10757
  * generously here; the backend validates each index against
10127
10758
  * landing.images.length and rejects out-of-range. */
10128
- numLandingImages: z48.number().int().min(1).max(20).optional(),
10129
- variantIndex: z48.number().int().min(0).optional(),
10759
+ numLandingImages: z51.number().int().min(1).max(20).optional(),
10760
+ variantIndex: z51.number().int().min(0).optional(),
10130
10761
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
10131
10762
  * When true, the landing renders variant-picker pills; when false, it ships
10132
10763
  * a single-option product page. Defaults to false. */
10133
- showVariantPicker: z48.boolean().optional(),
10764
+ showVariantPicker: z51.boolean().optional(),
10134
10765
  /** Default `auto` updates the product previously published for this task and
10135
10766
  * shop. Use `existing` with targetProductId to choose another product, or
10136
10767
  * `new` only when the user explicitly wants a duplicate/new listing. */
10137
- publicationMode: z48.enum([
10768
+ publicationMode: z51.enum([
10138
10769
  "auto",
10139
10770
  "existing",
10140
10771
  "new"
10141
10772
  ]).optional(),
10142
10773
  /** Shopify product GID, numeric id, or admin product URL. Required when
10143
10774
  * publicationMode is `existing`. */
10144
- targetProductId: z48.string().trim().min(1).max(300).optional()
10775
+ targetProductId: z51.string().trim().min(1).max(300).optional()
10145
10776
  });
10146
- var McpCampaignConfig = z48.object({
10777
+ var McpCampaignConfig = z51.object({
10147
10778
  /** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
10148
10779
  * converts to the account currency server-side. Do NOT pre-convert: on a COP
10149
10780
  * account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
10150
- dailyBudgetUsd: z48.number().positive().optional(),
10781
+ dailyBudgetUsd: z51.number().positive().optional(),
10151
10782
  /** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
10152
10783
  * ad set). TikTok takes only the first entry. */
10153
- targetCountries: z48.array(z48.string().length(2)).max(1).optional(),
10154
- campaignName: z48.string().trim().min(1).max(100).optional()
10784
+ targetCountries: z51.array(z51.string().length(2)).max(1).optional(),
10785
+ campaignName: z51.string().trim().min(1).max(100).optional()
10155
10786
  });
10156
10787
  var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
10157
- var MetaInput = z48.object({
10158
- taskId: z48.string().min(1),
10788
+ var MetaInput = z51.object({
10789
+ taskId: z51.string().min(1),
10790
+ /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
10791
+ portfolioId: z51.string().trim().min(1),
10792
+ /** Exact Facebook Page selected from `list_meta_destinations`. */
10793
+ pageId: z51.string().trim().min(1),
10159
10794
  adAccountId: McpAdAccountId,
10160
- pixelId: z48.string().min(1).optional(),
10161
- landingUrl: z48.string().url().optional(),
10795
+ pixelId: z51.string().min(1).optional(),
10796
+ landingUrl: z51.string().url().optional(),
10162
10797
  /** Select which angles to publish by 0-based index. Omit to publish all
10163
10798
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
10164
- angleIndexes: z48.array(z48.number().int().nonnegative()).optional(),
10799
+ angleIndexes: z51.array(z51.number().int().nonnegative()).optional(),
10165
10800
  campaignConfig: McpCampaignConfig.optional(),
10166
10801
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
10167
10802
  * uses the auto-generated copy attached to the task. Capped at 500 chars
10168
10803
  * (Meta's primary-text limit). */
10169
- primaryText: z48.string().trim().min(1).max(500).optional(),
10804
+ primaryText: z51.string().trim().min(1).max(500).optional(),
10170
10805
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
10171
10806
  * (wa.me) instead of a landing page. */
10172
- phoneNumber: z48.string().min(8).max(32).optional()
10807
+ phoneNumber: z51.string().min(8).max(32).optional()
10173
10808
  });
10174
- var GroupedTaskSource = z48.object({
10175
- type: z48.literal("task"),
10176
- taskId: z48.string().min(1),
10177
- angleIndex: z48.number().int().min(0).optional(),
10178
- videoVariant: z48.enum([
10809
+ var GroupedTaskSource = z51.object({
10810
+ type: z51.literal("task"),
10811
+ taskId: z51.string().min(1),
10812
+ angleIndex: z51.number().int().min(0).optional(),
10813
+ videoVariant: z51.enum([
10179
10814
  "with_subtitles",
10180
10815
  "without_subtitles"
10181
10816
  ]).optional(),
10182
- imageVariantIndex: z48.number().int().min(0).optional()
10183
- });
10184
- var GroupedMediaSource = z48.object({
10185
- type: z48.literal("media"),
10186
- url: z48.string().url().optional(),
10187
- path: z48.string().min(1).optional(),
10188
- thumbnailUrl: z48.string().url().optional(),
10189
- thumbnailPath: z48.string().min(1).optional()
10817
+ imageVariantIndex: z51.number().int().min(0).optional()
10818
+ });
10819
+ var GroupedMediaSource = z51.object({
10820
+ type: z51.literal("media"),
10821
+ 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."),
10822
+ 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".'),
10823
+ thumbnailUrl: z51.string().url().optional(),
10824
+ thumbnailPath: z51.string().min(1).optional()
10190
10825
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
10191
10826
  message: "media source needs exactly one of `url` or `path`"
10192
10827
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
10193
10828
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
10194
10829
  });
10195
- var GroupedCreative = z48.object({
10196
- type: z48.enum([
10830
+ var GroupedStagedMediaSource = z51.object({
10831
+ type: z51.literal("staged"),
10832
+ /** Returned by check_meta_media_upload after the user selects a local file. */
10833
+ stagingAssetId: z51.string().uuid(),
10834
+ thumbnailAssetId: z51.string().uuid().optional()
10835
+ });
10836
+ var GroupedCreative = z51.object({
10837
+ type: z51.enum([
10197
10838
  "image",
10198
10839
  "video"
10199
10840
  ]),
10200
- source: z48.union([
10841
+ source: z51.union([
10201
10842
  GroupedTaskSource,
10202
- GroupedMediaSource
10843
+ GroupedMediaSource,
10844
+ GroupedStagedMediaSource
10203
10845
  ]),
10204
- title: z48.string().max(120).optional(),
10205
- description: z48.string().max(500).optional(),
10846
+ title: z51.string().max(120).optional(),
10847
+ description: z51.string().max(500).optional(),
10206
10848
  cta: MetaCtaType.optional(),
10207
- creativeId: z48.string().min(1).optional()
10849
+ creativeId: z51.string().min(1).optional()
10208
10850
  });
10209
- var GroupedCampaignConfig = z48.object({
10851
+ var GroupedCampaignConfig = z51.object({
10210
10852
  campaignType: MetaCampaignType.default("sales"),
10211
10853
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
10212
10854
  budgetMode: MetaBudgetMode.default("campaign"),
10213
- campaignDailyBudget: z48.number().int().positive().optional(),
10214
- adSets: z48.array(z48.object({
10215
- tempId: z48.string().min(1).max(80),
10216
- name: z48.string().min(1).max(100),
10217
- dailyBudget: z48.number().int().positive().optional(),
10218
- creativeIds: z48.array(z48.string().min(1)).min(1)
10855
+ campaignDailyBudget: z51.number().int().positive().optional(),
10856
+ adSets: z51.array(z51.object({
10857
+ tempId: z51.string().min(1).max(80),
10858
+ name: z51.string().min(1).max(100),
10859
+ dailyBudget: z51.number().int().positive().optional(),
10860
+ creativeIds: z51.array(z51.string().min(1)).min(1)
10219
10861
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
10220
10862
  /**
10221
10863
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -10223,22 +10865,22 @@ var GroupedCampaignConfig = z48.object({
10223
10865
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
10224
10866
  * the API since #849 — unreachable from this tool.
10225
10867
  */
10226
- destination: z48.discriminatedUnion("type", [
10227
- z48.object({
10228
- type: z48.literal("landing"),
10229
- landingUrl: z48.string().url()
10868
+ destination: z51.discriminatedUnion("type", [
10869
+ z51.object({
10870
+ type: z51.literal("landing"),
10871
+ landingUrl: z51.string().url()
10230
10872
  }),
10231
- z48.object({
10232
- type: z48.literal("whatsapp"),
10233
- phoneNumber: z48.string().min(8).max(32)
10873
+ z51.object({
10874
+ type: z51.literal("whatsapp"),
10875
+ phoneNumber: z51.string().min(8).max(32)
10234
10876
  })
10235
10877
  ]),
10236
- saveAsDefault: z48.boolean().default(false),
10878
+ saveAsDefault: z51.boolean().default(false),
10237
10879
  budgetPolicy: MetaBudgetPolicy.optional()
10238
10880
  }).superRefine((value, ctx) => {
10239
10881
  if (value.budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
10240
10882
  ctx.addIssue({
10241
- code: z48.ZodIssueCode.custom,
10883
+ code: z51.ZodIssueCode.custom,
10242
10884
  path: [
10243
10885
  "campaignDailyBudget"
10244
10886
  ],
@@ -10249,7 +10891,7 @@ var GroupedCampaignConfig = z48.object({
10249
10891
  value.adSets.forEach((adSet, index) => {
10250
10892
  if (adSet.dailyBudget === void 0) {
10251
10893
  ctx.addIssue({
10252
- code: z48.ZodIssueCode.custom,
10894
+ code: z51.ZodIssueCode.custom,
10253
10895
  path: [
10254
10896
  "adSets",
10255
10897
  index,
@@ -10261,22 +10903,26 @@ var GroupedCampaignConfig = z48.object({
10261
10903
  });
10262
10904
  }
10263
10905
  });
10264
- var PublishCreativesToMetaInputBase = z48.object({
10265
- creatives: z48.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10906
+ var PublishCreativesToMetaInputBase = z51.object({
10907
+ creatives: z51.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10908
+ /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
10909
+ portfolioId: z51.string().trim().min(1),
10910
+ /** Exact Facebook Page selected from `list_meta_destinations`. */
10911
+ pageId: z51.string().trim().min(1),
10266
10912
  adAccountId: McpAdAccountId,
10267
- pixelId: z48.string().min(1).optional(),
10913
+ pixelId: z51.string().min(1).optional(),
10268
10914
  /** Required unless `campaignConfig.destination` is whatsapp — see the
10269
10915
  * superRefine below. Mirrors the API's `UploadToMetaRequest`. */
10270
- landingUrl: z48.string().url().optional(),
10271
- targetCountry: z48.string().length(2),
10272
- campaignName: z48.string().trim().min(1).max(100),
10916
+ landingUrl: z51.string().url().optional(),
10917
+ targetCountry: z51.string().length(2),
10918
+ campaignName: z51.string().trim().min(1).max(100),
10273
10919
  campaignConfig: GroupedCampaignConfig.optional()
10274
10920
  });
10275
10921
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
10276
10922
  const destination = value.campaignConfig?.destination;
10277
10923
  if (destination?.type !== "whatsapp" && !value.landingUrl) {
10278
10924
  ctx.addIssue({
10279
- code: z48.ZodIssueCode.custom,
10925
+ code: z51.ZodIssueCode.custom,
10280
10926
  path: [
10281
10927
  "landingUrl"
10282
10928
  ],
@@ -10285,7 +10931,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10285
10931
  }
10286
10932
  if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
10287
10933
  ctx.addIssue({
10288
- code: z48.ZodIssueCode.custom,
10934
+ code: z51.ZodIssueCode.custom,
10289
10935
  path: [
10290
10936
  "campaignConfig",
10291
10937
  "destination",
@@ -10295,46 +10941,50 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10295
10941
  });
10296
10942
  }
10297
10943
  });
10298
- var PresignUploadResponseSchema = z48.object({
10299
- uploadUrl: z48.string().url().refine((value) => {
10944
+ var PresignUploadResponseSchema = z51.object({
10945
+ uploadUrl: z51.string().url().refine((value) => {
10300
10946
  const url = new URL(value);
10301
10947
  return !url.username && !url.password && url.protocol === "https:";
10302
10948
  }, "presigned upload URL must be HTTPS without embedded credentials"),
10303
- publicUrl: z48.string().url().refine((value) => {
10949
+ publicUrl: z51.string().url().refine((value) => {
10304
10950
  const url = new URL(value);
10305
10951
  return !url.username && !url.password;
10306
- }, "public media URL must not contain embedded credentials")
10952
+ }, "public media URL must not contain embedded credentials"),
10953
+ stagingAssetId: z51.string().uuid()
10307
10954
  });
10308
- async function uploadResolvedMedia(api, media) {
10309
- if (media.url) return media.url;
10955
+ async function uploadResolvedMedia(api, media, assetRole) {
10310
10956
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
10311
10957
  const presigned = PresignUploadResponseSchema.parse(await api.postJson("/v1/ads/presign-upload", {
10312
10958
  contentType: media.mimetype,
10313
10959
  fileName: media.filename,
10314
- fileSize: media.buffer.byteLength
10960
+ fileSize: media.buffer.byteLength,
10961
+ purpose: "meta_staging",
10962
+ assetRole
10315
10963
  }));
10316
10964
  await api.putBytes(presigned.uploadUrl, media.buffer, media.mimetype);
10317
- return presigned.publicUrl;
10965
+ return {
10966
+ stagingAssetId: presigned.stagingAssetId
10967
+ };
10318
10968
  }
10319
10969
  __name(uploadResolvedMedia, "uploadResolvedMedia");
10320
- async function resolveAndUploadMedia(api, ref, expectedKind, transport) {
10970
+ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transport) {
10321
10971
  const media = await resolveMediaRef(ref, {
10322
10972
  transport,
10323
10973
  expectedKind
10324
10974
  });
10325
- return uploadResolvedMedia(api, media);
10975
+ return uploadResolvedMedia(api, media, assetRole);
10326
10976
  }
10327
10977
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
10328
- var TikTokInput = z48.object({
10329
- taskId: z48.string().min(1),
10330
- advertiserId: z48.string().min(1),
10331
- pixelId: z48.string().min(1).optional(),
10332
- landingUrl: z48.string().url().optional(),
10333
- angleIndexes: z48.array(z48.number().int().nonnegative()).optional(),
10978
+ var TikTokInput = z51.object({
10979
+ taskId: z51.string().min(1),
10980
+ advertiserId: z51.string().min(1),
10981
+ pixelId: z51.string().min(1).optional(),
10982
+ landingUrl: z51.string().url().optional(),
10983
+ angleIndexes: z51.array(z51.number().int().nonnegative()).optional(),
10334
10984
  campaignConfig: McpCampaignConfig.optional(),
10335
10985
  /** Override the ad's description (TikTok's body copy). If omitted,
10336
10986
  * ProductMaker uses the auto-generated copy attached to the task. */
10337
- primaryText: z48.string().trim().min(1).max(500).optional()
10987
+ primaryText: z51.string().trim().min(1).max(500).optional()
10338
10988
  });
10339
10989
  function registerPublishTools(server, api, opts = {}) {
10340
10990
  const transport = opts.transport ?? "http";
@@ -10373,7 +11023,7 @@ function registerPublishTools(server, api, opts = {}) {
10373
11023
  throw translateError(e);
10374
11024
  }
10375
11025
  });
10376
- 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 }) => {
11026
+ 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 }) => {
10377
11027
  try {
10378
11028
  const body = {
10379
11029
  ...rest
@@ -10401,7 +11051,7 @@ function registerPublishTools(server, api, opts = {}) {
10401
11051
  throw translateError(e);
10402
11052
  }
10403
11053
  });
10404
- 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) => {
11054
+ 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) => {
10405
11055
  try {
10406
11056
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
10407
11057
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -10427,25 +11077,44 @@ function registerPublishTools(server, api, opts = {}) {
10427
11077
  });
10428
11078
  continue;
10429
11079
  }
10430
- const mediaUrl = await resolveAndUploadMedia(api, {
11080
+ if (creative.source.type === "staged") {
11081
+ creatives.push({
11082
+ type: creative.type,
11083
+ source: {
11084
+ type: "media",
11085
+ mediaAssetId: creative.source.stagingAssetId,
11086
+ ...creative.source.thumbnailAssetId ? {
11087
+ thumbnailAssetId: creative.source.thumbnailAssetId
11088
+ } : {}
11089
+ },
11090
+ title: creative.title,
11091
+ description: creative.description,
11092
+ cta: creative.cta,
11093
+ creativeId
11094
+ });
11095
+ continue;
11096
+ }
11097
+ const stagedMedia = await resolveAndUploadMedia(api, {
10431
11098
  url: creative.source.url,
10432
11099
  path: creative.source.path
10433
- }, creative.type, transport);
10434
- let thumbnailUrl;
11100
+ }, creative.type, "media", transport);
11101
+ let stagedThumbnail;
10435
11102
  if (creative.type === "video") {
10436
11103
  if (creative.source.thumbnailUrl || creative.source.thumbnailPath) {
10437
- thumbnailUrl = await resolveAndUploadMedia(api, {
11104
+ stagedThumbnail = await resolveAndUploadMedia(api, {
10438
11105
  url: creative.source.thumbnailUrl,
10439
11106
  path: creative.source.thumbnailPath
10440
- }, "image", transport);
11107
+ }, "image", "thumbnail", transport);
10441
11108
  }
10442
11109
  }
10443
11110
  creatives.push({
10444
11111
  type: creative.type,
10445
11112
  source: {
10446
11113
  type: "media",
10447
- mediaUrl,
10448
- thumbnailUrl
11114
+ mediaAssetId: stagedMedia.stagingAssetId,
11115
+ ...stagedThumbnail ? {
11116
+ thumbnailAssetId: stagedThumbnail.stagingAssetId
11117
+ } : {}
10449
11118
  },
10450
11119
  title: creative.title,
10451
11120
  description: creative.description,
@@ -10455,6 +11124,8 @@ function registerPublishTools(server, api, opts = {}) {
10455
11124
  }
10456
11125
  const r = await api.postJson("/v1/facebook/campaigns/from-tasks", {
10457
11126
  creatives,
11127
+ portfolioId: input.portfolioId,
11128
+ pageId: input.pageId,
10458
11129
  adAccountId: input.adAccountId,
10459
11130
  pixelId: input.pixelId,
10460
11131
  landingUrl: input.landingUrl,
@@ -10544,14 +11215,146 @@ function toTikTokCampaignConfig(cfg) {
10544
11215
  }
10545
11216
  __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
10546
11217
 
11218
+ // src/tools/meta-upload.ts
11219
+ import { z as z52 } from "zod";
11220
+ var MetaUploadSessionInputBase = z52.object({
11221
+ mediaKind: z52.enum([
11222
+ "image",
11223
+ "video",
11224
+ "mixed"
11225
+ ]).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."),
11226
+ assetRole: z52.enum([
11227
+ "media",
11228
+ "thumbnail"
11229
+ ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail.")
11230
+ });
11231
+ var OpenAiFileReference = z52.object({
11232
+ download_url: z52.string().url(),
11233
+ file_id: z52.string().min(1),
11234
+ mime_type: z52.string().min(1).optional(),
11235
+ file_name: z52.string().min(1).optional()
11236
+ }).strict();
11237
+ var MetaUploadSessionToolInputShape = {
11238
+ ...MetaUploadSessionInputBase.shape,
11239
+ 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.")
11240
+ };
11241
+ var MetaUploadSessionToolInput = z52.object(MetaUploadSessionToolInputShape).superRefine((value, ctx) => {
11242
+ if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
11243
+ ctx.addIssue({
11244
+ code: z52.ZodIssueCode.custom,
11245
+ path: [
11246
+ "mediaKind"
11247
+ ],
11248
+ message: "Meta thumbnails must be images"
11249
+ });
11250
+ }
11251
+ });
11252
+ var MetaUploadSessionResponse2 = z52.object({
11253
+ sessionId: z52.string().uuid(),
11254
+ uploadPageUrl: z52.string().url(),
11255
+ mediaKind: z52.enum([
11256
+ "image",
11257
+ "video",
11258
+ "mixed"
11259
+ ]),
11260
+ assetRole: z52.enum([
11261
+ "media",
11262
+ "thumbnail"
11263
+ ]),
11264
+ maxFiles: z52.number().int().positive(),
11265
+ expiresAt: z52.string().datetime()
11266
+ });
11267
+ var MetaUploadStatusInput = z52.object({
11268
+ sessionId: z52.string().uuid().describe("The sessionId returned by request_meta_media_upload.")
11269
+ });
11270
+ var MetaUploadStatusResponse = z52.object({
11271
+ sessionId: z52.string().uuid(),
11272
+ status: z52.enum([
11273
+ "pending",
11274
+ "uploaded"
11275
+ ]),
11276
+ assets: z52.array(z52.object({
11277
+ stagingAssetId: z52.string().uuid(),
11278
+ mediaKind: z52.enum([
11279
+ "image",
11280
+ "video"
11281
+ ])
11282
+ })).max(10).default([]),
11283
+ stagingAssetId: z52.string().uuid().optional()
11284
+ });
11285
+ function uploadSessionText(result) {
11286
+ const mediaLabel = result.mediaKind === "mixed" ? "archivos" : result.mediaKind === "video" ? "video" : "imagen";
11287
+ return {
11288
+ content: [
11289
+ {
11290
+ type: "text",
11291
+ text: [
11292
+ `### Sube tu ${mediaLabel} a ProductMaker`,
11293
+ `[Abrir carga segura](${result.uploadPageUrl})`,
11294
+ `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.`,
11295
+ `sessionId: \`${result.sessionId}\``,
11296
+ `Este enlace expira: ${result.expiresAt}`
11297
+ ].join("\n\n")
11298
+ }
11299
+ ]
11300
+ };
11301
+ }
11302
+ __name(uploadSessionText, "uploadSessionText");
11303
+ function registerMetaUploadTools(server, api) {
11304
+ server.registerTool("request_meta_media_upload", {
11305
+ title: "Solicitar carga segura de Meta",
11306
+ 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.",
11307
+ inputSchema: MetaUploadSessionToolInputShape,
11308
+ outputSchema: MetaUploadSessionResponse2,
11309
+ _meta: {
11310
+ ui: {
11311
+ resourceUri: META_UPLOAD_APP_RESOURCE_URI,
11312
+ visibility: [
11313
+ "model",
11314
+ "app"
11315
+ ]
11316
+ },
11317
+ "openai/outputTemplate": META_UPLOAD_APP_RESOURCE_URI,
11318
+ "openai/widgetAccessible": true,
11319
+ "openai/fileParams": [
11320
+ "files"
11321
+ ],
11322
+ "openai/toolInvocation/invoking": "Preparando carga segura\u2026",
11323
+ "openai/toolInvocation/invoked": "Carga segura lista"
11324
+ }
11325
+ }, async (rawInput) => {
11326
+ try {
11327
+ const input = MetaUploadSessionToolInput.parse(rawInput);
11328
+ const { files: _files, ...sessionInput } = input;
11329
+ const result = MetaUploadSessionResponse2.parse(await api.postJson("/v1/ads/meta-upload-sessions", sessionInput));
11330
+ return {
11331
+ structuredContent: result,
11332
+ ...uploadSessionText(result)
11333
+ };
11334
+ } catch (error) {
11335
+ throw translateError(error);
11336
+ }
11337
+ });
11338
+ 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) => {
11339
+ try {
11340
+ const input = MetaUploadStatusInput.parse(rawInput);
11341
+ const result = MetaUploadStatusResponse.parse(await api.getJson(`/v1/ads/meta-upload-sessions/${encodeURIComponent(input.sessionId)}`));
11342
+ return asText(result);
11343
+ } catch (error) {
11344
+ throw translateError(error);
11345
+ }
11346
+ });
11347
+ }
11348
+ __name(registerMetaUploadTools, "registerMetaUploadTools");
11349
+
10547
11350
  // src/tools/campaigns.ts
10548
- import { z as z49 } from "zod";
10549
- var MetaStatusInput = z49.object({
10550
- campaignId: z49.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
11351
+ import { z as z53 } from "zod";
11352
+ var MetaStatusInput = z53.object({
11353
+ campaignId: z53.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
10551
11354
  });
10552
- var TikTokStatusInput = z49.object({
10553
- campaignId: z49.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
10554
- advertiserId: z49.string().min(1).describe("The same advertiserId used to publish the campaign.")
11355
+ var TikTokStatusInput = z53.object({
11356
+ campaignId: z53.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
11357
+ advertiserId: z53.string().min(1).describe("The same advertiserId used to publish the campaign.")
10555
11358
  });
10556
11359
  function registerCampaignTools(server, api) {
10557
11360
  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) => {
@@ -10648,10 +11451,10 @@ function renderTikTokStatusMarkdown(r) {
10648
11451
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
10649
11452
 
10650
11453
  // ../../packages/product-economics/src/product-costing.ts
10651
- import { z as z51 } from "zod";
11454
+ import { z as z55 } from "zod";
10652
11455
 
10653
11456
  // ../../packages/product-economics/src/product-costing-countries.ts
10654
- import { z as z50 } from "zod";
11457
+ import { z as z54 } from "zod";
10655
11458
 
10656
11459
  // ../../packages/product-economics/src/product-costing-shipping-references.json
10657
11460
  var product_costing_shipping_references_default = {
@@ -10715,21 +11518,21 @@ var product_costing_shipping_references_default = {
10715
11518
  };
10716
11519
 
10717
11520
  // ../../packages/product-economics/src/product-costing-countries.ts
10718
- var ShippingReferenceSchema = z50.object({
10719
- reference: z50.number().positive(),
10720
- min: z50.number().nonnegative(),
10721
- max: z50.number().positive(),
10722
- updatedAt: z50.string().date(),
10723
- confidence: z50.enum([
11521
+ var ShippingReferenceSchema = z54.object({
11522
+ reference: z54.number().positive(),
11523
+ min: z54.number().nonnegative(),
11524
+ max: z54.number().positive(),
11525
+ updatedAt: z54.string().date(),
11526
+ confidence: z54.enum([
10724
11527
  "high",
10725
11528
  "medium",
10726
11529
  "medium-low"
10727
11530
  ]),
10728
- sources: z50.array(z50.string().url().startsWith("https://")).min(1).max(3)
11531
+ sources: z54.array(z54.string().url().startsWith("https://")).min(1).max(3)
10729
11532
  }).superRefine(({ reference, min, max }, context) => {
10730
11533
  if (min > reference || reference > max) {
10731
11534
  context.addIssue({
10732
- code: z50.ZodIssueCode.custom,
11535
+ code: z54.ZodIssueCode.custom,
10733
11536
  message: "shipping_reference_outside_range",
10734
11537
  path: [
10735
11538
  "reference"
@@ -10737,7 +11540,7 @@ var ShippingReferenceSchema = z50.object({
10737
11540
  });
10738
11541
  }
10739
11542
  });
10740
- var SHIPPING_REFERENCES = z50.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
11543
+ var SHIPPING_REFERENCES = z54.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
10741
11544
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
10742
11545
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
10743
11546
  "MX",
@@ -10919,18 +11722,18 @@ function getProductCostingCountryName(country) {
10919
11722
  __name(getProductCostingCountryName, "getProductCostingCountryName");
10920
11723
 
10921
11724
  // ../../packages/product-economics/src/product-costing.ts
10922
- var ProductCostingInputSchema = z51.object({
10923
- country: z51.string().refine(isProductCostingCountryCode, "unsupported_country"),
10924
- supplierCost: z51.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
10925
- shippingCost: z51.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
10926
- returnRate: z51.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
10927
- cancellationRate: z51.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
10928
- adsManagerCpa: z51.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
10929
- desiredMargin: z51.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
11725
+ var ProductCostingInputSchema = z55.object({
11726
+ country: z55.string().refine(isProductCostingCountryCode, "unsupported_country"),
11727
+ supplierCost: z55.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
11728
+ shippingCost: z55.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
11729
+ returnRate: z55.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11730
+ cancellationRate: z55.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
11731
+ adsManagerCpa: z55.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
11732
+ desiredMargin: z55.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
10930
11733
  }).superRefine(({ returnRate, cancellationRate }, context) => {
10931
11734
  if (returnRate + cancellationRate >= 1) {
10932
11735
  context.addIssue({
10933
- code: z51.ZodIssueCode.custom,
11736
+ code: z55.ZodIssueCode.custom,
10934
11737
  message: "delivery_rate_positive",
10935
11738
  path: [
10936
11739
  "cancellationRate"
@@ -10997,14 +11800,14 @@ function calculateProductCosting(rawInput) {
10997
11800
  __name(calculateProductCosting, "calculateProductCosting");
10998
11801
 
10999
11802
  // src/tools/product-costing.ts
11000
- import { z as z52 } from "zod";
11001
- var optionalAmount = /* @__PURE__ */ __name((description) => z52.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
11002
- var optionalPercent = /* @__PURE__ */ __name((description) => z52.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
11003
- var ProductCostingToolInput = z52.object({
11004
- country: z52.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
11803
+ import { z as z56 } from "zod";
11804
+ var optionalAmount = /* @__PURE__ */ __name((description) => z56.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
11805
+ var optionalPercent = /* @__PURE__ */ __name((description) => z56.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
11806
+ var ProductCostingToolInput = z56.object({
11807
+ country: z56.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
11005
11808
  message: "Pa\xEDs no soportado por ProductMaker."
11006
11809
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
11007
- supplierCost: z52.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
11810
+ 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."),
11008
11811
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
11009
11812
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
11010
11813
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -11136,7 +11939,7 @@ function formatPercent(value) {
11136
11939
  __name(formatPercent, "formatPercent");
11137
11940
 
11138
11941
  // src/tools/product-research.ts
11139
- import { z as z53 } from "zod";
11942
+ import { z as z57 } from "zod";
11140
11943
 
11141
11944
  // src/tools/product-research-idempotency.ts
11142
11945
  import { createHash as createHash2 } from "crypto";
@@ -11252,17 +12055,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
11252
12055
  ]);
11253
12056
  var POLL_INTERVAL_MS = 5e3;
11254
12057
  var MAX_WAIT_SECONDS = 120;
11255
- var FindInput = z53.object({
12058
+ var FindInput = z57.object({
11256
12059
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
11257
12060
  categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
11258
- confirmed: z53.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
11259
- waitSeconds: z53.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
12061
+ 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.`),
12062
+ 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.")
11260
12063
  });
11261
- var GetInput = z53.object({
11262
- runId: z53.string().uuid().describe("ID del run devuelto por find_winning_products.")
12064
+ var GetInput = z57.object({
12065
+ runId: z57.string().uuid().describe("ID del run devuelto por find_winning_products.")
11263
12066
  });
11264
- var ListInput = z53.object({
11265
- limit: z53.number().int().min(1).max(20).default(10)
12067
+ var ListInput = z57.object({
12068
+ limit: z57.number().int().min(1).max(20).default(10)
11266
12069
  });
11267
12070
  function textResult(text) {
11268
12071
  return {
@@ -11354,14 +12157,14 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
11354
12157
  __name(registerProductResearchTools, "registerProductResearchTools");
11355
12158
 
11356
12159
  // src/tools/dropshipping-projects.ts
11357
- import { z as z54 } from "zod";
11358
- var ProjectId = z54.string().uuid();
11359
- var DropshippingProjectStatus = z54.enum([
12160
+ import { z as z58 } from "zod";
12161
+ var ProjectId = z58.string().uuid();
12162
+ var DropshippingProjectStatus = z58.enum([
11360
12163
  "active",
11361
12164
  "completed",
11362
12165
  "archived"
11363
12166
  ]);
11364
- var DropshippingProjectPhase = z54.enum([
12167
+ var DropshippingProjectPhase = z58.enum([
11365
12168
  "research",
11366
12169
  "costing",
11367
12170
  "media",
@@ -11370,21 +12173,21 @@ var DropshippingProjectPhase = z54.enum([
11370
12173
  "advertising",
11371
12174
  "complete"
11372
12175
  ]);
11373
- var DropshippingRewardMilestone = z54.enum([
12176
+ var DropshippingRewardMilestone = z58.enum([
11374
12177
  "product_selected",
11375
12178
  "costing_saved",
11376
12179
  "product_media_ready",
11377
12180
  "creatives_completed",
11378
12181
  "campaign_created"
11379
12182
  ]);
11380
- var TargetCountry = z54.enum([
12183
+ var TargetCountry = z58.enum([
11381
12184
  "CO",
11382
12185
  "MX",
11383
12186
  "PE",
11384
12187
  "EC",
11385
12188
  "CL"
11386
12189
  ]);
11387
- var ResearchCategory = z54.enum([
12190
+ var ResearchCategory = z58.enum([
11388
12191
  "auto",
11389
12192
  "automotive",
11390
12193
  "kitchen",
@@ -11399,7 +12202,7 @@ var ResearchCategory = z54.enum([
11399
12202
  "garden",
11400
12203
  "cleaning"
11401
12204
  ]);
11402
- var HttpUrl3 = z54.string().url().max(2e3).refine((value) => {
12205
+ var HttpUrl3 = z58.string().url().max(2e3).refine((value) => {
11403
12206
  try {
11404
12207
  return [
11405
12208
  "http:",
@@ -11409,46 +12212,46 @@ var HttpUrl3 = z54.string().url().max(2e3).refine((value) => {
11409
12212
  return false;
11410
12213
  }
11411
12214
  }, "URL must use HTTP(S)");
11412
- var ResearchCandidateOrigin = z54.object({
11413
- type: z54.literal("research_candidate"),
12215
+ var ResearchCandidateOrigin = z58.object({
12216
+ type: z58.literal("research_candidate"),
11414
12217
  runId: ProjectId,
11415
- candidateSourceId: z54.string().min(1).max(160)
12218
+ candidateSourceId: z58.string().min(1).max(160)
11416
12219
  });
11417
- var TaskOrigin = z54.object({
11418
- type: z54.literal("task"),
12220
+ var TaskOrigin = z58.object({
12221
+ type: z58.literal("task"),
11419
12222
  taskId: ProjectId
11420
12223
  });
11421
- var ManualOrigin = z54.object({
11422
- type: z54.literal("manual"),
12224
+ var ManualOrigin = z58.object({
12225
+ type: z58.literal("manual"),
11423
12226
  clientRequestId: ProjectId,
11424
- country: z54.string().min(2).max(3).optional()
12227
+ country: z58.string().min(2).max(3).optional()
11425
12228
  });
11426
- var StartProjectInput = z54.object({
11427
- origin: z54.discriminatedUnion("type", [
12229
+ var StartProjectInput = z58.object({
12230
+ origin: z58.discriminatedUnion("type", [
11428
12231
  ResearchCandidateOrigin,
11429
12232
  TaskOrigin,
11430
12233
  ManualOrigin
11431
12234
  ]),
11432
- productName: z54.string().trim().min(1).max(240).optional(),
12235
+ productName: z58.string().trim().min(1).max(240).optional(),
11433
12236
  productUrl: HttpUrl3.optional(),
11434
- country: z54.string().min(2).max(3).optional(),
11435
- languageCode: z54.string().min(2).max(16).optional()
12237
+ country: z58.string().min(2).max(3).optional(),
12238
+ languageCode: z58.string().min(2).max(16).optional()
11436
12239
  }).strict();
11437
- var CostProjectInput = z54.object({
12240
+ var CostProjectInput = z58.object({
11438
12241
  projectId: ProjectId,
11439
- version: z54.number().int().positive(),
11440
- country: z54.string().trim().min(2).max(3),
11441
- supplierCost: z54.number().finite().positive(),
11442
- shippingCost: z54.number().finite().nonnegative(),
11443
- returnRate: z54.number().finite().min(0).lt(1),
11444
- cancellationRate: z54.number().finite().min(0).lt(1),
11445
- adsManagerCpa: z54.number().finite().nonnegative(),
11446
- desiredMargin: z54.number().finite().min(0).lt(1)
11447
- });
11448
- var GuideInput = z54.object({
12242
+ version: z58.number().int().positive(),
12243
+ country: z58.string().trim().min(2).max(3),
12244
+ supplierCost: z58.number().finite().positive(),
12245
+ shippingCost: z58.number().finite().nonnegative(),
12246
+ returnRate: z58.number().finite().min(0).lt(1),
12247
+ cancellationRate: z58.number().finite().min(0).lt(1),
12248
+ adsManagerCpa: z58.number().finite().nonnegative(),
12249
+ desiredMargin: z58.number().finite().min(0).lt(1)
12250
+ });
12251
+ var GuideInput = z58.object({
11449
12252
  projectId: ProjectId.optional()
11450
12253
  }).strict();
11451
- var DropshippingProjectNextAction = z54.discriminatedUnion("kind", [
12254
+ var DropshippingProjectNextAction = z58.discriminatedUnion("kind", [
11452
12255
  "cost_product",
11453
12256
  "provide_product_media",
11454
12257
  "generate_creatives",
@@ -11462,78 +12265,78 @@ var DropshippingProjectNextAction = z54.discriminatedUnion("kind", [
11462
12265
  "create_meta_campaign",
11463
12266
  "repair_meta_campaign",
11464
12267
  "review_paused_campaign"
11465
- ].map((kind) => z54.object({
11466
- kind: z54.literal(kind),
11467
- href: z54.string().min(1)
12268
+ ].map((kind) => z58.object({
12269
+ kind: z58.literal(kind),
12270
+ href: z58.string().min(1)
11468
12271
  })));
11469
- var CostingInputSchema = z54.object({
11470
- country: z54.string().min(2).max(3),
11471
- supplierCost: z54.number().finite().positive(),
11472
- shippingCost: z54.number().finite().nonnegative(),
11473
- returnRate: z54.number().finite().min(0).lt(1),
11474
- cancellationRate: z54.number().finite().min(0).lt(1),
11475
- adsManagerCpa: z54.number().finite().nonnegative(),
11476
- desiredMargin: z54.number().finite().min(0).lt(1)
12272
+ var CostingInputSchema = z58.object({
12273
+ country: z58.string().min(2).max(3),
12274
+ supplierCost: z58.number().finite().positive(),
12275
+ shippingCost: z58.number().finite().nonnegative(),
12276
+ returnRate: z58.number().finite().min(0).lt(1),
12277
+ cancellationRate: z58.number().finite().min(0).lt(1),
12278
+ adsManagerCpa: z58.number().finite().nonnegative(),
12279
+ desiredMargin: z58.number().finite().min(0).lt(1)
11477
12280
  }).superRefine(({ returnRate, cancellationRate }, context) => {
11478
12281
  if (returnRate + cancellationRate >= 1) context.addIssue({
11479
- code: z54.ZodIssueCode.custom,
12282
+ code: z58.ZodIssueCode.custom,
11480
12283
  path: [
11481
12284
  "cancellationRate"
11482
12285
  ]
11483
12286
  });
11484
12287
  });
11485
- var CostingResultSchema = z54.object({
11486
- country: z54.string().min(2).max(3),
11487
- deliveryRate: z54.number().finite(),
11488
- adjustedShippingCost: z54.number().finite(),
11489
- adjustedCpa: z54.number().finite(),
11490
- totalCost: z54.number().finite(),
11491
- suggestedPrice: z54.number().finite(),
11492
- baseCommercialPrice: z54.number().finite(),
11493
- minimumSafePrice: z54.number().finite(),
11494
- commercialPrice: z54.number().finite(),
11495
- comparisonPrice: z54.number().finite(),
11496
- estimatedProfit: z54.number().finite(),
11497
- actualMargin: z54.number().finite(),
11498
- breakEvenAdsManagerCpa: z54.number().finite(),
11499
- wasPriceAutoAdjusted: z54.boolean(),
11500
- profitabilityStatus: z54.literal("healthy")
11501
- });
11502
- var RewardProgressSchema = z54.object({
11503
- campaignKey: z54.literal("dropshipping-launch-v1"),
11504
- totalCredits: z54.number().int().positive(),
11505
- earnedCredits: z54.number().int().nonnegative(),
11506
- milestones: z54.array(z54.object({
12288
+ var CostingResultSchema = z58.object({
12289
+ country: z58.string().min(2).max(3),
12290
+ deliveryRate: z58.number().finite(),
12291
+ adjustedShippingCost: z58.number().finite(),
12292
+ adjustedCpa: z58.number().finite(),
12293
+ totalCost: z58.number().finite(),
12294
+ suggestedPrice: z58.number().finite(),
12295
+ baseCommercialPrice: z58.number().finite(),
12296
+ minimumSafePrice: z58.number().finite(),
12297
+ commercialPrice: z58.number().finite(),
12298
+ comparisonPrice: z58.number().finite(),
12299
+ estimatedProfit: z58.number().finite(),
12300
+ actualMargin: z58.number().finite(),
12301
+ breakEvenAdsManagerCpa: z58.number().finite(),
12302
+ wasPriceAutoAdjusted: z58.boolean(),
12303
+ profitabilityStatus: z58.literal("healthy")
12304
+ });
12305
+ var RewardProgressSchema = z58.object({
12306
+ campaignKey: z58.literal("dropshipping-launch-v1"),
12307
+ totalCredits: z58.number().int().positive(),
12308
+ earnedCredits: z58.number().int().nonnegative(),
12309
+ milestones: z58.array(z58.object({
11507
12310
  milestone: DropshippingRewardMilestone,
11508
- credits: z54.number().int().positive(),
11509
- available: z54.boolean().optional(),
11510
- earned: z54.boolean(),
12311
+ credits: z58.number().int().positive(),
12312
+ available: z58.boolean().optional(),
12313
+ earned: z58.boolean(),
11511
12314
  grantId: ProjectId.nullable(),
11512
- grantedAt: z54.string().datetime().nullable()
12315
+ grantedAt: z58.string().datetime().nullable()
11513
12316
  })).length(DropshippingRewardMilestone.options.length)
11514
12317
  });
11515
- var ProjectResponseSchema = z54.object({
12318
+ var ProjectResponseSchema = z58.object({
11516
12319
  id: ProjectId,
11517
12320
  taskId: ProjectId.nullable(),
11518
12321
  status: DropshippingProjectStatus,
11519
- originKey: z54.string().min(1),
12322
+ originKey: z58.string().min(1),
11520
12323
  researchRunId: ProjectId.nullable(),
11521
- candidateSourceId: z54.string().nullable(),
11522
- productName: z54.string().nullable(),
12324
+ candidateSourceId: z58.string().nullable(),
12325
+ productName: z58.string().nullable(),
11523
12326
  productUrl: HttpUrl3.nullable(),
11524
12327
  referenceImageUrl: HttpUrl3.nullable(),
11525
- targetCountry: z54.string().nullable(),
11526
- languageCode: z54.string().nullable(),
11527
- hasReferenceImage: z54.boolean(),
12328
+ targetCountry: z58.string().nullable(),
12329
+ languageCode: z58.string().nullable(),
12330
+ hasReferenceImage: z58.boolean(),
11528
12331
  costingInput: CostingInputSchema.nullable(),
11529
12332
  costingResult: CostingResultSchema.nullable(),
11530
- costingVersion: z54.number().int().positive().nullable(),
11531
- version: z54.number().int().positive(),
12333
+ costingVersion: z58.number().int().positive().nullable(),
12334
+ version: z58.number().int().positive(),
11532
12335
  phase: DropshippingProjectPhase,
11533
12336
  nextAction: DropshippingProjectNextAction.nullable(),
11534
12337
  rewards: RewardProgressSchema,
11535
- createdAt: z54.string().datetime(),
11536
- updatedAt: z54.string().datetime()
12338
+ createdAt: z58.string().datetime(),
12339
+ updatedAt: z58.string().datetime()
11537
12340
  }).passthrough();
11538
12341
  function projectText(raw) {
11539
12342
  const project = ProjectResponseSchema.parse(raw);
@@ -11618,6 +12421,7 @@ function createServer(opts) {
11618
12421
  }
11619
12422
  });
11620
12423
  const api = new ApiClient(apiBaseUrl, apiKey);
12424
+ registerMetaUploadAppResource(server);
11621
12425
  registerProductCostingTools(server);
11622
12426
  registerProductResearchTools(server, api);
11623
12427
  registerDropshippingProjectTools(server, api);
@@ -11631,9 +12435,11 @@ function createServer(opts) {
11631
12435
  registerCreativeVariantTools(server, api);
11632
12436
  registerScriptReviewTools(server, api);
11633
12437
  registerConnectionTools(server, api);
12438
+ registerActorTools(server, api);
11634
12439
  registerPublishTools(server, api, {
11635
12440
  transport
11636
12441
  });
12442
+ registerMetaUploadTools(server, api);
11637
12443
  registerCampaignTools(server, api);
11638
12444
  return server;
11639
12445
  }