@hubfluencer/mcp 0.2.0 → 0.4.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/README.md +12 -3
- package/dist/index.js +449 -25
- package/package.json +1 -1
- package/src/index.ts +932 -32
- package/src/uploads.ts +25 -0
package/dist/index.js
CHANGED
|
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
19
37
|
var __export = (target, all) => {
|
|
20
38
|
for (var name in all)
|
|
21
39
|
__defProp(target, name, {
|
|
22
40
|
get: all[name],
|
|
23
41
|
enumerable: true,
|
|
24
42
|
configurable: true,
|
|
25
|
-
set: (
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
26
44
|
});
|
|
27
45
|
};
|
|
28
46
|
|
|
@@ -6285,7 +6303,7 @@ var require_formats = __commonJS((exports) => {
|
|
|
6285
6303
|
}
|
|
6286
6304
|
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
6287
6305
|
function getTime(strictTimeZone) {
|
|
6288
|
-
return function
|
|
6306
|
+
return function time3(str) {
|
|
6289
6307
|
const matches = TIME.exec(str);
|
|
6290
6308
|
if (!matches)
|
|
6291
6309
|
return false;
|
|
@@ -19409,7 +19427,7 @@ function finalize(ctx, schema) {
|
|
|
19409
19427
|
result.$schema = "http://json-schema.org/draft-07/schema#";
|
|
19410
19428
|
} else if (ctx.target === "draft-04") {
|
|
19411
19429
|
result.$schema = "http://json-schema.org/draft-04/schema#";
|
|
19412
|
-
} else if (ctx.target === "openapi-3.0") {}
|
|
19430
|
+
} else if (ctx.target === "openapi-3.0") {}
|
|
19413
19431
|
if (ctx.external?.uri) {
|
|
19414
19432
|
const id = ctx.external.registry.get(schema)?.id;
|
|
19415
19433
|
if (!id)
|
|
@@ -19657,7 +19675,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
|
|
|
19657
19675
|
if (val === undefined) {
|
|
19658
19676
|
if (ctx.unrepresentable === "throw") {
|
|
19659
19677
|
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
19660
|
-
}
|
|
19678
|
+
}
|
|
19661
19679
|
} else if (typeof val === "bigint") {
|
|
19662
19680
|
if (ctx.unrepresentable === "throw") {
|
|
19663
19681
|
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
@@ -29822,6 +29840,14 @@ async function uploadImageFile(client, presignPath, filePath, maxBytes = MAX_IMA
|
|
|
29822
29840
|
await putToPresignedUrl(presigned_url, buf, file.mime);
|
|
29823
29841
|
return { s3_key };
|
|
29824
29842
|
}
|
|
29843
|
+
async function uploadShortPoster(client, slug, filePath) {
|
|
29844
|
+
const file = await resolveReadPath(filePath, IMAGE_EXT_MIME, MAX_IMAGE_BYTES);
|
|
29845
|
+
const presign = await client.post(`/shorts/${slug}/poster/presign`, { content_type: file.mime });
|
|
29846
|
+
const { upload_url, s3_key } = presign;
|
|
29847
|
+
const buf = await readFile(file.path);
|
|
29848
|
+
await putToPresignedUrl(upload_url, buf, file.mime);
|
|
29849
|
+
return { s3_key };
|
|
29850
|
+
}
|
|
29825
29851
|
|
|
29826
29852
|
// src/index.ts
|
|
29827
29853
|
async function fetchStatus(client, kind, slug) {
|
|
@@ -29968,7 +29994,7 @@ var WRITE = {
|
|
|
29968
29994
|
destructiveHint: false,
|
|
29969
29995
|
openWorldHint: true
|
|
29970
29996
|
};
|
|
29971
|
-
var server = new McpServer({ name: "hubfluencer", version: "0.
|
|
29997
|
+
var server = new McpServer({ name: "hubfluencer", version: "0.3.0" });
|
|
29972
29998
|
var registerTool = server.registerTool.bind(server);
|
|
29973
29999
|
async function pollToTerminal(client, kind, slug, extra, budgetMs, intervalMs) {
|
|
29974
30000
|
const deadline = Date.now() + budgetMs;
|
|
@@ -30009,13 +30035,52 @@ async function pollSegmentToTerminal(client, slug, segmentId, extra, budgetMs, i
|
|
|
30009
30035
|
}
|
|
30010
30036
|
registerTool("make_video", {
|
|
30011
30037
|
title: "Make a video from a prompt (one shot)",
|
|
30012
|
-
description: "The simplest path: give a prompt, get a finished MP4. Creates the project (free), PRICES it against " + "your live credit balance, then — only if it's affordable and within max_credits — starts generation, " + "polls to completion (emitting progress), and (if save_path is given) downloads the result. " + "Spends credits (15 for a short; a multi-scene editor ad ~28). Pass dry_run:true to preview the cost " + "WITHOUT charging (returns {estimated_credits, available_credits, slug}); pass max_credits to cap the " + "spend. kind defaults to 'auto' — a multi-scene editor ad for ad/promo/story briefs, a single-clip short " + "for simple/short ones; the chosen kind is reported back as kind_inferred. If it returns terminal=false " + "the render is still running — call wait_for_completion with the returned slug to finish.",
|
|
30038
|
+
description: "The simplest path: give a prompt, get a finished MP4. Creates the project (free), PRICES it against " + "your live credit balance, then — only if it's affordable and within max_credits — starts generation, " + "polls to completion (emitting progress), and (if save_path is given) downloads the result. " + "Spends credits (15 for a short; a multi-scene editor ad ~28). Pass dry_run:true to preview the cost " + "WITHOUT charging (returns {estimated_credits, available_credits, slug}); pass max_credits to cap the " + "spend. kind defaults to 'auto' — a multi-scene editor ad for ad/promo/story briefs, a single-clip short " + "for simple/short ones; the chosen kind is reported back as kind_inferred. If it returns terminal=false " + "the render is still running — call wait_for_completion with the returned slug to finish. " + "BE PROACTIVE WITH BRANDING: pass headline (the on-screen TITLE) and subheadline (secondary title) plus " + "music_vibe/visual_language so a one-shot short isn't bare — these apply to SHORTS (music_vibe/headline/subheadline " + "are ignored for editor; theme applies to editor and is legacy-only for shorts). For richer branding — a product image, brand logo, or " + "closing card — drive the granular path instead: create_short + set_short_product/set_short_poster, or " + "create_editor_draft + set_product/set_logo/set_closing_image, then start_autopilot/generate_short.",
|
|
30013
30039
|
inputSchema: {
|
|
30014
30040
|
prompt: exports_external.string().describe("What the ad/video should be about (min 10 chars)"),
|
|
30015
30041
|
kind: exports_external.string().optional().describe("'short' (fast, 1 clip), 'editor' (multi-scene), or 'auto' (default — inferred)"),
|
|
30016
30042
|
language: exports_external.string().optional().describe('Language code, e.g. "en" (default)'),
|
|
30017
30043
|
aspect: exports_external.string().optional().describe("Aspect ratio for editor: 9:16 (default), 16:9, or 1:1"),
|
|
30018
30044
|
voice_id: exports_external.string().optional().describe("Preferred narration voice id for editor ads (see list_voices); shorts have no voiceover"),
|
|
30045
|
+
headline: exports_external.string().optional().describe("SHORTS only: the on-screen TITLE overlay (≤160). Set this so the short isn't bare."),
|
|
30046
|
+
subheadline: exports_external.string().optional().describe("SHORTS only: the secondary title / supporting line (≤200)"),
|
|
30047
|
+
text_beats: exports_external.array(exports_external.string()).optional().describe("SHORTS only: caption beats shown sequentially instead of a static subheadline (≤8, each ≤120 chars)."),
|
|
30048
|
+
headline_color: exports_external.string().optional().describe("SHORTS only: headline hex color, e.g. #ffffff"),
|
|
30049
|
+
subheadline_color: exports_external.string().optional().describe("SHORTS only: subheadline hex color, e.g. #ffffff"),
|
|
30050
|
+
accent_color: exports_external.string().optional().describe("SHORTS only: 6-digit brand accent hex color, e.g. #09EFBE"),
|
|
30051
|
+
offer_text: exports_external.string().optional().describe("SHORTS only: optional offer chip, e.g. -40% (≤16 chars)"),
|
|
30052
|
+
cta_text: exports_external.string().optional().describe("SHORTS only: optional CTA pill, e.g. Shop now (≤24 chars)"),
|
|
30053
|
+
badge_text: exports_external.string().optional().describe("SHORTS only: optional badge stamp, e.g. BEST SELLER (≤24 chars)"),
|
|
30054
|
+
star_rating: exports_external.number().optional().describe("SHORTS only: optional 0..5 star rating under the subheadline"),
|
|
30055
|
+
text_position: exports_external.enum(["top", "center", "bottom"]).optional().describe("SHORTS only: title overlay position (default bottom)"),
|
|
30056
|
+
text_animation: exports_external.enum([
|
|
30057
|
+
"reveal",
|
|
30058
|
+
"typewriter",
|
|
30059
|
+
"fade_in",
|
|
30060
|
+
"pop",
|
|
30061
|
+
"bounce",
|
|
30062
|
+
"word_stagger",
|
|
30063
|
+
"word_spotlight"
|
|
30064
|
+
]).optional().describe("SHORTS only: title overlay animation (default fade_in)"),
|
|
30065
|
+
font_family: exports_external.string().optional().describe("SHORTS only: overlay font family, e.g. ShortFontSpaceGrotesk"),
|
|
30066
|
+
music_vibe: exports_external.string().optional().describe("SHORTS only: music mood — Upbeat (default), Cinematic, Minimal, Luxury, Playful, Jazz"),
|
|
30067
|
+
creative_format: exports_external.enum([
|
|
30068
|
+
"problem_solution",
|
|
30069
|
+
"mistake_fix",
|
|
30070
|
+
"myth_vs_reality",
|
|
30071
|
+
"before_after",
|
|
30072
|
+
"proof_demo",
|
|
30073
|
+
"product_reveal"
|
|
30074
|
+
]).optional().describe("SHORTS only: segment structure. Omit for Auto/generic."),
|
|
30075
|
+
visual_language: exports_external.enum([
|
|
30076
|
+
"kinetic_creator",
|
|
30077
|
+
"premium_editorial",
|
|
30078
|
+
"cinematic_product",
|
|
30079
|
+
"ugc_realism",
|
|
30080
|
+
"startup_explainer",
|
|
30081
|
+
"luxury_minimal"
|
|
30082
|
+
]).optional().describe("SHORTS only: visual style direction and render look. Default app choice is kinetic_creator."),
|
|
30083
|
+
theme: exports_external.string().optional().describe("Visual theme: editor style, and legacy shorts fallback only when visual_language is unset. " + "none (literal — no imposed style), realistic (default), cinematic, " + "anime, sci_fi, fantasy, noir, superhero, horror, mockumentary, sports, gaming, retro_80s, " + "minimalist, cyberpunk"),
|
|
30019
30084
|
save_path: exports_external.string().optional().describe("Optional .mp4 path to download to (confined to HUBFLUENCER_OUTPUT_DIR or cwd)"),
|
|
30020
30085
|
max_wait_seconds: exports_external.number().optional().describe("Block budget seconds (default 240, capped 10–280)"),
|
|
30021
30086
|
dry_run: exports_external.boolean().optional().describe("Preview only: create a free draft, price it, and STOP before spending credits. " + "Returns {estimated_credits, available_credits, slug}. Resume with generate_short / start_autopilot."),
|
|
@@ -30034,15 +30099,36 @@ registerTool("make_video", {
|
|
|
30034
30099
|
resolveSavePath(args.save_path);
|
|
30035
30100
|
let slug;
|
|
30036
30101
|
if (kind === "short") {
|
|
30037
|
-
const created = await client.post("/shorts", {
|
|
30102
|
+
const created = await client.post("/shorts", {
|
|
30103
|
+
product_prompt: args.prompt,
|
|
30104
|
+
language: args.language,
|
|
30105
|
+
headline: args.headline,
|
|
30106
|
+
subheadline: args.subheadline,
|
|
30107
|
+
text_beats: args.text_beats,
|
|
30108
|
+
headline_color: args.headline_color,
|
|
30109
|
+
subheadline_color: args.subheadline_color,
|
|
30110
|
+
accent_color: args.accent_color,
|
|
30111
|
+
offer_text: args.offer_text,
|
|
30112
|
+
cta_text: args.cta_text,
|
|
30113
|
+
badge_text: args.badge_text,
|
|
30114
|
+
star_rating: args.star_rating,
|
|
30115
|
+
short_text_position: args.text_position,
|
|
30116
|
+
short_text_animation: args.text_animation,
|
|
30117
|
+
short_font_family: args.font_family,
|
|
30118
|
+
music_vibe: args.music_vibe,
|
|
30119
|
+
creative_format: args.creative_format,
|
|
30120
|
+
visual_language: args.visual_language,
|
|
30121
|
+
theme: args.theme
|
|
30122
|
+
}, idemKey("make-short", args.prompt, args.language ?? "", args.headline ?? "", args.subheadline ?? "", JSON.stringify(args.text_beats ?? []), args.headline_color ?? "", args.subheadline_color ?? "", args.accent_color ?? "", args.offer_text ?? "", args.cta_text ?? "", args.badge_text ?? "", String(args.star_rating ?? ""), args.text_position ?? "", args.text_animation ?? "", args.font_family ?? "", args.music_vibe ?? "", args.creative_format ?? "", args.visual_language ?? "", args.theme ?? ""));
|
|
30038
30123
|
slug = created.data.slug;
|
|
30039
30124
|
} else {
|
|
30040
30125
|
const created = await client.post("/editor", {
|
|
30041
30126
|
language: args.language ?? "en",
|
|
30042
30127
|
product_prompt: args.prompt,
|
|
30043
30128
|
export_aspect_ratio: args.aspect,
|
|
30044
|
-
voice_id: args.voice_id
|
|
30045
|
-
|
|
30129
|
+
voice_id: args.voice_id,
|
|
30130
|
+
theme: args.theme
|
|
30131
|
+
}, idemKey("make-editor", args.prompt, args.language ?? "en", args.aspect ?? "", args.voice_id ?? "", args.theme ?? ""));
|
|
30046
30132
|
slug = created.data.slug;
|
|
30047
30133
|
}
|
|
30048
30134
|
const costPath = kind === "short" ? `/shorts/${slug}/cost` : `/editor/${slug}/autopilot/cost`;
|
|
@@ -30101,7 +30187,17 @@ registerTool("get_credits", {
|
|
|
30101
30187
|
description: "Returns the authenticated account's credit balance. A short costs 15 credits.",
|
|
30102
30188
|
inputSchema: {},
|
|
30103
30189
|
annotations: { title: "Get credits", ...RO }
|
|
30104
|
-
}, tool(async (_args, client) =>
|
|
30190
|
+
}, tool(async (_args, client) => {
|
|
30191
|
+
const res = await client.get("/studio/credits");
|
|
30192
|
+
const d = asRecord(asRecord(res).data);
|
|
30193
|
+
return ok({
|
|
30194
|
+
credits: d.credits,
|
|
30195
|
+
spendable_credits: d.spendable_credits,
|
|
30196
|
+
reserved_credits: d.reserved_credits,
|
|
30197
|
+
remaining_reserved_credits: d.remaining_reserved_credits,
|
|
30198
|
+
message: d.message
|
|
30199
|
+
});
|
|
30200
|
+
}));
|
|
30105
30201
|
registerTool("list_voices", {
|
|
30106
30202
|
title: "List voices",
|
|
30107
30203
|
description: "Lists available narration voices (id + name). Pass a voice id as voice_id to create_editor_ad / " + "make_video to pick the narration voice for an editor ad. Shorts have no voiceover.",
|
|
@@ -30141,37 +30237,334 @@ registerTool("list_projects", {
|
|
|
30141
30237
|
}));
|
|
30142
30238
|
registerTool("create_short", {
|
|
30143
30239
|
title: "Create a short (draft)",
|
|
30144
|
-
description: "Creates a short draft from a product prompt (min 10 chars). Returns the slug
|
|
30240
|
+
description: "Creates a short draft from a product prompt (min 10 chars). A short is a 12s vertical (two 6s AI " + "segments + an on-screen title overlay + music; 14s when an end-card poster is set). Returns the slug, " + "costs 0 credits. Follow with generate_short to render. " + "BE PROACTIVE: don't ship a bare clip — set a headline (the on-screen TITLE) and subheadline (secondary " + "title), and pick a music_vibe plus visual_language that fit the brand. To brand it further, attach a product image " + "(set_short_product) or an end-card poster (set_short_poster) before generate_short. (Shorts have no logo " + "overlay — that's an editor-only feature; use create_editor_ad for logo branding.)",
|
|
30145
30241
|
inputSchema: {
|
|
30146
30242
|
product_prompt: exports_external.string().min(10).describe("What the short should be about"),
|
|
30147
30243
|
language: exports_external.string().optional().describe('Language code, e.g. "en" (default)'),
|
|
30148
|
-
|
|
30149
|
-
|
|
30150
|
-
|
|
30151
|
-
|
|
30244
|
+
headline: exports_external.string().max(160).optional().describe("The on-screen TITLE composited over the short (poster text overlay). ≤160 chars. Set this."),
|
|
30245
|
+
subheadline: exports_external.string().max(200).optional().describe("The SECONDARY title / supporting line under the headline. ≤200 chars."),
|
|
30246
|
+
creative_format: exports_external.enum([
|
|
30247
|
+
"problem_solution",
|
|
30248
|
+
"mistake_fix",
|
|
30249
|
+
"myth_vs_reality",
|
|
30250
|
+
"before_after",
|
|
30251
|
+
"proof_demo",
|
|
30252
|
+
"product_reveal"
|
|
30253
|
+
]).optional().describe("Optional structure: problem_solution, mistake_fix, myth_vs_reality, before_after, proof_demo, product_reveal. Omit for Auto."),
|
|
30254
|
+
visual_language: exports_external.enum([
|
|
30255
|
+
"kinetic_creator",
|
|
30256
|
+
"premium_editorial",
|
|
30257
|
+
"cinematic_product",
|
|
30258
|
+
"ugc_realism",
|
|
30259
|
+
"startup_explainer",
|
|
30260
|
+
"luxury_minimal"
|
|
30261
|
+
]).optional().describe("Visual language for Veo direction and render styling. Good default: kinetic_creator."),
|
|
30262
|
+
theme: exports_external.string().optional().describe("Deprecated for shorts: legacy visual theme used only when visual_language is unset."),
|
|
30263
|
+
music_vibe: exports_external.string().optional().describe("Background-music mood. Recognized: Upbeat (default), Cinematic, Minimal, Luxury, Playful, Jazz."),
|
|
30264
|
+
music_instruments: exports_external.array(exports_external.string()).optional().describe('Optional instrument hints, e.g. ["piano", "strings"]'),
|
|
30265
|
+
text_position: exports_external.enum(["top", "center", "bottom"]).optional().describe("Where the title overlay sits (default bottom)"),
|
|
30266
|
+
text_animation: exports_external.enum([
|
|
30267
|
+
"reveal",
|
|
30268
|
+
"typewriter",
|
|
30269
|
+
"fade_in",
|
|
30270
|
+
"pop",
|
|
30271
|
+
"bounce",
|
|
30272
|
+
"word_stagger",
|
|
30273
|
+
"word_spotlight"
|
|
30274
|
+
]).optional().describe("Title overlay animation (default fade_in)"),
|
|
30275
|
+
font_family: exports_external.string().optional().describe("Overlay font (default ShortFontSpaceGrotesk). One of: ShortFontSpaceGrotesk, ShortFontMontserrat, " + "ShortFontTheBold, ShortFontImpact, ShortFontLato, ShortFontAnton, " + "ShortFontBebasNeue, ShortFontOswald, ShortFontArchivoBlack, ShortFontPoppins, ShortFontInter, " + "ShortFontTikTokSans, ShortFontBangers, ShortFontDMSerif, ShortFontPermanentMarker."),
|
|
30276
|
+
headline_color: exports_external.string().optional().describe("Headline hex color, e.g. #ffffff"),
|
|
30277
|
+
subheadline_color: exports_external.string().optional().describe("Subheadline hex color, e.g. #ffffff"),
|
|
30278
|
+
accent_color: exports_external.string().optional().describe("6-digit brand accent hex color, e.g. #09EFBE"),
|
|
30279
|
+
offer_text: exports_external.string().max(16).optional().describe('Optional offer chip, e.g. "-40%" or "2 FOR 1"'),
|
|
30280
|
+
cta_text: exports_external.string().max(24).optional().describe('Optional CTA pill, e.g. "Shop now"'),
|
|
30281
|
+
badge_text: exports_external.string().max(24).optional().describe('Optional badge stamp, e.g. "BEST SELLER"'),
|
|
30282
|
+
star_rating: exports_external.number().min(0).max(5).optional().describe("Optional 0..5 star rating under the subheadline"),
|
|
30283
|
+
text_beats: exports_external.array(exports_external.string().max(120)).max(8).optional().describe("Optional caption beats shown sequentially instead of the static subheadline.")
|
|
30152
30284
|
},
|
|
30153
30285
|
annotations: { title: "Create short", ...WRITE, idempotentHint: true }
|
|
30154
30286
|
}, tool(async (args, client) => {
|
|
30155
|
-
const
|
|
30156
|
-
|
|
30287
|
+
const body = {
|
|
30288
|
+
product_prompt: args.product_prompt
|
|
30289
|
+
};
|
|
30290
|
+
if (args.language !== undefined)
|
|
30291
|
+
body.language = args.language;
|
|
30292
|
+
if (args.headline !== undefined)
|
|
30293
|
+
body.headline = args.headline;
|
|
30294
|
+
if (args.subheadline !== undefined)
|
|
30295
|
+
body.subheadline = args.subheadline;
|
|
30296
|
+
if (args.creative_format !== undefined)
|
|
30297
|
+
body.creative_format = args.creative_format;
|
|
30298
|
+
if (args.visual_language !== undefined)
|
|
30299
|
+
body.visual_language = args.visual_language;
|
|
30300
|
+
if (args.theme !== undefined)
|
|
30301
|
+
body.theme = args.theme;
|
|
30302
|
+
if (args.music_vibe !== undefined)
|
|
30303
|
+
body.music_vibe = args.music_vibe;
|
|
30304
|
+
if (args.music_instruments !== undefined)
|
|
30305
|
+
body.music_instruments = args.music_instruments;
|
|
30306
|
+
if (args.text_position !== undefined)
|
|
30307
|
+
body.short_text_position = args.text_position;
|
|
30308
|
+
if (args.text_animation !== undefined)
|
|
30309
|
+
body.short_text_animation = args.text_animation;
|
|
30310
|
+
if (args.font_family !== undefined)
|
|
30311
|
+
body.short_font_family = args.font_family;
|
|
30312
|
+
if (args.headline_color !== undefined)
|
|
30313
|
+
body.headline_color = args.headline_color;
|
|
30314
|
+
if (args.subheadline_color !== undefined)
|
|
30315
|
+
body.subheadline_color = args.subheadline_color;
|
|
30316
|
+
if (args.accent_color !== undefined)
|
|
30317
|
+
body.accent_color = args.accent_color;
|
|
30318
|
+
if (args.offer_text !== undefined)
|
|
30319
|
+
body.offer_text = args.offer_text;
|
|
30320
|
+
if (args.cta_text !== undefined)
|
|
30321
|
+
body.cta_text = args.cta_text;
|
|
30322
|
+
if (args.badge_text !== undefined)
|
|
30323
|
+
body.badge_text = args.badge_text;
|
|
30324
|
+
if (args.star_rating !== undefined)
|
|
30325
|
+
body.star_rating = args.star_rating;
|
|
30326
|
+
if (args.text_beats !== undefined)
|
|
30327
|
+
body.text_beats = args.text_beats;
|
|
30328
|
+
const res = await client.post("/shorts", body, idemKey("create-short", args.product_prompt, args.language ?? "", args.headline ?? "", args.subheadline ?? "", args.creative_format ?? "", args.visual_language ?? "", args.theme ?? "", args.music_vibe ?? "", JSON.stringify(args.music_instruments ?? []), args.text_position ?? "", args.text_animation ?? "", args.font_family ?? "", args.headline_color ?? "", args.subheadline_color ?? "", args.accent_color ?? "", args.offer_text ?? "", args.cta_text ?? "", args.badge_text ?? "", String(args.star_rating ?? ""), JSON.stringify(args.text_beats ?? [])));
|
|
30329
|
+
return ok({
|
|
30330
|
+
slug: res.data.slug,
|
|
30331
|
+
kind: "short",
|
|
30332
|
+
next: "set_short_product / set_short_poster (optional branding), then generate_short"
|
|
30333
|
+
});
|
|
30157
30334
|
}));
|
|
30158
30335
|
registerTool("generate_short", {
|
|
30159
30336
|
title: "Generate (render) a short",
|
|
30160
|
-
description: "Deducts 15 credits and starts the render pipeline for an existing short.
|
|
30337
|
+
description: "Deducts 15 credits and starts the render pipeline for an existing short. Safe to call again: a " + "duplicate while a render is in flight is reported as in-progress (no double charge), and a failed " + "short can be re-generated. Then poll with get_status or wait_for_completion (kind=short).",
|
|
30161
30338
|
inputSchema: { slug: exports_external.string().describe("Short slug from create_short") },
|
|
30162
30339
|
annotations: { title: "Generate short", ...WRITE, idempotentHint: true }
|
|
30163
30340
|
}, tool(async (args, client) => {
|
|
30164
|
-
|
|
30165
|
-
|
|
30166
|
-
|
|
30341
|
+
try {
|
|
30342
|
+
const res = await client.post(`/shorts/${args.slug}/generate`);
|
|
30343
|
+
const status = normalizeStatus("short", args.slug, asRecord(res).data);
|
|
30344
|
+
return ok(status);
|
|
30345
|
+
} catch (e) {
|
|
30346
|
+
if (e.status === 409) {
|
|
30347
|
+
return ok({
|
|
30348
|
+
kind: "short",
|
|
30349
|
+
slug: args.slug,
|
|
30350
|
+
stage: "processing",
|
|
30351
|
+
terminal: false,
|
|
30352
|
+
ready: false,
|
|
30353
|
+
video_url: null,
|
|
30354
|
+
error: null
|
|
30355
|
+
});
|
|
30356
|
+
}
|
|
30357
|
+
throw e;
|
|
30358
|
+
}
|
|
30359
|
+
}));
|
|
30360
|
+
registerTool("generate_short_text", {
|
|
30361
|
+
title: "Generate short overlay text (AI assist)",
|
|
30362
|
+
description: "Generates editable headline, subheadline, and caption beats for an existing short draft. " + "CONSUMES 1 AI ASSIST (free daily quota) and spends no video credits. The server reads the saved " + "short fields, so update/create the draft with the current product prompt, creative_format, visual_language, " + "language, and seed copy first. On 429 set auto_unlock:true (1 credit -> +10 assists, retried once) or write the text yourself.",
|
|
30363
|
+
inputSchema: {
|
|
30364
|
+
slug: exports_external.string().describe("Short slug from create_short"),
|
|
30365
|
+
auto_unlock: exports_external.boolean().optional().describe("On 429, spend 1 credit to unlock +10 assists and retry once (default false)")
|
|
30366
|
+
},
|
|
30367
|
+
annotations: {
|
|
30368
|
+
title: "Generate short text",
|
|
30369
|
+
...WRITE,
|
|
30370
|
+
idempotentHint: false
|
|
30371
|
+
}
|
|
30372
|
+
}, tool(async (args, client) => {
|
|
30373
|
+
const res = await withAssist(client, args.auto_unlock ?? false, () => client.post(`/shorts/${args.slug}/text/generate`));
|
|
30374
|
+
return ok(asRecord(res).data ?? res);
|
|
30375
|
+
}));
|
|
30376
|
+
registerTool("create_slider", {
|
|
30377
|
+
title: "Create an image slider / carousel (draft)",
|
|
30378
|
+
description: "Creates a carousel draft from a prompt (min 10 chars). One prompt produces N still slides (an AI " + "background + a composited headline/body + optional logo) PLUS a ready-to-post caption and hashtags. " + "Returns the slug, costs 0 credits. Follow with generate_slider to render, then get_slider to read the " + "slide image URLs + caption. mode 'creative' tells a story; 'ad_driven' lists product facts/benefits.",
|
|
30379
|
+
inputSchema: {
|
|
30380
|
+
prompt: exports_external.string().min(10).describe("What the carousel is about"),
|
|
30381
|
+
mode: exports_external.enum(["creative", "ad_driven"]).optional().describe("'creative' (storytelling) or 'ad_driven' (facts about the product). Default creative."),
|
|
30382
|
+
template: exports_external.enum([
|
|
30383
|
+
"boldStatement",
|
|
30384
|
+
"editorialStory",
|
|
30385
|
+
"scrapbook",
|
|
30386
|
+
"featureGrid",
|
|
30387
|
+
"offerCard",
|
|
30388
|
+
"comparison"
|
|
30389
|
+
]).optional().describe("Composition preset. boldStatement/editorialStory/scrapbook are creative; " + "featureGrid/offerCard/comparison are ad-driven. Defaults to the mode default."),
|
|
30390
|
+
slide_count: exports_external.number().optional().describe("Number of slides, integer 3–10 (default 5)"),
|
|
30391
|
+
aspect_ratio: exports_external.enum(["4:5", "1:1", "9:16"]).optional().describe("Canvas ratio (default 4:5, the highest-reach carousel ratio)"),
|
|
30392
|
+
accent_color: exports_external.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Brand accent: must start with #, exactly 6 hex digits, e.g. #09EFBE"),
|
|
30393
|
+
text_position: exports_external.enum(["top", "middle", "bottom"]).optional().describe("Vertical placement of the on-image copy across all slides. Omit for the template's natural placement.")
|
|
30394
|
+
},
|
|
30395
|
+
annotations: { title: "Create slider", ...WRITE, idempotentHint: true }
|
|
30396
|
+
}, tool(async (args, client) => {
|
|
30397
|
+
if (args.slide_count !== undefined) {
|
|
30398
|
+
if (!Number.isInteger(args.slide_count) || args.slide_count < 3 || args.slide_count > 10) {
|
|
30399
|
+
return fail("slide_count must be an integer between 3 and 10");
|
|
30400
|
+
}
|
|
30401
|
+
}
|
|
30402
|
+
const body = { prompt: args.prompt };
|
|
30403
|
+
if (args.mode !== undefined)
|
|
30404
|
+
body.mode = args.mode;
|
|
30405
|
+
if (args.template !== undefined)
|
|
30406
|
+
body.template = args.template;
|
|
30407
|
+
if (args.slide_count !== undefined)
|
|
30408
|
+
body.slide_count = args.slide_count;
|
|
30409
|
+
if (args.aspect_ratio !== undefined)
|
|
30410
|
+
body.aspect_ratio = args.aspect_ratio;
|
|
30411
|
+
if (args.accent_color !== undefined)
|
|
30412
|
+
body.accent_color = args.accent_color;
|
|
30413
|
+
if (args.text_position !== undefined)
|
|
30414
|
+
body.text_position = args.text_position;
|
|
30415
|
+
const res = await client.post("/sliders", body, idemKey("create-slider", args.prompt, args.mode ?? "", args.template ?? ""));
|
|
30416
|
+
return ok({
|
|
30417
|
+
slug: res.data.slug,
|
|
30418
|
+
kind: "slider",
|
|
30419
|
+
next: "generate_slider, then get_slider to read the slide URLs + caption"
|
|
30420
|
+
});
|
|
30421
|
+
}));
|
|
30422
|
+
registerTool("generate_slider", {
|
|
30423
|
+
title: "Generate (render) a carousel",
|
|
30424
|
+
description: "Deducts 1 credit per slide (3–10 slides) and starts the pipeline (copy → AI backgrounds → composite) for an existing " + "carousel. Safe to call again: a duplicate while a render is already in flight is reported as still " + "processing (no double charge), and a failed carousel can be re-generated. Then poll with get_slider " + "until status is 'completed' or 'failed'.",
|
|
30425
|
+
inputSchema: {
|
|
30426
|
+
slug: exports_external.string().describe("Slider slug from create_slider")
|
|
30427
|
+
},
|
|
30428
|
+
annotations: { title: "Generate slider", ...WRITE, idempotentHint: true }
|
|
30429
|
+
}, tool(async (args, client) => {
|
|
30430
|
+
try {
|
|
30431
|
+
const res = await client.post(`/sliders/${args.slug}/generate`);
|
|
30432
|
+
const data = asRecord(asRecord(res).data);
|
|
30433
|
+
return ok({
|
|
30434
|
+
slug: args.slug,
|
|
30435
|
+
kind: "slider",
|
|
30436
|
+
status: data.status ?? "processing"
|
|
30437
|
+
});
|
|
30438
|
+
} catch (e) {
|
|
30439
|
+
if (e.status === 409) {
|
|
30440
|
+
return ok({ slug: args.slug, kind: "slider", status: "processing" });
|
|
30441
|
+
}
|
|
30442
|
+
throw e;
|
|
30443
|
+
}
|
|
30444
|
+
}));
|
|
30445
|
+
registerTool("get_slider", {
|
|
30446
|
+
title: "Get carousel status + deliverable",
|
|
30447
|
+
description: "Returns the carousel's status and, when completed, the per-slide image URLs (download these), the " + "per-slide headline/body/kicker, the caption, and the hashtags. status flows draft → processing → " + "completed | failed. completed:true means every slide image_url is ready to save. Once completed you " + "can restyle_slider (new template/accent) or edit_slider_slide (one slide's text) for free.",
|
|
30448
|
+
inputSchema: { slug: exports_external.string().describe("Slider slug") },
|
|
30449
|
+
annotations: { title: "Get slider", ...RO }
|
|
30450
|
+
}, tool(async (args, client) => {
|
|
30451
|
+
const res = await client.get(`/sliders/${args.slug}`);
|
|
30452
|
+
const data = asRecord(asRecord(res).data);
|
|
30453
|
+
const slides = Array.isArray(data.slides) ? data.slides : [];
|
|
30454
|
+
const status = data.status ?? "unknown";
|
|
30455
|
+
return ok({
|
|
30456
|
+
slug: args.slug,
|
|
30457
|
+
kind: "slider",
|
|
30458
|
+
status,
|
|
30459
|
+
stage: status,
|
|
30460
|
+
terminal: status === "completed" || status === "failed",
|
|
30461
|
+
completed: status === "completed",
|
|
30462
|
+
error: data.error_message ?? null,
|
|
30463
|
+
caption: data.caption ?? null,
|
|
30464
|
+
hashtags: Array.isArray(data.hashtags) ? data.hashtags : [],
|
|
30465
|
+
slides: slides.map((s) => ({
|
|
30466
|
+
position: s.position,
|
|
30467
|
+
image_url: s.image_url ?? null,
|
|
30468
|
+
headline: s.headline ?? null,
|
|
30469
|
+
body: s.body ?? null,
|
|
30470
|
+
kicker: s.kicker ?? null,
|
|
30471
|
+
status: s.status ?? null
|
|
30472
|
+
}))
|
|
30473
|
+
});
|
|
30474
|
+
}));
|
|
30475
|
+
registerTool("restyle_slider", {
|
|
30476
|
+
title: "Restyle a carousel (free re-composite of every slide)",
|
|
30477
|
+
description: "Re-composites ALL slides of a completed carousel under a new template, accent color and/or text " + "position — for FREE (0 credits). Reuses the already-generated AI backgrounds, so no new images are paid for. The " + "carousel must already be 'completed' (generate it first); a draft/processing carousel returns a " + "409 conflict. Runs async: it marks every slide processing, so poll get_slider until status is back " + "to 'completed' (or 'failed'). Omit a field to leave it unchanged.",
|
|
30478
|
+
inputSchema: {
|
|
30479
|
+
slug: exports_external.string().describe("Slider slug from create_slider"),
|
|
30480
|
+
template: exports_external.enum([
|
|
30481
|
+
"boldStatement",
|
|
30482
|
+
"editorialStory",
|
|
30483
|
+
"scrapbook",
|
|
30484
|
+
"featureGrid",
|
|
30485
|
+
"offerCard",
|
|
30486
|
+
"comparison"
|
|
30487
|
+
]).optional().describe("New composition preset. boldStatement/editorialStory/scrapbook are creative; " + "featureGrid/offerCard/comparison are ad-driven."),
|
|
30488
|
+
accent_color: exports_external.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("New brand accent: must start with #, exactly 6 hex digits, e.g. #09EFBE"),
|
|
30489
|
+
text_position: exports_external.enum(["top", "middle", "bottom"]).optional().describe("New vertical placement of the on-image copy across all slides.")
|
|
30490
|
+
},
|
|
30491
|
+
annotations: { title: "Restyle slider", ...WRITE, idempotentHint: false }
|
|
30492
|
+
}, tool(async (args, client) => {
|
|
30493
|
+
const body = {};
|
|
30494
|
+
if (args.template !== undefined)
|
|
30495
|
+
body.template = args.template;
|
|
30496
|
+
if (args.accent_color !== undefined)
|
|
30497
|
+
body.accent_color = args.accent_color;
|
|
30498
|
+
if (args.text_position !== undefined)
|
|
30499
|
+
body.text_position = args.text_position;
|
|
30500
|
+
try {
|
|
30501
|
+
const res = await client.post(`/sliders/${args.slug}/restyle`, body);
|
|
30502
|
+
const data = asRecord(asRecord(res).data);
|
|
30503
|
+
return ok({
|
|
30504
|
+
slug: args.slug,
|
|
30505
|
+
kind: "slider",
|
|
30506
|
+
status: data.status ?? "processing",
|
|
30507
|
+
next: "poll get_slider until status is 'completed' (free re-composite, 0 credits)"
|
|
30508
|
+
});
|
|
30509
|
+
} catch (e) {
|
|
30510
|
+
if (e.status === 409) {
|
|
30511
|
+
return ok({ slug: args.slug, kind: "slider", status: "processing" });
|
|
30512
|
+
}
|
|
30513
|
+
throw e;
|
|
30514
|
+
}
|
|
30515
|
+
}));
|
|
30516
|
+
registerTool("edit_slider_slide", {
|
|
30517
|
+
title: "Edit one carousel slide's on-image text (free re-composite)",
|
|
30518
|
+
description: "Rewrites the on-image copy of a SINGLE slide (by position) and re-composites just that slide — for " + "FREE (0 credits). Reuses the slide's existing AI background, so no new image is paid for. The " + "carousel must already be 'completed' (a draft/processing carousel returns a 409 conflict). Runs " + "async: the slide goes back to processing, so poll get_slider until its status is 'completed'. Pass " + "only the fields you want to change. kicker = the small eyebrow/label line above the headline.",
|
|
30519
|
+
inputSchema: {
|
|
30520
|
+
slug: exports_external.string().describe("Slider slug from create_slider"),
|
|
30521
|
+
position: exports_external.number().int().min(1).describe("1-based slide position (from get_slider)"),
|
|
30522
|
+
headline: exports_external.string().max(120).optional().describe("Slide headline, ≤120 chars"),
|
|
30523
|
+
body: exports_external.string().max(600).optional().describe("Slide body, ≤600 chars"),
|
|
30524
|
+
kicker: exports_external.string().max(40).optional().describe("Small eyebrow/label line above the headline, ≤40 chars")
|
|
30525
|
+
},
|
|
30526
|
+
annotations: {
|
|
30527
|
+
title: "Edit slider slide",
|
|
30528
|
+
...WRITE,
|
|
30529
|
+
idempotentHint: false
|
|
30530
|
+
}
|
|
30531
|
+
}, tool(async (args, client) => {
|
|
30532
|
+
const body = {};
|
|
30533
|
+
if (args.headline !== undefined)
|
|
30534
|
+
body.headline = args.headline;
|
|
30535
|
+
if (args.body !== undefined)
|
|
30536
|
+
body.body = args.body;
|
|
30537
|
+
if (args.kicker !== undefined)
|
|
30538
|
+
body.kicker = args.kicker;
|
|
30539
|
+
try {
|
|
30540
|
+
const res = await client.patch(`/sliders/${args.slug}/slides/${args.position}`, body);
|
|
30541
|
+
const data = asRecord(asRecord(res).data);
|
|
30542
|
+
return ok({
|
|
30543
|
+
slug: args.slug,
|
|
30544
|
+
kind: "slider",
|
|
30545
|
+
position: args.position,
|
|
30546
|
+
status: data.status ?? "processing",
|
|
30547
|
+
next: "poll get_slider until the slide's status is 'completed' (free re-composite, 0 credits)"
|
|
30548
|
+
});
|
|
30549
|
+
} catch (e) {
|
|
30550
|
+
if (e.status === 409) {
|
|
30551
|
+
return ok({
|
|
30552
|
+
slug: args.slug,
|
|
30553
|
+
kind: "slider",
|
|
30554
|
+
position: args.position,
|
|
30555
|
+
status: "processing"
|
|
30556
|
+
});
|
|
30557
|
+
}
|
|
30558
|
+
throw e;
|
|
30559
|
+
}
|
|
30167
30560
|
}));
|
|
30168
30561
|
registerTool("create_editor_ad", {
|
|
30169
30562
|
title: "Create a multi-scene editor ad (autopilot)",
|
|
30170
|
-
description: "Creates an editor project from a product prompt and starts autopilot (server-orchestrated " + "scenario → segments → narration → voice → music → render). Each AI-generated scene is a fixed 8 seconds. " + "Returns the slug. Costs credits. Then poll with get_status / wait_for_completion (kind=editor). Prefer " + "make_video for the one-shot path.",
|
|
30563
|
+
description: "Creates an editor project from a product prompt and starts autopilot (server-orchestrated " + "scenario → segments → narration → voice → music → render). Each AI-generated scene is a fixed 8 seconds. " + "Returns the slug. Costs credits. Then poll with get_status / wait_for_completion (kind=editor). Prefer " + "make_video for the one-shot path. " + "BRANDING: to attach the user's own product image, brand logo, or closing card, create the draft with " + "create_editor_draft first, call set_product / set_logo / set_closing_image (all 0 credits), then " + "start_autopilot — autopilot weaves them in. (Editor ads carry their copy in-scene/narration, not as a " + "title overlay; for an on-screen title/subtitle use a short with headline/subheadline instead.)",
|
|
30171
30564
|
inputSchema: {
|
|
30172
30565
|
product_prompt: exports_external.string().min(10).describe("Brief for the ad (min 10 chars)"),
|
|
30173
30566
|
language: exports_external.string().optional().describe('Language code, e.g. "en" (default)'),
|
|
30174
|
-
theme: exports_external.string().optional().describe('Visual theme (default "realistic")'),
|
|
30567
|
+
theme: exports_external.string().optional().describe('Visual theme (default "realistic"). One of: none (no imposed style — segments follow your prompts literally), realistic, cinematic, anime, sci_fi, fantasy, noir, superhero, horror, mockumentary, sports, gaming, retro_80s, minimalist, cyberpunk.'),
|
|
30175
30568
|
voice_id: exports_external.string().optional().describe("Preferred narration voice id (see list_voices); omit for the default voice"),
|
|
30176
30569
|
export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")')
|
|
30177
30570
|
},
|
|
@@ -30226,7 +30619,7 @@ registerTool("create_editor_draft", {
|
|
|
30226
30619
|
inputSchema: {
|
|
30227
30620
|
product_prompt: exports_external.string().min(10).max(5000).optional().describe("Brief for the ad — 10–5000 chars, or omit entirely"),
|
|
30228
30621
|
language: exports_external.string().min(2).max(10).optional().describe('Language code, e.g. "en" (default)'),
|
|
30229
|
-
theme: exports_external.string().optional().describe('Visual theme (default "realistic")'),
|
|
30622
|
+
theme: exports_external.string().optional().describe('Visual theme (default "realistic"). One of: none (no imposed style — segments follow your prompts literally), realistic, cinematic, anime, sci_fi, fantasy, noir, superhero, horror, mockumentary, sports, gaming, retro_80s, minimalist, cyberpunk.'),
|
|
30230
30623
|
voice_id: exports_external.string().regex(/^[A-Za-z0-9_-]+$/).max(64).optional().describe("Preferred narration voice id (see list_voices); ≤64 chars, [A-Za-z0-9_-] only"),
|
|
30231
30624
|
export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")'),
|
|
30232
30625
|
project_intent: exports_external.enum(["social_ad", "creative_story"]).optional().describe("Project intent (optional)")
|
|
@@ -30798,6 +31191,37 @@ registerTool("set_logo", {
|
|
|
30798
31191
|
}
|
|
30799
31192
|
return ok(result);
|
|
30800
31193
|
}));
|
|
31194
|
+
registerTool("set_short_product", {
|
|
31195
|
+
title: "Attach a product image to a short (0 credits)",
|
|
31196
|
+
description: "Uploads a local product image and attaches it to a SHORT so the product is woven into the footage. " + "Accepted: " + IMAGE_EXTS.map((e) => `.${e}`).join(", ") + " (≤20 MB), local path confined to HUBFLUENCER_INPUT_DIR (or cwd). 0 credits. Optional description " + "(≤500 chars) guides how it's featured. Attach BEFORE generate_short so the product is part of the scenes. " + "(This is the shorts equivalent of set_product for editor projects.)",
|
|
31197
|
+
inputSchema: {
|
|
31198
|
+
slug: exports_external.string().describe("Short slug (from create_short)"),
|
|
31199
|
+
file_path: exports_external.string().describe("Local product image path (.jpg/.jpeg/.png)"),
|
|
31200
|
+
description: exports_external.string().max(500).optional().describe("Product description (≤500 chars)")
|
|
31201
|
+
},
|
|
31202
|
+
annotations: {
|
|
31203
|
+
title: "Set short product",
|
|
31204
|
+
...WRITE,
|
|
31205
|
+
idempotentHint: false
|
|
31206
|
+
}
|
|
31207
|
+
}, tool(async (args, client) => {
|
|
31208
|
+
const { s3_key } = await uploadImageFile(client, `/shorts/${args.slug}/product/presign`, args.file_path);
|
|
31209
|
+
const res = await client.post(`/shorts/${args.slug}/product/confirm`, { s3_key, product_description: args.description });
|
|
31210
|
+
return ok(asRecord(res).data ?? res);
|
|
31211
|
+
}));
|
|
31212
|
+
registerTool("set_short_poster", {
|
|
31213
|
+
title: "Set a short's end-card poster image (0 credits)",
|
|
31214
|
+
description: "Uploads a local image as the SHORT's end-card poster — a closing still shown at the end (extends the " + "render from 12s to 14s). Accepted: " + IMAGE_EXTS.map((e) => `.${e}`).join(", ") + " (≤20 MB), confined to HUBFLUENCER_INPUT_DIR/cwd. 0 credits. There is one poster per short; calling " + "again replaces it. (Shorts have no logo overlay — for a brand logo use an editor project + set_logo.)",
|
|
31215
|
+
inputSchema: {
|
|
31216
|
+
slug: exports_external.string().describe("Short slug (from create_short)"),
|
|
31217
|
+
file_path: exports_external.string().describe("Local poster image (.jpg/.jpeg/.png)")
|
|
31218
|
+
},
|
|
31219
|
+
annotations: { title: "Set short poster", ...WRITE, idempotentHint: false }
|
|
31220
|
+
}, tool(async (args, client) => {
|
|
31221
|
+
const { s3_key } = await uploadShortPoster(client, args.slug, args.file_path);
|
|
31222
|
+
const res = await client.post(`/shorts/${args.slug}/poster/confirm`, { s3_key });
|
|
31223
|
+
return ok(asRecord(res).data ?? res);
|
|
31224
|
+
}));
|
|
30801
31225
|
registerTool("get_status", {
|
|
30802
31226
|
title: "Get generation status",
|
|
30803
31227
|
description: "Returns a normalized status {stage, terminal, ready, video_url, error} for a short or editor " + "project. ready:true means the post-ready MP4 is at video_url.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubfluencer/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Model Context Protocol server for Hubfluencer — let AI agents generate post-ready shorts and editor ads.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Monocursive <contact@monocursive.com>",
|