@productmaker/mcp 2.9.1 → 2.9.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 +35 -14
- package/dist/stdio.js +35 -14
- package/dist/ui/meta-upload-app.html +106 -616
- package/package.json +3 -3
package/dist/http.js
CHANGED
|
@@ -153,7 +153,7 @@ var ApiClient = class {
|
|
|
153
153
|
};
|
|
154
154
|
|
|
155
155
|
// src/index.ts
|
|
156
|
-
var MCP_VERSION = "2.9.
|
|
156
|
+
var MCP_VERSION = "2.9.3";
|
|
157
157
|
|
|
158
158
|
// src/meta-upload-app.ts
|
|
159
159
|
import { readFileSync } from "fs";
|
|
@@ -8052,7 +8052,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
|
|
|
8052
8052
|
{
|
|
8053
8053
|
name: "publish_creatives_to_meta",
|
|
8054
8054
|
confirmation: "external_mutation",
|
|
8055
|
-
schemaHash: "
|
|
8055
|
+
schemaHash: "f712fe12baee19de6ac5ed94b23de630247a345ed13c3a431a647d421480280f"
|
|
8056
8056
|
},
|
|
8057
8057
|
{
|
|
8058
8058
|
name: "publish_to_meta",
|
|
@@ -8915,7 +8915,7 @@ var CODE_MESSAGES = {
|
|
|
8915
8915
|
META_TOKEN_EXPIRED: "Your Meta session expired. Reconnect Facebook at https://productmaker.app/integrations.",
|
|
8916
8916
|
META_ACCOUNT_RESTRICTED: "Your Meta Ads account is temporarily restricted from creating ads. Check the notices at https://business.facebook.com and complete the required verification.",
|
|
8917
8917
|
META_CAPABILITY_MISSING: "The Facebook app lacks required permissions for this ad account. Reconnect Facebook at https://productmaker.app/integrations and accept ALL requested permissions (including business_management).",
|
|
8918
|
-
META_PERMISSION_DENIED: "
|
|
8918
|
+
META_PERMISSION_DENIED: "Meta denied write access to the selected ad account. Ask a Business Manager admin to grant this user Advertiser role or higher for that account, then reconnect Facebook at https://productmaker.app/integrations and accept every requested permission, including ads_management.",
|
|
8919
8919
|
META_PIXEL_NOT_FOUND: "Pixel not found or not associated with the ad account. Verify the `pixelId` at https://business.facebook.com/events_manager.",
|
|
8920
8920
|
META_OBJECT_NOT_FOUND: "The requested object (campaign/ad set/ad) does not exist in Meta. Verify the ID or create a new one.",
|
|
8921
8921
|
META_AI_DISCLOSURE_REQUIRED: "Meta rejected the ad for undisclosed AI content. The system adds the disclosure automatically; if it persists, edit the copy to avoid language Meta flags as misleading.",
|
|
@@ -10901,10 +10901,10 @@ var GroupedCreative = z52.object({
|
|
|
10901
10901
|
cta: MetaCtaType.optional(),
|
|
10902
10902
|
creativeId: z52.string().min(1).optional()
|
|
10903
10903
|
});
|
|
10904
|
-
var
|
|
10904
|
+
var GroupedCampaignConfigBase = z52.object({
|
|
10905
10905
|
campaignType: MetaCampaignType.default("sales"),
|
|
10906
10906
|
optimizationMode: MetaOptimizationMode.default("advantage_plus"),
|
|
10907
|
-
budgetMode: MetaBudgetMode.
|
|
10907
|
+
budgetMode: MetaBudgetMode.optional(),
|
|
10908
10908
|
campaignDailyBudget: z52.number().int().positive().optional(),
|
|
10909
10909
|
adSets: z52.array(z52.object({
|
|
10910
10910
|
tempId: z52.string().min(1).max(80),
|
|
@@ -10930,8 +10930,17 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10930
10930
|
]),
|
|
10931
10931
|
saveAsDefault: z52.boolean().default(false),
|
|
10932
10932
|
budgetPolicy: MetaBudgetPolicy.optional()
|
|
10933
|
-
})
|
|
10934
|
-
|
|
10933
|
+
});
|
|
10934
|
+
function resolveBudgetMode(value) {
|
|
10935
|
+
if (value.budgetMode) return value.budgetMode;
|
|
10936
|
+
if (value.campaignDailyBudget !== void 0) return "campaign";
|
|
10937
|
+
if (value.adSets.every((adSet) => adSet.dailyBudget !== void 0)) return "ad_set";
|
|
10938
|
+
return "campaign";
|
|
10939
|
+
}
|
|
10940
|
+
__name(resolveBudgetMode, "resolveBudgetMode");
|
|
10941
|
+
var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) => {
|
|
10942
|
+
const budgetMode = resolveBudgetMode(value);
|
|
10943
|
+
if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
|
|
10935
10944
|
ctx.addIssue({
|
|
10936
10945
|
code: z52.ZodIssueCode.custom,
|
|
10937
10946
|
path: [
|
|
@@ -10940,7 +10949,7 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10940
10949
|
message: "campaignDailyBudget is required when budgetMode is campaign"
|
|
10941
10950
|
});
|
|
10942
10951
|
}
|
|
10943
|
-
if (
|
|
10952
|
+
if (budgetMode === "ad_set") {
|
|
10944
10953
|
value.adSets.forEach((adSet, index) => {
|
|
10945
10954
|
if (adSet.dailyBudget === void 0) {
|
|
10946
10955
|
ctx.addIssue({
|
|
@@ -10955,7 +10964,10 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10955
10964
|
}
|
|
10956
10965
|
});
|
|
10957
10966
|
}
|
|
10958
|
-
})
|
|
10967
|
+
}).transform((value) => ({
|
|
10968
|
+
...value,
|
|
10969
|
+
budgetMode: resolveBudgetMode(value)
|
|
10970
|
+
}));
|
|
10959
10971
|
var PublishCreativesToMetaInputBase = z52.object({
|
|
10960
10972
|
creatives: z52.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
|
|
10961
10973
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
@@ -10973,7 +10985,7 @@ var PublishCreativesToMetaInputBase = z52.object({
|
|
|
10973
10985
|
});
|
|
10974
10986
|
var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
|
|
10975
10987
|
const destination = value.campaignConfig?.destination;
|
|
10976
|
-
if (destination?.type !== "whatsapp" && !value.landingUrl) {
|
|
10988
|
+
if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
|
|
10977
10989
|
ctx.addIssue({
|
|
10978
10990
|
code: z52.ZodIssueCode.custom,
|
|
10979
10991
|
path: [
|
|
@@ -10982,7 +10994,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
10982
10994
|
message: "landingUrl is required unless campaignConfig.destination.type is whatsapp"
|
|
10983
10995
|
});
|
|
10984
10996
|
}
|
|
10985
|
-
if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
|
|
10997
|
+
if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
|
|
10986
10998
|
ctx.addIssue({
|
|
10987
10999
|
code: z52.ZodIssueCode.custom,
|
|
10988
11000
|
path: [
|
|
@@ -10993,6 +11005,15 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
10993
11005
|
message: "campaignConfig.destination.landingUrl must match landingUrl"
|
|
10994
11006
|
});
|
|
10995
11007
|
}
|
|
11008
|
+
}).transform((value) => {
|
|
11009
|
+
const destination = value.campaignConfig?.destination;
|
|
11010
|
+
if (destination?.type === "landing" && !value.landingUrl) {
|
|
11011
|
+
return {
|
|
11012
|
+
...value,
|
|
11013
|
+
landingUrl: destination.landingUrl
|
|
11014
|
+
};
|
|
11015
|
+
}
|
|
11016
|
+
return value;
|
|
10996
11017
|
});
|
|
10997
11018
|
var PresignUploadResponseSchema = z52.object({
|
|
10998
11019
|
uploadUrl: z52.string().url().refine((value) => {
|
|
@@ -11104,7 +11125,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
11104
11125
|
throw translateError(e);
|
|
11105
11126
|
}
|
|
11106
11127
|
});
|
|
11107
|
-
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo para reintento
|
|
11128
|
+
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 durante el TTL para reintento: nunca afirmes que una sesi\xF3n no expira. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
|
|
11108
11129
|
try {
|
|
11109
11130
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
11110
11131
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -11377,7 +11398,7 @@ __name(uploadSessionText, "uploadSessionText");
|
|
|
11377
11398
|
function registerMetaUploadTools(server, api) {
|
|
11378
11399
|
server.registerTool("request_meta_media_upload", {
|
|
11379
11400
|
title: "Solicitar carga segura de Meta",
|
|
11380
|
-
description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El
|
|
11401
|
+
description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El App es exclusivamente para cargar archivos: nunca debe pedir ni mostrar portafolio, cuenta publicitaria, p\xE1gina, IDs o JSON. El enlace externo de ProductMaker queda como fallback para hosts sin bridge de archivos. Conserva privadamente el `sessionId`; cuando el usuario termine, llama `check_meta_media_upload`, luego `list_meta_destinations` y pregunta en el chat qu\xE9 `portfolioId`, `adAccountId` y `pageId` desea usar. No muestres el `sessionId`, `stagingAssetId` ni un handoff JSON al usuario. Esta herramienta no publica ni consume cr\xE9ditos.",
|
|
11381
11402
|
inputSchema: MetaUploadSessionToolInputShape,
|
|
11382
11403
|
outputSchema: MetaUploadSessionResponse2,
|
|
11383
11404
|
_meta: {
|
|
@@ -11410,7 +11431,7 @@ function registerMetaUploadTools(server, api) {
|
|
|
11410
11431
|
}
|
|
11411
11432
|
});
|
|
11412
11433
|
server.registerTool("check_meta_media_upload", {
|
|
11413
|
-
description: 'Comprueba si el usuario ya termin\xF3 de subir desde el enlace de ProductMaker. Si status=uploaded,
|
|
11434
|
+
description: 'Comprueba si el usuario ya termin\xF3 de subir desde el App o enlace de ProductMaker. Si status=uploaded, conserva `assets[]` privadamente, llama `list_meta_destinations` y pregunta en lenguaje natural dentro del chat qu\xE9 portafolio, cuenta publicitaria y p\xE1gina desea usar; nunca muestres `sessionId`, `stagingAssetId`, IDs t\xE9cnicos ni un bloque JSON. Despu\xE9s crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como tipo, hasta diez en el batch. Si la carga fue parcial, usa \xFAnicamente los assets confirmados. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.',
|
|
11414
11435
|
inputSchema: MetaUploadStatusInput.shape,
|
|
11415
11436
|
_meta: {
|
|
11416
11437
|
"openai/widgetAccessible": true
|
package/dist/stdio.js
CHANGED
|
@@ -150,7 +150,7 @@ var ApiClient = class {
|
|
|
150
150
|
};
|
|
151
151
|
|
|
152
152
|
// src/index.ts
|
|
153
|
-
var MCP_VERSION = "2.9.
|
|
153
|
+
var MCP_VERSION = "2.9.3";
|
|
154
154
|
|
|
155
155
|
// src/meta-upload-app.ts
|
|
156
156
|
import { readFileSync } from "fs";
|
|
@@ -8049,7 +8049,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
|
|
|
8049
8049
|
{
|
|
8050
8050
|
name: "publish_creatives_to_meta",
|
|
8051
8051
|
confirmation: "external_mutation",
|
|
8052
|
-
schemaHash: "
|
|
8052
|
+
schemaHash: "f712fe12baee19de6ac5ed94b23de630247a345ed13c3a431a647d421480280f"
|
|
8053
8053
|
},
|
|
8054
8054
|
{
|
|
8055
8055
|
name: "publish_to_meta",
|
|
@@ -8912,7 +8912,7 @@ var CODE_MESSAGES = {
|
|
|
8912
8912
|
META_TOKEN_EXPIRED: "Your Meta session expired. Reconnect Facebook at https://productmaker.app/integrations.",
|
|
8913
8913
|
META_ACCOUNT_RESTRICTED: "Your Meta Ads account is temporarily restricted from creating ads. Check the notices at https://business.facebook.com and complete the required verification.",
|
|
8914
8914
|
META_CAPABILITY_MISSING: "The Facebook app lacks required permissions for this ad account. Reconnect Facebook at https://productmaker.app/integrations and accept ALL requested permissions (including business_management).",
|
|
8915
|
-
META_PERMISSION_DENIED: "
|
|
8915
|
+
META_PERMISSION_DENIED: "Meta denied write access to the selected ad account. Ask a Business Manager admin to grant this user Advertiser role or higher for that account, then reconnect Facebook at https://productmaker.app/integrations and accept every requested permission, including ads_management.",
|
|
8916
8916
|
META_PIXEL_NOT_FOUND: "Pixel not found or not associated with the ad account. Verify the `pixelId` at https://business.facebook.com/events_manager.",
|
|
8917
8917
|
META_OBJECT_NOT_FOUND: "The requested object (campaign/ad set/ad) does not exist in Meta. Verify the ID or create a new one.",
|
|
8918
8918
|
META_AI_DISCLOSURE_REQUIRED: "Meta rejected the ad for undisclosed AI content. The system adds the disclosure automatically; if it persists, edit the copy to avoid language Meta flags as misleading.",
|
|
@@ -10857,10 +10857,10 @@ var GroupedCreative = z52.object({
|
|
|
10857
10857
|
cta: MetaCtaType.optional(),
|
|
10858
10858
|
creativeId: z52.string().min(1).optional()
|
|
10859
10859
|
});
|
|
10860
|
-
var
|
|
10860
|
+
var GroupedCampaignConfigBase = z52.object({
|
|
10861
10861
|
campaignType: MetaCampaignType.default("sales"),
|
|
10862
10862
|
optimizationMode: MetaOptimizationMode.default("advantage_plus"),
|
|
10863
|
-
budgetMode: MetaBudgetMode.
|
|
10863
|
+
budgetMode: MetaBudgetMode.optional(),
|
|
10864
10864
|
campaignDailyBudget: z52.number().int().positive().optional(),
|
|
10865
10865
|
adSets: z52.array(z52.object({
|
|
10866
10866
|
tempId: z52.string().min(1).max(80),
|
|
@@ -10886,8 +10886,17 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10886
10886
|
]),
|
|
10887
10887
|
saveAsDefault: z52.boolean().default(false),
|
|
10888
10888
|
budgetPolicy: MetaBudgetPolicy.optional()
|
|
10889
|
-
})
|
|
10890
|
-
|
|
10889
|
+
});
|
|
10890
|
+
function resolveBudgetMode(value) {
|
|
10891
|
+
if (value.budgetMode) return value.budgetMode;
|
|
10892
|
+
if (value.campaignDailyBudget !== void 0) return "campaign";
|
|
10893
|
+
if (value.adSets.every((adSet) => adSet.dailyBudget !== void 0)) return "ad_set";
|
|
10894
|
+
return "campaign";
|
|
10895
|
+
}
|
|
10896
|
+
__name(resolveBudgetMode, "resolveBudgetMode");
|
|
10897
|
+
var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) => {
|
|
10898
|
+
const budgetMode = resolveBudgetMode(value);
|
|
10899
|
+
if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
|
|
10891
10900
|
ctx.addIssue({
|
|
10892
10901
|
code: z52.ZodIssueCode.custom,
|
|
10893
10902
|
path: [
|
|
@@ -10896,7 +10905,7 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10896
10905
|
message: "campaignDailyBudget is required when budgetMode is campaign"
|
|
10897
10906
|
});
|
|
10898
10907
|
}
|
|
10899
|
-
if (
|
|
10908
|
+
if (budgetMode === "ad_set") {
|
|
10900
10909
|
value.adSets.forEach((adSet, index) => {
|
|
10901
10910
|
if (adSet.dailyBudget === void 0) {
|
|
10902
10911
|
ctx.addIssue({
|
|
@@ -10911,7 +10920,10 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10911
10920
|
}
|
|
10912
10921
|
});
|
|
10913
10922
|
}
|
|
10914
|
-
})
|
|
10923
|
+
}).transform((value) => ({
|
|
10924
|
+
...value,
|
|
10925
|
+
budgetMode: resolveBudgetMode(value)
|
|
10926
|
+
}));
|
|
10915
10927
|
var PublishCreativesToMetaInputBase = z52.object({
|
|
10916
10928
|
creatives: z52.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
|
|
10917
10929
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
@@ -10929,7 +10941,7 @@ var PublishCreativesToMetaInputBase = z52.object({
|
|
|
10929
10941
|
});
|
|
10930
10942
|
var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
|
|
10931
10943
|
const destination = value.campaignConfig?.destination;
|
|
10932
|
-
if (destination?.type !== "whatsapp" && !value.landingUrl) {
|
|
10944
|
+
if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
|
|
10933
10945
|
ctx.addIssue({
|
|
10934
10946
|
code: z52.ZodIssueCode.custom,
|
|
10935
10947
|
path: [
|
|
@@ -10938,7 +10950,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
10938
10950
|
message: "landingUrl is required unless campaignConfig.destination.type is whatsapp"
|
|
10939
10951
|
});
|
|
10940
10952
|
}
|
|
10941
|
-
if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
|
|
10953
|
+
if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
|
|
10942
10954
|
ctx.addIssue({
|
|
10943
10955
|
code: z52.ZodIssueCode.custom,
|
|
10944
10956
|
path: [
|
|
@@ -10949,6 +10961,15 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
10949
10961
|
message: "campaignConfig.destination.landingUrl must match landingUrl"
|
|
10950
10962
|
});
|
|
10951
10963
|
}
|
|
10964
|
+
}).transform((value) => {
|
|
10965
|
+
const destination = value.campaignConfig?.destination;
|
|
10966
|
+
if (destination?.type === "landing" && !value.landingUrl) {
|
|
10967
|
+
return {
|
|
10968
|
+
...value,
|
|
10969
|
+
landingUrl: destination.landingUrl
|
|
10970
|
+
};
|
|
10971
|
+
}
|
|
10972
|
+
return value;
|
|
10952
10973
|
});
|
|
10953
10974
|
var PresignUploadResponseSchema = z52.object({
|
|
10954
10975
|
uploadUrl: z52.string().url().refine((value) => {
|
|
@@ -11060,7 +11081,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
11060
11081
|
throw translateError(e);
|
|
11061
11082
|
}
|
|
11062
11083
|
});
|
|
11063
|
-
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo para reintento
|
|
11084
|
+
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 durante el TTL para reintento: nunca afirmes que una sesi\xF3n no expira. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
|
|
11064
11085
|
try {
|
|
11065
11086
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
11066
11087
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -11333,7 +11354,7 @@ __name(uploadSessionText, "uploadSessionText");
|
|
|
11333
11354
|
function registerMetaUploadTools(server, api) {
|
|
11334
11355
|
server.registerTool("request_meta_media_upload", {
|
|
11335
11356
|
title: "Solicitar carga segura de Meta",
|
|
11336
|
-
description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El
|
|
11357
|
+
description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El App es exclusivamente para cargar archivos: nunca debe pedir ni mostrar portafolio, cuenta publicitaria, p\xE1gina, IDs o JSON. El enlace externo de ProductMaker queda como fallback para hosts sin bridge de archivos. Conserva privadamente el `sessionId`; cuando el usuario termine, llama `check_meta_media_upload`, luego `list_meta_destinations` y pregunta en el chat qu\xE9 `portfolioId`, `adAccountId` y `pageId` desea usar. No muestres el `sessionId`, `stagingAssetId` ni un handoff JSON al usuario. Esta herramienta no publica ni consume cr\xE9ditos.",
|
|
11337
11358
|
inputSchema: MetaUploadSessionToolInputShape,
|
|
11338
11359
|
outputSchema: MetaUploadSessionResponse2,
|
|
11339
11360
|
_meta: {
|
|
@@ -11366,7 +11387,7 @@ function registerMetaUploadTools(server, api) {
|
|
|
11366
11387
|
}
|
|
11367
11388
|
});
|
|
11368
11389
|
server.registerTool("check_meta_media_upload", {
|
|
11369
|
-
description: 'Comprueba si el usuario ya termin\xF3 de subir desde el enlace de ProductMaker. Si status=uploaded,
|
|
11390
|
+
description: 'Comprueba si el usuario ya termin\xF3 de subir desde el App o enlace de ProductMaker. Si status=uploaded, conserva `assets[]` privadamente, llama `list_meta_destinations` y pregunta en lenguaje natural dentro del chat qu\xE9 portafolio, cuenta publicitaria y p\xE1gina desea usar; nunca muestres `sessionId`, `stagingAssetId`, IDs t\xE9cnicos ni un bloque JSON. Despu\xE9s crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como tipo, hasta diez en el batch. Si la carga fue parcial, usa \xFAnicamente los assets confirmados. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.',
|
|
11370
11391
|
inputSchema: MetaUploadStatusInput.shape,
|
|
11371
11392
|
_meta: {
|
|
11372
11393
|
"openai/widgetAccessible": true
|
|
@@ -66,21 +66,16 @@
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
button,
|
|
69
|
-
input
|
|
70
|
-
select,
|
|
71
|
-
textarea {
|
|
69
|
+
input {
|
|
72
70
|
font: inherit;
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
button,
|
|
76
|
-
select,
|
|
77
74
|
a {
|
|
78
75
|
-webkit-tap-highlight-color: transparent;
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
button:focus-visible,
|
|
82
|
-
select:focus-visible,
|
|
83
|
-
textarea:focus-visible,
|
|
84
79
|
a:focus-visible,
|
|
85
80
|
[tabindex]:focus-visible {
|
|
86
81
|
outline: 3px solid
|
|
@@ -102,73 +97,12 @@
|
|
|
102
97
|
box-shadow: var(--pm-shadow);
|
|
103
98
|
}
|
|
104
99
|
|
|
105
|
-
.
|
|
106
|
-
padding: 19px 22px 14px;
|
|
107
|
-
border-bottom: 1px solid
|
|
108
|
-
color-mix(in srgb, var(--pm-line) 65%, transparent);
|
|
109
|
-
background:
|
|
110
|
-
radial-gradient(
|
|
111
|
-
circle at 100% 0%,
|
|
112
|
-
var(--pm-primary-soft),
|
|
113
|
-
transparent 48%
|
|
114
|
-
),
|
|
115
|
-
var(--pm-card);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.upload-header {
|
|
119
|
-
display: flex;
|
|
120
|
-
align-items: center;
|
|
121
|
-
justify-content: space-between;
|
|
122
|
-
gap: 12px;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.upload-title {
|
|
126
|
-
min-width: 0;
|
|
127
|
-
margin: 0;
|
|
128
|
-
overflow: hidden;
|
|
129
|
-
font-size: clamp(17px, 3vw, 24px);
|
|
130
|
-
font-weight: 760;
|
|
131
|
-
letter-spacing: -0.025em;
|
|
132
|
-
text-overflow: ellipsis;
|
|
133
|
-
white-space: nowrap;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.brand-mark {
|
|
137
|
-
display: grid;
|
|
138
|
-
width: 34px;
|
|
139
|
-
height: 34px;
|
|
140
|
-
flex: 0 0 auto;
|
|
141
|
-
place-items: center;
|
|
142
|
-
border: 1px solid
|
|
143
|
-
color-mix(in srgb, var(--pm-primary) 20%, var(--pm-line));
|
|
144
|
-
border-radius: 50%;
|
|
145
|
-
background: color-mix(
|
|
146
|
-
in srgb,
|
|
147
|
-
var(--pm-primary-soft) 70%,
|
|
148
|
-
var(--pm-card)
|
|
149
|
-
);
|
|
150
|
-
color: var(--pm-primary-strong);
|
|
151
|
-
font-size: 20px;
|
|
152
|
-
font-weight: 800;
|
|
153
|
-
line-height: 1;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.session-row {
|
|
100
|
+
.upload-feedback {
|
|
157
101
|
display: flex;
|
|
158
|
-
min-height: 0;
|
|
159
102
|
align-items: center;
|
|
103
|
+
flex-wrap: wrap;
|
|
160
104
|
gap: 10px;
|
|
161
|
-
margin-top:
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.session-id {
|
|
165
|
-
overflow: hidden;
|
|
166
|
-
max-width: 100%;
|
|
167
|
-
color: var(--pm-muted);
|
|
168
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
169
|
-
font-size: 10px;
|
|
170
|
-
text-overflow: ellipsis;
|
|
171
|
-
white-space: nowrap;
|
|
105
|
+
margin-top: 10px;
|
|
172
106
|
}
|
|
173
107
|
|
|
174
108
|
.fallback-link {
|
|
@@ -212,20 +146,23 @@
|
|
|
212
146
|
}
|
|
213
147
|
|
|
214
148
|
.section {
|
|
215
|
-
padding:
|
|
149
|
+
padding: 18px;
|
|
216
150
|
}
|
|
217
151
|
|
|
218
|
-
.
|
|
219
|
-
|
|
152
|
+
.media-grid {
|
|
153
|
+
display: grid;
|
|
154
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
155
|
+
gap: 12px;
|
|
156
|
+
align-items: start;
|
|
220
157
|
}
|
|
221
158
|
|
|
222
159
|
.dropzone {
|
|
223
160
|
display: grid;
|
|
224
|
-
|
|
161
|
+
aspect-ratio: 1;
|
|
162
|
+
min-width: 0;
|
|
225
163
|
place-items: center;
|
|
226
|
-
padding: 20px;
|
|
227
164
|
border: 2px dashed var(--pm-line);
|
|
228
|
-
border-radius:
|
|
165
|
+
border-radius: 16px;
|
|
229
166
|
background: color-mix(
|
|
230
167
|
in srgb,
|
|
231
168
|
var(--pm-card-muted) 62%,
|
|
@@ -251,13 +188,11 @@
|
|
|
251
188
|
|
|
252
189
|
.drop-trigger {
|
|
253
190
|
display: grid;
|
|
254
|
-
width:
|
|
255
|
-
|
|
191
|
+
width: 100%;
|
|
192
|
+
height: 100%;
|
|
256
193
|
place-items: center;
|
|
257
|
-
align-content: center;
|
|
258
|
-
gap: 16px;
|
|
259
194
|
border: 0;
|
|
260
|
-
border-radius:
|
|
195
|
+
border-radius: 14px;
|
|
261
196
|
background: transparent;
|
|
262
197
|
color: var(--pm-text);
|
|
263
198
|
cursor: pointer;
|
|
@@ -269,26 +204,19 @@
|
|
|
269
204
|
|
|
270
205
|
.drop-icon {
|
|
271
206
|
display: grid;
|
|
272
|
-
width:
|
|
273
|
-
height:
|
|
207
|
+
width: 48px;
|
|
208
|
+
height: 48px;
|
|
274
209
|
place-items: center;
|
|
275
210
|
border: 1px solid var(--pm-line);
|
|
276
211
|
border-radius: 50%;
|
|
277
212
|
background: var(--pm-card);
|
|
278
213
|
box-shadow: 0 7px 20px rgb(27 36 66 / 8%);
|
|
279
214
|
color: var(--pm-text);
|
|
280
|
-
font-size:
|
|
215
|
+
font-size: 29px;
|
|
281
216
|
font-weight: 350;
|
|
282
217
|
line-height: 1;
|
|
283
218
|
}
|
|
284
219
|
|
|
285
|
-
.drop-title {
|
|
286
|
-
margin: 0;
|
|
287
|
-
font-size: 18px;
|
|
288
|
-
font-weight: 520;
|
|
289
|
-
letter-spacing: -0.02em;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
220
|
.drop-help {
|
|
293
221
|
position: absolute;
|
|
294
222
|
width: 1px;
|
|
@@ -309,47 +237,6 @@
|
|
|
309
237
|
clip-path: inset(50%);
|
|
310
238
|
}
|
|
311
239
|
|
|
312
|
-
.button {
|
|
313
|
-
display: inline-flex;
|
|
314
|
-
min-height: 42px;
|
|
315
|
-
align-items: center;
|
|
316
|
-
justify-content: center;
|
|
317
|
-
gap: 8px;
|
|
318
|
-
padding: 10px 16px;
|
|
319
|
-
border: 1px solid transparent;
|
|
320
|
-
border-radius: 12px;
|
|
321
|
-
background: var(--pm-primary);
|
|
322
|
-
color: #ffffff;
|
|
323
|
-
cursor: pointer;
|
|
324
|
-
font-size: 13px;
|
|
325
|
-
font-weight: 780;
|
|
326
|
-
line-height: 1;
|
|
327
|
-
transition:
|
|
328
|
-
background 140ms ease,
|
|
329
|
-
opacity 140ms ease,
|
|
330
|
-
transform 140ms ease;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.button:hover:not(:disabled) {
|
|
334
|
-
background: var(--pm-primary-strong);
|
|
335
|
-
transform: translateY(-1px);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
.button:disabled {
|
|
339
|
-
cursor: not-allowed;
|
|
340
|
-
opacity: 0.5;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
.button.secondary {
|
|
344
|
-
border-color: var(--pm-line);
|
|
345
|
-
background: var(--pm-card);
|
|
346
|
-
color: var(--pm-text);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
.button.secondary:hover:not(:disabled) {
|
|
350
|
-
background: var(--pm-card-muted);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
240
|
.batch-error {
|
|
354
241
|
display: none;
|
|
355
242
|
margin: 10px 0 0;
|
|
@@ -362,10 +249,7 @@
|
|
|
362
249
|
}
|
|
363
250
|
|
|
364
251
|
.file-list {
|
|
365
|
-
display:
|
|
366
|
-
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
|
367
|
-
gap: 12px;
|
|
368
|
-
margin-top: 14px;
|
|
252
|
+
display: contents;
|
|
369
253
|
}
|
|
370
254
|
|
|
371
255
|
.file-card {
|
|
@@ -501,63 +385,11 @@
|
|
|
501
385
|
transition: width 120ms linear;
|
|
502
386
|
}
|
|
503
387
|
|
|
504
|
-
.destination-grid {
|
|
505
|
-
display: grid;
|
|
506
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
507
|
-
gap: 9px;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
.field {
|
|
511
|
-
display: grid;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
select,
|
|
515
|
-
textarea {
|
|
516
|
-
width: 100%;
|
|
517
|
-
border: 1px solid var(--pm-line);
|
|
518
|
-
border-radius: 11px;
|
|
519
|
-
background: var(--pm-card);
|
|
520
|
-
color: var(--pm-text);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
select {
|
|
524
|
-
min-height: 42px;
|
|
525
|
-
padding: 8px 30px 8px 10px;
|
|
526
|
-
font-size: 12px;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
textarea {
|
|
530
|
-
min-height: 135px;
|
|
531
|
-
padding: 12px;
|
|
532
|
-
resize: vertical;
|
|
533
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
534
|
-
font-size: 11px;
|
|
535
|
-
line-height: 1.5;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
.destination-actions,
|
|
539
|
-
.handoff-actions {
|
|
540
|
-
display: flex;
|
|
541
|
-
flex-wrap: wrap;
|
|
542
|
-
gap: 10px;
|
|
543
|
-
margin-top: 12px;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
388
|
.compatibility-control {
|
|
389
|
+
/* Retained for older hosts; recovery is visible only through the secure fallback. */
|
|
547
390
|
display: none !important;
|
|
548
391
|
}
|
|
549
392
|
|
|
550
|
-
.sr-only {
|
|
551
|
-
position: absolute;
|
|
552
|
-
width: 1px;
|
|
553
|
-
height: 1px;
|
|
554
|
-
padding: 0;
|
|
555
|
-
overflow: hidden;
|
|
556
|
-
clip: rect(0, 0, 0, 0);
|
|
557
|
-
white-space: nowrap;
|
|
558
|
-
border: 0;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
393
|
[hidden] {
|
|
562
394
|
display: none !important;
|
|
563
395
|
}
|
|
@@ -571,23 +403,13 @@
|
|
|
571
403
|
border-radius: 20px;
|
|
572
404
|
}
|
|
573
405
|
|
|
574
|
-
.hero,
|
|
575
406
|
.section {
|
|
576
407
|
padding-right: 14px;
|
|
577
408
|
padding-left: 14px;
|
|
578
409
|
}
|
|
579
410
|
|
|
580
|
-
.
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
.destination-grid {
|
|
585
|
-
grid-template-columns: 1fr;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
.destination-actions .button,
|
|
589
|
-
.handoff-actions .button {
|
|
590
|
-
width: 100%;
|
|
411
|
+
.media-grid {
|
|
412
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
591
413
|
}
|
|
592
414
|
}
|
|
593
415
|
|
|
@@ -604,71 +426,46 @@
|
|
|
604
426
|
<body>
|
|
605
427
|
<main class="app" data-testid="meta-upload-root">
|
|
606
428
|
<article class="card">
|
|
607
|
-
<header class="hero">
|
|
608
|
-
<div class="upload-header">
|
|
609
|
-
<p class="upload-title" data-testid="meta-upload-title">
|
|
610
|
-
Upload image
|
|
611
|
-
</p>
|
|
612
|
-
<span class="brand-mark" aria-label="ProductMaker" role="img"
|
|
613
|
-
>↗</span
|
|
614
|
-
>
|
|
615
|
-
</div>
|
|
616
|
-
<div class="session-row">
|
|
617
|
-
<code class="session-id" data-testid="meta-upload-session-id" hidden
|
|
618
|
-
>Esperando sesión…</code
|
|
619
|
-
>
|
|
620
|
-
<a
|
|
621
|
-
class="fallback-link"
|
|
622
|
-
data-testid="meta-upload-fallback-link"
|
|
623
|
-
href="#"
|
|
624
|
-
target="_blank"
|
|
625
|
-
rel="noopener noreferrer"
|
|
626
|
-
hidden
|
|
627
|
-
>Abrir carga segura en otra pestaña</a
|
|
628
|
-
>
|
|
629
|
-
</div>
|
|
630
|
-
<p
|
|
631
|
-
class="status"
|
|
632
|
-
data-testid="meta-upload-session-status"
|
|
633
|
-
aria-live="polite"
|
|
634
|
-
></p>
|
|
635
|
-
</header>
|
|
636
|
-
|
|
637
429
|
<section class="section" aria-label="Upload media">
|
|
638
|
-
<div
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
430
|
+
<div class="media-grid">
|
|
431
|
+
<div
|
|
432
|
+
class="file-list"
|
|
433
|
+
data-testid="meta-upload-file-list"
|
|
434
|
+
aria-live="polite"
|
|
435
|
+
></div>
|
|
436
|
+
<div
|
|
437
|
+
class="dropzone"
|
|
438
|
+
data-testid="meta-upload-dropzone"
|
|
439
|
+
data-dragging="false"
|
|
440
|
+
aria-disabled="true"
|
|
441
|
+
aria-describedby="meta-upload-constraints"
|
|
650
442
|
>
|
|
651
|
-
<
|
|
652
|
-
|
|
653
|
-
|
|
443
|
+
<button
|
|
444
|
+
class="drop-trigger"
|
|
445
|
+
type="button"
|
|
446
|
+
data-testid="meta-upload-add-tile"
|
|
447
|
+
aria-label="Agregar otro archivo"
|
|
448
|
+
disabled
|
|
654
449
|
>
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
450
|
+
<span class="drop-icon" aria-hidden="true">+</span>
|
|
451
|
+
</button>
|
|
452
|
+
<button
|
|
453
|
+
class="compatibility-control"
|
|
454
|
+
type="button"
|
|
455
|
+
data-testid="meta-upload-library-btn"
|
|
456
|
+
hidden
|
|
457
|
+
disabled
|
|
458
|
+
aria-label="Choose from ChatGPT library"
|
|
459
|
+
></button>
|
|
460
|
+
<input
|
|
461
|
+
class="native-input"
|
|
462
|
+
data-testid="meta-upload-input"
|
|
463
|
+
type="file"
|
|
464
|
+
accept="image/jpeg,image/png,image/webp,video/mp4,video/quicktime,video/webm"
|
|
465
|
+
multiple
|
|
466
|
+
disabled
|
|
467
|
+
/>
|
|
468
|
+
</div>
|
|
672
469
|
</div>
|
|
673
470
|
<p class="drop-help" id="meta-upload-constraints">
|
|
674
471
|
JPEG, PNG, WebP, MP4, MOV o WebM. Hasta 10 archivos de 350 MB; las
|
|
@@ -680,90 +477,21 @@
|
|
|
680
477
|
role="alert"
|
|
681
478
|
aria-live="assertive"
|
|
682
479
|
></p>
|
|
683
|
-
<div
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
type="button"
|
|
691
|
-
data-testid="meta-upload-pending-refresh"
|
|
692
|
-
hidden
|
|
693
|
-
aria-label="Refresh upload status"
|
|
694
|
-
></button>
|
|
695
|
-
</section>
|
|
696
|
-
|
|
697
|
-
<section
|
|
698
|
-
class="section"
|
|
699
|
-
data-testid="meta-upload-destination-section"
|
|
700
|
-
aria-label="Meta destination"
|
|
701
|
-
hidden
|
|
702
|
-
>
|
|
703
|
-
<div class="destination-grid">
|
|
704
|
-
<div class="field">
|
|
705
|
-
<label class="sr-only" for="portfolio-select">Portfolio</label>
|
|
706
|
-
<select
|
|
707
|
-
id="portfolio-select"
|
|
708
|
-
data-testid="meta-upload-portfolio-select"
|
|
709
|
-
>
|
|
710
|
-
<option value="">Portfolio</option>
|
|
711
|
-
</select>
|
|
712
|
-
</div>
|
|
713
|
-
<div class="field">
|
|
714
|
-
<label class="sr-only" for="ad-account-select">Ad account</label>
|
|
715
|
-
<select
|
|
716
|
-
id="ad-account-select"
|
|
717
|
-
data-testid="meta-upload-ad-account-select"
|
|
718
|
-
disabled
|
|
719
|
-
>
|
|
720
|
-
<option value="">Ad account</option>
|
|
721
|
-
</select>
|
|
722
|
-
</div>
|
|
723
|
-
<div class="field">
|
|
724
|
-
<label class="sr-only" for="page-select">Facebook Page</label>
|
|
725
|
-
<select id="page-select" data-testid="meta-upload-page-select">
|
|
726
|
-
<option value="">Facebook Page</option>
|
|
727
|
-
</select>
|
|
728
|
-
</div>
|
|
729
|
-
</div>
|
|
730
|
-
<div class="destination-actions">
|
|
731
|
-
<button
|
|
732
|
-
class="button"
|
|
733
|
-
type="button"
|
|
734
|
-
data-testid="meta-upload-submit-destination"
|
|
735
|
-
disabled
|
|
736
|
-
>
|
|
737
|
-
Continue
|
|
738
|
-
</button>
|
|
739
|
-
<button
|
|
740
|
-
class="compatibility-control"
|
|
741
|
-
type="button"
|
|
742
|
-
data-testid="meta-upload-check-status"
|
|
480
|
+
<div class="upload-feedback">
|
|
481
|
+
<a
|
|
482
|
+
class="fallback-link"
|
|
483
|
+
data-testid="meta-upload-fallback-link"
|
|
484
|
+
href="#"
|
|
485
|
+
target="_blank"
|
|
486
|
+
rel="noopener noreferrer"
|
|
743
487
|
hidden
|
|
744
|
-
|
|
745
|
-
></button>
|
|
746
|
-
</div>
|
|
747
|
-
</section>
|
|
748
|
-
|
|
749
|
-
<section
|
|
750
|
-
class="section"
|
|
751
|
-
data-testid="meta-upload-handoff-section"
|
|
752
|
-
aria-labelledby="handoff-heading"
|
|
753
|
-
hidden
|
|
754
|
-
>
|
|
755
|
-
<p id="handoff-heading" class="section-copy muted">
|
|
756
|
-
Copy this handoff into the chat.
|
|
757
|
-
</p>
|
|
758
|
-
<textarea data-testid="meta-upload-handoff-copy" readonly></textarea>
|
|
759
|
-
<div class="handoff-actions">
|
|
760
|
-
<button
|
|
761
|
-
class="button secondary"
|
|
762
|
-
type="button"
|
|
763
|
-
data-testid="meta-upload-copy-btn"
|
|
488
|
+
>Abrir carga segura en otra pestaña</a
|
|
764
489
|
>
|
|
765
|
-
|
|
766
|
-
|
|
490
|
+
<p
|
|
491
|
+
class="status"
|
|
492
|
+
data-testid="meta-upload-session-status"
|
|
493
|
+
aria-live="polite"
|
|
494
|
+
></p>
|
|
767
495
|
</div>
|
|
768
496
|
</section>
|
|
769
497
|
</article>
|
|
@@ -801,10 +529,6 @@
|
|
|
801
529
|
|
|
802
530
|
const elements = {
|
|
803
531
|
root: document.querySelector('[data-testid="meta-upload-root"]'),
|
|
804
|
-
title: document.querySelector('[data-testid="meta-upload-title"]'),
|
|
805
|
-
sessionId: document.querySelector(
|
|
806
|
-
'[data-testid="meta-upload-session-id"]',
|
|
807
|
-
),
|
|
808
532
|
fallbackLink: document.querySelector(
|
|
809
533
|
'[data-testid="meta-upload-fallback-link"]',
|
|
810
534
|
),
|
|
@@ -814,11 +538,8 @@
|
|
|
814
538
|
dropzone: document.querySelector(
|
|
815
539
|
'[data-testid="meta-upload-dropzone"]',
|
|
816
540
|
),
|
|
817
|
-
dropTitle: document.querySelector(
|
|
818
|
-
'[data-testid="meta-upload-drop-title"]',
|
|
819
|
-
),
|
|
820
541
|
pickerButton: document.querySelector(
|
|
821
|
-
'[data-testid="meta-upload-
|
|
542
|
+
'[data-testid="meta-upload-add-tile"]',
|
|
822
543
|
),
|
|
823
544
|
libraryPickerButton: document.querySelector(
|
|
824
545
|
'[data-testid="meta-upload-library-btn"]',
|
|
@@ -830,36 +551,6 @@
|
|
|
830
551
|
fileList: document.querySelector(
|
|
831
552
|
'[data-testid="meta-upload-file-list"]',
|
|
832
553
|
),
|
|
833
|
-
destinationSection: document.querySelector(
|
|
834
|
-
'[data-testid="meta-upload-destination-section"]',
|
|
835
|
-
),
|
|
836
|
-
portfolio: document.querySelector(
|
|
837
|
-
'[data-testid="meta-upload-portfolio-select"]',
|
|
838
|
-
),
|
|
839
|
-
adAccount: document.querySelector(
|
|
840
|
-
'[data-testid="meta-upload-ad-account-select"]',
|
|
841
|
-
),
|
|
842
|
-
page: document.querySelector(
|
|
843
|
-
'[data-testid="meta-upload-page-select"]',
|
|
844
|
-
),
|
|
845
|
-
submitDestination: document.querySelector(
|
|
846
|
-
'[data-testid="meta-upload-submit-destination"]',
|
|
847
|
-
),
|
|
848
|
-
checkStatus: document.querySelector(
|
|
849
|
-
'[data-testid="meta-upload-check-status"]',
|
|
850
|
-
),
|
|
851
|
-
pendingRefresh: document.querySelector(
|
|
852
|
-
'[data-testid="meta-upload-pending-refresh"]',
|
|
853
|
-
),
|
|
854
|
-
handoffSection: document.querySelector(
|
|
855
|
-
'[data-testid="meta-upload-handoff-section"]',
|
|
856
|
-
),
|
|
857
|
-
handoffCopy: document.querySelector(
|
|
858
|
-
'[data-testid="meta-upload-handoff-copy"]',
|
|
859
|
-
),
|
|
860
|
-
copyButton: document.querySelector(
|
|
861
|
-
'[data-testid="meta-upload-copy-btn"]',
|
|
862
|
-
),
|
|
863
554
|
};
|
|
864
555
|
|
|
865
556
|
const state = {
|
|
@@ -878,7 +569,8 @@
|
|
|
878
569
|
toolInput: {},
|
|
879
570
|
items: [],
|
|
880
571
|
assets: [],
|
|
881
|
-
|
|
572
|
+
completionNotified: false,
|
|
573
|
+
completionNotificationInFlight: false,
|
|
882
574
|
consumedBridgeIds: new Set(),
|
|
883
575
|
consumingBridgeFiles: false,
|
|
884
576
|
bridgeConsumptionQueued: false,
|
|
@@ -926,29 +618,6 @@
|
|
|
926
618
|
return null;
|
|
927
619
|
}
|
|
928
620
|
|
|
929
|
-
function defaultUploadTitle() {
|
|
930
|
-
return state.session?.mediaKind === "video"
|
|
931
|
-
? "Upload video"
|
|
932
|
-
: "Upload image";
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
function updateUploadTitle() {
|
|
936
|
-
const namedItems = state.items.filter(
|
|
937
|
-
(item) =>
|
|
938
|
-
(item.file && typeof item.file.name === "string") ||
|
|
939
|
-
(typeof item.displayName === "string" &&
|
|
940
|
-
item.displayName.length > 0),
|
|
941
|
-
);
|
|
942
|
-
const title =
|
|
943
|
-
namedItems.length === 1
|
|
944
|
-
? namedItems[0].file?.name || namedItems[0].displayName
|
|
945
|
-
: namedItems.length > 1
|
|
946
|
-
? `${namedItems.length} files`
|
|
947
|
-
: defaultUploadTitle();
|
|
948
|
-
elements.title.textContent = title;
|
|
949
|
-
elements.dropTitle.textContent = defaultUploadTitle();
|
|
950
|
-
}
|
|
951
|
-
|
|
952
621
|
function safeSessionLimit() {
|
|
953
622
|
const proposed =
|
|
954
623
|
state.session && Number.isInteger(state.session.maxFiles)
|
|
@@ -1281,32 +950,6 @@
|
|
|
1281
950
|
void processLocalFiles(files);
|
|
1282
951
|
});
|
|
1283
952
|
|
|
1284
|
-
elements.portfolio.addEventListener("change", () => {
|
|
1285
|
-
renderAdAccounts();
|
|
1286
|
-
updateDestinationSubmit();
|
|
1287
|
-
});
|
|
1288
|
-
elements.adAccount.addEventListener(
|
|
1289
|
-
"change",
|
|
1290
|
-
updateDestinationSubmit,
|
|
1291
|
-
);
|
|
1292
|
-
elements.page.addEventListener("change", updateDestinationSubmit);
|
|
1293
|
-
elements.submitDestination.addEventListener(
|
|
1294
|
-
"click",
|
|
1295
|
-
() => void submitDestination(),
|
|
1296
|
-
);
|
|
1297
|
-
elements.checkStatus.addEventListener(
|
|
1298
|
-
"click",
|
|
1299
|
-
() => void refreshSessionStatus(),
|
|
1300
|
-
);
|
|
1301
|
-
elements.pendingRefresh.addEventListener(
|
|
1302
|
-
"click",
|
|
1303
|
-
() => void refreshSessionStatus(),
|
|
1304
|
-
);
|
|
1305
|
-
elements.copyButton.addEventListener(
|
|
1306
|
-
"click",
|
|
1307
|
-
() => void copyHandoff(),
|
|
1308
|
-
);
|
|
1309
|
-
|
|
1310
953
|
state.hostMessageHandler = handleHostMessage;
|
|
1311
954
|
window.addEventListener("message", state.hostMessageHandler);
|
|
1312
955
|
}
|
|
@@ -1440,26 +1083,24 @@
|
|
|
1440
1083
|
if (sessionChanged) {
|
|
1441
1084
|
state.uploadBlocked = false;
|
|
1442
1085
|
state.fallbackRequired = false;
|
|
1086
|
+
state.completionNotified = false;
|
|
1087
|
+
state.completionNotificationInFlight = false;
|
|
1443
1088
|
state.workCancelled = false;
|
|
1444
1089
|
if (state.abortController?.signal.aborted) {
|
|
1445
1090
|
state.abortController = null;
|
|
1446
1091
|
}
|
|
1447
1092
|
}
|
|
1448
1093
|
state.session = session;
|
|
1449
|
-
elements.sessionId.textContent = session.sessionId;
|
|
1450
1094
|
elements.fallbackLink.href = session.uploadPageUrl;
|
|
1451
|
-
updateUploadTitle();
|
|
1452
1095
|
if (sessionExpired()) {
|
|
1453
1096
|
requireFallback(
|
|
1454
1097
|
"La sesión está expirada. Abre el enlace seguro para solicitar una nueva.",
|
|
1455
1098
|
"danger",
|
|
1456
1099
|
true,
|
|
1457
1100
|
);
|
|
1458
|
-
elements.pendingRefresh.hidden = true;
|
|
1459
1101
|
} else {
|
|
1460
1102
|
setFallbackVisibility(state.fallbackRequired);
|
|
1461
1103
|
setSessionStatus("Sesión lista para recibir archivos.", "neutral");
|
|
1462
|
-
elements.pendingRefresh.hidden = true;
|
|
1463
1104
|
}
|
|
1464
1105
|
setPickerAvailability();
|
|
1465
1106
|
void consumePendingBridgeFiles();
|
|
@@ -2444,7 +2085,6 @@
|
|
|
2444
2085
|
fragment.append(card);
|
|
2445
2086
|
});
|
|
2446
2087
|
elements.fileList.replaceChildren(fragment);
|
|
2447
|
-
updateUploadTitle();
|
|
2448
2088
|
scheduleIntrinsicSizeReport(true);
|
|
2449
2089
|
}
|
|
2450
2090
|
|
|
@@ -2458,8 +2098,6 @@
|
|
|
2458
2098
|
|
|
2459
2099
|
async function refreshSessionStatus() {
|
|
2460
2100
|
if (!state.session) return;
|
|
2461
|
-
elements.checkStatus.disabled = true;
|
|
2462
|
-
elements.pendingRefresh.disabled = true;
|
|
2463
2101
|
setSessionStatus("Comprobando el staging privado…", "neutral");
|
|
2464
2102
|
try {
|
|
2465
2103
|
const result = await callReadOnlyTool("check_meta_media_upload", {
|
|
@@ -2474,17 +2112,13 @@
|
|
|
2474
2112
|
"warning",
|
|
2475
2113
|
);
|
|
2476
2114
|
} else {
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
"La sesión sigue pendiente. Termina la carga o vuelve a comprobar.",
|
|
2115
|
+
requireFallback(
|
|
2116
|
+
"La sesión sigue pendiente. Continúa desde el enlace seguro para terminar la carga.",
|
|
2480
2117
|
"warning",
|
|
2481
2118
|
);
|
|
2482
2119
|
}
|
|
2483
|
-
elements.destinationSection.hidden = true;
|
|
2484
|
-
elements.pendingRefresh.hidden = true;
|
|
2485
2120
|
return;
|
|
2486
2121
|
}
|
|
2487
|
-
elements.pendingRefresh.hidden = true;
|
|
2488
2122
|
|
|
2489
2123
|
const partial =
|
|
2490
2124
|
status.status === "pending" ||
|
|
@@ -2501,15 +2135,10 @@
|
|
|
2501
2135
|
setSessionStatus(
|
|
2502
2136
|
partial
|
|
2503
2137
|
? "Carga parcial verificada. Continuaremos solo con los assets listos."
|
|
2504
|
-
: "Carga
|
|
2138
|
+
: "Carga completada.",
|
|
2505
2139
|
partial ? "warning" : "success",
|
|
2506
2140
|
);
|
|
2507
|
-
|
|
2508
|
-
"list_meta_destinations",
|
|
2509
|
-
{},
|
|
2510
|
-
);
|
|
2511
|
-
state.destinations = parseDestinationsResult(destinationsResult);
|
|
2512
|
-
renderDestinations();
|
|
2141
|
+
await notifyChatUploadComplete();
|
|
2513
2142
|
} catch (error) {
|
|
2514
2143
|
const expired =
|
|
2515
2144
|
sessionExpired() ||
|
|
@@ -2522,20 +2151,11 @@
|
|
|
2522
2151
|
? "host_tools_unavailable: este host no permite comprobar el staging desde la App. Usa el enlace seguro."
|
|
2523
2152
|
: "No se pudo comprobar la sesión. Conserva el enlace y vuelve a intentarlo.";
|
|
2524
2153
|
requireFallback(message, "danger", expired);
|
|
2525
|
-
elements.pendingRefresh.hidden = true;
|
|
2526
|
-
} finally {
|
|
2527
|
-
elements.checkStatus.disabled = false;
|
|
2528
|
-
elements.pendingRefresh.hidden = true;
|
|
2529
|
-
elements.pendingRefresh.disabled = false;
|
|
2530
2154
|
}
|
|
2531
2155
|
}
|
|
2532
2156
|
|
|
2533
2157
|
async function callReadOnlyTool(name, args) {
|
|
2534
|
-
if (
|
|
2535
|
-
!["check_meta_media_upload", "list_meta_destinations"].includes(
|
|
2536
|
-
name,
|
|
2537
|
-
)
|
|
2538
|
-
) {
|
|
2158
|
+
if (name !== "check_meta_media_upload") {
|
|
2539
2159
|
throw new Error("tool_not_allowed");
|
|
2540
2160
|
}
|
|
2541
2161
|
const bridge = openAiBridge();
|
|
@@ -2626,173 +2246,43 @@
|
|
|
2626
2246
|
return { status, assets, legacyKindAmbiguous };
|
|
2627
2247
|
}
|
|
2628
2248
|
|
|
2629
|
-
function
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
currency: boundedString(entry.currency, 20),
|
|
2640
|
-
portfolioId: boundedString(entry.portfolioId, 300),
|
|
2641
|
-
accountStatus: Number(entry.accountStatus),
|
|
2642
|
-
})).filter(
|
|
2643
|
-
(entry) => entry.portfolioId && entry.accountStatus === 1,
|
|
2644
|
-
),
|
|
2645
|
-
pages: parseDestinationList(payload.pages, (entry) => ({
|
|
2646
|
-
id: boundedString(entry.id, 300),
|
|
2647
|
-
name: boundedString(entry.name, 300) || "Página sin nombre",
|
|
2648
|
-
})),
|
|
2649
|
-
};
|
|
2650
|
-
}
|
|
2651
|
-
|
|
2652
|
-
function parseDestinationList(value, mapper) {
|
|
2653
|
-
if (!Array.isArray(value)) return [];
|
|
2654
|
-
return value.slice(0, 1000).flatMap((entry) => {
|
|
2655
|
-
if (!isRecord(entry)) return [];
|
|
2656
|
-
const mapped = mapper(entry);
|
|
2657
|
-
return mapped.id ? [mapped] : [];
|
|
2658
|
-
});
|
|
2659
|
-
}
|
|
2660
|
-
|
|
2661
|
-
function renderDestinations() {
|
|
2662
|
-
fillSelect(
|
|
2663
|
-
elements.portfolio,
|
|
2664
|
-
state.destinations.portfolios,
|
|
2665
|
-
"Portfolio",
|
|
2666
|
-
(entry) => entry.name,
|
|
2667
|
-
);
|
|
2668
|
-
fillSelect(
|
|
2669
|
-
elements.page,
|
|
2670
|
-
state.destinations.pages,
|
|
2671
|
-
"Facebook Page",
|
|
2672
|
-
(entry) => entry.name,
|
|
2673
|
-
);
|
|
2674
|
-
renderAdAccounts();
|
|
2675
|
-
elements.destinationSection.hidden = false;
|
|
2676
|
-
updateDestinationSubmit();
|
|
2677
|
-
scheduleIntrinsicSizeReport(true);
|
|
2678
|
-
}
|
|
2679
|
-
|
|
2680
|
-
function renderAdAccounts() {
|
|
2681
|
-
const portfolioId = elements.portfolio.value;
|
|
2682
|
-
const accounts = portfolioId
|
|
2683
|
-
? state.destinations.adAccounts.filter(
|
|
2684
|
-
(account) => account.portfolioId === portfolioId,
|
|
2685
|
-
)
|
|
2686
|
-
: [];
|
|
2687
|
-
fillSelect(
|
|
2688
|
-
elements.adAccount,
|
|
2689
|
-
accounts,
|
|
2690
|
-
portfolioId ? "Ad account" : "Ad account",
|
|
2691
|
-
(entry) =>
|
|
2692
|
-
`${entry.name}${entry.currency ? ` · ${entry.currency}` : ""}`,
|
|
2693
|
-
);
|
|
2694
|
-
elements.adAccount.disabled = !portfolioId || !accounts.length;
|
|
2695
|
-
}
|
|
2696
|
-
|
|
2697
|
-
function fillSelect(select, entries, placeholder, labelFor) {
|
|
2698
|
-
const fragment = document.createDocumentFragment();
|
|
2699
|
-
const empty = document.createElement("option");
|
|
2700
|
-
empty.value = "";
|
|
2701
|
-
empty.textContent = placeholder;
|
|
2702
|
-
fragment.append(empty);
|
|
2703
|
-
for (const entry of entries) {
|
|
2704
|
-
const option = document.createElement("option");
|
|
2705
|
-
option.value = entry.id;
|
|
2706
|
-
option.textContent = labelFor(entry);
|
|
2707
|
-
fragment.append(option);
|
|
2708
|
-
}
|
|
2709
|
-
select.replaceChildren(fragment);
|
|
2710
|
-
select.value = "";
|
|
2711
|
-
}
|
|
2712
|
-
|
|
2713
|
-
function updateDestinationSubmit() {
|
|
2714
|
-
elements.submitDestination.disabled = !(
|
|
2715
|
-
elements.portfolio.value &&
|
|
2716
|
-
elements.adAccount.value &&
|
|
2717
|
-
elements.page.value &&
|
|
2718
|
-
state.assets.length
|
|
2719
|
-
);
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
|
-
function exactHandoff() {
|
|
2723
|
-
return JSON.stringify({
|
|
2724
|
-
sessionId: state.session.sessionId,
|
|
2725
|
-
portfolioId: elements.portfolio.value,
|
|
2726
|
-
adAccountId: elements.adAccount.value,
|
|
2727
|
-
pageId: elements.page.value,
|
|
2728
|
-
assets: state.assets.slice(0, MAX_FILES).map((asset) => ({
|
|
2729
|
-
stagingAssetId: asset.stagingAssetId,
|
|
2730
|
-
mediaKind: asset.mediaKind,
|
|
2731
|
-
})),
|
|
2732
|
-
});
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
async function submitDestination() {
|
|
2736
|
-
if (elements.submitDestination.disabled || !state.session) return;
|
|
2737
|
-
const handoff = exactHandoff();
|
|
2738
|
-
elements.submitDestination.disabled = true;
|
|
2249
|
+
async function notifyChatUploadComplete() {
|
|
2250
|
+
if (
|
|
2251
|
+
state.completionNotified ||
|
|
2252
|
+
state.completionNotificationInFlight ||
|
|
2253
|
+
!state.session
|
|
2254
|
+
)
|
|
2255
|
+
return;
|
|
2256
|
+
state.completionNotificationInFlight = true;
|
|
2257
|
+
const message =
|
|
2258
|
+
"Terminé de subir los archivos. Verifica esta misma sesión de carga y continúa en el chat: muéstrame las opciones disponibles y pregúntame por el portafolio, la cuenta publicitaria y la página de Meta. No publiques nada todavía.";
|
|
2739
2259
|
try {
|
|
2740
2260
|
const bridge = openAiBridge();
|
|
2741
2261
|
if (bridge && typeof bridge.sendFollowUpMessage === "function") {
|
|
2742
|
-
await bridge.sendFollowUpMessage({ prompt:
|
|
2743
|
-
|
|
2744
|
-
"Selección enviada al chat. Espera la confirmación antes de continuar.",
|
|
2745
|
-
"success",
|
|
2746
|
-
);
|
|
2262
|
+
await bridge.sendFollowUpMessage({ prompt: message });
|
|
2263
|
+
state.completionNotified = true;
|
|
2747
2264
|
return;
|
|
2748
2265
|
}
|
|
2749
2266
|
if (state.hostCanMessage) {
|
|
2750
2267
|
await rpcRequest("ui/message", {
|
|
2751
2268
|
role: "user",
|
|
2752
|
-
content: [{ type: "text", text:
|
|
2269
|
+
content: [{ type: "text", text: message }],
|
|
2753
2270
|
});
|
|
2754
|
-
|
|
2755
|
-
"Selección enviada al chat. Espera la confirmación antes de continuar.",
|
|
2756
|
-
"success",
|
|
2757
|
-
);
|
|
2271
|
+
state.completionNotified = true;
|
|
2758
2272
|
return;
|
|
2759
2273
|
}
|
|
2760
|
-
|
|
2274
|
+
setSessionStatus(
|
|
2275
|
+
"Carga completada. Escribe “listo” en el chat para continuar.",
|
|
2276
|
+
"success",
|
|
2277
|
+
);
|
|
2761
2278
|
} catch {
|
|
2762
|
-
|
|
2279
|
+
setSessionStatus(
|
|
2280
|
+
"Carga completada. Escribe “listo” en el chat para continuar.",
|
|
2281
|
+
"success",
|
|
2282
|
+
);
|
|
2763
2283
|
} finally {
|
|
2764
|
-
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
function showCopyableHandoff(handoff) {
|
|
2769
|
-
elements.handoffCopy.value = handoff;
|
|
2770
|
-
elements.handoffSection.hidden = false;
|
|
2771
|
-
setSessionStatus(
|
|
2772
|
-
"Copia el handoff exacto y pégalo en el chat.",
|
|
2773
|
-
"warning",
|
|
2774
|
-
);
|
|
2775
|
-
scheduleIntrinsicSizeReport(true);
|
|
2776
|
-
}
|
|
2777
|
-
|
|
2778
|
-
async function copyHandoff() {
|
|
2779
|
-
const handoff = elements.handoffCopy.value;
|
|
2780
|
-
if (!handoff) return;
|
|
2781
|
-
if (
|
|
2782
|
-
navigator.clipboard &&
|
|
2783
|
-
typeof navigator.clipboard.writeText === "function"
|
|
2784
|
-
) {
|
|
2785
|
-
try {
|
|
2786
|
-
await navigator.clipboard.writeText(handoff);
|
|
2787
|
-
elements.copyButton.textContent = "Copiado";
|
|
2788
|
-
return;
|
|
2789
|
-
} catch {
|
|
2790
|
-
// Selection fallback below.
|
|
2791
|
-
}
|
|
2284
|
+
state.completionNotificationInFlight = false;
|
|
2792
2285
|
}
|
|
2793
|
-
elements.handoffCopy.focus();
|
|
2794
|
-
elements.handoffCopy.select();
|
|
2795
|
-
elements.copyButton.textContent = "Seleccionado para copiar";
|
|
2796
2286
|
}
|
|
2797
2287
|
|
|
2798
2288
|
document.addEventListener("DOMContentLoaded", initialize, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Product Maker MCP server — cost products, generate dropshipping landing pages, UGC videos, and ad creatives, and publish to Shopify and Meta Ads from Claude, ChatGPT, or Gemini.",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"tsx": "^4.16.0",
|
|
65
65
|
"typescript": "^7.0.2",
|
|
66
66
|
"vitest": "^2.1.9",
|
|
67
|
+
"@packages/product-economics": "0.1.0",
|
|
67
68
|
"@packages/product-research": "0.1.0",
|
|
68
|
-
"@packages/types": "0.1.0"
|
|
69
|
-
"@packages/product-economics": "0.1.0"
|
|
69
|
+
"@packages/types": "0.1.0"
|
|
70
70
|
},
|
|
71
71
|
"publishConfig": {
|
|
72
72
|
"access": "public"
|