@productmaker/mcp 0.8.0 → 0.10.0
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 +49 -5
- package/dist/stdio.js +49 -5
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -582,6 +582,10 @@ function resolveVideoTier(tier) {
|
|
|
582
582
|
return tier === "basic" ? "grok" : "standard";
|
|
583
583
|
}
|
|
584
584
|
__name(resolveVideoTier, "resolveVideoTier");
|
|
585
|
+
var VideoResolutionEnum = z.enum([
|
|
586
|
+
"480p",
|
|
587
|
+
"720p"
|
|
588
|
+
]);
|
|
585
589
|
var VideoStyleEnum = z.enum([
|
|
586
590
|
"ugc",
|
|
587
591
|
"cartoon_3d",
|
|
@@ -931,6 +935,7 @@ var CreateProductTaskInput = z2.object({
|
|
|
931
935
|
offer: z2.string().optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
|
|
932
936
|
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). Opciones: "auto", "ugc_dynamic" (UGC testimonial), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones, ideal 30s), "ugc_skeptic_confession" (esc\xE9ptico\u2192conversi\xF3n, ideal 30s), "ugc_demo_review" (demo en uso), "problem_solution" (problema\u2192producto\u2192CTA), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla), "product_showcase" (solo producto, sin actor), "wearable_showcase" (ropa/accesorios puestos). Si el usuario pide algo fuera de esta lista (ej. "estilo anime"), p\xEDdele una opci\xF3n v\xE1lida.'),
|
|
933
937
|
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video (en la web la etiqueta es "\xBFCalidad de video?", 2 opciones \u2014 sin escalera de calidad, son ESTILOS distintos). "basic" = "Realista" (RECOMENDADO por la web): render de 15s con audio nativo, el m\xE1s natural \u2014 S\xD3LO v\xE1lido si la duraci\xF3n del video es 15s. "standard" = "Multiescena": m\xFAltiples tomas, hasta 30s. Si el usuario no menciona estilo, OMITE este campo y el backend usa su default. Para duraci\xF3n 30s s\xF3lo aplica "standard" (Realista est\xE1 vetada).'),
|
|
938
|
+
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render \u2014 aplica S\xD3LO al tier "basic" (Realista); "standard" la deriva solo. En la web se pregunta "\xBFResoluci\xF3n del video?" tras elegir "basic". "720p" = m\xE1xima nitidez (default si se omite); "480p" = m\xE1s econ\xF3mico (~la mitad del precio). Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 720p para basic).'),
|
|
934
939
|
multiAngleVideos: z2.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
|
|
935
940
|
angleNarratives: NarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional, override avanzado). Usa los mismos identificadores de narrativa que videoPreset (ej. "animated_problem_pixar", "review", "ugc_skeptic_confession"). angleNarratives[i] fuerza la narrativa del \xE1ngulo en la posici\xF3n i; si la lista es m\xE1s corta que la cantidad de \xE1ngulos, las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para que haya varios \xE1ngulos. Si se omite, todas las narrativas se auto-seleccionan seg\xFAn el producto.'),
|
|
936
941
|
outputs: z2.array(z2.enum([
|
|
@@ -966,6 +971,17 @@ var PendingActionPayload = z2.discriminatedUnion("kind", [
|
|
|
966
971
|
z2.object({
|
|
967
972
|
kind: z2.literal("product_reference_confirmation"),
|
|
968
973
|
userChoice: ProductReferenceUserChoiceEnum
|
|
974
|
+
}),
|
|
975
|
+
z2.object({
|
|
976
|
+
kind: z2.literal("angle_review_approval"),
|
|
977
|
+
// All edits are optional — empty action body just approves the
|
|
978
|
+
// backend-generated draft as-is. Field shapes mirror ApproveAnglesRequest
|
|
979
|
+
// in @packages/types/wizard.ts (the only path-of-resolution endpoint is
|
|
980
|
+
// POST /v1/tasks/:taskId/approve-angles).
|
|
981
|
+
customAngle: z2.string().min(1).max(480).optional(),
|
|
982
|
+
painPoints: z2.string().max(3e3).optional(),
|
|
983
|
+
benefits: z2.string().max(3e3).optional(),
|
|
984
|
+
features: z2.array(z2.string().min(1).max(300)).max(10).optional()
|
|
969
985
|
})
|
|
970
986
|
]);
|
|
971
987
|
var ResolvePendingActionInput = z2.object({
|
|
@@ -976,7 +992,11 @@ var StatusForResolveSchema = z2.object({
|
|
|
976
992
|
pendingUserAction: z2.object({
|
|
977
993
|
kind: z2.string(),
|
|
978
994
|
candidateGrouping: z2.string().optional(),
|
|
979
|
-
selectedImageUrls: z2.array(z2.string()).optional()
|
|
995
|
+
selectedImageUrls: z2.array(z2.string()).optional(),
|
|
996
|
+
customAngleDraft: z2.string().optional(),
|
|
997
|
+
painPointsDraft: z2.string().optional(),
|
|
998
|
+
benefitsDraft: z2.string().optional(),
|
|
999
|
+
featuresDraft: z2.array(z2.string()).optional()
|
|
980
1000
|
}).optional()
|
|
981
1001
|
}).passthrough();
|
|
982
1002
|
function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
|
|
@@ -1000,6 +1020,11 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1000
1020
|
"options.videoStyle": resolved?.videoStyle,
|
|
1001
1021
|
"options.narrativeStyle": resolved?.narrativeStyle,
|
|
1002
1022
|
"options.videoTier": wireTier,
|
|
1023
|
+
// Resolution is meaningful ONLY for the basic/grok tier; for every
|
|
1024
|
+
// other tier the backend derives it. Forward it ONLY for grok so the
|
|
1025
|
+
// MCP never presents a "user choice where there is none" (the web has
|
|
1026
|
+
// no resolution knob for standard) — for non-grok it's dropped.
|
|
1027
|
+
"options.videoResolution": wireTier === "grok" ? input.videoResolution : void 0,
|
|
1003
1028
|
"options.multiAngleVideos": input.multiAngleVideos,
|
|
1004
1029
|
"options.angleNarratives": input.angleNarratives,
|
|
1005
1030
|
"options.selectedOutputs": input.outputs,
|
|
@@ -1074,7 +1099,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1074
1099
|
throw translateError(e);
|
|
1075
1100
|
}
|
|
1076
1101
|
});
|
|
1077
|
-
server.tool("resolve_pending_action", 'Resuelve una acci\xF3n que el pipeline est\xE1 esperando del usuario. La llamas cuando `get_task_status` devuelve `pendingUserAction` (sin esto la tarea NUNCA avanza por m\xE1s que esperes). El `action.kind` que mandas DEBE coincidir con el `pendingUserAction.kind` que viste en el status \u2014 es el contrato de simetr\xEDa que mantiene este tool como \xFAnica superficie de resoluci\xF3n de gates.\n\nKINDS soportados hoy:\n\n\u2022 `product_reference_confirmation` \u2014 el clasificador no est\xE1 seguro si las fotos son variantes del mismo producto o productos distintos. Payload: `userChoice` \u2208 { "same_product" (mismo producto, fotos son s\xF3lo \xE1ngulos distintos), "variants" (mismo producto, distintos colores/tallas/sabores), "combo_distinct" (son 2 productos distintos vendidos juntos como combo \u2014 marca la tarea como combo), "different_products" (productos distintos y el usuario quiere generar s\xF3lo uno \u2014 el backend bloquea hasta que selecciones im\xE1genes de un solo producto; normalmente NO es la respuesta correcta para destrabar) }. PREG\xDANTALE al usuario antes de invocar \u2014 `pendingUserAction.targetProductLabel` y `reason` te dan contexto para formularle la pregunta en lenguaje natural. Usa SOLO valores presentes en `pendingUserAction.options`.\n\nFuturos kinds (no implementados a\xFAn) se agregar\xE1n como nuevas variantes del `action` discriminated union \u2014 el flujo "lee kind del status \u2192 manda mismo kind aqu\xED" sigue igual.', ResolvePendingActionInput.shape, async (input) => {
|
|
1102
|
+
server.tool("resolve_pending_action", 'Resuelve una acci\xF3n que el pipeline est\xE1 esperando del usuario. La llamas cuando `get_task_status` devuelve `pendingUserAction` (sin esto la tarea NUNCA avanza por m\xE1s que esperes). El `action.kind` que mandas DEBE coincidir con el `pendingUserAction.kind` que viste en el status \u2014 es el contrato de simetr\xEDa que mantiene este tool como \xFAnica superficie de resoluci\xF3n de gates.\n\nKINDS soportados hoy:\n\n\u2022 `product_reference_confirmation` \u2014 el clasificador no est\xE1 seguro si las fotos son variantes del mismo producto o productos distintos. Payload: `userChoice` \u2208 { "same_product" (mismo producto, fotos son s\xF3lo \xE1ngulos distintos), "variants" (mismo producto, distintos colores/tallas/sabores), "combo_distinct" (son 2 productos distintos vendidos juntos como combo \u2014 marca la tarea como combo), "different_products" (productos distintos y el usuario quiere generar s\xF3lo uno \u2014 el backend bloquea hasta que selecciones im\xE1genes de un solo producto; normalmente NO es la respuesta correcta para destrabar) }. PREG\xDANTALE al usuario antes de invocar \u2014 `pendingUserAction.targetProductLabel` y `reason` te dan contexto para formularle la pregunta en lenguaje natural. Usa SOLO valores presentes en `pendingUserAction.options`.\n\n\u2022 `angle_review_approval` \u2014 el usuario opto por `pauseForAngleReview: true` al crear el creativo (en `generate_image_creatives`, `generate_video_creative` o `generate_landing`). La IA ya gener\xF3 el \xE1ngulo + pain points + benefits + features y la tarea est\xE1 pausada esperando que el usuario apruebe o edite antes de renderizar. Lee `customAngleDraft`, `painPointsDraft`, `benefitsDraft`, `featuresDraft` del `pendingUserAction` para mostr\xE1rselos al usuario en lenguaje natural ("la IA propuso este \xE1ngulo: ... \xBFlo apruebas o cambias algo?"). Payload (TODOS opcionales \u2014 sin nada = aprobar tal cual; con campos = aprobar con ediciones): `customAngle` (override del \xE1ngulo), `painPoints`, `benefits`, `features` (array de 1-10 bullets). Llamar sin ning\xFAn campo es v\xE1lido y aprueba el draft sin cambios.\n\nFuturos kinds (no implementados a\xFAn) se agregar\xE1n como nuevas variantes del `action` discriminated union \u2014 el flujo "lee kind del status \u2192 manda mismo kind aqu\xED" sigue igual.', ResolvePendingActionInput.shape, async (input) => {
|
|
1078
1103
|
try {
|
|
1079
1104
|
const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
|
|
1080
1105
|
const status = StatusForResolveSchema.parse(raw);
|
|
@@ -1104,9 +1129,26 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1104
1129
|
});
|
|
1105
1130
|
return asText(r);
|
|
1106
1131
|
}
|
|
1132
|
+
case "angle_review_approval": {
|
|
1133
|
+
const body = {};
|
|
1134
|
+
if (pending.customAngleDraft && pending.customAngleDraft.length > 0) {
|
|
1135
|
+
body.customAngle = pending.customAngleDraft;
|
|
1136
|
+
}
|
|
1137
|
+
if (pending.painPointsDraft != null) body.painPoints = pending.painPointsDraft;
|
|
1138
|
+
if (pending.benefitsDraft != null) body.benefits = pending.benefitsDraft;
|
|
1139
|
+
if (pending.featuresDraft != null) body.features = [
|
|
1140
|
+
...pending.featuresDraft
|
|
1141
|
+
];
|
|
1142
|
+
if (input.action.customAngle != null) body.customAngle = input.action.customAngle;
|
|
1143
|
+
if (input.action.painPoints != null) body.painPoints = input.action.painPoints;
|
|
1144
|
+
if (input.action.benefits != null) body.benefits = input.action.benefits;
|
|
1145
|
+
if (input.action.features != null) body.features = input.action.features;
|
|
1146
|
+
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-angles`, body);
|
|
1147
|
+
return asText(r);
|
|
1148
|
+
}
|
|
1107
1149
|
default: {
|
|
1108
|
-
const _exhaustive = input.action
|
|
1109
|
-
throw new Error(`Unsupported pending action kind: ${String(_exhaustive)}`);
|
|
1150
|
+
const _exhaustive = input.action;
|
|
1151
|
+
throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
|
|
1110
1152
|
}
|
|
1111
1153
|
}
|
|
1112
1154
|
} catch (e) {
|
|
@@ -1141,6 +1183,7 @@ var VideoInput = z3.object({
|
|
|
1141
1183
|
durationSeconds: z3.number().int().min(5).max(60).optional().describe("Opcional. Duraci\xF3n del video en segundos (5-60). Default 15s. Para hooks cortos usa 8, para storytelling usa 30."),
|
|
1142
1184
|
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). Opciones: "auto", "ugc_dynamic" (UGC cl\xE1sico testimonial multi-hook), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones numeradas, recomendado 30s), "ugc_skeptic_confession" (esc\xE9ptico\u2192conversi\xF3n, recomendado 30s), "ugc_demo_review" (demo en uso a lo largo de d\xEDas), "problem_solution" (problema\u2192producto\u2192CTA), "pixar_problem" (problema animado 3D estilo Pixar), "pixar_sassy" (producto que habla con personalidad), "product_showcase" (sin actor, solo producto), "wearable_showcase" (ropa/accesorios puestos).'),
|
|
1143
1185
|
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video (en la web la etiqueta es "\xBFCalidad de video?", 2 opciones \u2014 sin escalera de calidad, son ESTILOS distintos). "basic" = "Realista" (RECOMENDADO por la web): render de 15s con audio nativo, el m\xE1s natural \u2014 S\xD3LO v\xE1lido cuando durationSeconds \u2264 15 (el handler lo valida). "standard" = "Multiescena": m\xFAltiples tomas, hasta 30s. Si la duraci\xF3n es 30s, s\xF3lo aplica "standard". Si el usuario no menciona estilo, OMITE este campo y el backend usa su default.'),
|
|
1186
|
+
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render \u2014 aplica S\xD3LO al tier "basic" (Realista); el tier "standard" la deriva solo. En la web se pregunta "\xBFResoluci\xF3n del video?" tras elegir "basic". "720p" = m\xE1xima nitidez (default si se omite); "480p" = m\xE1s econ\xF3mico (~la mitad del precio). Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 720p para basic).'),
|
|
1144
1187
|
features: z3.array(z3.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
|
|
1145
1188
|
price: CreativeRefs.price,
|
|
1146
1189
|
offer: CreativeRefs.offer,
|
|
@@ -1194,7 +1237,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
1194
1237
|
}
|
|
1195
1238
|
const resolved = resolveVideoPreset(input.videoPreset);
|
|
1196
1239
|
const wireTier = resolveVideoTier(input.videoTier);
|
|
1197
|
-
const { videoTier: _publicTier, ...rest } = input;
|
|
1240
|
+
const { videoTier: _publicTier, videoResolution: _vr, ...rest } = input;
|
|
1198
1241
|
const wirePayload = {
|
|
1199
1242
|
...rest
|
|
1200
1243
|
};
|
|
@@ -1203,6 +1246,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
1203
1246
|
wirePayload.narrativeStyle = resolved.narrativeStyle;
|
|
1204
1247
|
}
|
|
1205
1248
|
if (wireTier !== void 0) wirePayload.videoTier = wireTier;
|
|
1249
|
+
if (wireTier === "grok" && input.videoResolution) wirePayload.videoResolution = input.videoResolution;
|
|
1206
1250
|
return await submitCreative(api, {
|
|
1207
1251
|
path: "/v1/creatives/videos",
|
|
1208
1252
|
estimatedSeconds: 300,
|
package/dist/stdio.js
CHANGED
|
@@ -579,6 +579,10 @@ function resolveVideoTier(tier) {
|
|
|
579
579
|
return tier === "basic" ? "grok" : "standard";
|
|
580
580
|
}
|
|
581
581
|
__name(resolveVideoTier, "resolveVideoTier");
|
|
582
|
+
var VideoResolutionEnum = z.enum([
|
|
583
|
+
"480p",
|
|
584
|
+
"720p"
|
|
585
|
+
]);
|
|
582
586
|
var VideoStyleEnum = z.enum([
|
|
583
587
|
"ugc",
|
|
584
588
|
"cartoon_3d",
|
|
@@ -887,6 +891,7 @@ var CreateProductTaskInput = z2.object({
|
|
|
887
891
|
offer: z2.string().optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
|
|
888
892
|
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). Opciones: "auto", "ugc_dynamic" (UGC testimonial), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones, ideal 30s), "ugc_skeptic_confession" (esc\xE9ptico\u2192conversi\xF3n, ideal 30s), "ugc_demo_review" (demo en uso), "problem_solution" (problema\u2192producto\u2192CTA), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla), "product_showcase" (solo producto, sin actor), "wearable_showcase" (ropa/accesorios puestos). Si el usuario pide algo fuera de esta lista (ej. "estilo anime"), p\xEDdele una opci\xF3n v\xE1lida.'),
|
|
889
893
|
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video (en la web la etiqueta es "\xBFCalidad de video?", 2 opciones \u2014 sin escalera de calidad, son ESTILOS distintos). "basic" = "Realista" (RECOMENDADO por la web): render de 15s con audio nativo, el m\xE1s natural \u2014 S\xD3LO v\xE1lido si la duraci\xF3n del video es 15s. "standard" = "Multiescena": m\xFAltiples tomas, hasta 30s. Si el usuario no menciona estilo, OMITE este campo y el backend usa su default. Para duraci\xF3n 30s s\xF3lo aplica "standard" (Realista est\xE1 vetada).'),
|
|
894
|
+
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render \u2014 aplica S\xD3LO al tier "basic" (Realista); "standard" la deriva solo. En la web se pregunta "\xBFResoluci\xF3n del video?" tras elegir "basic". "720p" = m\xE1xima nitidez (default si se omite); "480p" = m\xE1s econ\xF3mico (~la mitad del precio). Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 720p para basic).'),
|
|
890
895
|
multiAngleVideos: z2.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
|
|
891
896
|
angleNarratives: NarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional, override avanzado). Usa los mismos identificadores de narrativa que videoPreset (ej. "animated_problem_pixar", "review", "ugc_skeptic_confession"). angleNarratives[i] fuerza la narrativa del \xE1ngulo en la posici\xF3n i; si la lista es m\xE1s corta que la cantidad de \xE1ngulos, las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para que haya varios \xE1ngulos. Si se omite, todas las narrativas se auto-seleccionan seg\xFAn el producto.'),
|
|
892
897
|
outputs: z2.array(z2.enum([
|
|
@@ -922,6 +927,17 @@ var PendingActionPayload = z2.discriminatedUnion("kind", [
|
|
|
922
927
|
z2.object({
|
|
923
928
|
kind: z2.literal("product_reference_confirmation"),
|
|
924
929
|
userChoice: ProductReferenceUserChoiceEnum
|
|
930
|
+
}),
|
|
931
|
+
z2.object({
|
|
932
|
+
kind: z2.literal("angle_review_approval"),
|
|
933
|
+
// All edits are optional — empty action body just approves the
|
|
934
|
+
// backend-generated draft as-is. Field shapes mirror ApproveAnglesRequest
|
|
935
|
+
// in @packages/types/wizard.ts (the only path-of-resolution endpoint is
|
|
936
|
+
// POST /v1/tasks/:taskId/approve-angles).
|
|
937
|
+
customAngle: z2.string().min(1).max(480).optional(),
|
|
938
|
+
painPoints: z2.string().max(3e3).optional(),
|
|
939
|
+
benefits: z2.string().max(3e3).optional(),
|
|
940
|
+
features: z2.array(z2.string().min(1).max(300)).max(10).optional()
|
|
925
941
|
})
|
|
926
942
|
]);
|
|
927
943
|
var ResolvePendingActionInput = z2.object({
|
|
@@ -932,7 +948,11 @@ var StatusForResolveSchema = z2.object({
|
|
|
932
948
|
pendingUserAction: z2.object({
|
|
933
949
|
kind: z2.string(),
|
|
934
950
|
candidateGrouping: z2.string().optional(),
|
|
935
|
-
selectedImageUrls: z2.array(z2.string()).optional()
|
|
951
|
+
selectedImageUrls: z2.array(z2.string()).optional(),
|
|
952
|
+
customAngleDraft: z2.string().optional(),
|
|
953
|
+
painPointsDraft: z2.string().optional(),
|
|
954
|
+
benefitsDraft: z2.string().optional(),
|
|
955
|
+
featuresDraft: z2.array(z2.string()).optional()
|
|
936
956
|
}).optional()
|
|
937
957
|
}).passthrough();
|
|
938
958
|
function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
|
|
@@ -956,6 +976,11 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
956
976
|
"options.videoStyle": resolved?.videoStyle,
|
|
957
977
|
"options.narrativeStyle": resolved?.narrativeStyle,
|
|
958
978
|
"options.videoTier": wireTier,
|
|
979
|
+
// Resolution is meaningful ONLY for the basic/grok tier; for every
|
|
980
|
+
// other tier the backend derives it. Forward it ONLY for grok so the
|
|
981
|
+
// MCP never presents a "user choice where there is none" (the web has
|
|
982
|
+
// no resolution knob for standard) — for non-grok it's dropped.
|
|
983
|
+
"options.videoResolution": wireTier === "grok" ? input.videoResolution : void 0,
|
|
959
984
|
"options.multiAngleVideos": input.multiAngleVideos,
|
|
960
985
|
"options.angleNarratives": input.angleNarratives,
|
|
961
986
|
"options.selectedOutputs": input.outputs,
|
|
@@ -1030,7 +1055,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1030
1055
|
throw translateError(e);
|
|
1031
1056
|
}
|
|
1032
1057
|
});
|
|
1033
|
-
server.tool("resolve_pending_action", 'Resuelve una acci\xF3n que el pipeline est\xE1 esperando del usuario. La llamas cuando `get_task_status` devuelve `pendingUserAction` (sin esto la tarea NUNCA avanza por m\xE1s que esperes). El `action.kind` que mandas DEBE coincidir con el `pendingUserAction.kind` que viste en el status \u2014 es el contrato de simetr\xEDa que mantiene este tool como \xFAnica superficie de resoluci\xF3n de gates.\n\nKINDS soportados hoy:\n\n\u2022 `product_reference_confirmation` \u2014 el clasificador no est\xE1 seguro si las fotos son variantes del mismo producto o productos distintos. Payload: `userChoice` \u2208 { "same_product" (mismo producto, fotos son s\xF3lo \xE1ngulos distintos), "variants" (mismo producto, distintos colores/tallas/sabores), "combo_distinct" (son 2 productos distintos vendidos juntos como combo \u2014 marca la tarea como combo), "different_products" (productos distintos y el usuario quiere generar s\xF3lo uno \u2014 el backend bloquea hasta que selecciones im\xE1genes de un solo producto; normalmente NO es la respuesta correcta para destrabar) }. PREG\xDANTALE al usuario antes de invocar \u2014 `pendingUserAction.targetProductLabel` y `reason` te dan contexto para formularle la pregunta en lenguaje natural. Usa SOLO valores presentes en `pendingUserAction.options`.\n\nFuturos kinds (no implementados a\xFAn) se agregar\xE1n como nuevas variantes del `action` discriminated union \u2014 el flujo "lee kind del status \u2192 manda mismo kind aqu\xED" sigue igual.', ResolvePendingActionInput.shape, async (input) => {
|
|
1058
|
+
server.tool("resolve_pending_action", 'Resuelve una acci\xF3n que el pipeline est\xE1 esperando del usuario. La llamas cuando `get_task_status` devuelve `pendingUserAction` (sin esto la tarea NUNCA avanza por m\xE1s que esperes). El `action.kind` que mandas DEBE coincidir con el `pendingUserAction.kind` que viste en el status \u2014 es el contrato de simetr\xEDa que mantiene este tool como \xFAnica superficie de resoluci\xF3n de gates.\n\nKINDS soportados hoy:\n\n\u2022 `product_reference_confirmation` \u2014 el clasificador no est\xE1 seguro si las fotos son variantes del mismo producto o productos distintos. Payload: `userChoice` \u2208 { "same_product" (mismo producto, fotos son s\xF3lo \xE1ngulos distintos), "variants" (mismo producto, distintos colores/tallas/sabores), "combo_distinct" (son 2 productos distintos vendidos juntos como combo \u2014 marca la tarea como combo), "different_products" (productos distintos y el usuario quiere generar s\xF3lo uno \u2014 el backend bloquea hasta que selecciones im\xE1genes de un solo producto; normalmente NO es la respuesta correcta para destrabar) }. PREG\xDANTALE al usuario antes de invocar \u2014 `pendingUserAction.targetProductLabel` y `reason` te dan contexto para formularle la pregunta en lenguaje natural. Usa SOLO valores presentes en `pendingUserAction.options`.\n\n\u2022 `angle_review_approval` \u2014 el usuario opto por `pauseForAngleReview: true` al crear el creativo (en `generate_image_creatives`, `generate_video_creative` o `generate_landing`). La IA ya gener\xF3 el \xE1ngulo + pain points + benefits + features y la tarea est\xE1 pausada esperando que el usuario apruebe o edite antes de renderizar. Lee `customAngleDraft`, `painPointsDraft`, `benefitsDraft`, `featuresDraft` del `pendingUserAction` para mostr\xE1rselos al usuario en lenguaje natural ("la IA propuso este \xE1ngulo: ... \xBFlo apruebas o cambias algo?"). Payload (TODOS opcionales \u2014 sin nada = aprobar tal cual; con campos = aprobar con ediciones): `customAngle` (override del \xE1ngulo), `painPoints`, `benefits`, `features` (array de 1-10 bullets). Llamar sin ning\xFAn campo es v\xE1lido y aprueba el draft sin cambios.\n\nFuturos kinds (no implementados a\xFAn) se agregar\xE1n como nuevas variantes del `action` discriminated union \u2014 el flujo "lee kind del status \u2192 manda mismo kind aqu\xED" sigue igual.', ResolvePendingActionInput.shape, async (input) => {
|
|
1034
1059
|
try {
|
|
1035
1060
|
const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
|
|
1036
1061
|
const status = StatusForResolveSchema.parse(raw);
|
|
@@ -1060,9 +1085,26 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1060
1085
|
});
|
|
1061
1086
|
return asText(r);
|
|
1062
1087
|
}
|
|
1088
|
+
case "angle_review_approval": {
|
|
1089
|
+
const body = {};
|
|
1090
|
+
if (pending.customAngleDraft && pending.customAngleDraft.length > 0) {
|
|
1091
|
+
body.customAngle = pending.customAngleDraft;
|
|
1092
|
+
}
|
|
1093
|
+
if (pending.painPointsDraft != null) body.painPoints = pending.painPointsDraft;
|
|
1094
|
+
if (pending.benefitsDraft != null) body.benefits = pending.benefitsDraft;
|
|
1095
|
+
if (pending.featuresDraft != null) body.features = [
|
|
1096
|
+
...pending.featuresDraft
|
|
1097
|
+
];
|
|
1098
|
+
if (input.action.customAngle != null) body.customAngle = input.action.customAngle;
|
|
1099
|
+
if (input.action.painPoints != null) body.painPoints = input.action.painPoints;
|
|
1100
|
+
if (input.action.benefits != null) body.benefits = input.action.benefits;
|
|
1101
|
+
if (input.action.features != null) body.features = input.action.features;
|
|
1102
|
+
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-angles`, body);
|
|
1103
|
+
return asText(r);
|
|
1104
|
+
}
|
|
1063
1105
|
default: {
|
|
1064
|
-
const _exhaustive = input.action
|
|
1065
|
-
throw new Error(`Unsupported pending action kind: ${String(_exhaustive)}`);
|
|
1106
|
+
const _exhaustive = input.action;
|
|
1107
|
+
throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
|
|
1066
1108
|
}
|
|
1067
1109
|
}
|
|
1068
1110
|
} catch (e) {
|
|
@@ -1097,6 +1139,7 @@ var VideoInput = z3.object({
|
|
|
1097
1139
|
durationSeconds: z3.number().int().min(5).max(60).optional().describe("Opcional. Duraci\xF3n del video en segundos (5-60). Default 15s. Para hooks cortos usa 8, para storytelling usa 30."),
|
|
1098
1140
|
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). Opciones: "auto", "ugc_dynamic" (UGC cl\xE1sico testimonial multi-hook), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones numeradas, recomendado 30s), "ugc_skeptic_confession" (esc\xE9ptico\u2192conversi\xF3n, recomendado 30s), "ugc_demo_review" (demo en uso a lo largo de d\xEDas), "problem_solution" (problema\u2192producto\u2192CTA), "pixar_problem" (problema animado 3D estilo Pixar), "pixar_sassy" (producto que habla con personalidad), "product_showcase" (sin actor, solo producto), "wearable_showcase" (ropa/accesorios puestos).'),
|
|
1099
1141
|
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video (en la web la etiqueta es "\xBFCalidad de video?", 2 opciones \u2014 sin escalera de calidad, son ESTILOS distintos). "basic" = "Realista" (RECOMENDADO por la web): render de 15s con audio nativo, el m\xE1s natural \u2014 S\xD3LO v\xE1lido cuando durationSeconds \u2264 15 (el handler lo valida). "standard" = "Multiescena": m\xFAltiples tomas, hasta 30s. Si la duraci\xF3n es 30s, s\xF3lo aplica "standard". Si el usuario no menciona estilo, OMITE este campo y el backend usa su default.'),
|
|
1142
|
+
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render \u2014 aplica S\xD3LO al tier "basic" (Realista); el tier "standard" la deriva solo. En la web se pregunta "\xBFResoluci\xF3n del video?" tras elegir "basic". "720p" = m\xE1xima nitidez (default si se omite); "480p" = m\xE1s econ\xF3mico (~la mitad del precio). Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 720p para basic).'),
|
|
1100
1143
|
features: z3.array(z3.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
|
|
1101
1144
|
price: CreativeRefs.price,
|
|
1102
1145
|
offer: CreativeRefs.offer,
|
|
@@ -1150,7 +1193,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
1150
1193
|
}
|
|
1151
1194
|
const resolved = resolveVideoPreset(input.videoPreset);
|
|
1152
1195
|
const wireTier = resolveVideoTier(input.videoTier);
|
|
1153
|
-
const { videoTier: _publicTier, ...rest } = input;
|
|
1196
|
+
const { videoTier: _publicTier, videoResolution: _vr, ...rest } = input;
|
|
1154
1197
|
const wirePayload = {
|
|
1155
1198
|
...rest
|
|
1156
1199
|
};
|
|
@@ -1159,6 +1202,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
1159
1202
|
wirePayload.narrativeStyle = resolved.narrativeStyle;
|
|
1160
1203
|
}
|
|
1161
1204
|
if (wireTier !== void 0) wirePayload.videoTier = wireTier;
|
|
1205
|
+
if (wireTier === "grok" && input.videoResolution) wirePayload.videoResolution = input.videoResolution;
|
|
1162
1206
|
return await submitCreative(api, {
|
|
1163
1207
|
path: "/v1/creatives/videos",
|
|
1164
1208
|
estimatedSeconds: 300,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Product Maker MCP server — connect Claude Desktop, Claude Code, ChatGPT, or Gemini to Product Maker AI to generate dropshipping landing pages, UGC videos, and ad creatives, and publish to Shopify and Meta Ads from your AI assistant.",
|