@pixeldojo/mcp 0.16.2 → 0.17.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/LICENSE +21 -0
- package/README.md +3 -3
- package/dist/cli.cjs +146 -38
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +146 -38
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +146 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +146 -38
- package/dist/index.js.map +1 -1
- package/package.json +19 -3
package/dist/index.js
CHANGED
|
@@ -1453,6 +1453,39 @@ async function filmApi(ctx, method, path, body) {
|
|
|
1453
1453
|
const data = await res.json().catch(() => ({}));
|
|
1454
1454
|
return { status: res.status, data };
|
|
1455
1455
|
}
|
|
1456
|
+
async function applyCast(ctx, filmId, cast) {
|
|
1457
|
+
let charged = false;
|
|
1458
|
+
const lines = await Promise.all(cast.map(async (c) => {
|
|
1459
|
+
const r = await filmApi(
|
|
1460
|
+
ctx,
|
|
1461
|
+
"POST",
|
|
1462
|
+
`/api/films/${filmId}/cast`,
|
|
1463
|
+
{
|
|
1464
|
+
role: c.name,
|
|
1465
|
+
...c.character_id ? { characterId: c.character_id } : {},
|
|
1466
|
+
...c.image_urls ? { imageUrls: c.image_urls } : {},
|
|
1467
|
+
...c.prompt ? { prompt: c.prompt } : {}
|
|
1468
|
+
}
|
|
1469
|
+
);
|
|
1470
|
+
if (r.status === 200 && r.data.creditCost) charged = true;
|
|
1471
|
+
if (r.status !== 200) return `Could not cast ${c.name}: ${r.data.error ?? r.status}`;
|
|
1472
|
+
if (r.data.alreadyCast) return `${c.name} was already cast; nothing changed.`;
|
|
1473
|
+
return `Cast ${c.name}${r.data.creditCost ? ` (portrait, ${r.data.creditCost} credits)` : ""}${r.data.boundShots ? `, in ${r.data.boundShots} shot(s)` : ""}.`;
|
|
1474
|
+
}));
|
|
1475
|
+
return { lines, charged };
|
|
1476
|
+
}
|
|
1477
|
+
async function uncastRolesOf(ctx, filmId) {
|
|
1478
|
+
const r = await filmApi(ctx, "GET", `/api/films/${filmId}/cast`);
|
|
1479
|
+
return r.data.uncastRoles ?? [];
|
|
1480
|
+
}
|
|
1481
|
+
function castNeededText(filmId, roles) {
|
|
1482
|
+
const example = roles.map((r) => ({ name: r.name, prompt: r.suggestedPrompt || `describe ${r.name}` }));
|
|
1483
|
+
return [
|
|
1484
|
+
`Cast these characters before production (every named character needs a cast member so faces, ages and species hold in every shot):`,
|
|
1485
|
+
...roles.map((r) => ` ${r.name}${r.suggestedPrompt ? `: ${r.suggestedPrompt}` : ""}`),
|
|
1486
|
+
`Call pixeldojo_film with {"film_id": "${filmId}", "action": "cast", "cast": ${JSON.stringify(example)}}, giving each one a prompt (a portrait is generated, charged), image_urls of your own photos, or a character_id from Character Studio.`
|
|
1487
|
+
].join("\n");
|
|
1488
|
+
}
|
|
1456
1489
|
function progressLine(f) {
|
|
1457
1490
|
const all = shots(f);
|
|
1458
1491
|
const frames = all.filter((sh) => ["frame-ready", "approved", "clip-generating", "clip-ready"].includes(sh.status) || sh.frameUrl).length;
|
|
@@ -1526,32 +1559,44 @@ async function plan(input, ctx) {
|
|
|
1526
1559
|
});
|
|
1527
1560
|
if (!created.data.film) return { kind: "text", text: `Could not create the film: ${created.data.error ?? created.status}` };
|
|
1528
1561
|
const id = created.data.film.id;
|
|
1562
|
+
const castStarted = Date.now();
|
|
1563
|
+
const cast = input.cast?.length ? await applyCast(ctx, id, input.cast) : { lines: [], charged: false };
|
|
1564
|
+
const pollBudget = Math.max(0, PLANNER_MAX_POLLS - Math.ceil((Date.now() - castStarted) / PLANNER_POLL_MS));
|
|
1565
|
+
const reply = (text) => ({
|
|
1566
|
+
kind: "text",
|
|
1567
|
+
text: [
|
|
1568
|
+
...cast.lines,
|
|
1569
|
+
cast.charged ? text.replace(/Nothing has been charged( yet)?\./g, "Only the cast portraits above have been charged.") : text
|
|
1570
|
+
].join("\n")
|
|
1571
|
+
});
|
|
1529
1572
|
let planned;
|
|
1530
1573
|
if (music) {
|
|
1531
1574
|
const brief = [input.idea?.trim() || input.script.trim(), input.lyrics?.trim() ? `Lyrics:
|
|
1532
1575
|
${input.lyrics.trim()}` : ""].filter(Boolean).join("\n\n");
|
|
1533
|
-
planned = await runPlanner(ctx, id, `/api/films/${id}/plan`, { script: brief });
|
|
1576
|
+
planned = await runPlanner(ctx, id, `/api/films/${id}/plan`, { script: brief }, pollBudget);
|
|
1534
1577
|
} else {
|
|
1535
1578
|
let script = input.script?.trim() ?? "";
|
|
1536
1579
|
if (!script) {
|
|
1537
|
-
const wrote = await runPlanner(ctx, id, `/api/films/${id}/script`, { idea: input.idea.trim() });
|
|
1538
|
-
if ("pending" in wrote) return
|
|
1539
|
-
if ("error" in wrote) return
|
|
1580
|
+
const wrote = await runPlanner(ctx, id, `/api/films/${id}/script`, { idea: input.idea.trim() }, pollBudget);
|
|
1581
|
+
if ("pending" in wrote) return reply(stillPlanningText(id, "script"));
|
|
1582
|
+
if ("error" in wrote) return reply(`Script writing failed: ${wrote.error}`);
|
|
1540
1583
|
script = wrote.film.script ?? "";
|
|
1541
|
-
if (!script) return
|
|
1584
|
+
if (!script) return reply("Script writing failed: the studio returned an empty script.");
|
|
1542
1585
|
}
|
|
1543
1586
|
planned = await runPlanner(ctx, id, `/api/films/${id}/plan`, { script });
|
|
1544
1587
|
}
|
|
1545
|
-
if ("pending" in planned) return
|
|
1546
|
-
if ("error" in planned) return
|
|
1588
|
+
if ("pending" in planned) return reply(stillPlanningText(id));
|
|
1589
|
+
if ("error" in planned) return reply(`Shot planning failed: ${planned.error}`);
|
|
1547
1590
|
const film = planned.film;
|
|
1548
1591
|
if (needsSlice(film)) {
|
|
1549
1592
|
const err = await sliceSong(ctx, film);
|
|
1550
|
-
if (err) return
|
|
1593
|
+
if (err) return reply(`${plannedText(film, ctx)}
|
|
1551
1594
|
|
|
1552
|
-
${err} Call action "status" to retry the cut.`
|
|
1595
|
+
${err} Call action "status" to retry the cut.`);
|
|
1553
1596
|
}
|
|
1554
|
-
|
|
1597
|
+
const uncast = await uncastRolesOf(ctx, id);
|
|
1598
|
+
return reply([plannedText(film, ctx), uncast.length ? `
|
|
1599
|
+
${castNeededText(id, uncast)}` : ""].filter(Boolean).join("\n"));
|
|
1555
1600
|
}
|
|
1556
1601
|
async function resumePlanning(film, ctx) {
|
|
1557
1602
|
if (film.scenes.length > 0 && film.llmJob?.status !== "running") return null;
|
|
@@ -1578,6 +1623,8 @@ async function produce(filmId, ctx) {
|
|
|
1578
1623
|
if (!data.film) return { kind: "text", text: `Film not found: ${data.error ?? filmId}` };
|
|
1579
1624
|
const planning = await resumePlanning(data.film, ctx);
|
|
1580
1625
|
if (planning) return planning;
|
|
1626
|
+
const uncast = await uncastRolesOf(ctx, filmId);
|
|
1627
|
+
if (uncast.length) return { kind: "text", text: `Nothing was charged. ${castNeededText(filmId, uncast)}` };
|
|
1581
1628
|
const pending = shots(data.film).filter((sh) => sh.status === "idle" || sh.status === "failed" && !sh.frameUrl);
|
|
1582
1629
|
let fired = 0;
|
|
1583
1630
|
for (const sh of pending.slice(0, 6)) {
|
|
@@ -1651,19 +1698,27 @@ It is saved in the Library folder "Film: ${film.title}".`
|
|
|
1651
1698
|
if (shots(film).length > 0 && shots(film).every((sh) => sh.status === "idle")) {
|
|
1652
1699
|
return { kind: "text", text: plannedText(film, ctx) };
|
|
1653
1700
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1701
|
+
let started = 0;
|
|
1702
|
+
const resent = /* @__PURE__ */ new Set();
|
|
1703
|
+
const dispatch = async (sh, kind) => {
|
|
1704
|
+
const r = await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind });
|
|
1705
|
+
if (r.status !== 200) return;
|
|
1706
|
+
started += 1;
|
|
1707
|
+
if (sh.status === "failed") resent.add(sh.id);
|
|
1708
|
+
};
|
|
1709
|
+
const pendingFrames = all.filter((sh) => sh.status === "idle" || sh.status === "failed" && !sh.frameUrl && !isRefused(sh));
|
|
1710
|
+
if (pendingFrames.length > 0) {
|
|
1711
|
+
const uncast = await uncastRolesOf(ctx, filmId);
|
|
1712
|
+
if (uncast.length) return { kind: "text", text: castNeededText(filmId, uncast) };
|
|
1713
|
+
}
|
|
1714
|
+
for (const sh of pendingFrames.slice(0, 6)) await dispatch(sh, "frame");
|
|
1715
|
+
const framesDone = all.every(frameDone);
|
|
1716
|
+
if (all.some((sh) => frameDone(sh) && needsSave(sh.frameUrl))) {
|
|
1660
1717
|
await filmApi(ctx, "POST", `/api/films/${filmId}/save`, { kind: "frame" });
|
|
1661
1718
|
}
|
|
1662
1719
|
const inFlight = count(film, (sh) => sh.status === "clip-generating");
|
|
1663
|
-
const clipQueue = all.filter((sh) =>
|
|
1664
|
-
for (const sh of clipQueue.slice(0, Math.max(0, 4 - inFlight)))
|
|
1665
|
-
await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "clip" });
|
|
1666
|
-
}
|
|
1720
|
+
const clipQueue = framesDone ? all.filter((sh) => sh.status === "frame-ready" || sh.status === "failed" && frameDone(sh) && !isRefused(sh)) : [];
|
|
1721
|
+
for (const sh of clipQueue.slice(0, Math.max(0, 4 - inFlight))) await dispatch(sh, "clip");
|
|
1667
1722
|
const clipsDone = all.length > 0 && all.every((sh) => sh.status === "clip-ready");
|
|
1668
1723
|
if (clipsDone) {
|
|
1669
1724
|
if (all.some((sh) => needsSave(sh.videoUrl))) {
|
|
@@ -1686,30 +1741,53 @@ It is saved in the Library folder "Film: ${film.title}".`
|
|
|
1686
1741
|
return { kind: "text", text: `Score still composing. Poll again in ~20s with {"film_id": "${filmId}", "action": "status", "music_job_id": "${musicJobId}"}.` };
|
|
1687
1742
|
}
|
|
1688
1743
|
const body = soundtrack === "scene-synced" ? buildScorePlanBody(film) : buildFlatMusicBody(film, all);
|
|
1689
|
-
const
|
|
1690
|
-
return { kind: "text", text: `All ${all.length} clips ready \u2014 the ${soundtrack} score is composing (charged like any music generation). Poll again in ~20s with {"film_id": "${filmId}", "action": "status", "music_job_id": "${
|
|
1744
|
+
const dispatch2 = await ctx.client.run("text-to-music", body);
|
|
1745
|
+
return { kind: "text", text: `All ${all.length} clips ready \u2014 the ${soundtrack} score is composing (charged like any music generation). Poll again in ~20s with {"film_id": "${filmId}", "action": "status", "music_job_id": "${dispatch2.jobId}"}.` };
|
|
1691
1746
|
}
|
|
1692
1747
|
await filmApi(ctx, "POST", `/api/films/${filmId}/export`, {});
|
|
1693
1748
|
return { kind: "text", text: `All clips ready \u2014 export started (${progressLine(film)}). Poll again in ~60s with action "status" for the finished film URL.` };
|
|
1694
1749
|
}
|
|
1695
1750
|
const failed = all.filter((sh) => sh.status === "failed");
|
|
1751
|
+
const refused = failed.filter(isRefused);
|
|
1752
|
+
const retrying = failed.filter((sh) => !isRefused(sh));
|
|
1753
|
+
const sentAgain = retrying.filter((sh) => resent.has(sh.id)).length;
|
|
1754
|
+
const paused = refused.length > 0 && started === 0 && !all.some((sh) => sh.status === "frame-generating" || sh.status === "clip-generating");
|
|
1755
|
+
const labels = new Map(film.scenes.flatMap((s, si) => s.shots.map((sh, i) => [sh.id, `${si + 1}.${i + 1}`])));
|
|
1756
|
+
const reason = (sh) => {
|
|
1757
|
+
const text = (sh.errorMessage ?? "no reason given").replace(/\s*Retry (?:the shot|it)\.\s*$/i, "");
|
|
1758
|
+
return text.length > 300 ? `${text.slice(0, 297)}...` : text;
|
|
1759
|
+
};
|
|
1760
|
+
const one = refused.length === 1;
|
|
1696
1761
|
return {
|
|
1697
1762
|
kind: "text",
|
|
1698
1763
|
text: [
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1764
|
+
`${paused ? "Paused" : "In progress"}: ${progressLine(film)}.`,
|
|
1765
|
+
refused.length > 0 ? `${one ? "Shot" : "Shots"} ${refused.map((sh) => labels.get(sh.id)).join(", ")} need${one ? "s" : ""} a new frame or prompt. The provider refused ${one ? "it" : "them"} (${reason(refused[0])}) and usually refuses the same input again, so status does not resend ${one ? "it" : "them"}: every retry is a charged render. Change ${one ? "it" : "them"} and render again at ${ctx.baseUrl}/film-studio/${filmId}.` : "",
|
|
1766
|
+
retrying.length > 0 ? `${retrying.length} shot(s) failed (${reason(retrying[0])}): ${sentAgain === retrying.length ? "sent again" : sentAgain > 0 ? `${sentAgain} sent again, the rest on a later poll` : "sent again on a later poll"}.` : "",
|
|
1767
|
+
paused ? `Nothing else is rendering. Once the shot${one ? "" : "s"} render${one ? "s" : ""} again, call pixeldojo_film with {"film_id": "${filmId}", "action": "status"} to carry on.` : `Poll again in ~30s with {"film_id": "${filmId}", "action": "status"}.`
|
|
1702
1768
|
].filter(Boolean).join("\n")
|
|
1703
1769
|
};
|
|
1704
1770
|
}
|
|
1705
1771
|
async function run8(input, ctx) {
|
|
1706
|
-
const action = input.action === "plan" && input.film_id ? "status" : input.action;
|
|
1772
|
+
const action = input.action === "plan" && input.film_id ? input.cast?.length ? "cast" : "status" : input.action;
|
|
1707
1773
|
if (action === "plan") return plan(input, ctx);
|
|
1708
|
-
if (!input.film_id) return { kind: "text", text: "produce/status need the film_id returned by the plan step." };
|
|
1774
|
+
if (!input.film_id) return { kind: "text", text: "produce/status/cast need the film_id returned by the plan step." };
|
|
1775
|
+
if (action === "cast") {
|
|
1776
|
+
if (!input.cast?.length) return { kind: "text", text: "Pass cast: [{ name, prompt | image_urls | character_id }] to cast characters." };
|
|
1777
|
+
const { lines } = await applyCast(ctx, input.film_id, input.cast);
|
|
1778
|
+
const uncast = await uncastRolesOf(ctx, input.film_id);
|
|
1779
|
+
return {
|
|
1780
|
+
kind: "text",
|
|
1781
|
+
text: [
|
|
1782
|
+
...lines,
|
|
1783
|
+
uncast.length ? castNeededText(input.film_id, uncast) : `Everyone is cast. Call pixeldojo_film with {"film_id": "${input.film_id}", "action": "produce"} to start production.`
|
|
1784
|
+
].join("\n")
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1709
1787
|
if (action === "produce") return produce(input.film_id, ctx);
|
|
1710
1788
|
return advance(input.film_id, input.soundtrack, input.music_job_id, ctx);
|
|
1711
1789
|
}
|
|
1712
|
-
var STORY_LOOKS, MUSIC_VIDEO_LOOKS, LOOKS, MUSIC_CLIP_ENGINE_ID, InputSchema8, InputJsonSchema8, shots, count, PLANNER_POLL_MS, PLANNER_MAX_POLLS, isMusicVideo, needsSlice, stillPlanningText, LOOK_MUSIC, ARC, filmSkill;
|
|
1790
|
+
var STORY_LOOKS, MUSIC_VIDEO_LOOKS, LOOKS, MUSIC_CLIP_ENGINE_ID, InputSchema8, InputJsonSchema8, shots, count, frameDone, RETRY_CLEARS, REFUSED, isRefused, PLANNER_POLL_MS, PLANNER_MAX_POLLS, isMusicVideo, needsSlice, stillPlanningText, LOOK_MUSIC, ARC, filmSkill;
|
|
1713
1791
|
var init_film = __esm({
|
|
1714
1792
|
"src/skills/film.ts"() {
|
|
1715
1793
|
init_upload_helpers();
|
|
@@ -1727,8 +1805,16 @@ var init_film = __esm({
|
|
|
1727
1805
|
lyrics: z.string().max(1e4).optional().describe("Music video mode: the song lyrics, so shots land on the right lines. Optional."),
|
|
1728
1806
|
aspect: z.enum(["16:9", "9:16"]).default("16:9"),
|
|
1729
1807
|
length: z.enum(["30s", "60s", "2-3min", "5min"]).default("60s").describe("Target length; drives the shot count (~6/12/24/60 shots)."),
|
|
1730
|
-
film_id: z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status."),
|
|
1731
|
-
action: z.enum(["plan", "produce", "status"]).default("plan").describe("'plan' creates + storyboards (free). 'produce' starts charged generation. 'status' advances production and reports progress; poll it every ~30s until it returns the finished film."),
|
|
1808
|
+
film_id: z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status/cast."),
|
|
1809
|
+
action: z.enum(["plan", "produce", "status", "cast"]).default("plan").describe("'plan' creates + storyboards (free). 'cast' casts characters on an existing film. 'produce' starts charged generation. 'status' advances production and reports progress; poll it every ~30s until it returns the finished film."),
|
|
1810
|
+
cast: z.array(
|
|
1811
|
+
z.object({
|
|
1812
|
+
name: z.string().trim().min(1).max(60),
|
|
1813
|
+
image_urls: z.array(z.string().url().startsWith("https://").max(2048)).min(1).max(8).optional(),
|
|
1814
|
+
prompt: z.string().trim().min(1).max(600).optional(),
|
|
1815
|
+
character_id: z.string().max(40).optional()
|
|
1816
|
+
})
|
|
1817
|
+
).max(8).optional().describe("Cast members, one per named character. Each gives image_urls (your own photos), a prompt (a portrait is generated, charged), or a character_id (a Character Studio character); with none of these the Character Studio character of that name is used."),
|
|
1732
1818
|
soundtrack: z.enum(["scene-synced", "flat", "none"]).default("scene-synced").describe("scene-synced composes one musical section per scene, pinned to the scene lengths (default). flat is a single cohesive bed. none skips music."),
|
|
1733
1819
|
music_job_id: z.string().max(80).optional().describe("The music_job_id from the previous status reply while the score is composing, so that score is attached. A status call without it, once all clips are ready and no score is attached, starts and charges for a new score.")
|
|
1734
1820
|
});
|
|
@@ -1756,8 +1842,26 @@ var init_film = __esm({
|
|
|
1756
1842
|
film_id: { type: "string", description: 'The film_id returned by the plan step. Required for produce and status; with it, action "plan" is treated as "status".' },
|
|
1757
1843
|
action: {
|
|
1758
1844
|
type: "string",
|
|
1759
|
-
enum: ["plan", "produce", "status"],
|
|
1760
|
-
description: "plan (default, free): writes the script and plans shots, returns film_id
|
|
1845
|
+
enum: ["plan", "produce", "status", "cast"],
|
|
1846
|
+
description: "plan (default, free): writes the script and plans shots, returns film_id, a cost estimate, and any characters still to cast. cast: casts characters on an existing film (pass film_id and cast). produce (charged): starts rendering frames; refused until every named character is cast. status: advances production one step and reports progress; call it every ~30s until it returns the finished film URL."
|
|
1847
|
+
},
|
|
1848
|
+
cast: {
|
|
1849
|
+
type: "array",
|
|
1850
|
+
maxItems: 8,
|
|
1851
|
+
description: 'Cast members, one per named character. Every named character must be cast before frames render, so faces, ages and species hold in every shot. Pass them on the plan call (the script then stars them by name) or later with action "cast" using the names the plan reports. Each entry: name, plus ONE of image_urls (your own photos of an adult, made into a character), prompt (a portrait is generated from it, charged like any image), or character_id (a Character Studio character). An entry with only a name uses your Character Studio character of that name.',
|
|
1852
|
+
items: {
|
|
1853
|
+
type: "object",
|
|
1854
|
+
properties: {
|
|
1855
|
+
name: { type: "string", description: 'The character name as the film uses it (e.g. "Harlan").' },
|
|
1856
|
+
image_urls: { type: "array", items: { type: "string" }, description: "Your own https photos of the character (1-8). PixelDojo uploads work; they are copied to permanent storage." },
|
|
1857
|
+
prompt: {
|
|
1858
|
+
type: "string",
|
|
1859
|
+
description: 'A description to generate the portrait from: species if not human, gender, adult age, look and outfit (e.g. "man in his late 50s, grey mustache, sunburnt, red windbreaker").'
|
|
1860
|
+
},
|
|
1861
|
+
character_id: { type: "string", description: "A Character Studio character id." }
|
|
1862
|
+
},
|
|
1863
|
+
required: ["name"]
|
|
1864
|
+
}
|
|
1761
1865
|
},
|
|
1762
1866
|
soundtrack: {
|
|
1763
1867
|
type: "string",
|
|
@@ -1772,6 +1876,10 @@ var init_film = __esm({
|
|
|
1772
1876
|
};
|
|
1773
1877
|
shots = (f) => f.scenes.flatMap((s) => s.shots);
|
|
1774
1878
|
count = (f, pred) => shots(f).filter(pred).length;
|
|
1879
|
+
frameDone = (sh) => Boolean(sh.frameUrl) && sh.status !== "idle" && sh.status !== "frame-generating";
|
|
1880
|
+
RETRY_CLEARS = /automatic audio check flagged|output audio\b[\s\S]*?sensitive information|temporarily unavailable/i;
|
|
1881
|
+
REFUSED = /copyright|infringement|content filter|safety filter|moderation|blocked|flagged|sensitive|real (?:person|human)|nsfw|content policy|policy violation|inappropriate/i;
|
|
1882
|
+
isRefused = (sh) => sh.status === "failed" && Boolean(sh.errorMessage) && !RETRY_CLEARS.test(sh.errorMessage) && REFUSED.test(sh.errorMessage);
|
|
1775
1883
|
PLANNER_POLL_MS = 3e3;
|
|
1776
1884
|
PLANNER_MAX_POLLS = 8;
|
|
1777
1885
|
isMusicVideo = (f) => f.musicVideoMode === true;
|
|
@@ -1797,8 +1905,8 @@ var init_film = __esm({
|
|
|
1797
1905
|
};
|
|
1798
1906
|
filmSkill = {
|
|
1799
1907
|
name: "pixeldojo_film",
|
|
1800
|
-
description: "Make a finished short film from one idea: screenplay, storyboard frames (AI-reviewed), video clips with spoken dialogue, scene-synced soundtrack, stitched export. Or a music video: pass song_path (a local file) or song_url (your own PixelDojo upload from pixeldojo_upload, never a third-party link) with optional lyrics; the film is cut to that song, each clip lip-synced to its slice on the studio music video engine, and no score is composed: the export plays the song. Phased: plan is free; produce starts charged generation; status advances production (poll every ~30s).",
|
|
1801
|
-
hostedDescription: "Make a finished short film from one idea: screenplay, storyboard frames (AI-reviewed), video clips with spoken dialogue, scene-synced soundtrack, stitched export. Or a music video: pass song_url (your own PixelDojo upload made with POST /api/v1/upload, never a third-party link) with optional lyrics; the film is cut to that song, each clip lip-synced to its slice on the studio music video engine, and no score is composed: the export plays the song. Phased: plan is free; produce starts charged generation; status advances production (poll every ~30s).",
|
|
1908
|
+
description: "Make a finished short film from one idea: screenplay, storyboard frames (AI-reviewed), video clips with spoken dialogue, scene-synced soundtrack, stitched export. Or a music video: pass song_path (a local file) or song_url (your own PixelDojo upload from pixeldojo_upload, never a third-party link) with optional lyrics; the film is cut to that song, each clip lip-synced to its slice on the studio music video engine, and no score is composed: the export plays the song. Every named character must be cast (cast: your own images, a portrait prompt, or a Character Studio character) before frames render. Phased: plan is free; cast; produce starts charged generation; status advances production (poll every ~30s).",
|
|
1909
|
+
hostedDescription: "Make a finished short film from one idea: screenplay, storyboard frames (AI-reviewed), video clips with spoken dialogue, scene-synced soundtrack, stitched export. Or a music video: pass song_url (your own PixelDojo upload made with POST /api/v1/upload, never a third-party link) with optional lyrics; the film is cut to that song, each clip lip-synced to its slice on the studio music video engine, and no score is composed: the export plays the song. Every named character must be cast (cast: your own images, a portrait prompt, or a Character Studio character) before frames render. Phased: plan is free; cast; produce starts charged generation; status advances production (poll every ~30s).",
|
|
1802
1910
|
inputZod: InputSchema8,
|
|
1803
1911
|
inputJsonSchema: InputJsonSchema8,
|
|
1804
1912
|
allowedModels: [],
|
|
@@ -2232,7 +2340,7 @@ async function produce2(id, ctx) {
|
|
|
2232
2340
|
if (saved.status !== 200 || !url) {
|
|
2233
2341
|
return { kind: "text", text: `The export is ready but could not be saved: ${saved.data.error ?? saved.status}. The draft (kept 24h): ${exp.url}. ${next(id, "produce", "~30s")}` };
|
|
2234
2342
|
}
|
|
2235
|
-
return { kind: "text", text: `"${ep.title}" is finished and saved to
|
|
2343
|
+
return { kind: "text", text: `"${ep.title}" is finished and saved to your Library: ${url}` };
|
|
2236
2344
|
}
|
|
2237
2345
|
const started = await episodeApi(ctx, "POST", `/${id}/export`, {});
|
|
2238
2346
|
if (started.status !== 202) {
|
|
@@ -2303,7 +2411,7 @@ var init_episode = __esm({
|
|
|
2303
2411
|
stillPlanning = (id) => `The script is still being written (episode_id: ${id}). Nothing has been charged. ${next(id, "status", "~30s")}`;
|
|
2304
2412
|
episodeSkill = {
|
|
2305
2413
|
name: "pixeldojo_episode",
|
|
2306
|
-
description: "Make a finished narrated faceless video (explainer, history, kids story) from one topic: script, scene visuals, AI narration, music bed, stitched export saved to
|
|
2414
|
+
description: "Make a finished narrated faceless video (explainer, history, kids story) from one topic: script, scene visuals, AI narration, music bed, stitched export saved to the Library. Phased: create is free and returns a credit quote; produce is charged and advances one step per call (poll every ~30s until it returns the video URL); status is free.",
|
|
2307
2415
|
inputZod: InputSchema10,
|
|
2308
2416
|
inputJsonSchema: InputJsonSchema10,
|
|
2309
2417
|
allowedModels: [],
|
|
@@ -2853,7 +2961,7 @@ var init_library = __esm({
|
|
|
2853
2961
|
};
|
|
2854
2962
|
librarySkill = {
|
|
2855
2963
|
name: "pixeldojo_library",
|
|
2856
|
-
description: "Search your
|
|
2964
|
+
description: "Search your Library: saved media (permanent) and recent API/MCP generations (deleted 1 hour after they finish), newest first. Returns asset URLs to reuse as reference inputs.",
|
|
2857
2965
|
inputZod: InputSchema16,
|
|
2858
2966
|
inputJsonSchema: InputJsonSchema16,
|
|
2859
2967
|
allowedModels: [],
|
|
@@ -3275,7 +3383,7 @@ function makeRedactedLogger(target = console) {
|
|
|
3275
3383
|
}
|
|
3276
3384
|
|
|
3277
3385
|
// src/server.ts
|
|
3278
|
-
var PACKAGE_VERSION = "0.
|
|
3386
|
+
var PACKAGE_VERSION = "0.17.0" ;
|
|
3279
3387
|
var SERVER_INSTRUCTIONS = "PixelDojo generates images, video, audio and short films across 100+ models, billed in credits from the connected PixelDojo account. A job that runs past the 30-second wait returns a jobId to check later; check it rather than submitting again, since every new submission is charged. Generated asset URLs are deleted one hour after they are created, so download anything the user wants to keep. Finished films are the exception and stay in the account library.";
|
|
3280
3388
|
function buildServer(options) {
|
|
3281
3389
|
if (!options.apiKey) {
|