@hubfluencer/mcp 0.4.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 CHANGED
@@ -67,7 +67,7 @@ claude mcp add hubfluencer --env HUBFLUENCER_API_TOKEN=YOUR_TOKEN -- npx -y @hub
67
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
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
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 |
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). |
71
71
  | `start_autopilot` | Run autopilot on an **existing** editor draft (e.g. resume a `make_video` `dry_run`, or after topping up) |
72
72
  | `get_status` / `wait_for_completion` | Normalized `{stage, terminal, ready, video_url, error}`; block-poll until terminal (bounded) |
73
73
  | `download_result` | Get (or save) the finished MP4 URL |
@@ -77,8 +77,8 @@ claude mcp add hubfluencer --env HUBFLUENCER_API_TOKEN=YOUR_TOKEN -- npx -y @hub
77
77
 
78
78
  | Tool | What it does |
79
79
  |---|---|
80
- | `create_editor_draft` | Editor project, no autopilot (0 credits) |
81
- | `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`. |
82
82
  | `get_editor` | Full project state — the review step |
83
83
  | `set_scene_count` | Grow/shrink to N scenes (1–20) |
84
84
  | `set_segment_prompt` / `generate_segment` / `generate_all_segments` | Write a scene prompt (free); render one (5 credits) or every scene in order |
package/dist/index.js CHANGED
@@ -29988,13 +29988,29 @@ function tool(fn) {
29988
29988
  }
29989
29989
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
29990
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
+ ];
29991
30007
  var RO = { readOnlyHint: true, destructiveHint: false, openWorldHint: true };
29992
30008
  var WRITE = {
29993
30009
  readOnlyHint: false,
29994
30010
  destructiveHint: false,
29995
30011
  openWorldHint: true
29996
30012
  };
29997
- var server = new McpServer({ name: "hubfluencer", version: "0.3.0" });
30013
+ var server = new McpServer({ name: "hubfluencer", version: "0.5.0" });
29998
30014
  var registerTool = server.registerTool.bind(server);
