@hubfluencer/mcp 0.3.0 → 0.5.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 +15 -6
- package/dist/index.js +365 -24
- package/package.json +1 -1
- package/src/index.ts +744 -24
package/README.md
CHANGED
|
@@ -64,8 +64,10 @@ claude mcp add hubfluencer --env HUBFLUENCER_API_TOKEN=YOUR_TOKEN -- npx -y @hub
|
|
|
64
64
|
| Tool | What it does |
|
|
65
65
|
|---|---|
|
|
66
66
|
| **`make_video`** | **One shot: prompt → finished MP4** (create → **price** → start → poll → download). Use this by default. Prices the job and checks your balance before charging; pass `dry_run:true` to preview the cost without spending, or `max_credits` to cap it. `kind:"auto"` picks a multi-scene editor ad for ad/promo/story briefs, a short for simple ones (reported as `kind_inferred`). |
|
|
67
|
-
| `create_short` / `generate_short` | Create a short draft (0 credits) / render it (15 credits) |
|
|
68
|
-
| `
|
|
67
|
+
| `create_short` / `generate_short` / `generate_short_text` | Create a short draft (0 credits) / generate editable overlay copy (1 AI assist) / render it (15 credits) |
|
|
68
|
+
| `create_slider` / `generate_slider` / `get_slider` | Create an image carousel draft (0 credits) / render it (1 credit per slide, 3–10) / read the per-slide image URLs + caption + hashtags. One prompt → N still slides + ready-to-post text. |
|
|
69
|
+
| `restyle_slider` / `edit_slider_slide` | Re-composite a **completed** carousel for **free** (0 credits): swap the template/accent on every slide, or rewrite one slide's headline/body/kicker. Reuses the AI backgrounds; poll `get_slider` until `completed`. |
|
|
70
|
+
| `create_editor_ad` | Create an editor project **and** run autopilot end-to-end. Style it with `creative_format` (narrative arc) + `visual_language` (render look) + `theme` (genre overlay). |
|
|
69
71
|
| `start_autopilot` | Run autopilot on an **existing** editor draft (e.g. resume a `make_video` `dry_run`, or after topping up) |
|
|
70
72
|
| `get_status` / `wait_for_completion` | Normalized `{stage, terminal, ready, video_url, error}`; block-poll until terminal (bounded) |
|
|
71
73
|
| `download_result` | Get (or save) the finished MP4 URL |
|
|
@@ -75,8 +77,8 @@ claude mcp add hubfluencer --env HUBFLUENCER_API_TOKEN=YOUR_TOKEN -- npx -y @hub
|
|
|
75
77
|
|
|
76
78
|
| Tool | What it does |
|
|
77
79
|
|---|---|
|
|
78
|
-
| `create_editor_draft` | Editor project, no autopilot (0 credits) |
|
|
79
|
-
| `generate_scenario` / `set_scenario` | AI-draft the scenario (1 assist) **or** write your own (free) |
|
|
80
|
+
| `create_editor_draft` | Editor project, no autopilot (0 credits). Accepts `creative_format` / `visual_language` / `theme`. |
|
|
81
|
+
| `generate_scenario` / `set_scenario` | AI-draft the scenario (1 assist) **or** write your own (free). `generate_scenario` can also persist `creative_format` / `visual_language` / `theme`. |
|
|
80
82
|
| `get_editor` | Full project state — the review step |
|
|
81
83
|
| `set_scene_count` | Grow/shrink to N scenes (1–20) |
|
|
82
84
|
| `set_segment_prompt` / `generate_segment` / `generate_all_segments` | Write a scene prompt (free); render one (5 credits) or every scene in order |
|
|
@@ -89,7 +91,7 @@ claude mcp add hubfluencer --env HUBFLUENCER_API_TOKEN=YOUR_TOKEN -- npx -y @hub
|
|
|
89
91
|
| Tool | What it does |
|
|
90
92
|
|---|---|
|
|
91
93
|
| `get_ai_assists` / `unlock_ai_assists` | Check the daily quota / spend 1 credit for +10 |
|
|
92
|
-
| `enhance_prompt` / `suggest_next_scene` / `suggest_music_prompt` | AI helpers (1 assist each) |
|
|
94
|
+
| `generate_short_text` / `enhance_prompt` / `suggest_next_scene` / `suggest_music_prompt` | AI helpers (1 assist each) |
|
|
93
95
|
|
|
94
96
|
## Typical flow
|
|
95
97
|
|
|
@@ -100,9 +102,16 @@ It prices the job and only charges if it's affordable (and within `max_credits`
|
|
|
100
102
|
If it returns `terminal:false`, the render is still going — call `wait_for_completion` with the returned slug.
|
|
101
103
|
|
|
102
104
|
**Granular (control/recovery):**
|
|
103
|
-
`create_short` → `generate_short` → `wait_for_completion {kind:"short"}` → `download_result`, or
|
|
105
|
+
`create_short` → optional `generate_short_text` → `generate_short` → `wait_for_completion {kind:"short"}` → `download_result`, or
|
|
104
106
|
`create_editor_ad` → `wait_for_completion {kind:"editor"}` → `download_result`.
|
|
105
107
|
|
|
108
|
+
**Image carousel:**
|
|
109
|
+
`create_slider({ prompt: "5 tips for…", mode: "ad_driven", slide_count: 5 })` → `generate_slider` →
|
|
110
|
+
poll `get_slider` until `completed:true`, then download each `slides[].image_url` and post them with the
|
|
111
|
+
returned `caption` + `hashtags`. To tweak the look afterward without re-paying, `restyle_slider`
|
|
112
|
+
(new template/accent, re-renders every slide) or `edit_slider_slide` (rewrite one slide's text) — both
|
|
113
|
+
free; poll `get_slider` again until `completed`.
|
|
114
|
+
|
|
106
115
|
> Result URLs are presigned and expire (~24h). Download promptly. Publishing to
|
|
107
116
|
> TikTok/Instagram requires a human-linked social account and is out of scope —
|
|
108
117
|
> return the MP4 + a suggested caption instead.
|
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");
|
|
@@ -29970,13 +29988,29 @@ function tool(fn) {
|
|
|
29970
29988
|
}
|
|
29971
29989
|
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
29972
29990
|
var kindSchema = exports_external.enum(["short", "editor"]).describe("Project kind");
|
|
29991
|
+
var CREATIVE_FORMATS = [
|
|
29992
|
+
"problem_solution",
|
|
29993
|
+
"mistake_fix",
|
|
29994
|
+
"myth_vs_reality",
|
|
29995
|
+
"before_after",
|
|
29996
|
+
"proof_demo",
|
|
29997
|
+
"product_reveal"
|
|
29998
|
+
];
|
|
29999
|
+
var VISUAL_LANGUAGES = [
|
|
30000
|
+
"kinetic_creator",
|
|
30001
|
+
"premium_editorial",
|
|
30002
|
+
"cinematic_product",
|
|
30003
|
+
"ugc_realism",
|
|
30004
|
+
"startup_explainer",
|
|
30005
|
+
"luxury_minimal"
|
|
30006
|
+
];
|
|
29973
30007
|
var RO = { readOnlyHint: true, destructiveHint: false, openWorldHint: true };
|
|
29974
30008
|
var WRITE = {
|
|
29975
30009
|
readOnlyHint: false,
|
|
29976
30010
|
destructiveHint: false,
|
|
29977
30011
|
openWorldHint: true
|
|
29978
30012
|
};
|
|
29979
|
-
var server = new McpServer({ name: "hubfluencer", version: "0.
|
|
30013
|
+
var server = new McpServer({ name: "hubfluencer", version: "0.5.0" });
|
|
29980
30014
|
var registerTool = server.registerTool.bind(server);
|
|
29981
30015
|
async function pollToTerminal(client, kind, slug, extra, budgetMs, intervalMs) {
|
|
29982
30016
|
const deadline = Date.now() + budgetMs;
|
|
@@ -30017,7 +30051,7 @@ async function pollSegmentToTerminal(client, slug, segmentId, extra, budgetMs, i
|
|
|
30017
30051
|
}
|
|
30018
30052
|
registerTool("make_video", {
|
|
30019
30053
|
title: "Make a video from a prompt (one shot)",
|
|
30020
|
-
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
|
|
30054
|
+
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 so a one-shot short isn't bare. creative_format + visual_language apply to BOTH kinds (editor and " + "shorts); headline/subheadline/music_vibe/text_* and the conversion graphics are SHORTS-only and ignored for " + "editor; theme applies to editor (genre overlay) and is legacy-only for shorts (used when visual_language is unset). " + "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.",
|
|
30021
30055
|
inputSchema: {
|
|
30022
30056
|
prompt: exports_external.string().describe("What the ad/video should be about (min 10 chars)"),
|
|
30023
30057
|
kind: exports_external.string().optional().describe("'short' (fast, 1 clip), 'editor' (multi-scene), or 'auto' (default — inferred)"),
|
|
@@ -30026,8 +30060,29 @@ registerTool("make_video", {
|
|
|
30026
30060
|
voice_id: exports_external.string().optional().describe("Preferred narration voice id for editor ads (see list_voices); shorts have no voiceover"),
|
|
30027
30061
|
headline: exports_external.string().optional().describe("SHORTS only: the on-screen TITLE overlay (≤160). Set this so the short isn't bare."),
|
|
30028
30062
|
subheadline: exports_external.string().optional().describe("SHORTS only: the secondary title / supporting line (≤200)"),
|
|
30063
|
+
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)."),
|
|
30064
|
+
headline_color: exports_external.string().optional().describe("SHORTS only: headline hex color, e.g. #ffffff"),
|
|
30065
|
+
subheadline_color: exports_external.string().optional().describe("SHORTS only: subheadline hex color, e.g. #ffffff"),
|
|
30066
|
+
accent_color: exports_external.string().optional().describe("SHORTS only: 6-digit brand accent hex color, e.g. #09EFBE"),
|
|
30067
|
+
offer_text: exports_external.string().optional().describe("SHORTS only: optional offer chip, e.g. -40% (≤16 chars)"),
|
|
30068
|
+
cta_text: exports_external.string().optional().describe("SHORTS only: optional CTA pill, e.g. Shop now (≤24 chars)"),
|
|
30069
|
+
badge_text: exports_external.string().optional().describe("SHORTS only: optional badge stamp, e.g. BEST SELLER (≤24 chars)"),
|
|
30070
|
+
star_rating: exports_external.number().optional().describe("SHORTS only: optional 0..5 star rating under the subheadline"),
|
|
30071
|
+
text_position: exports_external.enum(["top", "center", "bottom"]).optional().describe("SHORTS only: title overlay position (default bottom)"),
|
|
30072
|
+
text_animation: exports_external.enum([
|
|
30073
|
+
"reveal",
|
|
30074
|
+
"typewriter",
|
|
30075
|
+
"fade_in",
|
|
30076
|
+
"pop",
|
|
30077
|
+
"bounce",
|
|
30078
|
+
"word_stagger",
|
|
30079
|
+
"word_spotlight"
|
|
30080
|
+
]).optional().describe("SHORTS only: title overlay animation (default fade_in)"),
|
|
30081
|
+
font_family: exports_external.string().optional().describe("SHORTS only: overlay font family, e.g. ShortFontSpaceGrotesk"),
|
|
30029
30082
|
music_vibe: exports_external.string().optional().describe("SHORTS only: music mood — Upbeat (default), Cinematic, Minimal, Luxury, Playful, Jazz"),
|
|
30030
|
-
|
|
30083
|
+
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("SHORTS + EDITOR: narrative arc / segment structure. Omit for Auto/generic."),
|
|
30084
|
+
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("SHORTS + EDITOR: visual style direction and render look. Default app choice is kinetic_creator."),
|
|
30085
|
+
theme: exports_external.string().optional().describe("Visual theme: editor genre overlay, and legacy shorts fallback only when visual_language is unset. " + "For editor, when visual_language is set it drives the look and 'none' is ignored. " + "none (literal — no imposed style), realistic (default), cinematic, " + "anime, sci_fi, fantasy, noir, superhero, horror, mockumentary, sports, gaming, retro_80s, " + "minimalist, cyberpunk"),
|
|
30031
30086
|
save_path: exports_external.string().optional().describe("Optional .mp4 path to download to (confined to HUBFLUENCER_OUTPUT_DIR or cwd)"),
|
|
30032
30087
|
max_wait_seconds: exports_external.number().optional().describe("Block budget seconds (default 240, capped 10–280)"),
|
|
30033
30088
|
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."),
|
|
@@ -30051,9 +30106,22 @@ registerTool("make_video", {
|
|
|
30051
30106
|
language: args.language,
|
|
30052
30107
|
headline: args.headline,
|
|
30053
30108
|
subheadline: args.subheadline,
|
|
30109
|
+
text_beats: args.text_beats,
|
|
30110
|
+
headline_color: args.headline_color,
|
|
30111
|
+
subheadline_color: args.subheadline_color,
|
|
30112
|
+
accent_color: args.accent_color,
|
|
30113
|
+
offer_text: args.offer_text,
|
|
30114
|
+
cta_text: args.cta_text,
|
|
30115
|
+
badge_text: args.badge_text,
|
|
30116
|
+
star_rating: args.star_rating,
|
|
30117
|
+
short_text_position: args.text_position,
|
|
30118
|
+
short_text_animation: args.text_animation,
|
|
30119
|
+
short_font_family: args.font_family,
|
|
30054
30120
|
music_vibe: args.music_vibe,
|
|
30121
|
+
creative_format: args.creative_format,
|
|
30122
|
+
visual_language: args.visual_language,
|
|
30055
30123
|
theme: args.theme
|
|
30056
|
-
}, idemKey("make-short", args.prompt, args.language ?? "", args.headline ?? "", args.subheadline ?? "", args.music_vibe ?? "", args.theme ?? ""));
|
|
30124
|
+
}, 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 ?? ""));
|
|
30057
30125
|
slug = created.data.slug;
|
|
30058
30126
|
} else {
|
|
30059
30127
|
const created = await client.post("/editor", {
|
|
@@ -30061,8 +30129,10 @@ registerTool("make_video", {
|
|
|
30061
30129
|
product_prompt: args.prompt,
|
|
30062
30130
|
export_aspect_ratio: args.aspect,
|
|
30063
30131
|
voice_id: args.voice_id,
|
|
30132
|
+
creative_format: args.creative_format,
|
|
30133
|
+
visual_language: args.visual_language,
|
|
30064
30134
|
theme: args.theme
|
|
30065
|
-
}, idemKey("make-editor", args.prompt, args.language ?? "en", args.aspect ?? "", args.voice_id ?? "", args.theme ?? ""));
|
|
30135
|
+
}, idemKey("make-editor", args.prompt, args.language ?? "en", args.aspect ?? "", args.voice_id ?? "", args.creative_format ?? "", args.visual_language ?? "", args.theme ?? ""));
|
|
30066
30136
|
slug = created.data.slug;
|
|
30067
30137
|
}
|
|
30068
30138
|
const costPath = kind === "short" ? `/shorts/${slug}/cost` : `/editor/${slug}/autopilot/cost`;
|
|
@@ -30171,18 +30241,36 @@ registerTool("list_projects", {
|
|
|
30171
30241
|
}));
|
|
30172
30242
|
registerTool("create_short", {
|
|
30173
30243
|
title: "Create a short (draft)",
|
|
30174
|
-
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
|
|
30244
|
+
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.)",
|
|
30175
30245
|
inputSchema: {
|
|
30176
30246
|
product_prompt: exports_external.string().min(10).describe("What the short should be about"),
|
|
30177
30247
|
language: exports_external.string().optional().describe('Language code, e.g. "en" (default)'),
|
|
30178
30248
|
headline: exports_external.string().max(160).optional().describe("The on-screen TITLE composited over the short (poster text overlay). ≤160 chars. Set this."),
|
|
30179
30249
|
subheadline: exports_external.string().max(200).optional().describe("The SECONDARY title / supporting line under the headline. ≤200 chars."),
|
|
30180
|
-
|
|
30250
|
+
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Optional structure: problem_solution, mistake_fix, myth_vs_reality, before_after, proof_demo, product_reveal. Omit for Auto."),
|
|
30251
|
+
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("Visual language for Veo direction and render styling. Good default: kinetic_creator."),
|
|
30252
|
+
theme: exports_external.string().optional().describe("Deprecated for shorts: legacy visual theme used only when visual_language is unset."),
|
|
30181
30253
|
music_vibe: exports_external.string().optional().describe("Background-music mood. Recognized: Upbeat (default), Cinematic, Minimal, Luxury, Playful, Jazz."),
|
|
30182
30254
|
music_instruments: exports_external.array(exports_external.string()).optional().describe('Optional instrument hints, e.g. ["piano", "strings"]'),
|
|
30183
30255
|
text_position: exports_external.enum(["top", "center", "bottom"]).optional().describe("Where the title overlay sits (default bottom)"),
|
|
30184
|
-
text_animation: exports_external.enum([
|
|
30185
|
-
|
|
30256
|
+
text_animation: exports_external.enum([
|
|
30257
|
+
"reveal",
|
|
30258
|
+
"typewriter",
|
|
30259
|
+
"fade_in",
|
|
30260
|
+
"pop",
|
|
30261
|
+
"bounce",
|
|
30262
|
+
"word_stagger",
|
|
30263
|
+
"word_spotlight"
|
|
30264
|
+
]).optional().describe("Title overlay animation (default fade_in)"),
|
|
30265
|
+
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."),
|
|
30266
|
+
headline_color: exports_external.string().optional().describe("Headline hex color, e.g. #ffffff"),
|
|
30267
|
+
subheadline_color: exports_external.string().optional().describe("Subheadline hex color, e.g. #ffffff"),
|
|
30268
|
+
accent_color: exports_external.string().optional().describe("6-digit brand accent hex color, e.g. #09EFBE"),
|
|
30269
|
+
offer_text: exports_external.string().max(16).optional().describe('Optional offer chip, e.g. "-40%" or "2 FOR 1"'),
|
|
30270
|
+
cta_text: exports_external.string().max(24).optional().describe('Optional CTA pill, e.g. "Shop now"'),
|
|
30271
|
+
badge_text: exports_external.string().max(24).optional().describe('Optional badge stamp, e.g. "BEST SELLER"'),
|
|
30272
|
+
star_rating: exports_external.number().min(0).max(5).optional().describe("Optional 0..5 star rating under the subheadline"),
|
|
30273
|
+
text_beats: exports_external.array(exports_external.string().max(120)).max(8).optional().describe("Optional caption beats shown sequentially instead of the static subheadline.")
|
|
30186
30274
|
},
|
|
30187
30275
|
annotations: { title: "Create short", ...WRITE, idempotentHint: true }
|
|
30188
30276
|
}, tool(async (args, client) => {
|
|
@@ -30195,6 +30283,10 @@ registerTool("create_short", {
|
|
|
30195
30283
|
body.headline = args.headline;
|
|
30196
30284
|
if (args.subheadline !== undefined)
|
|
30197
30285
|
body.subheadline = args.subheadline;
|
|
30286
|
+
if (args.creative_format !== undefined)
|
|
30287
|
+
body.creative_format = args.creative_format;
|
|
30288
|
+
if (args.visual_language !== undefined)
|
|
30289
|
+
body.visual_language = args.visual_language;
|
|
30198
30290
|
if (args.theme !== undefined)
|
|
30199
30291
|
body.theme = args.theme;
|
|
30200
30292
|
if (args.music_vibe !== undefined)
|
|
@@ -30207,7 +30299,23 @@ registerTool("create_short", {
|
|
|
30207
30299
|
body.short_text_animation = args.text_animation;
|
|
30208
30300
|
if (args.font_family !== undefined)
|
|
30209
30301
|
body.short_font_family = args.font_family;
|
|
30210
|
-
|
|
30302
|
+
if (args.headline_color !== undefined)
|
|
30303
|
+
body.headline_color = args.headline_color;
|
|
30304
|
+
if (args.subheadline_color !== undefined)
|
|
30305
|
+
body.subheadline_color = args.subheadline_color;
|
|
30306
|
+
if (args.accent_color !== undefined)
|
|
30307
|
+
body.accent_color = args.accent_color;
|
|
30308
|
+
if (args.offer_text !== undefined)
|
|
30309
|
+
body.offer_text = args.offer_text;
|
|
30310
|
+
if (args.cta_text !== undefined)
|
|
30311
|
+
body.cta_text = args.cta_text;
|
|
30312
|
+
if (args.badge_text !== undefined)
|
|
30313
|
+
body.badge_text = args.badge_text;
|
|
30314
|
+
if (args.star_rating !== undefined)
|
|
30315
|
+
body.star_rating = args.star_rating;
|
|
30316
|
+
if (args.text_beats !== undefined)
|
|
30317
|
+
body.text_beats = args.text_beats;
|
|
30318
|
+
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 ?? [])));
|
|
30211
30319
|
return ok({
|
|
30212
30320
|
slug: res.data.slug,
|
|
30213
30321
|
kind: "short",
|
|
@@ -30216,13 +30324,229 @@ registerTool("create_short", {
|
|
|
30216
30324
|
}));
|
|
30217
30325
|
registerTool("generate_short", {
|
|
30218
30326
|
title: "Generate (render) a short",
|
|
30219
|
-
description: "Deducts 15 credits and starts the render pipeline for an existing short.
|
|
30327
|
+
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).",
|
|
30220
30328
|
inputSchema: { slug: exports_external.string().describe("Short slug from create_short") },
|
|
30221
30329
|
annotations: { title: "Generate short", ...WRITE, idempotentHint: true }
|
|
30222
30330
|
}, tool(async (args, client) => {
|
|
30223
|
-
|
|
30224
|
-
|
|
30225
|
-
|
|
30331
|
+
try {
|
|
30332
|
+
const res = await client.post(`/shorts/${args.slug}/generate`);
|
|
30333
|
+
const status = normalizeStatus("short", args.slug, asRecord(res).data);
|
|
30334
|
+
return ok(status);
|
|
30335
|
+
} catch (e) {
|
|
30336
|
+
if (e.status === 409) {
|
|
30337
|
+
return ok({
|
|
30338
|
+
kind: "short",
|
|
30339
|
+
slug: args.slug,
|
|
30340
|
+
stage: "processing",
|
|
30341
|
+
terminal: false,
|
|
30342
|
+
ready: false,
|
|
30343
|
+
video_url: null,
|
|
30344
|
+
error: null
|
|
30345
|
+
});
|
|
30346
|
+
}
|
|
30347
|
+
throw e;
|
|
30348
|
+
}
|
|
30349
|
+
}));
|
|
30350
|
+
registerTool("generate_short_text", {
|
|
30351
|
+
title: "Generate short overlay text (AI assist)",
|
|
30352
|
+
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.",
|
|
30353
|
+
inputSchema: {
|
|
30354
|
+
slug: exports_external.string().describe("Short slug from create_short"),
|
|
30355
|
+
auto_unlock: exports_external.boolean().optional().describe("On 429, spend 1 credit to unlock +10 assists and retry once (default false)")
|
|
30356
|
+
},
|
|
30357
|
+
annotations: {
|
|
30358
|
+
title: "Generate short text",
|
|
30359
|
+
...WRITE,
|
|
30360
|
+
idempotentHint: false
|
|
30361
|
+
}
|
|
30362
|
+
}, tool(async (args, client) => {
|
|
30363
|
+
const res = await withAssist(client, args.auto_unlock ?? false, () => client.post(`/shorts/${args.slug}/text/generate`));
|
|
30364
|
+
return ok(asRecord(res).data ?? res);
|
|
30365
|
+
}));
|
|
30366
|
+
registerTool("create_slider", {
|
|
30367
|
+
title: "Create an image slider / carousel (draft)",
|
|
30368
|
+
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.",
|
|
30369
|
+
inputSchema: {
|
|
30370
|
+
prompt: exports_external.string().min(10).describe("What the carousel is about"),
|
|
30371
|
+
mode: exports_external.enum(["creative", "ad_driven"]).optional().describe("'creative' (storytelling) or 'ad_driven' (facts about the product). Default creative."),
|
|
30372
|
+
template: exports_external.enum([
|
|
30373
|
+
"boldStatement",
|
|
30374
|
+
"editorialStory",
|
|
30375
|
+
"scrapbook",
|
|
30376
|
+
"featureGrid",
|
|
30377
|
+
"offerCard",
|
|
30378
|
+
"comparison"
|
|
30379
|
+
]).optional().describe("Composition preset. boldStatement/editorialStory/scrapbook are creative; " + "featureGrid/offerCard/comparison are ad-driven. Defaults to the mode default."),
|
|
30380
|
+
slide_count: exports_external.number().optional().describe("Number of slides, integer 3–10 (default 5)"),
|
|
30381
|
+
aspect_ratio: exports_external.enum(["4:5", "1:1", "9:16"]).optional().describe("Canvas ratio (default 4:5, the highest-reach carousel ratio)"),
|
|
30382
|
+
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"),
|
|
30383
|
+
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.")
|
|
30384
|
+
},
|
|
30385
|
+
annotations: { title: "Create slider", ...WRITE, idempotentHint: true }
|
|
30386
|
+
}, tool(async (args, client) => {
|
|
30387
|
+
if (args.slide_count !== undefined) {
|
|
30388
|
+
if (!Number.isInteger(args.slide_count) || args.slide_count < 3 || args.slide_count > 10) {
|
|
30389
|
+
return fail("slide_count must be an integer between 3 and 10");
|
|
30390
|
+
}
|
|
30391
|
+
}
|
|
30392
|
+
const body = { prompt: args.prompt };
|
|
30393
|
+
if (args.mode !== undefined)
|
|
30394
|
+
body.mode = args.mode;
|
|
30395
|
+
if (args.template !== undefined)
|
|
30396
|
+
body.template = args.template;
|
|
30397
|
+
if (args.slide_count !== undefined)
|
|
30398
|
+
body.slide_count = args.slide_count;
|
|
30399
|
+
if (args.aspect_ratio !== undefined)
|
|
30400
|
+
body.aspect_ratio = args.aspect_ratio;
|
|
30401
|
+
if (args.accent_color !== undefined)
|
|
30402
|
+
body.accent_color = args.accent_color;
|
|
30403
|
+
if (args.text_position !== undefined)
|
|
30404
|
+
body.text_position = args.text_position;
|
|
30405
|
+
const res = await client.post("/sliders", body, idemKey("create-slider", args.prompt, args.mode ?? "", args.template ?? ""));
|
|
30406
|
+
return ok({
|
|
30407
|
+
slug: res.data.slug,
|
|
30408
|
+
kind: "slider",
|
|
30409
|
+
next: "generate_slider, then get_slider to read the slide URLs + caption"
|
|
30410
|
+
});
|
|
30411
|
+
}));
|
|
30412
|
+
registerTool("generate_slider", {
|
|
30413
|
+
title: "Generate (render) a carousel",
|
|
30414
|
+
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'.",
|
|
30415
|
+
inputSchema: {
|
|
30416
|
+
slug: exports_external.string().describe("Slider slug from create_slider")
|
|
30417
|
+
},
|
|
30418
|
+
annotations: { title: "Generate slider", ...WRITE, idempotentHint: true }
|
|
30419
|
+
}, tool(async (args, client) => {
|
|
30420
|
+
try {
|
|
30421
|
+
const res = await client.post(`/sliders/${args.slug}/generate`);
|
|
30422
|
+
const data = asRecord(asRecord(res).data);
|
|
30423
|
+
return ok({
|
|
30424
|
+
slug: args.slug,
|
|
30425
|
+
kind: "slider",
|
|
30426
|
+
status: data.status ?? "processing"
|
|
30427
|
+
});
|
|
30428
|
+
} catch (e) {
|
|
30429
|
+
if (e.status === 409) {
|
|
30430
|
+
return ok({ slug: args.slug, kind: "slider", status: "processing" });
|
|
30431
|
+
}
|
|
30432
|
+
throw e;
|
|
30433
|
+
}
|
|
30434
|
+
}));
|
|
30435
|
+
registerTool("get_slider", {
|
|
30436
|
+
title: "Get carousel status + deliverable",
|
|
30437
|
+
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.",
|
|
30438
|
+
inputSchema: { slug: exports_external.string().describe("Slider slug") },
|
|
30439
|
+
annotations: { title: "Get slider", ...RO }
|
|
30440
|
+
}, tool(async (args, client) => {
|
|
30441
|
+
const res = await client.get(`/sliders/${args.slug}`);
|
|
30442
|
+
const data = asRecord(asRecord(res).data);
|
|
30443
|
+
const slides = Array.isArray(data.slides) ? data.slides : [];
|
|
30444
|
+
const status = data.status ?? "unknown";
|
|
30445
|
+
return ok({
|
|
30446
|
+
slug: args.slug,
|
|
30447
|
+
kind: "slider",
|
|
30448
|
+
status,
|
|
30449
|
+
stage: status,
|
|
30450
|
+
terminal: status === "completed" || status === "failed",
|
|
30451
|
+
completed: status === "completed",
|
|
30452
|
+
error: data.error_message ?? null,
|
|
30453
|
+
caption: data.caption ?? null,
|
|
30454
|
+
hashtags: Array.isArray(data.hashtags) ? data.hashtags : [],
|
|
30455
|
+
slides: slides.map((s) => ({
|
|
30456
|
+
position: s.position,
|
|
30457
|
+
image_url: s.image_url ?? null,
|
|
30458
|
+
headline: s.headline ?? null,
|
|
30459
|
+
body: s.body ?? null,
|
|
30460
|
+
kicker: s.kicker ?? null,
|
|
30461
|
+
status: s.status ?? null
|
|
30462
|
+
}))
|
|
30463
|
+
});
|
|
30464
|
+
}));
|
|
30465
|
+
registerTool("restyle_slider", {
|
|
30466
|
+
title: "Restyle a carousel (free re-composite of every slide)",
|
|
30467
|
+
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.",
|
|
30468
|
+
inputSchema: {
|
|
30469
|
+
slug: exports_external.string().describe("Slider slug from create_slider"),
|
|
30470
|
+
template: exports_external.enum([
|
|
30471
|
+
"boldStatement",
|
|
30472
|
+
"editorialStory",
|
|
30473
|
+
"scrapbook",
|
|
30474
|
+
"featureGrid",
|
|
30475
|
+
"offerCard",
|
|
30476
|
+
"comparison"
|
|
30477
|
+
]).optional().describe("New composition preset. boldStatement/editorialStory/scrapbook are creative; " + "featureGrid/offerCard/comparison are ad-driven."),
|
|
30478
|
+
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"),
|
|
30479
|
+
text_position: exports_external.enum(["top", "middle", "bottom"]).optional().describe("New vertical placement of the on-image copy across all slides.")
|
|
30480
|
+
},
|
|
30481
|
+
annotations: { title: "Restyle slider", ...WRITE, idempotentHint: false }
|
|
30482
|
+
}, tool(async (args, client) => {
|
|
30483
|
+
const body = {};
|
|
30484
|
+
if (args.template !== undefined)
|
|
30485
|
+
body.template = args.template;
|
|
30486
|
+
if (args.accent_color !== undefined)
|
|
30487
|
+
body.accent_color = args.accent_color;
|
|
30488
|
+
if (args.text_position !== undefined)
|
|
30489
|
+
body.text_position = args.text_position;
|
|
30490
|
+
try {
|
|
30491
|
+
const res = await client.post(`/sliders/${args.slug}/restyle`, body);
|
|
30492
|
+
const data = asRecord(asRecord(res).data);
|
|
30493
|
+
return ok({
|
|
30494
|
+
slug: args.slug,
|
|
30495
|
+
kind: "slider",
|
|
30496
|
+
status: data.status ?? "processing",
|
|
30497
|
+
next: "poll get_slider until status is 'completed' (free re-composite, 0 credits)"
|
|
30498
|
+
});
|
|
30499
|
+
} catch (e) {
|
|
30500
|
+
if (e.status === 409) {
|
|
30501
|
+
return ok({ slug: args.slug, kind: "slider", status: "processing" });
|
|
30502
|
+
}
|
|
30503
|
+
throw e;
|
|
30504
|
+
}
|
|
30505
|
+
}));
|
|
30506
|
+
registerTool("edit_slider_slide", {
|
|
30507
|
+
title: "Edit one carousel slide's on-image text (free re-composite)",
|
|
30508
|
+
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.",
|
|
30509
|
+
inputSchema: {
|
|
30510
|
+
slug: exports_external.string().describe("Slider slug from create_slider"),
|
|
30511
|
+
position: exports_external.number().int().min(1).describe("1-based slide position (from get_slider)"),
|
|
30512
|
+
headline: exports_external.string().max(120).optional().describe("Slide headline, ≤120 chars"),
|
|
30513
|
+
body: exports_external.string().max(600).optional().describe("Slide body, ≤600 chars"),
|
|
30514
|
+
kicker: exports_external.string().max(40).optional().describe("Small eyebrow/label line above the headline, ≤40 chars")
|
|
30515
|
+
},
|
|
30516
|
+
annotations: {
|
|
30517
|
+
title: "Edit slider slide",
|
|
30518
|
+
...WRITE,
|
|
30519
|
+
idempotentHint: false
|
|
30520
|
+
}
|
|
30521
|
+
}, tool(async (args, client) => {
|
|
30522
|
+
const body = {};
|
|
30523
|
+
if (args.headline !== undefined)
|
|
30524
|
+
body.headline = args.headline;
|
|
30525
|
+
if (args.body !== undefined)
|
|
30526
|
+
body.body = args.body;
|
|
30527
|
+
if (args.kicker !== undefined)
|
|
30528
|
+
body.kicker = args.kicker;
|
|
30529
|
+
try {
|
|
30530
|
+
const res = await client.patch(`/sliders/${args.slug}/slides/${args.position}`, body);
|
|
30531
|
+
const data = asRecord(asRecord(res).data);
|
|
30532
|
+
return ok({
|
|
30533
|
+
slug: args.slug,
|
|
30534
|
+
kind: "slider",
|
|
30535
|
+
position: args.position,
|
|
30536
|
+
status: data.status ?? "processing",
|
|
30537
|
+
next: "poll get_slider until the slide's status is 'completed' (free re-composite, 0 credits)"
|
|
30538
|
+
});
|
|
30539
|
+
} catch (e) {
|
|
30540
|
+
if (e.status === 409) {
|
|
30541
|
+
return ok({
|
|
30542
|
+
slug: args.slug,
|
|
30543
|
+
kind: "slider",
|
|
30544
|
+
position: args.position,
|
|
30545
|
+
status: "processing"
|
|
30546
|
+
});
|
|
30547
|
+
}
|
|
30548
|
+
throw e;
|
|
30549
|
+
}
|
|
30226
30550
|
}));
|
|
30227
30551
|
registerTool("create_editor_ad", {
|
|
30228
30552
|
title: "Create a multi-scene editor ad (autopilot)",
|
|
@@ -30230,7 +30554,9 @@ registerTool("create_editor_ad", {
|
|
|
30230
30554
|
inputSchema: {
|
|
30231
30555
|
product_prompt: exports_external.string().min(10).describe("Brief for the ad (min 10 chars)"),
|
|
30232
30556
|
language: exports_external.string().optional().describe('Language code, e.g. "en" (default)'),
|
|
30233
|
-
|
|
30557
|
+
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Narrative arc / segment structure. Omit for Auto/generic."),
|
|
30558
|
+
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("Visual style direction and render look. Good default: kinetic_creator. When set it drives the look and a theme of 'none' is ignored."),
|
|
30559
|
+
theme: exports_external.string().optional().describe('Visual theme / genre overlay (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.'),
|
|
30234
30560
|
voice_id: exports_external.string().optional().describe("Preferred narration voice id (see list_voices); omit for the default voice"),
|
|
30235
30561
|
export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")')
|
|
30236
30562
|
},
|
|
@@ -30239,10 +30565,12 @@ registerTool("create_editor_ad", {
|
|
|
30239
30565
|
const created = await client.post("/editor", {
|
|
30240
30566
|
language: args.language ?? "en",
|
|
30241
30567
|
product_prompt: args.product_prompt,
|
|
30568
|
+
creative_format: args.creative_format,
|
|
30569
|
+
visual_language: args.visual_language,
|
|
30242
30570
|
theme: args.theme,
|
|
30243
30571
|
voice_id: args.voice_id,
|
|
30244
30572
|
export_aspect_ratio: args.export_aspect_ratio
|
|
30245
|
-
}, idemKey("create-editor", String(args.product_prompt ?? "")));
|
|
30573
|
+
}, idemKey("create-editor", String(args.product_prompt ?? ""), String(args.language ?? "en"), String(args.creative_format ?? ""), String(args.visual_language ?? ""), String(args.theme ?? ""), String(args.voice_id ?? ""), String(args.export_aspect_ratio ?? "")));
|
|
30246
30574
|
const slug = created.data.slug;
|
|
30247
30575
|
const started = await client.post(`/editor/${slug}/autopilot`, undefined, `autopilot:${slug}`);
|
|
30248
30576
|
const status = normalizeStatus("editor", slug, asRecord(started).data);
|
|
@@ -30285,7 +30613,9 @@ registerTool("create_editor_draft", {
|
|
|
30285
30613
|
inputSchema: {
|
|
30286
30614
|
product_prompt: exports_external.string().min(10).max(5000).optional().describe("Brief for the ad — 10–5000 chars, or omit entirely"),
|
|
30287
30615
|
language: exports_external.string().min(2).max(10).optional().describe('Language code, e.g. "en" (default)'),
|
|
30288
|
-
|
|
30616
|
+
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Narrative arc / segment structure. Omit for Auto/generic."),
|
|
30617
|
+
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("Visual style direction and render look. Good default: kinetic_creator. When set it drives the look and a theme of 'none' is ignored."),
|
|
30618
|
+
theme: exports_external.string().optional().describe('Visual theme / genre overlay (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.'),
|
|
30289
30619
|
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"),
|
|
30290
30620
|
export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")'),
|
|
30291
30621
|
project_intent: exports_external.enum(["social_ad", "creative_story"]).optional().describe("Project intent (optional)")
|
|
@@ -30299,6 +30629,8 @@ registerTool("create_editor_draft", {
|
|
|
30299
30629
|
const prompt = args.product_prompt?.trim();
|
|
30300
30630
|
const body = {
|
|
30301
30631
|
language: args.language ?? "en",
|
|
30632
|
+
creative_format: args.creative_format,
|
|
30633
|
+
visual_language: args.visual_language,
|
|
30302
30634
|
theme: args.theme,
|
|
30303
30635
|
voice_id: args.voice_id,
|
|
30304
30636
|
export_aspect_ratio: args.export_aspect_ratio,
|
|
@@ -30306,7 +30638,7 @@ registerTool("create_editor_draft", {
|
|
|
30306
30638
|
};
|
|
30307
30639
|
if (prompt)
|
|
30308
30640
|
body.product_prompt = prompt;
|
|
30309
|
-
const created = await client.post("/editor", body, idemKey("create-editor-draft", prompt ?? "", args.language ?? "en"));
|
|
30641
|
+
const created = await client.post("/editor", body, idemKey("create-editor-draft", prompt ?? "", args.language ?? "en", args.creative_format ?? "", args.visual_language ?? "", args.theme ?? ""));
|
|
30310
30642
|
return ok({
|
|
30311
30643
|
slug: created.data.slug,
|
|
30312
30644
|
kind: "editor",
|
|
@@ -30336,10 +30668,13 @@ registerTool("set_scenario", {
|
|
|
30336
30668
|
}));
|
|
30337
30669
|
registerTool("generate_scenario", {
|
|
30338
30670
|
title: "Generate the editor scenario (AI assist)",
|
|
30339
|
-
description: "Generates a scenario with AI. CONSUMES 1 AI ASSIST (free daily quota; check get_ai_assists). On 429 " + "(quota exhausted) either set auto_unlock:true to spend 1 credit for +10 assists and retry once, or " + "write the scenario yourself with set_scenario. Server default segments_count is 5.",
|
|
30671
|
+
description: "Generates a scenario with AI. CONSUMES 1 AI ASSIST (free daily quota; check get_ai_assists). On 429 " + "(quota exhausted) either set auto_unlock:true to spend 1 credit for +10 assists and retry once, or " + "write the scenario yourself with set_scenario. Server default segments_count is 5. " + "Optionally pass creative_format / visual_language / theme to persist the project’s creative style before " + "generating (this is the granular path’s way to set/change style; PATCH /scenario does not).",
|
|
30340
30672
|
inputSchema: {
|
|
30341
30673
|
slug: exports_external.string().describe("Editor project slug"),
|
|
30342
30674
|
segments_count: exports_external.number().int().min(3).max(10).optional().describe("How many scenes (3–10, server default 5)"),
|
|
30675
|
+
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Persist the narrative arc / segment structure before generating. Omit to leave it unchanged; pass an empty string to clear."),
|
|
30676
|
+
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("Persist the visual style / render look before generating. When set it drives the look and a theme of 'none' is ignored."),
|
|
30677
|
+
theme: exports_external.string().optional().describe("Persist the genre overlay / theme before generating (e.g. realistic, cinematic, anime, none)."),
|
|
30343
30678
|
auto_unlock: exports_external.boolean().optional().describe("On 429, spend 1 credit to unlock +10 assists and retry once (default false)")
|
|
30344
30679
|
},
|
|
30345
30680
|
annotations: {
|
|
@@ -30351,6 +30686,12 @@ registerTool("generate_scenario", {
|
|
|
30351
30686
|
const body = {};
|
|
30352
30687
|
if (args.segments_count !== undefined)
|
|
30353
30688
|
body.segments_count = args.segments_count;
|
|
30689
|
+
if (args.creative_format !== undefined)
|
|
30690
|
+
body.creative_format = args.creative_format;
|
|
30691
|
+
if (args.visual_language !== undefined)
|
|
30692
|
+
body.visual_language = args.visual_language;
|
|
30693
|
+
if (args.theme !== undefined)
|
|
30694
|
+
body.theme = args.theme;
|
|
30354
30695
|
const res = await withAssist(client, args.auto_unlock ?? false, () => client.post(`/editor/${args.slug}/generate-scenario`, body));
|
|
30355
30696
|
return ok(asRecord(res).data ?? res);
|
|
30356
30697
|
}));
|