@productmaker/mcp 11.5.3 → 11.5.5

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/README.md CHANGED
@@ -303,6 +303,11 @@ The `base64` field was removed. MCP JSON-RPC transports truncate large tool argu
303
303
 
304
304
  ## Version history
305
305
 
306
+ ### 11.5.4 — Limpieza protegida al recrear una landing de servicio
307
+
308
+ - La web usa el parámetro opcional `failedCreationOnly` en `DELETE /v1/service-pages/:id` para reemplazar una creación fallida sin borrar una landing generada ni una generación en curso.
309
+ - No cambia ninguna herramienta ni esquema público MCP. La versión patch cumple la sincronización obligatoria del repositorio para cambios publicables; el paquete npm se publicará tras el merge.
310
+
306
311
  ### 11.5.1 — Copy junto a cada pieza de servicios
307
312
 
308
313
  Los resultados de videos e imágenes de servicios incluyen texto para publicar
package/dist/http.js CHANGED
@@ -8403,7 +8403,9 @@ var CampaignCopyEstimationOptions = z40.object({
8403
8403
  /** Number of image creatives that can each use one vision analysis (0-10). */
8404
8404
  nImages: z40.number().int().min(0).max(10).default(0)
8405
8405
  });
8406
- var BrandExtractEstimationOptions = z40.object({}).strict();
8406
+ var BrandExtractEstimationOptions = z40.object({
8407
+ sourceUrl: z40.string().url().optional()
8408
+ }).strict();
8407
8409
  var PersonaSuggestEstimationOptions = z40.object({}).strict();
8408
8410
  var ChatParseEstimationOptions = z40.object({}).strict();
8409
8411
  var CodeLandingEditEstimationOptions = z40.object({}).strict();
