@hubfluencer/mcp 0.11.0 → 0.13.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 +37 -10
- package/dist/index.js +160 -64
- package/package.json +1 -1
- package/src/core.ts +17 -8
- package/src/index.ts +277 -105
- package/src/output-schemas.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -29508,6 +29508,8 @@ function normalizeStatus(kind, slug, data) {
|
|
|
29508
29508
|
status.failure_source = d.failure_source;
|
|
29509
29509
|
if (typeof d.failure_attempt === "number")
|
|
29510
29510
|
status.failure_attempt = d.failure_attempt;
|
|
29511
|
+
if (typeof d.generation_attempt === "number")
|
|
29512
|
+
status.generation_attempt = d.generation_attempt;
|
|
29511
29513
|
if (typeof d.latest_render_free === "boolean") {
|
|
29512
29514
|
status.latest_render_free = d.latest_render_free;
|
|
29513
29515
|
}
|
|
@@ -29555,6 +29557,7 @@ function normalizeStatus(kind, slug, data) {
|
|
|
29555
29557
|
const failedSegments = segments.filter((segment) => segment.status === "failed");
|
|
29556
29558
|
const batchStatus = d.batch_generation_status ?? null;
|
|
29557
29559
|
const batchActive = batchStatus === "running" || batchStatus === "preparing" || batchStatus === "awaiting_previews";
|
|
29560
|
+
const autoRenderHandoffActive = batchStatus === "completed" && d.auto_render_after_batch === true;
|
|
29558
29561
|
const batchRetryRequired = batchStatus === "paused_for_retry";
|
|
29559
29562
|
const batchInsufficientCredits = batchStatus === "paused_insufficient_credits";
|
|
29560
29563
|
const batchFailed = batchStatus === "failed";
|
|
@@ -29599,7 +29602,7 @@ function normalizeStatus(kind, slug, data) {
|
|
|
29599
29602
|
currentMusic.error_message
|
|
29600
29603
|
].find((value) => typeof value === "string" && value.trim().length > 0);
|
|
29601
29604
|
const audioActive = currentAudio.status === "pending" || currentAudio.status === "processing" || currentMusic.status === "pending" || currentMusic.status === "processing" || newerAudioVersions.some(versionActive) || newerMusicVersions.some(versionActive);
|
|
29602
|
-
const childWorkActive = batchActive || renderActive || scenarioActive || narrationActive || audioActive || activeSegments.length > 0;
|
|
29605
|
+
const childWorkActive = batchActive || autoRenderHandoffActive || renderActive || scenarioActive || narrationActive || audioActive || activeSegments.length > 0;
|
|
29603
29606
|
const autopilotStartedAt = typeof d.autopilot_started_at === "string" ? Date.parse(d.autopilot_started_at) : Number.NaN;
|
|
29604
29607
|
const now = Date.now();
|
|
29605
29608
|
const autopilotResumeGraceActive = autopilotActive && Number.isFinite(autopilotStartedAt) && autopilotStartedAt <= now && now - autopilotStartedAt <= 120000;
|
|
@@ -29642,6 +29645,8 @@ function normalizeStatus(kind, slug, data) {
|
|
|
29642
29645
|
stage = "segment_processing";
|
|
29643
29646
|
else if (batchActive)
|
|
29644
29647
|
stage = "segment_generation";
|
|
29648
|
+
else if (autoRenderHandoffActive)
|
|
29649
|
+
stage = "rendering";
|
|
29645
29650
|
else if (renderActive)
|
|
29646
29651
|
stage = "rendering";
|
|
29647
29652
|
else if (scenarioActive)
|
|
@@ -29661,7 +29666,7 @@ function normalizeStatus(kind, slug, data) {
|
|
|
29661
29666
|
terminal,
|
|
29662
29667
|
ready,
|
|
29663
29668
|
video_url: videoUrl,
|
|
29664
|
-
error: ready ? null : batchRetryRequired ? "Batch generation is paused after a failed scene. Call retry_editor_batch or cancel_editor_batch before continuing." : (parentFailureActionable ? d.autopilot_error_message : null) ?? actionableChildError ?? (batchInsufficientCredits ? "Batch generation paused: not enough credits to finish. Top up or reduce scope, then
|
|
29669
|
+
error: ready ? null : batchRetryRequired ? "Batch generation is paused after a failed scene. Call retry_editor_batch or cancel_editor_batch before continuing." : (parentFailureActionable ? d.autopilot_error_message : null) ?? actionableChildError ?? (batchInsufficientCredits ? "Batch generation paused: not enough credits to finish. Top up or reduce scope, then invoke the generation/render path again; retry_editor_batch only applies to paused_for_retry." : null),
|
|
29665
29670
|
stale,
|
|
29666
29671
|
active_segment_ids: activeSegments.map((segment) => segment.id).filter((id) => typeof id === "number" || typeof id === "string"),
|
|
29667
29672
|
stale_segment_ids: staleSegments.map((segment) => segment.id).filter((id) => typeof id === "number" || typeof id === "string")
|
|
@@ -29694,10 +29699,10 @@ function makeVideoAutopilotKey(slug, a, startState) {
|
|
|
29694
29699
|
return idemKey("autopilot", slug, a.prompt, a.product_subject ?? "", a.project_intent ?? "", a.language ?? "en", a.aspect ?? "", a.voice_id ?? "", a.creative_format ?? "", a.visual_language ?? "", a.theme ?? "", startState);
|
|
29695
29700
|
}
|
|
29696
29701
|
function editorAdCreateKey(a) {
|
|
29697
|
-
return idemKey("create-editor", a.product_prompt ?? "", a.product_subject ?? "", a.project_intent ?? "", a.language ?? "en", a.creative_format ?? "", a.visual_language ?? "", a.theme ?? "", a.voice_id ?? "", a.export_aspect_ratio ?? "", a.product_image_path ?? "", a.product_image_identity ?? "", a.product_description ?? "", a.closing_image_path ?? "", a.closing_image_identity ?? "", a.logo_path ?? "", a.logo_identity ?? "", a.logo_treatment ?? "", a.logo_position ?? "", a.logo_duration_seconds === undefined ? "" : String(a.logo_duration_seconds)
|
|
29702
|
+
return idemKey("create-editor", a.product_prompt ?? "", a.product_subject ?? "", a.project_intent ?? "", a.language ?? "en", a.creative_format ?? "", a.visual_language ?? "", a.theme ?? "", a.voice_id ?? "", a.export_aspect_ratio ?? "", a.product_image_path ?? "", a.product_image_identity ?? "", a.product_description ?? "", a.closing_image_path ?? "", a.closing_image_identity ?? "", a.logo_path ?? "", a.logo_identity ?? "", a.logo_treatment ?? "", a.logo_position ?? "", a.logo_duration_seconds === undefined ? "" : String(a.logo_duration_seconds));
|
|
29698
29703
|
}
|
|
29699
29704
|
function editorAdAutopilotKey(slug, a) {
|
|
29700
|
-
return idemKey("autopilot", slug, a.product_prompt ?? "", a.product_subject ?? "", a.project_intent ?? "", a.language ?? "en", a.creative_format ?? "", a.visual_language ?? "", a.theme ?? "", a.voice_id ?? "", a.export_aspect_ratio ?? "", a.product_image_path ?? "", a.product_description ?? "", a.closing_image_path ?? "", a.logo_path ?? "", a.logo_treatment ?? "", a.logo_position ?? "", a.logo_duration_seconds === undefined ? "" : String(a.logo_duration_seconds), a.
|
|
29705
|
+
return idemKey("autopilot", slug, a.product_prompt ?? "", a.product_subject ?? "", a.project_intent ?? "", a.language ?? "en", a.creative_format ?? "", a.visual_language ?? "", a.theme ?? "", a.voice_id ?? "", a.export_aspect_ratio ?? "", a.product_image_path ?? "", a.product_description ?? "", a.closing_image_path ?? "", a.logo_path ?? "", a.logo_treatment ?? "", a.logo_position ?? "", a.logo_duration_seconds === undefined ? "" : String(a.logo_duration_seconds), a.max_credits === undefined ? "" : String(a.max_credits));
|
|
29701
29706
|
}
|
|
29702
29707
|
function sliderCreateKey(a) {
|
|
29703
29708
|
return idemKey("create-slider", a.prompt, a.mode ?? "", a.template ?? "", a.slide_count === undefined ? "" : String(a.slide_count), a.aspect_ratio ?? "", a.accent_color ?? "", a.text_position ?? "");
|
|
@@ -30254,7 +30259,7 @@ function readStoredCredentials() {
|
|
|
30254
30259
|
// package.json
|
|
30255
30260
|
var package_default = {
|
|
30256
30261
|
name: "@hubfluencer/mcp",
|
|
30257
|
-
version: "0.
|
|
30262
|
+
version: "0.13.0",
|
|
30258
30263
|
description: "Model Context Protocol server for Hubfluencer — let AI agents generate post-ready shorts and editor ads.",
|
|
30259
30264
|
license: "MIT",
|
|
30260
30265
|
author: "Monocursive <contact@monocursive.com>",
|
|
@@ -30572,6 +30577,7 @@ var getStatusOutput = exports_external.object({
|
|
|
30572
30577
|
failure_details: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Short only: structured provider/QC details for recovery."),
|
|
30573
30578
|
failure_source: exports_external.string().optional().describe("Short only: worker/recovery layer that finalized the failure."),
|
|
30574
30579
|
failure_attempt: exports_external.number().optional().describe("Short only: paid generation attempt number that failed."),
|
|
30580
|
+
generation_attempt: exports_external.number().optional().describe("Short only: current paid attempt; echo when cancelling."),
|
|
30575
30581
|
latest_render_free: exports_external.boolean().optional(),
|
|
30576
30582
|
last_free_rerender_failed: exports_external.boolean().optional()
|
|
30577
30583
|
}).passthrough();
|
|
@@ -30591,6 +30597,7 @@ var waitForCompletionOutput = exports_external.object({
|
|
|
30591
30597
|
failure_details: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Short only: structured provider/QC details for recovery."),
|
|
30592
30598
|
failure_source: exports_external.string().optional().describe("Short only: worker/recovery layer that finalized the failure."),
|
|
30593
30599
|
failure_attempt: exports_external.number().optional().describe("Short only: paid generation attempt number that failed."),
|
|
30600
|
+
generation_attempt: exports_external.number().optional().describe("Short only: current paid attempt; echo when cancelling."),
|
|
30594
30601
|
latest_render_free: exports_external.boolean().optional(),
|
|
30595
30602
|
last_free_rerender_failed: exports_external.boolean().optional(),
|
|
30596
30603
|
saved_to: exports_external.union([exports_external.string(), exports_external.null()]).optional(),
|
|
@@ -31566,9 +31573,9 @@ async function editorAutopilotQuote(client, slug, overrides = {}) {
|
|
|
31566
31573
|
const params = new URLSearchParams;
|
|
31567
31574
|
if (overrides.language)
|
|
31568
31575
|
params.set("language", overrides.language);
|
|
31569
|
-
if (overrides.product_subject)
|
|
31576
|
+
if (overrides.product_subject !== undefined)
|
|
31570
31577
|
params.set("product_subject", overrides.product_subject);
|
|
31571
|
-
if (overrides.product_prompt)
|
|
31578
|
+
if (overrides.product_prompt !== undefined)
|
|
31572
31579
|
params.set("product_prompt", overrides.product_prompt);
|
|
31573
31580
|
if (overrides.restart)
|
|
31574
31581
|
params.set("restart", "true");
|
|
@@ -31795,6 +31802,23 @@ var VISUAL_LANGUAGES = [
|
|
|
31795
31802
|
"startup_explainer",
|
|
31796
31803
|
"luxury_minimal"
|
|
31797
31804
|
];
|
|
31805
|
+
var SCENARIO_THEMES = [
|
|
31806
|
+
"none",
|
|
31807
|
+
"realistic",
|
|
31808
|
+
"cinematic",
|
|
31809
|
+
"anime",
|
|
31810
|
+
"sci_fi",
|
|
31811
|
+
"fantasy",
|
|
31812
|
+
"noir",
|
|
31813
|
+
"superhero",
|
|
31814
|
+
"horror",
|
|
31815
|
+
"mockumentary",
|
|
31816
|
+
"sports",
|
|
31817
|
+
"gaming",
|
|
31818
|
+
"retro_80s",
|
|
31819
|
+
"minimalist",
|
|
31820
|
+
"cyberpunk"
|
|
31821
|
+
];
|
|
31798
31822
|
var LANGUAGES = [
|
|
31799
31823
|
"en",
|
|
31800
31824
|
"fr",
|
|
@@ -31921,7 +31945,7 @@ registerTool("make_video", {
|
|
|
31921
31945
|
music_instruments: exports_external.array(exports_external.string()).optional().describe('SHORTS only: instrument hints woven into the music (≤10, each ≤40 chars), e.g. ["piano", "strings"]'),
|
|
31922
31946
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("SHORTS + EDITOR: narrative arc / segment structure. Omit for Auto/generic."),
|
|
31923
31947
|
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("SHORTS + EDITOR: visual style direction and render look. Default app choice is kinetic_creator."),
|
|
31924
|
-
theme: exports_external.
|
|
31948
|
+
theme: exports_external.enum(SCENARIO_THEMES).optional().describe("Visual theme: editor genre overlay, and legacy shorts fallback only when visual_language is unset. " + "For editor, visual_language drives the look when present; make_video defaults theme to none " + "(literal — no imposed genre style). Options: none, realistic, cinematic, " + "anime, sci_fi, fantasy, noir, superhero, horror, mockumentary, sports, gaming, retro_80s, " + "minimalist, cyberpunk"),
|
|
31925
31949
|
save_path: exports_external.string().optional().describe("Optional .mp4 path to download to (confined to HUBFLUENCER_OUTPUT_DIR or cwd)"),
|
|
31926
31950
|
max_wait_seconds: exports_external.number().optional().describe("Block budget seconds (default 240, capped 10–280)"),
|
|
31927
31951
|
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."),
|
|
@@ -32000,8 +32024,8 @@ registerTool("make_video", {
|
|
|
32000
32024
|
voice_id: args.voice_id,
|
|
32001
32025
|
creative_format: args.creative_format,
|
|
32002
32026
|
visual_language: args.visual_language,
|
|
32003
|
-
theme: args.theme
|
|
32004
|
-
}, makeVideoEditorCreateKey(args));
|
|
32027
|
+
theme: args.theme ?? "none"
|
|
32028
|
+
}, makeVideoEditorCreateKey({ ...args, theme: args.theme ?? "none" }));
|
|
32005
32029
|
slug = created.data.slug;
|
|
32006
32030
|
}
|
|
32007
32031
|
const costPath = kind === "short" ? `/shorts/${slug}/cost` : `/editor/${slug}/autopilot/cost`;
|
|
@@ -32152,7 +32176,7 @@ registerTool("create_short", {
|
|
|
32152
32176
|
subheadline: exports_external.string().max(200).optional().describe("The SECONDARY title / supporting line under the headline. ≤200 chars."),
|
|
32153
32177
|
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."),
|
|
32154
32178
|
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("Visual language for Veo direction and render styling. Good default: kinetic_creator."),
|
|
32155
|
-
theme: exports_external.
|
|
32179
|
+
theme: exports_external.enum(SCENARIO_THEMES).optional().describe("Deprecated for shorts: legacy visual theme used only when visual_language is unset."),
|
|
32156
32180
|
music_vibe: exports_external.string().optional().describe("Background-music mood. Recognized: Upbeat (default), Cinematic, Minimal, Luxury, Playful, Jazz."),
|
|
32157
32181
|
music_instruments: exports_external.array(exports_external.string().max(40)).max(10).optional().describe('Optional instrument hints (≤10, each ≤40 chars), e.g. ["piano", "strings"]'),
|
|
32158
32182
|
text_position: exports_external.enum(["top", "center", "bottom"]).optional().describe("Where the title overlay sits (default bottom)"),
|
|
@@ -32250,7 +32274,7 @@ registerTool("update_short", {
|
|
|
32250
32274
|
subheadline: exports_external.string().max(200).optional().describe("Secondary title / supporting line. ≤200 chars."),
|
|
32251
32275
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Structure: problem_solution, mistake_fix, myth_vs_reality, before_after, proof_demo, product_reveal."),
|
|
32252
32276
|
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("Visual language for Veo direction and render styling."),
|
|
32253
|
-
theme: exports_external.
|
|
32277
|
+
theme: exports_external.enum(SCENARIO_THEMES).optional().describe("Deprecated for shorts: legacy visual theme used only when visual_language is unset."),
|
|
32254
32278
|
music_vibe: exports_external.string().optional().describe("Background-music mood: Upbeat, Cinematic, Minimal, Luxury, Playful, Jazz."),
|
|
32255
32279
|
music_instruments: exports_external.array(exports_external.string().max(40)).max(10).optional().describe('Instrument hints (≤10, each ≤40 chars), e.g. ["piano", "strings"]'),
|
|
32256
32280
|
text_position: exports_external.enum(["top", "center", "bottom"]).optional().describe("Where the title overlay sits."),
|
|
@@ -32513,6 +32537,31 @@ registerTool("generate_short", {
|
|
|
32513
32537
|
throw e;
|
|
32514
32538
|
}
|
|
32515
32539
|
}));
|
|
32540
|
+
registerTool("cancel_short_generation", {
|
|
32541
|
+
title: "Cancel a running short generation",
|
|
32542
|
+
description: "Stops the caller-observed paid Short attempt, fences late provider callbacks, and refunds the 15-credit " + "generation exactly once. Pass generation_attempt from get_status so a delayed command cannot cancel a " + "newer run on the same draft. The draft is preserved for editing and regeneration. Safe to repeat with " + "the same attempt after a successful cancellation. Scope: video:generate.",
|
|
32543
|
+
inputSchema: {
|
|
32544
|
+
slug: exports_external.string().describe("Slug of the processing short"),
|
|
32545
|
+
generation_attempt: exports_external.number().int().min(0).describe("generation_attempt from the latest get_status response")
|
|
32546
|
+
},
|
|
32547
|
+
annotations: {
|
|
32548
|
+
title: "Cancel short generation",
|
|
32549
|
+
readOnlyHint: false,
|
|
32550
|
+
destructiveHint: true,
|
|
32551
|
+
openWorldHint: true,
|
|
32552
|
+
idempotentHint: true
|
|
32553
|
+
}
|
|
32554
|
+
}, tool(async (args, client) => {
|
|
32555
|
+
const res = await client.post(`/shorts/${args.slug}/cancel`, { generation_attempt: args.generation_attempt }, undefined);
|
|
32556
|
+
const data = asRecord(asRecord(res).data ?? res);
|
|
32557
|
+
return ok({
|
|
32558
|
+
...data,
|
|
32559
|
+
slug: args.slug,
|
|
32560
|
+
charged: false,
|
|
32561
|
+
refund: "refunded" in data ? data.refunded : "settled (idempotent — no new credits minted on repeat)",
|
|
32562
|
+
next: "Edit the preserved draft, then call generate_short when ready."
|
|
32563
|
+
});
|
|
32564
|
+
}));
|
|
32516
32565
|
registerTool("rerender_short", {
|
|
32517
32566
|
title: "Re-render a short (FREE — 0 credits)",
|
|
32518
32567
|
description: "FREE re-render: applies the short's current text/style/CTA/end-card/logo state over the already-paid " + "footage and music — 0 credits. Use after update_short for copy/style/CTA tweaks, or after a logo change " + "(set_short_logo, or short_logo_position/short_logo_treatment via update_short). Footage or music " + "changes need generate_short (15 credits) instead. Edits that add/remove the end card (poster, " + "end_card) can shift the video between 12s and 14s; when the pinned music bed is shorter than the " + "export it crossfades into a repeated continuation, then fades at the export end, without regeneration. " + "Requires a completed generation to re-render over " + "(422 short_not_ready otherwise — run generate_short once first); while any generation/render is in " + "flight it reports in-progress (409) rather than duplicating. Then poll with get_status or " + "wait_for_completion (kind=short). A failed free re-render leaves the delivered video intact — retry " + "rerender_short, it stays free (the status fields latest_render_free / last_free_rerender_failed " + "report this when the server sends them).",
|
|
@@ -32756,7 +32805,7 @@ registerTool("edit_slider_slide", {
|
|
|
32756
32805
|
}));
|
|
32757
32806
|
registerTool("create_editor_ad", {
|
|
32758
32807
|
title: "Create, configure, and price a multi-scene editor ad",
|
|
32759
|
-
description: "Creates an editor project, attaches any supplied local product/logo/closing assets BEFORE generation, " + "and prices autopilot (server-orchestrated " + "scenario → segments → narration → voice → music → render). Each AI-generated scene is a fixed 8 seconds. " + "Without max_credits it STOPS after the free draft/configuration and returns the live estimate. It starts " + "the paid pipeline only when max_credits is supplied and covers the estimate. Then poll with get_status / " + "wait_for_completion (kind=editor). Prefer make_video for a prompt-only one-shot. " + "
|
|
32808
|
+
description: "Creates an editor project, attaches any supplied local product/logo/closing assets BEFORE generation, " + "and prices autopilot (server-orchestrated " + "scenario → segments → narration → voice → music → render). Each AI-generated scene is a fixed 8 seconds. " + "Without max_credits it STOPS after the free draft/configuration and returns the live estimate. It starts " + "the paid pipeline only when max_credits is supplied and covers the estimate. Then poll with get_status / " + "wait_for_completion (kind=editor). Prefer make_video for a prompt-only one-shot. " + "A product image is analyzed into semantic project context and sent as a product-identity reference to every " + "generated Editor scene. The exact photo is also linked as the closing image unless a separate closing image " + "was authored; generated fine label text can still vary. Scene prompts are the complete content contract: describe any people, " + "products, visual continuity, absences, and composition directly in the brief. (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.)",
|
|
32760
32809
|
inputSchema: {
|
|
32761
32810
|
product_prompt: exports_external.string().min(10).describe("Brief for the ad (min 10 chars)"),
|
|
32762
32811
|
product_subject: exports_external.string().max(300).optional().describe('The concrete product or main subject the video must be about (e.g. "Bordeaux red wine, dark green ' + 'bottle with a gold label"). Distinct from the freeform brief: hard-grounds the AI scenario + narration ' + "so it cannot invent an unrelated product. Strongly recommended for ads."),
|
|
@@ -32764,17 +32813,16 @@ registerTool("create_editor_ad", {
|
|
|
32764
32813
|
language: exports_external.enum(LANGUAGES).optional().describe('Language (default "en"). Bare ISO-639-1 code only — no region/script subtag (use "es", not "es-MX"). ' + "Drives the WHOLE video: scenario, narration script, narration voice, and captions. " + "Ask the user / infer from their locale."),
|
|
32765
32814
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Narrative arc / segment structure. Omit for Auto/generic."),
|
|
32766
32815
|
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."),
|
|
32767
|
-
theme: exports_external.
|
|
32816
|
+
theme: exports_external.enum(SCENARIO_THEMES).optional().describe('Optional visual theme / genre overlay (default "none", so scene prompts drive the look). One of: none, realistic, cinematic, anime, sci_fi, fantasy, noir, superhero, horror, mockumentary, sports, gaming, retro_80s, minimalist, cyberpunk.'),
|
|
32768
32817
|
voice_id: exports_external.string().optional().describe("Preferred narration voice id (see list_voices); omit for the default voice"),
|
|
32769
32818
|
export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")'),
|
|
32770
|
-
product_image_path: exports_external.string().optional().describe("Local product image (.jpg/.jpeg/.png, ≤8 MiB)
|
|
32771
|
-
product_description: exports_external.string().max(500).optional().describe("
|
|
32819
|
+
product_image_path: exports_external.string().optional().describe("Local product image (.jpg/.jpeg/.png, ≤8 MiB) used as the product-identity reference for every generated Editor scene and linked as the exact closing image unless one was already authored."),
|
|
32820
|
+
product_description: exports_external.string().max(500).optional().describe("Product description used to ground the scenario and narration when product_image_path is supplied."),
|
|
32772
32821
|
closing_image_path: exports_external.string().optional().describe("Local closing-card image (.jpg/.jpeg/.png, ≤20 MiB)."),
|
|
32773
32822
|
logo_path: exports_external.string().optional().describe("Local logo (.jpg/.jpeg/.png, ≤5 MiB) to overlay."),
|
|
32774
32823
|
logo_treatment: exports_external.enum(["intro", "outro", "both", "none"]).optional().describe('When the logo bumper plays: "intro", "outro", "both" (default), or "none". Requires logo_path.'),
|
|
32775
32824
|
logo_position: exports_external.enum(["top-left", "top-right", "bottom-left", "bottom-right"]).optional().describe('Logo corner: "top-left", "top-right" (default), "bottom-left", or "bottom-right". Requires logo_path.'),
|
|
32776
32825
|
logo_duration_seconds: exports_external.number().min(0.5).max(3).optional().describe("Logo bumper duration in seconds, 0.5–3 (default 1.5). Requires logo_path."),
|
|
32777
|
-
cast_mode: exports_external.literal("product_only").optional().describe("Lock every AI scene to the supplied product/mascot reference with no people. Requires product_image_path."),
|
|
32778
32826
|
max_credits: exports_external.number().int().nonnegative().optional().describe("Explicit spend cap. Omit to create/configure/price only; no credits are charged.")
|
|
32779
32827
|
},
|
|
32780
32828
|
outputSchema: createEditorOutput,
|
|
@@ -32791,9 +32839,6 @@ registerTool("create_editor_ad", {
|
|
|
32791
32839
|
const promptErr = validateProductPrompt(args.product_prompt);
|
|
32792
32840
|
if (promptErr)
|
|
32793
32841
|
return fail(promptErr);
|
|
32794
|
-
if (args.cast_mode === "product_only" && !args.product_image_path) {
|
|
32795
|
-
return fail("cast_mode product_only requires product_image_path so identity can be locked before generation.");
|
|
32796
|
-
}
|
|
32797
32842
|
if ((args.logo_treatment !== undefined || args.logo_position !== undefined || args.logo_duration_seconds !== undefined) && !args.logo_path) {
|
|
32798
32843
|
return fail("logo_treatment/logo_position/logo_duration_seconds require logo_path (the logo image they style). Provide logo_path or drop these settings.");
|
|
32799
32844
|
}
|
|
@@ -32819,12 +32864,12 @@ registerTool("create_editor_ad", {
|
|
|
32819
32864
|
project_intent: args.project_intent,
|
|
32820
32865
|
creative_format: args.creative_format,
|
|
32821
32866
|
visual_language: args.visual_language,
|
|
32822
|
-
theme: args.theme,
|
|
32867
|
+
theme: args.theme ?? "none",
|
|
32823
32868
|
voice_id: args.voice_id,
|
|
32824
|
-
export_aspect_ratio: args.export_aspect_ratio
|
|
32825
|
-
cast_mode: args.cast_mode
|
|
32869
|
+
export_aspect_ratio: args.export_aspect_ratio
|
|
32826
32870
|
}, editorAdCreateKey({
|
|
32827
32871
|
...args,
|
|
32872
|
+
theme: args.theme ?? "none",
|
|
32828
32873
|
product_image_identity: productImage ? preparedUploadIdentity(productImage) : undefined,
|
|
32829
32874
|
closing_image_identity: closingImage ? preparedUploadIdentity(closingImage) : undefined,
|
|
32830
32875
|
logo_identity: logo ? preparedUploadIdentity(logo) : undefined
|
|
@@ -32897,8 +32942,8 @@ registerTool("start_autopilot", {
|
|
|
32897
32942
|
inputSchema: {
|
|
32898
32943
|
slug: exports_external.string().describe("Editor project slug"),
|
|
32899
32944
|
language: exports_external.enum(LANGUAGES).optional().describe('Optional language override applied before launch. Bare ISO-639-1 code only, e.g. "fr" — no ' + "region/script subtag. Drives scenario, narration, voice, and captions."),
|
|
32900
|
-
product_subject: exports_external.string().max(300).optional().describe("Optional locked product / main subject applied before launch so the AI cannot invent an " + "unrelated product."),
|
|
32901
|
-
product_prompt: exports_external.string().optional().describe("Optional freeform brief to set/replace before launch."),
|
|
32945
|
+
product_subject: exports_external.string().max(300).optional().describe("Optional locked product / main subject applied before launch so the AI cannot invent an " + "unrelated product. Omit to keep the stored subject; send blank to clear it."),
|
|
32946
|
+
product_prompt: exports_external.string().optional().describe("Optional freeform brief to set/replace before launch. Omit to keep the stored brief; send blank to clear it."),
|
|
32902
32947
|
max_credits: exports_external.number().int().nonnegative().optional().describe("Explicit spend cap. Omit to quote only; no credits are charged."),
|
|
32903
32948
|
restart: exports_external.boolean().optional().describe("Set true only on a completed project to quote/build a fresh creative run. The delivered timeline stays available until replacement apply.")
|
|
32904
32949
|
},
|
|
@@ -32948,7 +32993,7 @@ registerTool("start_autopilot", {
|
|
|
32948
32993
|
}));
|
|
32949
32994
|
registerTool("retry_editor_batch", {
|
|
32950
32995
|
title: "Retry a paused editor batch",
|
|
32951
|
-
description: "Recovers an editor whose normalized status is batch_retry_required by atomically re-queueing the failed " + "scene and resuming its prepaid batch. This spends 0 additional credits: the batch generation was already " + "paid. It does not authorize or restart a failed parent Autopilot run; after retrying, wait for the batch " + "to settle. If the parent is then terminal, call start_autopilot without max_credits for a fresh quote and " + "relaunch only with an approved cap. A batch parked on " + "insufficient_credits needs a top-up/scope change instead. Scope: video:generate.",
|
|
32996
|
+
description: "Recovers an editor whose normalized status is batch_retry_required by atomically re-queueing the failed " + "scene and resuming its prepaid batch. This spends 0 additional credits: the batch generation was already " + "paid. Before retrying you may REPAIR the one failed scene: while paused_for_retry, set_segment_prompt can " + "edit that scene's prompt (and use_previous_frame) — but only for the exact failed position the server " + "names in batch_generation_failed_position (from get_editor); the rest of the paused batch stays " + "mutation-locked. Explicit paused retries are capped — once the failure code is free_retries_exhausted a " + "further retry is rejected (start a fresh run instead). It does not authorize or restart a failed parent " + "Autopilot run; after retrying, wait for the batch " + "to settle. If the parent is then terminal, call start_autopilot without max_credits for a fresh quote and " + "relaunch only with an approved cap. A batch parked on " + "insufficient_credits needs a top-up/scope change instead. Scope: video:generate.",
|
|
32952
32997
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
32953
32998
|
annotations: {
|
|
32954
32999
|
title: "Retry editor batch",
|
|
@@ -32956,7 +33001,10 @@ registerTool("retry_editor_batch", {
|
|
|
32956
33001
|
idempotentHint: false
|
|
32957
33002
|
}
|
|
32958
33003
|
}, tool(async (args, client) => {
|
|
32959
|
-
const
|
|
33004
|
+
const command = await observedEditorBatchCommand(client, args.slug, [
|
|
33005
|
+
"paused_for_retry"
|
|
33006
|
+
]);
|
|
33007
|
+
const res = await client.post(`/video-factories/${args.slug}/retry-batch-segment`, command);
|
|
32960
33008
|
return ok({
|
|
32961
33009
|
...asRecord(asRecord(res).data ?? res),
|
|
32962
33010
|
slug: args.slug,
|
|
@@ -32967,7 +33015,7 @@ registerTool("retry_editor_batch", {
|
|
|
32967
33015
|
}));
|
|
32968
33016
|
registerTool("cancel_editor_batch", {
|
|
32969
33017
|
title: "Cancel an editor batch",
|
|
32970
|
-
description: "Cancels an editor batch
|
|
33018
|
+
description: "Cancels an editor batch only while it is preparing or paused_for_retry so the project can be edited or " + "Autopilot can be started again. Preparing cancellation releases the uncharged preflight. Cancelling a paused " + "batch is destructive: dispatched scene work is stopped and its prepaid credits are not refunded. Spends " + "0 additional credits. Scope: video:generate.",
|
|
32971
33019
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
32972
33020
|
annotations: {
|
|
32973
33021
|
title: "Cancel editor batch",
|
|
@@ -32977,7 +33025,11 @@ registerTool("cancel_editor_batch", {
|
|
|
32977
33025
|
idempotentHint: false
|
|
32978
33026
|
}
|
|
32979
33027
|
}, tool(async (args, client) => {
|
|
32980
|
-
const
|
|
33028
|
+
const command = await observedEditorBatchCommand(client, args.slug, [
|
|
33029
|
+
"preparing",
|
|
33030
|
+
"paused_for_retry"
|
|
33031
|
+
]);
|
|
33032
|
+
const res = await client.post(`/video-factories/${args.slug}/cancel-batch`, command);
|
|
32981
33033
|
return ok({
|
|
32982
33034
|
...asRecord(asRecord(res).data ?? res),
|
|
32983
33035
|
slug: args.slug,
|
|
@@ -32985,6 +33037,22 @@ registerTool("cancel_editor_batch", {
|
|
|
32985
33037
|
next: "get_status"
|
|
32986
33038
|
});
|
|
32987
33039
|
}));
|
|
33040
|
+
async function observedEditorBatchCommand(client, slug, allowedStatuses) {
|
|
33041
|
+
const response = await client.get(`/editor/${slug}`);
|
|
33042
|
+
const state = asRecord(asRecord(response).data);
|
|
33043
|
+
const status = state.batch_generation_status;
|
|
33044
|
+
if (typeof status !== "string" || !allowedStatuses.includes(status)) {
|
|
33045
|
+
throw new Error(`Editor batch is not in an allowed phase (${allowedStatuses.join(" or ")}); refresh status before retrying.`);
|
|
33046
|
+
}
|
|
33047
|
+
const runId = state.batch_generation_run_id;
|
|
33048
|
+
if (runId !== null && (typeof runId !== "string" || runId.trim().length === 0)) {
|
|
33049
|
+
throw new Error("Editor state did not include a valid batch_generation_run_id; refresh status before retrying.");
|
|
33050
|
+
}
|
|
33051
|
+
return {
|
|
33052
|
+
expected_batch_run_id: runId,
|
|
33053
|
+
expected_batch_status: status
|
|
33054
|
+
};
|
|
33055
|
+
}
|
|
32988
33056
|
registerTool("get_ai_assists", {
|
|
32989
33057
|
title: "Get AI assist quota",
|
|
32990
33058
|
description: "Returns the free daily AI-assist quota {used, limit, bonus, remaining, resets_at, unlock_cost, " + "unlock_batch_size}. AI assists power helper calls (generate_scenario, generate_narration, " + "enhance_prompt, suggest_next_scene, suggest_music_prompt). Default limit is 20/day. When remaining " + "hits 0 those helpers return 429 — either unlock_ai_assists (1 credit → +10) or write the content " + "yourself with set_scenario / set_narration_script / set_segment_prompt (those are free, no quota).",
|
|
@@ -33010,8 +33078,7 @@ registerTool("create_editor_draft", {
|
|
|
33010
33078
|
language: exports_external.enum(LANGUAGES).optional().describe('Language code, e.g. "en" (default). Bare ISO-639-1 code only — no region/script subtag like ' + '"en-US". Drives scenario, narration, voice, and captions.'),
|
|
33011
33079
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Narrative arc / segment structure. Omit for Auto/generic."),
|
|
33012
33080
|
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."),
|
|
33013
|
-
theme: exports_external.
|
|
33014
|
-
cast_mode: exports_external.enum(["product_only", "presenter_led"]).optional().describe("Who/what anchors every AI scene. product_only = a product-led ad with NO people; requires a " + "product image (set_product) before generation and generates every scene from persistent product " + "reference images — the best product fidelity. presenter_led = one locked presenter appears in " + "every scene; requires a presenter to be configured first (in the app / REST presenter endpoints " + "— no MCP tool yet), else generation fails with presenter_required. Omit for the default auto " + "behavior."),
|
|
33081
|
+
theme: exports_external.enum(SCENARIO_THEMES).optional().describe('Optional visual theme / genre overlay (default "none", so scene prompts drive the look). One of: none, realistic, cinematic, anime, sci_fi, fantasy, noir, superhero, horror, mockumentary, sports, gaming, retro_80s, minimalist, cyberpunk.'),
|
|
33015
33082
|
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"),
|
|
33016
33083
|
export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")'),
|
|
33017
33084
|
project_intent: exports_external.enum(["social_ad", "creative_story"]).optional().describe("social_ad for promotional/ad content (product focus + CTA), creative_story for brand " + "narratives (default).")
|
|
@@ -33029,15 +33096,14 @@ registerTool("create_editor_draft", {
|
|
|
33029
33096
|
product_subject: args.product_subject,
|
|
33030
33097
|
creative_format: args.creative_format,
|
|
33031
33098
|
visual_language: args.visual_language,
|
|
33032
|
-
theme: args.theme,
|
|
33033
|
-
cast_mode: args.cast_mode,
|
|
33099
|
+
theme: args.theme ?? "none",
|
|
33034
33100
|
voice_id: args.voice_id,
|
|
33035
33101
|
export_aspect_ratio: args.export_aspect_ratio,
|
|
33036
33102
|
project_intent: args.project_intent
|
|
33037
33103
|
};
|
|
33038
33104
|
if (prompt)
|
|
33039
33105
|
body.product_prompt = prompt;
|
|
33040
|
-
const created = await client.post("/editor", body, idemKey("create-editor-draft", prompt ?? "", args.product_subject ?? "", args.project_intent ?? "", args.language ?? "en", args.creative_format ?? "", args.visual_language ?? "", args.theme ?? "", args.voice_id ?? "", args.export_aspect_ratio ?? ""
|
|
33106
|
+
const created = await client.post("/editor", body, idemKey("create-editor-draft", prompt ?? "", args.product_subject ?? "", args.project_intent ?? "", args.language ?? "en", args.creative_format ?? "", args.visual_language ?? "", args.theme ?? "none", args.voice_id ?? "", args.export_aspect_ratio ?? ""));
|
|
33041
33107
|
return ok({
|
|
33042
33108
|
slug: created.data.slug,
|
|
33043
33109
|
kind: "editor",
|
|
@@ -33060,6 +33126,9 @@ function conciseEditorState(value) {
|
|
|
33060
33126
|
"scenario_apply_status",
|
|
33061
33127
|
"scenario_prompt",
|
|
33062
33128
|
"batch_generation_status",
|
|
33129
|
+
"batch_generation_run_id",
|
|
33130
|
+
"batch_generation_failed_position",
|
|
33131
|
+
"auto_render_after_batch",
|
|
33063
33132
|
"narration_status",
|
|
33064
33133
|
"narration_script",
|
|
33065
33134
|
"narration_stale",
|
|
@@ -33083,9 +33152,8 @@ function conciseEditorState(value) {
|
|
|
33083
33152
|
prompt: segment.prompt,
|
|
33084
33153
|
status: segment.status,
|
|
33085
33154
|
source_type: segment.source_type,
|
|
33155
|
+
generation_claim_token: segment.generation_claim_token,
|
|
33086
33156
|
use_previous_frame: segment.use_previous_frame,
|
|
33087
|
-
protagonist_presence: segment.protagonist_presence,
|
|
33088
|
-
cast_mode_snapshot: segment.cast_mode_snapshot,
|
|
33089
33157
|
video_stale: segment.video_stale,
|
|
33090
33158
|
error_code: segment.error_code,
|
|
33091
33159
|
error_message: segment.error_message,
|
|
@@ -33095,7 +33163,7 @@ function conciseEditorState(value) {
|
|
|
33095
33163
|
}
|
|
33096
33164
|
registerTool("get_editor", {
|
|
33097
33165
|
title: "Get editor state (review step)",
|
|
33098
|
-
description: "Returns the full editor state by default for backward compatibility. Pass response_format:'concise' for a smaller review projection containing orchestration, scenario, scene prompts/status,
|
|
33166
|
+
description: "Returns the full editor state by default for backward compatibility. Pass response_format:'concise' for a smaller review projection containing orchestration, scenario, scene prompts/status, narration_status, current_audio, current_music, latest_render, batch_generation_status, spend, and quota.",
|
|
33099
33167
|
inputSchema: {
|
|
33100
33168
|
slug: exports_external.string().describe("Editor project slug"),
|
|
33101
33169
|
response_format: exports_external.enum(["concise", "full"]).default("full")
|
|
@@ -33124,9 +33192,9 @@ registerTool("generate_scenario", {
|
|
|
33124
33192
|
inputSchema: {
|
|
33125
33193
|
slug: exports_external.string().describe("Editor project slug"),
|
|
33126
33194
|
segments_count: exports_external.number().int().min(3).max(10).optional().describe("How many scenes (3–10, server default 5)"),
|
|
33127
|
-
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."),
|
|
33128
|
-
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."),
|
|
33129
|
-
theme: exports_external.
|
|
33195
|
+
creative_format: exports_external.union([exports_external.enum(CREATIVE_FORMATS), exports_external.literal("")]).optional().describe("Persist the narrative arc / segment structure before generating. Omit to leave it unchanged; pass an empty string to clear."),
|
|
33196
|
+
visual_language: exports_external.union([exports_external.enum(VISUAL_LANGUAGES), exports_external.literal("")]).optional().describe("Persist the visual style / render look before generating. Omit to leave it unchanged; pass an empty string to clear. When set it drives the look and a theme of 'none' is ignored."),
|
|
33197
|
+
theme: exports_external.enum(SCENARIO_THEMES).optional().describe("Persist the genre overlay / theme before generating (e.g. realistic, cinematic, anime, none)."),
|
|
33130
33198
|
auto_unlock: exports_external.boolean().optional().describe("On 429, spend 1 credit to unlock +10 assists and retry once (default false)")
|
|
33131
33199
|
},
|
|
33132
33200
|
annotations: {
|
|
@@ -33281,6 +33349,50 @@ registerTool("generate_segment", {
|
|
|
33281
33349
|
const res = await client.post(`/editor/${args.slug}/segments/${String(args.segment_id)}/generate`, undefined, undefined);
|
|
33282
33350
|
return ok(asRecord(res).data ?? res);
|
|
33283
33351
|
}));
|
|
33352
|
+
registerTool("cancel_segment_generation", {
|
|
33353
|
+
title: "Cancel one editor scene generation",
|
|
33354
|
+
description: "Stops one caller-observed, individually processing Editor scene; its generation_claim_token fence " + "prevents a delayed command from cancelling a newer attempt. The token is returned by get_editor. " + "Refunds that scene's charge idempotently (a free retry creates no refund). Use cancel_editor_batch or " + "cancel_autopilot for workflow-owned scenes. After cancellation, edit the prompt if needed and call " + "generate_segment again. Scope: video:generate.",
|
|
33355
|
+
inputSchema: {
|
|
33356
|
+
slug: exports_external.string().describe("Editor project slug"),
|
|
33357
|
+
segment_id: exports_external.union([exports_external.number(), exports_external.string()]).describe("Processing segment id (from get_editor)"),
|
|
33358
|
+
generation_claim_token: exports_external.string().nullable().describe("generation_claim_token from that processing segment")
|
|
33359
|
+
},
|
|
33360
|
+
annotations: {
|
|
33361
|
+
title: "Cancel segment generation",
|
|
33362
|
+
readOnlyHint: false,
|
|
33363
|
+
destructiveHint: true,
|
|
33364
|
+
openWorldHint: true,
|
|
33365
|
+
idempotentHint: true
|
|
33366
|
+
}
|
|
33367
|
+
}, tool(async (args, client) => {
|
|
33368
|
+
const res = await client.post(`/editor/${args.slug}/segments/${String(args.segment_id)}/cancel`, { expected_generation_claim_token: args.generation_claim_token }, undefined);
|
|
33369
|
+
return ok({
|
|
33370
|
+
...asRecord(asRecord(res).data ?? res),
|
|
33371
|
+
slug: args.slug,
|
|
33372
|
+
charged: false,
|
|
33373
|
+
next: "set_segment_prompt or generate_segment"
|
|
33374
|
+
});
|
|
33375
|
+
}));
|
|
33376
|
+
registerTool("cancel_autopilot", {
|
|
33377
|
+
title: "Cancel a running Autopilot run",
|
|
33378
|
+
description: "Stops an in-flight Autopilot run on an editor project: sets autopilot_status to cancelled so the " + "orchestration chain (scenario → scenes → narration → voice → music → render) advances no further. Use " + "this when Autopilot is still running and you want to halt it. Any scenes already charged stay charged and " + "all completed work (generated scenes, voice, music, a finished render) is kept — a step already enqueued " + "may still finish, but nothing new is started. Idempotent: a no-op that returns the project unchanged when " + "Autopilot is not running (already completed, failed, cancelled, or never launched), so it is safe to " + "repeat. It does NOT itself refund or re-run anything. For a non-Autopilot prepaid batch " + "(batch_generation_status preparing / paused_for_retry) use cancel_editor_batch; to stop one " + "individually-generated scene use cancel_segment_generation. Scope: video:generate.",
|
|
33379
|
+
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
33380
|
+
annotations: {
|
|
33381
|
+
title: "Cancel autopilot",
|
|
33382
|
+
readOnlyHint: false,
|
|
33383
|
+
destructiveHint: true,
|
|
33384
|
+
openWorldHint: true,
|
|
33385
|
+
idempotentHint: true
|
|
33386
|
+
}
|
|
33387
|
+
}, tool(async (args, client) => {
|
|
33388
|
+
const res = await client.post(`/editor/${args.slug}/autopilot/cancel`, undefined, undefined);
|
|
33389
|
+
return ok({
|
|
33390
|
+
...asRecord(asRecord(res).data ?? res),
|
|
33391
|
+
slug: args.slug,
|
|
33392
|
+
charged: false,
|
|
33393
|
+
next: "get_status"
|
|
33394
|
+
});
|
|
33395
|
+
}));
|
|
33284
33396
|
registerTool("regenerate_segment", {
|
|
33285
33397
|
title: "Regenerate a completed segment (new version, 4 credits)",
|
|
33286
33398
|
description: "Re-rolls a COMPLETED scene as a NEW VERSION of that segment — the previous version is kept (switch back " + "in the app). Costs 4 credits. Use generate_segment for pending/failed scenes; use THIS for a completed " + "scene you want different — optionally with a new prompt (omit it to re-roll the current prompt). The new " + "version starts rendering immediately; watch it with get_editor (status processing → completed|failed). " + "KNOWN LIMITATION: scenes chain visual continuity off the previous scene's last frame, so regenerating a " + "MID-TIMELINE scene can degrade the transition into the FOLLOWING scene (it was seeded from the old " + "version's last frame). A scene still processing returns editor_segment_busy (no double charge); 402 " + "credits_insufficient fails cleanly.",
|
|
@@ -33303,7 +33415,7 @@ registerTool("regenerate_segment", {
|
|
|
33303
33415
|
}));
|
|
33304
33416
|
registerTool("generate_all_segments", {
|
|
33305
33417
|
title: "Generate all pending segments (sequential)",
|
|
33306
|
-
description: "Generates every not-yet-completed scene (pending or previously failed) in position order, waiting for each to finish before starting" + "the next (preserves visual continuity). Each AI-generated scene is a fixed 8 seconds, so N scenes ≈ N×8s of footage. Costs 5 credits per segment generated. Stops and reports what " + "completed if a scene errors on submit (e.g. 402 credits_insufficient) or fails while rendering; if a " + "scene is still rendering past the budget it returns timed_out so you can re-run to continue (completed " + "scenes are skipped). The whole call is capped at ~280s: on a project with many scenes it processes as " + "many as fit, returns timed_out, and you re-run to continue (it picks up the remaining pending scenes). " + "Tip: to generate everything at lowest cost, skip this and just call render — it auto-generates any ungenerated scenes at the batch rate (4 credits/scene for ≥3); use this tool when you want to review or stop per scene. May take several minutes.",
|
|
33418
|
+
description: "Generates every not-yet-completed scene (pending or previously failed) in position order, waiting for each to finish before starting " + "the next (preserves visual continuity). Each AI-generated scene is a fixed 8 seconds, so N scenes ≈ N×8s of footage. Costs 5 credits per segment generated. Stops and reports what " + "completed if a scene errors on submit (e.g. 402 credits_insufficient) or fails while rendering; if a " + "scene is still rendering past the budget it returns timed_out so you can re-run to continue (completed " + "scenes are skipped). The whole call is capped at ~280s: on a project with many scenes it processes as " + "many as fit, returns timed_out, and you re-run to continue (it picks up the remaining pending scenes). " + "Tip: to generate everything at lowest cost, skip this and just call render — it auto-generates any ungenerated scenes at the batch rate (4 credits/scene for ≥3); use this tool when you want to review or stop per scene. May take several minutes.",
|
|
33307
33419
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
33308
33420
|
annotations: {
|
|
33309
33421
|
title: "Generate all segments",
|
|
@@ -33429,10 +33541,10 @@ registerTool("generate_all_segments", {
|
|
|
33429
33541
|
}));
|
|
33430
33542
|
registerTool("set_narration_script", {
|
|
33431
33543
|
title: "Set the narration script",
|
|
33432
|
-
description: "Writes your own narration script (1–
|
|
33544
|
+
description: "Writes your own narration script (1–10000 chars) instead of generating one. Free — no credits, no AI " + "assist. Use this in place of generate_narration when you want to control the voiceover text. Changing the narration after generating voice/music marks them stale — regenerate (generate_voice / generate_music) before render.",
|
|
33433
33545
|
inputSchema: {
|
|
33434
33546
|
slug: exports_external.string().describe("Editor project slug"),
|
|
33435
|
-
script: exports_external.string().min(1).max(
|
|
33547
|
+
script: exports_external.string().min(1).max(1e4).describe("Narration / voiceover text (1–10000 chars)")
|
|
33436
33548
|
},
|
|
33437
33549
|
annotations: { title: "Set narration", ...WRITE, idempotentHint: true }
|
|
33438
33550
|
}, tool(async (args, client) => {
|
|
@@ -33443,7 +33555,7 @@ registerTool("set_narration_script", {
|
|
|
33443
33555
|
}));
|
|
33444
33556
|
registerTool("generate_narration", {
|
|
33445
33557
|
title: "Generate the narration (AI assist)",
|
|
33446
|
-
description: "Generates
|
|
33558
|
+
description: "Generates one coherent narration script with AI from the complete timeline. Every current scene must be completed with a known positive duration, and at least one scene must not keep original upload audio. CONSUMES 1 AI ASSIST (free daily quota) only when a fresh job is queued; an exact retry is deduplicated without consuming another assist — " + "it is free of CREDITS but NOT free of quota. On 429 set auto_unlock:true (1 credit → +10 assists, " + "retried once) or write the script yourself with set_narration_script.",
|
|
33447
33559
|
inputSchema: {
|
|
33448
33560
|
slug: exports_external.string().describe("Editor project slug"),
|
|
33449
33561
|
auto_unlock: exports_external.boolean().optional().describe("On 429, spend 1 credit to unlock +10 assists and retry once (default false)")
|
|
@@ -33459,7 +33571,7 @@ registerTool("generate_narration", {
|
|
|
33459
33571
|
}));
|
|
33460
33572
|
registerTool("generate_voice", {
|
|
33461
33573
|
title: "Generate the voiceover (3 credits)",
|
|
33462
|
-
description: "Synthesizes the voiceover from the narration script. Costs 3 credits. Safe to retry; re-call after changing the narration or voice to regenerate." + "voice_id is REQUIRED — pick one from list_voices (≤64 chars, [A-Za-z0-9_-] only). 402 fails cleanly. Editing the scenario/narration/timeline after this marks the voice stale, and render will 422 (editor_voice_stale) until you re-run generate_voice.",
|
|
33574
|
+
description: "Synthesizes the voiceover from the narration script. Costs 3 credits. Safe to retry; re-call after changing the narration or voice to regenerate." + " All current scenes must be completed with known durations first. voice_id is REQUIRED — pick one from list_voices (≤64 chars, [A-Za-z0-9_-] only). 402 fails cleanly. Editing the scenario/narration/timeline after this marks the voice stale, and render will 422 (editor_voice_stale) until you re-run generate_voice.",
|
|
33463
33575
|
inputSchema: {
|
|
33464
33576
|
slug: exports_external.string().describe("Editor project slug"),
|
|
33465
33577
|
voice_id: exports_external.string().regex(/^[A-Za-z0-9_-]+$/).max(64).describe("Narration voice id from list_voices (required)")
|
|
@@ -33504,7 +33616,7 @@ registerTool("generate_music", {
|
|
|
33504
33616
|
}));
|
|
33505
33617
|
registerTool("render", {
|
|
33506
33618
|
title: "Render the final editor video (0 credits)",
|
|
33507
|
-
description: "Renders the final MP4 from the generated segments + voice + music. Costs 0 credits
|
|
33619
|
+
description: "Renders the final MP4 from the generated segments + voice + music. Costs 0 credits. Pending or previously " + "failed AI scenes are generated first and charged as a new batch. Render NEVER generates or charges for voice/music; a new " + "music charge comes only from generate_music or an already-running autopilot child job. Re-rendering reflects the current segments/voice/music (it does not replay a prior render). Returns the latest_render; " + 'pair with wait_for_completion({ slug, kind: "editor" }) then download_result. ' + "STALENESS: editing the scenario or narration AFTER generating voice/music marks those audio tracks stale, and render then 422s with editor_narration_stale / editor_voice_stale / editor_music_stale — regenerate the named track (generate_narration → generate_voice, and/or generate_music) before rendering.",
|
|
33508
33620
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
33509
33621
|
annotations: { title: "Render", ...WRITE, idempotentHint: false }
|
|
33510
33622
|
}, tool(async (args, client) => {
|
|
@@ -33571,7 +33683,7 @@ registerTool("suggest_music_prompt", {
|
|
|
33571
33683
|
}));
|
|
33572
33684
|
registerTool("list_renders", {
|
|
33573
33685
|
title: "List renders for an editor project",
|
|
33574
|
-
description: "Lists every render version for an editor project: {id, version, status, video_url,
|
|
33686
|
+
description: "Lists every render version for an editor project: {id, version, status, video_url, thumbnail_url, " + "duration_seconds, error_message, retryable, combination_hash, is_stale, inserted_at}. Use it to find a " + "FAILED render to retry, or to recover a finished video_url (presigned, ~24h). status is pending | " + "processing | completed | failed; is_stale null means unknown, not current. Read-only.",
|
|
33575
33687
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
33576
33688
|
annotations: { title: "List renders", ...RO }
|
|
33577
33689
|
}, tool(async (args, client) => {
|
|
@@ -33744,7 +33856,7 @@ registerTool("use_asset_for_segment", {
|
|
|
33744
33856
|
}));
|
|
33745
33857
|
registerTool("set_product", {
|
|
33746
33858
|
title: "Attach a product image (from a local file)",
|
|
33747
|
-
description: "Uploads a local product image and attaches it to the project as the product. Accepted: " + IMAGE_EXTS.map((e) => `.${e}`).join(", ") + " (≤8 MiB), local path confined to HUBFLUENCER_INPUT_DIR (or cwd). 0 credits.
|
|
33859
|
+
description: "Uploads a local product image and attaches it to the project as the product. Accepted: " + IMAGE_EXTS.map((e) => `.${e}`).join(", ") + " (≤8 MiB), local path confined to HUBFLUENCER_INPUT_DIR (or cwd). 0 credits. Its pixels guide product identity " + "in every generated Editor scene and its confirmed description grounds scenario and narration. The exact photo " + "is linked as the closing image unless a separate closing image was authored; generated fine label text can still vary. " + "WARNING: replacing the product photo on a project that already has generated AI scenes invalidates their product " + "identity — those scenes were built from the old photo and must be regenerated to match the new one.",
|
|
33748
33860
|
inputSchema: {
|
|
33749
33861
|
slug: exports_external.string().describe("Editor project slug"),
|
|
33750
33862
|
file_path: exports_external.string().describe("Local product image path (.jpg/.jpeg/.png)"),
|
|
@@ -33756,22 +33868,6 @@ registerTool("set_product", {
|
|
|
33756
33868
|
const res = await client.post(`/editor/${args.slug}/product/confirm`, { s3_key, product_description: args.description });
|
|
33757
33869
|
return ok(asRecord(res).data ?? res);
|
|
33758
33870
|
}));
|
|
33759
|
-
registerTool("set_product_placement", {
|
|
33760
|
-
title: "Set default product placement on pending scenes (0 credits)",
|
|
33761
|
-
description: "Sets where the product appears across pending (never-generated) AI scenes. mode is 'throughout' (every scene) " + "or 'end' (closing scene only; requires a first+last-frame capable model). 0 credits. Add a product first with " + "set_product. (There is no 'none' — to remove the product, that's a separate action.)",
|
|
33762
|
-
inputSchema: {
|
|
33763
|
-
slug: exports_external.string().describe("Editor project slug"),
|
|
33764
|
-
mode: exports_external.enum(["throughout", "end"]).describe("'throughout' or 'end'")
|
|
33765
|
-
},
|
|
33766
|
-
annotations: {
|
|
33767
|
-
title: "Set product placement",
|
|
33768
|
-
...WRITE,
|
|
33769
|
-
idempotentHint: true
|
|
33770
|
-
}
|
|
33771
|
-
}, tool(async (args, client) => {
|
|
33772
|
-
const res = await client.post(`/editor/${args.slug}/product/apply-default-placement`, { placement: args.mode });
|
|
33773
|
-
return ok(asRecord(res).data ?? res);
|
|
33774
|
-
}));
|
|
33775
33871
|
registerTool("set_closing_image", {
|
|
33776
33872
|
title: "Set the closing-card image (0 credits)",
|
|
33777
33873
|
description: "Sets the optional ~2s end-card image. Either upload a local file (file_path; .jpg/.jpeg/.png ≤20 MiB, confined " + "to HUBFLUENCER_INPUT_DIR/cwd) OR reuse the project's product image (from_product:true — a 0-credit server-side " + "copy, no upload). If a closing image already exists, pass overwrite:true to replace it (else " + "editor_closing_image_exists).",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubfluencer/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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>",
|