@writepanda/mcp 1.222.0 → 1.225.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 +1 -0
- package/bin/appConnection.mjs +169 -1
- package/bin/server.mjs +533 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,6 +71,7 @@ Restart the client afterwards. The server auto-launches PandaStudio if it isn't
|
|
|
71
71
|
| `caption_*` | 3 | Toggle, templates, per-style overrides |
|
|
72
72
|
| `media_*` | 3 | Generate images, music, narration |
|
|
73
73
|
| `preview_*` / `job_*` / `agent_*` / `audio_*` / `timeline_*` | 14 | Live preview overlay, async job polling, agent sessions, audio cleanup, time-base conversion |
|
|
74
|
+
| `review_*` | 5 | The pins, drawings and picked graphics the user marked on the preview: list them (with frame crops), resolve them when done, add / update / delete |
|
|
74
75
|
|
|
75
76
|
Plus `pandastudio_call` — a raw `verb.noun` escape hatch for anything not yet a first-class tool.
|
|
76
77
|
|
package/bin/appConnection.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
//
|
|
17
17
|
// Kept free of MCP-sdk imports so the pure parts are unit-testable.
|
|
18
18
|
|
|
19
|
-
import { readFile } from "node:fs/promises";
|
|
19
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
20
20
|
import http from "node:http";
|
|
21
21
|
import os from "node:os";
|
|
22
22
|
import path from "node:path";
|
|
@@ -239,3 +239,171 @@ export function appLaunchEnv(env = process.env) {
|
|
|
239
239
|
}
|
|
240
240
|
return out;
|
|
241
241
|
}
|
|
242
|
+
|
|
243
|
+
// ── Launching the app (external agents only) ─────────────────────────────
|
|
244
|
+
//
|
|
245
|
+
// Jared (Windows, 2.1.15): "PandaStudio seemed to restart with every
|
|
246
|
+
// request". Every call that didn't find a healthy server at the port file's
|
|
247
|
+
// address launched PandaStudio.exe again: while the app was busy for longer
|
|
248
|
+
// than the busy wait, while it was still booting, and after another process
|
|
249
|
+
// had replaced or deleted the token/port files. Now a launch happens only
|
|
250
|
+
// when nothing says PandaStudio is running, and at most once per
|
|
251
|
+
// LAUNCH_DEBOUNCE_MS across every MCP server / CLI process on the machine.
|
|
252
|
+
|
|
253
|
+
/** Default automation port (Settings → Automation). */
|
|
254
|
+
export const DEFAULT_AUTOMATION_PORT = 7878;
|
|
255
|
+
/** An instance.json heartbeat older than this is a dead (or hung) app. */
|
|
256
|
+
export const INSTANCE_STALE_MS = 20_000;
|
|
257
|
+
/** Don't launch again within this long of the last launch, from anywhere. */
|
|
258
|
+
export const LAUNCH_DEBOUNCE_MS = 90_000;
|
|
259
|
+
|
|
260
|
+
/** Is a process with this pid running? (EPERM: it exists, owned by someone else.) */
|
|
261
|
+
export function pidAlive(pid) {
|
|
262
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
263
|
+
try {
|
|
264
|
+
process.kill(pid, 0);
|
|
265
|
+
return true;
|
|
266
|
+
} catch (err) {
|
|
267
|
+
return err?.code === "EPERM";
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** The running app's instance.json (pid + heartbeat), or null. */
|
|
272
|
+
export async function readInstanceRecord(dir = configDir()) {
|
|
273
|
+
try {
|
|
274
|
+
const v = JSON.parse(cleanFileValue(await readFile(path.join(dir, "instance.json"), "utf-8")));
|
|
275
|
+
return v && typeof v === "object" ? v : null;
|
|
276
|
+
} catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** A fresh heartbeat from a process that still exists. */
|
|
282
|
+
export function instanceAlive(record, now = Date.now(), isAlive = pidAlive) {
|
|
283
|
+
if (!record || !Number.isInteger(record.pid)) return false;
|
|
284
|
+
const beat = Number(record.heartbeatAt);
|
|
285
|
+
if (!Number.isFinite(beat) || now - beat > INSTANCE_STALE_MS) return false;
|
|
286
|
+
return isAlive(record.pid);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function launchMarkerPath(dir) {
|
|
290
|
+
return path.join(dir, "launch-requested");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Milliseconds since any process last launched the app, or null. */
|
|
294
|
+
export async function lastLaunchAgeMs(dir = configDir(), now = Date.now()) {
|
|
295
|
+
try {
|
|
296
|
+
const at = Number(cleanFileValue(await readFile(launchMarkerPath(dir), "utf-8")));
|
|
297
|
+
if (!Number.isFinite(at) || at > now) return null;
|
|
298
|
+
return now - at;
|
|
299
|
+
} catch {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export async function recordLaunch(dir = configDir(), now = Date.now()) {
|
|
305
|
+
try {
|
|
306
|
+
await mkdir(dir, { recursive: true });
|
|
307
|
+
await writeFile(launchMarkerPath(dir), String(now), "utf-8");
|
|
308
|
+
} catch {
|
|
309
|
+
/* best-effort: the in-process flag still stops a second launch */
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* What to do this round:
|
|
315
|
+
* use — the port file's server answered
|
|
316
|
+
* wait — a PandaStudio is running (busy, booting, files being rewritten,
|
|
317
|
+
* or Local automation off) or one was launched moments ago
|
|
318
|
+
* launch — nothing is running and nobody launched it recently
|
|
319
|
+
*
|
|
320
|
+
* healthState / preferredState: probe of the port file's address / of the
|
|
321
|
+
* default port ("up" | "busy" | "down", or null when not probed).
|
|
322
|
+
*/
|
|
323
|
+
export function launchDecision({
|
|
324
|
+
healthState = null,
|
|
325
|
+
preferredState = null,
|
|
326
|
+
instanceIsAlive = false,
|
|
327
|
+
launchAgeMs = null,
|
|
328
|
+
debounceMs = LAUNCH_DEBOUNCE_MS,
|
|
329
|
+
}) {
|
|
330
|
+
if (healthState === "up") return "use";
|
|
331
|
+
if (healthState === "busy") return "wait";
|
|
332
|
+
if (instanceIsAlive) return "wait";
|
|
333
|
+
if (preferredState === "up" || preferredState === "busy") return "wait";
|
|
334
|
+
if (launchAgeMs !== null && launchAgeMs < debounceMs) return "wait";
|
|
335
|
+
return "launch";
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Reach the running app, launching it only when none is running. Returns the
|
|
340
|
+
* credentials whose server answered. `launch()` starts the app (throws when
|
|
341
|
+
* it can't). Everything else is injectable for tests.
|
|
342
|
+
*/
|
|
343
|
+
export async function ensureApp({
|
|
344
|
+
dir = configDir(),
|
|
345
|
+
launch,
|
|
346
|
+
timeoutMs = 60_000,
|
|
347
|
+
pollMs = 500,
|
|
348
|
+
preferredPort = DEFAULT_AUTOMATION_PORT,
|
|
349
|
+
probe = probeHealth,
|
|
350
|
+
readFiles = fileCredentials,
|
|
351
|
+
readInstance = readInstanceRecord,
|
|
352
|
+
isAlive = pidAlive,
|
|
353
|
+
launchAge = lastLaunchAgeMs,
|
|
354
|
+
markLaunch = recordLaunch,
|
|
355
|
+
now = Date.now,
|
|
356
|
+
sleep = (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
357
|
+
} = {}) {
|
|
358
|
+
const start = now();
|
|
359
|
+
let launched = false;
|
|
360
|
+
let last = { healthState: null, instanceIsAlive: false, recentLaunch: false };
|
|
361
|
+
for (;;) {
|
|
362
|
+
let creds = null;
|
|
363
|
+
let healthState = null;
|
|
364
|
+
try {
|
|
365
|
+
creds = await readFiles(dir);
|
|
366
|
+
healthState = (await probe(creds.port, 1500)).state;
|
|
367
|
+
} catch {
|
|
368
|
+
/* no files yet: not running, booting, or they're being rewritten */
|
|
369
|
+
}
|
|
370
|
+
if (healthState === "up") return creds;
|
|
371
|
+
const alive = instanceAlive(await readInstance(dir), now(), isAlive);
|
|
372
|
+
let preferredState = null;
|
|
373
|
+
if (!alive && healthState !== "busy" && creds?.port !== preferredPort) {
|
|
374
|
+
preferredState = (await probe(preferredPort, 1000)).state;
|
|
375
|
+
}
|
|
376
|
+
const age = launched ? 0 : await launchAge(dir, now());
|
|
377
|
+
const decision = launchDecision({
|
|
378
|
+
healthState,
|
|
379
|
+
preferredState,
|
|
380
|
+
instanceIsAlive: alive,
|
|
381
|
+
launchAgeMs: age,
|
|
382
|
+
});
|
|
383
|
+
last = { healthState, preferredState, instanceIsAlive: alive };
|
|
384
|
+
if (decision === "launch") {
|
|
385
|
+
await markLaunch(dir, now());
|
|
386
|
+
launched = true;
|
|
387
|
+
await launch();
|
|
388
|
+
}
|
|
389
|
+
if (now() - start >= timeoutMs) throw new Error(notReadyMessage(last, launched, timeoutMs));
|
|
390
|
+
await sleep(pollMs);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function notReadyMessage({ healthState, preferredState, instanceIsAlive }, launched, timeoutMs) {
|
|
395
|
+
const secs = Math.round(timeoutMs / 1000);
|
|
396
|
+
if (preferredState === "up" && !instanceIsAlive) {
|
|
397
|
+
return `A PandaStudio is answering on 127.0.0.1:${DEFAULT_AUTOMATION_PORT} but its access files in ${configDir()} are missing or stale. Quit PandaStudio completely and open it again.`;
|
|
398
|
+
}
|
|
399
|
+
if (healthState === "busy") {
|
|
400
|
+
return `PandaStudio is running but busy: it didn't answer for ${secs}s (a heavy export or import can do this). Try again in a moment; nothing was relaunched.`;
|
|
401
|
+
}
|
|
402
|
+
if (instanceIsAlive) {
|
|
403
|
+
return `PandaStudio is running but its automation server didn't answer within ${secs}s. In PandaStudio open Settings → Automation and make sure Local automation is on.`;
|
|
404
|
+
}
|
|
405
|
+
if (launched) {
|
|
406
|
+
return `PandaStudio did not become ready within ${secs}s. Open PandaStudio, go to Settings → Automation and turn on Local automation.`;
|
|
407
|
+
}
|
|
408
|
+
return `PandaStudio was started moments ago and isn't ready yet (waited ${secs}s). Open PandaStudio and check Settings → Automation → Local automation is on.`;
|
|
409
|
+
}
|
package/bin/server.mjs
CHANGED
|
@@ -42,11 +42,10 @@ import {
|
|
|
42
42
|
cleanFileValue,
|
|
43
43
|
configDir,
|
|
44
44
|
connectInApp,
|
|
45
|
+
ensureApp,
|
|
45
46
|
fileCredentials,
|
|
46
47
|
IN_APP_UNAVAILABLE,
|
|
47
48
|
isInAppCaller,
|
|
48
|
-
probeHealth,
|
|
49
|
-
waitForHealth,
|
|
50
49
|
} from "./appConnection.mjs";
|
|
51
50
|
import { portableSchema } from "./portableSchema.mjs";
|
|
52
51
|
|
|
@@ -56,10 +55,6 @@ import { portableSchema } from "./portableSchema.mjs";
|
|
|
56
55
|
|
|
57
56
|
const IN_APP = isInAppCaller();
|
|
58
57
|
|
|
59
|
-
async function readCredentials() {
|
|
60
|
-
return fileCredentials(configDir());
|
|
61
|
-
}
|
|
62
|
-
|
|
63
58
|
function findInstalledApp() {
|
|
64
59
|
if (process.env.PANDASTUDIO_BIN) return process.env.PANDASTUDIO_BIN;
|
|
65
60
|
// The app records its own location (it can live anywhere on Windows,
|
|
@@ -102,46 +97,34 @@ function findInstalledApp() {
|
|
|
102
97
|
return candidates.find((c) => existsSync(c)) ?? candidates[0];
|
|
103
98
|
}
|
|
104
99
|
|
|
100
|
+
// Reach the running app; launch it only when nothing says it is running
|
|
101
|
+
// (ensureApp in appConnection.mjs: port-file health, instance.json heartbeat,
|
|
102
|
+
// the default port, and a machine-wide launch debounce). Launching on every
|
|
103
|
+
// call that missed the server restarted PandaStudio "with every request".
|
|
105
104
|
async function autoLaunchAndWait(timeoutSec = 60) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
throw new Error(`failed to launch PandaStudio (${bin}): ${err?.message ?? err}`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const deadline = Date.now() + timeoutSec * 1000;
|
|
133
|
-
while (Date.now() < deadline) {
|
|
134
|
-
try {
|
|
135
|
-
const c = await readCredentials();
|
|
136
|
-
if ((await probeHealth(c.port, 2000)).state === "up") return c;
|
|
137
|
-
} catch {
|
|
138
|
-
/* still booting */
|
|
139
|
-
}
|
|
140
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
141
|
-
}
|
|
142
|
-
throw new Error(
|
|
143
|
-
`PandaStudio did not become ready within ${timeoutSec}s. Open PandaStudio, go to Settings → Automation and turn on Local automation.`,
|
|
144
|
-
);
|
|
105
|
+
return ensureApp({
|
|
106
|
+
timeoutMs: timeoutSec * 1000,
|
|
107
|
+
launch: async () => {
|
|
108
|
+
const bin = findInstalledApp();
|
|
109
|
+
try {
|
|
110
|
+
// appLaunchEnv drops ELECTRON_RUN_AS_NODE: inherited, it made the
|
|
111
|
+
// "launch" start a Node REPL that exits instead of PandaStudio.
|
|
112
|
+
// windowsHide: false: this is the desktop app, whose window must show.
|
|
113
|
+
const child = spawn(bin, [], {
|
|
114
|
+
detached: true,
|
|
115
|
+
stdio: "ignore",
|
|
116
|
+
env: appLaunchEnv(),
|
|
117
|
+
windowsHide: false,
|
|
118
|
+
});
|
|
119
|
+
child.on("error", () => {
|
|
120
|
+
/* reported by the readiness wait */
|
|
121
|
+
});
|
|
122
|
+
child.unref();
|
|
123
|
+
} catch (err) {
|
|
124
|
+
throw new Error(`failed to launch PandaStudio (${bin}): ${err?.message ?? err}`);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
});
|
|
145
128
|
}
|
|
146
129
|
|
|
147
130
|
// The app answers /v1/call when the command finishes, and some commands run
|
|
@@ -1057,6 +1040,77 @@ const TOOLS = [
|
|
|
1057
1040
|
},
|
|
1058
1041
|
command: "workspace.contents",
|
|
1059
1042
|
},
|
|
1043
|
+
{
|
|
1044
|
+
name: "brand_list_assets",
|
|
1045
|
+
description: "List reusable assets in the active workspace brand library.",
|
|
1046
|
+
inputSchema: {
|
|
1047
|
+
type: "object",
|
|
1048
|
+
properties: {},
|
|
1049
|
+
required: [],
|
|
1050
|
+
},
|
|
1051
|
+
command: "brand.list-assets",
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
name: "brand_add_asset",
|
|
1055
|
+
description: "Copy a local image, video or audio file into the workspace brand library.",
|
|
1056
|
+
inputSchema: {
|
|
1057
|
+
type: "object",
|
|
1058
|
+
properties: {
|
|
1059
|
+
path: {
|
|
1060
|
+
type: "string",
|
|
1061
|
+
},
|
|
1062
|
+
name: {
|
|
1063
|
+
type: "string",
|
|
1064
|
+
},
|
|
1065
|
+
role: {
|
|
1066
|
+
type: "string",
|
|
1067
|
+
enum: ["logo", "outro", "intro", "cta", "watermark", "other"],
|
|
1068
|
+
},
|
|
1069
|
+
},
|
|
1070
|
+
required: ["path"],
|
|
1071
|
+
},
|
|
1072
|
+
command: "brand.add-asset",
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
name: "brand_remove_asset",
|
|
1076
|
+
description: "Remove an asset from the library. Existing timeline media remains available.",
|
|
1077
|
+
inputSchema: {
|
|
1078
|
+
type: "object",
|
|
1079
|
+
properties: {
|
|
1080
|
+
id: {
|
|
1081
|
+
type: "string",
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
required: ["id"],
|
|
1085
|
+
},
|
|
1086
|
+
command: "brand.remove-asset",
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
name: "project_add_brand_asset",
|
|
1090
|
+
description:
|
|
1091
|
+
"Insert a brand asset at edited atMs (default 0). Images are overlays, videos clips, audio tracks. Outros append.",
|
|
1092
|
+
inputSchema: {
|
|
1093
|
+
type: "object",
|
|
1094
|
+
properties: {
|
|
1095
|
+
id: {
|
|
1096
|
+
type: "string",
|
|
1097
|
+
},
|
|
1098
|
+
assetId: {
|
|
1099
|
+
type: "string",
|
|
1100
|
+
},
|
|
1101
|
+
atMs: {
|
|
1102
|
+
type: "number",
|
|
1103
|
+
minimum: 0,
|
|
1104
|
+
},
|
|
1105
|
+
placement: {
|
|
1106
|
+
type: "string",
|
|
1107
|
+
enum: ["clip", "overlay", "append"],
|
|
1108
|
+
},
|
|
1109
|
+
},
|
|
1110
|
+
required: ["id", "assetId"],
|
|
1111
|
+
},
|
|
1112
|
+
command: "project.add-brand-asset",
|
|
1113
|
+
},
|
|
1060
1114
|
{
|
|
1061
1115
|
name: "workspace_get_brand",
|
|
1062
1116
|
description:
|
|
@@ -1074,6 +1128,7 @@ const TOOLS = [
|
|
|
1074
1128
|
brand: {
|
|
1075
1129
|
type: ["object", "null"],
|
|
1076
1130
|
properties: {
|
|
1131
|
+
savedColors: { type: "array", items: { type: "string" }, maxItems: 24 },
|
|
1077
1132
|
name: { type: "string" },
|
|
1078
1133
|
tagline: { type: "string" },
|
|
1079
1134
|
colors: {
|
|
@@ -1524,6 +1579,35 @@ const TOOLS = [
|
|
|
1524
1579
|
},
|
|
1525
1580
|
command: "matte.bake",
|
|
1526
1581
|
},
|
|
1582
|
+
{
|
|
1583
|
+
name: "matte_export_cutout",
|
|
1584
|
+
description:
|
|
1585
|
+
"Export the person cut out of a clip as a transparent file for other tools (bakes the matte first if needed). format webm (VP9 + alpha, default; OBS, web), mov (ProRes 4444 + alpha; Premiere, Final Cut, Resolve, After Effects, Keynote) or png (one frame at atMs; thumbnails, Canva). Source size, follows the edit (cuts skipped, 1x speed), clip audio unless audio=false; inverse also writes the background plate. Returns jobId; job_wait gives outputPath, backgroundPath, durationMs, usedIn.",
|
|
1586
|
+
inputSchema: {
|
|
1587
|
+
type: "object",
|
|
1588
|
+
properties: {
|
|
1589
|
+
id: { type: "string" },
|
|
1590
|
+
path: { type: "string" },
|
|
1591
|
+
clip: {
|
|
1592
|
+
type: "string",
|
|
1593
|
+
description: "Clip id. Default: the camera clip / first clip with a person.",
|
|
1594
|
+
},
|
|
1595
|
+
source: { type: "string", enum: ["camera", "main"] },
|
|
1596
|
+
format: { type: "string", enum: ["webm", "mov", "png"] },
|
|
1597
|
+
startMs: { type: "number", description: "Edited-timeline start; default the clip start." },
|
|
1598
|
+
endMs: { type: "number", description: "Edited-timeline end; default the clip end." },
|
|
1599
|
+
atMs: { type: "number", description: "png: edited-timeline time of the frame." },
|
|
1600
|
+
inverse: {
|
|
1601
|
+
type: "boolean",
|
|
1602
|
+
description: "Also write the background with the person removed.",
|
|
1603
|
+
},
|
|
1604
|
+
audio: { type: "boolean", description: "webm/mov: include the clip audio (default true)." },
|
|
1605
|
+
outPath: { type: "string", description: "Absolute output path. Default: beside exports." },
|
|
1606
|
+
},
|
|
1607
|
+
required: ["id"],
|
|
1608
|
+
},
|
|
1609
|
+
command: "matte.export-cutout",
|
|
1610
|
+
},
|
|
1527
1611
|
{
|
|
1528
1612
|
name: "project_read",
|
|
1529
1613
|
description:
|
|
@@ -1787,6 +1871,15 @@ const TOOLS = [
|
|
|
1787
1871
|
path: { type: "string" },
|
|
1788
1872
|
overlayId: { type: "string" },
|
|
1789
1873
|
slots: { type: "object", description: "Templates: changed slots only." },
|
|
1874
|
+
variables: {
|
|
1875
|
+
type: "object",
|
|
1876
|
+
description:
|
|
1877
|
+
"Typed values by id (motion_variables lists them). Validated by type/range/step/options; template slots and HyperFrames HTML declarations alike. Images embedded.",
|
|
1878
|
+
},
|
|
1879
|
+
preset: {
|
|
1880
|
+
type: "string",
|
|
1881
|
+
description: "Saved variable preset name (motion_list_presets); variables apply on top.",
|
|
1882
|
+
},
|
|
1790
1883
|
background: { type: "string", enum: ["solid", "transparent", "glass"] },
|
|
1791
1884
|
html: { type: "string", description: "HTML graphics: full replacement." },
|
|
1792
1885
|
durationMs: { type: "number", description: "text-behind length." },
|
|
@@ -1955,6 +2048,24 @@ const TOOLS = [
|
|
|
1955
2048
|
},
|
|
1956
2049
|
command: "project.set-overlay-chroma-key",
|
|
1957
2050
|
},
|
|
2051
|
+
{
|
|
2052
|
+
name: "project_remove_overlay_background",
|
|
2053
|
+
description:
|
|
2054
|
+
"Cut the subject out of an IMAGE overlay (on-device; any background). Swaps its source to <name>-cutout.png at the same size and remembers the original; restore=true puts it back.",
|
|
2055
|
+
inputSchema: {
|
|
2056
|
+
type: "object",
|
|
2057
|
+
properties: {
|
|
2058
|
+
id: { type: "string" },
|
|
2059
|
+
path: { type: "string" },
|
|
2060
|
+
regionId: { type: "string" },
|
|
2061
|
+
restore: { type: "boolean" },
|
|
2062
|
+
engine: { type: "string", enum: ["auto", "vision", "birefnet"] },
|
|
2063
|
+
expectedRevision: { type: "number" },
|
|
2064
|
+
},
|
|
2065
|
+
required: ["regionId"],
|
|
2066
|
+
},
|
|
2067
|
+
command: "project.remove-overlay-background",
|
|
2068
|
+
},
|
|
1958
2069
|
{
|
|
1959
2070
|
name: "project_add_fx",
|
|
1960
2071
|
description: "Add an FX overlay (bundled fxId or custom src).",
|
|
@@ -2072,6 +2183,40 @@ const TOOLS = [
|
|
|
2072
2183
|
},
|
|
2073
2184
|
command: "project.add-title-behind",
|
|
2074
2185
|
},
|
|
2186
|
+
{
|
|
2187
|
+
name: "project_add_3d_captions",
|
|
2188
|
+
description:
|
|
2189
|
+
"3D camera captions on a talking-head HOOK (5-20 s, 16:9, locked medium shot): the camera whips in and trucks between caption groups at different depths, a hero word hides behind the head, an optional ring of words turns in front of the speaker, a staircase finale. Rendered from the clip's footage, matte and transcript; placed over [startMs, endMs] (editor captions hidden there). Do it after the cuts. Refuses a moving camera or extreme close-up. ASYNC: job_wait gives overlayId, beats[].still (look at each), qc, warnings. Re-run replaces; reference/3d-captions.md.",
|
|
2190
|
+
inputSchema: {
|
|
2191
|
+
type: "object",
|
|
2192
|
+
properties: {
|
|
2193
|
+
id: { type: "string" },
|
|
2194
|
+
path: { type: "string" },
|
|
2195
|
+
startMs: {
|
|
2196
|
+
type: "number",
|
|
2197
|
+
description: "Edited-timeline start (a pause between sentences).",
|
|
2198
|
+
},
|
|
2199
|
+
endMs: { type: "number", description: "Edited-timeline end, 5-20 s after startMs." },
|
|
2200
|
+
style: { type: "string", enum: ["bold", "editorial"], description: "Default bold." },
|
|
2201
|
+
heroWords: {
|
|
2202
|
+
type: "array",
|
|
2203
|
+
items: { type: "string" },
|
|
2204
|
+
description:
|
|
2205
|
+
"Words/short phrases to feature big. Default: the most stressed word per beat.",
|
|
2206
|
+
},
|
|
2207
|
+
accent: { type: "string", description: "Hero colour, CSS hex." },
|
|
2208
|
+
grade: { type: "boolean", description: "Film finish (curves, grain). Default true." },
|
|
2209
|
+
hideCaptions: {
|
|
2210
|
+
type: "boolean",
|
|
2211
|
+
description: "Hide editor captions over the range. Default true.",
|
|
2212
|
+
},
|
|
2213
|
+
force: { type: "boolean", description: "Run despite a moving camera or extreme close-up." },
|
|
2214
|
+
expectedRevision: { type: "number" },
|
|
2215
|
+
},
|
|
2216
|
+
required: ["id", "startMs", "endMs"],
|
|
2217
|
+
},
|
|
2218
|
+
command: "project.add-3d-captions",
|
|
2219
|
+
},
|
|
2075
2220
|
|
|
2076
2221
|
// ── compose: visual regions ─────────────────────────────────────
|
|
2077
2222
|
{
|
|
@@ -2968,6 +3113,28 @@ const TOOLS = [
|
|
|
2968
3113
|
},
|
|
2969
3114
|
command: "project.add-freeze-frame",
|
|
2970
3115
|
},
|
|
3116
|
+
{
|
|
3117
|
+
name: "project_close_gap",
|
|
3118
|
+
description:
|
|
3119
|
+
"Close a gap on the timeline (the editor's right-click Close gap), one undo step. Times are EDITED ms (project_read / timeline_source_to_edited). lane main (default): ripple-delete startMs..endMs from the video; later content, captions and anchored graphics move back with their words; inserted pauses in the range are shortened or removed. lane media[:i] / audio[:i] / effects[:i] (zero-based sub-lane, packed by start time like the timeline rows): the range must be empty in that lane; every later item in that lane shifts left by the gap. Speed changes never move.",
|
|
3120
|
+
inputSchema: {
|
|
3121
|
+
type: "object",
|
|
3122
|
+
properties: {
|
|
3123
|
+
id: { type: "string", description: "Project id (or pass path)." },
|
|
3124
|
+
path: { type: "string", description: "Project file path (or pass id)." },
|
|
3125
|
+
startMs: { type: "number", description: "Gap start, edited ms." },
|
|
3126
|
+
endMs: { type: "number", description: "Gap end, edited ms (> startMs)." },
|
|
3127
|
+
lane: {
|
|
3128
|
+
type: "string",
|
|
3129
|
+
description: "main (default), or media[:i], audio[:i], effects[:i] for a sub-lane.",
|
|
3130
|
+
},
|
|
3131
|
+
expectedRevision: { type: "number" },
|
|
3132
|
+
},
|
|
3133
|
+
required: ["startMs", "endMs"],
|
|
3134
|
+
},
|
|
3135
|
+
command: "project.close-gap",
|
|
3136
|
+
},
|
|
3137
|
+
|
|
2971
3138
|
{
|
|
2972
3139
|
name: "project_remove_pause",
|
|
2973
3140
|
description:
|
|
@@ -3240,7 +3407,7 @@ const TOOLS = [
|
|
|
3240
3407
|
{
|
|
3241
3408
|
name: "project_remove_region",
|
|
3242
3409
|
description:
|
|
3243
|
-
"Delete a region by id (editor.*Regions, motion-element = editor.motionElements, or audioOverlays);
|
|
3410
|
+
"Delete a region by id (editor.*Regions, motion-element = editor.motionElements, or audioOverlays); only this region is removed by default. Pass group=true to also remove linked peers.",
|
|
3244
3411
|
inputSchema: {
|
|
3245
3412
|
type: "object",
|
|
3246
3413
|
properties: {
|
|
@@ -3248,6 +3415,7 @@ const TOOLS = [
|
|
|
3248
3415
|
path: { type: "string" },
|
|
3249
3416
|
regionType: { type: "string", enum: [...REGION_TYPES, "motion-element"] },
|
|
3250
3417
|
regionId: { type: "string" },
|
|
3418
|
+
group: { type: "boolean", description: "Remove all linked peers too. Default false." },
|
|
3251
3419
|
expectedRevision: { type: "number" },
|
|
3252
3420
|
},
|
|
3253
3421
|
required: ["regionType", "regionId"],
|
|
@@ -3346,7 +3514,7 @@ const TOOLS = [
|
|
|
3346
3514
|
{
|
|
3347
3515
|
name: "project_update_region",
|
|
3348
3516
|
description:
|
|
3349
|
-
"Patch a region; unspecified fields are kept. Patchable: zoom (depth, focusX, focusY, focusMode static|auto), trim
|
|
3517
|
+
"Patch a region; unspecified fields are kept. Put the fields in patch. Every type takes startMs, endMs (edited ms; trim and speed are source ms); a retimed region is re-anchored at its new place, so later cuts and editor reloads keep it there. Patchable: zoom (depth, focusX, focusY, followCursor, focusMode static|auto), trim (startMs, endMs), speed (speed, rampIn, rampOut, preservePitch, freezeMs, holdMs, reverse, reverseAudio), fx (opacity, blendMode, speed, zIndex), annotation (text = the words shown, content, textContent, x, y, width, height, fontSize, fontFamily, textColor, color, backgroundColor, fontWeight, fontStyle, textDecoration, textAlign, style, zIndex), overlay (x, y, width, height in canvas %, layer, opacity, blendMode, muted, volume, loop, motionBlur, zIndex), adjustment (blendMode, fadeIn, fadeOut), motion (motionBlur), clip-transform (preset, transitionMs, easing, bezier, cameraFit, backgroundColor), background-effect (mode, strength, backgroundImage, backgroundFit, outline, outlineWidth, outlineColor, outlineShadow, matteContract, matteFeather), audio-overlay (sourceStartMs, volume 0-2, fadeIn, fadeOut in ms, 0 or null removes; fadeInMs, fadeOutMs are aliases). A key the region type doesn't have is not applied: the result lists it in ignoredKeys with a warning, and the call fails when nothing in the patch applies.",
|
|
3350
3518
|
inputSchema: {
|
|
3351
3519
|
type: "object",
|
|
3352
3520
|
properties: {
|
|
@@ -3975,6 +4143,97 @@ const TOOLS = [
|
|
|
3975
4143
|
},
|
|
3976
4144
|
command: "project.remove-volume-keyframe",
|
|
3977
4145
|
},
|
|
4146
|
+
{
|
|
4147
|
+
name: "project_set_audio_carve",
|
|
4148
|
+
description:
|
|
4149
|
+
"Voiceover carve on a music bed (audio overlay): while the main voice or a voiceover speaks, dip the bed's speech band (default 1000-4000 Hz) instead of lowering the whole track. amount 0..1 = deepest cut 2..18 dB (default 0.25 = 6 dB). Combine with a lighter project_set_audio_ducking. Omitted fields keep their value. Export accurate; the editor preview does not carve.",
|
|
4150
|
+
inputSchema: {
|
|
4151
|
+
type: "object",
|
|
4152
|
+
properties: {
|
|
4153
|
+
id: { type: "string" },
|
|
4154
|
+
path: { type: "string" },
|
|
4155
|
+
trackId: { type: "string", description: "The music bed's audio overlay id, e.g. audio-1." },
|
|
4156
|
+
enabled: { type: "boolean", description: "Carve on (default) or off." },
|
|
4157
|
+
amount: { type: "number", minimum: 0, maximum: 1 },
|
|
4158
|
+
lowHz: {
|
|
4159
|
+
type: "number",
|
|
4160
|
+
minimum: 160,
|
|
4161
|
+
maximum: 6000,
|
|
4162
|
+
description: "Band low edge (default 1000).",
|
|
4163
|
+
},
|
|
4164
|
+
highHz: {
|
|
4165
|
+
type: "number",
|
|
4166
|
+
minimum: 160,
|
|
4167
|
+
maximum: 6000,
|
|
4168
|
+
description: "Band high edge, above lowHz (default 4000).",
|
|
4169
|
+
},
|
|
4170
|
+
remove: { type: "boolean", description: "Delete the carve settings." },
|
|
4171
|
+
expectedRevision: { type: "number" },
|
|
4172
|
+
},
|
|
4173
|
+
required: ["trackId"],
|
|
4174
|
+
},
|
|
4175
|
+
command: "project.set-audio-carve",
|
|
4176
|
+
},
|
|
4177
|
+
{
|
|
4178
|
+
name: "project_set_audio_fx",
|
|
4179
|
+
description:
|
|
4180
|
+
'Replace a track\'s ordered FX rack. trackId = audio overlay id or "main" (the main voice, one rack for all clips). fx = [{type, enabled?, params?}], [] clears. Types (params, ranges): highpass/lowpass (frequency Hz, q 0.1-20, poles "1"|"2"); lowshelf/highshelf (frequency, gain -40..40 dB); peaking (frequency, gain, q; a 3-band EQ = three peaking at 200/1000/4000 Hz); compressor (threshold -60..0 dB, ratio 1-20, attack 0.01-2000 ms, release 0.01-9000 ms, knee 1-8, makeup 0-36 dB, mix 0-1); deesser (intensity 0-1, frequency 0-1); reverb (size 0.05-1, damping 0-1, wet 0-1, dry 0-1); gain (gain -60..12 dB). Export applies all; preview: overlays hear EQ/gain/reverb, compressor approximated, de-esser export only; main voice previews gain only.',
|
|
4181
|
+
inputSchema: {
|
|
4182
|
+
type: "object",
|
|
4183
|
+
properties: {
|
|
4184
|
+
id: { type: "string" },
|
|
4185
|
+
path: { type: "string" },
|
|
4186
|
+
trackId: { type: "string", description: 'Audio overlay id, or "main".' },
|
|
4187
|
+
main: { type: "boolean", description: 'Same as trackId "main".' },
|
|
4188
|
+
fx: {
|
|
4189
|
+
type: "array",
|
|
4190
|
+
items: {
|
|
4191
|
+
type: "object",
|
|
4192
|
+
properties: {
|
|
4193
|
+
type: {
|
|
4194
|
+
type: "string",
|
|
4195
|
+
enum: [
|
|
4196
|
+
"highpass",
|
|
4197
|
+
"lowpass",
|
|
4198
|
+
"lowshelf",
|
|
4199
|
+
"highshelf",
|
|
4200
|
+
"peaking",
|
|
4201
|
+
"compressor",
|
|
4202
|
+
"deesser",
|
|
4203
|
+
"reverb",
|
|
4204
|
+
"gain",
|
|
4205
|
+
],
|
|
4206
|
+
},
|
|
4207
|
+
enabled: { type: "boolean" },
|
|
4208
|
+
params: { type: "object" },
|
|
4209
|
+
},
|
|
4210
|
+
required: ["type"],
|
|
4211
|
+
},
|
|
4212
|
+
},
|
|
4213
|
+
expectedRevision: { type: "number" },
|
|
4214
|
+
},
|
|
4215
|
+
required: ["fx"],
|
|
4216
|
+
},
|
|
4217
|
+
command: "project.set-audio-fx",
|
|
4218
|
+
},
|
|
4219
|
+
{
|
|
4220
|
+
name: "project_set_audio_buses",
|
|
4221
|
+
description:
|
|
4222
|
+
"Mix group gains in dB (-60..12): voice (main track, voiceovers, overlay video sound), music (music beds), sfx (sound effects). Multiplies every track in the group after its own volume, ducking and FX. Omitted buses keep their value; reset sets all to 0 first. Preview and export match (preview caps each track at 100%).",
|
|
4223
|
+
inputSchema: {
|
|
4224
|
+
type: "object",
|
|
4225
|
+
properties: {
|
|
4226
|
+
id: { type: "string" },
|
|
4227
|
+
path: { type: "string" },
|
|
4228
|
+
voice: { type: "number", minimum: -60, maximum: 12 },
|
|
4229
|
+
music: { type: "number", minimum: -60, maximum: 12 },
|
|
4230
|
+
sfx: { type: "number", minimum: -60, maximum: 12 },
|
|
4231
|
+
reset: { type: "boolean" },
|
|
4232
|
+
expectedRevision: { type: "number" },
|
|
4233
|
+
},
|
|
4234
|
+
},
|
|
4235
|
+
command: "project.set-audio-buses",
|
|
4236
|
+
},
|
|
3978
4237
|
{
|
|
3979
4238
|
name: "project_set_audio_ducking",
|
|
3980
4239
|
description:
|
|
@@ -4681,6 +4940,24 @@ const TOOLS = [
|
|
|
4681
4940
|
inputSchema: {
|
|
4682
4941
|
type: "object",
|
|
4683
4942
|
properties: {
|
|
4943
|
+
area: {
|
|
4944
|
+
type: "object",
|
|
4945
|
+
description: "Display-local points. Native region capture. Minimum 32 x 32 points.",
|
|
4946
|
+
properties: {
|
|
4947
|
+
displayId: { type: "number" },
|
|
4948
|
+
x: { type: "number" },
|
|
4949
|
+
y: { type: "number" },
|
|
4950
|
+
width: { type: "number", minimum: 32 },
|
|
4951
|
+
height: { type: "number", minimum: 32 },
|
|
4952
|
+
},
|
|
4953
|
+
required: ["displayId", "x", "y", "width", "height"],
|
|
4954
|
+
additionalProperties: false,
|
|
4955
|
+
},
|
|
4956
|
+
aspectRatio: {
|
|
4957
|
+
type: "string",
|
|
4958
|
+
enum: ["Free", "16:9", "9:16", "1:1", "4:3"],
|
|
4959
|
+
description: "Lock area aspect. Without area, fit on the selected display.",
|
|
4960
|
+
},
|
|
4684
4961
|
source: { type: "string", description: "From recording_list_sources." },
|
|
4685
4962
|
fps: { type: "number", description: "1-60, default 30." },
|
|
4686
4963
|
systemAudio: { type: "boolean", description: "Mic is never captured." },
|
|
@@ -5164,6 +5441,25 @@ const TOOLS = [
|
|
|
5164
5441
|
},
|
|
5165
5442
|
command: "media.generate-music",
|
|
5166
5443
|
},
|
|
5444
|
+
{
|
|
5445
|
+
name: "media_remove_background",
|
|
5446
|
+
description:
|
|
5447
|
+
"Remove a still image's background on this computer (people, products, pets, objects, logos). Writes <name>-cutout.png next to it; returns { path, width, height, engine }.",
|
|
5448
|
+
inputSchema: {
|
|
5449
|
+
type: "object",
|
|
5450
|
+
properties: {
|
|
5451
|
+
path: { type: "string", description: "Absolute image path." },
|
|
5452
|
+
trim: {
|
|
5453
|
+
type: "boolean",
|
|
5454
|
+
description: "Crop the empty margin (default true); false keeps the canvas size.",
|
|
5455
|
+
},
|
|
5456
|
+
outPath: { type: "string", description: "Optional absolute .png path." },
|
|
5457
|
+
engine: { type: "string", enum: ["auto", "vision", "birefnet"] },
|
|
5458
|
+
},
|
|
5459
|
+
required: ["path"],
|
|
5460
|
+
},
|
|
5461
|
+
command: "media.remove-background",
|
|
5462
|
+
},
|
|
5167
5463
|
{
|
|
5168
5464
|
name: "media_import",
|
|
5169
5465
|
description:
|
|
@@ -5387,7 +5683,7 @@ const TOOLS = [
|
|
|
5387
5683
|
{
|
|
5388
5684
|
name: "motion_list",
|
|
5389
5685
|
description:
|
|
5390
|
-
"List motion-graphic templates (slots, family, tags
|
|
5686
|
+
"List motion-graphic templates (each with typed variables: type, default, min/max/step, options; plus slots, family, tags) and registryBlocks (with any declared variables). Render templates with motion_generate, blocks with motion_render_html. Retired templates are hidden unless includeRetired.",
|
|
5391
5687
|
inputSchema: {
|
|
5392
5688
|
type: "object",
|
|
5393
5689
|
properties: {
|
|
@@ -5405,6 +5701,58 @@ const TOOLS = [
|
|
|
5405
5701
|
},
|
|
5406
5702
|
command: "motion.list",
|
|
5407
5703
|
},
|
|
5704
|
+
{
|
|
5705
|
+
name: "motion_variables",
|
|
5706
|
+
description:
|
|
5707
|
+
"Typed variables of a graphic (text, multiline, number, color, image, font, boolean, enum, list) with defaults/constraints, its current values and preset templateKey. source = an overlay's generatedFrom from project_read (template or HyperFrames HTML).",
|
|
5708
|
+
inputSchema: {
|
|
5709
|
+
type: "object",
|
|
5710
|
+
properties: { source: { type: "object" } },
|
|
5711
|
+
required: ["source"],
|
|
5712
|
+
},
|
|
5713
|
+
command: "motion.variables",
|
|
5714
|
+
},
|
|
5715
|
+
{
|
|
5716
|
+
name: "motion_list_presets",
|
|
5717
|
+
description:
|
|
5718
|
+
"List the active workspace's saved variable presets {name, templateKey, values}. Pass source or templateId to list only those for that graphic.",
|
|
5719
|
+
inputSchema: {
|
|
5720
|
+
type: "object",
|
|
5721
|
+
properties: { source: { type: "object" }, templateId: { type: "string" } },
|
|
5722
|
+
},
|
|
5723
|
+
command: "motion.list-presets",
|
|
5724
|
+
},
|
|
5725
|
+
{
|
|
5726
|
+
name: "motion_save_preset",
|
|
5727
|
+
description:
|
|
5728
|
+
"Save a named variable set (e.g. 'Client A lower third') for graphics of the same template in this workspace; same name replaces. Images are embedded.",
|
|
5729
|
+
inputSchema: {
|
|
5730
|
+
type: "object",
|
|
5731
|
+
properties: {
|
|
5732
|
+
source: { type: "object" },
|
|
5733
|
+
name: { type: "string" },
|
|
5734
|
+
variables: { type: "object" },
|
|
5735
|
+
},
|
|
5736
|
+
required: ["source", "name", "variables"],
|
|
5737
|
+
},
|
|
5738
|
+
command: "motion.save-preset",
|
|
5739
|
+
},
|
|
5740
|
+
{
|
|
5741
|
+
name: "motion_delete_preset",
|
|
5742
|
+
description:
|
|
5743
|
+
"Delete a saved variable preset by name; identify its template with source, templateId or templateKey.",
|
|
5744
|
+
inputSchema: {
|
|
5745
|
+
type: "object",
|
|
5746
|
+
properties: {
|
|
5747
|
+
name: { type: "string" },
|
|
5748
|
+
source: { type: "object" },
|
|
5749
|
+
templateId: { type: "string" },
|
|
5750
|
+
templateKey: { type: "string" },
|
|
5751
|
+
},
|
|
5752
|
+
required: ["name"],
|
|
5753
|
+
},
|
|
5754
|
+
command: "motion.delete-preset",
|
|
5755
|
+
},
|
|
5408
5756
|
{
|
|
5409
5757
|
name: "motion_generate",
|
|
5410
5758
|
description:
|
|
@@ -5414,6 +5762,15 @@ const TOOLS = [
|
|
|
5414
5762
|
properties: {
|
|
5415
5763
|
templateId: { type: "string" },
|
|
5416
5764
|
slots: { type: "object", description: "By key; image slots take a file path." },
|
|
5765
|
+
variables: {
|
|
5766
|
+
type: "object",
|
|
5767
|
+
description:
|
|
5768
|
+
"Typed values by id (templates[].variables from motion_list), validated, overriding slots. Images: path, URL or {url}; fonts: name or {name}.",
|
|
5769
|
+
},
|
|
5770
|
+
preset: {
|
|
5771
|
+
type: "string",
|
|
5772
|
+
description: "Saved variable preset name for this template (motion_list_presets).",
|
|
5773
|
+
},
|
|
5417
5774
|
aspectRatio: { type: "string", enum: ["16:9", "9:16", "1:1"] },
|
|
5418
5775
|
background: {
|
|
5419
5776
|
type: "string",
|
|
@@ -5426,7 +5783,7 @@ const TOOLS = [
|
|
|
5426
5783
|
description: "Length for templates with a durationRangeMs (clamped); ignored otherwise.",
|
|
5427
5784
|
},
|
|
5428
5785
|
},
|
|
5429
|
-
required: ["templateId"
|
|
5786
|
+
required: ["templateId"],
|
|
5430
5787
|
},
|
|
5431
5788
|
command: "motion.generate",
|
|
5432
5789
|
},
|
|
@@ -5469,7 +5826,8 @@ const TOOLS = [
|
|
|
5469
5826
|
},
|
|
5470
5827
|
{
|
|
5471
5828
|
name: "motion_catalog_item",
|
|
5472
|
-
description:
|
|
5829
|
+
description:
|
|
5830
|
+
"One catalog item: variables (raw and typedVariables), htmlPath, demoPath and a mount snippet.",
|
|
5473
5831
|
inputSchema: {
|
|
5474
5832
|
type: "object",
|
|
5475
5833
|
properties: { name: { type: "string" } },
|
|
@@ -5527,6 +5885,23 @@ const TOOLS = [
|
|
|
5527
5885
|
},
|
|
5528
5886
|
command: "motion.render-film",
|
|
5529
5887
|
},
|
|
5888
|
+
{
|
|
5889
|
+
name: "motion_edit_element",
|
|
5890
|
+
description:
|
|
5891
|
+
'Edit one graphic element, write stored HTML and re-render in place with the same timeline timing. Async jobId. selector: #id or [data-panda-element="handle"]. patch: text, x/y (paired translation in px), width/height (px), rotation (degrees), layer (z-index), styles (color, background-color, font-family, font-size, font-weight, border-radius, opacity). Mapped text-only edits preserve template slots; structural edits become custom HTML.',
|
|
5892
|
+
inputSchema: {
|
|
5893
|
+
type: "object",
|
|
5894
|
+
properties: {
|
|
5895
|
+
id: { type: "string" },
|
|
5896
|
+
regionId: { type: "string" },
|
|
5897
|
+
selector: { type: "string" },
|
|
5898
|
+
patch: { type: "object" },
|
|
5899
|
+
expectedRevision: { type: "number" },
|
|
5900
|
+
},
|
|
5901
|
+
required: ["id", "regionId", "selector", "patch"],
|
|
5902
|
+
},
|
|
5903
|
+
command: "motion.edit-element",
|
|
5904
|
+
},
|
|
5530
5905
|
{
|
|
5531
5906
|
name: "motion_render_html",
|
|
5532
5907
|
description:
|
|
@@ -6190,6 +6565,83 @@ const TOOLS = [
|
|
|
6190
6565
|
command: "job.get",
|
|
6191
6566
|
},
|
|
6192
6567
|
|
|
6568
|
+
// ── review items (pins / drawings the user placed on the preview) ──
|
|
6569
|
+
{
|
|
6570
|
+
name: "review_list",
|
|
6571
|
+
description:
|
|
6572
|
+
"The review items the user marked on the preview (numbered pins and drawings, each with the graphic or element under it), open ones by default. Each: id, timeMs and optional range (edited ms), position {x,y} (0..1 of the frame), drawing points, targetIds (graphic / element / region / clip ids), note, sentAt, and imagePath: a PNG crop of the frame with the drawing burned in. Defaults to the open project.",
|
|
6573
|
+
inputSchema: {
|
|
6574
|
+
type: "object",
|
|
6575
|
+
properties: {
|
|
6576
|
+
id: { type: "string", description: "Project id. Omit for the open project." },
|
|
6577
|
+
path: { type: "string" },
|
|
6578
|
+
includeResolved: { type: "boolean", description: "Also list resolved items." },
|
|
6579
|
+
},
|
|
6580
|
+
},
|
|
6581
|
+
command: "review.list",
|
|
6582
|
+
},
|
|
6583
|
+
{
|
|
6584
|
+
name: "review_resolve",
|
|
6585
|
+
description:
|
|
6586
|
+
"Mark review items done after you applied them: their pins leave the preview and the Pending edits tray. Needs the project id (or path) and the item ids.",
|
|
6587
|
+
inputSchema: {
|
|
6588
|
+
type: "object",
|
|
6589
|
+
properties: {
|
|
6590
|
+
id: { type: "string" },
|
|
6591
|
+
path: { type: "string" },
|
|
6592
|
+
annotationIds: { type: "array", items: { type: "string" } },
|
|
6593
|
+
},
|
|
6594
|
+
required: ["annotationIds"],
|
|
6595
|
+
},
|
|
6596
|
+
command: "review.resolve",
|
|
6597
|
+
},
|
|
6598
|
+
|
|
6599
|
+
{
|
|
6600
|
+
name: "review_add",
|
|
6601
|
+
description:
|
|
6602
|
+
"Add a review item (numbered pin) to the project. annotation: { timeMs (edited ms), position {x,y} 0..1, note, kind comment|freehand, targetIds?, range? {startMs,endMs}, drawing? [{x,y}], id? }.",
|
|
6603
|
+
inputSchema: {
|
|
6604
|
+
type: "object",
|
|
6605
|
+
properties: {
|
|
6606
|
+
id: { type: "string" },
|
|
6607
|
+
path: { type: "string" },
|
|
6608
|
+
annotation: { type: "object" },
|
|
6609
|
+
},
|
|
6610
|
+
required: ["annotation"],
|
|
6611
|
+
},
|
|
6612
|
+
command: "review.add",
|
|
6613
|
+
},
|
|
6614
|
+
{
|
|
6615
|
+
name: "review_update",
|
|
6616
|
+
description:
|
|
6617
|
+
"Change a review item's fields (note, range, position, targetIds, resolved); other fields are kept.",
|
|
6618
|
+
inputSchema: {
|
|
6619
|
+
type: "object",
|
|
6620
|
+
properties: {
|
|
6621
|
+
id: { type: "string" },
|
|
6622
|
+
path: { type: "string" },
|
|
6623
|
+
annotationId: { type: "string" },
|
|
6624
|
+
annotation: { type: "object", description: "Fields to change." },
|
|
6625
|
+
},
|
|
6626
|
+
required: ["annotationId", "annotation"],
|
|
6627
|
+
},
|
|
6628
|
+
command: "review.update",
|
|
6629
|
+
},
|
|
6630
|
+
{
|
|
6631
|
+
name: "review_delete",
|
|
6632
|
+
description: "Delete a review item. Use review_resolve for items you completed.",
|
|
6633
|
+
inputSchema: {
|
|
6634
|
+
type: "object",
|
|
6635
|
+
properties: {
|
|
6636
|
+
id: { type: "string" },
|
|
6637
|
+
path: { type: "string" },
|
|
6638
|
+
annotationId: { type: "string" },
|
|
6639
|
+
},
|
|
6640
|
+
required: ["annotationId"],
|
|
6641
|
+
},
|
|
6642
|
+
command: "review.delete",
|
|
6643
|
+
},
|
|
6644
|
+
|
|
6193
6645
|
// ── timeline helpers ────────────────────────────────────────────
|
|
6194
6646
|
{
|
|
6195
6647
|
name: "timeline_source_to_edited",
|
|
@@ -6221,6 +6673,35 @@ const TOOLS = [
|
|
|
6221
6673
|
command: "timeline.edited-to-source",
|
|
6222
6674
|
},
|
|
6223
6675
|
|
|
6676
|
+
{
|
|
6677
|
+
name: "agent_run_list",
|
|
6678
|
+
description:
|
|
6679
|
+
"List a project's agent runs that changed it (newest last): runId, sessionId, prompt, and whether each is currently undone.",
|
|
6680
|
+
inputSchema: {
|
|
6681
|
+
type: "object",
|
|
6682
|
+
properties: {
|
|
6683
|
+
projectId: { type: "string" },
|
|
6684
|
+
sessionId: { type: "string", description: "Only runs from this chat" },
|
|
6685
|
+
},
|
|
6686
|
+
required: ["projectId"],
|
|
6687
|
+
},
|
|
6688
|
+
command: "agent.run-list",
|
|
6689
|
+
},
|
|
6690
|
+
{
|
|
6691
|
+
name: "agent_run_restore",
|
|
6692
|
+
description:
|
|
6693
|
+
"Undo an agent run (restore the whole project as it was before the run), or redo it when it is undone. If edits were made after the run it changes nothing and answers confirmationRequired with laterEdits and a revision: show the user what would be lost, and only after they agree call again with confirmRevision set to that revision.",
|
|
6694
|
+
inputSchema: {
|
|
6695
|
+
type: "object",
|
|
6696
|
+
properties: {
|
|
6697
|
+
projectId: { type: "string" },
|
|
6698
|
+
runId: { type: "string" },
|
|
6699
|
+
confirmRevision: { type: "number" },
|
|
6700
|
+
},
|
|
6701
|
+
required: ["projectId", "runId"],
|
|
6702
|
+
},
|
|
6703
|
+
command: "agent.run-restore",
|
|
6704
|
+
},
|
|
6224
6705
|
...["read", "search"].map((action) => ({
|
|
6225
6706
|
name: `agent_session_${action}`,
|
|
6226
6707
|
description:
|
|
@@ -6302,6 +6783,8 @@ Publishing has daily limits (YouTube uploads and updates per license, social pos
|
|
|
6302
6783
|
|
|
6303
6784
|
PandaStudio problems (a capability gap like an imported font the export can't draw, a command failing unexpectedly, a render not matching the project): call system_report_issue yourself, then tell the user you reported it. Not for user mistakes, missing keys or taste.
|
|
6304
6785
|
|
|
6786
|
+
Review items: the user can pin notes and drawings on the preview and send them in one turn (listed in the editor context and the message, crops attached in item order). Apply each one, then review_resolve with its id. review_list shows the open ones with a crop image path.
|
|
6787
|
+
|
|
6305
6788
|
Destination decides everything: infer it (Shorts/TikTok/vertical/<=90s: shorts; YouTube/tutorial/>8 min: youtube-long; LinkedIn; Loom/internal), else ask once.
|
|
6306
6789
|
|
|
6307
6790
|
Full playbook (per-destination defaults, motion graphics, recipes, publishing): the PandaStudio skill. If you can't load it as a skill, call skill_read with no arguments at the start, then read the sections and reference docs the task needs. The rules above are only a summary.`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@writepanda/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.225.0",
|
|
4
4
|
"description": "Model Context Protocol server for PandaStudio. Exposes the desktop video editor's automation surface to Cursor, Continue, Cline, Claude Desktop, and any MCP-compliant client.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pandastudio",
|