creavit-studio-mcp 1.0.0 → 1.1.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 +62 -3
- package/package.json +1 -1
- package/src/protocol.mjs +1 -1
- package/src/tools/browserTools.mjs +336 -0
- package/src/tools/editorTools.mjs +121 -0
- package/src/tools/index.mjs +8 -1
package/README.md
CHANGED
|
@@ -50,14 +50,14 @@ npx creavit-studio-mcp --doctor
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
```
|
|
53
|
-
creavit-studio v1.
|
|
53
|
+
creavit-studio v1.1.0
|
|
54
54
|
|
|
55
|
-
✓
|
|
55
|
+
✓ 46 MCP tools defined
|
|
56
56
|
✓ Offline project reading available (.crvt files readable without the app)
|
|
57
57
|
✓ Connected to the app (v3.0.5, pid 61068)
|
|
58
58
|
Open windows: 2
|
|
59
59
|
Agent-connected renderers: main, editor
|
|
60
|
-
✓
|
|
60
|
+
✓ 52 bridge commands available
|
|
61
61
|
|
|
62
62
|
Everything looks good.
|
|
63
63
|
```
|
|
@@ -110,9 +110,20 @@ files directly and work with the app closed.
|
|
|
110
110
|
`creavit_editor_playback`, `creavit_editor_screenshot`,
|
|
111
111
|
`creavit_editor_export`, `creavit_editor_history`
|
|
112
112
|
|
|
113
|
+
**Appearance** — `creavit_editor_background_get`,
|
|
114
|
+
`creavit_editor_background_set`, `creavit_editor_camera_get`,
|
|
115
|
+
`creavit_editor_camera_set`, `creavit_editor_audio`
|
|
116
|
+
|
|
117
|
+
**Perspective** — `creavit_editor_perspectives`,
|
|
118
|
+
`creavit_editor_add_perspective`, `creavit_editor_update_perspective`,
|
|
119
|
+
`creavit_editor_remove_perspective`
|
|
120
|
+
|
|
113
121
|
**Recording** — `creavit_devices_list`, `creavit_recording_status`,
|
|
114
122
|
`creavit_recording_start`, `creavit_recording_stop`
|
|
115
123
|
|
|
124
|
+
**Browser walkthrough** — `creavit_browser_open`, `creavit_browser_scroll`,
|
|
125
|
+
`creavit_record_url_walkthrough`
|
|
126
|
+
|
|
116
127
|
**Escape hatches** — `creavit_capabilities`, `creavit_call`, `creavit_ipc`,
|
|
117
128
|
`creavit_eval`, `creavit_logs`
|
|
118
129
|
|
|
@@ -135,6 +146,52 @@ creavit_editor_screenshot VERIFY the result
|
|
|
135
146
|
creavit_project_save persist
|
|
136
147
|
```
|
|
137
148
|
|
|
149
|
+
### Background, camera, and mute
|
|
150
|
+
|
|
151
|
+
Use the typed tools instead of guessing generic setting keys:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
creavit_editor_background_set type=solid color=#10131a
|
|
155
|
+
creavit_editor_background_set type=gradient gradient={type, direction, colors}
|
|
156
|
+
creavit_editor_camera_set settings={visible,size,radius,shadow,mirror,...}
|
|
157
|
+
creavit_editor_audio action=mute
|
|
158
|
+
creavit_project_save
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`creavit_editor_audio action=mute` changes both preview audio and every clip's
|
|
162
|
+
timeline volume, so the saved project also exports without source audio.
|
|
163
|
+
|
|
164
|
+
### Record a URL walkthrough
|
|
165
|
+
|
|
166
|
+
`creavit_record_url_walkthrough` is the high-level workflow. It opens the URL,
|
|
167
|
+
selects that browser window, records a smooth section-by-section scroll, opens
|
|
168
|
+
the generated project, adds scroll-highlight zooms, optionally covers the whole
|
|
169
|
+
video with a perspective preset, mutes it, and saves it.
|
|
170
|
+
|
|
171
|
+
When the calling agent has inspected the page, it can pass `sections` entries
|
|
172
|
+
(`scrollPages`, `dwellMs`, `zoom`, `scale`, `x`, `y`) to dwell on and frame the
|
|
173
|
+
important parts instead of using the generic evenly spaced scroll plan.
|
|
174
|
+
|
|
175
|
+
Example input:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"url": "https://example.com",
|
|
180
|
+
"browser": "Zen Browser",
|
|
181
|
+
"scrollCount": 5,
|
|
182
|
+
"scrollIntervalMs": 1800,
|
|
183
|
+
"autoZoom": true,
|
|
184
|
+
"zoomScale": 1.8,
|
|
185
|
+
"perspective": true,
|
|
186
|
+
"perspectiveTemplate": "minimal-tilt",
|
|
187
|
+
"mute": true
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
For hand-directed recordings, use `creavit_browser_open`,
|
|
192
|
+
`creavit_recording_start`, and `creavit_browser_scroll` separately, then add
|
|
193
|
+
zooms/perspective with the editor tools.
|
|
194
|
+
|
|
138
195
|
Two things worth knowing:
|
|
139
196
|
|
|
140
197
|
- **Wait for the load.** After `creavit_project_open`, poll `creavit_editor_state`
|
|
@@ -151,6 +208,8 @@ Two things worth knowing:
|
|
|
151
208
|
- Every request requires a bearer token, regenerated on each app launch.
|
|
152
209
|
- The endpoint file is written with `0600` permissions.
|
|
153
210
|
- Arbitrary code execution (`creavit_eval`) is **off by default**.
|
|
211
|
+
- Browser scrolling uses macOS Accessibility. Enable Creavit Studio under
|
|
212
|
+
**System Settings → Privacy & Security → Accessibility** when macOS asks.
|
|
154
213
|
|
|
155
214
|
### Environment variables
|
|
156
215
|
|
package/package.json
CHANGED
package/src/protocol.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export const ENDPOINT_FILENAME = "agent-bridge.json";
|
|
|
6
6
|
|
|
7
7
|
export const MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
8
8
|
export const SERVER_NAME = "creavit-studio";
|
|
9
|
-
export const SERVER_VERSION = "1.
|
|
9
|
+
export const SERVER_VERSION = "1.1.0";
|
|
10
10
|
|
|
11
11
|
export const JSONRPC_ERRORS = {
|
|
12
12
|
PARSE_ERROR: -32700,
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// MCP araçları — tarayıcı açma/scroll ve uçtan uca URL walkthrough kaydı.
|
|
2
|
+
|
|
3
|
+
import { bridgeTool, schema, S, textResult } from "./defineTool.mjs";
|
|
4
|
+
import { callCommand } from "../bridgeClient.mjs";
|
|
5
|
+
|
|
6
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
|
|
8
|
+
const BROWSER_APP_NAMES = {
|
|
9
|
+
zen: "Zen Browser",
|
|
10
|
+
"zen browser": "Zen Browser",
|
|
11
|
+
safari: "Safari",
|
|
12
|
+
chrome: "Google Chrome",
|
|
13
|
+
"google chrome": "Google Chrome",
|
|
14
|
+
arc: "Arc",
|
|
15
|
+
firefox: "Firefox",
|
|
16
|
+
brave: "Brave Browser",
|
|
17
|
+
"brave browser": "Brave Browser",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function canonicalBrowser(value) {
|
|
21
|
+
const key = String(value || "zen").trim().toLowerCase();
|
|
22
|
+
return BROWSER_APP_NAMES[key] || value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function windowList(result) {
|
|
26
|
+
if (Array.isArray(result)) return result;
|
|
27
|
+
if (Array.isArray(result?.windows)) return result.windows;
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function searchableWindowText(windowInfo) {
|
|
32
|
+
return [
|
|
33
|
+
windowInfo?.appName,
|
|
34
|
+
windowInfo?.ownerName,
|
|
35
|
+
windowInfo?.name,
|
|
36
|
+
windowInfo?.title,
|
|
37
|
+
windowInfo?.windowTitle,
|
|
38
|
+
]
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.join(" ")
|
|
41
|
+
.toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findBrowserWindow(windows, browserName, titleContains) {
|
|
45
|
+
const browserNeedle = String(browserName || "").toLowerCase();
|
|
46
|
+
const titleNeedle = String(titleContains || "").trim().toLowerCase();
|
|
47
|
+
const candidates = windows.filter((entry) => {
|
|
48
|
+
const text = searchableWindowText(entry);
|
|
49
|
+
return text.includes(browserNeedle) ||
|
|
50
|
+
(browserNeedle === "google chrome" && text.includes("chrome")) ||
|
|
51
|
+
(browserNeedle === "zen browser" && text.includes("zen"));
|
|
52
|
+
});
|
|
53
|
+
if (titleNeedle) {
|
|
54
|
+
const titled = candidates.find((entry) => searchableWindowText(entry).includes(titleNeedle));
|
|
55
|
+
if (titled) return titled;
|
|
56
|
+
}
|
|
57
|
+
return candidates.find((entry) => entry?.isOnScreen !== false && Number(entry?.id) > 0) ||
|
|
58
|
+
candidates.find((entry) => Number(entry?.id) > 0) ||
|
|
59
|
+
null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function waitForBrowserWindow(browserName, titleContains, timeoutMs) {
|
|
63
|
+
const deadline = Date.now() + timeoutMs;
|
|
64
|
+
let lastWindows = [];
|
|
65
|
+
while (Date.now() < deadline) {
|
|
66
|
+
const devices = await callCommand("devices.list", { kinds: ["windows"] }, 60_000);
|
|
67
|
+
lastWindows = windowList(devices?.windows);
|
|
68
|
+
const found = findBrowserWindow(lastWindows, browserName, titleContains);
|
|
69
|
+
if (found) return found;
|
|
70
|
+
await sleep(500);
|
|
71
|
+
}
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Could not find a recordable ${browserName} window. Opened windows: ${lastWindows
|
|
74
|
+
.map((entry) => searchableWindowText(entry))
|
|
75
|
+
.filter(Boolean)
|
|
76
|
+
.slice(0, 12)
|
|
77
|
+
.join(" | ")}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function waitForEditor(timeoutMs = 300_000) {
|
|
82
|
+
const deadline = Date.now() + timeoutMs;
|
|
83
|
+
let lastError = null;
|
|
84
|
+
while (Date.now() < deadline) {
|
|
85
|
+
try {
|
|
86
|
+
const state = await callCommand("editor.getState", {}, 15_000);
|
|
87
|
+
if (Number(state?.duration) > 0 && state?.playerMounted) return state;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
lastError = error;
|
|
90
|
+
}
|
|
91
|
+
await sleep(1000);
|
|
92
|
+
}
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Recording finished but the editor did not become ready within ${Math.round(timeoutMs / 1000)}s${
|
|
95
|
+
lastError ? `: ${lastError.message}` : ""
|
|
96
|
+
}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function clearZooms() {
|
|
101
|
+
const result = await callCommand("editor.listZooms", {});
|
|
102
|
+
const ranges = Array.isArray(result?.zoomRanges) ? result.zoomRanges : [];
|
|
103
|
+
for (let index = ranges.length - 1; index >= 0; index -= 1) {
|
|
104
|
+
await callCommand("editor.removeZoom", { index });
|
|
105
|
+
}
|
|
106
|
+
return ranges.length;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function browserTools() {
|
|
110
|
+
return [
|
|
111
|
+
bridgeTool({
|
|
112
|
+
name: "creavit_browser_open",
|
|
113
|
+
command: "browser.open",
|
|
114
|
+
description:
|
|
115
|
+
"Opens an http(s) URL in Zen Browser, Safari, Chrome, Arc, Firefox, Brave, or the default browser.",
|
|
116
|
+
inputSchema: schema(
|
|
117
|
+
{
|
|
118
|
+
url: S.string("http(s) URL to open"),
|
|
119
|
+
browser: S.string("Browser name (default: system browser)"),
|
|
120
|
+
},
|
|
121
|
+
["url"],
|
|
122
|
+
),
|
|
123
|
+
}),
|
|
124
|
+
|
|
125
|
+
bridgeTool({
|
|
126
|
+
name: "creavit_browser_scroll",
|
|
127
|
+
command: "browser.scroll",
|
|
128
|
+
description:
|
|
129
|
+
"Activates a browser and scrolls by page. Useful while recording. Requires macOS Accessibility permission for Creavit Studio.",
|
|
130
|
+
inputSchema: schema(
|
|
131
|
+
{
|
|
132
|
+
browser: S.string("Zen Browser, Safari, Google Chrome, Arc, Firefox, or Brave Browser"),
|
|
133
|
+
direction: S.string("Scroll direction", {
|
|
134
|
+
enum: ["down", "up", "top", "bottom"],
|
|
135
|
+
}),
|
|
136
|
+
style: S.string("page or smooth (smooth uses small animated steps)", {
|
|
137
|
+
enum: ["page", "smooth"],
|
|
138
|
+
}),
|
|
139
|
+
count: S.number("Number of page-key presses (1-50)"),
|
|
140
|
+
intervalMs: S.number("Pause after each press in milliseconds"),
|
|
141
|
+
},
|
|
142
|
+
["browser"],
|
|
143
|
+
),
|
|
144
|
+
}),
|
|
145
|
+
|
|
146
|
+
{
|
|
147
|
+
name: "creavit_record_url_walkthrough",
|
|
148
|
+
description:
|
|
149
|
+
"End-to-end website recording: opens a URL, finds that browser window, records it while scrolling, opens the result in Creavit Studio, optionally replaces zooms with automatic scroll-highlight zooms, applies a full-video perspective, and guarantees a muted export timeline. This operation can take several minutes.",
|
|
150
|
+
inputSchema: schema(
|
|
151
|
+
{
|
|
152
|
+
url: S.string("http(s) page to record"),
|
|
153
|
+
browser: S.string("Browser to use (default: Zen Browser)"),
|
|
154
|
+
titleContains: S.string("Optional browser window title hint"),
|
|
155
|
+
loadWaitMs: S.number("Wait for the page to load before recording (default 3000)"),
|
|
156
|
+
leadInMs: S.number("Still time at the top before scrolling (default 1500)"),
|
|
157
|
+
scrollCount: S.number("Number of Page Down scrolls (default 4, max 20)"),
|
|
158
|
+
scrollIntervalMs: S.number("Time between scrolls / section dwell (default 1800)"),
|
|
159
|
+
sections: S.array(
|
|
160
|
+
"Optional agent-planned important sections. Each item: {scrollPages,dwellMs,zoom,scale,x,y}. Replaces scrollCount when present.",
|
|
161
|
+
{ type: "object" },
|
|
162
|
+
),
|
|
163
|
+
tailMs: S.number("Still time after the last scroll (default 1500)"),
|
|
164
|
+
mute: S.bool("Disable microphone/system audio and mute all export clips (default true)"),
|
|
165
|
+
autoZoom: S.bool("Add zooms around scroll destinations (default true)"),
|
|
166
|
+
replaceZooms: S.bool("Remove recorder-created zooms first (default true)"),
|
|
167
|
+
zoomScale: S.number("Automatic zoom scale (default 1.7)"),
|
|
168
|
+
perspective: S.bool("Apply perspective to the complete result (default false)"),
|
|
169
|
+
perspectiveTemplate: S.string("Perspective template id (default minimal-tilt)"),
|
|
170
|
+
save: S.bool("Save the generated project after editing (default true)"),
|
|
171
|
+
},
|
|
172
|
+
["url"],
|
|
173
|
+
),
|
|
174
|
+
run: async (args) => {
|
|
175
|
+
const browser = canonicalBrowser(args.browser || "Zen Browser");
|
|
176
|
+
const loadWaitMs = Math.max(500, Math.min(60_000, Number(args.loadWaitMs) || 3000));
|
|
177
|
+
const leadInMs = Math.max(300, Math.min(30_000, Number(args.leadInMs) || 1500));
|
|
178
|
+
const scrollCount = Math.max(
|
|
179
|
+
0,
|
|
180
|
+
Math.min(20, Math.round(args.scrollCount === undefined ? 4 : Number(args.scrollCount))),
|
|
181
|
+
);
|
|
182
|
+
const scrollIntervalMs = Math.max(
|
|
183
|
+
400,
|
|
184
|
+
Math.min(15_000, Number(args.scrollIntervalMs) || 1800),
|
|
185
|
+
);
|
|
186
|
+
const tailMs = Math.max(300, Math.min(30_000, Number(args.tailMs) || 1500));
|
|
187
|
+
const muted = args.mute !== false;
|
|
188
|
+
const autoZoom = args.autoZoom !== false;
|
|
189
|
+
const replaceZooms = args.replaceZooms !== false;
|
|
190
|
+
const zoomScale = Math.max(1, Math.min(5, Number(args.zoomScale) || 1.7));
|
|
191
|
+
const sectionPlan = Array.isArray(args.sections) && args.sections.length
|
|
192
|
+
? args.sections.slice(0, 20).map((section) => {
|
|
193
|
+
const scrollPages = Number(section?.scrollPages);
|
|
194
|
+
const focusX = Number(section?.x);
|
|
195
|
+
const focusY = Number(section?.y);
|
|
196
|
+
return {
|
|
197
|
+
scrollPages: Math.max(
|
|
198
|
+
0,
|
|
199
|
+
Math.min(
|
|
200
|
+
5,
|
|
201
|
+
Math.round(Number.isFinite(scrollPages) ? scrollPages : 1),
|
|
202
|
+
),
|
|
203
|
+
),
|
|
204
|
+
dwellMs: Math.max(
|
|
205
|
+
400,
|
|
206
|
+
Math.min(15_000, Number(section?.dwellMs) || scrollIntervalMs),
|
|
207
|
+
),
|
|
208
|
+
zoom: section?.zoom !== false,
|
|
209
|
+
scale: Math.max(
|
|
210
|
+
1,
|
|
211
|
+
Math.min(5, Number(section?.scale) || zoomScale),
|
|
212
|
+
),
|
|
213
|
+
x: Math.max(
|
|
214
|
+
0,
|
|
215
|
+
Math.min(1, Number.isFinite(focusX) ? focusX : 0.5),
|
|
216
|
+
),
|
|
217
|
+
y: Math.max(
|
|
218
|
+
0,
|
|
219
|
+
Math.min(1, Number.isFinite(focusY) ? focusY : 0.5),
|
|
220
|
+
),
|
|
221
|
+
};
|
|
222
|
+
})
|
|
223
|
+
: Array.from({ length: scrollCount }, (_, index) => ({
|
|
224
|
+
scrollPages: 1,
|
|
225
|
+
dwellMs: scrollIntervalMs,
|
|
226
|
+
zoom: true,
|
|
227
|
+
scale: zoomScale,
|
|
228
|
+
x: index % 2 === 0 ? 0.42 : 0.58,
|
|
229
|
+
y: 0.5,
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
await callCommand("browser.open", { url: args.url, browser }, 30_000);
|
|
233
|
+
await sleep(loadWaitMs);
|
|
234
|
+
await callCommand("browser.scroll", { browser, direction: "top", style: "page", count: 1, intervalMs: 100 });
|
|
235
|
+
const browserWindow = await waitForBrowserWindow(browser, args.titleContains, 20_000);
|
|
236
|
+
|
|
237
|
+
await callCommand(
|
|
238
|
+
"recording.start",
|
|
239
|
+
{
|
|
240
|
+
options: {
|
|
241
|
+
startScreen: true,
|
|
242
|
+
cameraEnabled: false,
|
|
243
|
+
micEnabled: !muted,
|
|
244
|
+
systemAudioEnabled: !muted,
|
|
245
|
+
recordingSource: {
|
|
246
|
+
type: "window",
|
|
247
|
+
windowId: browserWindow.id,
|
|
248
|
+
windowInfo: browserWindow,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
180_000,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const recordingStartedAt = Date.now();
|
|
256
|
+
const zoomWindows = [];
|
|
257
|
+
await sleep(leadInMs);
|
|
258
|
+
for (let index = 0; index < sectionPlan.length; index += 1) {
|
|
259
|
+
const section = sectionPlan[index];
|
|
260
|
+
if (section.scrollPages > 0) {
|
|
261
|
+
await callCommand(
|
|
262
|
+
"browser.scroll",
|
|
263
|
+
{
|
|
264
|
+
browser,
|
|
265
|
+
direction: "down",
|
|
266
|
+
style: "smooth",
|
|
267
|
+
count: section.scrollPages,
|
|
268
|
+
intervalMs: 120,
|
|
269
|
+
},
|
|
270
|
+
30_000,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
const arrivedAt = (Date.now() - recordingStartedAt) / 1000;
|
|
274
|
+
if (section.zoom) {
|
|
275
|
+
zoomWindows.push({
|
|
276
|
+
start: Math.max(0, arrivedAt + 0.12),
|
|
277
|
+
end: Math.max(0.5, arrivedAt + section.dwellMs / 1000 - 0.12),
|
|
278
|
+
scale: section.scale,
|
|
279
|
+
x: section.x,
|
|
280
|
+
y: section.y,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
await sleep(section.dwellMs);
|
|
284
|
+
}
|
|
285
|
+
await sleep(tailMs);
|
|
286
|
+
await callCommand("recording.stop", {}, 600_000);
|
|
287
|
+
|
|
288
|
+
const editorState = await waitForEditor(600_000);
|
|
289
|
+
const applied = { muted: false, zooms: [], perspective: null, saved: false };
|
|
290
|
+
|
|
291
|
+
if (muted) {
|
|
292
|
+
await callCommand("editor.setMuted", { muted: true });
|
|
293
|
+
applied.muted = true;
|
|
294
|
+
}
|
|
295
|
+
if (autoZoom) {
|
|
296
|
+
if (replaceZooms) applied.removedZooms = await clearZooms();
|
|
297
|
+
for (let index = 0; index < zoomWindows.length; index += 1) {
|
|
298
|
+
const window = zoomWindows[index];
|
|
299
|
+
const start = Math.min(window.start, Math.max(0, editorState.duration - 0.25));
|
|
300
|
+
const end = Math.min(window.end, editorState.duration);
|
|
301
|
+
if (end - start < 0.25) continue;
|
|
302
|
+
const result = await callCommand("editor.addZoom", {
|
|
303
|
+
start,
|
|
304
|
+
end,
|
|
305
|
+
scale: window.scale,
|
|
306
|
+
x: window.x,
|
|
307
|
+
y: window.y,
|
|
308
|
+
});
|
|
309
|
+
applied.zooms.push(result?.added || result);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (args.perspective === true) {
|
|
313
|
+
const result = await callCommand("editor.addPerspective", {
|
|
314
|
+
fullDuration: true,
|
|
315
|
+
templateId: args.perspectiveTemplate || "minimal-tilt",
|
|
316
|
+
});
|
|
317
|
+
applied.perspective = result?.added || result;
|
|
318
|
+
}
|
|
319
|
+
if (args.save !== false) {
|
|
320
|
+
await callCommand("editor.saveProject", {}, 180_000);
|
|
321
|
+
applied.saved = true;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return textResult({
|
|
325
|
+
ok: true,
|
|
326
|
+
url: args.url,
|
|
327
|
+
browser,
|
|
328
|
+
window: browserWindow,
|
|
329
|
+
duration: editorState.duration,
|
|
330
|
+
projectFilePath: editorState.projectFilePath,
|
|
331
|
+
applied,
|
|
332
|
+
});
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
];
|
|
336
|
+
}
|
|
@@ -49,6 +49,81 @@ export function editorTools() {
|
|
|
49
49
|
}),
|
|
50
50
|
}),
|
|
51
51
|
|
|
52
|
+
bridgeTool({
|
|
53
|
+
name: "creavit_editor_background_get",
|
|
54
|
+
command: "editor.getBackground",
|
|
55
|
+
description:
|
|
56
|
+
"Reads the complete canvas background configuration: solid color, gradient, image, blur, and dynamic pattern values.",
|
|
57
|
+
inputSchema: schema(),
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
bridgeTool({
|
|
61
|
+
name: "creavit_editor_background_set",
|
|
62
|
+
command: "editor.setBackground",
|
|
63
|
+
description:
|
|
64
|
+
"Sets the canvas background. For solid use type='solid' and color. For gradient pass at least two stops as {type:'linear'|'radial', direction:'to-right', colors:[{color:'#...',position:0},...]}. Call creavit_project_save afterwards.",
|
|
65
|
+
inputSchema: schema(
|
|
66
|
+
{
|
|
67
|
+
type: S.string("Background type", {
|
|
68
|
+
enum: ["solid", "color", "gradient", "image", "dynamic"],
|
|
69
|
+
}),
|
|
70
|
+
color: S.string("CSS color for a solid background"),
|
|
71
|
+
image: S.string("Image URL or local path"),
|
|
72
|
+
blur: S.number("Image blur amount"),
|
|
73
|
+
gradient: S.object(
|
|
74
|
+
"Gradient configuration: {type, direction, colors:[{color,position}]}.",
|
|
75
|
+
),
|
|
76
|
+
dynamic: S.object("Dynamic pattern configuration"),
|
|
77
|
+
},
|
|
78
|
+
["type"],
|
|
79
|
+
),
|
|
80
|
+
mapResult: (result) =>
|
|
81
|
+
textResult({ ...result, note: "Applied. Call creavit_project_save to persist." }),
|
|
82
|
+
}),
|
|
83
|
+
|
|
84
|
+
bridgeTool({
|
|
85
|
+
name: "creavit_editor_camera_get",
|
|
86
|
+
command: "editor.getCamera",
|
|
87
|
+
description: "Reads all camera overlay settings.",
|
|
88
|
+
inputSchema: schema(),
|
|
89
|
+
}),
|
|
90
|
+
|
|
91
|
+
bridgeTool({
|
|
92
|
+
name: "creavit_editor_camera_set",
|
|
93
|
+
command: "editor.setCamera",
|
|
94
|
+
description:
|
|
95
|
+
"Partially updates camera overlay settings: visible, size, radius, shadow, position crop, cropZoom, followMouse, oscillationMovement, mirror, aspectRatio, border, background removal, mergeWithCursor, lockSizeOnZoom and sizeOnZoom. Call creavit_project_save afterwards.",
|
|
96
|
+
inputSchema: schema(
|
|
97
|
+
{ settings: S.object("Camera settings to update") },
|
|
98
|
+
["settings"],
|
|
99
|
+
),
|
|
100
|
+
mapResult: (result) =>
|
|
101
|
+
textResult({ ...result, note: "Applied. Call creavit_project_save to persist." }),
|
|
102
|
+
}),
|
|
103
|
+
|
|
104
|
+
{
|
|
105
|
+
name: "creavit_editor_audio",
|
|
106
|
+
description:
|
|
107
|
+
"Reads audio state or mutes/unmutes both preview and every timeline clip used by export. Use action='mute' to guarantee that the exported video has no source audio.",
|
|
108
|
+
inputSchema: schema(
|
|
109
|
+
{
|
|
110
|
+
action: S.string("status, mute, or unmute", {
|
|
111
|
+
enum: ["status", "mute", "unmute"],
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
["action"],
|
|
115
|
+
),
|
|
116
|
+
run: async ({ action }) => {
|
|
117
|
+
if (action === "status") return textResult(await callCommand("editor.getAudio", {}));
|
|
118
|
+
if (action !== "mute" && action !== "unmute") {
|
|
119
|
+
throw new Error("`action` must be status, mute, or unmute");
|
|
120
|
+
}
|
|
121
|
+
return textResult(
|
|
122
|
+
await callCommand("editor.setMuted", { muted: action === "mute" }),
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
|
|
52
127
|
bridgeTool({
|
|
53
128
|
name: "creavit_editor_list_zooms",
|
|
54
129
|
command: "editor.listZooms",
|
|
@@ -99,6 +174,52 @@ export function editorTools() {
|
|
|
99
174
|
}),
|
|
100
175
|
}),
|
|
101
176
|
|
|
177
|
+
bridgeTool({
|
|
178
|
+
name: "creavit_editor_perspectives",
|
|
179
|
+
command: "editor.listPerspectives",
|
|
180
|
+
description: "Lists all perspective (3D animation) ranges on the timeline.",
|
|
181
|
+
inputSchema: schema(),
|
|
182
|
+
}),
|
|
183
|
+
|
|
184
|
+
bridgeTool({
|
|
185
|
+
name: "creavit_editor_add_perspective",
|
|
186
|
+
command: "editor.addPerspective",
|
|
187
|
+
description:
|
|
188
|
+
"Adds a Creavit perspective range. Set fullDuration=true to cover the complete video. Templates include slant-left, slant-right, hero, lift, corner-left, corner-right, floating, perspective-left, perspective-right, minimal-tilt and cinematic-wide.",
|
|
189
|
+
inputSchema: schema({
|
|
190
|
+
start: S.number("Start time in seconds"),
|
|
191
|
+
end: S.number("End time in seconds"),
|
|
192
|
+
fullDuration: S.bool("Cover the complete video, ignoring start/end"),
|
|
193
|
+
templateId: S.string("Perspective template id"),
|
|
194
|
+
settings: S.object("Optional overrides for the perspective range"),
|
|
195
|
+
}),
|
|
196
|
+
}),
|
|
197
|
+
|
|
198
|
+
bridgeTool({
|
|
199
|
+
name: "creavit_editor_update_perspective",
|
|
200
|
+
command: "editor.updatePerspective",
|
|
201
|
+
description: "Updates an existing perspective range by id or zero-based index.",
|
|
202
|
+
inputSchema: schema(
|
|
203
|
+
{
|
|
204
|
+
id: S.string("Perspective range ID"),
|
|
205
|
+
index: S.number("Perspective range index"),
|
|
206
|
+
patch: S.object("Fields to update"),
|
|
207
|
+
},
|
|
208
|
+
["patch"],
|
|
209
|
+
),
|
|
210
|
+
}),
|
|
211
|
+
|
|
212
|
+
bridgeTool({
|
|
213
|
+
name: "creavit_editor_remove_perspective",
|
|
214
|
+
command: "editor.removePerspective",
|
|
215
|
+
description: "Deletes a perspective range by id/index, or all perspective ranges with all=true.",
|
|
216
|
+
inputSchema: schema({
|
|
217
|
+
id: S.string("Perspective range ID"),
|
|
218
|
+
index: S.number("Perspective range index"),
|
|
219
|
+
all: S.bool("Delete every perspective range"),
|
|
220
|
+
}),
|
|
221
|
+
}),
|
|
222
|
+
|
|
102
223
|
bridgeTool({
|
|
103
224
|
name: "creavit_editor_segments",
|
|
104
225
|
command: "editor.listSegments",
|
package/src/tools/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { projectTools } from "./projectTools.mjs";
|
|
4
4
|
import { editorTools } from "./editorTools.mjs";
|
|
5
5
|
import { systemTools } from "./systemTools.mjs";
|
|
6
|
+
import { browserTools } from "./browserTools.mjs";
|
|
6
7
|
import { escapeTools } from "./escapeTools.mjs";
|
|
7
8
|
import { errorResult } from "./defineTool.mjs";
|
|
8
9
|
import { BridgeUnavailableError } from "../bridgeClient.mjs";
|
|
@@ -11,7 +12,13 @@ let registry = null;
|
|
|
11
12
|
|
|
12
13
|
function buildRegistry() {
|
|
13
14
|
if (registry) return registry;
|
|
14
|
-
const all = [
|
|
15
|
+
const all = [
|
|
16
|
+
...systemTools(),
|
|
17
|
+
...projectTools(),
|
|
18
|
+
...editorTools(),
|
|
19
|
+
...browserTools(),
|
|
20
|
+
...escapeTools(),
|
|
21
|
+
];
|
|
15
22
|
|
|
16
23
|
const byName = new Map();
|
|
17
24
|
for (const tool of all) {
|