@hubfluencer/mcp 0.12.0 → 0.14.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 +154 -19
- package/package.json +1 -1
- package/src/core.ts +22 -0
- package/src/index.ts +237 -24
- package/src/output-schemas.ts +14 -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,15 @@ 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;
|
|
29513
|
+
if (typeof d.segments_completed === "number")
|
|
29514
|
+
status.segments_completed = d.segments_completed;
|
|
29515
|
+
if (typeof d.segments_total === "number")
|
|
29516
|
+
status.segments_total = d.segments_total;
|
|
29517
|
+
if (Array.isArray(d.active_segment_positions) && d.active_segment_positions.every((position) => typeof position === "number")) {
|
|
29518
|
+
status.active_segment_positions = d.active_segment_positions;
|
|
29519
|
+
}
|
|
29511
29520
|
if (typeof d.latest_render_free === "boolean") {
|
|
29512
29521
|
status.latest_render_free = d.latest_render_free;
|
|
29513
29522
|
}
|
|
@@ -30257,7 +30266,7 @@ function readStoredCredentials() {
|
|
|
30257
30266
|
// package.json
|
|
30258
30267
|
var package_default = {
|
|
30259
30268
|
name: "@hubfluencer/mcp",
|
|
30260
|
-
version: "0.
|
|
30269
|
+
version: "0.14.0",
|
|
30261
30270
|
description: "Model Context Protocol server for Hubfluencer — let AI agents generate post-ready shorts and editor ads.",
|
|
30262
30271
|
license: "MIT",
|
|
30263
30272
|
author: "Monocursive <contact@monocursive.com>",
|
|
@@ -30575,6 +30584,10 @@ var getStatusOutput = exports_external.object({
|
|
|
30575
30584
|
failure_details: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Short only: structured provider/QC details for recovery."),
|
|
30576
30585
|
failure_source: exports_external.string().optional().describe("Short only: worker/recovery layer that finalized the failure."),
|
|
30577
30586
|
failure_attempt: exports_external.number().optional().describe("Short only: paid generation attempt number that failed."),
|
|
30587
|
+
generation_attempt: exports_external.number().optional().describe("Short only: current paid attempt; echo when cancelling."),
|
|
30588
|
+
segments_completed: exports_external.number().optional(),
|
|
30589
|
+
segments_total: exports_external.number().optional(),
|
|
30590
|
+
active_segment_positions: exports_external.array(exports_external.number()).optional(),
|
|
30578
30591
|
latest_render_free: exports_external.boolean().optional(),
|
|
30579
30592
|
last_free_rerender_failed: exports_external.boolean().optional()
|
|
30580
30593
|
}).passthrough();
|
|
@@ -30594,6 +30607,10 @@ var waitForCompletionOutput = exports_external.object({
|
|
|
30594
30607
|
failure_details: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Short only: structured provider/QC details for recovery."),
|
|
30595
30608
|
failure_source: exports_external.string().optional().describe("Short only: worker/recovery layer that finalized the failure."),
|
|
30596
30609
|
failure_attempt: exports_external.number().optional().describe("Short only: paid generation attempt number that failed."),
|
|
30610
|
+
generation_attempt: exports_external.number().optional().describe("Short only: current paid attempt; echo when cancelling."),
|
|
30611
|
+
segments_completed: exports_external.number().optional(),
|
|
30612
|
+
segments_total: exports_external.number().optional(),
|
|
30613
|
+
active_segment_positions: exports_external.array(exports_external.number()).optional(),
|
|
30597
30614
|
latest_render_free: exports_external.boolean().optional(),
|
|
30598
30615
|
last_free_rerender_failed: exports_external.boolean().optional(),
|
|
30599
30616
|
saved_to: exports_external.union([exports_external.string(), exports_external.null()]).optional(),
|
|
@@ -31798,6 +31815,23 @@ var VISUAL_LANGUAGES = [
|
|
|
31798
31815
|
"startup_explainer",
|
|
31799
31816
|
"luxury_minimal"
|
|
31800
31817
|
];
|
|
31818
|
+
var SCENARIO_THEMES = [
|
|
31819
|
+
"none",
|
|
31820
|
+
"realistic",
|
|
31821
|
+
"cinematic",
|
|
31822
|
+
"anime",
|
|
31823
|
+
"sci_fi",
|
|
31824
|
+
"fantasy",
|
|
31825
|
+
"noir",
|
|
31826
|
+
"superhero",
|
|
31827
|
+
"horror",
|
|
31828
|
+
"mockumentary",
|
|
31829
|
+
"sports",
|
|
31830
|
+
"gaming",
|
|
31831
|
+
"retro_80s",
|
|
31832
|
+
"minimalist",
|
|
31833
|
+
"cyberpunk"
|
|
31834
|
+
];
|
|
31801
31835
|
var LANGUAGES = [
|
|
31802
31836
|
"en",
|
|
31803
31837
|
"fr",
|
|
@@ -31924,7 +31958,7 @@ registerTool("make_video", {
|
|
|
31924
31958
|
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
31959
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("SHORTS + EDITOR: narrative arc / segment structure. Omit for Auto/generic."),
|
|
31926
31960
|
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.
|
|
31961
|
+
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
31962
|
save_path: exports_external.string().optional().describe("Optional .mp4 path to download to (confined to HUBFLUENCER_OUTPUT_DIR or cwd)"),
|
|
31929
31963
|
max_wait_seconds: exports_external.number().optional().describe("Block budget seconds (default 240, capped 10–280)"),
|
|
31930
31964
|
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 +32037,8 @@ registerTool("make_video", {
|
|
|
32003
32037
|
voice_id: args.voice_id,
|
|
32004
32038
|
creative_format: args.creative_format,
|
|
32005
32039
|
visual_language: args.visual_language,
|
|
32006
|
-
theme: args.theme
|
|
32007
|
-
}, makeVideoEditorCreateKey(args));
|
|
32040
|
+
theme: args.theme ?? "none"
|
|
32041
|
+
}, makeVideoEditorCreateKey({ ...args, theme: args.theme ?? "none" }));
|
|
32008
32042
|
slug = created.data.slug;
|
|
32009
32043
|
}
|
|
32010
32044
|
const costPath = kind === "short" ? `/shorts/${slug}/cost` : `/editor/${slug}/autopilot/cost`;
|
|
@@ -32109,7 +32143,30 @@ registerTool("list_voices", {
|
|
|
32109
32143
|
description: "Lists available narration voices (id + name). Pass a voice id as voice_id to create_editor_ad / " + "make_video to pick the narration voice for an editor ad. Shorts have no voiceover.",
|
|
32110
32144
|
inputSchema: {},
|
|
32111
32145
|
annotations: { title: "List voices", ...RO }
|
|
32112
|
-
}, tool(async (_args, client) =>
|
|
32146
|
+
}, tool(async (_args, client) => {
|
|
32147
|
+
const response = asRecord(await client.get("/voices"));
|
|
32148
|
+
const data = asRecord(response.data ?? response);
|
|
32149
|
+
const voices = Array.isArray(data.voices) ? data.voices.map(asRecord) : [];
|
|
32150
|
+
return ok({
|
|
32151
|
+
voices: voices.map((voice) => {
|
|
32152
|
+
const verified = Array.isArray(voice.verified_languages) ? voice.verified_languages.map(asRecord) : [];
|
|
32153
|
+
return {
|
|
32154
|
+
voice_id: voice.voice_id,
|
|
32155
|
+
name: voice.name,
|
|
32156
|
+
category: voice.category,
|
|
32157
|
+
description: voice.description,
|
|
32158
|
+
labels: voice.labels,
|
|
32159
|
+
preview_url: voice.preview_url,
|
|
32160
|
+
languages: [
|
|
32161
|
+
...new Set(verified.map((language) => language.language).filter((language) => typeof language === "string"))
|
|
32162
|
+
]
|
|
32163
|
+
};
|
|
32164
|
+
}),
|
|
32165
|
+
has_more: data.has_more,
|
|
32166
|
+
total_count: data.total_count,
|
|
32167
|
+
next_page_token: data.next_page_token
|
|
32168
|
+
});
|
|
32169
|
+
}));
|
|
32113
32170
|
registerTool("list_projects", {
|
|
32114
32171
|
title: "List recent projects",
|
|
32115
32172
|
description: "Lists the caller's recent shorts and editor/video-factory projects, each normalized to " + "{slug, stage, terminal, ready, video_url}. By default the shorts listing is limited to " + "in-progress/failed; pass include_completed:true to also surface finished shorts — useful to " + "recover a lost slug. Capped to the most recent items per kind (use get_status for authoritative state).",
|
|
@@ -32155,7 +32212,7 @@ registerTool("create_short", {
|
|
|
32155
32212
|
subheadline: exports_external.string().max(200).optional().describe("The SECONDARY title / supporting line under the headline. ≤200 chars."),
|
|
32156
32213
|
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
32214
|
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.
|
|
32215
|
+
theme: exports_external.enum(SCENARIO_THEMES).optional().describe("Deprecated for shorts: legacy visual theme used only when visual_language is unset."),
|
|
32159
32216
|
music_vibe: exports_external.string().optional().describe("Background-music mood. Recognized: Upbeat (default), Cinematic, Minimal, Luxury, Playful, Jazz."),
|
|
32160
32217
|
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
32218
|
text_position: exports_external.enum(["top", "center", "bottom"]).optional().describe("Where the title overlay sits (default bottom)"),
|
|
@@ -32253,7 +32310,7 @@ registerTool("update_short", {
|
|
|
32253
32310
|
subheadline: exports_external.string().max(200).optional().describe("Secondary title / supporting line. ≤200 chars."),
|
|
32254
32311
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Structure: problem_solution, mistake_fix, myth_vs_reality, before_after, proof_demo, product_reveal."),
|
|
32255
32312
|
visual_language: exports_external.enum(VISUAL_LANGUAGES).optional().describe("Visual language for Veo direction and render styling."),
|
|
32256
|
-
theme: exports_external.
|
|
32313
|
+
theme: exports_external.enum(SCENARIO_THEMES).optional().describe("Deprecated for shorts: legacy visual theme used only when visual_language is unset."),
|
|
32257
32314
|
music_vibe: exports_external.string().optional().describe("Background-music mood: Upbeat, Cinematic, Minimal, Luxury, Playful, Jazz."),
|
|
32258
32315
|
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
32316
|
text_position: exports_external.enum(["top", "center", "bottom"]).optional().describe("Where the title overlay sits."),
|
|
@@ -32348,7 +32405,13 @@ registerTool("update_short", {
|
|
|
32348
32405
|
const res = await client.patch(`/shorts/${args.slug}`, body);
|
|
32349
32406
|
const data = asRecord(res).data ?? res;
|
|
32350
32407
|
return ok({
|
|
32351
|
-
|
|
32408
|
+
slug: args.slug,
|
|
32409
|
+
kind: "short",
|
|
32410
|
+
stage: asRecord(data).stage,
|
|
32411
|
+
status: asRecord(data).status,
|
|
32412
|
+
updated: body,
|
|
32413
|
+
end_card_mode: asRecord(data).end_card_mode,
|
|
32414
|
+
resolved_cta_text: asRecord(data).resolved_cta_text,
|
|
32352
32415
|
next: "Saved. Only text/style/CTA/end-card/logo changed on an already-generated short? " + `rerender_short({ slug: "${args.slug}" }) applies it for 0 credits. Footage/music edits ` + "(prompt, product image, format, visual_language, theme, music_*, language) need " + "generate_short (15 credits)."
|
|
32353
32416
|
});
|
|
32354
32417
|
}));
|
|
@@ -32516,6 +32579,31 @@ registerTool("generate_short", {
|
|
|
32516
32579
|
throw e;
|
|
32517
32580
|
}
|
|
32518
32581
|
}));
|
|
32582
|
+
registerTool("cancel_short_generation", {
|
|
32583
|
+
title: "Cancel a running short generation",
|
|
32584
|
+
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.",
|
|
32585
|
+
inputSchema: {
|
|
32586
|
+
slug: exports_external.string().describe("Slug of the processing short"),
|
|
32587
|
+
generation_attempt: exports_external.number().int().min(0).describe("generation_attempt from the latest get_status response")
|
|
32588
|
+
},
|
|
32589
|
+
annotations: {
|
|
32590
|
+
title: "Cancel short generation",
|
|
32591
|
+
readOnlyHint: false,
|
|
32592
|
+
destructiveHint: true,
|
|
32593
|
+
openWorldHint: true,
|
|
32594
|
+
idempotentHint: true
|
|
32595
|
+
}
|
|
32596
|
+
}, tool(async (args, client) => {
|
|
32597
|
+
const res = await client.post(`/shorts/${args.slug}/cancel`, { generation_attempt: args.generation_attempt }, undefined);
|
|
32598
|
+
const data = asRecord(asRecord(res).data ?? res);
|
|
32599
|
+
return ok({
|
|
32600
|
+
...data,
|
|
32601
|
+
slug: args.slug,
|
|
32602
|
+
charged: false,
|
|
32603
|
+
refund: "refunded" in data ? data.refunded : "settled (idempotent — no new credits minted on repeat)",
|
|
32604
|
+
next: "Edit the preserved draft, then call generate_short when ready."
|
|
32605
|
+
});
|
|
32606
|
+
}));
|
|
32519
32607
|
registerTool("rerender_short", {
|
|
32520
32608
|
title: "Re-render a short (FREE — 0 credits)",
|
|
32521
32609
|
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 +32855,7 @@ registerTool("create_editor_ad", {
|
|
|
32767
32855
|
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
32856
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Narrative arc / segment structure. Omit for Auto/generic."),
|
|
32769
32857
|
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.
|
|
32858
|
+
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
32859
|
voice_id: exports_external.string().optional().describe("Preferred narration voice id (see list_voices); omit for the default voice"),
|
|
32772
32860
|
export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")'),
|
|
32773
32861
|
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 +32906,12 @@ registerTool("create_editor_ad", {
|
|
|
32818
32906
|
project_intent: args.project_intent,
|
|
32819
32907
|
creative_format: args.creative_format,
|
|
32820
32908
|
visual_language: args.visual_language,
|
|
32821
|
-
theme: args.theme,
|
|
32909
|
+
theme: args.theme ?? "none",
|
|
32822
32910
|
voice_id: args.voice_id,
|
|
32823
32911
|
export_aspect_ratio: args.export_aspect_ratio
|
|
32824
32912
|
}, editorAdCreateKey({
|
|
32825
32913
|
...args,
|
|
32914
|
+
theme: args.theme ?? "none",
|
|
32826
32915
|
product_image_identity: productImage ? preparedUploadIdentity(productImage) : undefined,
|
|
32827
32916
|
closing_image_identity: closingImage ? preparedUploadIdentity(closingImage) : undefined,
|
|
32828
32917
|
logo_identity: logo ? preparedUploadIdentity(logo) : undefined
|
|
@@ -32946,7 +33035,7 @@ registerTool("start_autopilot", {
|
|
|
32946
33035
|
}));
|
|
32947
33036
|
registerTool("retry_editor_batch", {
|
|
32948
33037
|
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.",
|
|
33038
|
+
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
33039
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
32951
33040
|
annotations: {
|
|
32952
33041
|
title: "Retry editor batch",
|
|
@@ -33031,7 +33120,7 @@ registerTool("create_editor_draft", {
|
|
|
33031
33120
|
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
33121
|
creative_format: exports_external.enum(CREATIVE_FORMATS).optional().describe("Narrative arc / segment structure. Omit for Auto/generic."),
|
|
33033
33122
|
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.
|
|
33123
|
+
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
33124
|
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
33125
|
export_aspect_ratio: exports_external.enum(["9:16", "16:9", "1:1"]).optional().describe('Aspect ratio (default "9:16")'),
|
|
33037
33126
|
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 +33138,14 @@ registerTool("create_editor_draft", {
|
|
|
33049
33138
|
product_subject: args.product_subject,
|
|
33050
33139
|
creative_format: args.creative_format,
|
|
33051
33140
|
visual_language: args.visual_language,
|
|
33052
|
-
theme: args.theme,
|
|
33141
|
+
theme: args.theme ?? "none",
|
|
33053
33142
|
voice_id: args.voice_id,
|
|
33054
33143
|
export_aspect_ratio: args.export_aspect_ratio,
|
|
33055
33144
|
project_intent: args.project_intent
|
|
33056
33145
|
};
|
|
33057
33146
|
if (prompt)
|
|
33058
33147
|
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 ?? ""));
|
|
33148
|
+
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
33149
|
return ok({
|
|
33061
33150
|
slug: created.data.slug,
|
|
33062
33151
|
kind: "editor",
|
|
@@ -33080,6 +33169,7 @@ function conciseEditorState(value) {
|
|
|
33080
33169
|
"scenario_prompt",
|
|
33081
33170
|
"batch_generation_status",
|
|
33082
33171
|
"batch_generation_run_id",
|
|
33172
|
+
"batch_generation_failed_position",
|
|
33083
33173
|
"auto_render_after_batch",
|
|
33084
33174
|
"narration_status",
|
|
33085
33175
|
"narration_script",
|
|
@@ -33104,6 +33194,7 @@ function conciseEditorState(value) {
|
|
|
33104
33194
|
prompt: segment.prompt,
|
|
33105
33195
|
status: segment.status,
|
|
33106
33196
|
source_type: segment.source_type,
|
|
33197
|
+
generation_claim_token: segment.generation_claim_token,
|
|
33107
33198
|
use_previous_frame: segment.use_previous_frame,
|
|
33108
33199
|
video_stale: segment.video_stale,
|
|
33109
33200
|
error_code: segment.error_code,
|
|
@@ -33145,7 +33236,7 @@ registerTool("generate_scenario", {
|
|
|
33145
33236
|
segments_count: exports_external.number().int().min(3).max(10).optional().describe("How many scenes (3–10, server default 5)"),
|
|
33146
33237
|
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
33238
|
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.
|
|
33239
|
+
theme: exports_external.enum(SCENARIO_THEMES).optional().describe("Persist the genre overlay / theme before generating (e.g. realistic, cinematic, anime, none)."),
|
|
33149
33240
|
auto_unlock: exports_external.boolean().optional().describe("On 429, spend 1 credit to unlock +10 assists and retry once (default false)")
|
|
33150
33241
|
},
|
|
33151
33242
|
annotations: {
|
|
@@ -33300,6 +33391,50 @@ registerTool("generate_segment", {
|
|
|
33300
33391
|
const res = await client.post(`/editor/${args.slug}/segments/${String(args.segment_id)}/generate`, undefined, undefined);
|
|
33301
33392
|
return ok(asRecord(res).data ?? res);
|
|
33302
33393
|
}));
|
|
33394
|
+
registerTool("cancel_segment_generation", {
|
|
33395
|
+
title: "Cancel one editor scene generation",
|
|
33396
|
+
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.",
|
|
33397
|
+
inputSchema: {
|
|
33398
|
+
slug: exports_external.string().describe("Editor project slug"),
|
|
33399
|
+
segment_id: exports_external.union([exports_external.number(), exports_external.string()]).describe("Processing segment id (from get_editor)"),
|
|
33400
|
+
generation_claim_token: exports_external.string().nullable().describe("generation_claim_token from that processing segment")
|
|
33401
|
+
},
|
|
33402
|
+
annotations: {
|
|
33403
|
+
title: "Cancel segment generation",
|
|
33404
|
+
readOnlyHint: false,
|
|
33405
|
+
destructiveHint: true,
|
|
33406
|
+
openWorldHint: true,
|
|
33407
|
+
idempotentHint: true
|
|
33408
|
+
}
|
|
33409
|
+
}, tool(async (args, client) => {
|
|
33410
|
+
const res = await client.post(`/editor/${args.slug}/segments/${String(args.segment_id)}/cancel`, { expected_generation_claim_token: args.generation_claim_token }, undefined);
|
|
33411
|
+
return ok({
|
|
33412
|
+
...asRecord(asRecord(res).data ?? res),
|
|
33413
|
+
slug: args.slug,
|
|
33414
|
+
charged: false,
|
|
33415
|
+
next: "set_segment_prompt or generate_segment"
|
|
33416
|
+
});
|
|
33417
|
+
}));
|
|
33418
|
+
registerTool("cancel_autopilot", {
|
|
33419
|
+
title: "Cancel a running Autopilot run",
|
|
33420
|
+
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.",
|
|
33421
|
+
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
33422
|
+
annotations: {
|
|
33423
|
+
title: "Cancel autopilot",
|
|
33424
|
+
readOnlyHint: false,
|
|
33425
|
+
destructiveHint: true,
|
|
33426
|
+
openWorldHint: true,
|
|
33427
|
+
idempotentHint: true
|
|
33428
|
+
}
|
|
33429
|
+
}, tool(async (args, client) => {
|
|
33430
|
+
const res = await client.post(`/editor/${args.slug}/autopilot/cancel`, undefined, undefined);
|
|
33431
|
+
return ok({
|
|
33432
|
+
...asRecord(asRecord(res).data ?? res),
|
|
33433
|
+
slug: args.slug,
|
|
33434
|
+
charged: false,
|
|
33435
|
+
next: "get_status"
|
|
33436
|
+
});
|
|
33437
|
+
}));
|
|
33303
33438
|
registerTool("regenerate_segment", {
|
|
33304
33439
|
title: "Regenerate a completed segment (new version, 4 credits)",
|
|
33305
33440
|
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 +33457,7 @@ registerTool("regenerate_segment", {
|
|
|
33322
33457
|
}));
|
|
33323
33458
|
registerTool("generate_all_segments", {
|
|
33324
33459
|
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.",
|
|
33460
|
+
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
33461
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
33327
33462
|
annotations: {
|
|
33328
33463
|
title: "Generate all segments",
|
|
@@ -33500,7 +33635,7 @@ registerTool("generate_music", {
|
|
|
33500
33635
|
mood: exports_external.string().optional().describe("Optional mood hint"),
|
|
33501
33636
|
genre: exports_external.string().optional().describe("Optional genre hint"),
|
|
33502
33637
|
tempo: exports_external.string().optional().describe("Optional tempo hint"),
|
|
33503
|
-
instruments: exports_external.string().optional().describe(
|
|
33638
|
+
instruments: exports_external.array(exports_external.string().min(1).max(80)).max(10).optional().describe('Optional instrument hints, e.g. ["piano", "strings"]')
|
|
33504
33639
|
},
|
|
33505
33640
|
annotations: { title: "Generate music", ...WRITE, idempotentHint: false }
|
|
33506
33641
|
}, tool(async (args, client) => {
|
|
@@ -33523,7 +33658,7 @@ registerTool("generate_music", {
|
|
|
33523
33658
|
}));
|
|
33524
33659
|
registerTool("render", {
|
|
33525
33660
|
title: "Render the final editor video (0 credits)",
|
|
33526
|
-
description: "Renders the final MP4 from the generated segments + voice + music. Costs 0 credits
|
|
33661
|
+
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
33662
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
33528
33663
|
annotations: { title: "Render", ...WRITE, idempotentHint: false }
|
|
33529
33664
|
}, tool(async (args, client) => {
|
|
@@ -33763,7 +33898,7 @@ registerTool("use_asset_for_segment", {
|
|
|
33763
33898
|
}));
|
|
33764
33899
|
registerTool("set_product", {
|
|
33765
33900
|
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.",
|
|
33901
|
+
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
33902
|
inputSchema: {
|
|
33768
33903
|
slug: exports_external.string().describe("Editor project slug"),
|
|
33769
33904
|
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.14.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,14 @@ 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;
|
|
280
|
+
/** Short only: completed/current Veo clip count for useful long-poll progress. */
|
|
281
|
+
segments_completed?: number;
|
|
282
|
+
/** Short only: total current Veo clips in the paid attempt. */
|
|
283
|
+
segments_total?: number;
|
|
284
|
+
/** Short only: clip positions that are pending or processing. */
|
|
285
|
+
active_segment_positions?: number[];
|
|
278
286
|
/** Editor only: the newest delivered render no longer matches the live project. */
|
|
279
287
|
stale?: boolean;
|
|
280
288
|
/** Editor only: current scene ids that are still generating. */
|
|
@@ -361,6 +369,20 @@ export function normalizeStatus(
|
|
|
361
369
|
status.failure_source = d.failure_source;
|
|
362
370
|
if (typeof d.failure_attempt === "number")
|
|
363
371
|
status.failure_attempt = d.failure_attempt;
|
|
372
|
+
if (typeof d.generation_attempt === "number")
|
|
373
|
+
status.generation_attempt = d.generation_attempt;
|
|
374
|
+
if (typeof d.segments_completed === "number")
|
|
375
|
+
status.segments_completed = d.segments_completed;
|
|
376
|
+
if (typeof d.segments_total === "number")
|
|
377
|
+
status.segments_total = d.segments_total;
|
|
378
|
+
if (
|
|
379
|
+
Array.isArray(d.active_segment_positions) &&
|
|
380
|
+
d.active_segment_positions.every(
|
|
381
|
+
(position) => typeof position === "number",
|
|
382
|
+
)
|
|
383
|
+
) {
|
|
384
|
+
status.active_segment_positions = d.active_segment_positions as number[];
|
|
385
|
+
}
|
|
364
386
|
// Additive free-re-render bookkeeping (2026-07 API): surfaced only when
|
|
365
387
|
// the server actually sends the booleans, so older payloads normalize
|
|
366
388
|
// 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
|
}
|
|
@@ -1396,7 +1417,40 @@ registerTool(
|
|
|
1396
1417
|
inputSchema: {},
|
|
1397
1418
|
annotations: { title: "List voices", ...RO },
|
|
1398
1419
|
},
|
|
1399
|
-
tool(async (_args, client) =>
|
|
1420
|
+
tool(async (_args, client) => {
|
|
1421
|
+
const response = asRecord(await client.get("/voices"));
|
|
1422
|
+
const data = asRecord(response.data ?? response);
|
|
1423
|
+
const voices = Array.isArray(data.voices) ? data.voices.map(asRecord) : [];
|
|
1424
|
+
|
|
1425
|
+
return ok({
|
|
1426
|
+
voices: voices.map((voice) => {
|
|
1427
|
+
const verified = Array.isArray(voice.verified_languages)
|
|
1428
|
+
? voice.verified_languages.map(asRecord)
|
|
1429
|
+
: [];
|
|
1430
|
+
return {
|
|
1431
|
+
voice_id: voice.voice_id,
|
|
1432
|
+
name: voice.name,
|
|
1433
|
+
category: voice.category,
|
|
1434
|
+
description: voice.description,
|
|
1435
|
+
labels: voice.labels,
|
|
1436
|
+
preview_url: voice.preview_url,
|
|
1437
|
+
languages: [
|
|
1438
|
+
...new Set(
|
|
1439
|
+
verified
|
|
1440
|
+
.map((language) => language.language)
|
|
1441
|
+
.filter(
|
|
1442
|
+
(language): language is string =>
|
|
1443
|
+
typeof language === "string",
|
|
1444
|
+
),
|
|
1445
|
+
),
|
|
1446
|
+
],
|
|
1447
|
+
};
|
|
1448
|
+
}),
|
|
1449
|
+
has_more: data.has_more,
|
|
1450
|
+
total_count: data.total_count,
|
|
1451
|
+
next_page_token: data.next_page_token,
|
|
1452
|
+
});
|
|
1453
|
+
}),
|
|
1400
1454
|
);
|
|
1401
1455
|
|
|
1402
1456
|
// ── Projects ──────────────────────────────────────────────────────────────
|
|
@@ -1530,7 +1584,7 @@ registerTool(
|
|
|
1530
1584
|
"Visual language for Veo direction and render styling. Good default: kinetic_creator.",
|
|
1531
1585
|
),
|
|
1532
1586
|
theme: z
|
|
1533
|
-
.
|
|
1587
|
+
.enum(SCENARIO_THEMES)
|
|
1534
1588
|
.optional()
|
|
1535
1589
|
.describe(
|
|
1536
1590
|
"Deprecated for shorts: legacy visual theme used only when visual_language is unset.",
|
|
@@ -1837,7 +1891,7 @@ registerTool(
|
|
|
1837
1891
|
.optional()
|
|
1838
1892
|
.describe("Visual language for Veo direction and render styling."),
|
|
1839
1893
|
theme: z
|
|
1840
|
-
.
|
|
1894
|
+
.enum(SCENARIO_THEMES)
|
|
1841
1895
|
.optional()
|
|
1842
1896
|
.describe(
|
|
1843
1897
|
"Deprecated for shorts: legacy visual theme used only when visual_language is unset.",
|
|
@@ -2092,7 +2146,13 @@ registerTool(
|
|
|
2092
2146
|
);
|
|
2093
2147
|
const data = asRecord(res).data ?? res;
|
|
2094
2148
|
return ok({
|
|
2095
|
-
|
|
2149
|
+
slug: args.slug,
|
|
2150
|
+
kind: "short",
|
|
2151
|
+
stage: asRecord(data).stage,
|
|
2152
|
+
status: asRecord(data).status,
|
|
2153
|
+
updated: body,
|
|
2154
|
+
end_card_mode: asRecord(data).end_card_mode,
|
|
2155
|
+
resolved_cta_text: asRecord(data).resolved_cta_text,
|
|
2096
2156
|
next:
|
|
2097
2157
|
"Saved. Only text/style/CTA/end-card/logo changed on an already-generated short? " +
|
|
2098
2158
|
`rerender_short({ slug: "${args.slug}" }) applies it for 0 credits. Footage/music edits ` +
|
|
@@ -2529,6 +2589,55 @@ registerTool(
|
|
|
2529
2589
|
}),
|
|
2530
2590
|
);
|
|
2531
2591
|
|
|
2592
|
+
registerTool(
|
|
2593
|
+
"cancel_short_generation",
|
|
2594
|
+
{
|
|
2595
|
+
title: "Cancel a running short generation",
|
|
2596
|
+
description:
|
|
2597
|
+
"Stops the caller-observed paid Short attempt, fences late provider callbacks, and refunds the 15-credit " +
|
|
2598
|
+
"generation exactly once. Pass generation_attempt from get_status so a delayed command cannot cancel a " +
|
|
2599
|
+
"newer run on the same draft. The draft is preserved for editing and regeneration. Safe to repeat with " +
|
|
2600
|
+
"the same attempt after a successful cancellation. Scope: video:generate.",
|
|
2601
|
+
inputSchema: {
|
|
2602
|
+
slug: z.string().describe("Slug of the processing short"),
|
|
2603
|
+
generation_attempt: z
|
|
2604
|
+
.number()
|
|
2605
|
+
.int()
|
|
2606
|
+
.min(0)
|
|
2607
|
+
.describe("generation_attempt from the latest get_status response"),
|
|
2608
|
+
},
|
|
2609
|
+
annotations: {
|
|
2610
|
+
title: "Cancel short generation",
|
|
2611
|
+
readOnlyHint: false,
|
|
2612
|
+
destructiveHint: true,
|
|
2613
|
+
openWorldHint: true,
|
|
2614
|
+
idempotentHint: true,
|
|
2615
|
+
},
|
|
2616
|
+
},
|
|
2617
|
+
tool(async (args: { slug: string; generation_attempt: number }, client) => {
|
|
2618
|
+
const res = await client.post<{ data: unknown }>(
|
|
2619
|
+
`/shorts/${args.slug}/cancel`,
|
|
2620
|
+
{ generation_attempt: args.generation_attempt },
|
|
2621
|
+
undefined,
|
|
2622
|
+
);
|
|
2623
|
+
const data = asRecord(asRecord(res).data ?? res);
|
|
2624
|
+
return ok({
|
|
2625
|
+
...data,
|
|
2626
|
+
slug: args.slug,
|
|
2627
|
+
charged: false,
|
|
2628
|
+
// The cancel endpoint returns the short's STATE, not a refund receipt,
|
|
2629
|
+
// so don't assert refunded:true on every call — a repeat cancel is a
|
|
2630
|
+
// no-op that mints no new credits. Echo the server's own flag if it ever
|
|
2631
|
+
// sends one; otherwise describe the honest idempotent settlement.
|
|
2632
|
+
refund:
|
|
2633
|
+
"refunded" in data
|
|
2634
|
+
? data.refunded
|
|
2635
|
+
: "settled (idempotent — no new credits minted on repeat)",
|
|
2636
|
+
next: "Edit the preserved draft, then call generate_short when ready.",
|
|
2637
|
+
});
|
|
2638
|
+
}),
|
|
2639
|
+
);
|
|
2640
|
+
|
|
2532
2641
|
registerTool(
|
|
2533
2642
|
"rerender_short",
|
|
2534
2643
|
{
|
|
@@ -3066,10 +3175,10 @@ registerTool(
|
|
|
3066
3175
|
"Visual style direction and render look. Good default: kinetic_creator. When set it drives the look and a theme of 'none' is ignored.",
|
|
3067
3176
|
),
|
|
3068
3177
|
theme: z
|
|
3069
|
-
.
|
|
3178
|
+
.enum(SCENARIO_THEMES)
|
|
3070
3179
|
.optional()
|
|
3071
3180
|
.describe(
|
|
3072
|
-
'
|
|
3181
|
+
'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
3182
|
),
|
|
3074
3183
|
voice_id: z
|
|
3075
3184
|
.string()
|
|
@@ -3226,7 +3335,7 @@ registerTool(
|
|
|
3226
3335
|
project_intent: args.project_intent,
|
|
3227
3336
|
creative_format: args.creative_format,
|
|
3228
3337
|
visual_language: args.visual_language,
|
|
3229
|
-
theme: args.theme,
|
|
3338
|
+
theme: args.theme ?? "none",
|
|
3230
3339
|
voice_id: args.voice_id,
|
|
3231
3340
|
export_aspect_ratio: args.export_aspect_ratio,
|
|
3232
3341
|
},
|
|
@@ -3235,6 +3344,7 @@ registerTool(
|
|
|
3235
3344
|
// Composition lives in core.ts, shared with the golden-pin test.
|
|
3236
3345
|
editorAdCreateKey({
|
|
3237
3346
|
...args,
|
|
3347
|
+
theme: args.theme ?? "none",
|
|
3238
3348
|
product_image_identity: productImage
|
|
3239
3349
|
? preparedUploadIdentity(productImage)
|
|
3240
3350
|
: undefined,
|
|
@@ -3507,7 +3617,12 @@ registerTool(
|
|
|
3507
3617
|
description:
|
|
3508
3618
|
"Recovers an editor whose normalized status is batch_retry_required by atomically re-queueing the failed " +
|
|
3509
3619
|
"scene and resuming its prepaid batch. This spends 0 additional credits: the batch generation was already " +
|
|
3510
|
-
"paid.
|
|
3620
|
+
"paid. Before retrying you may REPAIR the one failed scene: while paused_for_retry, set_segment_prompt can " +
|
|
3621
|
+
"edit that scene's prompt (and use_previous_frame) — but only for the exact failed position the server " +
|
|
3622
|
+
"names in batch_generation_failed_position (from get_editor); the rest of the paused batch stays " +
|
|
3623
|
+
"mutation-locked. Explicit paused retries are capped — once the failure code is free_retries_exhausted a " +
|
|
3624
|
+
"further retry is rejected (start a fresh run instead). It does not authorize or restart a failed parent " +
|
|
3625
|
+
"Autopilot run; after retrying, wait for the batch " +
|
|
3511
3626
|
"to settle. If the parent is then terminal, call start_autopilot without max_credits for a fresh quote and " +
|
|
3512
3627
|
"relaunch only with an approved cap. A batch parked on " +
|
|
3513
3628
|
"insufficient_credits needs a top-up/scope change instead. Scope: video:generate.",
|
|
@@ -3713,10 +3828,10 @@ registerTool(
|
|
|
3713
3828
|
"Visual style direction and render look. Good default: kinetic_creator. When set it drives the look and a theme of 'none' is ignored.",
|
|
3714
3829
|
),
|
|
3715
3830
|
theme: z
|
|
3716
|
-
.
|
|
3831
|
+
.enum(SCENARIO_THEMES)
|
|
3717
3832
|
.optional()
|
|
3718
3833
|
.describe(
|
|
3719
|
-
'
|
|
3834
|
+
'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
3835
|
),
|
|
3721
3836
|
voice_id: z
|
|
3722
3837
|
.string()
|
|
@@ -3766,7 +3881,7 @@ registerTool(
|
|
|
3766
3881
|
product_subject: args.product_subject,
|
|
3767
3882
|
creative_format: args.creative_format,
|
|
3768
3883
|
visual_language: args.visual_language,
|
|
3769
|
-
theme: args.theme,
|
|
3884
|
+
theme: args.theme ?? "none",
|
|
3770
3885
|
voice_id: args.voice_id,
|
|
3771
3886
|
export_aspect_ratio: args.export_aspect_ratio,
|
|
3772
3887
|
project_intent: args.project_intent,
|
|
@@ -3786,7 +3901,7 @@ registerTool(
|
|
|
3786
3901
|
args.language ?? "en",
|
|
3787
3902
|
args.creative_format ?? "",
|
|
3788
3903
|
args.visual_language ?? "",
|
|
3789
|
-
args.theme ?? "",
|
|
3904
|
+
args.theme ?? "none",
|
|
3790
3905
|
args.voice_id ?? "",
|
|
3791
3906
|
args.export_aspect_ratio ?? "",
|
|
3792
3907
|
),
|
|
@@ -3817,6 +3932,7 @@ function conciseEditorState(value: unknown): Record<string, unknown> {
|
|
|
3817
3932
|
"scenario_prompt",
|
|
3818
3933
|
"batch_generation_status",
|
|
3819
3934
|
"batch_generation_run_id",
|
|
3935
|
+
"batch_generation_failed_position",
|
|
3820
3936
|
"auto_render_after_batch",
|
|
3821
3937
|
"narration_status",
|
|
3822
3938
|
"narration_script",
|
|
@@ -3841,6 +3957,7 @@ function conciseEditorState(value: unknown): Record<string, unknown> {
|
|
|
3841
3957
|
prompt: segment.prompt,
|
|
3842
3958
|
status: segment.status,
|
|
3843
3959
|
source_type: segment.source_type,
|
|
3960
|
+
generation_claim_token: segment.generation_claim_token,
|
|
3844
3961
|
use_previous_frame: segment.use_previous_frame,
|
|
3845
3962
|
video_stale: segment.video_stale,
|
|
3846
3963
|
error_code: segment.error_code,
|
|
@@ -3938,7 +4055,7 @@ registerTool(
|
|
|
3938
4055
|
"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
4056
|
),
|
|
3940
4057
|
theme: z
|
|
3941
|
-
.
|
|
4058
|
+
.enum(SCENARIO_THEMES)
|
|
3942
4059
|
.optional()
|
|
3943
4060
|
.describe(
|
|
3944
4061
|
"Persist the genre overlay / theme before generating (e.g. realistic, cinematic, anime, none).",
|
|
@@ -4247,6 +4364,96 @@ registerTool(
|
|
|
4247
4364
|
}),
|
|
4248
4365
|
);
|
|
4249
4366
|
|
|
4367
|
+
registerTool(
|
|
4368
|
+
"cancel_segment_generation",
|
|
4369
|
+
{
|
|
4370
|
+
title: "Cancel one editor scene generation",
|
|
4371
|
+
description:
|
|
4372
|
+
"Stops one caller-observed, individually processing Editor scene; its generation_claim_token fence " +
|
|
4373
|
+
"prevents a delayed command from cancelling a newer attempt. The token is returned by get_editor. " +
|
|
4374
|
+
"Refunds that scene's charge idempotently (a free retry creates no refund). Use cancel_editor_batch or " +
|
|
4375
|
+
"cancel_autopilot for workflow-owned scenes. After cancellation, edit the prompt if needed and call " +
|
|
4376
|
+
"generate_segment again. Scope: video:generate.",
|
|
4377
|
+
inputSchema: {
|
|
4378
|
+
slug: z.string().describe("Editor project slug"),
|
|
4379
|
+
segment_id: z
|
|
4380
|
+
.union([z.number(), z.string()])
|
|
4381
|
+
.describe("Processing segment id (from get_editor)"),
|
|
4382
|
+
generation_claim_token: z
|
|
4383
|
+
.string()
|
|
4384
|
+
.nullable()
|
|
4385
|
+
.describe("generation_claim_token from that processing segment"),
|
|
4386
|
+
},
|
|
4387
|
+
annotations: {
|
|
4388
|
+
title: "Cancel segment generation",
|
|
4389
|
+
readOnlyHint: false,
|
|
4390
|
+
destructiveHint: true,
|
|
4391
|
+
openWorldHint: true,
|
|
4392
|
+
idempotentHint: true,
|
|
4393
|
+
},
|
|
4394
|
+
},
|
|
4395
|
+
tool(
|
|
4396
|
+
async (
|
|
4397
|
+
args: {
|
|
4398
|
+
slug: string;
|
|
4399
|
+
segment_id: number | string;
|
|
4400
|
+
generation_claim_token: string | null;
|
|
4401
|
+
},
|
|
4402
|
+
client,
|
|
4403
|
+
) => {
|
|
4404
|
+
const res = await client.post<{ data: unknown }>(
|
|
4405
|
+
`/editor/${args.slug}/segments/${String(args.segment_id)}/cancel`,
|
|
4406
|
+
{ expected_generation_claim_token: args.generation_claim_token },
|
|
4407
|
+
undefined,
|
|
4408
|
+
);
|
|
4409
|
+
return ok({
|
|
4410
|
+
...asRecord(asRecord(res).data ?? res),
|
|
4411
|
+
slug: args.slug,
|
|
4412
|
+
charged: false,
|
|
4413
|
+
next: "set_segment_prompt or generate_segment",
|
|
4414
|
+
});
|
|
4415
|
+
},
|
|
4416
|
+
),
|
|
4417
|
+
);
|
|
4418
|
+
|
|
4419
|
+
registerTool(
|
|
4420
|
+
"cancel_autopilot",
|
|
4421
|
+
{
|
|
4422
|
+
title: "Cancel a running Autopilot run",
|
|
4423
|
+
description:
|
|
4424
|
+
"Stops an in-flight Autopilot run on an editor project: sets autopilot_status to cancelled so the " +
|
|
4425
|
+
"orchestration chain (scenario → scenes → narration → voice → music → render) advances no further. Use " +
|
|
4426
|
+
"this when Autopilot is still running and you want to halt it. Any scenes already charged stay charged and " +
|
|
4427
|
+
"all completed work (generated scenes, voice, music, a finished render) is kept — a step already enqueued " +
|
|
4428
|
+
"may still finish, but nothing new is started. Idempotent: a no-op that returns the project unchanged when " +
|
|
4429
|
+
"Autopilot is not running (already completed, failed, cancelled, or never launched), so it is safe to " +
|
|
4430
|
+
"repeat. It does NOT itself refund or re-run anything. For a non-Autopilot prepaid batch " +
|
|
4431
|
+
"(batch_generation_status preparing / paused_for_retry) use cancel_editor_batch; to stop one " +
|
|
4432
|
+
"individually-generated scene use cancel_segment_generation. Scope: video:generate.",
|
|
4433
|
+
inputSchema: { slug: z.string().describe("Editor project slug") },
|
|
4434
|
+
annotations: {
|
|
4435
|
+
title: "Cancel autopilot",
|
|
4436
|
+
readOnlyHint: false,
|
|
4437
|
+
destructiveHint: true,
|
|
4438
|
+
openWorldHint: true,
|
|
4439
|
+
idempotentHint: true,
|
|
4440
|
+
},
|
|
4441
|
+
},
|
|
4442
|
+
tool(async (args: { slug: string }, client) => {
|
|
4443
|
+
const res = await client.post<{ data: unknown }>(
|
|
4444
|
+
`/editor/${args.slug}/autopilot/cancel`,
|
|
4445
|
+
undefined,
|
|
4446
|
+
undefined,
|
|
4447
|
+
);
|
|
4448
|
+
return ok({
|
|
4449
|
+
...asRecord(asRecord(res).data ?? res),
|
|
4450
|
+
slug: args.slug,
|
|
4451
|
+
charged: false,
|
|
4452
|
+
next: "get_status",
|
|
4453
|
+
});
|
|
4454
|
+
}),
|
|
4455
|
+
);
|
|
4456
|
+
|
|
4250
4457
|
registerTool(
|
|
4251
4458
|
"regenerate_segment",
|
|
4252
4459
|
{
|
|
@@ -4308,7 +4515,7 @@ registerTool(
|
|
|
4308
4515
|
{
|
|
4309
4516
|
title: "Generate all pending segments (sequential)",
|
|
4310
4517
|
description:
|
|
4311
|
-
"Generates every not-yet-completed scene (pending or previously failed) in position order, waiting for each to finish before starting" +
|
|
4518
|
+
"Generates every not-yet-completed scene (pending or previously failed) in position order, waiting for each to finish before starting " +
|
|
4312
4519
|
"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
4520
|
"completed if a scene errors on submit (e.g. 402 credits_insufficient) or fails while rendering; if a " +
|
|
4314
4521
|
"scene is still rendering past the budget it returns timed_out so you can re-run to continue (completed " +
|
|
@@ -4610,7 +4817,11 @@ registerTool(
|
|
|
4610
4817
|
mood: z.string().optional().describe("Optional mood hint"),
|
|
4611
4818
|
genre: z.string().optional().describe("Optional genre hint"),
|
|
4612
4819
|
tempo: z.string().optional().describe("Optional tempo hint"),
|
|
4613
|
-
instruments: z
|
|
4820
|
+
instruments: z
|
|
4821
|
+
.array(z.string().min(1).max(80))
|
|
4822
|
+
.max(10)
|
|
4823
|
+
.optional()
|
|
4824
|
+
.describe('Optional instrument hints, e.g. ["piano", "strings"]'),
|
|
4614
4825
|
},
|
|
4615
4826
|
annotations: { title: "Generate music", ...WRITE, idempotentHint: false },
|
|
4616
4827
|
},
|
|
@@ -4622,7 +4833,7 @@ registerTool(
|
|
|
4622
4833
|
mood?: string;
|
|
4623
4834
|
genre?: string;
|
|
4624
4835
|
tempo?: string;
|
|
4625
|
-
instruments?: string;
|
|
4836
|
+
instruments?: string[];
|
|
4626
4837
|
},
|
|
4627
4838
|
client,
|
|
4628
4839
|
) => {
|
|
@@ -4655,8 +4866,8 @@ registerTool(
|
|
|
4655
4866
|
{
|
|
4656
4867
|
title: "Render the final editor video (0 credits)",
|
|
4657
4868
|
description:
|
|
4658
|
-
"Renders the final MP4 from the generated segments + voice + music. Costs 0 credits
|
|
4659
|
-
"
|
|
4869
|
+
"Renders the final MP4 from the generated segments + voice + music. Costs 0 credits. Pending or previously " +
|
|
4870
|
+
"failed AI scenes are generated first and charged as a new batch. Render NEVER generates or charges for voice/music; a new " +
|
|
4660
4871
|
"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
4872
|
'pair with wait_for_completion({ slug, kind: "editor" }) then download_result. ' +
|
|
4662
4873
|
"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 +5371,9 @@ registerTool(
|
|
|
5160
5371
|
IMAGE_EXTS.map((e) => `.${e}`).join(", ") +
|
|
5161
5372
|
" (≤8 MiB), local path confined to HUBFLUENCER_INPUT_DIR (or cwd). 0 credits. Its pixels guide product identity " +
|
|
5162
5373
|
"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."
|
|
5374
|
+
"is linked as the closing image unless a separate closing image was authored; generated fine label text can still vary. " +
|
|
5375
|
+
"WARNING: replacing the product photo on a project that already has generated AI scenes invalidates their product " +
|
|
5376
|
+
"identity — those scenes were built from the old photo and must be regenerated to match the new one.",
|
|
5164
5377
|
inputSchema: {
|
|
5165
5378
|
slug: z.string().describe("Editor project slug"),
|
|
5166
5379
|
file_path: z
|
package/src/output-schemas.ts
CHANGED
|
@@ -91,6 +91,13 @@ 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."),
|
|
98
|
+
segments_completed: z.number().optional(),
|
|
99
|
+
segments_total: z.number().optional(),
|
|
100
|
+
active_segment_positions: z.array(z.number()).optional(),
|
|
94
101
|
latest_render_free: z.boolean().optional(),
|
|
95
102
|
last_free_rerender_failed: z.boolean().optional(),
|
|
96
103
|
})
|
|
@@ -134,6 +141,13 @@ export const waitForCompletionOutput = z
|
|
|
134
141
|
.number()
|
|
135
142
|
.optional()
|
|
136
143
|
.describe("Short only: paid generation attempt number that failed."),
|
|
144
|
+
generation_attempt: z
|
|
145
|
+
.number()
|
|
146
|
+
.optional()
|
|
147
|
+
.describe("Short only: current paid attempt; echo when cancelling."),
|
|
148
|
+
segments_completed: z.number().optional(),
|
|
149
|
+
segments_total: z.number().optional(),
|
|
150
|
+
active_segment_positions: z.array(z.number()).optional(),
|
|
137
151
|
latest_render_free: z.boolean().optional(),
|
|
138
152
|
last_free_rerender_failed: z.boolean().optional(),
|
|
139
153
|
saved_to: z.union([z.string(), z.null()]).optional(),
|