@@ -9858,10 +9860,476 @@ var TikTokCreateCampaignResponseSchema = z52.object({
9858
9860
  var GEMINI_OMNI_FLAT_OUR_COST_CENTS = 84;
9859
9861
  var GEMINI_OMNI_FLAT_USER_PRICE_CENTS = 168;
9860
9862
 
9863
+ // ../../packages/types/src/public-host.ts
9864
+ function normalizeHostname(value) {
9865
+ const normalized = value.trim().toLowerCase().replace(/\.+$/, "");
9866
+ return normalized.startsWith("[") && normalized.endsWith("]") ? normalized.slice(1, -1) : normalized;
9867
+ }
9868
+ __name(normalizeHostname, "normalizeHostname");
9869
+ function parseIpv4(address) {
9870
+ const octets = address.split(".");
9871
+ if (octets.length !== 4 || !octets.every((octet) => /^(0|[1-9]\d{0,2})$/.test(octet))) {
9872
+ return null;
9873
+ }
9874
+ const parsedOctets = octets.map(Number);
9875
+ if (parsedOctets.some((octet) => octet > 255)) return null;
9876
+ const [first, second, third, fourth] = parsedOctets;
9877
+ if (first === void 0 || second === void 0 || third === void 0 || fourth === void 0) {
9878
+ return null;
9879
+ }
9880
+ return [
9881
+ first,
9882
+ second,
9883
+ third,
9884
+ fourth
9885
+ ];
9886
+ }
9887
+ __name(parseIpv4, "parseIpv4");
9888
+ function isPublicIpv4Octets([first, second, third]) {
9889
+ if (first === 0 || first === 10 || first === 127 || first >= 224) return false;
9890
+ if (first === 100 && second >= 64 && second <= 127) return false;
9891
+ if (first === 169 && second === 254) return false;
9892
+ if (first === 172 && second >= 16 && second <= 31) return false;
9893
+ if (first === 192 && second === 0 && (third === 0 || third === 2)) return false;
9894
+ if (first === 192 && second === 88 && third === 99) return false;
9895
+ if (first === 192 && second === 168) return false;
9896
+ if (first === 198 && (second === 18 || second === 19)) return false;
9897
+ if (first === 198 && second === 51 && third === 100) return false;
9898
+ if (first === 203 && second === 0 && third === 113) return false;
9899
+ return true;
9900
+ }
9901
+ __name(isPublicIpv4Octets, "isPublicIpv4Octets");
9902
+ function parseIpv6Hextets(address) {
9903
+ let normalized = address;
9904
+ const lastColon = normalized.lastIndexOf(":");
9905
+ const finalPart = normalized.slice(lastColon + 1);
9906
+ if (finalPart.includes(".")) {
9907
+ const octets = parseIpv4(finalPart);
9908
+ if (octets === null) return null;
9909
+ const [first, second, third, fourth] = octets;
9910
+ normalized = `${normalized.slice(0, lastColon + 1)}${(first << 8 | second).toString(16)}:${(third << 8 | fourth).toString(16)}`;
9911
+ }
9912
+ const parts = normalized.split("::");
9913
+ if (parts.length > 2) return null;
9914
+ const left = parts[0] ? parts[0].split(":") : [];
9915
+ const right = parts[1] ? parts[1].split(":") : [];
9916
+ const explicit = [];
9917
+ for (const part of [
9918
+ ...left,
9919
+ ...right
9920
+ ]) {
9921
+ if (!/^[\da-f]{1,4}$/i.test(part)) return null;
9922
+ explicit.push(Number.parseInt(part, 16));
9923
+ }
9924
+ if (parts.length === 1) return explicit.length === 8 ? explicit : null;
9925
+ if (explicit.length >= 8) return null;
9926
+ return [
9927
+ ...explicit.slice(0, left.length),
9928
+ ...Array(8 - explicit.length).fill(0),
9929
+ ...explicit.slice(left.length)
9930
+ ];
9931
+ }
9932
+ __name(parseIpv6Hextets, "parseIpv6Hextets");
9933
+ function matchesIpv6Prefix(hextets, prefix, prefixLength) {
9934
+ const completeHextets = Math.floor(prefixLength / 16);
9935
+ for (let index = 0; index < completeHextets; index += 1) {
9936
+ if (hextets[index] !== prefix[index]) return false;
9937
+ }
9938
+ const remainingBits = prefixLength % 16;
9939
+ if (remainingBits === 0) return true;
9940
+ const mask = 65535 << 16 - remainingBits & 65535;
9941
+ return ((hextets[completeHextets] ?? 0) & mask) === ((prefix[completeHextets] ?? 0) & mask);
9942
+ }
9943
+ __name(matchesIpv6Prefix, "matchesIpv6Prefix");
9944
+ function embeddedIpv4(hextets) {
9945
+ const seventh = hextets[6] ?? 0;
9946
+ const eighth = hextets[7] ?? 0;
9947
+ return [
9948
+ seventh >> 8,
9949
+ seventh & 255,
9950
+ eighth >> 8,
9951
+ eighth & 255
9952
+ ];
9953
+ }
9954
+ __name(embeddedIpv4, "embeddedIpv4");
9955
+ function isPublicIpv6(address) {
9956
+ const hextets = parseIpv6Hextets(address);
9957
+ if (hextets === null) return false;
9958
+ const isIpv4Compatible = matchesIpv6Prefix(hextets, [
9959
+ 0,
9960
+ 0,
9961
+ 0,
9962
+ 0,
9963
+ 0,
9964
+ 0
9965
+ ], 96);
9966
+ const isIpv4Mapped = matchesIpv6Prefix(hextets, [
9967
+ 0,
9968
+ 0,
9969
+ 0,
9970
+ 0,
9971
+ 0,
9972
+ 65535
9973
+ ], 96);
9974
+ if (isIpv4Compatible || isIpv4Mapped) {
9975
+ return isPublicIpv4Octets(embeddedIpv4(hextets));
9976
+ }
9977
+ const first = hextets[0] ?? 0;
9978
+ if (first === 8194) {
9979
+ const second = hextets[1] ?? 0;
9980
+ const third = hextets[2] ?? 0;
9981
+ return isPublicIpv4Octets([
9982
+ second >> 8,
9983
+ second & 255,
9984
+ third >> 8,
9985
+ third & 255
9986
+ ]);
9987
+ }
9988
+ const nonPublicPrefixes = [
9989
+ [
9990
+ [
9991
+ 100,
9992
+ 65435,
9993
+ 1
9994
+ ],
9995
+ 48
9996
+ ],
9997
+ [
9998
+ [
9999
+ 256,
10000
+ 0,
10001
+ 0,
10002
+ 0
10003
+ ],
10004
+ 64
10005
+ ],
10006
+ [
10007
+ [
10008
+ 8193,
10009
+ 0
10010
+ ],
10011
+ 32
10012
+ ],
10013
+ [
10014
+ [
10015
+ 8193,
10016
+ 2,
10017
+ 0
10018
+ ],
10019
+ 48
10020
+ ],
10021
+ [
10022
+ [
10023
+ 8193,
10024
+ 16
10025
+ ],
10026
+ 28
10027
+ ],
10028
+ [
10029
+ [
10030
+ 8193,
10031
+ 32
10032
+ ],
10033
+ 28
10034
+ ],
10035
+ [
10036
+ [
10037
+ 8193,
10038
+ 3512
10039
+ ],
10040
+ 32
10041
+ ],
10042
+ [
10043
+ [
10044
+ 16383
10045
+ ],
10046
+ 20
10047
+ ],
10048
+ [
10049
+ [
10050
+ 24320
10051
+ ],
10052
+ 16
10053
+ ],
10054
+ [
10055
+ [
10056
+ 64512
10057
+ ],
10058
+ 7
10059
+ ],
10060
+ [
10061
+ [
10062
+ 65152
10063
+ ],
10064
+ 10
10065
+ ],
10066
+ [
10067
+ [
10068
+ 65216
10069
+ ],
10070
+ 10
10071
+ ],
10072
+ [
10073
+ [
10074
+ 65280
10075
+ ],
10076
+ 8
10077
+ ]
10078
+ ];
10079
+ return matchesIpv6Prefix(hextets, [
10080
+ 8192
10081
+ ], 3) && !nonPublicPrefixes.some(([prefix, length]) => matchesIpv6Prefix(hextets, prefix, length));
10082
+ }
10083
+ __name(isPublicIpv6, "isPublicIpv6");
10084
+ function isPublicHost(value) {
10085
+ const hostname = normalizeHostname(value);
10086
+ if (hostname.length === 0 || hostname === "localhost" || hostname.endsWith(".localhost")) {
10087
+ return false;
10088
+ }
10089
+ const ipv4 = parseIpv4(hostname);
10090
+ if (ipv4 !== null) return isPublicIpv4Octets(ipv4);
10091
+ if (hostname.includes(":")) return isPublicIpv6(hostname);
10092
+ if (/^[\d.]+$/.test(hostname)) return false;
10093
+ return true;
10094
+ }
10095
+ __name(isPublicHost, "isPublicHost");
10096
+ function parsePublicHttpsOrigin(value) {
10097
+ let parsed;
10098
+ try {
10099
+ parsed = new URL(value);
10100
+ } catch {
10101
+ return null;
10102
+ }
10103
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash || parsed.port !== "" || !isPublicHost(parsed.hostname)) {
10104
+ return null;
10105
+ }
10106
+ return parsed;
10107
+ }
10108
+ __name(parsePublicHttpsOrigin, "parsePublicHttpsOrigin");
10109
+
10110
+ // ../../packages/types/src/social-profile-source.ts
10111
+ var INSTAGRAM_HOSTS = /* @__PURE__ */ new Set([
10112
+ "instagram.com",
10113
+ "www.instagram.com",
10114
+ "m.instagram.com",
10115
+ "instagr.am",
10116
+ "www.instagr.am"
10117
+ ]);
10118
+ var FACEBOOK_HOSTS = /* @__PURE__ */ new Set([
10119
+ "facebook.com",
10120
+ "www.facebook.com",
10121
+ "m.facebook.com",
10122
+ "mbasic.facebook.com",
10123
+ "web.facebook.com",
10124
+ "fb.com",
10125
+ "www.fb.com"
10126
+ ]);
10127
+ var INSTAGRAM_RESERVED = /* @__PURE__ */ new Set([
10128
+ "accounts",
10129
+ "about",
10130
+ "api",
10131
+ "challenge",
10132
+ "developer",
10133
+ "developers",
10134
+ "direct",
10135
+ "directory",
10136
+ "emails",
10137
+ "explore",
10138
+ "legal",
10139
+ "p",
10140
+ "privacy",
10141
+ "reel",
10142
+ "reels",
10143
+ "stories",
10144
+ "tv",
10145
+ "web"
10146
+ ]);
10147
+ var FACEBOOK_RESERVED = /* @__PURE__ */ new Set([
10148
+ "about",
10149
+ "ads",
10150
+ "business",
10151
+ "checkpoint",
10152
+ "dialog",
10153
+ "events",
10154
+ "friends",
10155
+ "gaming",
10156
+ "groups",
10157
+ "help",
10158
+ "home.php",
10159
+ "login",
10160
+ "login.php",
10161
+ "logout.php",
10162
+ "marketplace",
10163
+ "messages",
10164
+ "pages",
10165
+ "people",
10166
+ "p",
10167
+ "photo",
10168
+ "photo.php",
10169
+ "photos",
10170
+ "plugins",
10171
+ "policies",
10172
+ "privacy",
10173
+ "profile.php",
10174
+ "recover",
10175
+ "reel",
10176
+ "reels",
10177
+ "search",
10178
+ "settings",
10179
+ "share",
10180
+ "sharer",
10181
+ "sharer.php",
10182
+ "story.php",
10183
+ "video.php",
10184
+ "videos",
10185
+ "watch"
10186
+ ]);
10187
+ function socialProfileRequest(input) {
10188
+ let parsed;
10189
+ try {
10190
+ parsed = new URL(input.trim());
10191
+ } catch {
10192
+ return null;
10193
+ }
10194
+ if (![
10195
+ "http:",
10196
+ "https:"
10197
+ ].includes(parsed.protocol) || parsed.username || parsed.password || parsed.port || parsed.pathname.includes("%")) return null;
10198
+ const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
10199
+ const segments = parsed.pathname.split("/").filter(Boolean);
10200
+ const first = segments[0]?.toLowerCase();
10201
+ if (INSTAGRAM_HOSTS.has(host)) {
10202
+ if (segments.length !== 1 || !first || !/^[a-z0-9._]{1,30}$/.test(first) || INSTAGRAM_RESERVED.has(first)) return null;
10203
+ return {
10204
+ platform: "instagram",
10205
+ handle: first,
10206
+ url: `https://www.instagram.com/${first}/`
10207
+ };
10208
+ }
10209
+ if (!FACEBOOK_HOSTS.has(host) || !first) return null;
10210
+ const numericId = first === "profile.php" && segments.length === 1 ? parsed.searchParams.get("id") : first === "people" && segments.length === 3 ? segments[2] : first === "p" && segments.length === 2 ? segments[1]?.match(/-(\d+)$/)?.[1] : void 0;
10211
+ if (numericId && /^[1-9]\d{0,29}$/.test(numericId)) {
10212
+ return {
10213
+ platform: "facebook",
10214
+ handle: numericId,
10215
+ url: `https://www.facebook.com/profile.php?id=${numericId}`
10216
+ };
10217
+ }
10218
+ if (segments.length !== 1 || !/^[a-z0-9._-]{1,100}$/.test(first) || FACEBOOK_RESERVED.has(first)) return null;
10219
+ return {
10220
+ platform: "facebook",
10221
+ handle: first,
10222
+ url: `https://www.facebook.com/${first}/`
10223
+ };
10224
+ }
10225
+ __name(socialProfileRequest, "socialProfileRequest");
10226
+
10227
+ // ../../packages/types/src/web-address.ts
10228
+ function normalizeWebAddress(input) {
10229
+ const trimmed = input.trim();
10230
+ if (!trimmed || /\s/.test(trimmed)) return null;
10231
+ const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed);
10232
+ const hasOpaqueScheme = !hasScheme && /^[a-z][a-z0-9+.-]*:(?!\d)/i.test(trimmed);
10233
+ if (hasOpaqueScheme) return null;
10234
+ const candidate = hasScheme ? trimmed : `https://${trimmed}`;
10235
+ let parsed;
10236
+ try {
10237
+ parsed = new URL(candidate);
10238
+ } catch {
10239
+ return null;
10240
+ }
10241
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
10242
+ if (parsed.username || parsed.password) return null;
10243
+ if (!isPublicHostname(parsed.hostname)) return null;
10244
+ return candidate;
10245
+ }
10246
+ __name(normalizeWebAddress, "normalizeWebAddress");
10247
+ var LOGIN_REDIRECTS = [
10248
+ {
10249
+ hosts: /* @__PURE__ */ new Set([
10250
+ "instagram.com",
10251
+ "m.instagram.com"
10252
+ ]),
10253
+ loginPath: /^\/accounts\/login\/?$/i,
10254
+ canonicalOrigin: "https://www.instagram.com"
10255
+ },
10256
+ {
10257
+ hosts: /* @__PURE__ */ new Set([
10258
+ "facebook.com",
10259
+ "m.facebook.com"
10260
+ ]),
10261
+ loginPath: /^\/login(?:\.php)?\/?$/i,
10262
+ canonicalOrigin: "https://www.facebook.com"
10263
+ }
10264
+ ];
10265
+ function unwrapLoginRedirectUrl(input) {
10266
+ let parsed;
10267
+ try {
10268
+ parsed = new URL(input.trim());
10269
+ } catch {
10270
+ return input;
10271
+ }
10272
+ const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
10273
+ const rule = LOGIN_REDIRECTS.find((candidate) => candidate.hosts.has(host) && candidate.loginPath.test(parsed.pathname));
10274
+ const next = rule ? parsed.searchParams.get("next") : null;
10275
+ if (!rule || !next) return input;
10276
+ let target;
10277
+ try {
10278
+ target = new URL(next, rule.canonicalOrigin);
10279
+ } catch {
10280
+ return input;
10281
+ }
10282
+ const targetHost = target.hostname.toLowerCase().replace(/^www\./, "");
10283
+ if (!rule.hosts.has(targetHost) || ![
10284
+ "http:",
10285
+ "https:"
10286
+ ].includes(target.protocol) || rule.loginPath.test(target.pathname) || target.pathname === "/") return input;
10287
+ return `${rule.canonicalOrigin}${target.pathname}${target.search}`;
10288
+ }
10289
+ __name(unwrapLoginRedirectUrl, "unwrapLoginRedirectUrl");
10290
+ var LABEL = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
10291
+ var TLD = /^(?:[a-z]{2,}|xn--[a-z0-9-]+)$/i;
10292
+ function isPublicHostname(hostname) {
10293
+ const host = hostname.replace(/\.$/, "").toLowerCase();
10294
+ if (!host || host.startsWith("[")) return false;
10295
+ if (host === "localhost" || host.endsWith(".localhost")) return false;
10296
+ const labels = host.split(".");
10297
+ if (labels.length < 2) return false;
10298
+ if (!labels.every((label) => label.length <= 63 && LABEL.test(label))) return false;
10299
+ const tld = labels[labels.length - 1] ?? "";
10300
+ return TLD.test(tld);
10301
+ }
10302
+ __name(isPublicHostname, "isPublicHostname");
10303
+
9861
10304
  // ../../packages/types/src/pricing.ts
