@hubfluencer/mcp 0.9.0 → 0.9.1
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 +1 -1
- package/dist/index.js +49 -14
- package/package.json +1 -1
- package/src/core.ts +28 -26
- package/src/index.ts +69 -5
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ instead draw a **free daily quota of 20 AI assists** (top up with `unlock_ai_ass
|
|
|
85
85
|
| `rerender_short` | **FREE re-render** — applies the short's current text/style/CTA/end-card state over the already-paid footage + music. Use after `update_short` for copy/style/CTA tweaks; 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
86
|
| `generate_short_text` | AI-draft editable headline / subheadline / caption beats for a short. | 1 assist |
|
|
87
87
|
| `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. For a mascot/hero product that must remain identical with no humans, pass `cast_mode:"product_only"` plus `product_image_path`. | **0** until capped launch |
|
|
88
|
-
| `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. | **0** quote; credits on capped launch |
|
|
88
|
+
| `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
89
|
| `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** |
|
|
90
90
|
| `generate_slider` | Render a carousel (copy → AI backgrounds → composite). Poll `get_slider` until `completed`. | **1 / slide** (3–10) |
|
|
91
91
|
| `get_slider` | Read a carousel's status and, when completed, the per-slide image URLs + caption + hashtags. | **0** |
|
package/dist/index.js
CHANGED
|
@@ -29558,7 +29558,7 @@ function normalizeStatus(kind, slug, data) {
|
|
|
29558
29558
|
const renderFresh = latest.is_stale === false;
|
|
29559
29559
|
const stale = renderStale || staleSegments.length > 0 || audioStale;
|
|
29560
29560
|
const renderActive = renderStatus === "pending" || renderStatus === "rendering" || renderStatus === "processing";
|
|
29561
|
-
const scenarioActive = d.scenario_status === "generating" || d.scenario_apply_status === "applying";
|
|
29561
|
+
const scenarioActive = d.scenario_status === "generating" || d.scenario_apply_status === "pending" || d.scenario_apply_status === "applying";
|
|
29562
29562
|
const narrationActive = d.narration_status === "generating";
|
|
29563
29563
|
const scenarioFailed = d.scenario_status === "failed";
|
|
29564
29564
|
const scenarioApplyFailed = d.scenario_apply_status === "failed";
|
|
@@ -29589,13 +29589,16 @@ function normalizeStatus(kind, slug, data) {
|
|
|
29589
29589
|
].find((value) => typeof value === "string" && value.trim().length > 0);
|
|
29590
29590
|
const audioActive = currentAudio.status === "pending" || currentAudio.status === "processing" || currentMusic.status === "pending" || currentMusic.status === "processing" || newerAudioVersions.some(versionActive) || newerMusicVersions.some(versionActive);
|
|
29591
29591
|
const workActive = autopilotActive || batchActive || renderActive || scenarioActive || narrationActive || audioActive || activeSegments.length > 0;
|
|
29592
|
-
const
|
|
29592
|
+
const deliveredArtifactSupersedesOrchestrationFailure = renderStatus === "completed" && Boolean(videoUrl) && renderFresh && !stale && !workActive && failedSegments.length === 0 && !audioFailed;
|
|
29593
|
+
const ready = deliveredArtifactSupersedesOrchestrationFailure;
|
|
29593
29594
|
const autopilotFailed = autopilot === "failed" || autopilot === "cancelled";
|
|
29594
29595
|
const renderFailed = renderStatus === "failed";
|
|
29595
|
-
const failed = autopilotFailed || renderFailed || scenarioApplyFailed || scenarioFailed || narrationFailed || batchFailed || failedSegments.length > 0 || audioFailed;
|
|
29596
|
+
const failed = autopilotFailed && !deliveredArtifactSupersedesOrchestrationFailure || renderFailed || scenarioApplyFailed && !deliveredArtifactSupersedesOrchestrationFailure || scenarioFailed && !deliveredArtifactSupersedesOrchestrationFailure || narrationFailed && !deliveredArtifactSupersedesOrchestrationFailure || batchFailed && !deliveredArtifactSupersedesOrchestrationFailure || failedSegments.length > 0 || audioFailed;
|
|
29596
29597
|
const terminal = ready || failed || batchInsufficientCredits || !workActive;
|
|
29597
29598
|
let stage = autopilot;
|
|
29598
|
-
if (
|
|
29599
|
+
if (ready)
|
|
29600
|
+
stage = "completed";
|
|
29601
|
+
else if (autopilot === "failed")
|
|
29599
29602
|
stage = "failed";
|
|
29600
29603
|
else if (autopilot === "cancelled")
|
|
29601
29604
|
stage = "cancelled";
|
|
@@ -29629,8 +29632,6 @@ function normalizeStatus(kind, slug, data) {
|
|
|
29629
29632
|
stage = "audio_processing";
|
|
29630
29633
|
else if (!workActive && stale)
|
|
29631
29634
|
stage = "editing_required";
|
|
29632
|
-
else if (ready)
|
|
29633
|
-
stage = "completed";
|
|
29634
29635
|
const segmentError = failedSegments.map((segment) => segment.error_message).find((value) => typeof value === "string" && value.length > 0);
|
|
29635
29636
|
return {
|
|
29636
29637
|
kind,
|
|
@@ -29639,7 +29640,7 @@ function normalizeStatus(kind, slug, data) {
|
|
|
29639
29640
|
terminal,
|
|
29640
29641
|
ready,
|
|
29641
29642
|
video_url: videoUrl,
|
|
29642
|
-
error: d.autopilot_error_message ?? latest.error_message ?? d.scenario_apply_error_message ?? d.scenario_error_message ?? d.narration_error_message ?? d.batch_generation_error_message ?? segmentError ?? audioError ?? (batchInsufficientCredits ? "Batch generation paused: not enough credits to finish. Top up or reduce scope, then resume." : null),
|
|
29643
|
+
error: ready ? null : d.autopilot_error_message ?? latest.error_message ?? d.scenario_apply_error_message ?? d.scenario_error_message ?? d.narration_error_message ?? d.batch_generation_error_message ?? segmentError ?? audioError ?? (batchInsufficientCredits ? "Batch generation paused: not enough credits to finish. Top up or reduce scope, then resume." : null),
|
|
29643
29644
|
stale,
|
|
29644
29645
|
active_segment_ids: activeSegments.map((segment) => segment.id).filter((id) => typeof id === "number" || typeof id === "string"),
|
|
29645
29646
|
stale_segment_ids: staleSegments.map((segment) => segment.id).filter((id) => typeof id === "number" || typeof id === "string")
|
|
@@ -30232,7 +30233,7 @@ function readStoredCredentials() {
|
|
|
30232
30233
|
// package.json
|
|
30233
30234
|
var package_default = {
|
|
30234
30235
|
name: "@hubfluencer/mcp",
|
|
30235
|
-
version: "0.9.
|
|
30236
|
+
version: "0.9.1",
|
|
30236
30237
|
description: "Model Context Protocol server for Hubfluencer — let AI agents generate post-ready shorts and editor ads.",
|
|
30237
30238
|
license: "MIT",
|
|
30238
30239
|
author: "Monocursive <contact@monocursive.com>",
|
|
@@ -31531,6 +31532,8 @@ async function editorAutopilotQuote(client, slug, overrides = {}) {
|
|
|
31531
31532
|
params.set("product_subject", overrides.product_subject);
|
|
31532
31533
|
if (overrides.product_prompt)
|
|
31533
31534
|
params.set("product_prompt", overrides.product_prompt);
|
|
31535
|
+
if (overrides.restart)
|
|
31536
|
+
params.set("restart", "true");
|
|
31534
31537
|
const query = params.toString();
|
|
31535
31538
|
const response = await client.get(`/editor/${slug}/autopilot/cost${query ? `?${query}` : ""}`);
|
|
31536
31539
|
const cost = asRecord(asRecord(response).data);
|
|
@@ -31553,6 +31556,26 @@ async function editorAutopilotQuote(client, slug, overrides = {}) {
|
|
|
31553
31556
|
};
|
|
31554
31557
|
}
|
|
31555
31558
|
}
|
|
31559
|
+
async function editorChargeReceipt(client, slug, action, listPriceCredits) {
|
|
31560
|
+
try {
|
|
31561
|
+
const response = await client.get(`/editor/${slug}`);
|
|
31562
|
+
const editor = asRecord(asRecord(response).data);
|
|
31563
|
+
return {
|
|
31564
|
+
action,
|
|
31565
|
+
list_price_credits: listPriceCredits,
|
|
31566
|
+
project_spend: editor.project_spend ?? null,
|
|
31567
|
+
approved_cap: editor.autopilot_max_credits ?? null,
|
|
31568
|
+
approved_run_spend: editor.autopilot_credits_spent ?? null
|
|
31569
|
+
};
|
|
31570
|
+
} catch {
|
|
31571
|
+
return {
|
|
31572
|
+
action,
|
|
31573
|
+
list_price_credits: listPriceCredits,
|
|
31574
|
+
project_spend: null,
|
|
31575
|
+
enrichment_unavailable: true
|
|
31576
|
+
};
|
|
31577
|
+
}
|
|
31578
|
+
}
|
|
31556
31579
|
function isRecompositeInProgressConflict(e) {
|
|
31557
31580
|
const err = e;
|
|
31558
31581
|
if (err?.status !== 409)
|
|
@@ -32805,13 +32828,14 @@ registerTool("create_editor_ad", {
|
|
|
32805
32828
|
}));
|
|
32806
32829
|
registerTool("start_autopilot", {
|
|
32807
32830
|
title: "Start autopilot on an existing editor draft",
|
|
32808
|
-
description: "Starts server-orchestrated autopilot (scenario → segments → narration → voice → music → render) on an " + "EXISTING editor project/draft. Without max_credits it returns the live quote and charges nothing. Supply an " + "explicit max_credits cap to authorize launch; the tool refuses if pricing is unavailable, the estimate exceeds " + "the cap, or the balance is insufficient. Each authorized call is a genuine start attempt (like tapping Launch in the app): " + "if a run is already in progress for this slug the server returns already_running — it never double-starts " + "or double-charges.
|
|
32831
|
+
description: "Starts server-orchestrated autopilot (scenario → segments → narration → voice → music → render) on an " + "EXISTING editor project/draft. Without max_credits it returns the live quote and charges nothing. Supply an " + "explicit max_credits cap to authorize launch; the tool refuses if pricing is unavailable, the estimate exceeds " + "the cap, or the balance is insufficient. Each authorized call is a genuine start attempt (like tapping Launch in the app): " + "if a run is already in progress for this slug the server returns already_running — it never double-starts " + "or double-charges. Failed or cancelled runs resume normally. For a completed project, set restart:true to quote and build a fresh creative run; omitting it only resumes pending edits. Then poll with " + 'wait_for_completion({ slug, kind: "editor" }).',
|
|
32809
32832
|
inputSchema: {
|
|
32810
32833
|
slug: exports_external.string().describe("Editor project slug"),
|
|
32811
32834
|
language: exports_external.enum(LANGUAGES).optional().describe('Optional language override applied before launch. Bare ISO-639-1 code only, e.g. "fr" — no ' + "region/script subtag. Drives scenario, narration, voice, and captions."),
|
|
32812
32835
|
product_subject: exports_external.string().max(300).optional().describe("Optional locked product / main subject applied before launch so the AI cannot invent an " + "unrelated product."),
|
|
32813
32836
|
product_prompt: exports_external.string().optional().describe("Optional freeform brief to set/replace before launch."),
|
|
32814
|
-
max_credits: exports_external.number().int().nonnegative().optional().describe("Explicit spend cap. Omit to quote only; no credits are charged.")
|
|
32837
|
+
max_credits: exports_external.number().int().nonnegative().optional().describe("Explicit spend cap. Omit to quote only; no credits are charged."),
|
|
32838
|
+
restart: exports_external.boolean().optional().describe("Set true only on a completed project to quote/build a fresh creative run. The delivered timeline stays available until replacement apply.")
|
|
32815
32839
|
},
|
|
32816
32840
|
outputSchema: createEditorOutput,
|
|
32817
32841
|
annotations: { title: "Start autopilot", ...WRITE, idempotentHint: false }
|
|
@@ -32830,6 +32854,8 @@ registerTool("start_autopilot", {
|
|
|
32830
32854
|
overrides.product_subject = args.product_subject;
|
|
32831
32855
|
if (args.product_prompt !== undefined)
|
|
32832
32856
|
overrides.product_prompt = args.product_prompt;
|
|
32857
|
+
if (args.restart)
|
|
32858
|
+
overrides.restart = true;
|
|
32833
32859
|
const quote = await editorAutopilotQuote(client, slug, overrides);
|
|
32834
32860
|
const overCap = args.max_credits !== undefined && quote.estimated_credits !== null && quote.estimated_credits > args.max_credits;
|
|
32835
32861
|
if (args.max_credits === undefined || quote.estimated_credits === null || overCap || !quote.affordable) {
|
|
@@ -33110,7 +33136,10 @@ registerTool("regenerate_segment", {
|
|
|
33110
33136
|
}
|
|
33111
33137
|
}, tool(async (args, client) => {
|
|
33112
33138
|
const res = await client.post(`/editor/${args.slug}/segments/${String(args.segment_id)}/regenerate`, args.prompt !== undefined ? { prompt: args.prompt } : undefined, undefined);
|
|
33113
|
-
return ok(
|
|
33139
|
+
return ok({
|
|
33140
|
+
...asRecord(asRecord(res).data ?? res),
|
|
33141
|
+
charge_receipt: await editorChargeReceipt(client, args.slug, "scene_regeneration", 4)
|
|
33142
|
+
});
|
|
33114
33143
|
}));
|
|
33115
33144
|
registerTool("generate_all_segments", {
|
|
33116
33145
|
title: "Generate all pending segments (sequential)",
|
|
@@ -33227,7 +33256,10 @@ registerTool("generate_voice", {
|
|
|
33227
33256
|
annotations: { title: "Generate voice", ...WRITE, idempotentHint: true }
|
|
33228
33257
|
}, tool(async (args, client) => {
|
|
33229
33258
|
const res = await client.post(`/editor/${args.slug}/generate-voice`, { voice_id: args.voice_id }, await voiceKey(client, args.slug, args.voice_id));
|
|
33230
|
-
return ok(
|
|
33259
|
+
return ok({
|
|
33260
|
+
...asRecord(asRecord(res).data ?? res),
|
|
33261
|
+
charge_receipt: await editorChargeReceipt(client, args.slug, "voice_generation", 3)
|
|
33262
|
+
});
|
|
33231
33263
|
}));
|
|
33232
33264
|
registerTool("generate_music", {
|
|
33233
33265
|
title: "Generate the background music (5 credits)",
|
|
@@ -33254,11 +33286,14 @@ registerTool("generate_music", {
|
|
|
33254
33286
|
if (args.instruments !== undefined)
|
|
33255
33287
|
body.instruments = args.instruments;
|
|
33256
33288
|
const res = await client.post(`/editor/${args.slug}/generate-music`, body, undefined);
|
|
33257
|
-
return ok(
|
|
33289
|
+
return ok({
|
|
33290
|
+
...asRecord(asRecord(res).data ?? res),
|
|
33291
|
+
charge_receipt: await editorChargeReceipt(client, args.slug, "music_generation", 5)
|
|
33292
|
+
});
|
|
33258
33293
|
}));
|
|
33259
33294
|
registerTool("render", {
|
|
33260
33295
|
title: "Render the final editor video (0 credits)",
|
|
33261
|
-
description: "Renders the final MP4 from the generated segments + voice + music. Costs 0 credits (any still-ungenerated " + "segments are auto-charged at generation cost first). 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.",
|
|
33296
|
+
description: "Renders the final MP4 from the generated segments + voice + music. Costs 0 credits (any still-ungenerated " + "segments are auto-charged at generation cost first). 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.",
|
|
33262
33297
|
inputSchema: { slug: exports_external.string().describe("Editor project slug") },
|
|
33263
33298
|
annotations: { title: "Render", ...WRITE, idempotentHint: false }
|
|
33264
33299
|
}, tool(async (args, client) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubfluencer/mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
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
|
@@ -477,10 +477,11 @@ export function normalizeStatus(
|
|
|
477
477
|
renderStatus === "processing";
|
|
478
478
|
// Scenario text generation / scenario apply run as async workers too
|
|
479
479
|
// (scenario_status: none|generating|ready|failed; scenario_apply_status:
|
|
480
|
-
// idle|applying|failed) — count them as active so a wait right after
|
|
480
|
+
// idle|pending|applying|failed) — count them as active so a wait right after
|
|
481
481
|
// generate_scenario/apply_scenario doesn't bail out as an "idle" draft.
|
|
482
482
|
const scenarioActive =
|
|
483
483
|
d.scenario_status === "generating" ||
|
|
484
|
+
d.scenario_apply_status === "pending" ||
|
|
484
485
|
d.scenario_apply_status === "applying";
|
|
485
486
|
// Narration generation is a distinct async worker that runs before an
|
|
486
487
|
// AudioVersion exists. Without this gate, a project with an older completed
|
|
@@ -553,27 +554,27 @@ export function normalizeStatus(
|
|
|
553
554
|
narrationActive ||
|
|
554
555
|
audioActive ||
|
|
555
556
|
activeSegments.length > 0;
|
|
556
|
-
|
|
557
|
+
// Orchestration failures are historical once a repaired timeline has a fresh,
|
|
558
|
+
// completed MP4. Keeping them terminal would permanently block download_result
|
|
559
|
+
// after a successful granular recovery.
|
|
560
|
+
const deliveredArtifactSupersedesOrchestrationFailure =
|
|
557
561
|
renderStatus === "completed" &&
|
|
558
562
|
Boolean(videoUrl) &&
|
|
559
563
|
renderFresh &&
|
|
560
564
|
!stale &&
|
|
561
565
|
!workActive &&
|
|
562
566
|
failedSegments.length === 0 &&
|
|
563
|
-
!audioFailed
|
|
564
|
-
|
|
565
|
-
!scenarioApplyFailed &&
|
|
566
|
-
!narrationFailed &&
|
|
567
|
-
!batchFailed;
|
|
567
|
+
!audioFailed;
|
|
568
|
+
const ready = deliveredArtifactSupersedesOrchestrationFailure;
|
|
568
569
|
const autopilotFailed = autopilot === "failed" || autopilot === "cancelled";
|
|
569
570
|
const renderFailed = renderStatus === "failed";
|
|
570
571
|
const failed =
|
|
571
|
-
autopilotFailed ||
|
|
572
|
+
(autopilotFailed && !deliveredArtifactSupersedesOrchestrationFailure) ||
|
|
572
573
|
renderFailed ||
|
|
573
|
-
scenarioApplyFailed ||
|
|
574
|
-
scenarioFailed ||
|
|
575
|
-
narrationFailed ||
|
|
576
|
-
batchFailed ||
|
|
574
|
+
(scenarioApplyFailed && !deliveredArtifactSupersedesOrchestrationFailure) ||
|
|
575
|
+
(scenarioFailed && !deliveredArtifactSupersedesOrchestrationFailure) ||
|
|
576
|
+
(narrationFailed && !deliveredArtifactSupersedesOrchestrationFailure) ||
|
|
577
|
+
(batchFailed && !deliveredArtifactSupersedesOrchestrationFailure) ||
|
|
577
578
|
failedSegments.length > 0 ||
|
|
578
579
|
audioFailed;
|
|
579
580
|
// Terminal = control should return to the agent because nothing will progress
|
|
@@ -589,7 +590,8 @@ export function normalizeStatus(
|
|
|
589
590
|
// Failure always wins over an unrelated active flag. Workers can leave an
|
|
590
591
|
// outer autopilot/batch state active after a child stage has failed; reporting
|
|
591
592
|
// the active stage would make agents poll forever instead of taking action.
|
|
592
|
-
if (
|
|
593
|
+
if (ready) stage = "completed";
|
|
594
|
+
else if (autopilot === "failed") stage = "failed";
|
|
593
595
|
else if (autopilot === "cancelled") stage = "cancelled";
|
|
594
596
|
else if (renderFailed) stage = "render_failed";
|
|
595
597
|
else if (scenarioApplyFailed) stage = "scenario_apply_failed";
|
|
@@ -606,7 +608,6 @@ export function normalizeStatus(
|
|
|
606
608
|
else if (narrationActive) stage = "narration_processing";
|
|
607
609
|
else if (audioActive) stage = "audio_processing";
|
|
608
610
|
else if (!workActive && stale) stage = "editing_required";
|
|
609
|
-
else if (ready) stage = "completed";
|
|
610
611
|
const segmentError = failedSegments
|
|
611
612
|
.map((segment) => segment.error_message)
|
|
612
613
|
.find(
|
|
@@ -619,18 +620,19 @@ export function normalizeStatus(
|
|
|
619
620
|
terminal,
|
|
620
621
|
ready,
|
|
621
622
|
video_url: videoUrl,
|
|
622
|
-
error:
|
|
623
|
-
|
|
624
|
-
(
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
623
|
+
error: ready
|
|
624
|
+
? null
|
|
625
|
+
: ((d.autopilot_error_message as string) ??
|
|
626
|
+
(latest.error_message as string) ??
|
|
627
|
+
(d.scenario_apply_error_message as string) ??
|
|
628
|
+
(d.scenario_error_message as string) ??
|
|
629
|
+
(d.narration_error_message as string) ??
|
|
630
|
+
(d.batch_generation_error_message as string) ??
|
|
631
|
+
segmentError ??
|
|
632
|
+
audioError ??
|
|
633
|
+
(batchInsufficientCredits
|
|
634
|
+
? "Batch generation paused: not enough credits to finish. Top up or reduce scope, then resume."
|
|
635
|
+
: null)),
|
|
634
636
|
stale,
|
|
635
637
|
active_segment_ids: activeSegments
|
|
636
638
|
.map((segment) => segment.id)
|
package/src/index.ts
CHANGED
|
@@ -160,6 +160,7 @@ async function editorAutopilotQuote(
|
|
|
160
160
|
language?: string;
|
|
161
161
|
product_subject?: string;
|
|
162
162
|
product_prompt?: string;
|
|
163
|
+
restart?: boolean;
|
|
163
164
|
} = {},
|
|
164
165
|
): Promise<{
|
|
165
166
|
estimated_credits: number | null;
|
|
@@ -173,6 +174,7 @@ async function editorAutopilotQuote(
|
|
|
173
174
|
params.set("product_subject", overrides.product_subject);
|
|
174
175
|
if (overrides.product_prompt)
|
|
175
176
|
params.set("product_prompt", overrides.product_prompt);
|
|
177
|
+
if (overrides.restart) params.set("restart", "true");
|
|
176
178
|
const query = params.toString();
|
|
177
179
|
const response = await client.get<{ data: unknown }>(
|
|
178
180
|
`/editor/${slug}/autopilot/cost${query ? `?${query}` : ""}`,
|
|
@@ -215,6 +217,34 @@ async function editorAutopilotQuote(
|
|
|
215
217
|
}
|
|
216
218
|
}
|
|
217
219
|
|
|
220
|
+
async function editorChargeReceipt(
|
|
221
|
+
client: HubfluencerClient,
|
|
222
|
+
slug: string,
|
|
223
|
+
action: "scene_regeneration" | "voice_generation" | "music_generation",
|
|
224
|
+
listPriceCredits: number,
|
|
225
|
+
): Promise<Record<string, unknown>> {
|
|
226
|
+
try {
|
|
227
|
+
const response = await client.get<{ data: unknown }>(`/editor/${slug}`);
|
|
228
|
+
const editor = asRecord(asRecord(response).data);
|
|
229
|
+
return {
|
|
230
|
+
action,
|
|
231
|
+
list_price_credits: listPriceCredits,
|
|
232
|
+
project_spend: editor.project_spend ?? null,
|
|
233
|
+
approved_cap: editor.autopilot_max_credits ?? null,
|
|
234
|
+
approved_run_spend: editor.autopilot_credits_spent ?? null,
|
|
235
|
+
};
|
|
236
|
+
} catch {
|
|
237
|
+
// The paid mutation already succeeded. Never make a receipt-enrichment GET
|
|
238
|
+
// look like a failed charge that an agent should retry.
|
|
239
|
+
return {
|
|
240
|
+
action,
|
|
241
|
+
list_price_credits: listPriceCredits,
|
|
242
|
+
project_spend: null,
|
|
243
|
+
enrichment_unavailable: true,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
218
248
|
function isRecompositeInProgressConflict(e: unknown): boolean {
|
|
219
249
|
const err = e as Partial<HubfluencerError>;
|
|
220
250
|
if (err?.status !== 409) return false;
|
|
@@ -3278,7 +3308,7 @@ registerTool(
|
|
|
3278
3308
|
"explicit max_credits cap to authorize launch; the tool refuses if pricing is unavailable, the estimate exceeds " +
|
|
3279
3309
|
"the cap, or the balance is insufficient. Each authorized call is a genuine start attempt (like tapping Launch in the app): " +
|
|
3280
3310
|
"if a run is already in progress for this slug the server returns already_running — it never double-starts " +
|
|
3281
|
-
"or double-charges.
|
|
3311
|
+
"or double-charges. Failed or cancelled runs resume normally. For a completed project, set restart:true to quote and build a fresh creative run; omitting it only resumes pending edits. Then poll with " +
|
|
3282
3312
|
'wait_for_completion({ slug, kind: "editor" }).',
|
|
3283
3313
|
inputSchema: {
|
|
3284
3314
|
slug: z.string().describe("Editor project slug"),
|
|
@@ -3309,6 +3339,12 @@ registerTool(
|
|
|
3309
3339
|
.describe(
|
|
3310
3340
|
"Explicit spend cap. Omit to quote only; no credits are charged.",
|
|
3311
3341
|
),
|
|
3342
|
+
restart: z
|
|
3343
|
+
.boolean()
|
|
3344
|
+
.optional()
|
|
3345
|
+
.describe(
|
|
3346
|
+
"Set true only on a completed project to quote/build a fresh creative run. The delivered timeline stays available until replacement apply.",
|
|
3347
|
+
),
|
|
3312
3348
|
},
|
|
3313
3349
|
outputSchema: createEditorOutput,
|
|
3314
3350
|
annotations: { title: "Start autopilot", ...WRITE, idempotentHint: false },
|
|
@@ -3321,6 +3357,7 @@ registerTool(
|
|
|
3321
3357
|
product_subject?: string;
|
|
3322
3358
|
product_prompt?: string;
|
|
3323
3359
|
max_credits?: number;
|
|
3360
|
+
restart?: boolean;
|
|
3324
3361
|
},
|
|
3325
3362
|
client,
|
|
3326
3363
|
) => {
|
|
@@ -3334,12 +3371,14 @@ registerTool(
|
|
|
3334
3371
|
language?: string;
|
|
3335
3372
|
product_subject?: string;
|
|
3336
3373
|
product_prompt?: string;
|
|
3374
|
+
restart?: boolean;
|
|
3337
3375
|
} = {};
|
|
3338
3376
|
if (args.language !== undefined) overrides.language = args.language;
|
|
3339
3377
|
if (args.product_subject !== undefined)
|
|
3340
3378
|
overrides.product_subject = args.product_subject;
|
|
3341
3379
|
if (args.product_prompt !== undefined)
|
|
3342
3380
|
overrides.product_prompt = args.product_prompt;
|
|
3381
|
+
if (args.restart) overrides.restart = true;
|
|
3343
3382
|
|
|
3344
3383
|
const quote = await editorAutopilotQuote(client, slug, overrides);
|
|
3345
3384
|
const overCap =
|
|
@@ -4021,7 +4060,15 @@ registerTool(
|
|
|
4021
4060
|
args.prompt !== undefined ? { prompt: args.prompt } : undefined,
|
|
4022
4061
|
undefined,
|
|
4023
4062
|
);
|
|
4024
|
-
return ok(
|
|
4063
|
+
return ok({
|
|
4064
|
+
...asRecord(asRecord(res).data ?? res),
|
|
4065
|
+
charge_receipt: await editorChargeReceipt(
|
|
4066
|
+
client,
|
|
4067
|
+
args.slug,
|
|
4068
|
+
"scene_regeneration",
|
|
4069
|
+
4,
|
|
4070
|
+
),
|
|
4071
|
+
});
|
|
4025
4072
|
},
|
|
4026
4073
|
),
|
|
4027
4074
|
);
|
|
@@ -4230,7 +4277,15 @@ registerTool(
|
|
|
4230
4277
|
{ voice_id: args.voice_id },
|
|
4231
4278
|
await voiceKey(client, args.slug, args.voice_id),
|
|
4232
4279
|
);
|
|
4233
|
-
return ok(
|
|
4280
|
+
return ok({
|
|
4281
|
+
...asRecord(asRecord(res).data ?? res),
|
|
4282
|
+
charge_receipt: await editorChargeReceipt(
|
|
4283
|
+
client,
|
|
4284
|
+
args.slug,
|
|
4285
|
+
"voice_generation",
|
|
4286
|
+
3,
|
|
4287
|
+
),
|
|
4288
|
+
});
|
|
4234
4289
|
}),
|
|
4235
4290
|
);
|
|
4236
4291
|
|
|
@@ -4278,7 +4333,15 @@ registerTool(
|
|
|
4278
4333
|
body,
|
|
4279
4334
|
undefined,
|
|
4280
4335
|
);
|
|
4281
|
-
return ok(
|
|
4336
|
+
return ok({
|
|
4337
|
+
...asRecord(asRecord(res).data ?? res),
|
|
4338
|
+
charge_receipt: await editorChargeReceipt(
|
|
4339
|
+
client,
|
|
4340
|
+
args.slug,
|
|
4341
|
+
"music_generation",
|
|
4342
|
+
5,
|
|
4343
|
+
),
|
|
4344
|
+
});
|
|
4282
4345
|
},
|
|
4283
4346
|
),
|
|
4284
4347
|
);
|
|
@@ -4289,7 +4352,8 @@ registerTool(
|
|
|
4289
4352
|
title: "Render the final editor video (0 credits)",
|
|
4290
4353
|
description:
|
|
4291
4354
|
"Renders the final MP4 from the generated segments + voice + music. Costs 0 credits (any still-ungenerated " +
|
|
4292
|
-
"segments are auto-charged at generation cost first).
|
|
4355
|
+
"segments are auto-charged at generation cost first). Render NEVER generates or charges for voice/music; a new " +
|
|
4356
|
+
"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; " +
|
|
4293
4357
|
'pair with wait_for_completion({ slug, kind: "editor" }) then download_result. ' +
|
|
4294
4358
|
"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.",
|
|
4295
4359
|
inputSchema: { slug: z.string().describe("Editor project slug") },
|