29999
30015
  async function pollToTerminal(client, kind, slug, extra, budgetMs, intervalMs) {
30000
30016
  const deadline = Date.now() + budgetMs;
@@ -30035,7 +30051,7 @@ async function pollSegmentToTerminal(client, slug, segmentId, extra, budgetMs, i
30035
30051
  }
30036
30052
  registerTool("make_video", {
30037
30053
  title: "Make a video from a prompt (one shot)",
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.",
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.",
30039
30055
  inputSchema: {
30040
30056
  prompt: exports_external.string().describe("What the ad/video should be about (min 10 chars)"),
30041
30057
  kind: exports_external.string().optional().describe("'short' (fast, 1 clip), 'editor' (multi-scene), or 'auto' (default — inferred)"),
@@ -30064,23 +30080,9 @@ registerTool("make_video", {
30064
30080
  ]).optional().describe("SHORTS only: title overlay animation (default fade_in)"),
30065
30081
  font_family: exports_external.string().optional().describe("SHORTS only: overlay font family, e.g. ShortFontSpaceGrotesk"),
30066
30082
  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"),
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"),
30084
30086
  save_path: exports_external.string().optional().describe("Optional .mp4 path to download to (confined to HUBFLUENCER_OUTPUT_DIR or cwd)"),
30085
30087
  max_wait_seconds: exports_external.number().optional().describe("Block budget seconds (default 240, capped 10–280)"),
30086
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."),
@@ -30127,8 +30129,10 @@ registerTool("make_video", {
30127
30129
  product_prompt: args.prompt,
30128
30130
  export_aspect_ratio: args.aspect,
30129
30131
  voice_id: args.voice_id,
30132
+ creative_format: args.creative_format,
30133
+ visual_language: args.visual_language,
30130
30134
  theme: args.theme
30131
- }, 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 ?? ""));
30132
30136
  slug = created.data.slug;
30133
30137
  }
30134
30138
  const costPath = kind === "short" ? `/shorts/${slug}/cost` : `/editor/${slug}/autopilot/cost`;
@@ -30243,22 +30247,8 @@ registerTool("create_short", {
30243
30247
  language: exports_external.string().optional().describe('Language code, e.g. "en" (default)'),
30244
30248
  headline: exports_external.string().max(160).optional().describe("The on-screen TITLE composited over the short (poster text overlay). ≤160 chars. Set this."),
30245
30249
  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."),
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."),
30262
30252
  theme: exports_external.string().optional().describe("Deprecated for shorts: legacy visual theme used only when visual_language is unset."),
30263
30253
  music_vibe: exports_external.string().optional().describe("Background-music mood. Recognized: Upbeat (default), Cinematic, Minimal, Luxury, Playful, Jazz."),
30264
30254
  music_instruments: exports_external.array(exports_external.string()).optional().describe('Optional instrument hints, e.g. ["piano", "strings"]'),
@@ -30564,7 +30554,9 @@ registerTool("create_editor_ad", {
30564
30554
  inputSchema: {
30565
30555
  product_prompt: exports_external.string().min(10).describe("Brief for the ad (min 10 chars)"),
30566
30556
  language: exports_external.string().optional().describe('Language code, e.g. "en" (default)'),
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.'),
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.'),
30568
30560
  voice_id: exports_external.string().optional().describe("Preferred narration voice id (see list_voices); omit for the default voice"),
30569
30561
  export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")')
30570
30562
  },
@@ -30573,10 +30565,12 @@ registerTool("create_editor_ad", {
30573
30565
  const created = await client.post("/editor", {
30574
30566
  language: args.language ?? "en",
30575
30567
  product_prompt: args.product_prompt,
30568
+ creative_format: args.creative_format,
30569
+ visual_language: args.visual_language,
30576
30570
  theme: args.theme,
30577
30571
  voice_id: args.voice_id,
30578
30572
  export_aspect_ratio: args.export_aspect_ratio
30579
- }, 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 ?? "")));
30580
30574
  const slug = created.data.slug;
30581
30575
  const started = await client.post(`/editor/${slug}/autopilot`, undefined, `autopilot:${slug}`);
30582
30576
  const status = normalizeStatus("editor", slug, asRecord(started).data);
@@ -30619,7 +30613,9 @@ registerTool("create_editor_draft", {
30619
30613
  inputSchema: {
30620
30614
  product_prompt: exports_external.string().min(10).max(5000).optional().describe("Brief for the ad — 10–5000 chars, or omit entirely"),
30621
30615
  language: exports_external.string().min(2).max(10).optional().describe('Language code, e.g. "en" (default)'),
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.'),
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.'),
30623
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"),
30624
30620
  export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")'),
30625
30621
  project_intent: exports_external.enum(["social_ad", "creative_story"]).optional().describe("Project intent (optional)")
@@ -30633,6 +30629,8 @@ registerTool("create_editor_draft", {
30633
30629
  const prompt = args.product_prompt?.trim();
30634
30630
  const body = {
30635
30631
  language: args.language ?? "en",
30632
+ creative_format: args.creative_format,
30633
+ visual_language: args.visual_language,
30636
30634
  theme: args.theme,
30637
30635
  voice_id: args.voice_id,
30638
30636
  export_aspect_ratio: args.export_aspect_ratio,
@@ -30640,7 +30638,7 @@ registerTool("create_editor_draft", {
30640
30638
  };
30641
30639
  if (prompt)
30642
30640
  body.product_prompt = prompt;
30643
- 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 ?? ""));
30644
30642
  return ok({
30645
30643
  slug: created.data.slug,
30646
30644
  kind: "editor",
@@ -30670,10 +30668,13 @@ registerTool("set_scenario", {
30670
30668
  }));
30671
30669
  registerTool("generate_scenario", {
30672
30670
  title: "Generate the editor scenario (AI assist)",
30673
- 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).",
30674
30672
  inputSchema: {
30675
30673
  slug: exports_external.string().describe("Editor project slug"),
30676
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)."),
30677
30678
  auto_unlock: exports_external.boolean().optional().describe("On 429, spend 1 credit to unlock +10 assists and retry once (default false)")
30678
30679
  },
30679
30680
  annotations: {
@@ -30685,6 +30686,12 @@ registerTool("generate_scenario", {
30685
30686
  const body = {};
30686
30687
  if (args.segments_count !== undefined)
30687
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;
30688
30695
  const res = await withAssist(client, args.auto_unlock ?? false, () => client.post(`/editor/${args.slug}/generate-scenario`, body));
30689
30696
  return ok(asRecord(res).data ?? res);
30690
30697
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubfluencer/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.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>",
package/src/index.ts CHANGED
@@ -317,6 +317,27 @@ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
317
317
 
318
318
  const kindSchema = z.enum(["short", "editor"]).describe("Project kind");
319
319
 
320
+ // Creative-style value lists shared by SHORTS and EDITOR (single source of
321
+ // truth, mirroring the API's VideoFactory.creative_formats/0 +
322
+ // visual_languages/0). creative_format = narrative arc; visual_language =
323
+ // production treatment / render look.
324
+ const CREATIVE_FORMATS = [
325
+ "problem_solution",
326
+ "mistake_fix",
327
+ "myth_vs_reality",
328
+ "before_after",
329
+ "proof_demo",
330
+ "product_reveal",
331
+ ] as const;
332
+ const VISUAL_LANGUAGES = [
333
+ "kinetic_creator",
334
+ "premium_editorial",
335
+ "cinematic_product",
336
+ "ugc_realism",
337
+ "startup_explainer",
338
+ "luxury_minimal",
339
+ ] as const;
340
+
320
341
  const RO = { readOnlyHint: true, destructiveHint: false, openWorldHint: true };
321
342
  const WRITE = {
322
343
  readOnlyHint: false,
@@ -324,7 +345,7 @@ const WRITE = {
324
345
  openWorldHint: true,
325
346
  };
326
347
 
327
- const server = new McpServer({ name: "hubfluencer", version: "0.3.0" });
348
+ const server = new McpServer({ name: "hubfluencer", version: "0.5.0" });
328
349
 
329
350
  // The SDK's `registerTool` is generic over the Zod input shape; with this many
330
351
  // tools its conditional types hit TS2589 ("excessively deep"). Inputs are still
@@ -427,8 +448,10 @@ registerTool(
427
448
  "for simple/short ones; the chosen kind is reported back as kind_inferred. If it returns terminal=false " +
428
449
  "the render is still running — call wait_for_completion with the returned slug to finish. " +
429
450
  "BE PROACTIVE WITH BRANDING: pass headline (the on-screen TITLE) and subheadline (secondary title) plus " +
430
- "music_vibe/visual_language so a one-shot short isn't bare these apply to SHORTS (music_vibe/headline/subheadline " +
431
- "are ignored for editor; theme applies to editor and is legacy-only for shorts). For richer branding — a product image, brand logo, or " +
451
+ "music_vibe so a one-shot short isn't bare. creative_format + visual_language apply to BOTH kinds (editor and " +
452
+ "shorts); headline/subheadline/music_vibe/text_* and the conversion graphics are SHORTS-only and ignored for " +
453
+ "editor; theme applies to editor (genre overlay) and is legacy-only for shorts (used when visual_language is unset). " +
454
+ "For richer branding — a product image, brand logo, or " +
432
455
  "closing card — drive the granular path instead: create_short + set_short_product/set_short_poster, or " +
433
456
  "create_editor_draft + set_product/set_logo/set_closing_image, then start_autopilot/generate_short.",
434
457
  // Schema kept intentionally flat (no .min/.max/.int chains) — the SDK's
@@ -535,34 +558,23 @@ registerTool(
535
558
  "SHORTS only: music mood — Upbeat (default), Cinematic, Minimal, Luxury, Playful, Jazz",
536
559
  ),
537
560
  creative_format: z
538
- .enum([
539
- "problem_solution",
540
- "mistake_fix",
541
- "myth_vs_reality",
542
- "before_after",
543
- "proof_demo",
544
- "product_reveal",
545
- ])
561
+ .enum(CREATIVE_FORMATS)
546
562
  .optional()
547
- .describe("SHORTS only: segment structure. Omit for Auto/generic."),
563
+ .describe(
564
+ "SHORTS + EDITOR: narrative arc / segment structure. Omit for Auto/generic.",
565
+ ),
548
566
  visual_language: z
549
- .enum([
550
- "kinetic_creator",
551
- "premium_editorial",
552
- "cinematic_product",
553
- "ugc_realism",
554
- "startup_explainer",
555
- "luxury_minimal",
556
- ])
567
+ .enum(VISUAL_LANGUAGES)
557
568
  .optional()
558
569
  .describe(
559
- "SHORTS only: visual style direction and render look. Default app choice is kinetic_creator.",
570
+ "SHORTS + EDITOR: visual style direction and render look. Default app choice is kinetic_creator.",
560
571
  ),
561
572
  theme: z
562
573
  .string()
563
574
  .optional()
564
575
  .describe(
565
- "Visual theme: editor style, and legacy shorts fallback only when visual_language is unset. " +
576
+ "Visual theme: editor genre overlay, and legacy shorts fallback only when visual_language is unset. " +
577
+ "For editor, when visual_language is set it drives the look and 'none' is ignored. " +
566
578
  "none (literal — no imposed style), realistic (default), cinematic, " +
567
579
  "anime, sci_fi, fantasy, noir, superhero, horror, mockumentary, sports, gaming, retro_80s, " +
568
580
  "minimalist, cyberpunk",
@@ -708,6 +720,8 @@ registerTool(
708
720
  product_prompt: args.prompt,
709
721
  export_aspect_ratio: args.aspect,
710
722
  voice_id: args.voice_id,
723
+ creative_format: args.creative_format,
724
+ visual_language: args.visual_language,
711
725
  theme: args.theme,
712
726
  },
713
727
  idemKey(
@@ -716,6 +730,8 @@ registerTool(
716
730
  args.language ?? "en",
717
731
  args.aspect ?? "",
718
732
  args.voice_id ?? "",
733
+ args.creative_format ?? "",
734
+ args.visual_language ?? "",
719
735
  args.theme ?? "",
720
736
  ),
721
737
  );
@@ -995,27 +1011,13 @@ registerTool(
995
1011
  "The SECONDARY title / supporting line under the headline. ≤200 chars.",
996
1012
  ),
997
1013
  creative_format: z
998
- .enum([
999
- "problem_solution",
1000
- "mistake_fix",
1001
- "myth_vs_reality",
1002
- "before_after",
1003
- "proof_demo",
1004
- "product_reveal",
1005
- ])
1014
+ .enum(CREATIVE_FORMATS)
1006
1015
  .optional()
1007
1016
  .describe(
1008
1017
  "Optional structure: problem_solution, mistake_fix, myth_vs_reality, before_after, proof_demo, product_reveal. Omit for Auto.",
1009
1018
  ),
1010
1019
  visual_language: z
1011
- .enum([
1012
- "kinetic_creator",
1013
- "premium_editorial",
1014
- "cinematic_product",
1015
- "ugc_realism",
1016
- "startup_explainer",
1017
- "luxury_minimal",
1018
- ])
1020
+ .enum(VISUAL_LANGUAGES)
1019
1021
  .optional()
1020
1022
  .describe(
1021
1023
  "Visual language for Veo direction and render styling. Good default: kinetic_creator.",
@@ -1652,11 +1654,21 @@ registerTool(
1652
1654
  .string()
1653
1655
  .optional()
1654
1656
  .describe('Language code, e.g. "en" (default)'),
1657
+ creative_format: z
1658
+ .enum(CREATIVE_FORMATS)
1659
+ .optional()
1660
+ .describe("Narrative arc / segment structure. Omit for Auto/generic."),
1661
+ visual_language: z
1662
+ .enum(VISUAL_LANGUAGES)
1663
+ .optional()
1664
+ .describe(
1665
+ "Visual style direction and render look. Good default: kinetic_creator. When set it drives the look and a theme of 'none' is ignored.",
1666
+ ),
1655
1667
  theme: z
1656
1668
  .string()
1657
1669
  .optional()
1658
1670
  .describe(
1659
- '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.',
1671
+ '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.',
1660
1672
  ),
1661
1673
  voice_id: z
1662
1674
  .string()
@@ -1677,11 +1689,24 @@ registerTool(
1677
1689
  {
1678
1690
  language: args.language ?? "en",
1679
1691
  product_prompt: args.product_prompt,
1692
+ creative_format: args.creative_format,
1693
+ visual_language: args.visual_language,
1680
1694
  theme: args.theme,
1681
1695
  voice_id: args.voice_id,
1682
1696
  export_aspect_ratio: args.export_aspect_ratio,
1683
1697
  },
1684
- idemKey("create-editor", String(args.product_prompt ?? "")),
1698
+ // Fold every create-affecting param into the key so two calls that
1699
+ // differ only by style/theme/voice/aspect get distinct drafts.
1700
+ idemKey(
1701
+ "create-editor",
1702
+ String(args.product_prompt ?? ""),
1703
+ String(args.language ?? "en"),
1704
+ String(args.creative_format ?? ""),
1705
+ String(args.visual_language ?? ""),
1706
+ String(args.theme ?? ""),
1707
+ String(args.voice_id ?? ""),
1708
+ String(args.export_aspect_ratio ?? ""),
1709
+ ),
1685
1710
  );
1686
1711
  const slug = created.data.slug;
1687
1712
  const started = await client.post<{ data: unknown }>(
@@ -1799,11 +1824,21 @@ registerTool(
1799
1824
  .max(10)
1800
1825
  .optional()
1801
1826
  .describe('Language code, e.g. "en" (default)'),
1827
+ creative_format: z
1828
+ .enum(CREATIVE_FORMATS)
1829
+ .optional()
1830
+ .describe("Narrative arc / segment structure. Omit for Auto/generic."),
1831
+ visual_language: z
1832
+ .enum(VISUAL_LANGUAGES)
1833
+ .optional()
1834
+ .describe(
1835
+ "Visual style direction and render look. Good default: kinetic_creator. When set it drives the look and a theme of 'none' is ignored.",
1836
+ ),
1802
1837
  theme: z
1803
1838
  .string()
1804
1839
  .optional()
1805
1840
  .describe(
1806
- '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.',
1841
+ '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.',
1807
1842
  ),
1808
1843
  voice_id: z
1809
1844
  .string()
@@ -1833,6 +1868,8 @@ registerTool(
1833
1868
  args: {
1834
1869
  product_prompt?: string;
1835
1870
  language?: string;
1871
+ creative_format?: string;
1872
+ visual_language?: string;
1836
1873
  theme?: string;
1837
1874
  voice_id?: string;
1838
1875
  export_aspect_ratio?: string;
@@ -1843,6 +1880,8 @@ registerTool(
1843
1880
  const prompt = args.product_prompt?.trim();
1844
1881
  const body: Record<string, unknown> = {
1845
1882
  language: args.language ?? "en",
1883
+ creative_format: args.creative_format,
1884
+ visual_language: args.visual_language,
1846
1885
  theme: args.theme,
1847
1886
  voice_id: args.voice_id,
1848
1887
  export_aspect_ratio: args.export_aspect_ratio,
@@ -1852,7 +1891,14 @@ registerTool(
1852
1891
  const created = await client.post<{ data: { slug: string } }>(
1853
1892
  "/editor",
1854
1893
  body,
1855
- idemKey("create-editor-draft", prompt ?? "", args.language ?? "en"),
1894
+ idemKey(
1895
+ "create-editor-draft",
1896
+ prompt ?? "",
1897
+ args.language ?? "en",
1898
+ args.creative_format ?? "",
1899
+ args.visual_language ?? "",
1900
+ args.theme ?? "",
1901
+ ),
1856
1902
  );
1857
1903
  return ok({
1858
1904
  slug: created.data.slug,
@@ -1913,7 +1959,9 @@ registerTool(
1913
1959
  description:
1914
1960
  "Generates a scenario with AI. CONSUMES 1 AI ASSIST (free daily quota; check get_ai_assists). On 429 " +
1915
1961
  "(quota exhausted) either set auto_unlock:true to spend 1 credit for +10 assists and retry once, or " +
1916
- "write the scenario yourself with set_scenario. Server default segments_count is 5.",
1962
+ "write the scenario yourself with set_scenario. Server default segments_count is 5. " +
1963
+ "Optionally pass creative_format / visual_language / theme to persist the project\u2019s creative style before " +
1964
+ "generating (this is the granular path\u2019s way to set/change style; PATCH /scenario does not).",
1917
1965
  inputSchema: {
1918
1966
  slug: z.string().describe("Editor project slug"),
1919
1967
  segments_count: z
@@ -1923,6 +1971,24 @@ registerTool(
1923
1971
  .max(10)
1924
1972
  .optional()
1925
1973
  .describe("How many scenes (3–10, server default 5)"),
1974
+ creative_format: z
1975
+ .enum(CREATIVE_FORMATS)
1976
+ .optional()
1977
+ .describe(
1978
+ "Persist the narrative arc / segment structure before generating. Omit to leave it unchanged; pass an empty string to clear.",
1979
+ ),
1980
+ visual_language: z
1981
+ .enum(VISUAL_LANGUAGES)
1982
+ .optional()
1983
+ .describe(
1984
+ "Persist the visual style / render look before generating. When set it drives the look and a theme of 'none' is ignored.",
1985
+ ),
1986
+ theme: z
1987
+ .string()
1988
+ .optional()
1989
+ .describe(
1990
+ "Persist the genre overlay / theme before generating (e.g. realistic, cinematic, anime, none).",
1991
+ ),
1926
1992
  auto_unlock: z
1927
1993
  .boolean()
1928
1994
  .optional()
@@ -1941,6 +2007,9 @@ registerTool(
1941
2007
  args: {
1942
2008
  slug: string;
1943
2009
  segments_count?: number;
2010
+ creative_format?: string;
2011
+ visual_language?: string;
2012
+ theme?: string;
1944
2013
  auto_unlock?: boolean;
1945
2014
  },
1946
2015
  client,
@@ -1948,6 +2017,11 @@ registerTool(
1948
2017
  const body: Record<string, unknown> = {};
1949
2018
  if (args.segments_count !== undefined)
1950
2019
  body.segments_count = args.segments_count;
2020
+ if (args.creative_format !== undefined)
2021
+ body.creative_format = args.creative_format;
2022
+ if (args.visual_language !== undefined)
2023
+ body.visual_language = args.visual_language;
2024
+ if (args.theme !== undefined) body.theme = args.theme;
1951
2025
  const res = await withAssist(client, args.auto_unlock ?? false, () =>
1952
2026
  client.post<{ data: unknown }>(
1953
2027
  `/editor/${args.slug}/generate-scenario`,