9862
10305
  var GEMINI_TTS_CREDIT_PRICING_ACTION = "tts-generation:gemini-3-1-flash-tts-percredit";
9863
10306
  var CREDITS_PER_USD_CENT = 10;
9864
10307
  var CREDITS_PER_DOLLAR = 100 * CREDITS_PER_USD_CENT;
10308
+ var APIFY_SOCIAL_PROFILE_MAX_RUN_COST_USD = 0.1;
10309
+ var APIFY_SOCIAL_PROFILE_EXPECTED_RUN_COST_CENTS = 0.23;
10310
+ function apifySocialProfileUserPriceCents(costCents) {
10311
+ if (!Number.isFinite(costCents * 2) || costCents < 0) {
10312
+ throw new RangeError("APIFY_SOCIAL_INVALID_COST");
10313
+ }
10314
+ return Number((costCents * 2).toFixed(10));
10315
+ }
10316
+ __name(apifySocialProfileUserPriceCents, "apifySocialProfileUserPriceCents");
10317
+ var APIFY_INSTAGRAM_PROFILE_PRICE_RANGE_CENTS = Object.freeze({
10318
+ min: apifySocialProfileUserPriceCents(APIFY_SOCIAL_PROFILE_EXPECTED_RUN_COST_CENTS),
10319
+ max: apifySocialProfileUserPriceCents(APIFY_SOCIAL_PROFILE_MAX_RUN_COST_USD * 100)
10320
+ });
10321
+ function estimateInstagramProfileAcquisitionCostCents(sourceUrl) {
10322
+ if (socialProfileRequest(unwrapLoginRedirectUrl(sourceUrl ?? ""))?.platform !== "instagram") {
10323
+ return {
10324
+ min: 0,
10325
+ max: 0
10326
+ };
10327
+ }
10328
+ return {
10329
+ ...APIFY_INSTAGRAM_PROFILE_PRICE_RANGE_CENTS
10330
+ };
10331
+ }
10332
+ __name(estimateInstagramProfileAcquisitionCostCents, "estimateInstagramProfileAcquisitionCostCents");
9865
10333
  var AD_CREATIVE_MODELS = {
9866
10334
  STANDARD: IMAGE_WRITE_TOKENS["1k"],
9867
10335
  PREMIUM: IMAGE_WRITE_TOKENS["2k"]
@@ -14359,9 +14827,17 @@ function motionGraphicsPriceCents(durationSec, options = {}) {
14359
14827
  }
14360
14828
  __name(motionGraphicsPriceCents, "motionGraphicsPriceCents");
14361
14829
  function motionGraphicsPriceRangeCents(durationSec, options = {}) {
14830
+ const pending = options.socialAcquisitionPriceCents;
14831
+ if (pending !== void 0 && (!Number.isFinite(pending) || pending < 0)) {
14832
+ throw new RangeError("APIFY_SOCIAL_INVALID_PRICE");
14833
+ }
14834
+ const acquisition = pending !== void 0 ? {
14835
+ min: pending,
14836
+ max: pending
14837
+ } : estimateInstagramProfileAcquisitionCostCents(options.socialSourceUrl);
14362
14838
  return {
14363
- minCents: roundClipCents(motionGraphicsBasePriceCents(durationSec)),
14364
- maxCents: motionGraphicsPriceCents(durationSec, options)
14839
+ minCents: roundClipCents(motionGraphicsBasePriceCents(durationSec) + acquisition.min),
14840
+ maxCents: roundClipCents(motionGraphicsPriceCents(durationSec, options) + acquisition.max)
14365
14841
  };
14366
14842
  }
14367
14843
  __name(motionGraphicsPriceRangeCents, "motionGraphicsPriceRangeCents");
@@ -15621,42 +16097,8 @@ var MotionGraphicsTranscriptResponseSchema = z84.discriminatedUnion("status", [
15621
16097
  z84.object({
15622
16098
  status: z84.literal("skipped"),
15623
16099
  reason: MotionGraphicsTranscriptSkipReasonSchema
15624
- }).strict()
15625
- ]);
15626
-
15627
- // ../../packages/types/src/web-address.ts
15628
- function normalizeWebAddress(input) {
15629
- const trimmed = input.trim();
15630
- if (!trimmed || /\s/.test(trimmed)) return null;
15631
- const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed);
15632
- const hasOpaqueScheme = !hasScheme && /^[a-z][a-z0-9+.-]*:(?!\d)/i.test(trimmed);
15633
- if (hasOpaqueScheme) return null;
15634
- const candidate = hasScheme ? trimmed : `https://${trimmed}`;
15635
- let parsed;
15636
- try {
15637
- parsed = new URL(candidate);
15638
- } catch {
15639
- return null;
15640
- }
15641
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
15642
- if (parsed.username || parsed.password) return null;
15643
- if (!isPublicHostname(parsed.hostname)) return null;
15644
- return candidate;
15645
- }
15646
- __name(normalizeWebAddress, "normalizeWebAddress");
15647
- var LABEL = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
15648
- var TLD = /^(?:[a-z]{2,}|xn--[a-z0-9-]+)$/i;
15649
- function isPublicHostname(hostname) {
15650
- const host = hostname.replace(/\.$/, "").toLowerCase();
15651
- if (!host || host.startsWith("[")) return false;
15652
- if (host === "localhost" || host.endsWith(".localhost")) return false;
15653
- const labels = host.split(".");
15654
- if (labels.length < 2) return false;
15655
- if (!labels.every((label) => label.length <= 63 && LABEL.test(label))) return false;
15656
- const tld = labels[labels.length - 1] ?? "";
15657
- return TLD.test(tld);
15658
- }
15659
- __name(isPublicHostname, "isPublicHostname");
16100
+ }).strict()
16101
+ ]);
15660
16102
 
15661
16103
  // ../../packages/types/src/internal/deep-freeze.ts
