@hubfluencer/mcp 0.12.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 +15 -6
- package/dist/index.js +109 -16
- package/package.json +1 -1
- package/src/core.ts +4 -0
- package/src/index.ts +190 -20
- package/src/output-schemas.ts +8 -0
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ claude mcp add hubfluencer --env HUBFLUENCER_API_TOKEN=YOUR_TOKEN -- npx -y @hub
|
|
|
68
68
|
|
|
69
69
|
## Tools
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
97 tools, grouped by workflow. Every tool prices its action in its own `description` — costs
|
|
72
72
|
below are the ground truth. **"$0" means it spends no video credits**; planning/AI-helper tools
|
|
73
73
|
instead draw a **free daily quota of 20 AI assists** (top up with `unlock_ai_assists`: 1 credit
|
|
74
74
|
→ +10). Reads need the `video:read` scope; writes need `video:generate`. Nothing here needs
|
|
@@ -82,12 +82,14 @@ instead draw a **free daily quota of 20 AI assists** (top up with `unlock_ai_ass
|
|
|
82
82
|
| `create_short` | Create a short draft (12s vertical: two 6s AI segments + title overlay + music; 14s when it ends on a poster or brand-lockup end card). Set `headline` (≈4 words, ≤40 chars), `subheadline`, `music_vibe`, and an explicit `visual_language` (`kinetic_creator` is a strong default) — and **always `cta_text`** (a short without a CTA doesn't convert; blank `cta_text` → the server renders a neutral localized ask, e.g. "Learn more" — a truly CTA-less short is not currently supported). End-card fields: `closing_claim` (≤80), `brand_name` (≤40), `end_card` (`"auto"`\|`"none"`). Other opt-in conversion graphics: `offer_text`/`badge_text`/`star_rating` — only what you can substantiate. | **0** |
|
|
83
83
|
| `update_short` | Patch an existing short draft — brief (`product_prompt`), copy, styling, end-card fields (`closing_claim`/`brand_name`/`poster_includes_lockup`/`end_card`), logo overlay placement (`short_logo_position`/`short_logo_treatment`), or the opt-in conversion graphics; only the fields you pass change. Clear one with `""` (`[]` for `text_beats`, `0` for `star_rating`). Apply text/style/CTA/end-card/logo edits to a generated short free with `rerender_short`; footage/music edits need `generate_short`. | **0** |
|
|
84
84
|
| `generate_short` | Render an existing short — re-rolls the footage + music (fresh AI generation). If headline/subheadline/beats are all blank the server auto-writes the copy first (`skip_auto_text:true` for a deliberately bare clip). Safe to re-call (in-flight = reported, no double charge; a failed short can re-generate). | **15** |
|
|
85
|
+
| `cancel_short_generation` | Stop the caller-observed paid short attempt, fence late workers from publishing, preserve the draft, and restore its 15 credits idempotently. Requires `generation_attempt` from `get_status`; stale commands cannot cancel a newer run. | **full refund** |
|
|
85
86
|
| `rerender_short` | **FREE re-render** — applies the short's current text/style/CTA/end-card/logo state over the already-paid footage + music. Use after `update_short` for copy/style/CTA tweaks, or after a logo change (`set_short_logo`, or `short_logo_position`/`short_logo_treatment`); footage or music changes need `generate_short` instead. Needs one completed generation first (422 `short_not_ready` otherwise); changed footage/music inputs return 422 `short_paid_regeneration_required`. A failed free re-render leaves the delivered video intact — retry `rerender_short`, it stays free. | **0** |
|
|
86
87
|
| `generate_short_text` | AI-draft editable headline / subheadline / caption beats for a short. | 1 assist |
|
|
87
88
|
| `create_editor_ad` | Create an editor project, attach optional local product/logo/closing assets, and return the live autopilot quote. Omit `max_credits` to stop free; provide an approved cap to launch. Write every required person, product, object, and continuity decision directly in the prompt. | **0** until capped launch |
|
|
88
89
|
| `start_autopilot` | Quote autopilot on an **existing** editor draft when called with only `slug`; launch only with an explicit `max_credits` that covers the estimate and an adequate balance. On a completed project, pass `restart:true` to quote and build a fresh creative run; omit it to resume pending edits. | **0** quote; credits on capped launch |
|
|
89
90
|
| `retry_editor_batch` | Recover `batch_retry_required` by atomically re-queueing the failed scene and resuming its prepaid batch. If the parent Autopilot run is terminal, quote/relaunch it separately afterward. | **0 additional** |
|
|
90
91
|
| `cancel_editor_batch` | Cancel an editor batch only while it is `preparing` or `paused_for_retry` (awaiting-previews/running batches are worker-owned and not cancellable). Preparing cancellation releases the uncharged preflight; paused cancellation is destructive — prepaid credits are not refunded. | **0 additional** |
|
|
92
|
+
| `cancel_autopilot` | Stop a **running** Autopilot run: flips `autopilot_status` to `cancelled` so the chain (scenario → scenes → narration → voice → music → render) advances no further. Charged scenes stay charged and all completed work is kept; a step already enqueued may still finish. Idempotent — a no-op when Autopilot isn't running. (Use `cancel_editor_batch` for a non-Autopilot prepaid batch, `cancel_segment_generation` for one scene.) | **0 additional** |
|
|
91
93
|
| `create_slider` | Create an image-carousel draft: one prompt → N still slides + a ready-to-post caption + hashtags. `mode` `creative` (story) or `ad_driven` (facts). | **0** |
|
|
92
94
|
| `generate_slider` | Render a carousel (copy → AI backgrounds → composite). Poll `get_slider` until `completed`. | **1 / slide** (3–10) |
|
|
93
95
|
| `get_slider` | Read a carousel's status and, when completed, the per-slide image URLs + caption + hashtags. | **0** |
|
|
@@ -106,12 +108,13 @@ instead draw a **free daily quota of 20 AI assists** (top up with `unlock_ai_ass
|
|
|
106
108
|
| `set_scene_count` | Grow/shrink to N scenes (1–20); deletes only trailing un-generated scenes. Each AI scene is a fixed 8s. | **0** |
|
|
107
109
|
| `set_segment_prompt` | Write one scene's prompt (1–2000 chars), identified by segment id. | **0** |
|
|
108
110
|
| `generate_segment` | Render one **pending/failed** scene (re-call to retry a failed one). | **5** |
|
|
111
|
+
| `cancel_segment_generation` | Stop the caller-observed individually generated scene, fence its provider job from publishing, and restore any charged credits idempotently. Requires that scene's `generation_claim_token` from `get_editor`; stale commands cannot cancel a newer attempt. Batch/Autopilot scenes use their workflow-level controls. | **full refund** |
|
|
109
112
|
| `regenerate_segment` | Re-roll a **completed** scene as a new version (optional new `prompt`). Mid-timeline: the next scene's transition can degrade because it was seeded from the old version's last frame. | **4** |
|
|
110
113
|
| `generate_all_segments` | Render every not-yet-completed scene in order (continuity preserved); capped ~280s → re-run to continue. | **5 / segment** |
|
|
111
114
|
| `set_narration_script` / `generate_narration` | Write your own voiceover script (free) **or** AI-draft one coherent script after every scene has completed with a known positive duration. AI drafting requires at least one scene without retained original audio and consumes an assist only when a fresh job is queued (exact retries dedupe for free). | free / 1 assist |
|
|
112
115
|
| `generate_voice` | Synthesize the voiceover. `voice_id` required (see `list_voices`). | **3** |
|
|
113
116
|
| `generate_music` | Generate background music (each call is a fresh take). | **5** |
|
|
114
|
-
| `render` | Final MP4. Auto-charges any
|
|
117
|
+
| `render` | Final MP4. Auto-charges and generates any pending or failed scenes first (4/scene in a batch of ≥3). | **0** |
|
|
115
118
|
| `list_renders` | List 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`) — find a failed render or recover a finished URL. | **0** |
|
|
116
119
|
| `retry_render` | Re-run a **failed** render from its exact saved snapshot (recovery without re-driving the pipeline). | **0** |
|
|
117
120
|
| `enhance_prompt` / `suggest_next_scene` / `suggest_music_prompt` | AI helpers along the way. | 1 assist each |
|
|
@@ -159,9 +162,15 @@ completed | failed`). Batch state is `idle | preparing | awaiting_previews | run
|
|
|
159
162
|
paused_for_retry | paused_insufficient_credits | completed | cancelled | failed`.
|
|
160
163
|
Only `paused_for_retry` is resumed by `retry_editor_batch`; `cancel_editor_batch` works
|
|
161
164
|
only while the batch is `preparing` or `paused_for_retry` — awaiting-previews/running
|
|
162
|
-
batches are worker-owned and cannot be cancelled.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
batches are worker-owned and cannot be cancelled. While `paused_for_retry` you may first
|
|
166
|
+
REPAIR the one failed scene before retrying: `set_segment_prompt` edits its prompt (and
|
|
167
|
+
`use_previous_frame`), but only for the exact failed position the server names in
|
|
168
|
+
`batch_generation_failed_position` — the rest of the paused batch stays mutation-locked, and
|
|
169
|
+
explicit paused retries are capped (`free_retries_exhausted`). Preparing cancellation releases
|
|
170
|
+
the uncharged preflight (nothing was charged yet); paused cancellation is destructive and
|
|
171
|
+
non-refundable. To stop a **running** parent Autopilot run (not a paused batch), use
|
|
172
|
+
`cancel_autopilot` — it flips `autopilot_status` to `cancelled`, keeps completed work and
|
|
173
|
+
charged scenes, and is a no-op once the run has finished. Both tools re-read the editor state and echo `expected_batch_run_id` +
|
|
165
174
|
`expected_batch_status` with the REST command, so a phase change between poll and command
|
|
166
175
|
is a safe 409 `batch_command_stale` (re-poll and retry) instead of acting on newer work;
|
|
167
176
|
the REST endpoints reject editor commands that omit these fields with 409
|
|
@@ -281,7 +290,7 @@ If it returns `terminal:false`, the render is still going — call `wait_for_com
|
|
|
281
290
|
`create_short` → optional `generate_hook_variations` (test hook angles free) / `generate_short_text` → `generate_short` → `wait_for_completion {kind:"short"}` → `download_result`, or
|
|
282
291
|
`create_editor_ad` (free configure + quote) → obtain spend approval → `start_autopilot {slug, max_credits}` → `wait_for_completion {kind:"editor"}` → `download_result`.
|
|
283
292
|
|
|
284
|
-
If waiting returns `stage:"batch_retry_required"`, call `retry_editor_batch` to continue the prepaid failed scene, or `cancel_editor_batch` to abandon the batch. Resolve that child batch before calling `start_autopilot` again; a retry does not itself authorize a new parent Autopilot run.
|
|
293
|
+
If waiting returns `stage:"batch_retry_required"`, call `retry_editor_batch` to continue the prepaid failed scene (optionally repairing its prompt first with `set_segment_prompt` — see the batch-state notes above), or `cancel_editor_batch` to abandon the batch. Resolve that child batch before calling `start_autopilot` again; a retry does not itself authorize a new parent Autopilot run. To halt a **running** Autopilot run outright, call `cancel_autopilot`.
|
|
285
294
|
|
|
286
295
|
**Iterate a short for free (never re-roll):** the first `generate_short` (15 credits) buys the footage +
|
|
287
296
|
music; after that, every text/style/CTA/end-card/logo tweak re-renders over them for **0 credits** —
|
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
|
}
|
|
@@ -30257,7 +30259,7 @@ function readStoredCredentials() {
|
|
|
30257
30259
|
// package.json
|
|
30258
30260
|
var package_default = {
|
|
30259
30261
|
name: "@hubfluencer/mcp",
|
|
30260
|
-
version: "0.
|
|
30262
|
+
version: "0.13.0",
|
|
30261
30263
|
description: "Model Context Protocol server for Hubfluencer — let AI agents generate post-ready shorts and editor ads.",
|
|
30262
30264
|
license: "MIT",
|
|
30263
30265
|
author: "Monocursive <contact@monocursive.com>",
|
|
@@ -30575,6 +30577,7 @@ var getStatusOutput = exports_external.object({
|
|
|
30575
30577
|
failure_details: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Short only: structured provider/QC details for recovery."),
|
|
30576
30578
|
failure_source: exports_external.string().optional().describe("Short only: worker/recovery layer that finalized the failure."),
|
|
30577
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."),
|
|
30578
30581
|
latest_render_free: exports_external.boolean().optional(),
|
|
30579
30582
|
last_free_rerender_failed: exports_external.boolean().optional()
|
|
30580
30583
|
}).passthrough();
|
|
@@ -30594,6 +30597,7 @@ var waitForCompletionOutput = exports_external.object({
|
|
|
30594
30597
|
failure_details: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Short only: structured provider/QC details for recovery."),
|
|
30595
30598
|
failure_source: exports_external.string().optional().describe("Short only: worker/recovery layer that finalized the failure."),
|
|
30596
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."),
|
|
30597
30601
|
latest_render_free: exports_external.boolean().optional(),
|
|
30598
30602
|
last_free_rerender_failed: exports_external.boolean().optional(),
|
|
30599
30603
|
saved_to: exports_external.union([exports_external.string(), exports_external.null()]).optional(),
|
|
@@ -31798,6 +31802,23 @@ var VISUAL_LANGUAGES = [
|
|
|
31798
31802
|
"startup_explainer",
|
|
31799
31803
|
"luxury_minimal"
|
|
31800
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
|
+
];
|
|
31801
31822
|
var LANGUAGES = [
|
|
31802
31823
|
"en",
|
|
31803
31824
|
"fr",
|
|
@@ -31924,7 +31945,7 @@ registerTool("make_video", {
|
|
|
31924
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"]'),
|
|
31925
31946
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("SHORTS + EDITOR: narrative arc / segment structure. Omit for Auto/generic."),
|
|
31926
31947
|
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("SHORTS + EDITOR: visual style direction and render look. Default app choice is kinetic_creator."),
|
|
31927
|
-
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"),
|
|
31928
31949
|
save_path: exports_external.string().optional().describe("Optional .mp4 path to download to (confined to HUBFLUENCER_OUTPUT_DIR or cwd)"),
|
|
31929
31950
|
max_wait_seconds: exports_external.number().optional().describe("Block budget seconds (default 240, capped 10–280)"),
|
|
31930
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."),
|
|
@@ -32003,8 +32024,8 @@ registerTool("make_video", {
|
|
|
32003
32024
|
voice_id: args.voice_id,
|
|
32004
32025
|
creative_format: args.creative_format,
|
|
32005
32026
|
visual_language: args.visual_language,
|
|
32006
|
-
theme: args.theme
|
|
32007
|
-
}, makeVideoEditorCreateKey(args));
|
|
32027
|
+
theme: args.theme ?? "none"
|
|
32028
|
+
}, makeVideoEditorCreateKey({ ...args, theme: args.theme ?? "none" }));
|
|
32008
32029
|
slug = created.data.slug;
|
|
32009
32030
|
}
|
|
32010
32031
|
const costPath = kind === "short" ? `/shorts/${slug}/cost` : `/editor/${slug}/autopilot/cost`;
|
|
@@ -32155,7 +32176,7 @@ registerTool("create_short", {
|
|
|
32155
32176
|
subheadline: exports_external.string().max(200).optional().describe("The SECONDARY title / supporting line under the headline. ≤200 chars."),
|
|
32156
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."),
|
|
32157
32178
|
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("Visual language for Veo direction and render styling. Good default: kinetic_creator."),
|
|
32158
|
-
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."),
|
|
32159
32180
|
music_vibe: exports_external.string().optional().describe("Background-music mood. Recognized: Upbeat (default), Cinematic, Minimal, Luxury, Playful, Jazz."),
|
|
32160
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"]'),
|
|
32161
32182
|
text_position: exports_external.enum(["top", "center", "bottom"]).optional().describe("Where the title overlay sits (default bottom)"),
|
|
@@ -32253,7 +32274,7 @@ registerTool("update_short", {
|
|
|
32253
32274
|
subheadline: exports_external.string().max(200).optional().describe("Secondary title / supporting line. ≤200 chars."),
|
|
32254
32275
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Structure: problem_solution, mistake_fix, myth_vs_reality, before_after, proof_demo, product_reveal."),
|
|
32255
32276
|
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("Visual language for Veo direction and render styling."),
|
|
32256
|
-
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."),
|
|
32257
32278
|
music_vibe: exports_external.string().optional().describe("Background-music mood: Upbeat, Cinematic, Minimal, Luxury, Playful, Jazz."),
|
|
32258
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"]'),
|
|
32259
32280
|
text_position: exports_external.enum(["top", "center", "bottom"]).optional().describe("Where the title overlay sits."),
|
|
@@ -32516,6 +32537,31 @@ registerTool("generate_short", {
|
|
|
32516
32537
|
throw e;
|
|
32517
32538
|
}
|
|
32518
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
|
+
}));
|
|
32519
32565
|
registerTool("rerender_short", {
|
|
32520
32566
|
title: "Re-render a short (FREE — 0 credits)",
|
|
32521
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).",
|
|
@@ -32767,7 +32813,7 @@ registerTool("create_editor_ad", {
|
|
|
32767
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."),
|
|
32768
32814
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Narrative arc / segment structure. Omit for Auto/generic."),
|
|
32769
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."),
|
|
32770
|
-
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.'),
|
|
32771
32817
|
voice_id: exports_external.string().optional().describe("Preferred narration voice id (see list_voices); omit for the default voice"),
|
|
32772
32818
|
export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")'),
|
|
32773
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."),
|
|
@@ -32818,11 +32864,12 @@ registerTool("create_editor_ad", {
|
|
|
32818
32864
|
project_intent: args.project_intent,
|
|
32819
32865
|
creative_format: args.creative_format,
|
|
32820
32866
|
visual_language: args.visual_language,
|
|
32821
|
-
theme: args.theme,
|
|
32867
|
+
theme: args.theme ?? "none",
|
|
32822
32868
|
voice_id: args.voice_id,
|
|
32823
32869
|
export_aspect_ratio: args.export_aspect_ratio
|
|
32824
32870
|
}, editorAdCreateKey({
|
|
32825
32871
|
...args,
|
|
32872
|
+
theme: args.theme ?? "none",
|
|
32826
32873
|
product_image_identity: productImage ? preparedUploadIdentity(productImage) : undefined,
|
|
32827
32874
|
closing_image_identity: closingImage ? preparedUploadIdentity(closingImage) : undefined,
|
|
32828
32875
|
logo_identity: logo ? preparedUploadIdentity(logo) : undefined
|
|
@@ -32946,7 +32993,7 @@ registerTool("start_autopilot", {
|
|
|
32946
32993
|
}));
|
|
32947
32994
|
registerTool("retry_editor_batch", {
|
|
32948
32995
|
title: "Retry a paused editor batch",
|
|
32949
|
-
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.",
|
|
32950
32997
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
32951
32998
|
annotations: {
|
|
32952
32999
|
title: "Retry editor batch",
|
|
@@ -33031,7 +33078,7 @@ registerTool("create_editor_draft", {
|
|
|
33031
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.'),
|
|
33032
33079
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Narrative arc / segment structure. Omit for Auto/generic."),
|
|
33033
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."),
|
|
33034
|
-
theme: exports_external.
|
|
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.'),
|
|
33035
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"),
|
|
33036
33083
|
export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")'),
|
|
33037
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).")
|
|
@@ -33049,14 +33096,14 @@ registerTool("create_editor_draft", {
|
|
|
33049
33096
|
product_subject: args.product_subject,
|
|
33050
33097
|
creative_format: args.creative_format,
|
|
33051
33098
|
visual_language: args.visual_language,
|
|
33052
|
-
theme: args.theme,
|
|
33099
|
+
theme: args.theme ?? "none",
|
|
33053
33100
|
voice_id: args.voice_id,
|
|
33054
33101
|
export_aspect_ratio: args.export_aspect_ratio,
|
|
33055
33102
|
project_intent: args.project_intent
|
|
33056
33103
|
};
|
|
33057
33104
|
if (prompt)
|
|
33058
33105
|
body.product_prompt = prompt;
|
|
33059
|
-
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 ?? ""));
|
|
33060
33107
|
return ok({
|
|
33061
33108
|
slug: created.data.slug,
|
|
33062
33109
|
kind: "editor",
|
|
@@ -33080,6 +33127,7 @@ function conciseEditorState(value) {
|
|
|
33080
33127
|
"scenario_prompt",
|
|
33081
33128
|
"batch_generation_status",
|
|
33082
33129
|
"batch_generation_run_id",
|
|
33130
|
+
"batch_generation_failed_position",
|
|
33083
33131
|
"auto_render_after_batch",
|
|
33084
33132
|
"narration_status",
|
|
33085
33133
|
"narration_script",
|
|
@@ -33104,6 +33152,7 @@ function conciseEditorState(value) {
|
|
|
33104
33152
|
prompt: segment.prompt,
|
|
33105
33153
|
status: segment.status,
|
|
33106
33154
|
source_type: segment.source_type,
|
|
33155
|
+
generation_claim_token: segment.generation_claim_token,
|
|
33107
33156
|
use_previous_frame: segment.use_previous_frame,
|
|
33108
33157
|
video_stale: segment.video_stale,
|
|
33109
33158
|
error_code: segment.error_code,
|
|
@@ -33145,7 +33194,7 @@ registerTool("generate_scenario", {
|
|
|
33145
33194
|
segments_count: exports_external.number().int().min(3).max(10).optional().describe("How many scenes (3–10, server default 5)"),
|
|
33146
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."),
|
|
33147
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."),
|
|
33148
|
-
theme: exports_external.
|
|
33197
|
+
theme: exports_external.enum(SCENARIO_THEMES).optional().describe("Persist the genre overlay / theme before generating (e.g. realistic, cinematic, anime, none)."),
|
|
33149
33198
|
auto_unlock: exports_external.boolean().optional().describe("On 429, spend 1 credit to unlock +10 assists and retry once (default false)")
|
|
33150
33199
|
},
|
|
33151
33200
|
annotations: {
|
|
@@ -33300,6 +33349,50 @@ registerTool("generate_segment", {
|
|
|
33300
33349
|
const res = await client.post(`/editor/${args.slug}/segments/${String(args.segment_id)}/generate`, undefined, undefined);
|
|
33301
33350
|
return ok(asRecord(res).data ?? res);
|
|
33302
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
|
+
}));
|
|
33303
33396
|
registerTool("regenerate_segment", {
|
|
33304
33397
|
title: "Regenerate a completed segment (new version, 4 credits)",
|
|
33305
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.",
|
|
@@ -33322,7 +33415,7 @@ registerTool("regenerate_segment", {
|
|
|
33322
33415
|
}));
|
|
33323
33416
|
registerTool("generate_all_segments", {
|
|
33324
33417
|
title: "Generate all pending segments (sequential)",
|
|
33325
|
-
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.",
|
|
33326
33419
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
33327
33420
|
annotations: {
|
|
33328
33421
|
title: "Generate all segments",
|
|
@@ -33523,7 +33616,7 @@ registerTool("generate_music", {
|
|
|
33523
33616
|
}));
|
|
33524
33617
|
registerTool("render", {
|
|
33525
33618
|
title: "Render the final editor video (0 credits)",
|
|
33526
|
-
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.",
|
|
33527
33620
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
33528
33621
|
annotations: { title: "Render", ...WRITE, idempotentHint: false }
|
|
33529
33622
|
}, tool(async (args, client) => {
|
|
@@ -33763,7 +33856,7 @@ registerTool("use_asset_for_segment", {
|
|
|
33763
33856
|
}));
|
|
33764
33857
|
registerTool("set_product", {
|
|
33765
33858
|
title: "Attach a product image (from a local file)",
|
|
33766
|
-
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.",
|
|
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.",
|
|
33767
33860
|
inputSchema: {
|
|
33768
33861
|
slug: exports_external.string().describe("Editor project slug"),
|
|
33769
33862
|
file_path: exports_external.string().describe("Local product image path (.jpg/.jpeg/.png)"),
|
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>",
|
package/src/core.ts
CHANGED
|
@@ -275,6 +275,8 @@ export interface NormalizedStatus {
|
|
|
275
275
|
failure_source?: string;
|
|
276
276
|
/** Short only: paid generation attempt number that failed. */
|
|
277
277
|
failure_attempt?: number;
|
|
278
|
+
/** Short only: current paid attempt; echo when cancelling. */
|
|
279
|
+
generation_attempt?: number;
|
|
278
280
|
/** Editor only: the newest delivered render no longer matches the live project. */
|
|
279
281
|
stale?: boolean;
|
|
280
282
|
/** Editor only: current scene ids that are still generating. */
|
|
@@ -361,6 +363,8 @@ export function normalizeStatus(
|
|
|
361
363
|
status.failure_source = d.failure_source;
|
|
362
364
|
if (typeof d.failure_attempt === "number")
|
|
363
365
|
status.failure_attempt = d.failure_attempt;
|
|
366
|
+
if (typeof d.generation_attempt === "number")
|
|
367
|
+
status.generation_attempt = d.generation_attempt;
|
|
364
368
|
// Additive free-re-render bookkeeping (2026-07 API): surfaced only when
|
|
365
369
|
// the server actually sends the booleans, so older payloads normalize
|
|
366
370
|
// byte-identically. A failed free re-render does NOT flip stage to
|
package/src/index.ts
CHANGED
|
@@ -565,6 +565,25 @@ const VISUAL_LANGUAGES = [
|
|
|
565
565
|
"startup_explainer",
|
|
566
566
|
"luxury_minimal",
|
|
567
567
|
] as const;
|
|
568
|
+
// Closed API enum from VideoFactories.ScenarioThemes. Keeping this explicit in
|
|
569
|
+
// MCP prevents an agent typo from spending a round trip on an avoidable 422.
|
|
570
|
+
const SCENARIO_THEMES = [
|
|
571
|
+
"none",
|
|
572
|
+
"realistic",
|
|
573
|
+
"cinematic",
|
|
574
|
+
"anime",
|
|
575
|
+
"sci_fi",
|
|
576
|
+
"fantasy",
|
|
577
|
+
"noir",
|
|
578
|
+
"superhero",
|
|
579
|
+
"horror",
|
|
580
|
+
"mockumentary",
|
|
581
|
+
"sports",
|
|
582
|
+
"gaming",
|
|
583
|
+
"retro_80s",
|
|
584
|
+
"minimalist",
|
|
585
|
+
"cyberpunk",
|
|
586
|
+
] as const;
|
|
568
587
|
// The languages the API's VideoFactory.language accepts (video_factory.ex
|
|
569
588
|
// @supported_languages, validate_inclusion at create + autopilot). It's a CLOSED
|
|
570
589
|
// set of bare ISO-639-1 codes — the server rejects region/script subtags ("en",
|
|
@@ -914,12 +933,12 @@ registerTool(
|
|
|
914
933
|
"SHORTS + EDITOR: visual style direction and render look. Default app choice is kinetic_creator.",
|
|
915
934
|
),
|
|
916
935
|
theme: z
|
|
917
|
-
.
|
|
936
|
+
.enum(SCENARIO_THEMES)
|
|
918
937
|
.optional()
|
|
919
938
|
.describe(
|
|
920
939
|
"Visual theme: editor genre overlay, and legacy shorts fallback only when visual_language is unset. " +
|
|
921
|
-
"For editor,
|
|
922
|
-
"
|
|
940
|
+
"For editor, visual_language drives the look when present; make_video defaults theme to none " +
|
|
941
|
+
"(literal — no imposed genre style). Options: none, realistic, cinematic, " +
|
|
923
942
|
"anime, sci_fi, fantasy, noir, superhero, horror, mockumentary, sports, gaming, retro_80s, " +
|
|
924
943
|
"minimalist, cyberpunk",
|
|
925
944
|
),
|
|
@@ -1115,12 +1134,14 @@ registerTool(
|
|
|
1115
1134
|
voice_id: args.voice_id,
|
|
1116
1135
|
creative_format: args.creative_format,
|
|
1117
1136
|
visual_language: args.visual_language,
|
|
1118
|
-
|
|
1137
|
+
// Prompt-first default: do not silently inject the API's legacy
|
|
1138
|
+
// realistic/neutral art direction when the caller authored a look.
|
|
1139
|
+
theme: args.theme ?? "none",
|
|
1119
1140
|
},
|
|
1120
1141
|
// Fold every create-affecting param into the key so two calls that
|
|
1121
1142
|
// differ only by style/theme/voice/aspect get distinct drafts (shared
|
|
1122
1143
|
// with the golden-pin test via core.ts).
|
|
1123
|
-
makeVideoEditorCreateKey(args),
|
|
1144
|
+
makeVideoEditorCreateKey({ ...args, theme: args.theme ?? "none" }),
|
|
1124
1145
|
);
|
|
1125
1146
|
slug = created.data.slug;
|
|
1126
1147
|
}
|
|
@@ -1530,7 +1551,7 @@ registerTool(
|
|
|
1530
1551
|
"Visual language for Veo direction and render styling. Good default: kinetic_creator.",
|
|
1531
1552
|
),
|
|
1532
1553
|
theme: z
|
|
1533
|
-
.
|
|
1554
|
+
.enum(SCENARIO_THEMES)
|
|
1534
1555
|
.optional()
|
|
1535
1556
|
.describe(
|
|
1536
1557
|
"Deprecated for shorts: legacy visual theme used only when visual_language is unset.",
|
|
@@ -1837,7 +1858,7 @@ registerTool(
|
|
|
1837
1858
|
.optional()
|
|
1838
1859
|
.describe("Visual language for Veo direction and render styling."),
|
|
1839
1860
|
theme: z
|
|
1840
|
-
.
|
|
1861
|
+
.enum(SCENARIO_THEMES)
|
|
1841
1862
|
.optional()
|
|
1842
1863
|
.describe(
|
|
1843
1864
|
"Deprecated for shorts: legacy visual theme used only when visual_language is unset.",
|
|
@@ -2529,6 +2550,55 @@ registerTool(
|
|
|
2529
2550
|
}),
|
|
2530
2551
|
);
|
|
2531
2552
|
|
|
2553
|
+
registerTool(
|
|
2554
|
+
"cancel_short_generation",
|
|
2555
|
+
{
|
|
2556
|
+
title: "Cancel a running short generation",
|
|
2557
|
+
description:
|
|
2558
|
+
"Stops the caller-observed paid Short attempt, fences late provider callbacks, and refunds the 15-credit " +
|
|
2559
|
+
"generation exactly once. Pass generation_attempt from get_status so a delayed command cannot cancel a " +
|
|
2560
|
+
"newer run on the same draft. The draft is preserved for editing and regeneration. Safe to repeat with " +
|
|
2561
|
+
"the same attempt after a successful cancellation. Scope: video:generate.",
|
|
2562
|
+
inputSchema: {
|
|
2563
|
+
slug: z.string().describe("Slug of the processing short"),
|
|
2564
|
+
generation_attempt: z
|
|
2565
|
+
.number()
|
|
2566
|
+
.int()
|
|
2567
|
+
.min(0)
|
|
2568
|
+
.describe("generation_attempt from the latest get_status response"),
|
|
2569
|
+
},
|
|
2570
|
+
annotations: {
|
|
2571
|
+
title: "Cancel short generation",
|
|
2572
|
+
readOnlyHint: false,
|
|
2573
|
+
destructiveHint: true,
|
|
2574
|
+
openWorldHint: true,
|
|
2575
|
+
idempotentHint: true,
|
|
2576
|
+
},
|
|
2577
|
+
},
|
|
2578
|
+
tool(async (args: { slug: string; generation_attempt: number }, client) => {
|
|
2579
|
+
const res = await client.post<{ data: unknown }>(
|
|
2580
|
+
`/shorts/${args.slug}/cancel`,
|
|
2581
|
+
{ generation_attempt: args.generation_attempt },
|
|
2582
|
+
undefined,
|
|
2583
|
+
);
|
|
2584
|
+
const data = asRecord(asRecord(res).data ?? res);
|
|
2585
|
+
return ok({
|
|
2586
|
+
...data,
|
|
2587
|
+
slug: args.slug,
|
|
2588
|
+
charged: false,
|
|
2589
|
+
// The cancel endpoint returns the short's STATE, not a refund receipt,
|
|
2590
|
+
// so don't assert refunded:true on every call — a repeat cancel is a
|
|
2591
|
+
// no-op that mints no new credits. Echo the server's own flag if it ever
|
|
2592
|
+
// sends one; otherwise describe the honest idempotent settlement.
|
|
2593
|
+
refund:
|
|
2594
|
+
"refunded" in data
|
|
2595
|
+
? data.refunded
|
|
2596
|
+
: "settled (idempotent — no new credits minted on repeat)",
|
|
2597
|
+
next: "Edit the preserved draft, then call generate_short when ready.",
|
|
2598
|
+
});
|
|
2599
|
+
}),
|
|
2600
|
+
);
|
|
2601
|
+
|
|
2532
2602
|
registerTool(
|
|
2533
2603
|
"rerender_short",
|
|
2534
2604
|
{
|
|
@@ -3066,10 +3136,10 @@ registerTool(
|
|
|
3066
3136
|
"Visual style direction and render look. Good default: kinetic_creator. When set it drives the look and a theme of 'none' is ignored.",
|
|
3067
3137
|
),
|
|
3068
3138
|
theme: z
|
|
3069
|
-
.
|
|
3139
|
+
.enum(SCENARIO_THEMES)
|
|
3070
3140
|
.optional()
|
|
3071
3141
|
.describe(
|
|
3072
|
-
'
|
|
3142
|
+
'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.',
|
|
3073
3143
|
),
|
|
3074
3144
|
voice_id: z
|
|
3075
3145
|
.string()
|
|
@@ -3226,7 +3296,7 @@ registerTool(
|
|
|
3226
3296
|
project_intent: args.project_intent,
|
|
3227
3297
|
creative_format: args.creative_format,
|
|
3228
3298
|
visual_language: args.visual_language,
|
|
3229
|
-
theme: args.theme,
|
|
3299
|
+
theme: args.theme ?? "none",
|
|
3230
3300
|
voice_id: args.voice_id,
|
|
3231
3301
|
export_aspect_ratio: args.export_aspect_ratio,
|
|
3232
3302
|
},
|
|
@@ -3235,6 +3305,7 @@ registerTool(
|
|
|
3235
3305
|
// Composition lives in core.ts, shared with the golden-pin test.
|
|
3236
3306
|
editorAdCreateKey({
|
|
3237
3307
|
...args,
|
|
3308
|
+
theme: args.theme ?? "none",
|
|
3238
3309
|
product_image_identity: productImage
|
|
3239
3310
|
? preparedUploadIdentity(productImage)
|
|
3240
3311
|
: undefined,
|
|
@@ -3507,7 +3578,12 @@ registerTool(
|
|
|
3507
3578
|
description:
|
|
3508
3579
|
"Recovers an editor whose normalized status is batch_retry_required by atomically re-queueing the failed " +
|
|
3509
3580
|
"scene and resuming its prepaid batch. This spends 0 additional credits: the batch generation was already " +
|
|
3510
|
-
"paid.
|
|
3581
|
+
"paid. Before retrying you may REPAIR the one failed scene: while paused_for_retry, set_segment_prompt can " +
|
|
3582
|
+
"edit that scene's prompt (and use_previous_frame) — but only for the exact failed position the server " +
|
|
3583
|
+
"names in batch_generation_failed_position (from get_editor); the rest of the paused batch stays " +
|
|
3584
|
+
"mutation-locked. Explicit paused retries are capped — once the failure code is free_retries_exhausted a " +
|
|
3585
|
+
"further retry is rejected (start a fresh run instead). It does not authorize or restart a failed parent " +
|
|
3586
|
+
"Autopilot run; after retrying, wait for the batch " +
|
|
3511
3587
|
"to settle. If the parent is then terminal, call start_autopilot without max_credits for a fresh quote and " +
|
|
3512
3588
|
"relaunch only with an approved cap. A batch parked on " +
|
|
3513
3589
|
"insufficient_credits needs a top-up/scope change instead. Scope: video:generate.",
|
|
@@ -3713,10 +3789,10 @@ registerTool(
|
|
|
3713
3789
|
"Visual style direction and render look. Good default: kinetic_creator. When set it drives the look and a theme of 'none' is ignored.",
|
|
3714
3790
|
),
|
|
3715
3791
|
theme: z
|
|
3716
|
-
.
|
|
3792
|
+
.enum(SCENARIO_THEMES)
|
|
3717
3793
|
.optional()
|
|
3718
3794
|
.describe(
|
|
3719
|
-
'
|
|
3795
|
+
'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.',
|
|
3720
3796
|
),
|
|
3721
3797
|
voice_id: z
|
|
3722
3798
|
.string()
|
|
@@ -3766,7 +3842,7 @@ registerTool(
|
|
|
3766
3842
|
product_subject: args.product_subject,
|
|
3767
3843
|
creative_format: args.creative_format,
|
|
3768
3844
|
visual_language: args.visual_language,
|
|
3769
|
-
theme: args.theme,
|
|
3845
|
+
theme: args.theme ?? "none",
|
|
3770
3846
|
voice_id: args.voice_id,
|
|
3771
3847
|
export_aspect_ratio: args.export_aspect_ratio,
|
|
3772
3848
|
project_intent: args.project_intent,
|
|
@@ -3786,7 +3862,7 @@ registerTool(
|
|
|
3786
3862
|
args.language ?? "en",
|
|
3787
3863
|
args.creative_format ?? "",
|
|
3788
3864
|
args.visual_language ?? "",
|
|
3789
|
-
args.theme ?? "",
|
|
3865
|
+
args.theme ?? "none",
|
|
3790
3866
|
args.voice_id ?? "",
|
|
3791
3867
|
args.export_aspect_ratio ?? "",
|
|
3792
3868
|
),
|
|
@@ -3817,6 +3893,7 @@ function conciseEditorState(value: unknown): Record<string, unknown> {
|
|
|
3817
3893
|
"scenario_prompt",
|
|
3818
3894
|
"batch_generation_status",
|
|
3819
3895
|
"batch_generation_run_id",
|
|
3896
|
+
"batch_generation_failed_position",
|
|
3820
3897
|
"auto_render_after_batch",
|
|
3821
3898
|
"narration_status",
|
|
3822
3899
|
"narration_script",
|
|
@@ -3841,6 +3918,7 @@ function conciseEditorState(value: unknown): Record<string, unknown> {
|
|
|
3841
3918
|
prompt: segment.prompt,
|
|
3842
3919
|
status: segment.status,
|
|
3843
3920
|
source_type: segment.source_type,
|
|
3921
|
+
generation_claim_token: segment.generation_claim_token,
|
|
3844
3922
|
use_previous_frame: segment.use_previous_frame,
|
|
3845
3923
|
video_stale: segment.video_stale,
|
|
3846
3924
|
error_code: segment.error_code,
|
|
@@ -3938,7 +4016,7 @@ registerTool(
|
|
|
3938
4016
|
"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.",
|
|
3939
4017
|
),
|
|
3940
4018
|
theme: z
|
|
3941
|
-
.
|
|
4019
|
+
.enum(SCENARIO_THEMES)
|
|
3942
4020
|
.optional()
|
|
3943
4021
|
.describe(
|
|
3944
4022
|
"Persist the genre overlay / theme before generating (e.g. realistic, cinematic, anime, none).",
|
|
@@ -4247,6 +4325,96 @@ registerTool(
|
|
|
4247
4325
|
}),
|
|
4248
4326
|
);
|
|
4249
4327
|
|
|
4328
|
+
registerTool(
|
|
4329
|
+
"cancel_segment_generation",
|
|
4330
|
+
{
|
|
4331
|
+
title: "Cancel one editor scene generation",
|
|
4332
|
+
description:
|
|
4333
|
+
"Stops one caller-observed, individually processing Editor scene; its generation_claim_token fence " +
|
|
4334
|
+
"prevents a delayed command from cancelling a newer attempt. The token is returned by get_editor. " +
|
|
4335
|
+
"Refunds that scene's charge idempotently (a free retry creates no refund). Use cancel_editor_batch or " +
|
|
4336
|
+
"cancel_autopilot for workflow-owned scenes. After cancellation, edit the prompt if needed and call " +
|
|
4337
|
+
"generate_segment again. Scope: video:generate.",
|
|
4338
|
+
inputSchema: {
|
|
4339
|
+
slug: z.string().describe("Editor project slug"),
|
|
4340
|
+
segment_id: z
|
|
4341
|
+
.union([z.number(), z.string()])
|
|
4342
|
+
.describe("Processing segment id (from get_editor)"),
|
|
4343
|
+
generation_claim_token: z
|
|
4344
|
+
.string()
|
|
4345
|
+
.nullable()
|
|
4346
|
+
.describe("generation_claim_token from that processing segment"),
|
|
4347
|
+
},
|
|
4348
|
+
annotations: {
|
|
4349
|
+
title: "Cancel segment generation",
|
|
4350
|
+
readOnlyHint: false,
|
|
4351
|
+
destructiveHint: true,
|
|
4352
|
+
openWorldHint: true,
|
|
4353
|
+
idempotentHint: true,
|
|
4354
|
+
},
|
|
4355
|
+
},
|
|
4356
|
+
tool(
|
|
4357
|
+
async (
|
|
4358
|
+
args: {
|
|
4359
|
+
slug: string;
|
|
4360
|
+
segment_id: number | string;
|
|
4361
|
+
generation_claim_token: string | null;
|
|
4362
|
+
},
|
|
4363
|
+
client,
|
|
4364
|
+
) => {
|
|
4365
|
+
const res = await client.post<{ data: unknown }>(
|
|
4366
|
+
`/editor/${args.slug}/segments/${String(args.segment_id)}/cancel`,
|
|
4367
|
+
{ expected_generation_claim_token: args.generation_claim_token },
|
|
4368
|
+
undefined,
|
|
4369
|
+
);
|
|
4370
|
+
return ok({
|
|
4371
|
+
...asRecord(asRecord(res).data ?? res),
|
|
4372
|
+
slug: args.slug,
|
|
4373
|
+
charged: false,
|
|
4374
|
+
next: "set_segment_prompt or generate_segment",
|
|
4375
|
+
});
|
|
4376
|
+
},
|
|
4377
|
+
),
|
|
4378
|
+
);
|
|
4379
|
+
|
|
4380
|
+
registerTool(
|
|
4381
|
+
"cancel_autopilot",
|
|
4382
|
+
{
|
|
4383
|
+
title: "Cancel a running Autopilot run",
|
|
4384
|
+
description:
|
|
4385
|
+
"Stops an in-flight Autopilot run on an editor project: sets autopilot_status to cancelled so the " +
|
|
4386
|
+
"orchestration chain (scenario → scenes → narration → voice → music → render) advances no further. Use " +
|
|
4387
|
+
"this when Autopilot is still running and you want to halt it. Any scenes already charged stay charged and " +
|
|
4388
|
+
"all completed work (generated scenes, voice, music, a finished render) is kept — a step already enqueued " +
|
|
4389
|
+
"may still finish, but nothing new is started. Idempotent: a no-op that returns the project unchanged when " +
|
|
4390
|
+
"Autopilot is not running (already completed, failed, cancelled, or never launched), so it is safe to " +
|
|
4391
|
+
"repeat. It does NOT itself refund or re-run anything. For a non-Autopilot prepaid batch " +
|
|
4392
|
+
"(batch_generation_status preparing / paused_for_retry) use cancel_editor_batch; to stop one " +
|
|
4393
|
+
"individually-generated scene use cancel_segment_generation. Scope: video:generate.",
|
|
4394
|
+
inputSchema: { slug: z.string().describe("Editor project slug") },
|
|
4395
|
+
annotations: {
|
|
4396
|
+
title: "Cancel autopilot",
|
|
4397
|
+
readOnlyHint: false,
|
|
4398
|
+
destructiveHint: true,
|
|
4399
|
+
openWorldHint: true,
|
|
4400
|
+
idempotentHint: true,
|
|
4401
|
+
},
|
|
4402
|
+
},
|
|
4403
|
+
tool(async (args: { slug: string }, client) => {
|
|
4404
|
+
const res = await client.post<{ data: unknown }>(
|
|
4405
|
+
`/editor/${args.slug}/autopilot/cancel`,
|
|
4406
|
+
undefined,
|
|
4407
|
+
undefined,
|
|
4408
|
+
);
|
|
4409
|
+
return ok({
|
|
4410
|
+
...asRecord(asRecord(res).data ?? res),
|
|
4411
|
+
slug: args.slug,
|
|
4412
|
+
charged: false,
|
|
4413
|
+
next: "get_status",
|
|
4414
|
+
});
|
|
4415
|
+
}),
|
|
4416
|
+
);
|
|
4417
|
+
|
|
4250
4418
|
registerTool(
|
|
4251
4419
|
"regenerate_segment",
|
|
4252
4420
|
{
|
|
@@ -4308,7 +4476,7 @@ registerTool(
|
|
|
4308
4476
|
{
|
|
4309
4477
|
title: "Generate all pending segments (sequential)",
|
|
4310
4478
|
description:
|
|
4311
|
-
"Generates every not-yet-completed scene (pending or previously failed) in position order, waiting for each to finish before starting" +
|
|
4479
|
+
"Generates every not-yet-completed scene (pending or previously failed) in position order, waiting for each to finish before starting " +
|
|
4312
4480
|
"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 " +
|
|
4313
4481
|
"completed if a scene errors on submit (e.g. 402 credits_insufficient) or fails while rendering; if a " +
|
|
4314
4482
|
"scene is still rendering past the budget it returns timed_out so you can re-run to continue (completed " +
|
|
@@ -4655,8 +4823,8 @@ registerTool(
|
|
|
4655
4823
|
{
|
|
4656
4824
|
title: "Render the final editor video (0 credits)",
|
|
4657
4825
|
description:
|
|
4658
|
-
"Renders the final MP4 from the generated segments + voice + music. Costs 0 credits
|
|
4659
|
-
"
|
|
4826
|
+
"Renders the final MP4 from the generated segments + voice + music. Costs 0 credits. Pending or previously " +
|
|
4827
|
+
"failed AI scenes are generated first and charged as a new batch. Render NEVER generates or charges for voice/music; a new " +
|
|
4660
4828
|
"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; " +
|
|
4661
4829
|
'pair with wait_for_completion({ slug, kind: "editor" }) then download_result. ' +
|
|
4662
4830
|
"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.",
|
|
@@ -5160,7 +5328,9 @@ registerTool(
|
|
|
5160
5328
|
IMAGE_EXTS.map((e) => `.${e}`).join(", ") +
|
|
5161
5329
|
" (≤8 MiB), local path confined to HUBFLUENCER_INPUT_DIR (or cwd). 0 credits. Its pixels guide product identity " +
|
|
5162
5330
|
"in every generated Editor scene and its confirmed description grounds scenario and narration. The exact photo " +
|
|
5163
|
-
"is linked as the closing image unless a separate closing image was authored; generated fine label text can still vary."
|
|
5331
|
+
"is linked as the closing image unless a separate closing image was authored; generated fine label text can still vary. " +
|
|
5332
|
+
"WARNING: replacing the product photo on a project that already has generated AI scenes invalidates their product " +
|
|
5333
|
+
"identity — those scenes were built from the old photo and must be regenerated to match the new one.",
|
|
5164
5334
|
inputSchema: {
|
|
5165
5335
|
slug: z.string().describe("Editor project slug"),
|
|
5166
5336
|
file_path: z
|
package/src/output-schemas.ts
CHANGED
|
@@ -91,6 +91,10 @@ export const getStatusOutput = z
|
|
|
91
91
|
.number()
|
|
92
92
|
.optional()
|
|
93
93
|
.describe("Short only: paid generation attempt number that failed."),
|
|
94
|
+
generation_attempt: z
|
|
95
|
+
.number()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Short only: current paid attempt; echo when cancelling."),
|
|
94
98
|
latest_render_free: z.boolean().optional(),
|
|
95
99
|
last_free_rerender_failed: z.boolean().optional(),
|
|
96
100
|
})
|
|
@@ -134,6 +138,10 @@ export const waitForCompletionOutput = z
|
|
|
134
138
|
.number()
|
|
135
139
|
.optional()
|
|
136
140
|
.describe("Short only: paid generation attempt number that failed."),
|
|
141
|
+
generation_attempt: z
|
|
142
|
+
.number()
|
|
143
|
+
.optional()
|
|
144
|
+
.describe("Short only: current paid attempt; echo when cancelling."),
|
|
137
145
|
latest_render_free: z.boolean().optional(),
|
|
138
146
|
last_free_rerender_failed: z.boolean().optional(),
|
|
139
147
|
saved_to: z.union([z.string(), z.null()]).optional(),
|