@instavar/mcp-server 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -4
- package/dist/index.js +795 -212
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,10 +4,103 @@
|
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
|
|
7
|
-
// src/
|
|
7
|
+
// src/creative-guides.ts
|
|
8
8
|
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
// src/creative-guides.generated.ts
|
|
11
|
+
var CREATIVE_GUIDES = [
|
|
12
|
+
{
|
|
13
|
+
"id": "visual-copy",
|
|
14
|
+
"title": "Concise plain and poetic visual copy",
|
|
15
|
+
"uri": "instavar://creative-guides/visual-copy",
|
|
16
|
+
"text": "# Write concise text for visual stories\n\nStatus: practical guidance, adapted from documented writing methods and bounded human feedback. This is not a measured guarantee of comprehension or engagement.\n\nUse for carousel headlines, supporting text, captions and video scripts. Begin with plain language. Add poetic expression when the brief asks for an experience or mood; keep instructions, factual claims and interface states literal.\n\n## Start with the viewer and the evidence\n\nName the audience, what they already know, the one idea they should understand, and the action or feeling the piece invites. List supplied facts and necessary qualifications separately from imagined details. Inspect the accompanying image before describing it. If it is unavailable, ask for context or mark the visual description as a proposal.\n\nDecide what the image demonstrates and what the words must add. A caption can rely on an accompanying picture only when the intended viewer receives that picture. Provide a self-contained alternative description for accessibility and text-only reuse.\n\n## Expand, select, connect, compress\n\n1. Expand enough to understand the full thought. Find the concrete objects, actions, relationships, evidence and limits.\n2. Select what must survive. In an explanation, preserve the causal link, units, conditions and uncertainty. In poetic copy, preserve the scene, voice and details that carry the experience.\n3. Connect the ideas in the order a new viewer needs them. Each slide or scene should advance the explanation or experience.\n4. Compress by removing repeated meaning, vague labels and redundant explanations. Read it again against the source. Shorter wording must not make the claim stronger.\n\nTreat brevity as revision pressure, not a word-count target. If a condition matters to the decision, retain it close to that decision. A tiny footnote or a later slide may be missed when the current slide is shared alone.\n\n## Use poetic language selectively\n\nLet a concrete action or sensory detail suggest emotion before naming the emotion. Choose details that belong to this scene. Movement, posture, pressure, temperature, hesitation and small corrections can matter as much as sight or sound. Do not force every sense into a passage.\n\nMetaphor, personification and rhythm should serve one governing idea. Remove mixed metaphors and phrases that could describe any product. Leave room for interpretation in expressive copy, but do not leave factual instructions ambiguous. Do not invent an observed event, testimonial, feeling or product result. Sensuality is an optional requested mode, not a default meaning of poetic.\n\n## Draft examples\n\nThese are newly authored teaching examples, not approved campaign copy.\n\n| Purpose | Weak draft | More concrete draft | What must remain |\n| ------------------- | --------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------- |\n| Product instruction | Experience seamless creative transformation. | Choose a clip. Add your caption. Preview the result. | Name only actions the product actually supports. |\n| Quiet atmosphere | Discover the magic of a new morning. | The kettle clicks. Light reaches the table. | Use only supplied or explicitly imagined scene details. |\n| Evidence summary | This format makes everyone understand faster. | In this review, two readers found the revised chart easier to follow. | Counts, population and evidence source must be real. |\n\n## Deliver copy the creator can use\n\nReturn the title and caption separately. For a carousel or video, give one row per slide or scene: purpose, on-screen text, visual evidence, optional narration, and necessary qualification. Keep narration separate from on-screen copy so the screen need not repeat every spoken sentence.\n\nUse line breaks to shape meaningful groups. A single newline can keep short actions together; a blank line can mark a change of scene or thought. Follow the requested channel formatting. Neither spacing rule is universal.\n\nReview the actual render at the intended viewing size. Read the headline sequence alone, then the complete piece. Ask an unfamiliar viewer what happened, why, and what they would do next. A correct schema or short sentence does not establish reader understanding.\n\n## Reusable request\n\n\u201CUsing the supplied facts and visuals, draft concise copy for [audience] in [carousel/video format]. The viewer should understand [idea]. Use [plain/poetic] language. Give each scene a clear job, keep necessary conditions and evidence, and separate on-screen text from narration. Mark missing facts rather than inventing them. Return title, scene copy, caption and a short source-preservation check.\u201D\n\nSee the companion carousel, design and human-review guides. Source provenance and scope are recorded in the repository's September 2026 creative-playbook transfer report.\n"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "carousel-stories",
|
|
20
|
+
"title": "Carousel story structures and transfer limits",
|
|
21
|
+
"uri": "instavar://creative-guides/carousel-stories",
|
|
22
|
+
"text": "# Choose a carousel story structure\n\nStatus: planning recipes adapted from the Eclat carousel programme. Recipes are not newly ported renderers or proof of audience performance.\n\nChoose a structure for the relationship being explained. A topic is not a template: circular motion, collisions and half-life can share a questioning structure while needing different diagrams, conditions and notation.\n\n## Existing Instavar templates\n\nThe HTTP template MCP currently scaffolds `listicle`, `manifesto`, `before-after` and `value-first-reveal`. Use `list_carousel_templates` and `get_carousel_schema` for the executable contract. The npm connector's video tools are a separate catalogue; these recipes do not imply npm carousel rendering support.\n\n- Listicle: parallel points that belong together.\n- Manifesto: one idea per slide in a connected progression.\n- Before/after: a genuine two-state contrast. This builder uses the first and last point; do not put essential intermediate reasoning into points that will be omitted.\n- Value-first reveal: useful information followed by evidence and a restrained product reveal. Do not invent product proof to fill a slot.\n\n## Newer educational recipes\n\n| Recipe | Reading path | Use when | Preserve |\n| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------- |\n| Question, evidence, explanation, check | Ask a specific question; show the relevant objects; explain the relationship; compare a case; ask a final check with its answer | A plausible misconception can be resolved visually | The conditions that make the explanation true; a complete answer |\n| Observation to conclusion | Show an observation; separate it from inference; compare explanations; state what can be concluded | The viewer must reason from visible evidence | Uncertainty and alternative explanations |\n| Worked comparison | Establish objects and quantities; show the symbolic relationship; substitute values; compare outcomes; conclude | A calculation needs to stay connected to the physical situation | Units, signs, definitions and unchanged variables |\n| Action and correction | Show the setup; show an action; show a small correction; show the result | A practical action or sensory moment carries the story | Safe technique, sequence and a truthful visible result |\n\nThese can be drafted with one idea per slide using the manifesto skeleton, then reviewed and edited. Diagrams, equation blocks, editable placeholders and scientific apparatus require an appropriate construction layer; the text scaffold does not create or verify them. Never flatten a necessary diagram into decorative background artwork.\n\n## Lessons from the newer drafts\n\nKeep the relevant objects beside their equations so the viewer can connect symbols to the situation. State a useful symbolic relationship before numerical substitution when that helps the intended reader. Typeset fractions and symbols rather than using ambiguous slash text.\n\nGroup related words with their diagram. Give each calculation slide one main job. Remove duplicate results when the chart already carries them, but retain necessary assumptions. Make supporting text readable at delivery size. Recheck the whole deck after a local change.\n\nThe September 2026 Eclat batch included photoelectric threshold, circular motion, terminal velocity, sticking collision, wave amplitude, SHM turning points, standing waves, single-slit diffraction, field versus potential and half-life. A later six-deck revision applied earlier feedback to circular motion, SHM, standing waves, diffraction, field/potential and half-life. Those revised drafts were awaiting human review in the inspected record. This topic inventory is provenance, not a claim of ten new Instavar templates.\n\n## Delivery check\n\nReview full masters and every intended crop. A centred 4:5 crop of a 9:16 master was an Eclat production choice, not a universal platform requirement. Keep essential text, diagrams and qualifications inside the actual delivery region; verify current platform constraints separately before publication.\n\nTransfer the story method. Choose branding, type, texture, colour and imagery for the current creator. Eclat logos, source images, specialist answers and approval labels do not transfer automatically.\n"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "visual-design",
|
|
26
|
+
"title": "UI, graphic design and video principles",
|
|
27
|
+
"uri": "instavar://creative-guides/visual-design",
|
|
28
|
+
"text": "# Make meaning visible across slides, video and interfaces\n\nStatus: practical design and review guidance. Context-specific aesthetic and outcome claims still need audience evidence.\n\n## Give attention a useful path\n\nStart with the audience's task. Identify what should be noticed first, what it means, where attention moves next, and what evidence supports the conclusion. Use hierarchy to express that path. The first meaningful text should usually carry the main idea; a small label above it earns its place only when it changes interpretation.\n\nMatch form to relationship: sequence for steps, side-by-side comparison for alternatives, a diagram for a mechanism, and an explicit state change for an action. Do not force every relationship into the same card layout. Make the heading sequence tell a connected story while the full reading supplies proof and limits.\n\n## Graphic design\n\nUse proximity, alignment, spacing, scale, weight and contrast to show grouping and priority. Keep type and colour roles consistent. Test text against its actual background and at its final size; do not assume a colour token alone guarantees contrast. Keep non-colour cues for important distinctions.\n\nTypography and colour can suggest tone, but their meanings depend on audience and context. A serif does not prove quality and a particular hue does not guarantee trust. Choose an expressive treatment only when the core content remains legible.\n\nReview local details, the whole image and the collection separately. A sharper object or stronger accent may weaken overall harmony. Compare against the verified source image; a file labelled original can still be an edited derivative. Retaining the original is a valid outcome.\n\n## Video and motion\n\nPlan what changes in the viewer's understanding across time. Show the setup before an action that requires it. Keep spatial and causal continuity when the viewer must follow a procedure. Use cuts, movement and sound to reveal a relationship or shift attention, not simply to add activity.\n\nGive viewers enough time to read the actual text and inspect the evidence. Test at normal playback speed. Treat muted review, picture review, narration review and the combined sound mix as separate checks. Time visible contact and its sound together when synchrony matters. Keep necessary information available when audio is absent.\n\nDo not import film pacing into interactive controls blindly. A film controls its timeline; an interface must preserve user agency, orientation and recovery.\n\n## UI and editing workflows\n\nMake the current object, possible action, saved state and consequence clear. Keep frequent controls visible at the intended viewport. Distinguish selection, dragging and text editing. Provide undo and understandable conflict handling for editable work. A preview and its export should reflect the same saved document.\n\nThese are review questions, not claims that the Eclat prototype's editor has been integrated into Instavar. Prototype gestures, exact button layouts and crop choices require fresh implementation and testing in the receiving product.\n\n## Verify the artifact\n\nInspect final-size text, crops, diagram labels, symbols, contrast, grouping and the overall sequence. For interfaces, also exercise keyboard use, focus, narrow viewports, saving, errors and recovery. For video, review timing and sound in playback rather than relying only on contact sheets.\n\nAutomated checks establish their measured properties, such as dimensions, parsing or bounds. They do not establish human preference, comprehension or campaign performance. Ask a new viewer to explain the idea or perform the intended task before making those stronger claims.\n"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "human-review",
|
|
32
|
+
"title": "Human-guided copy, visual and media review",
|
|
33
|
+
"uri": "instavar://creative-guides/human-review",
|
|
34
|
+
"text": "# Preserve human feedback without inventing approval\n\nStatus: repository adaptation of the shared copy, UI/UX and media recording protocols. This guide is read-only; it does not store feedback or publish media.\n\nRecord a meaningful transition when a person reviews an identifiable draft, gives concrete feedback and a later revision responds. Preserve the exact draft and context, the feedback, the revision and the follow-up judgment. Do not treat every autonomous adjustment as human-guided work.\n\n## Keep one owning record\n\nUse an existing workstream report when available. Otherwise use `reports/copy/<workstream>/` for wording, `reports/ui-ux/` for layout and controls, or `reports/media/<workstream>/` for rendered picture, pacing, narration and sound. Link overlapping records instead of duplicating their histories. Keep old evidence at its existing paths.\n\nFor copy, preserve exact text, punctuation and line breaks. Record title and caption separately. For visual work, identify slide, route, state, viewport and before/after render. For media, also identify seconds or frames and playback conditions, including whether sound was on. If evidence is unavailable or a revision is source-only, say so.\n\n## Record template\n\n- Artifact and audience:\n- Before version, exact text or safe render reference:\n- Reviewed context, slide/range, viewport or playback conditions:\n- Human feedback, exact wording when safe:\n- Perceived problem and requested change:\n- Edit hypothesis:\n- Revision and after version:\n- Kept stable:\n- Follow-up judgment: not_judged / accepted_in_scope / rejected / mixed / superseded\n- Acceptance scope:\n- Publication: not_requested / requested / authorized / sent / verified, with evidence\n- Audience outcome: not_measured, or the actual observation and its limits\n- Source provenance, rights status and safe evidence references:\n- New issue and successor reference, if any:\n\nUse superseded only with a successor and keep the earlier judgment. A new complaint begins a new transition; chronology alone does not prove the previous change caused it.\n\n## What transfers\n\nKeep the problem-solving principle distinct from the particular remedy. \u201CThese calculation slides look cluttered\u201D can motivate grouping and removing repeated results. It does not justify removing assumptions from all science slides. A preference for one background, font size or line break applies to the reviewed context until further evidence supports transfer.\n\nApproval of a local detail is not approval of a whole image. Approval of a silent video does not approve the mix. Delivery is not approval, and approval is not evidence of audience impact. Silence remains not_judged.\n\nUse only public, owned or cleared examples in shared resources. Do not copy private screenshots, raw conversations, source photographs or unrelated account details to complete a record. A file hash identifies bytes; it does not establish rights, accuracy or quality. Recording feedback does not authorize publication or admission to a training or taste-critic corpus.\n"
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// src/creative-guides.ts
|
|
39
|
+
var creativeGuideIdSchema = z.enum([
|
|
40
|
+
"visual-copy",
|
|
41
|
+
"carousel-stories",
|
|
42
|
+
"visual-design",
|
|
43
|
+
"human-review"
|
|
44
|
+
]);
|
|
45
|
+
var CREATIVE_GUIDE_INDEX = CREATIVE_GUIDES.map(
|
|
46
|
+
({ id: id2, title, uri }) => ({
|
|
47
|
+
id: id2,
|
|
48
|
+
title,
|
|
49
|
+
uri
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
function registerCreativeGuides(server) {
|
|
53
|
+
for (const guide of CREATIVE_GUIDES) {
|
|
54
|
+
server.registerResource(
|
|
55
|
+
guide.id,
|
|
56
|
+
guide.uri,
|
|
57
|
+
{
|
|
58
|
+
title: guide.title,
|
|
59
|
+
description: guide.title,
|
|
60
|
+
mimeType: "text/markdown"
|
|
61
|
+
},
|
|
62
|
+
async () => ({
|
|
63
|
+
contents: [
|
|
64
|
+
{ uri: guide.uri, mimeType: "text/markdown", text: guide.text }
|
|
65
|
+
]
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
server.registerTool(
|
|
70
|
+
"get_creative_guide",
|
|
71
|
+
{
|
|
72
|
+
title: "Get a visual communication guide",
|
|
73
|
+
description: "Read practical guidance before drafting concise plain or poetic copy, choosing carousel stories, designing visuals or recording human feedback. Returns guidance for the calling assistant to apply, not generated copy or saved feedback. Omit guideId to list guides.",
|
|
74
|
+
inputSchema: { guideId: creativeGuideIdSchema.optional() },
|
|
75
|
+
annotations: {
|
|
76
|
+
readOnlyHint: true,
|
|
77
|
+
destructiveHint: false,
|
|
78
|
+
openWorldHint: false
|
|
79
|
+
},
|
|
80
|
+
_meta: { securitySchemes: [{ type: "noauth" }] }
|
|
81
|
+
},
|
|
82
|
+
async ({ guideId }) => {
|
|
83
|
+
const guide = CREATIVE_GUIDES.find((entry) => entry.id === guideId);
|
|
84
|
+
return {
|
|
85
|
+
structuredContent: guide ? { guide } : { guides: CREATIVE_GUIDE_INDEX },
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: guide ? guide.text : CREATIVE_GUIDE_INDEX.map(
|
|
90
|
+
(entry) => `${entry.id}: ${entry.title} (${entry.uri})`
|
|
91
|
+
).join("\n")
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/public-templates.ts
|
|
100
|
+
import { z as z2 } from "zod";
|
|
9
101
|
var TEMPLATE_REPOSITORY = "https://github.com/instavar/remotion-templates";
|
|
10
102
|
var TEMPLATE_ENDPOINT = "https://instavar.com/api/mcp/templates";
|
|
103
|
+
var VOICE_STUDIO_URL = "https://instavar.com/voice";
|
|
11
104
|
var templateIds = [
|
|
12
105
|
"qa-ad",
|
|
13
106
|
"proof-walkthrough",
|
|
@@ -15,8 +108,8 @@ var templateIds = [
|
|
|
15
108
|
"social-remix",
|
|
16
109
|
"finance-brief"
|
|
17
110
|
];
|
|
18
|
-
var templateIdSchema =
|
|
19
|
-
var aspectSchema =
|
|
111
|
+
var templateIdSchema = z2.enum(templateIds);
|
|
112
|
+
var aspectSchema = z2.enum(["9:16", "4:5", "1:1"]);
|
|
20
113
|
var templates = [
|
|
21
114
|
{
|
|
22
115
|
id: "qa-ad",
|
|
@@ -86,6 +179,42 @@ var videoSpecSchema = {
|
|
|
86
179
|
fixedDurationSec: { type: "number", exclusiveMinimum: 0 }
|
|
87
180
|
}
|
|
88
181
|
},
|
|
182
|
+
audio: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: {
|
|
185
|
+
mode: { enum: ["auto", "narration", "music", "silent"] },
|
|
186
|
+
narrationText: { type: "string" },
|
|
187
|
+
narrationSrc: { type: "string" },
|
|
188
|
+
narrationVolume: { type: "number", minimum: 0, maximum: 1 },
|
|
189
|
+
musicSrc: { type: "string" },
|
|
190
|
+
musicVolume: { type: "number", minimum: 0, maximum: 1 },
|
|
191
|
+
ducking: { type: "boolean" },
|
|
192
|
+
narrationTiming: {
|
|
193
|
+
type: "object",
|
|
194
|
+
required: ["schemaVersion", "source", "durationMs", "segments"],
|
|
195
|
+
properties: {
|
|
196
|
+
schemaVersion: { const: "1.0" },
|
|
197
|
+
source: { const: "segmented-synthesis" },
|
|
198
|
+
durationMs: { type: "integer", minimum: 1 },
|
|
199
|
+
segments: {
|
|
200
|
+
type: "array",
|
|
201
|
+
minItems: 1,
|
|
202
|
+
maxItems: 24,
|
|
203
|
+
items: {
|
|
204
|
+
type: "object",
|
|
205
|
+
required: ["sceneId", "startMs", "endMs", "durationMs"],
|
|
206
|
+
properties: {
|
|
207
|
+
sceneId: { type: "string", minLength: 1 },
|
|
208
|
+
startMs: { type: "integer", minimum: 0 },
|
|
209
|
+
endMs: { type: "integer", minimum: 1 },
|
|
210
|
+
durationMs: { type: "integer", minimum: 1 }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
},
|
|
89
218
|
scenes: {
|
|
90
219
|
type: "array",
|
|
91
220
|
minItems: 1,
|
|
@@ -97,6 +226,8 @@ var videoSpecSchema = {
|
|
|
97
226
|
kind: {
|
|
98
227
|
enum: [
|
|
99
228
|
"hero",
|
|
229
|
+
"statement",
|
|
230
|
+
"mechanism",
|
|
100
231
|
"points",
|
|
101
232
|
"equation",
|
|
102
233
|
"video-window",
|
|
@@ -108,6 +239,17 @@ var videoSpecSchema = {
|
|
|
108
239
|
timing: { type: "object" }
|
|
109
240
|
}
|
|
110
241
|
}
|
|
242
|
+
},
|
|
243
|
+
style: {
|
|
244
|
+
type: "object",
|
|
245
|
+
properties: {
|
|
246
|
+
theme: {
|
|
247
|
+
enum: ["default", "editorial-light", "editorial-dark", "clinical"]
|
|
248
|
+
},
|
|
249
|
+
variant: { type: "string" },
|
|
250
|
+
safeAreaProfile: { enum: ["baseline", "metaSafe"] },
|
|
251
|
+
showSceneLabels: { type: "boolean", default: false }
|
|
252
|
+
}
|
|
111
253
|
}
|
|
112
254
|
}
|
|
113
255
|
};
|
|
@@ -134,35 +276,77 @@ function scaffoldVideoSpec(args) {
|
|
|
134
276
|
content: {
|
|
135
277
|
title: args.title,
|
|
136
278
|
subtitle: "Replace with a concise promise"
|
|
137
|
-
}
|
|
279
|
+
},
|
|
280
|
+
timing: { durationMs: 4e3 }
|
|
138
281
|
}
|
|
139
282
|
];
|
|
140
283
|
if (args.templateId === "qa-ad" || args.templateId === "social-remix") {
|
|
141
284
|
scenes.push({
|
|
142
285
|
id: "source-video",
|
|
143
286
|
kind: "video-window",
|
|
144
|
-
content: {
|
|
287
|
+
content: {
|
|
288
|
+
title: "See it in context",
|
|
289
|
+
src: args.mediaSrc ?? "public/replace-with-your-video.mp4"
|
|
290
|
+
},
|
|
291
|
+
timing: { durationMs: 7e3 }
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if (args.templateId === "announcement-brief") {
|
|
295
|
+
scenes.push(
|
|
296
|
+
{
|
|
297
|
+
id: "context",
|
|
298
|
+
kind: "statement",
|
|
299
|
+
content: {
|
|
300
|
+
title: "Name the change",
|
|
301
|
+
subtitle: "Explain why it matters in one sentence"
|
|
302
|
+
},
|
|
303
|
+
timing: { durationMs: 6e3 }
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: "mechanism",
|
|
307
|
+
kind: "mechanism",
|
|
308
|
+
content: {
|
|
309
|
+
title: "Show how the change happens",
|
|
310
|
+
before: "Before",
|
|
311
|
+
after: "After"
|
|
312
|
+
},
|
|
313
|
+
timing: { durationMs: 7e3 }
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: "outcomes",
|
|
317
|
+
kind: "points",
|
|
318
|
+
content: {
|
|
319
|
+
title: "What changes",
|
|
320
|
+
points: ["First outcome", "Second outcome", "Third outcome"]
|
|
321
|
+
},
|
|
322
|
+
timing: { durationMs: 7e3 }
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
} else {
|
|
326
|
+
scenes.push({
|
|
327
|
+
id: args.templateId === "proof-walkthrough" ? "working" : "key-points",
|
|
328
|
+
kind: args.templateId === "proof-walkthrough" ? "equation" : "points",
|
|
329
|
+
content: args.templateId === "proof-walkthrough" ? {
|
|
330
|
+
title: "Work through the idea",
|
|
331
|
+
latexBlocks: ["x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}"]
|
|
332
|
+
} : {
|
|
333
|
+
title: "Key points",
|
|
334
|
+
points: [
|
|
335
|
+
"Replace with evidence",
|
|
336
|
+
"Replace with explanation",
|
|
337
|
+
"Replace with takeaway"
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
timing: { durationMs: 8e3 }
|
|
145
341
|
});
|
|
146
342
|
}
|
|
147
|
-
scenes.push({
|
|
148
|
-
id: args.templateId === "proof-walkthrough" ? "working" : "key-points",
|
|
149
|
-
kind: args.templateId === "proof-walkthrough" ? "equation" : "points",
|
|
150
|
-
content: args.templateId === "proof-walkthrough" ? {
|
|
151
|
-
title: "Work through the idea",
|
|
152
|
-
latexBlocks: ["x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}"]
|
|
153
|
-
} : {
|
|
154
|
-
title: "Key points",
|
|
155
|
-
points: [
|
|
156
|
-
"Replace with evidence",
|
|
157
|
-
"Replace with explanation",
|
|
158
|
-
"Replace with takeaway"
|
|
159
|
-
]
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
343
|
scenes.push({
|
|
163
344
|
id: "cta",
|
|
164
345
|
kind: "cta",
|
|
165
|
-
content: { line1: "Replace with the next action" }
|
|
346
|
+
content: { line1: "Replace with the next action" },
|
|
347
|
+
timing: {
|
|
348
|
+
durationMs: args.templateId === "announcement-brief" ? 6e3 : 5e3
|
|
349
|
+
}
|
|
166
350
|
});
|
|
167
351
|
return {
|
|
168
352
|
schemaVersion: "1.0",
|
|
@@ -171,16 +355,24 @@ function scaffoldVideoSpec(args) {
|
|
|
171
355
|
meta: { title: args.title, tags: [] },
|
|
172
356
|
target: { aspect: args.aspect, fps: 30, durationMode: "auto" },
|
|
173
357
|
style: {
|
|
174
|
-
theme: "default",
|
|
358
|
+
theme: args.templateId === "announcement-brief" ? "editorial-light" : "default",
|
|
175
359
|
variant: "default",
|
|
176
|
-
safeAreaProfile: "metaSafe"
|
|
360
|
+
safeAreaProfile: "metaSafe",
|
|
361
|
+
showSceneLabels: false
|
|
362
|
+
},
|
|
363
|
+
audio: {
|
|
364
|
+
mode: "silent",
|
|
365
|
+
narrationText: "",
|
|
366
|
+
narrationVolume: 1,
|
|
367
|
+
musicVolume: 0.16,
|
|
368
|
+
ducking: true
|
|
177
369
|
},
|
|
178
|
-
audio: { ducking: true },
|
|
179
370
|
assets: {},
|
|
180
371
|
scenes
|
|
181
372
|
};
|
|
182
373
|
}
|
|
183
374
|
function registerPublicTemplateTools(server) {
|
|
375
|
+
registerCreativeGuides(server);
|
|
184
376
|
server.registerTool(
|
|
185
377
|
"get_capabilities",
|
|
186
378
|
{
|
|
@@ -195,13 +387,23 @@ function registerPublicTemplateTools(server) {
|
|
|
195
387
|
apiKeyConfigured: Boolean(process.env.INSTAVAR_API_KEY?.trim()),
|
|
196
388
|
repository: TEMPLATE_REPOSITORY,
|
|
197
389
|
endpoint: TEMPLATE_ENDPOINT,
|
|
390
|
+
creativeGuides: CREATIVE_GUIDE_INDEX,
|
|
198
391
|
localFeatures: [
|
|
199
392
|
"template discovery",
|
|
200
393
|
"VideoSpec schema and examples",
|
|
201
394
|
"deterministic scaffolding",
|
|
202
395
|
"local render instructions",
|
|
203
|
-
"render error diagnosis"
|
|
396
|
+
"render error diagnosis",
|
|
397
|
+
"keyless on-device narration with scene timing",
|
|
398
|
+
"every-scene preview frames",
|
|
399
|
+
"post-render audible-audio verification"
|
|
204
400
|
],
|
|
401
|
+
narration: {
|
|
402
|
+
url: VOICE_STUDIO_URL,
|
|
403
|
+
cost: "free",
|
|
404
|
+
apiKeyRequired: false,
|
|
405
|
+
guidance: "Write one paragraph per scene. Download the WAV and timing manifest, then apply both before rendering."
|
|
406
|
+
},
|
|
205
407
|
accountFeatures: [
|
|
206
408
|
"managed rendering and storage",
|
|
207
409
|
"workflow review and approval",
|
|
@@ -284,9 +486,9 @@ ${template?.description ?? ""}`
|
|
|
284
486
|
description: "Return a deterministic VideoSpec skeleton without writing files.",
|
|
285
487
|
inputSchema: {
|
|
286
488
|
templateId: templateIdSchema,
|
|
287
|
-
title:
|
|
489
|
+
title: z2.string().min(1).max(160),
|
|
288
490
|
aspect: aspectSchema.default("9:16"),
|
|
289
|
-
mediaSrc:
|
|
491
|
+
mediaSrc: z2.string().min(1).max(500).optional()
|
|
290
492
|
},
|
|
291
493
|
annotations
|
|
292
494
|
},
|
|
@@ -304,7 +506,7 @@ ${template?.description ?? ""}`
|
|
|
304
506
|
title: "Get local render instructions",
|
|
305
507
|
description: "Return clone, install, validate, preview, and render commands.",
|
|
306
508
|
inputSchema: {
|
|
307
|
-
client:
|
|
509
|
+
client: z2.enum(["codex", "claude-code", "manual"]).default("manual")
|
|
308
510
|
},
|
|
309
511
|
annotations
|
|
310
512
|
},
|
|
@@ -313,9 +515,12 @@ ${template?.description ?? ""}`
|
|
|
313
515
|
`git clone ${TEMPLATE_REPOSITORY}.git`,
|
|
314
516
|
"cd remotion-templates",
|
|
315
517
|
"npm install",
|
|
518
|
+
`# Generate one narration paragraph per scene at ${VOICE_STUDIO_URL}`,
|
|
519
|
+
"npm run video:timing -- path/to/video.video.json --audio public/generated/narration.wav --manifest path/to/narration.timing.json",
|
|
316
520
|
"npm run video:validate -- path/to/video.video.json",
|
|
317
521
|
"npm run video:preview -- path/to/video.video.json",
|
|
318
|
-
"npm run video:render -- path/to/video.video.json"
|
|
522
|
+
"npm run video:render -- path/to/video.video.json",
|
|
523
|
+
"npm run video:qa -- path/to/video.video.json path/to/video.mp4"
|
|
319
524
|
];
|
|
320
525
|
return result(
|
|
321
526
|
{ repository: TEMPLATE_REPOSITORY, client, commands },
|
|
@@ -328,7 +533,7 @@ ${template?.description ?? ""}`
|
|
|
328
533
|
{
|
|
329
534
|
title: "Diagnose render error",
|
|
330
535
|
description: "Classify a local validation or render error.",
|
|
331
|
-
inputSchema: { errorText:
|
|
536
|
+
inputSchema: { errorText: z2.string().min(1).max(1e4) },
|
|
332
537
|
annotations
|
|
333
538
|
},
|
|
334
539
|
async ({ errorText }) => {
|
|
@@ -346,7 +551,7 @@ ${template?.description ?? ""}`
|
|
|
346
551
|
}
|
|
347
552
|
|
|
348
553
|
// src/tools.ts
|
|
349
|
-
import { z as
|
|
554
|
+
import { z as z5 } from "zod";
|
|
350
555
|
|
|
351
556
|
// src/http.ts
|
|
352
557
|
import { randomUUID } from "crypto";
|
|
@@ -470,7 +675,7 @@ async function runTool(fn) {
|
|
|
470
675
|
}
|
|
471
676
|
|
|
472
677
|
// src/brief-schema.ts
|
|
473
|
-
import { z as
|
|
678
|
+
import { z as z3 } from "zod";
|
|
474
679
|
var PUBLISH_TARGETS = [
|
|
475
680
|
"linkedin",
|
|
476
681
|
"x",
|
|
@@ -502,16 +707,16 @@ var REFERENCE_ROLES = [
|
|
|
502
707
|
"environment",
|
|
503
708
|
"style"
|
|
504
709
|
];
|
|
505
|
-
var optionalDirectionText =
|
|
506
|
-
var visualDirectionSchema =
|
|
710
|
+
var optionalDirectionText = z3.string().trim().min(1).max(500).optional();
|
|
711
|
+
var visualDirectionSchema = z3.object({
|
|
507
712
|
communicationJob: optionalDirectionText,
|
|
508
713
|
emotionalTarget: optionalDirectionText,
|
|
509
|
-
attentionPath:
|
|
714
|
+
attentionPath: z3.object({
|
|
510
715
|
first: optionalDirectionText,
|
|
511
716
|
next: optionalDirectionText,
|
|
512
717
|
proof: optionalDirectionText
|
|
513
718
|
}).optional(),
|
|
514
|
-
visualLanguage:
|
|
719
|
+
visualLanguage: z3.object({
|
|
515
720
|
space: optionalDirectionText,
|
|
516
721
|
line: optionalDirectionText,
|
|
517
722
|
shape: optionalDirectionText,
|
|
@@ -520,24 +725,24 @@ var visualDirectionSchema = z2.object({
|
|
|
520
725
|
movement: optionalDirectionText,
|
|
521
726
|
rhythm: optionalDirectionText
|
|
522
727
|
}).optional(),
|
|
523
|
-
camera:
|
|
728
|
+
camera: z3.object({
|
|
524
729
|
shotSize: optionalDirectionText,
|
|
525
730
|
angle: optionalDirectionText,
|
|
526
731
|
movement: optionalDirectionText,
|
|
527
732
|
lens: optionalDirectionText
|
|
528
733
|
}).optional()
|
|
529
734
|
});
|
|
530
|
-
var generationContractSchema =
|
|
531
|
-
globalInvariants:
|
|
532
|
-
shotDelta:
|
|
533
|
-
referenceBindings:
|
|
534
|
-
|
|
535
|
-
assetId:
|
|
536
|
-
roles:
|
|
735
|
+
var generationContractSchema = z3.object({
|
|
736
|
+
globalInvariants: z3.array(z3.string().trim().min(1).max(300)).max(20).optional(),
|
|
737
|
+
shotDelta: z3.string().trim().min(1).max(500).optional(),
|
|
738
|
+
referenceBindings: z3.array(
|
|
739
|
+
z3.object({
|
|
740
|
+
assetId: z3.string().trim().min(1).max(200),
|
|
741
|
+
roles: z3.array(z3.enum(REFERENCE_ROLES)).min(1).max(7)
|
|
537
742
|
})
|
|
538
743
|
).max(20).optional(),
|
|
539
|
-
prohibitedChanges:
|
|
540
|
-
reviewCriteria:
|
|
744
|
+
prohibitedChanges: z3.array(z3.string().trim().min(1).max(300)).max(20).optional(),
|
|
745
|
+
reviewCriteria: z3.array(z3.string().trim().min(1).max(300)).max(20).optional()
|
|
541
746
|
});
|
|
542
747
|
var REGION_NAMES = [
|
|
543
748
|
"background",
|
|
@@ -554,47 +759,92 @@ var ASSET_ROLES = [
|
|
|
554
759
|
"lower-third",
|
|
555
760
|
"diagram-node"
|
|
556
761
|
];
|
|
557
|
-
var assetSlotStyleSchema =
|
|
558
|
-
opacity:
|
|
559
|
-
fit:
|
|
560
|
-
z:
|
|
762
|
+
var assetSlotStyleSchema = z3.object({
|
|
763
|
+
opacity: z3.number().min(0).max(1).optional(),
|
|
764
|
+
fit: z3.enum(["cover", "contain"]).optional(),
|
|
765
|
+
z: z3.number().optional()
|
|
561
766
|
}).strict();
|
|
562
|
-
var assetSlotSchema =
|
|
563
|
-
id:
|
|
564
|
-
sceneId:
|
|
565
|
-
region:
|
|
566
|
-
role:
|
|
567
|
-
kind:
|
|
568
|
-
source:
|
|
569
|
-
intent:
|
|
767
|
+
var assetSlotSchema = z3.object({
|
|
768
|
+
id: z3.string().min(1),
|
|
769
|
+
sceneId: z3.string().min(1).optional(),
|
|
770
|
+
region: z3.enum(REGION_NAMES),
|
|
771
|
+
role: z3.enum(ASSET_ROLES),
|
|
772
|
+
kind: z3.enum(["image", "video"]),
|
|
773
|
+
source: z3.enum(["auto", "stock", "generate"]),
|
|
774
|
+
intent: z3.string(),
|
|
570
775
|
style: assetSlotStyleSchema.optional(),
|
|
571
|
-
timing:
|
|
572
|
-
fromPct:
|
|
573
|
-
toPct:
|
|
776
|
+
timing: z3.object({
|
|
777
|
+
fromPct: z3.number().min(0).max(1).optional(),
|
|
778
|
+
toPct: z3.number().min(0).max(1).optional()
|
|
574
779
|
}).strict().optional(),
|
|
575
|
-
url:
|
|
780
|
+
url: z3.string().url().optional()
|
|
576
781
|
}).strict();
|
|
577
|
-
var briefStepSchema =
|
|
578
|
-
label:
|
|
579
|
-
latex:
|
|
580
|
-
explanation:
|
|
782
|
+
var briefStepSchema = z3.object({
|
|
783
|
+
label: z3.string(),
|
|
784
|
+
latex: z3.string().optional(),
|
|
785
|
+
explanation: z3.string().optional()
|
|
786
|
+
});
|
|
787
|
+
var briefAudioLayerSchema = z3.object({
|
|
788
|
+
id: z3.string().min(1),
|
|
789
|
+
src: z3.string().url(),
|
|
790
|
+
startFrame: z3.number().int().nonnegative().default(0),
|
|
791
|
+
durationInFrames: z3.number().int().positive().optional(),
|
|
792
|
+
volume: z3.number().min(0).max(1).default(1),
|
|
793
|
+
fadeFrames: z3.number().int().nonnegative().default(0),
|
|
794
|
+
role: z3.enum(["music", "foley", "transition", "effect"])
|
|
581
795
|
});
|
|
582
|
-
var
|
|
583
|
-
id:
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
796
|
+
var protectedRegionEffectSchema = z3.object({
|
|
797
|
+
id: z3.string().min(1).max(80),
|
|
798
|
+
kind: z3.literal("border-glint"),
|
|
799
|
+
coordinateSpace: z3.literal("output-frame-v1").default("output-frame-v1"),
|
|
800
|
+
region: z3.object({
|
|
801
|
+
leftPercent: z3.number().min(0).max(100),
|
|
802
|
+
topPercent: z3.number().min(0).max(100),
|
|
803
|
+
widthPercent: z3.number().positive().max(100),
|
|
804
|
+
heightPercent: z3.number().positive().max(100)
|
|
805
|
+
}),
|
|
806
|
+
startFrame: z3.number().int().nonnegative().default(0),
|
|
807
|
+
endFrame: z3.number().int().positive(),
|
|
808
|
+
speedUpFrame: z3.number().int().nonnegative().optional(),
|
|
809
|
+
color: z3.string().max(64).default("#ffffff"),
|
|
810
|
+
thicknessPercent: z3.number().positive().max(20).default(3)
|
|
811
|
+
}).superRefine((effect2, context) => {
|
|
812
|
+
if (effect2.region.leftPercent + effect2.region.widthPercent > 100) {
|
|
813
|
+
context.addIssue({
|
|
814
|
+
code: "custom",
|
|
815
|
+
message: "Protected region exceeds the output frame width.",
|
|
816
|
+
path: ["region", "widthPercent"]
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
if (effect2.region.topPercent + effect2.region.heightPercent > 100) {
|
|
820
|
+
context.addIssue({
|
|
821
|
+
code: "custom",
|
|
822
|
+
message: "Protected region exceeds the output frame height.",
|
|
823
|
+
path: ["region", "heightPercent"]
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
if (effect2.endFrame <= effect2.startFrame) {
|
|
827
|
+
context.addIssue({
|
|
828
|
+
code: "custom",
|
|
829
|
+
message: "Effect endFrame must be after startFrame.",
|
|
830
|
+
path: ["endFrame"]
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
if (effect2.speedUpFrame !== void 0 && (effect2.speedUpFrame < effect2.startFrame || effect2.speedUpFrame >= effect2.endFrame)) {
|
|
834
|
+
context.addIssue({
|
|
835
|
+
code: "custom",
|
|
836
|
+
message: "speedUpFrame must fall inside the effect window.",
|
|
837
|
+
path: ["speedUpFrame"]
|
|
838
|
+
});
|
|
839
|
+
}
|
|
590
840
|
});
|
|
591
|
-
var beatVisualRequestSchema =
|
|
592
|
-
intent:
|
|
593
|
-
media:
|
|
594
|
-
source:
|
|
841
|
+
var beatVisualRequestSchema = z3.object({
|
|
842
|
+
intent: z3.string().trim().min(1).optional(),
|
|
843
|
+
media: z3.enum(["still", "motion"]).default("still"),
|
|
844
|
+
source: z3.enum(["auto", "stock", "generate"]).default("auto")
|
|
595
845
|
}).strict();
|
|
596
|
-
var briefSceneSchema =
|
|
597
|
-
type:
|
|
846
|
+
var briefSceneSchema = z3.object({
|
|
847
|
+
type: z3.enum([
|
|
598
848
|
"hero",
|
|
599
849
|
"points",
|
|
600
850
|
"equation",
|
|
@@ -617,154 +867,428 @@ var briefSceneSchema = z2.object({
|
|
|
617
867
|
"image-window",
|
|
618
868
|
"custom"
|
|
619
869
|
]),
|
|
620
|
-
durationInFrames:
|
|
621
|
-
narrationText:
|
|
870
|
+
durationInFrames: z3.number().optional(),
|
|
871
|
+
narrationText: z3.string().optional(),
|
|
622
872
|
visualResolution: beatVisualRequestSchema.optional(),
|
|
623
|
-
title:
|
|
624
|
-
subtitle:
|
|
625
|
-
text:
|
|
626
|
-
points:
|
|
627
|
-
latex:
|
|
628
|
-
value:
|
|
629
|
-
max:
|
|
630
|
-
label:
|
|
631
|
-
unit:
|
|
632
|
-
prefix:
|
|
633
|
-
suffix:
|
|
634
|
-
decimals:
|
|
635
|
-
color:
|
|
636
|
-
tone:
|
|
637
|
-
iconKey:
|
|
638
|
-
stepNumber:
|
|
639
|
-
totalSteps:
|
|
640
|
-
items:
|
|
641
|
-
src:
|
|
642
|
-
caption:
|
|
873
|
+
title: z3.string().optional(),
|
|
874
|
+
subtitle: z3.string().optional(),
|
|
875
|
+
text: z3.string().optional(),
|
|
876
|
+
points: z3.array(z3.string()).optional(),
|
|
877
|
+
latex: z3.string().optional(),
|
|
878
|
+
value: z3.union([z3.string(), z3.number()]).optional(),
|
|
879
|
+
max: z3.number().optional(),
|
|
880
|
+
label: z3.string().optional(),
|
|
881
|
+
unit: z3.string().optional(),
|
|
882
|
+
prefix: z3.string().optional(),
|
|
883
|
+
suffix: z3.string().optional(),
|
|
884
|
+
decimals: z3.number().optional(),
|
|
885
|
+
color: z3.string().optional(),
|
|
886
|
+
tone: z3.string().optional(),
|
|
887
|
+
iconKey: z3.string().optional(),
|
|
888
|
+
stepNumber: z3.number().optional(),
|
|
889
|
+
totalSteps: z3.number().optional(),
|
|
890
|
+
items: z3.array(z3.object({ label: z3.string(), value: z3.number() })).optional(),
|
|
891
|
+
src: z3.string().optional(),
|
|
892
|
+
caption: z3.string().optional(),
|
|
643
893
|
// A-roll generation (video-window scenes): source "generate" triggers
|
|
644
894
|
// flag-gated WaveSpeed image/video gen server-side from generatePrompt;
|
|
645
895
|
// "url" (default) uses src as-is. Kept in sync with the app's
|
|
646
896
|
// briefSceneSchema - see the parity test in the app suite.
|
|
647
|
-
source:
|
|
648
|
-
generatePrompt:
|
|
897
|
+
source: z3.enum(["url", "generate"]).optional(),
|
|
898
|
+
generatePrompt: z3.string().optional(),
|
|
649
899
|
generationContract: generationContractSchema.optional(),
|
|
650
|
-
trimStartSeconds:
|
|
651
|
-
trimEndSeconds:
|
|
652
|
-
playbackRate:
|
|
653
|
-
objectFit:
|
|
654
|
-
objectPosition:
|
|
655
|
-
scaleFrom:
|
|
656
|
-
scaleTo:
|
|
657
|
-
panFromX:
|
|
658
|
-
panToX:
|
|
659
|
-
panFromY:
|
|
660
|
-
panToY:
|
|
661
|
-
routeFrom:
|
|
662
|
-
routeTo:
|
|
663
|
-
routeLabel:
|
|
664
|
-
overlayKind:
|
|
665
|
-
overlayPosition:
|
|
666
|
-
accentColor:
|
|
667
|
-
overlayBackgroundColor:
|
|
668
|
-
cutFlash:
|
|
669
|
-
muted:
|
|
900
|
+
trimStartSeconds: z3.number().nonnegative().optional(),
|
|
901
|
+
trimEndSeconds: z3.number().positive().optional(),
|
|
902
|
+
playbackRate: z3.number().min(0.25).max(8).optional(),
|
|
903
|
+
objectFit: z3.enum(["cover", "contain"]).optional(),
|
|
904
|
+
objectPosition: z3.string().optional(),
|
|
905
|
+
scaleFrom: z3.number().min(0.5).max(2).optional(),
|
|
906
|
+
scaleTo: z3.number().min(0.5).max(2).optional(),
|
|
907
|
+
panFromX: z3.number().min(-50).max(50).optional(),
|
|
908
|
+
panToX: z3.number().min(-50).max(50).optional(),
|
|
909
|
+
panFromY: z3.number().min(-50).max(50).optional(),
|
|
910
|
+
panToY: z3.number().min(-50).max(50).optional(),
|
|
911
|
+
routeFrom: z3.string().optional(),
|
|
912
|
+
routeTo: z3.string().optional(),
|
|
913
|
+
routeLabel: z3.string().optional(),
|
|
914
|
+
overlayKind: z3.enum(["none", "hook", "stage", "end-card"]).optional(),
|
|
915
|
+
overlayPosition: z3.enum(["center", "top", "bottom"]).optional(),
|
|
916
|
+
accentColor: z3.string().optional(),
|
|
917
|
+
overlayBackgroundColor: z3.string().optional(),
|
|
918
|
+
cutFlash: z3.boolean().optional(),
|
|
919
|
+
muted: z3.boolean().optional(),
|
|
920
|
+
protectedRegionEffects: z3.array(protectedRegionEffectSchema).max(4).optional(),
|
|
921
|
+
flashAtFrame: z3.number().int().nonnegative().optional(),
|
|
922
|
+
flashDurationFrames: z3.number().int().positive().max(15).optional()
|
|
670
923
|
});
|
|
671
|
-
var directionsBriefSchema =
|
|
672
|
-
subtitle:
|
|
673
|
-
unitLabel:
|
|
674
|
-
distanceTimeChip:
|
|
675
|
-
clips:
|
|
676
|
-
|
|
677
|
-
src:
|
|
678
|
-
trimStartSeconds:
|
|
679
|
-
trimEndSeconds:
|
|
680
|
-
speed:
|
|
681
|
-
mirrorFlip:
|
|
924
|
+
var directionsBriefSchema = z3.object({
|
|
925
|
+
subtitle: z3.string().optional(),
|
|
926
|
+
unitLabel: z3.string().optional(),
|
|
927
|
+
distanceTimeChip: z3.string().optional(),
|
|
928
|
+
clips: z3.array(
|
|
929
|
+
z3.object({
|
|
930
|
+
src: z3.string(),
|
|
931
|
+
trimStartSeconds: z3.number().optional(),
|
|
932
|
+
trimEndSeconds: z3.number(),
|
|
933
|
+
speed: z3.number().optional(),
|
|
934
|
+
mirrorFlip: z3.boolean().optional()
|
|
682
935
|
})
|
|
683
936
|
).min(1),
|
|
684
|
-
cues:
|
|
685
|
-
|
|
686
|
-
atSeconds:
|
|
687
|
-
text:
|
|
688
|
-
arrow:
|
|
689
|
-
step:
|
|
937
|
+
cues: z3.array(
|
|
938
|
+
z3.object({
|
|
939
|
+
atSeconds: z3.number(),
|
|
940
|
+
text: z3.string(),
|
|
941
|
+
arrow: z3.enum(["left", "right", "forward"]).optional(),
|
|
942
|
+
step: z3.number().optional()
|
|
690
943
|
})
|
|
691
944
|
),
|
|
692
|
-
outro:
|
|
693
|
-
heading:
|
|
694
|
-
body:
|
|
695
|
-
seconds:
|
|
945
|
+
outro: z3.object({
|
|
946
|
+
heading: z3.string().optional(),
|
|
947
|
+
body: z3.array(z3.string()).optional(),
|
|
948
|
+
seconds: z3.number().optional()
|
|
696
949
|
}).optional(),
|
|
697
|
-
music:
|
|
698
|
-
perSegment:
|
|
699
|
-
|
|
700
|
-
src:
|
|
701
|
-
clipIndexStart:
|
|
702
|
-
hotOffsetSeconds:
|
|
703
|
-
volume:
|
|
950
|
+
music: z3.object({
|
|
951
|
+
perSegment: z3.array(
|
|
952
|
+
z3.object({
|
|
953
|
+
src: z3.string(),
|
|
954
|
+
clipIndexStart: z3.number().optional(),
|
|
955
|
+
hotOffsetSeconds: z3.number().optional(),
|
|
956
|
+
volume: z3.number().optional()
|
|
704
957
|
})
|
|
705
958
|
).optional(),
|
|
706
|
-
crossfadeSeconds:
|
|
959
|
+
crossfadeSeconds: z3.number().optional()
|
|
707
960
|
}).optional()
|
|
708
961
|
});
|
|
709
962
|
var createBriefShape = {
|
|
710
|
-
title:
|
|
711
|
-
script:
|
|
712
|
-
caption:
|
|
713
|
-
publishTarget:
|
|
714
|
-
|
|
715
|
-
|
|
963
|
+
title: z3.string().min(1).max(120).describe("Short title (max 120 chars)"),
|
|
964
|
+
script: z3.string().min(1).describe("Full narration script (sent to TTS)"),
|
|
965
|
+
caption: z3.string().min(1).describe("Social caption with hooks, hashtags, CTA"),
|
|
966
|
+
publishTarget: z3.enum(PUBLISH_TARGETS).describe("Target social platform"),
|
|
967
|
+
publishTargets: z3.array(z3.enum(PUBLISH_TARGETS)).max(9).optional().describe("All allowed social destinations for this video"),
|
|
968
|
+
objective: z3.enum(BRIEF_OBJECTIVES).describe("Content objective (drives composition routing)"),
|
|
969
|
+
targetDurationSeconds: z3.number().int().min(5).max(600).optional().describe("Requested final video duration in seconds"),
|
|
970
|
+
narrationMode: z3.enum(["tts", "off"]).optional().describe("Disable synthesized narration for source-audio or silent work"),
|
|
971
|
+
lessonTitle: z3.string().optional().describe("Display title rendered inside the video"),
|
|
716
972
|
visualDirection: visualDirectionSchema.optional().describe(
|
|
717
973
|
"Whole-video communication, attention, visual-language, and camera direction."
|
|
718
974
|
),
|
|
719
|
-
steps:
|
|
720
|
-
scenes:
|
|
975
|
+
steps: z3.array(briefStepSchema).optional().describe("Structured steps for educational content (LaTeX)"),
|
|
976
|
+
scenes: z3.array(briefSceneSchema).optional().describe("Scene graph for the UniversalVideo composition"),
|
|
721
977
|
// No zod .default() here: a default would materialize into the parsed tool
|
|
722
978
|
// arguments and relay to /api/v1/jobs as if the caller stated it, corrupting
|
|
723
979
|
// the resolution provenance the API echoes back. Unset must stay absent;
|
|
724
980
|
// the API re-applies 9:16 server-side.
|
|
725
|
-
aspectRatio:
|
|
726
|
-
compositionId:
|
|
727
|
-
voiceId:
|
|
981
|
+
aspectRatio: z3.enum(ASPECT_RATIOS).optional().describe("Aspect ratio (defaults to 9:16 when omitted)"),
|
|
982
|
+
compositionId: z3.string().optional().describe("Remotion composition ID override (advanced)"),
|
|
983
|
+
voiceId: z3.string().optional().describe(
|
|
728
984
|
"TTS narrator voice ID. Unknown IDs are rejected before rendering; when omitted, the org's saved voice preference or the system default is used and reported in the response's resolution block."
|
|
729
985
|
),
|
|
730
986
|
directions: directionsBriefSchema.optional().describe(
|
|
731
987
|
"DirectionsWalkthrough 'find-us' payload: source clips (legs), decision-point cues on the realtime walk timeline, outro, and optional music. Set compositionId 'DirectionsWalkthrough' (or objective 'directions')."
|
|
732
988
|
),
|
|
733
|
-
assetSlots:
|
|
989
|
+
assetSlots: z3.array(assetSlotSchema).optional().describe(
|
|
734
990
|
"Optional storyboard asset slots: region-placed image/video assets or placeholders attached to the whole video or a specific scene."
|
|
735
991
|
),
|
|
736
|
-
audioLayers:
|
|
992
|
+
audioLayers: z3.array(briefAudioLayerSchema).optional().describe("Optional typed music, Foley, transition, and effect layers.")
|
|
737
993
|
};
|
|
738
994
|
var editBriefShape = {
|
|
739
|
-
jobId:
|
|
740
|
-
title:
|
|
741
|
-
script:
|
|
742
|
-
caption:
|
|
743
|
-
lessonTitle:
|
|
995
|
+
jobId: z3.string().uuid().describe("The job to edit"),
|
|
996
|
+
title: z3.string().optional().describe("Updated title"),
|
|
997
|
+
script: z3.string().optional().describe("Updated narration script"),
|
|
998
|
+
caption: z3.string().optional().describe("Updated social caption"),
|
|
999
|
+
lessonTitle: z3.string().optional().describe("Updated lesson title"),
|
|
1000
|
+
narrationMode: z3.enum(["tts", "off"]).optional().describe("Updated synthesized narration mode"),
|
|
744
1001
|
visualDirection: visualDirectionSchema.optional().describe("Updated whole-video visual direction"),
|
|
745
|
-
steps:
|
|
746
|
-
scenes:
|
|
747
|
-
aspectRatio:
|
|
748
|
-
objective:
|
|
1002
|
+
steps: z3.array(briefStepSchema).optional().describe("Updated steps (replaces the array)"),
|
|
1003
|
+
scenes: z3.array(briefSceneSchema).optional().describe("Updated scenes (replaces the array)"),
|
|
1004
|
+
aspectRatio: z3.enum(ASPECT_RATIOS).optional().describe("Updated aspect ratio"),
|
|
1005
|
+
objective: z3.enum(BRIEF_OBJECTIVES).optional().describe(
|
|
749
1006
|
"Updated content objective. Re-routes the composition and triggers a re-render."
|
|
750
1007
|
),
|
|
751
|
-
publishTarget:
|
|
1008
|
+
publishTarget: z3.enum(PUBLISH_TARGETS).optional().describe(
|
|
752
1009
|
"Updated target social platform. Distribution metadata only - does not trigger a re-render."
|
|
753
1010
|
),
|
|
754
|
-
|
|
1011
|
+
publishTargets: z3.array(z3.enum(PUBLISH_TARGETS)).max(9).optional().describe("Replacement allowed social destination list"),
|
|
1012
|
+
targetDurationSeconds: z3.number().int().min(5).max(600).optional().describe("Updated final video duration in seconds"),
|
|
1013
|
+
voiceId: z3.string().optional().describe(
|
|
755
1014
|
"Updated TTS narrator voice ID. Triggers a re-render with the new voice."
|
|
756
1015
|
),
|
|
757
|
-
assetSlots:
|
|
1016
|
+
assetSlots: z3.array(assetSlotSchema).optional().describe(
|
|
758
1017
|
"Replacement storyboard asset slots. Omit to preserve existing slots; pass [] to clear them."
|
|
759
1018
|
),
|
|
760
|
-
audioLayers:
|
|
1019
|
+
audioLayers: z3.array(briefAudioLayerSchema).optional().describe(
|
|
761
1020
|
"Replacement typed audio layers. Omit to preserve existing layers; pass [] to clear them."
|
|
762
1021
|
)
|
|
763
1022
|
};
|
|
764
1023
|
|
|
1024
|
+
// src/render-diagnostics.ts
|
|
1025
|
+
function record(value) {
|
|
1026
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1027
|
+
}
|
|
1028
|
+
function rows(value) {
|
|
1029
|
+
return Array.isArray(value) ? value.map(record).filter((row) => !!row) : [];
|
|
1030
|
+
}
|
|
1031
|
+
function string(value) {
|
|
1032
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1033
|
+
}
|
|
1034
|
+
function number(value) {
|
|
1035
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1036
|
+
}
|
|
1037
|
+
function currentVideoArtifact(status) {
|
|
1038
|
+
return rows(status?.artifacts).find(
|
|
1039
|
+
(artifact) => artifact.artifactType === "video" && artifact.isCurrent === true
|
|
1040
|
+
) ?? null;
|
|
1041
|
+
}
|
|
1042
|
+
function verificationMatches(verifications, pattern) {
|
|
1043
|
+
return verifications.filter(
|
|
1044
|
+
(verification) => pattern.test(
|
|
1045
|
+
[
|
|
1046
|
+
verification.phase,
|
|
1047
|
+
verification.checkKey,
|
|
1048
|
+
verification.verifier,
|
|
1049
|
+
JSON.stringify(verification.detailsJson ?? null)
|
|
1050
|
+
].map((value) => String(value ?? "")).join(" ")
|
|
1051
|
+
)
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
function buildRenderDiagnosticSnapshot(params) {
|
|
1055
|
+
const failed = [params.jobStatus, params.videoState].find(
|
|
1056
|
+
(result2) => !result2.ok
|
|
1057
|
+
);
|
|
1058
|
+
if (failed) return failed;
|
|
1059
|
+
const status = record(params.jobStatus.body);
|
|
1060
|
+
const stateResponse = record(params.videoState.body);
|
|
1061
|
+
const state = record(stateResponse?.state);
|
|
1062
|
+
const runs = rows(status?.runs);
|
|
1063
|
+
const latestRunId = string(status?.latestRunId);
|
|
1064
|
+
const latestRun = runs.find((run) => string(run.id) === latestRunId) ?? runs[0] ?? null;
|
|
1065
|
+
const videoArtifact = currentVideoArtifact(status);
|
|
1066
|
+
const artifactMetadata = record(videoArtifact?.metadataJson);
|
|
1067
|
+
const verifications = rows(status?.verificationTimeline);
|
|
1068
|
+
const blankChecks = verificationMatches(verifications, /blank|black frame/i);
|
|
1069
|
+
const placeholderChecks = verificationMatches(
|
|
1070
|
+
verifications,
|
|
1071
|
+
/placeholder|source clip|source asset/i
|
|
1072
|
+
);
|
|
1073
|
+
const rightsChecks = verificationMatches(
|
|
1074
|
+
verifications,
|
|
1075
|
+
/rights|licen[cs]|clearance/i
|
|
1076
|
+
);
|
|
1077
|
+
const currentVideoArtifactId = string(videoArtifact?.id);
|
|
1078
|
+
const stateVideoArtifactId = string(state?.currentVideoArtifactId);
|
|
1079
|
+
const expectedDurationSeconds = number(state?.durationSeconds) ?? number(status?.durationSec);
|
|
1080
|
+
const actualDurationSeconds = number(videoArtifact?.durationSeconds) ?? number(artifactMetadata?.durationSeconds) ?? number(artifactMetadata?.durationSec) ?? number(artifactMetadata?.duration);
|
|
1081
|
+
return {
|
|
1082
|
+
ok: true,
|
|
1083
|
+
status: 200,
|
|
1084
|
+
body: {
|
|
1085
|
+
job: {
|
|
1086
|
+
id: string(status?.id),
|
|
1087
|
+
title: string(status?.title),
|
|
1088
|
+
status: string(status?.status),
|
|
1089
|
+
latestRunId
|
|
1090
|
+
},
|
|
1091
|
+
latestRun: latestRun ? {
|
|
1092
|
+
id: string(latestRun.id),
|
|
1093
|
+
runType: string(latestRun.runType),
|
|
1094
|
+
status: string(latestRun.status),
|
|
1095
|
+
errorCode: string(latestRun.errorCode),
|
|
1096
|
+
errorMessage: string(latestRun.errorMessage)
|
|
1097
|
+
} : null,
|
|
1098
|
+
artifactBinding: {
|
|
1099
|
+
jobCurrentVideoArtifactId: currentVideoArtifactId,
|
|
1100
|
+
videoStateCurrentArtifactId: stateVideoArtifactId,
|
|
1101
|
+
matches: currentVideoArtifactId && stateVideoArtifactId ? currentVideoArtifactId === stateVideoArtifactId : null,
|
|
1102
|
+
currentVideoUrl: string(status?.currentVideoUrl) ?? string(state?.currentVideoUrl)
|
|
1103
|
+
},
|
|
1104
|
+
duration: {
|
|
1105
|
+
expectedSeconds: expectedDurationSeconds,
|
|
1106
|
+
actualSeconds: actualDurationSeconds,
|
|
1107
|
+
deltaSeconds: expectedDurationSeconds !== null && actualDurationSeconds !== null ? Math.round(
|
|
1108
|
+
(actualDurationSeconds - expectedDurationSeconds) * 1e3
|
|
1109
|
+
) / 1e3 : null
|
|
1110
|
+
},
|
|
1111
|
+
retryRecommendations: Array.isArray(status?.retryRecommendations) ? status?.retryRecommendations : [],
|
|
1112
|
+
evidence: {
|
|
1113
|
+
rights: rightsChecks,
|
|
1114
|
+
blankFrames: blankChecks,
|
|
1115
|
+
placeholders: placeholderChecks,
|
|
1116
|
+
videoState: state
|
|
1117
|
+
},
|
|
1118
|
+
evidenceLimits: {
|
|
1119
|
+
visualContentInspection: blankChecks.length || placeholderChecks.length ? "verification_rows_available" : "not_evaluated",
|
|
1120
|
+
note: "No blank or placeholder verdict is inferred when the current verification timeline contains no matching check. Inspect the video artifact visually before deciding on an edit."
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/design-action-schema.ts
|
|
1127
|
+
import { z as z4 } from "zod";
|
|
1128
|
+
var id = z4.string().min(1).max(120);
|
|
1129
|
+
var point = z4.object({ x: z4.number().min(0).max(1), y: z4.number().min(0).max(1) }).strict();
|
|
1130
|
+
var polygonShape = z4.object({
|
|
1131
|
+
kind: z4.literal("polygon"),
|
|
1132
|
+
points: z4.array(point).min(3).max(512)
|
|
1133
|
+
}).strict();
|
|
1134
|
+
var rasterShape = z4.object({
|
|
1135
|
+
kind: z4.literal("raster"),
|
|
1136
|
+
source: z4.object({
|
|
1137
|
+
assetId: id,
|
|
1138
|
+
expectedSha256: z4.string().regex(/^[a-f0-9]{64}$/),
|
|
1139
|
+
sourceRange: z4.object({
|
|
1140
|
+
startFrame: z4.number().int().nonnegative(),
|
|
1141
|
+
endFrame: z4.number().int().positive()
|
|
1142
|
+
}).strict().optional()
|
|
1143
|
+
}).strict(),
|
|
1144
|
+
widthPx: z4.number().int().positive().max(8192),
|
|
1145
|
+
heightPx: z4.number().int().positive().max(8192)
|
|
1146
|
+
}).strict();
|
|
1147
|
+
var shape = z4.discriminatedUnion("kind", [polygonShape, rasterShape]);
|
|
1148
|
+
var mask = z4.object({
|
|
1149
|
+
id,
|
|
1150
|
+
shape,
|
|
1151
|
+
operation: z4.enum(["add", "subtract"]),
|
|
1152
|
+
inverted: z4.boolean().default(false),
|
|
1153
|
+
feather: z4.number().finite().nonnegative().max(0.25).default(0),
|
|
1154
|
+
allowedSupportMargin: z4.number().finite().nonnegative().max(0.25).default(0),
|
|
1155
|
+
reviewState: z4.enum(["draft", "approved", "rejected"]),
|
|
1156
|
+
provenance: z4.object({
|
|
1157
|
+
kind: z4.enum(["manual", "imported", "model-proposed"]),
|
|
1158
|
+
sourceAssetId: id.optional(),
|
|
1159
|
+
modelId: z4.string().min(1).max(160).optional()
|
|
1160
|
+
}).strict()
|
|
1161
|
+
}).strict();
|
|
1162
|
+
var transform = z4.object({
|
|
1163
|
+
x: z4.number().min(0).max(1),
|
|
1164
|
+
y: z4.number().min(0).max(1),
|
|
1165
|
+
width: z4.number().positive().max(1),
|
|
1166
|
+
height: z4.number().positive().max(1),
|
|
1167
|
+
rotationDegrees: z4.number().min(-360).max(360),
|
|
1168
|
+
opacity: z4.number().min(0).max(1)
|
|
1169
|
+
}).strict();
|
|
1170
|
+
var colourParameters = {
|
|
1171
|
+
exposure: z4.number().finite().min(-2).max(2).default(0),
|
|
1172
|
+
contrast: z4.number().finite().min(0.5).max(2).default(1),
|
|
1173
|
+
saturation: z4.number().finite().min(0).max(2).default(1),
|
|
1174
|
+
temperature: z4.number().finite().min(-1).max(1).default(0),
|
|
1175
|
+
tint: z4.number().finite().min(-1).max(1).default(0)
|
|
1176
|
+
};
|
|
1177
|
+
var effect = z4.object({
|
|
1178
|
+
id,
|
|
1179
|
+
kind: z4.literal("local-colour"),
|
|
1180
|
+
version: z4.literal(1),
|
|
1181
|
+
maskId: id,
|
|
1182
|
+
...colourParameters
|
|
1183
|
+
}).strict();
|
|
1184
|
+
var motionPath = z4.object({
|
|
1185
|
+
id,
|
|
1186
|
+
kind: z4.literal("cubic-bezier"),
|
|
1187
|
+
startFrame: z4.number().int().nonnegative(),
|
|
1188
|
+
endFrame: z4.number().int().positive(),
|
|
1189
|
+
start: point,
|
|
1190
|
+
control1: point,
|
|
1191
|
+
control2: point,
|
|
1192
|
+
end: point,
|
|
1193
|
+
easing: z4.enum(["linear", "ease-in", "ease-out", "ease-in-out"]),
|
|
1194
|
+
orientToPath: z4.boolean().default(false)
|
|
1195
|
+
}).strict();
|
|
1196
|
+
var region = z4.object({ id, shape, label: z4.string().min(1).max(160) }).strict();
|
|
1197
|
+
var base = { actionId: id, sceneId: id };
|
|
1198
|
+
var vendoredDesignActionSchema = z4.discriminatedUnion("kind", [
|
|
1199
|
+
z4.object({
|
|
1200
|
+
...base,
|
|
1201
|
+
kind: z4.literal("set-layer-transform"),
|
|
1202
|
+
layerId: id,
|
|
1203
|
+
transform
|
|
1204
|
+
}).strict(),
|
|
1205
|
+
z4.object({
|
|
1206
|
+
...base,
|
|
1207
|
+
kind: z4.literal("set-local-colour-effect"),
|
|
1208
|
+
effectId: id,
|
|
1209
|
+
parameters: z4.object(colourParameters).strict()
|
|
1210
|
+
}).strict(),
|
|
1211
|
+
z4.object({ ...base, kind: z4.literal("create-mask"), mask }).strict(),
|
|
1212
|
+
z4.object({ ...base, kind: z4.literal("set-mask"), maskId: id, mask }).strict(),
|
|
1213
|
+
z4.object({
|
|
1214
|
+
...base,
|
|
1215
|
+
kind: z4.literal("set-mask-review-state"),
|
|
1216
|
+
maskId: id,
|
|
1217
|
+
reviewState: z4.enum(["draft", "approved", "rejected"])
|
|
1218
|
+
}).strict(),
|
|
1219
|
+
z4.object({ ...base, kind: z4.literal("delete-mask"), maskId: id }).strict(),
|
|
1220
|
+
z4.object({
|
|
1221
|
+
...base,
|
|
1222
|
+
kind: z4.literal("attach-effect"),
|
|
1223
|
+
effect,
|
|
1224
|
+
layerIds: z4.array(id).min(1).max(50)
|
|
1225
|
+
}).strict(),
|
|
1226
|
+
z4.object({ ...base, kind: z4.literal("remove-effect"), effectId: id }).strict(),
|
|
1227
|
+
z4.object({
|
|
1228
|
+
...base,
|
|
1229
|
+
kind: z4.literal("set-layer-text"),
|
|
1230
|
+
layerId: id,
|
|
1231
|
+
text: z4.string().max(1e4)
|
|
1232
|
+
}).strict(),
|
|
1233
|
+
z4.object({
|
|
1234
|
+
...base,
|
|
1235
|
+
kind: z4.literal("set-layer-visibility"),
|
|
1236
|
+
layerId: id,
|
|
1237
|
+
visible: z4.boolean()
|
|
1238
|
+
}).strict(),
|
|
1239
|
+
z4.object({
|
|
1240
|
+
...base,
|
|
1241
|
+
kind: z4.literal("set-layer-locked"),
|
|
1242
|
+
layerId: id,
|
|
1243
|
+
locked: z4.boolean()
|
|
1244
|
+
}).strict(),
|
|
1245
|
+
z4.object({
|
|
1246
|
+
...base,
|
|
1247
|
+
kind: z4.literal("set-layer-mask-ids"),
|
|
1248
|
+
layerId: id,
|
|
1249
|
+
maskIds: z4.array(id).max(16)
|
|
1250
|
+
}).strict(),
|
|
1251
|
+
z4.object({
|
|
1252
|
+
...base,
|
|
1253
|
+
kind: z4.literal("set-layer-order"),
|
|
1254
|
+
orderedLayerIds: z4.array(id).min(1).max(500)
|
|
1255
|
+
}).strict(),
|
|
1256
|
+
z4.object({
|
|
1257
|
+
...base,
|
|
1258
|
+
kind: z4.literal("set-motion-path"),
|
|
1259
|
+
path: motionPath,
|
|
1260
|
+
layerIds: z4.array(id).max(50).default([])
|
|
1261
|
+
}).strict(),
|
|
1262
|
+
z4.object({ ...base, kind: z4.literal("remove-motion-path"), pathId: id }).strict(),
|
|
1263
|
+
z4.object({ ...base, kind: z4.literal("set-protected-region"), region }).strict(),
|
|
1264
|
+
z4.object({
|
|
1265
|
+
...base,
|
|
1266
|
+
kind: z4.literal("remove-protected-region"),
|
|
1267
|
+
regionId: id
|
|
1268
|
+
}).strict()
|
|
1269
|
+
]);
|
|
1270
|
+
var inspectDesignDocumentToolSchema = z4.object({
|
|
1271
|
+
projectId: z4.string().uuid(),
|
|
1272
|
+
documentId: z4.string().uuid(),
|
|
1273
|
+
includeVersions: z4.boolean().default(true)
|
|
1274
|
+
}).strict();
|
|
1275
|
+
var applyDesignActionsToolSchema = z4.object({
|
|
1276
|
+
projectId: z4.string().uuid(),
|
|
1277
|
+
documentId: z4.string().uuid(),
|
|
1278
|
+
actionGroupId: z4.string().min(1).max(120),
|
|
1279
|
+
expectedVersion: z4.number().int().positive(),
|
|
1280
|
+
actions: z4.array(vendoredDesignActionSchema).min(1).max(50),
|
|
1281
|
+
clientCreatedAt: z4.string().datetime()
|
|
1282
|
+
}).strict();
|
|
1283
|
+
|
|
765
1284
|
// src/tools.ts
|
|
766
|
-
var uuid = (desc) =>
|
|
767
|
-
var READ_ONLY = {
|
|
1285
|
+
var uuid = (desc) => z5.string().uuid().describe(desc);
|
|
1286
|
+
var READ_ONLY = {
|
|
1287
|
+
readOnlyHint: true,
|
|
1288
|
+
destructiveHint: false,
|
|
1289
|
+
idempotentHint: true,
|
|
1290
|
+
openWorldHint: false
|
|
1291
|
+
};
|
|
768
1292
|
var SAFE_WRITE = {
|
|
769
1293
|
readOnlyHint: false,
|
|
770
1294
|
destructiveHint: false,
|
|
@@ -778,8 +1302,8 @@ function registerTools(server) {
|
|
|
778
1302
|
title: "List jobs",
|
|
779
1303
|
description: "List recent video jobs for the current organization (newest first). Start here to discover job IDs; for one job's full detail use get_job_status.",
|
|
780
1304
|
inputSchema: {
|
|
781
|
-
limit:
|
|
782
|
-
status:
|
|
1305
|
+
limit: z5.number().int().min(1).max(50).default(10).describe("Number of jobs to return (default 10, max 50)"),
|
|
1306
|
+
status: z5.enum([
|
|
783
1307
|
"queued",
|
|
784
1308
|
"rendering",
|
|
785
1309
|
"draft_ready",
|
|
@@ -820,6 +1344,23 @@ function registerTools(server) {
|
|
|
820
1344
|
() => apiGet(`/api/v1/jobs/${encodeURIComponent(jobId)}/video-state`)
|
|
821
1345
|
)
|
|
822
1346
|
);
|
|
1347
|
+
server.registerTool(
|
|
1348
|
+
"diagnose_render",
|
|
1349
|
+
{
|
|
1350
|
+
title: "Diagnose render",
|
|
1351
|
+
description: "Build one read-only troubleshooting snapshot from the current job status and video state. Reports run failures, artifact binding, duration evidence, retry recommendations, rights checks, and whether blank-frame or placeholder verification evidence exists. It does not visually inspect the video.",
|
|
1352
|
+
inputSchema: { jobId: uuid("The job ID to diagnose") },
|
|
1353
|
+
annotations: READ_ONLY
|
|
1354
|
+
},
|
|
1355
|
+
async ({ jobId }) => runTool(async () => {
|
|
1356
|
+
const encodedJobId = encodeURIComponent(jobId);
|
|
1357
|
+
const [jobStatus, videoState] = await Promise.all([
|
|
1358
|
+
apiGet(`/api/v1/jobs/${encodedJobId}`),
|
|
1359
|
+
apiGet(`/api/v1/jobs/${encodedJobId}/video-state`)
|
|
1360
|
+
]);
|
|
1361
|
+
return buildRenderDiagnosticSnapshot({ jobStatus, videoState });
|
|
1362
|
+
})
|
|
1363
|
+
);
|
|
823
1364
|
server.registerTool(
|
|
824
1365
|
"get_job_metrics",
|
|
825
1366
|
{
|
|
@@ -827,7 +1368,7 @@ function registerTools(server) {
|
|
|
827
1368
|
description: "Get the latest platform engagement metric snapshots for a published job (newest first). Read-only. Only meaningful after publish_job; for render progress use get_job_status.",
|
|
828
1369
|
inputSchema: {
|
|
829
1370
|
jobId: uuid("The job ID"),
|
|
830
|
-
limit:
|
|
1371
|
+
limit: z5.number().int().min(1).max(20).default(10).describe("Max metric snapshots (default 10, max 20)")
|
|
831
1372
|
},
|
|
832
1373
|
annotations: READ_ONLY
|
|
833
1374
|
},
|
|
@@ -843,10 +1384,10 @@ function registerTools(server) {
|
|
|
843
1384
|
title: "Get cost summary",
|
|
844
1385
|
description: "Query production infrastructure costs (Lambda, RunPod, R2, WaveSpeed, PoYo), optionally with reconciliation drift. This is spend data, not engagement \u2014 for a published job's audience numbers use get_job_metrics.",
|
|
845
1386
|
inputSchema: {
|
|
846
|
-
since:
|
|
847
|
-
provider:
|
|
848
|
-
groupBy:
|
|
849
|
-
includeReconciliation:
|
|
1387
|
+
since: z5.string().optional().describe("ISO date \u2014 only include events after this date"),
|
|
1388
|
+
provider: z5.enum(["runpod", "lambda", "r2", "wavespeed", "poyo", "all"]).default("all").describe("Filter by provider"),
|
|
1389
|
+
groupBy: z5.enum(["provider", "job", "day"]).default("provider").describe("How to aggregate the summary"),
|
|
1390
|
+
includeReconciliation: z5.boolean().default(false).describe("Include account-wide reconciliation events")
|
|
850
1391
|
},
|
|
851
1392
|
annotations: READ_ONLY
|
|
852
1393
|
},
|
|
@@ -862,13 +1403,50 @@ function registerTools(server) {
|
|
|
862
1403
|
{
|
|
863
1404
|
title: "Inspect workflow",
|
|
864
1405
|
description: "Inspect the current Studio workflow and its version history for a job.",
|
|
865
|
-
inputSchema:
|
|
1406
|
+
inputSchema: z5.object({ jobId: uuid("The job ID") }).strict(),
|
|
866
1407
|
annotations: READ_ONLY
|
|
867
1408
|
},
|
|
868
1409
|
async ({ jobId }) => runTool(
|
|
869
1410
|
() => apiGet(`/api/v1/jobs/${encodeURIComponent(jobId)}/workflow`)
|
|
870
1411
|
)
|
|
871
1412
|
);
|
|
1413
|
+
server.registerTool(
|
|
1414
|
+
"inspect_design_document",
|
|
1415
|
+
{
|
|
1416
|
+
title: "Inspect design document",
|
|
1417
|
+
description: "Inspect one versioned design document and its recent immutable version history. This tool never changes the document.",
|
|
1418
|
+
inputSchema: inspectDesignDocumentToolSchema,
|
|
1419
|
+
annotations: READ_ONLY
|
|
1420
|
+
},
|
|
1421
|
+
async ({ projectId, documentId, includeVersions }) => {
|
|
1422
|
+
const query = new URLSearchParams({
|
|
1423
|
+
documentId,
|
|
1424
|
+
includeVersions: includeVersions ? "1" : "0"
|
|
1425
|
+
});
|
|
1426
|
+
return runTool(
|
|
1427
|
+
() => apiGet(
|
|
1428
|
+
`/api/v1/projects/${encodeURIComponent(
|
|
1429
|
+
projectId
|
|
1430
|
+
)}/design?${query.toString()}`
|
|
1431
|
+
)
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
);
|
|
1435
|
+
server.registerTool(
|
|
1436
|
+
"apply_design_actions",
|
|
1437
|
+
{
|
|
1438
|
+
title: "Apply design actions",
|
|
1439
|
+
description: "Apply a reviewed group of closed, invertible design actions to an expected document version. Inspect first. A stale version returns a readable conflict and does not overwrite customer work.",
|
|
1440
|
+
inputSchema: applyDesignActionsToolSchema,
|
|
1441
|
+
annotations: SAFE_WRITE
|
|
1442
|
+
},
|
|
1443
|
+
async (input) => runTool(
|
|
1444
|
+
() => apiPost(
|
|
1445
|
+
`/api/v1/projects/${encodeURIComponent(input.projectId)}/design`,
|
|
1446
|
+
input
|
|
1447
|
+
)
|
|
1448
|
+
)
|
|
1449
|
+
);
|
|
872
1450
|
server.registerTool(
|
|
873
1451
|
"create_video_brief",
|
|
874
1452
|
{
|
|
@@ -905,10 +1483,10 @@ function registerTools(server) {
|
|
|
905
1483
|
{
|
|
906
1484
|
title: "Draft workflow",
|
|
907
1485
|
description: "Create a durable Documentary Journey or Transformation Montage workflow version. Inspect first and pass its current version, or 0 only when no workflow exists.",
|
|
908
|
-
inputSchema:
|
|
1486
|
+
inputSchema: z5.object({
|
|
909
1487
|
jobId: uuid("The job ID"),
|
|
910
|
-
expectedVersion:
|
|
911
|
-
document:
|
|
1488
|
+
expectedVersion: z5.number().int().nonnegative().describe("Use 0 when no workflow exists yet"),
|
|
1489
|
+
document: z5.unknown().describe("The complete Studio workflow document")
|
|
912
1490
|
}).strict(),
|
|
913
1491
|
annotations: SAFE_WRITE
|
|
914
1492
|
},
|
|
@@ -919,10 +1497,10 @@ function registerTools(server) {
|
|
|
919
1497
|
{
|
|
920
1498
|
title: "Patch workflow",
|
|
921
1499
|
description: "Create a new workflow version by recursively merging a patch into the current version.",
|
|
922
|
-
inputSchema:
|
|
1500
|
+
inputSchema: z5.object({
|
|
923
1501
|
jobId: uuid("The job ID"),
|
|
924
|
-
expectedVersion:
|
|
925
|
-
patch:
|
|
1502
|
+
expectedVersion: z5.number().int().positive(),
|
|
1503
|
+
patch: z5.record(z5.string(), z5.unknown())
|
|
926
1504
|
}).strict(),
|
|
927
1505
|
annotations: SAFE_WRITE
|
|
928
1506
|
},
|
|
@@ -933,9 +1511,9 @@ function registerTools(server) {
|
|
|
933
1511
|
{
|
|
934
1512
|
title: "Validate workflow",
|
|
935
1513
|
description: "Validate and compile the inspected workflow version without rendering or publishing it.",
|
|
936
|
-
inputSchema:
|
|
1514
|
+
inputSchema: z5.object({
|
|
937
1515
|
jobId: uuid("The job ID"),
|
|
938
|
-
expectedVersion:
|
|
1516
|
+
expectedVersion: z5.number().int().positive()
|
|
939
1517
|
}).strict(),
|
|
940
1518
|
annotations: SAFE_WRITE
|
|
941
1519
|
},
|
|
@@ -946,7 +1524,7 @@ function registerTools(server) {
|
|
|
946
1524
|
{
|
|
947
1525
|
title: "Preview workflow",
|
|
948
1526
|
description: "Return the current workflow's compiled UniversalVideo brief for a safe pre-render preview.",
|
|
949
|
-
inputSchema:
|
|
1527
|
+
inputSchema: z5.object({ jobId: uuid("The job ID") }).strict(),
|
|
950
1528
|
annotations: READ_ONLY
|
|
951
1529
|
},
|
|
952
1530
|
async ({ jobId }) => runTool(
|
|
@@ -960,10 +1538,10 @@ function registerTools(server) {
|
|
|
960
1538
|
{
|
|
961
1539
|
title: "Compare workflow versions",
|
|
962
1540
|
description: "Compare two durable workflow versions field by field.",
|
|
963
|
-
inputSchema:
|
|
1541
|
+
inputSchema: z5.object({
|
|
964
1542
|
jobId: uuid("The job ID"),
|
|
965
|
-
fromVersion:
|
|
966
|
-
toVersion:
|
|
1543
|
+
fromVersion: z5.number().int().positive(),
|
|
1544
|
+
toVersion: z5.number().int().positive()
|
|
967
1545
|
}).strict(),
|
|
968
1546
|
annotations: READ_ONLY
|
|
969
1547
|
},
|
|
@@ -987,9 +1565,9 @@ function registerTools(server) {
|
|
|
987
1565
|
{
|
|
988
1566
|
title: "Approve workflow",
|
|
989
1567
|
description: "Validate and approve the inspected workflow version. This does not approve or publish the video job.",
|
|
990
|
-
inputSchema:
|
|
1568
|
+
inputSchema: z5.object({
|
|
991
1569
|
jobId: uuid("The job ID"),
|
|
992
|
-
expectedVersion:
|
|
1570
|
+
expectedVersion: z5.number().int().positive()
|
|
993
1571
|
}).strict(),
|
|
994
1572
|
annotations: {
|
|
995
1573
|
readOnlyHint: false,
|
|
@@ -1046,7 +1624,7 @@ function registerTools(server) {
|
|
|
1046
1624
|
inputSchema: {
|
|
1047
1625
|
jobId: uuid("The current job ID"),
|
|
1048
1626
|
sourceRunId: uuid("The current run ID"),
|
|
1049
|
-
trigger:
|
|
1627
|
+
trigger: z5.enum(["user_requested", "verifier_recommendation"])
|
|
1050
1628
|
},
|
|
1051
1629
|
annotations: SAFE_WRITE
|
|
1052
1630
|
},
|
|
@@ -1082,15 +1660,15 @@ function registerTools(server) {
|
|
|
1082
1660
|
description: "Publish an APPROVED job to its connected social destination via the gated publish API. All production gates run (paid plan, ownership, abuse, QA, disclosure, destination, rights, moderation). Requires a key with publish scope; the post goes live on the external platform and cannot be recalled by the API. For YouTube pass youtubePrivacyStatus:'private' for safe runs.",
|
|
1083
1661
|
inputSchema: {
|
|
1084
1662
|
jobId: uuid("The approved job to publish"),
|
|
1085
|
-
youtubePrivacyStatus:
|
|
1086
|
-
tiktokPostMode:
|
|
1087
|
-
connectedAccountDestinationId:
|
|
1663
|
+
youtubePrivacyStatus: z5.enum(["private", "unlisted", "public"]).optional().describe("YouTube only. Use 'private' for safe dogfood runs."),
|
|
1664
|
+
tiktokPostMode: z5.enum(["direct_publish", "draft_upload"]).optional().describe("TikTok only. Use 'draft_upload' for safe dogfood runs."),
|
|
1665
|
+
connectedAccountDestinationId: z5.string().uuid().optional().describe(
|
|
1088
1666
|
"Specific destination when the org has multiple accounts for the provider."
|
|
1089
1667
|
),
|
|
1090
|
-
confirmSyntheticDisclosure:
|
|
1668
|
+
confirmSyntheticDisclosure: z5.boolean().optional().describe(
|
|
1091
1669
|
"Set true when the job requires an AI synthetic-media disclosure."
|
|
1092
1670
|
),
|
|
1093
|
-
confirmQaOverride:
|
|
1671
|
+
confirmQaOverride: z5.boolean().optional().describe("Set true to override a non-passing QA gate.")
|
|
1094
1672
|
},
|
|
1095
1673
|
annotations: {
|
|
1096
1674
|
readOnlyHint: false,
|
|
@@ -1113,7 +1691,7 @@ function registerTools(server) {
|
|
|
1113
1691
|
title: "Connect social account",
|
|
1114
1692
|
description: "Start connecting a social account headlessly (no local browser needed). Returns an approve URL + a short confirm code: open the URL in a browser signed in to Instavar, check the code matches THIS terminal, and approve. Then poll connect_account_status with the returned pairingId until it reports 'connected'.",
|
|
1115
1693
|
inputSchema: {
|
|
1116
|
-
provider:
|
|
1694
|
+
provider: z5.enum([
|
|
1117
1695
|
"youtube",
|
|
1118
1696
|
"x",
|
|
1119
1697
|
"tiktok",
|
|
@@ -1162,7 +1740,7 @@ function registerConfiguredTools(server, apiKey = process.env.INSTAVAR_API_KEY)
|
|
|
1162
1740
|
// package.json
|
|
1163
1741
|
var package_default = {
|
|
1164
1742
|
name: "@instavar/mcp-server",
|
|
1165
|
-
version: "0.
|
|
1743
|
+
version: "0.5.0",
|
|
1166
1744
|
mcpName: "com.instavar/mcp-server",
|
|
1167
1745
|
description: "Make local videos with Instavar Remotion templates for free, then add an API key for hosted Studio workflows.",
|
|
1168
1746
|
type: "module",
|
|
@@ -1236,7 +1814,12 @@ async function main() {
|
|
|
1236
1814
|
process.stdout.write(HELP);
|
|
1237
1815
|
return;
|
|
1238
1816
|
}
|
|
1239
|
-
const server = new McpServer(
|
|
1817
|
+
const server = new McpServer(
|
|
1818
|
+
{ name: "instavar", version: VERSION },
|
|
1819
|
+
{
|
|
1820
|
+
instructions: "Before drafting concise carousel or video copy, call get_creative_guide for visual-copy. Use the carousel-stories, visual-design and human-review guides as relevant. Apply them to the user brief; the guides do not generate, render, save or publish media."
|
|
1821
|
+
}
|
|
1822
|
+
);
|
|
1240
1823
|
registerConfiguredTools(server);
|
|
1241
1824
|
await server.connect(new StdioServerTransport());
|
|
1242
1825
|
}
|