15662
16104
  function deepFreeze(value) {
@@ -19778,253 +20220,6 @@ var MCP_ANGLE_NARRATIVE_STYLES = [
19778
20220
  ];
19779
20221
  var McpAngleNarrativeStyleSchema = NarrativeStyle.extract(MCP_ANGLE_NARRATIVE_STYLES);
19780
20222
 
19781
- // ../../packages/types/src/public-host.ts
19782
- function normalizeHostname(value) {
19783
- const normalized = value.trim().toLowerCase().replace(/\.+$/, "");
19784
- return normalized.startsWith("[") && normalized.endsWith("]") ? normalized.slice(1, -1) : normalized;
19785
- }
19786
- __name(normalizeHostname, "normalizeHostname");
19787
- function parseIpv4(address) {
19788
- const octets = address.split(".");
19789
- if (octets.length !== 4 || !octets.every((octet) => /^(0|[1-9]\d{0,2})$/.test(octet))) {
19790
- return null;
19791
- }
19792
- const parsedOctets = octets.map(Number);
19793
- if (parsedOctets.some((octet) => octet > 255)) return null;
19794
- const [first, second, third, fourth] = parsedOctets;
19795
- if (first === void 0 || second === void 0 || third === void 0 || fourth === void 0) {
19796
- return null;
19797
- }
19798
- return [
19799
- first,
19800
- second,
19801
- third,
19802
- fourth
19803
- ];
19804
- }
19805
- __name(parseIpv4, "parseIpv4");
19806
- function isPublicIpv4Octets([first, second, third]) {
19807
- if (first === 0 || first === 10 || first === 127 || first >= 224) return false;
19808
- if (first === 100 && second >= 64 && second <= 127) return false;
19809
- if (first === 169 && second === 254) return false;
19810
- if (first === 172 && second >= 16 && second <= 31) return false;
19811
- if (first === 192 && second === 0 && (third === 0 || third === 2)) return false;
19812
- if (first === 192 && second === 88 && third === 99) return false;
19813
- if (first === 192 && second === 168) return false;
19814
- if (first === 198 && (second === 18 || second === 19)) return false;
19815
- if (first === 198 && second === 51 && third === 100) return false;
19816
- if (first === 203 && second === 0 && third === 113) return false;
19817
- return true;
19818
- }
19819
- __name(isPublicIpv4Octets, "isPublicIpv4Octets");
19820
- function parseIpv6Hextets(address) {
19821
- let normalized = address;
19822
- const lastColon = normalized.lastIndexOf(":");
19823
- const finalPart = normalized.slice(lastColon + 1);
19824
- if (finalPart.includes(".")) {
19825
- const octets = parseIpv4(finalPart);
19826
- if (octets === null) return null;
19827
- const [first, second, third, fourth] = octets;
19828
- normalized = `${normalized.slice(0, lastColon + 1)}${(first << 8 | second).toString(16)}:${(third << 8 | fourth).toString(16)}`;
19829
- }
19830
- const parts = normalized.split("::");
19831
- if (parts.length > 2) return null;
19832
- const left = parts[0] ? parts[0].split(":") : [];
19833
- const right = parts[1] ? parts[1].split(":") : [];
19834
- const explicit = [];
19835
- for (const part of [
19836
- ...left,
19837
- ...right
19838
- ]) {
19839
- if (!/^[\da-f]{1,4}$/i.test(part)) return null;
19840
- explicit.push(Number.parseInt(part, 16));
19841
- }
19842
- if (parts.length === 1) return explicit.length === 8 ? explicit : null;
19843
- if (explicit.length >= 8) return null;
19844
- return [
19845
- ...explicit.slice(0, left.length),
19846
- ...Array(8 - explicit.length).fill(0),
19847
- ...explicit.slice(left.length)
19848
- ];
19849
- }
19850
- __name(parseIpv6Hextets, "parseIpv6Hextets");
19851
- function matchesIpv6Prefix(hextets, prefix, prefixLength) {
19852
- const completeHextets = Math.floor(prefixLength / 16);
19853
- for (let index = 0; index < completeHextets; index += 1) {
19854
- if (hextets[index] !== prefix[index]) return false;
19855
- }
19856
- const remainingBits = prefixLength % 16;
19857
- if (remainingBits === 0) return true;
19858
- const mask = 65535 << 16 - remainingBits & 65535;
19859
- return ((hextets[completeHextets] ?? 0) & mask) === ((prefix[completeHextets] ?? 0) & mask);
19860
- }
19861
- __name(matchesIpv6Prefix, "matchesIpv6Prefix");
19862
- function embeddedIpv4(hextets) {
19863
- const seventh = hextets[6] ?? 0;
19864
- const eighth = hextets[7] ?? 0;
19865
- return [
19866
- seventh >> 8,
19867
- seventh & 255,
19868
- eighth >> 8,
19869
- eighth & 255
19870
- ];
19871
- }
19872
- __name(embeddedIpv4, "embeddedIpv4");
19873
- function isPublicIpv6(address) {
19874
- const hextets = parseIpv6Hextets(address);
19875
- if (hextets === null) return false;
19876
- const isIpv4Compatible = matchesIpv6Prefix(hextets, [
19877
- 0,
19878
- 0,
19879
- 0,
19880
- 0,
19881
- 0,
19882
- 0
19883
- ], 96);
19884
- const isIpv4Mapped = matchesIpv6Prefix(hextets, [
19885
- 0,
19886
- 0,
19887
- 0,
19888
- 0,
19889
- 0,
19890
- 65535
19891
- ], 96);
19892
- if (isIpv4Compatible || isIpv4Mapped) {
19893
- return isPublicIpv4Octets(embeddedIpv4(hextets));
19894
- }
19895
- const first = hextets[0] ?? 0;
19896
- if (first === 8194) {
19897
- const second = hextets[1] ?? 0;
19898
- const third = hextets[2] ?? 0;
19899
- return isPublicIpv4Octets([
19900
- second >> 8,
19901
- second & 255,
19902
- third >> 8,
19903
- third & 255
19904
- ]);
19905
- }
19906
- const nonPublicPrefixes = [
19907
- [
19908
- [
19909
- 100,
19910
- 65435,
19911
- 1
19912
- ],
19913
- 48
19914
- ],
19915
- [
19916
- [
19917
- 256,
19918
- 0,
19919
- 0,
19920
- 0
19921
- ],
19922
- 64
19923
- ],
19924
- [
19925
- [
19926
- 8193,
19927
- 0
19928
- ],
19929
- 32
19930
- ],
19931
- [
19932
- [
19933
- 8193,
19934
- 2,
19935
- 0
19936
- ],
19937
- 48
19938
- ],
19939
- [
19940
- [
19941
- 8193,
19942
- 16
19943
- ],
19944
- 28
19945
- ],
19946
- [
19947
- [
19948
- 8193,
19949
- 32
19950
- ],
19951
- 28
19952
- ],
19953
- [
19954
- [
19955
- 8193,
19956
- 3512
19957
- ],
19958
- 32
19959
- ],
19960
- [
19961
- [
19962
- 16383
19963
- ],
19964
- 20
19965
- ],
19966
- [
19967
- [
19968
- 24320
19969
- ],
19970
- 16
19971
- ],
19972
- [
19973
- [
19974
- 64512
19975
- ],
19976
- 7
19977
- ],
19978
- [
19979
- [
19980
- 65152
19981
- ],
19982
- 10
19983
- ],
19984
- [
19985
- [
19986
- 65216
19987
- ],
19988
- 10
19989
- ],
19990
- [
19991
- [
19992
- 65280
19993
- ],
19994
- 8
19995
- ]
19996
- ];
19997
- return matchesIpv6Prefix(hextets, [
19998
- 8192
19999
- ], 3) && !nonPublicPrefixes.some(([prefix, length]) => matchesIpv6Prefix(hextets, prefix, length));
20000
- }
20001
- __name(isPublicIpv6, "isPublicIpv6");
20002
- function isPublicHost(value) {
20003
- const hostname = normalizeHostname(value);
20004
- if (hostname.length === 0 || hostname === "localhost" || hostname.endsWith(".localhost")) {
20005
- return false;
20006
- }
20007
- const ipv4 = parseIpv4(hostname);
20008
- if (ipv4 !== null) return isPublicIpv4Octets(ipv4);
20009
- if (hostname.includes(":")) return isPublicIpv6(hostname);
20010
- if (/^[\d.]+$/.test(hostname)) return false;
20011
- return true;
20012
- }
20013
- __name(isPublicHost, "isPublicHost");
20014
- function parsePublicHttpsOrigin(value) {
20015
- let parsed;
20016
- try {
20017
- parsed = new URL(value);
20018
- } catch {
20019
- return null;
20020
- }
20021
- if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash || parsed.port !== "" || !isPublicHost(parsed.hostname)) {
20022
- return null;
20023
- }
20024
- return parsed;
20025
- }
20026
- __name(parsePublicHttpsOrigin, "parsePublicHttpsOrigin");
20027
-
20028
20223
  // ../../packages/types/src/whatsapp.ts
20029
20224
  var WHATSAPP_MAX_AUDIO_BYTES = 16 * 1024 * 1024;
20030
20225
  var WHATSAPP_MAX_AUDIO_DURATION_SECONDS = 10 * 60;
@@ -21831,6 +22026,9 @@ var ServicePagePublicReadSchema = z101.object({
21831
22026
  assetsById: z101.record(AssetIdSchema, ServicePagePublicAssetSchema)
21832
22027
  }).strict();
21833
22028
 
22029
+ // ../../packages/types/src/service-page-creation-status.ts
22030
+ var SERVICE_PAGE_CREATION_HANDOFF_GRACE_MS = 2 * 6e4;
22031
+
21834
22032
  // ../../packages/types/src/service-landing-analytics.ts
21835
22033
  import { z as z102 } from "zod";
21836
22034
  var DimensionsSchema = z102.object({
@@ -22231,6 +22429,8 @@ var WebsiteCaptureResponseSchema = z109.object({
22231
22429
  status: WebsiteCaptureStatusSchema,
22232
22430
  expiresAt: z109.string().datetime(),
22233
22431
  previewUrl: z109.string().url().optional(),
22432
+ /** Final customer amount for accepted Instagram acquisition still pending payment. */
22433
+ socialAcquisitionPriceCents: z109.number().finite().nonnegative().optional(),
22234
22434
  cause: WebsiteCaptureCauseSchema.optional()
22235
22435
  });
22236
22436
  var WebsiteCaptureJobSchema = WebsiteCaptureRequestSchema.extend({
@@ -24186,7 +24386,7 @@ var ApiClient = class {
24186
24386
  };
24187
24387
 
24188
24388
  // src/index.ts
24189
- var MCP_VERSION = "11.5.3";
24389
+ var MCP_VERSION = "11.5.5";
24190
24390
 
24191
24391
  // src/meta-upload-app.ts
24192
24392
  import { readFileSync } from "fs";
@@ -26783,15 +26983,11 @@ function centsToCredits(cents) {
26783
26983
  return Math.round(cents * CREDITS_PER_USD_CENT);
26784
26984
  }
26785
26985
  __name(centsToCredits, "centsToCredits");
26786
- function animatedVideoCredits(durationSec, hookVariants = DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS) {
26787
- return centsToCredits(motionGraphicsPriceCents(durationSec, {
26788
- hookVariants
26789
- }));
26790
- }
26791
- __name(animatedVideoCredits, "animatedVideoCredits");
26792
- function animatedVideoCreditRange(durationSec, hookVariants = DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS) {
26986
+ function animatedVideoCreditRange(durationSec, hookVariants = DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS, acquisition = {}) {
26793
26987
  const { minCents, maxCents } = motionGraphicsPriceRangeCents(durationSec, {
26794
- hookVariants
26988
+ hookVariants,
26989
+ socialSourceUrl: acquisition.sourceUrl,
26990
+ socialAcquisitionPriceCents: acquisition.priceCents
26795
26991
  });
26796
26992
  return {
26797
26993
  minCredits: centsToCredits(minCents),
@@ -26820,8 +27016,8 @@ function describeAnimatedVideoPrices() {
26820
27016
  return `${ranges}. Cada versi\xF3n extra del gancho suma ${centsToCredits(MOTION_GRAPHICS_VARIANT_PRICE_CENTS)} cr\xE9ditos (US$${usd(MOTION_GRAPHICS_VARIANT_PRICE_CENTS)})`;
26821
27017
  }
26822
27018
  __name(describeAnimatedVideoPrices, "describeAnimatedVideoPrices");
26823
- function animatedVideoMaxPriceText(durationSec, hookVariants = DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS) {
26824
- return `m\xE1ximo ${animatedVideoCredits(durationSec, hookVariants)} cr\xE9ditos (se devuelve lo que no use: clips IA, voz en off, clips hablados con la voz del narrador y versiones no entregadas)`;
27019
+ function animatedVideoMaxPriceText(durationSec, hookVariants = DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS, acquisition = {}) {
27020
+ return `m\xE1ximo ${animatedVideoCreditRange(durationSec, hookVariants, acquisition).maxCredits} cr\xE9ditos (se devuelve lo que no use: clips IA, voz en off, clips hablados con la voz del narrador y versiones no entregadas)`;
26825
27021
  }
26826
27022
  __name(animatedVideoMaxPriceText, "animatedVideoMaxPriceText");
26827
27023
  function hookVariantsLabel(count) {
@@ -27053,7 +27249,7 @@ function renderAnimatedVideoStatus(result) {
27053
27249
  __name(renderAnimatedVideoStatus, "renderAnimatedVideoStatus");
27054
27250
  function registerAnimatedVideoTools(server, api) {
27055
27251
  server.registerTool("create_animated_video", {
27056
- description: guidance.summary + ` Hace un video animado de un SERVICIO a partir del enlace de su p\xE1gina, con la identidad visual del sitio, m\xFAsica y efectos, y voz en off si el agente la usa. El agente que lo crea decide libremente la narrativa y la mezcla: solo gr\xE1ficos animados, clips de video generados con IA (incluidas personas hablando a c\xE1mara) o ambos intercalados; como m\xE1ximo un clip IA por cada ${MOTION_GRAPHICS_BROLL_CLIP_SECONDS} s de video. Desde la web el video tambi\xE9n puede usar los videos y fotos propios del usuario; aqu\xED se hace desde el enlace (gr\xE1ficos, clips IA y voz en off), sin archivos subidos. Si el video lleva voz en off, una persona que habla a c\xE1mara (en el gancho y, si acaso, en el cierre) habla con la misma voz del narrador: una sola voz en todo el video. Con hookVariants (2 o 3, solo si el usuario lo pide) entrega adem\xE1s versiones del mismo video con otra apertura, para probar cu\xE1l funciona mejor. Con music=false (solo si el usuario lo pide) sale sin m\xFAsica de fondo, al mismo precio. Primero llama action=prepare con landingUrl e idioma: prepara gratis una captura y NUNCA crea un video aunque responda ready. Sigue con action=status y websiteCaptureId hasta ready o fallo; cancel permite cancelar. Si est\xE1 bloqueada, pide otro enlace p\xFAblico sin iniciar sesi\xF3n; no solicites contrase\xF1as. Muestra la vista previa y confirma formato, duraci\xF3n, idioma y precio con el usuario. \xDAnicamente despu\xE9s llama action=generate con websiteCaptureId y confirmed=true. La generaci\xF3n reutiliza esa captura; no cobra por preparar ni por una captura pendiente/bloqueada. Repite el mismo websiteCaptureId y las mismas opciones para recuperar una respuesta perdida, incluso si la captura ya expir\xF3; la API devolver\xE1 el mismo taskId sin cobrar de nuevo. Conserva tambi\xE9n generationRequestId si lo enviaste. La misma captura, opciones e identidad representan esa generaci\xF3n; si el usuario pide y confirma otro video, usa un generationRequestId nuevo. Nunca cambies esa identidad autom\xE1ticamente por un fallo de transporte. Precio por duraci\xF3n, seg\xFAn lo que el video necesite: ${describeAnimatedVideoPrices()}. Al crearlo se cobra el m\xE1ximo de ese rango; cuando termina, lo que no us\xF3 (cada clip IA que no qued\xF3 en el video, la voz en off si no la us\xF3, cada clip hablado con la voz del narrador que no us\xF3 y cada versi\xF3n del gancho que no se entregue) se devuelve autom\xE1ticamente. Si falla, se devuelve todo. Tarda varios minutos, m\xE1s si hay otros videos en la cola. Generate devuelve un taskId: sigue el avance con get_task_status (etapa, posici\xF3n en la cola, % de render, lo que va haciendo y vista previa) hasta done, que trae el enlace del MP4 (y el de cada versi\xF3n del gancho), o error, que trae el motivo. Es el \xFAnico video de servicio disponible por ahora: no ofrezcas un video con presentador. Puede no estar disponible para todas las cuentas: si responde que la funci\xF3n no est\xE1 disponible, dilo y no ofrezcas alternativas pagadas.`,
27252
+ description: guidance.summary + ` Hace un video animado de un SERVICIO a partir del enlace de su p\xE1gina, con la identidad visual del sitio, m\xFAsica y efectos, y voz en off si el agente la usa. El agente que lo crea decide libremente la narrativa y la mezcla: solo gr\xE1ficos animados, clips de video generados con IA (incluidas personas hablando a c\xE1mara) o ambos intercalados; como m\xE1ximo un clip IA por cada ${MOTION_GRAPHICS_BROLL_CLIP_SECONDS} s de video. Desde la web el video tambi\xE9n puede usar los videos y fotos propios del usuario; aqu\xED se hace desde el enlace (gr\xE1ficos, clips IA y voz en off), sin archivos subidos. Si el video lleva voz en off, una persona que habla a c\xE1mara (en el gancho y, si acaso, en el cierre) habla con la misma voz del narrador: una sola voz en todo el video. Con hookVariants (2 o 3, solo si el usuario lo pide) entrega adem\xE1s versiones del mismo video con otra apertura, para probar cu\xE1l funciona mejor. Con music=false (solo si el usuario lo pide) sale sin m\xFAsica de fondo, al mismo precio. Primero llama action=prepare con landingUrl e idioma: prepara gratis una captura y NUNCA crea un video aunque responda ready. Sigue con action=status y websiteCaptureId hasta ready o fallo; cancel permite cancelar. Si est\xE1 bloqueada, pide otro enlace p\xFAblico sin iniciar sesi\xF3n; no solicites contrase\xF1as. Muestra la vista previa y confirma formato, duraci\xF3n, idioma y precio con el usuario. \xDAnicamente despu\xE9s llama action=generate con websiteCaptureId y confirmed=true. La generaci\xF3n reutiliza esa captura; no cobra por preparar ni por una captura pendiente/bloqueada. En Instagram, el precio incluye una sola adquisici\xF3n de la fuente a 2x su costo real, usando socialAcquisitionPriceCents de la captura ready; si ya se pag\xF3, ese importe es cero. Repite el mismo websiteCaptureId y las mismas opciones para recuperar una respuesta perdida, incluso si la captura ya expir\xF3; la API devolver\xE1 el mismo taskId sin cobrar de nuevo. Conserva tambi\xE9n generationRequestId si lo enviaste. La misma captura, opciones e identidad representan esa generaci\xF3n; si el usuario pide y confirma otro video, usa un generationRequestId nuevo. Nunca cambies esa identidad autom\xE1ticamente por un fallo de transporte. Precio por duraci\xF3n, seg\xFAn lo que el video necesite: ${describeAnimatedVideoPrices()}. Al crearlo se cobra el m\xE1ximo de ese rango; cuando termina, lo que no us\xF3 (cada clip IA que no qued\xF3 en el video, la voz en off si no la us\xF3, cada clip hablado con la voz del narrador que no us\xF3 y cada versi\xF3n del gancho que no se entregue) se devuelve autom\xE1ticamente. Si falla, se devuelve todo. Tarda varios minutos, m\xE1s si hay otros videos en la cola. Generate devuelve un taskId: sigue el avance con get_task_status (etapa, posici\xF3n en la cola, % de render, lo que va haciendo y vista previa) hasta done, que trae el enlace del MP4 (y el de cada versi\xF3n del gancho), o error, que trae el motivo. Es el \xFAnico video de servicio disponible por ahora: no ofrezcas un video con presentador. Puede no estar disponible para todas las cuentas: si responde que la funci\xF3n no est\xE1 disponible, dilo y no ofrezcas alternativas pagadas.`,
27057
27253
  inputSchema: CreateAnimatedVideoInput.shape,
27058
27254
  annotations: CREATES_PAID_OPEN_WORLD
27059
27255
  }, async (input) => {
@@ -27101,8 +27297,14 @@ function registerAnimatedVideoTools(server, api) {
27101
27297
  const motionGraphics = request.data;
27102
27298
  const hookVariants = motionGraphics.hookVariants ?? DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS;
27103
27299
  const clipCount = motionGraphicsMaxClips(motionGraphics.durationSec);
27104
- const { minCredits, maxCredits } = animatedVideoCreditRange(motionGraphics.durationSec, hookVariants);
27105
- const priceText = animatedVideoMaxPriceText(motionGraphics.durationSec, hookVariants);
27300
+ const captureId = input.websiteCaptureId;
27301
+ const captureQuote = socialProfileRequest(landingUrl)?.platform === "instagram" ? await Promise.resolve().then(() => api.getJson(`/v1/motion-graphics/captures/${encodeURIComponent(captureId)}`)).then((value) => WebsiteCaptureResponseSchema.safeParse(value), () => null) : null;
27302
+ const acquisition = {
27303
+ sourceUrl: landingUrl,
27304
+ priceCents: captureQuote?.success ? captureQuote.data.socialAcquisitionPriceCents : void 0
27305
+ };
27306
+ const { minCredits, maxCredits } = animatedVideoCreditRange(motionGraphics.durationSec, hookVariants, acquisition);
27307
+ const priceText = animatedVideoMaxPriceText(motionGraphics.durationSec, hookVariants, acquisition);
27106
27308
  const versionsText = hookVariants > 1 ? ` y ${hookVariantsLabel(hookVariants)}` : "";
27107
27309
  const music = motionGraphicsMusic(motionGraphics);
27108
27310
  const musicText = music ? "" : ", sin m\xFAsica de fondo";
@@ -28736,7 +28938,9 @@ function registerBrandTools(server, api, options = {}) {
28736
28938
  }
28737
28939
  const estimate = await api.postJson("/v1/billing/estimate", {
28738
28940
  type: "brand-extract",
28739
- options: {}
28941
+ options: {
28942
+ sourceUrl
28943
+ }
28740
28944
  });
28741
28945
  const priceCredits = Math.ceil(estimate.estimatedMaxCostCredits);
28742
28946
  if (input.confirmed !== true) {
package/dist/stdio.js CHANGED
@@ -8425,7 +8425,9 @@ var CampaignCopyEstimationOptions = z40.object({
8425
8425
  /** Number of image creatives that can each use one vision analysis (0-10). */
8426
8426
  nImages: z40.number().int().min(0).max(10).default(0)
8427
8427
  });
8428
- var BrandExtractEstimationOptions = z40.object({}).strict();
8428
+ var BrandExtractEstimationOptions = z40.object({
8429
+ sourceUrl: z40.string().url().optional()
8430
+ }).strict();
8429
8431
  var PersonaSuggestEstimationOptions = z40.object({}).strict();
8430
8432
  var ChatParseEstimationOptions = z40.object({}).strict();
8431
8433
  var CodeLandingEditEstimationOptions = z40.object({}).strict();
@@ -9880,10 +9882,229 @@ var TikTokCreateCampaignResponseSchema = z52.object({
9880
9882
  var GEMINI_OMNI_FLAT_OUR_COST_CENTS = 84;
9881
9883
  var GEMINI_OMNI_FLAT_USER_PRICE_CENTS = 168;
9882
9884
 
9885
+ // ../../packages/types/src/social-profile-source.ts
9886
+ var INSTAGRAM_HOSTS = /* @__PURE__ */ new Set([
9887
+ "instagram.com",
9888
+ "www.instagram.com",
9889
+ "m.instagram.com",
9890
+ "instagr.am",
9891
+ "www.instagr.am"
9892
+ ]);
9893
+ var FACEBOOK_HOSTS = /* @__PURE__ */ new Set([
9894
+ "facebook.com",
9895
+ "www.facebook.com",
9896
+ "m.facebook.com",
9897
+ "mbasic.facebook.com",
9898
+ "web.facebook.com",
9899
+ "fb.com",
9900
+ "www.fb.com"
9901
+ ]);
9902
+ var INSTAGRAM_RESERVED = /* @__PURE__ */ new Set([
9903
+ "accounts",
9904
+ "about",
9905
+ "api",
9906
+ "challenge",
9907
+ "developer",
9908
+ "developers",
9909
+ "direct",
9910
+ "directory",
9911
+ "emails",
9912
+ "explore",
9913
+ "legal",
9914
+ "p",
9915
+ "privacy",
9916
+ "reel",
9917
+ "reels",
9918
+ "stories",
9919
+ "tv",
9920
+ "web"
9921
+ ]);
9922
+ var FACEBOOK_RESERVED = /* @__PURE__ */ new Set([
9923
+ "about",
9924
+ "ads",
9925
+ "business",
9926
+ "checkpoint",
9927
+ "dialog",
9928
+ "events",
9929
+ "friends",
9930
+ "gaming",
9931
+ "groups",
9932
+ "help",
9933
+ "home.php",
9934
+ "login",
9935
+ "login.php",
9936
+ "logout.php",
9937
+ "marketplace",
9938
+ "messages",
9939
+ "pages",
9940
+ "people",
9941
+ "p",
9942
+ "photo",
9943
+ "photo.php",
9944
+ "photos",
9945
+ "plugins",
9946
+ "policies",
9947
+ "privacy",
9948
+ "profile.php",
9949
+ "recover",
9950
+ "reel",
9951
+ "reels",
9952
+ "search",
9953
+ "settings",
9954
+ "share",
9955
+ "sharer",
9956
+ "sharer.php",
9957
+ "story.php",
9958
+ "video.php",
9959
+ "videos",
9960
+ "watch"
9961
+ ]);
9962
+ function socialProfileRequest(input) {
9963
+ let parsed;
9964
+ try {
9965
+ parsed = new URL(input.trim());
9966
+ } catch {
9967
+ return null;
9968
+ }
9969
+ if (![
9970
+ "http:",
9971
+ "https:"
9972
+ ].includes(parsed.protocol) || parsed.username || parsed.password || parsed.port || parsed.pathname.includes("%")) return null;
9973
+ const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
9974
+ const segments = parsed.pathname.split("/").filter(Boolean);
9975
+ const first = segments[0]?.toLowerCase();
9976
+ if (INSTAGRAM_HOSTS.has(host)) {
9977
+ if (segments.length !== 1 || !first || !/^[a-z0-9._]{1,30}$/.test(first) || INSTAGRAM_RESERVED.has(first)) return null;
9978
+ return {
9979
+ platform: "instagram",
9980
+ handle: first,
9981
+ url: `https://www.instagram.com/${first}/`
9982
+ };
9983
+ }
9984
+ if (!FACEBOOK_HOSTS.has(host) || !first) return null;
9985
+ const numericId = first === "profile.php" && segments.length === 1 ? parsed.searchParams.get("id") : first === "people" && segments.length === 3 ? segments[2] : first === "p" && segments.length === 2 ? segments[1]?.match(/-(\d+)$/)?.[1] : void 0;
9986
+ if (numericId && /^[1-9]\d{0,29}$/.test(numericId)) {
9987
+ return {
9988
+ platform: "facebook",
9989
+ handle: numericId,
9990
+ url: `https://www.facebook.com/profile.php?id=${numericId}`
9991
+ };
9992
+ }
9993
+ if (segments.length !== 1 || !/^[a-z0-9._-]{1,100}$/.test(first) || FACEBOOK_RESERVED.has(first)) return null;
9994
+ return {
9995
+ platform: "facebook",
9996
+ handle: first,
9997
+ url: `https://www.facebook.com/${first}/`
9998
+ };
9999
+ }
10000
+ __name(socialProfileRequest, "socialProfileRequest");
10001
+
10002
+ // ../../packages/types/src/web-address.ts
10003
+ function normalizeWebAddress(input) {
10004
+ const trimmed = input.trim();
10005
+ if (!trimmed || /\s/.test(trimmed)) return null;
10006
+ const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed);
10007
+ const hasOpaqueScheme = !hasScheme && /^[a-z][a-z0-9+.-]*:(?!\d)/i.test(trimmed);
10008
+ if (hasOpaqueScheme) return null;
10009
+ const candidate = hasScheme ? trimmed : `https://${trimmed}`;
10010
+ let parsed;
10011
+ try {
10012
+ parsed = new URL(candidate);
10013
+ } catch {
10014
+ return null;
10015
+ }
10016
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
10017
+ if (parsed.username || parsed.password) return null;
10018
+ if (!isPublicHostname(parsed.hostname)) return null;
10019
+ return candidate;
10020
+ }
10021
+ __name(normalizeWebAddress, "normalizeWebAddress");
10022
+ var LOGIN_REDIRECTS = [
10023
+ {
10024
+ hosts: /* @__PURE__ */ new Set([
10025
+ "instagram.com",
10026
+ "m.instagram.com"
10027
+ ]),
10028
+ loginPath: /^\/accounts\/login\/?$/i,
10029
+ canonicalOrigin: "https://www.instagram.com"
10030
+ },
10031
+ {
10032
+ hosts: /* @__PURE__ */ new Set([
10033
+ "facebook.com",
10034
+ "m.facebook.com"
10035
+ ]),
10036
+ loginPath: /^\/login(?:\.php)?\/?$/i,
10037
+ canonicalOrigin: "https://www.facebook.com"
10038
+ }
10039
+ ];
10040
+ function unwrapLoginRedirectUrl(input) {
10041
+ let parsed;
10042
+ try {
10043
+ parsed = new URL(input.trim());
10044
+ } catch {
10045
+ return input;
10046
+ }
10047
+ const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
10048
+ const rule = LOGIN_REDIRECTS.find((candidate) => candidate.hosts.has(host) && candidate.loginPath.test(parsed.pathname));
10049
+ const next = rule ? parsed.searchParams.get("next") : null;
10050
+ if (!rule || !next) return input;
10051
+ let target;
10052
+ try {
10053
+ target = new URL(next, rule.canonicalOrigin);
10054
+ } catch {
10055
+ return input;
10056
+ }
10057
+ const targetHost = target.hostname.toLowerCase().replace(/^www\./, "");
10058
+ if (!rule.hosts.has(targetHost) || ![
10059
+ "http:",
10060
+ "https:"
10061
+ ].includes(target.protocol) || rule.loginPath.test(target.pathname) || target.pathname === "/") return input;
10062
+ return `${rule.canonicalOrigin}${target.pathname}${target.search}`;
10063
+ }
10064
+ __name(unwrapLoginRedirectUrl, "unwrapLoginRedirectUrl");
10065
+ var LABEL = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
10066
+ var TLD = /^(?:[a-z]{2,}|xn--[a-z0-9-]+)$/i;
10067
+ function isPublicHostname(hostname) {
10068
+ const host = hostname.replace(/\.$/, "").toLowerCase();
10069
+ if (!host || host.startsWith("[")) return false;
10070
+ if (host === "localhost" || host.endsWith(".localhost")) return false;
10071
+ const labels = host.split(".");
10072
+ if (labels.length < 2) return false;
10073
+ if (!labels.every((label) => label.length <= 63 && LABEL.test(label))) return false;
10074
+ const tld = labels[labels.length - 1] ?? "";
10075
+ return TLD.test(tld);
10076
+ }
10077
+ __name(isPublicHostname, "isPublicHostname");
10078
+
9883
10079
  // ../../packages/types/src/pricing.ts
9884
10080
  var GEMINI_TTS_CREDIT_PRICING_ACTION = "tts-generation:gemini-3-1-flash-tts-percredit";
9885
10081
  var CREDITS_PER_USD_CENT = 10;
9886
10082
  var CREDITS_PER_DOLLAR = 100 * CREDITS_PER_USD_CENT;
10083
+ var APIFY_SOCIAL_PROFILE_MAX_RUN_COST_USD = 0.1;
10084
+ var APIFY_SOCIAL_PROFILE_EXPECTED_RUN_COST_CENTS = 0.23;
10085
+ function apifySocialProfileUserPriceCents(costCents) {
10086
+ if (!Number.isFinite(costCents * 2) || costCents < 0) {
10087
+ throw new RangeError("APIFY_SOCIAL_INVALID_COST");
10088
+ }
10089
+ return Number((costCents * 2).toFixed(10));
10090
+ }
10091
+ __name(apifySocialProfileUserPriceCents, "apifySocialProfileUserPriceCents");
10092
+ var APIFY_INSTAGRAM_PROFILE_PRICE_RANGE_CENTS = Object.freeze({
10093
+ min: apifySocialProfileUserPriceCents(APIFY_SOCIAL_PROFILE_EXPECTED_RUN_COST_CENTS),
10094
+ max: apifySocialProfileUserPriceCents(APIFY_SOCIAL_PROFILE_MAX_RUN_COST_USD * 100)
10095
+ });
10096
+ function estimateInstagramProfileAcquisitionCostCents(sourceUrl) {
10097
+ if (socialProfileRequest(unwrapLoginRedirectUrl(sourceUrl ?? ""))?.platform !== "instagram") {
10098
+ return {
10099
+ min: 0,
10100
+ max: 0
10101
+ };
10102
+ }
10103
+ return {
10104
+ ...APIFY_INSTAGRAM_PROFILE_PRICE_RANGE_CENTS
10105
+ };
10106
+ }
10107
+ __name(estimateInstagramProfileAcquisitionCostCents, "estimateInstagramProfileAcquisitionCostCents");
9887
10108
  var AD_CREATIVE_MODELS = {
9888
10109
  STANDARD: IMAGE_WRITE_TOKENS["1k"],
9889
10110
  PREMIUM: IMAGE_WRITE_TOKENS["2k"]
@@ -14381,9 +14602,17 @@ function motionGraphicsPriceCents(durationSec, options = {}) {
14381
14602
  }
14382
14603
  __name(motionGraphicsPriceCents, "motionGraphicsPriceCents");
14383
14604
  function motionGraphicsPriceRangeCents(durationSec, options = {}) {
14605
+ const pending = options.socialAcquisitionPriceCents;
14606
+ if (pending !== void 0 && (!Number.isFinite(pending) || pending < 0)) {
14607
+ throw new RangeError("APIFY_SOCIAL_INVALID_PRICE");
14608
+ }
14609
+ const acquisition = pending !== void 0 ? {
14610
+ min: pending,
14611
+ max: pending
14612
+ } : estimateInstagramProfileAcquisitionCostCents(options.socialSourceUrl);
14384
14613
  return {
14385
- minCents: roundClipCents(motionGraphicsBasePriceCents(durationSec)),
14386
- maxCents: motionGraphicsPriceCents(durationSec, options)
14614
+ minCents: roundClipCents(motionGraphicsBasePriceCents(durationSec) + acquisition.min),
14615
+ maxCents: roundClipCents(motionGraphicsPriceCents(durationSec, options) + acquisition.max)
14387
14616
  };
14388
14617
  }
14389
14618
  __name(motionGraphicsPriceRangeCents, "motionGraphicsPriceRangeCents");
@@ -15646,40 +15875,6 @@ var MotionGraphicsTranscriptResponseSchema = z84.discriminatedUnion("status", [
15646
15875
  }).strict()
15647
15876
  ]);
15648
15877
 
15649
- // ../../packages/types/src/web-address.ts
15650
- function normalizeWebAddress(input) {
15651
- const trimmed = input.trim();
15652
- if (!trimmed || /\s/.test(trimmed)) return null;
15653
- const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed);
15654
- const hasOpaqueScheme = !hasScheme && /^[a-z][a-z0-9+.-]*:(?!\d)/i.test(trimmed);
15655
- if (hasOpaqueScheme) return null;
15656
- const candidate = hasScheme ? trimmed : `https://${trimmed}`;
15657
- let parsed;
15658
- try {
15659
- parsed = new URL(candidate);
15660
- } catch {
15661
- return null;
15662
- }
15663
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
15664
- if (parsed.username || parsed.password) return null;
15665
- if (!isPublicHostname(parsed.hostname)) return null;
15666
- return candidate;
15667
- }
15668
- __name(normalizeWebAddress, "normalizeWebAddress");
15669
- var LABEL = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
15670
- var TLD = /^(?:[a-z]{2,}|xn--[a-z0-9-]+)$/i;
15671
- function isPublicHostname(hostname) {
15672
- const host = hostname.replace(/\.$/, "").toLowerCase();
15673
- if (!host || host.startsWith("[")) return false;
15674
- if (host === "localhost" || host.endsWith(".localhost")) return false;
15675
- const labels = host.split(".");
15676
- if (labels.length < 2) return false;
15677
- if (!labels.every((label) => label.length <= 63 && LABEL.test(label))) return false;
15678
- const tld = labels[labels.length - 1] ?? "";
15679
- return TLD.test(tld);
15680
- }
15681
- __name(isPublicHostname, "isPublicHostname");
15682
-
15683
15878
  // ../../packages/types/src/internal/deep-freeze.ts
15684
15879
  function deepFreeze(value) {
15685
15880
  if (value !== null && typeof value === "object") {
@@ -21599,6 +21794,9 @@ var ServicePagePublicReadSchema = z101.object({
21599
21794
  assetsById: z101.record(AssetIdSchema, ServicePagePublicAssetSchema)
21600
21795
  }).strict();
21601
21796
 
21797
+ // ../../packages/types/src/service-page-creation-status.ts
21798
+ var SERVICE_PAGE_CREATION_HANDOFF_GRACE_MS = 2 * 6e4;
21799
+
21602
21800
  // ../../packages/types/src/service-landing-analytics.ts
21603
21801
  import { z as z102 } from "zod";
21604
21802
  var DimensionsSchema = z102.object({
@@ -21999,6 +22197,8 @@ var WebsiteCaptureResponseSchema = z109.object({
21999
22197
  status: WebsiteCaptureStatusSchema,
22000
22198
  expiresAt: z109.string().datetime(),
22001
22199
  previewUrl: z109.string().url().optional(),
22200
+ /** Final customer amount for accepted Instagram acquisition still pending payment. */
22201
+ socialAcquisitionPriceCents: z109.number().finite().nonnegative().optional(),
22002
22202
  cause: WebsiteCaptureCauseSchema.optional()
22003
22203
  });
22004
22204
  var WebsiteCaptureJobSchema = WebsiteCaptureRequestSchema.extend({
@@ -22515,7 +22715,7 @@ var ApiClient = class {
22515
22715
  };
22516
22716
 
22517
22717
  // src/index.ts
22518
- var MCP_VERSION = "11.5.3";
22718
+ var MCP_VERSION = "11.5.5";
22519
22719
 
22520
22720
  // src/meta-upload-app.ts
22521
22721
  import { readFileSync } from "fs";
@@ -25165,15 +25365,11 @@ function centsToCredits(cents) {
25165
25365
  return Math.round(cents * CREDITS_PER_USD_CENT);
25166
25366
  }
25167
25367
  __name(centsToCredits, "centsToCredits");
25168
- function animatedVideoCredits(durationSec, hookVariants = DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS) {
25169
- return centsToCredits(motionGraphicsPriceCents(durationSec, {
25170
- hookVariants
25171
- }));
25172
- }
25173
- __name(animatedVideoCredits, "animatedVideoCredits");
25174
- function animatedVideoCreditRange(durationSec, hookVariants = DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS) {
25368
+ function animatedVideoCreditRange(durationSec, hookVariants = DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS, acquisition = {}) {
25175
25369
  const { minCents, maxCents } = motionGraphicsPriceRangeCents(durationSec, {
25176
- hookVariants
25370
+ hookVariants,
25371
+ socialSourceUrl: acquisition.sourceUrl,
25372
+ socialAcquisitionPriceCents: acquisition.priceCents
25177
25373
  });
25178
25374
  return {
25179
25375
  minCredits: centsToCredits(minCents),
@@ -25202,8 +25398,8 @@ function describeAnimatedVideoPrices() {
25202
25398
  return `${ranges}. Cada versi\xF3n extra del gancho suma ${centsToCredits(MOTION_GRAPHICS_VARIANT_PRICE_CENTS)} cr\xE9ditos (US$${usd(MOTION_GRAPHICS_VARIANT_PRICE_CENTS)})`;
25203
25399
  }
25204
25400
  __name(describeAnimatedVideoPrices, "describeAnimatedVideoPrices");
25205
- function animatedVideoMaxPriceText(durationSec, hookVariants = DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS) {
25206
- return `m\xE1ximo ${animatedVideoCredits(durationSec, hookVariants)} cr\xE9ditos (se devuelve lo que no use: clips IA, voz en off, clips hablados con la voz del narrador y versiones no entregadas)`;
25401
+ function animatedVideoMaxPriceText(durationSec, hookVariants = DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS, acquisition = {}) {
25402
+ return `m\xE1ximo ${animatedVideoCreditRange(durationSec, hookVariants, acquisition).maxCredits} cr\xE9ditos (se devuelve lo que no use: clips IA, voz en off, clips hablados con la voz del narrador y versiones no entregadas)`;
25207
25403
  }
25208
25404
  __name(animatedVideoMaxPriceText, "animatedVideoMaxPriceText");
25209
25405
  function hookVariantsLabel(count) {
@@ -25435,7 +25631,7 @@ function renderAnimatedVideoStatus(result) {
25435
25631
  __name(renderAnimatedVideoStatus, "renderAnimatedVideoStatus");
25436
25632
  function registerAnimatedVideoTools(server, api) {
25437
25633
  server.registerTool("create_animated_video", {
25438
- description: guidance.summary + ` Hace un video animado de un SERVICIO a partir del enlace de su p\xE1gina, con la identidad visual del sitio, m\xFAsica y efectos, y voz en off si el agente la usa. El agente que lo crea decide libremente la narrativa y la mezcla: solo gr\xE1ficos animados, clips de video generados con IA (incluidas personas hablando a c\xE1mara) o ambos intercalados; como m\xE1ximo un clip IA por cada ${MOTION_GRAPHICS_BROLL_CLIP_SECONDS} s de video. Desde la web el video tambi\xE9n puede usar los videos y fotos propios del usuario; aqu\xED se hace desde el enlace (gr\xE1ficos, clips IA y voz en off), sin archivos subidos. Si el video lleva voz en off, una persona que habla a c\xE1mara (en el gancho y, si acaso, en el cierre) habla con la misma voz del narrador: una sola voz en todo el video. Con hookVariants (2 o 3, solo si el usuario lo pide) entrega adem\xE1s versiones del mismo video con otra apertura, para probar cu\xE1l funciona mejor. Con music=false (solo si el usuario lo pide) sale sin m\xFAsica de fondo, al mismo precio. Primero llama action=prepare con landingUrl e idioma: prepara gratis una captura y NUNCA crea un video aunque responda ready. Sigue con action=status y websiteCaptureId hasta ready o fallo; cancel permite cancelar. Si est\xE1 bloqueada, pide otro enlace p\xFAblico sin iniciar sesi\xF3n; no solicites contrase\xF1as. Muestra la vista previa y confirma formato, duraci\xF3n, idioma y precio con el usuario. \xDAnicamente despu\xE9s llama action=generate con websiteCaptureId y confirmed=true. La generaci\xF3n reutiliza esa captura; no cobra por preparar ni por una captura pendiente/bloqueada. Repite el mismo websiteCaptureId y las mismas opciones para recuperar una respuesta perdida, incluso si la captura ya expir\xF3; la API devolver\xE1 el mismo taskId sin cobrar de nuevo. Conserva tambi\xE9n generationRequestId si lo enviaste. La misma captura, opciones e identidad representan esa generaci\xF3n; si el usuario pide y confirma otro video, usa un generationRequestId nuevo. Nunca cambies esa identidad autom\xE1ticamente por un fallo de transporte. Precio por duraci\xF3n, seg\xFAn lo que el video necesite: ${describeAnimatedVideoPrices()}. Al crearlo se cobra el m\xE1ximo de ese rango; cuando termina, lo que no us\xF3 (cada clip IA que no qued\xF3 en el video, la voz en off si no la us\xF3, cada clip hablado con la voz del narrador que no us\xF3 y cada versi\xF3n del gancho que no se entregue) se devuelve autom\xE1ticamente. Si falla, se devuelve todo. Tarda varios minutos, m\xE1s si hay otros videos en la cola. Generate devuelve un taskId: sigue el avance con get_task_status (etapa, posici\xF3n en la cola, % de render, lo que va haciendo y vista previa) hasta done, que trae el enlace del MP4 (y el de cada versi\xF3n del gancho), o error, que trae el motivo. Es el \xFAnico video de servicio disponible por ahora: no ofrezcas un video con presentador. Puede no estar disponible para todas las cuentas: si responde que la funci\xF3n no est\xE1 disponible, dilo y no ofrezcas alternativas pagadas.`,
25634
+ description: guidance.summary + ` Hace un video animado de un SERVICIO a partir del enlace de su p\xE1gina, con la identidad visual del sitio, m\xFAsica y efectos, y voz en off si el agente la usa. El agente que lo crea decide libremente la narrativa y la mezcla: solo gr\xE1ficos animados, clips de video generados con IA (incluidas personas hablando a c\xE1mara) o ambos intercalados; como m\xE1ximo un clip IA por cada ${MOTION_GRAPHICS_BROLL_CLIP_SECONDS} s de video. Desde la web el video tambi\xE9n puede usar los videos y fotos propios del usuario; aqu\xED se hace desde el enlace (gr\xE1ficos, clips IA y voz en off), sin archivos subidos. Si el video lleva voz en off, una persona que habla a c\xE1mara (en el gancho y, si acaso, en el cierre) habla con la misma voz del narrador: una sola voz en todo el video. Con hookVariants (2 o 3, solo si el usuario lo pide) entrega adem\xE1s versiones del mismo video con otra apertura, para probar cu\xE1l funciona mejor. Con music=false (solo si el usuario lo pide) sale sin m\xFAsica de fondo, al mismo precio. Primero llama action=prepare con landingUrl e idioma: prepara gratis una captura y NUNCA crea un video aunque responda ready. Sigue con action=status y websiteCaptureId hasta ready o fallo; cancel permite cancelar. Si est\xE1 bloqueada, pide otro enlace p\xFAblico sin iniciar sesi\xF3n; no solicites contrase\xF1as. Muestra la vista previa y confirma formato, duraci\xF3n, idioma y precio con el usuario. \xDAnicamente despu\xE9s llama action=generate con websiteCaptureId y confirmed=true. La generaci\xF3n reutiliza esa captura; no cobra por preparar ni por una captura pendiente/bloqueada. En Instagram, el precio incluye una sola adquisici\xF3n de la fuente a 2x su costo real, usando socialAcquisitionPriceCents de la captura ready; si ya se pag\xF3, ese importe es cero. Repite el mismo websiteCaptureId y las mismas opciones para recuperar una respuesta perdida, incluso si la captura ya expir\xF3; la API devolver\xE1 el mismo taskId sin cobrar de nuevo. Conserva tambi\xE9n generationRequestId si lo enviaste. La misma captura, opciones e identidad representan esa generaci\xF3n; si el usuario pide y confirma otro video, usa un generationRequestId nuevo. Nunca cambies esa identidad autom\xE1ticamente por un fallo de transporte. Precio por duraci\xF3n, seg\xFAn lo que el video necesite: ${describeAnimatedVideoPrices()}. Al crearlo se cobra el m\xE1ximo de ese rango; cuando termina, lo que no us\xF3 (cada clip IA que no qued\xF3 en el video, la voz en off si no la us\xF3, cada clip hablado con la voz del narrador que no us\xF3 y cada versi\xF3n del gancho que no se entregue) se devuelve autom\xE1ticamente. Si falla, se devuelve todo. Tarda varios minutos, m\xE1s si hay otros videos en la cola. Generate devuelve un taskId: sigue el avance con get_task_status (etapa, posici\xF3n en la cola, % de render, lo que va haciendo y vista previa) hasta done, que trae el enlace del MP4 (y el de cada versi\xF3n del gancho), o error, que trae el motivo. Es el \xFAnico video de servicio disponible por ahora: no ofrezcas un video con presentador. Puede no estar disponible para todas las cuentas: si responde que la funci\xF3n no est\xE1 disponible, dilo y no ofrezcas alternativas pagadas.`,
25439
25635
  inputSchema: CreateAnimatedVideoInput.shape,
25440
25636
  annotations: CREATES_PAID_OPEN_WORLD
25441
25637
  }, async (input) => {
@@ -25483,8 +25679,14 @@ function registerAnimatedVideoTools(server, api) {
25483
25679
  const motionGraphics = request.data;
25484
25680
  const hookVariants = motionGraphics.hookVariants ?? DEFAULT_MOTION_GRAPHICS_HOOK_VARIANTS;
25485
25681
  const clipCount = motionGraphicsMaxClips(motionGraphics.durationSec);
25486
- const { minCredits, maxCredits } = animatedVideoCreditRange(motionGraphics.durationSec, hookVariants);
25487
- const priceText = animatedVideoMaxPriceText(motionGraphics.durationSec, hookVariants);
25682
+ const captureId = input.websiteCaptureId;
25683
+ const captureQuote = socialProfileRequest(landingUrl)?.platform === "instagram" ? await Promise.resolve().then(() => api.getJson(`/v1/motion-graphics/captures/${encodeURIComponent(captureId)}`)).then((value) => WebsiteCaptureResponseSchema.safeParse(value), () => null) : null;
25684
+ const acquisition = {
25685
+ sourceUrl: landingUrl,
25686
+ priceCents: captureQuote?.success ? captureQuote.data.socialAcquisitionPriceCents : void 0
25687
+ };
25688
+ const { minCredits, maxCredits } = animatedVideoCreditRange(motionGraphics.durationSec, hookVariants, acquisition);
25689
+ const priceText = animatedVideoMaxPriceText(motionGraphics.durationSec, hookVariants, acquisition);
25488
25690
  const versionsText = hookVariants > 1 ? ` y ${hookVariantsLabel(hookVariants)}` : "";
25489
25691
  const music = motionGraphicsMusic(motionGraphics);
25490
25692
  const musicText = music ? "" : ", sin m\xFAsica de fondo";
@@ -27118,7 +27320,9 @@ function registerBrandTools(server, api, options = {}) {
27118
27320
  }
27119
27321
  const estimate = await api.postJson("/v1/billing/estimate", {
27120
27322
  type: "brand-extract",
27121
- options: {}
27323
+ options: {
27324
+ sourceUrl
27325
+ }
27122
27326
  });
27123
27327
  const priceCredits = Math.ceil(estimate.estimatedMaxCostCredits);
27124
27328
  if (input.confirmed !== true) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "11.5.3",
3
+ "version": "11.5.5",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "ProductMaker MCP server — cost products, generate landing pages, UGC videos, and ad creatives, and publish to Shopify, Meta Ads, and TikTok Ads.",