creavit-studio-mcp 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -17
- package/package.json +1 -1
- package/src/appLauncher.mjs +104 -0
- package/src/bridgeClient.mjs +33 -5
- package/src/protocol.mjs +1 -1
- package/src/tools/browserTools.mjs +290 -190
- package/src/tools/systemTools.mjs +30 -2
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.2.0
|
|
54
54
|
|
|
55
|
-
✓
|
|
55
|
+
✓ 50 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
|
+
✓ 56 bridge commands available
|
|
61
61
|
|
|
62
62
|
Everything looks good.
|
|
63
63
|
```
|
|
@@ -121,8 +121,9 @@ files directly and work with the app closed.
|
|
|
121
121
|
**Recording** — `creavit_devices_list`, `creavit_recording_status`,
|
|
122
122
|
`creavit_recording_start`, `creavit_recording_stop`
|
|
123
123
|
|
|
124
|
-
**Browser walkthrough** — `creavit_browser_open`, `
|
|
125
|
-
`
|
|
124
|
+
**Browser walkthrough** — `creavit_browser_open`, `creavit_browser_inspect`,
|
|
125
|
+
`creavit_browser_focus`, `creavit_browser_interact`, `creavit_browser_scroll`,
|
|
126
|
+
`creavit_browser_close`, `creavit_record_url_walkthrough`
|
|
126
127
|
|
|
127
128
|
**Escape hatches** — `creavit_capabilities`, `creavit_call`, `creavit_ipc`,
|
|
128
129
|
`creavit_eval`, `creavit_logs`
|
|
@@ -163,13 +164,18 @@ timeline volume, so the saved project also exports without source audio.
|
|
|
163
164
|
|
|
164
165
|
### Record a URL walkthrough
|
|
165
166
|
|
|
166
|
-
`creavit_record_url_walkthrough` is the high-level workflow.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
167
|
+
`creavit_record_url_walkthrough` is the high-level workflow. By default it opens
|
|
168
|
+
the URL in a frameless site-only capture window (`16:9`, `4:3`, `1:1`, `9:16`,
|
|
169
|
+
or another requested ratio) and waits for the DOM, fonts, and images before
|
|
170
|
+
recording. It then executes ordered semantic actions with the real macOS mouse,
|
|
171
|
+
so the normal Creavit custom-cursor track receives authentic move/click events.
|
|
172
|
+
It trims the timeline to `durationMs`, adds
|
|
173
|
+
scroll-highlight zooms, optionally covers the whole video with a perspective
|
|
174
|
+
preset, mutes it, and saves it.
|
|
175
|
+
|
|
176
|
+
When the calling agent has inspected the page, it can pass ordered `steps`
|
|
177
|
+
(`scrollPages`, `dwellMs`, `zoom`, `scale`, `x`, `y`, and optionally
|
|
178
|
+
`selector`/`text` plus `action`) to dwell on, hover, click, type, and frame the
|
|
173
179
|
important parts instead of using the generic evenly spaced scroll plan.
|
|
174
180
|
|
|
175
181
|
Example input:
|
|
@@ -177,9 +183,15 @@ Example input:
|
|
|
177
183
|
```json
|
|
178
184
|
{
|
|
179
185
|
"url": "https://example.com",
|
|
180
|
-
"
|
|
181
|
-
"
|
|
182
|
-
"
|
|
186
|
+
"cleanWindow": true,
|
|
187
|
+
"aspectRatio": "4:3",
|
|
188
|
+
"durationMs": 10000,
|
|
189
|
+
"steps": [
|
|
190
|
+
{ "action": "click", "text": "Features", "dwellMs": 1000 },
|
|
191
|
+
{ "action": "scrollTo", "text": "Get Started", "dwellMs": 700 },
|
|
192
|
+
{ "action": "click", "text": "Get Started", "dwellMs": 3000 }
|
|
193
|
+
],
|
|
194
|
+
"controlSystemMouse": true,
|
|
183
195
|
"autoZoom": true,
|
|
184
196
|
"zoomScale": 1.8,
|
|
185
197
|
"perspective": true,
|
|
@@ -208,8 +220,9 @@ Two things worth knowing:
|
|
|
208
220
|
- Every request requires a bearer token, regenerated on each app launch.
|
|
209
221
|
- The endpoint file is written with `0600` permissions.
|
|
210
222
|
- Arbitrary code execution (`creavit_eval`) is **off by default**.
|
|
211
|
-
-
|
|
212
|
-
|
|
223
|
+
- Page loading and scrolling inside a clean capture window do not require
|
|
224
|
+
Accessibility permission. Real system mouse move/click automation and
|
|
225
|
+
external-browser scrolling do require macOS Accessibility permission.
|
|
213
226
|
|
|
214
227
|
### Environment variables
|
|
215
228
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// MCP sunucusu — uygulama kapalıysa başlatma.
|
|
2
|
+
//
|
|
3
|
+
// Köprü yalnızca uygulama çalışırken vardır. Ajanın "önce uygulamayı sen aç"
|
|
4
|
+
// diyememesi her oturumun başında elle müdahale gerektiriyordu. Burası
|
|
5
|
+
// uygulamayı bulur, açar ve köprü hazır olana kadar bekler.
|
|
6
|
+
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
const APP_BUNDLE_NAME = "Creavit Studio.app";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_APP_PATHS = [
|
|
15
|
+
path.join("/Applications", APP_BUNDLE_NAME),
|
|
16
|
+
path.join(os.homedir(), "Applications", APP_BUNDLE_NAME),
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
/** Açılışın tamamlanması için verilen süre; soğuk başlangıç yavaş olabilir. */
|
|
20
|
+
export const DEFAULT_LAUNCH_TIMEOUT_MS = 45_000;
|
|
21
|
+
const POLL_INTERVAL_MS = 500;
|
|
22
|
+
|
|
23
|
+
export class AppLaunchError extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "AppLaunchError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Uygulama paketinin yolu: env override > /Applications > ~/Applications */
|
|
31
|
+
export function resolveAppPath() {
|
|
32
|
+
const fromEnv = process.env.CREAVIT_APP_PATH;
|
|
33
|
+
const candidates = fromEnv ? [fromEnv, ...DEFAULT_APP_PATHS] : DEFAULT_APP_PATHS;
|
|
34
|
+
|
|
35
|
+
for (const candidate of candidates) {
|
|
36
|
+
try {
|
|
37
|
+
if (candidate && fs.existsSync(candidate)) return candidate;
|
|
38
|
+
} catch (_) {}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Otomatik başlatma kapatılabilir olmalı (CI, kısıtlı ortam). */
|
|
44
|
+
export function isAutoLaunchEnabled() {
|
|
45
|
+
return process.env.CREAVIT_MCP_AUTOLAUNCH !== "0";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Uygulamayı açar (beklemez). macOS'ta `open` kullanılır: zaten açıksa yeni
|
|
52
|
+
* kopya başlatmaz, kapalıysa başlatır.
|
|
53
|
+
*/
|
|
54
|
+
export function launchApp() {
|
|
55
|
+
if (process.platform !== "darwin") {
|
|
56
|
+
throw new AppLaunchError(
|
|
57
|
+
"Automatic launch is only supported on macOS. Start Creavit Studio manually.",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const appPath = resolveAppPath();
|
|
62
|
+
if (!appPath) {
|
|
63
|
+
throw new AppLaunchError(
|
|
64
|
+
`Could not find ${APP_BUNDLE_NAME} in /Applications or ~/Applications. ` +
|
|
65
|
+
"Set CREAVIT_APP_PATH to the bundle path, or start the app manually.",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const child = spawn("open", ["-a", appPath], {
|
|
70
|
+
detached: true,
|
|
71
|
+
stdio: "ignore",
|
|
72
|
+
});
|
|
73
|
+
child.unref();
|
|
74
|
+
return appPath;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Uygulama çalışmıyorsa başlatır ve köprü yanıt verene kadar bekler.
|
|
79
|
+
*
|
|
80
|
+
* @param {object} options
|
|
81
|
+
* @param {() => Promise<boolean>} options.isRunning Köprü sağlık kontrolü
|
|
82
|
+
* @param {number} [options.timeoutMs]
|
|
83
|
+
* @returns {Promise<{ launched: boolean, appPath: string|null }>}
|
|
84
|
+
*/
|
|
85
|
+
export async function ensureAppRunning({
|
|
86
|
+
isRunning,
|
|
87
|
+
timeoutMs = DEFAULT_LAUNCH_TIMEOUT_MS,
|
|
88
|
+
}) {
|
|
89
|
+
if (await isRunning()) return { launched: false, appPath: null };
|
|
90
|
+
|
|
91
|
+
const appPath = launchApp();
|
|
92
|
+
const deadline = Date.now() + timeoutMs;
|
|
93
|
+
|
|
94
|
+
while (Date.now() < deadline) {
|
|
95
|
+
await delay(POLL_INTERVAL_MS);
|
|
96
|
+
if (await isRunning()) return { launched: true, appPath };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
throw new AppLaunchError(
|
|
100
|
+
`Launched ${appPath} but its agent bridge did not become ready within ` +
|
|
101
|
+
`${Math.round(timeoutMs / 1000)}s. The app may still be starting, or the ` +
|
|
102
|
+
"bridge may be disabled (CREAVIT_AGENT_BRIDGE=0).",
|
|
103
|
+
);
|
|
104
|
+
}
|
package/src/bridgeClient.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import fs from "node:fs";
|
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { ENDPOINT_FILENAME, APP_NOT_RUNNING_HINT } from "./protocol.mjs";
|
|
10
|
+
import { ensureAppRunning, isAutoLaunchEnabled } from "./appLauncher.mjs";
|
|
10
11
|
|
|
11
12
|
const DEFAULT_ENDPOINT_DIRS = [
|
|
12
13
|
path.join(os.homedir(), "Library", "Application Support", "creavit-studio"),
|
|
@@ -107,12 +108,39 @@ async function request(route, { method = "GET", body, timeoutMs = 120_000 } = {}
|
|
|
107
108
|
return payload.result;
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
// Otomatik başlatma süreç başına EN FAZLA BİR KEZ denenir. Her başarısız
|
|
112
|
+
// çağrıda yeniden denemek, uygulama gerçekten açılamıyorsa her aracı
|
|
113
|
+
// dakikalarca bekletirdi.
|
|
114
|
+
let autoLaunchAttempt = null;
|
|
115
|
+
|
|
116
|
+
async function tryAutoLaunch() {
|
|
117
|
+
if (!isAutoLaunchEnabled()) return false;
|
|
118
|
+
if (!autoLaunchAttempt) {
|
|
119
|
+
autoLaunchAttempt = ensureAppRunning({ isRunning: isAppRunning })
|
|
120
|
+
.then(() => true)
|
|
121
|
+
.catch(() => false);
|
|
122
|
+
}
|
|
123
|
+
return autoLaunchAttempt;
|
|
124
|
+
}
|
|
125
|
+
|
|
110
126
|
export async function callCommand(command, params, timeoutMs) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
const send = () =>
|
|
128
|
+
request("/rpc", {
|
|
129
|
+
method: "POST",
|
|
130
|
+
body: { command, params: params || {}, timeoutMs },
|
|
131
|
+
timeoutMs: timeoutMs || 120_000,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
return await send();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
// Uygulama kapalıysa aç ve komutu bir kez tekrarla; ajanın "önce
|
|
138
|
+
// uygulamayı elle başlat" demek zorunda kalmaması için.
|
|
139
|
+
if (!(error instanceof BridgeUnavailableError)) throw error;
|
|
140
|
+
const launched = await tryAutoLaunch();
|
|
141
|
+
if (!launched) throw error;
|
|
142
|
+
return send();
|
|
143
|
+
}
|
|
116
144
|
}
|
|
117
145
|
|
|
118
146
|
export async function health() {
|
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.2.0";
|
|
10
10
|
|
|
11
11
|
export const JSONRPC_ERRORS = {
|
|
12
12
|
PARSE_ERROR: -32700,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// MCP
|
|
1
|
+
// MCP tools — deterministic website walkthrough recording and browser control.
|
|
2
2
|
|
|
3
3
|
import { bridgeTool, schema, S, textResult } from "./defineTool.mjs";
|
|
4
4
|
import { callCommand } from "../bridgeClient.mjs";
|
|
@@ -29,31 +29,27 @@ function windowList(result) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function searchableWindowText(windowInfo) {
|
|
32
|
-
return [
|
|
33
|
-
windowInfo?.appName,
|
|
34
|
-
windowInfo?.ownerName,
|
|
35
|
-
windowInfo?.name,
|
|
36
|
-
windowInfo?.title,
|
|
37
|
-
windowInfo?.windowTitle,
|
|
38
|
-
]
|
|
32
|
+
return [windowInfo?.appName, windowInfo?.ownerName, windowInfo?.name, windowInfo?.title, windowInfo?.windowTitle]
|
|
39
33
|
.filter(Boolean)
|
|
40
34
|
.join(" ")
|
|
41
35
|
.toLowerCase();
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
function findBrowserWindow(windows, browserName, titleContains) {
|
|
45
|
-
const browserNeedle = String(browserName || "").toLowerCase();
|
|
46
39
|
const titleNeedle = String(titleContains || "").trim().toLowerCase();
|
|
40
|
+
if (titleNeedle) {
|
|
41
|
+
const exactCapture = windows.find(
|
|
42
|
+
(entry) => Number(entry?.id) > 0 && searchableWindowText(entry).includes(titleNeedle),
|
|
43
|
+
);
|
|
44
|
+
if (exactCapture) return exactCapture;
|
|
45
|
+
}
|
|
46
|
+
const browserNeedle = String(browserName || "").toLowerCase();
|
|
47
47
|
const candidates = windows.filter((entry) => {
|
|
48
48
|
const text = searchableWindowText(entry);
|
|
49
49
|
return text.includes(browserNeedle) ||
|
|
50
50
|
(browserNeedle === "google chrome" && text.includes("chrome")) ||
|
|
51
51
|
(browserNeedle === "zen browser" && text.includes("zen"));
|
|
52
52
|
});
|
|
53
|
-
if (titleNeedle) {
|
|
54
|
-
const titled = candidates.find((entry) => searchableWindowText(entry).includes(titleNeedle));
|
|
55
|
-
if (titled) return titled;
|
|
56
|
-
}
|
|
57
53
|
return candidates.find((entry) => entry?.isOnScreen !== false && Number(entry?.id) > 0) ||
|
|
58
54
|
candidates.find((entry) => Number(entry?.id) > 0) ||
|
|
59
55
|
null;
|
|
@@ -71,29 +67,34 @@ async function waitForBrowserWindow(browserName, titleContains, timeoutMs) {
|
|
|
71
67
|
}
|
|
72
68
|
throw new Error(
|
|
73
69
|
`Could not find a recordable ${browserName} window. Opened windows: ${lastWindows
|
|
74
|
-
.map((entry) => searchableWindowText(entry))
|
|
75
|
-
.filter(Boolean)
|
|
76
|
-
.slice(0, 12)
|
|
77
|
-
.join(" | ")}`,
|
|
70
|
+
.map((entry) => searchableWindowText(entry)).filter(Boolean).slice(0, 12).join(" | ")}`,
|
|
78
71
|
);
|
|
79
72
|
}
|
|
80
73
|
|
|
81
|
-
async function
|
|
74
|
+
async function readEditorState() {
|
|
75
|
+
try {
|
|
76
|
+
return await callCommand("editor.getState", {}, 15_000);
|
|
77
|
+
} catch (_) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function waitForEditor(previousProjectFilePath, timeoutMs = 300_000) {
|
|
82
83
|
const deadline = Date.now() + timeoutMs;
|
|
83
84
|
let lastError = null;
|
|
84
85
|
while (Date.now() < deadline) {
|
|
85
86
|
try {
|
|
86
87
|
const state = await callCommand("editor.getState", {}, 15_000);
|
|
87
|
-
|
|
88
|
+
const isNewProject = !previousProjectFilePath ||
|
|
89
|
+
(state?.projectFilePath && state.projectFilePath !== previousProjectFilePath);
|
|
90
|
+
if (Number(state?.duration) > 0 && state?.playerMounted && isNewProject) return state;
|
|
88
91
|
} catch (error) {
|
|
89
92
|
lastError = error;
|
|
90
93
|
}
|
|
91
94
|
await sleep(1000);
|
|
92
95
|
}
|
|
93
96
|
throw new Error(
|
|
94
|
-
`Recording finished but the
|
|
95
|
-
lastError ? `: ${lastError.message}` : ""
|
|
96
|
-
}`,
|
|
97
|
+
`Recording finished but the new project did not become ready within ${Math.round(timeoutMs / 1000)}s${lastError ? `: ${lastError.message}` : ""}`,
|
|
97
98
|
);
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -106,66 +107,143 @@ async function clearZooms() {
|
|
|
106
107
|
return ranges.length;
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
function trimSegmentsToDuration(segments, targetDuration) {
|
|
111
|
+
return segments
|
|
112
|
+
.map((segment) => {
|
|
113
|
+
const timelineStart = Number(segment.timelineStart ?? segment.start ?? segment.startTime ?? 0);
|
|
114
|
+
const timelineEnd = Number(segment.timelineEnd ?? segment.end ?? segment.endTime ?? timelineStart);
|
|
115
|
+
if (!Number.isFinite(timelineStart) || timelineStart >= targetDuration || timelineEnd <= timelineStart) return null;
|
|
116
|
+
if (timelineEnd <= targetDuration) return segment;
|
|
117
|
+
const clippedTimelineEnd = targetDuration;
|
|
118
|
+
const timelineSpan = Math.max(0.0001, timelineEnd - timelineStart);
|
|
119
|
+
const videoStart = Number(segment.videoStart ?? segment.startTime ?? 0);
|
|
120
|
+
const videoEnd = Number(segment.videoEnd ?? segment.endTime ?? videoStart + timelineSpan);
|
|
121
|
+
const ratio = Math.max(0, videoEnd - videoStart) / timelineSpan;
|
|
122
|
+
const clippedVideoEnd = videoStart + (clippedTimelineEnd - timelineStart) * ratio;
|
|
123
|
+
return {
|
|
124
|
+
...segment,
|
|
125
|
+
timelineEnd: clippedTimelineEnd,
|
|
126
|
+
end: clippedTimelineEnd,
|
|
127
|
+
duration: clippedTimelineEnd - timelineStart,
|
|
128
|
+
videoEnd: clippedVideoEnd,
|
|
129
|
+
videoDuration: clippedVideoEnd - videoStart,
|
|
130
|
+
endTime: clippedVideoEnd,
|
|
131
|
+
sourceVideoEnd: clippedVideoEnd,
|
|
132
|
+
};
|
|
133
|
+
})
|
|
134
|
+
.filter(Boolean);
|
|
135
|
+
}
|
|
136
|
+
|
|
109
137
|
export function browserTools() {
|
|
110
138
|
return [
|
|
111
139
|
bridgeTool({
|
|
112
140
|
name: "creavit_browser_open",
|
|
113
141
|
command: "browser.open",
|
|
114
142
|
description:
|
|
115
|
-
"Opens an http(s) URL
|
|
143
|
+
"Opens an http(s) URL. Set cleanWindow=true for an isolated frameless capture window; it waits for DOM, fonts, and images before returning.",
|
|
116
144
|
inputSchema: schema(
|
|
117
145
|
{
|
|
118
146
|
url: S.string("http(s) URL to open"),
|
|
119
|
-
browser: S.string("
|
|
147
|
+
browser: S.string("External browser name when cleanWindow is false"),
|
|
148
|
+
cleanWindow: S.bool("Open an isolated site-only capture window"),
|
|
149
|
+
aspectRatio: S.string("Capture ratio such as 16:9, 4:3, 1:1, or 9:16 (default 16:9)"),
|
|
150
|
+
width: S.number("Optional capture content width; height is derived from aspectRatio"),
|
|
151
|
+
height: S.number("Optional capture content height when width is omitted"),
|
|
152
|
+
loadWaitMs: S.number("Extra settle time after DOM/fonts/images load (default 1200)"),
|
|
120
153
|
},
|
|
121
154
|
["url"],
|
|
122
155
|
),
|
|
123
156
|
}),
|
|
124
157
|
|
|
125
158
|
bridgeTool({
|
|
126
|
-
name: "
|
|
127
|
-
command: "browser.
|
|
159
|
+
name: "creavit_browser_inspect",
|
|
160
|
+
command: "browser.inspect",
|
|
161
|
+
description: "Lists headings and interactive elements in a clean capture window so an agent can choose meaningful focus/click targets.",
|
|
162
|
+
inputSchema: schema(
|
|
163
|
+
{ captureWindowId: S.number("ID returned by creavit_browser_open"), limit: S.number("Maximum elements (default 80)") },
|
|
164
|
+
["captureWindowId"],
|
|
165
|
+
),
|
|
166
|
+
}),
|
|
167
|
+
|
|
168
|
+
bridgeTool({
|
|
169
|
+
name: "creavit_browser_focus",
|
|
170
|
+
command: "browser.focusPoint",
|
|
171
|
+
description: "Smoothly moves the real macOS system mouse to a normalized page point, so Creavit records its native custom-cursor data.",
|
|
172
|
+
inputSchema: schema(
|
|
173
|
+
{ captureWindowId: S.number("Capture window ID"), x: S.number("Horizontal position, 0..1"), y: S.number("Vertical position, 0..1"), durationMs: S.number("Mouse travel duration (default 520)"), click: S.bool("Perform a real left click after moving") },
|
|
174
|
+
["captureWindowId", "x", "y"],
|
|
175
|
+
),
|
|
176
|
+
}),
|
|
177
|
+
|
|
178
|
+
bridgeTool({
|
|
179
|
+
name: "creavit_browser_interact",
|
|
180
|
+
command: "browser.interact",
|
|
128
181
|
description:
|
|
129
|
-
"
|
|
182
|
+
"Finds a page element by CSS selector or visible text, scrolls to it, and performs a hover/click/type action. By default it moves the REAL system mouse, which looks natural in a recording. Set useSystemCursor:false when the user asks you not to touch their cursor — the click is still delivered to the page, the pointer just never moves.",
|
|
130
183
|
inputSchema: schema(
|
|
131
184
|
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
185
|
+
captureWindowId: S.number("Capture window ID"),
|
|
186
|
+
action: S.string("Interaction", { enum: ["hover", "click", "type", "scrollIntoView", "scrollTo"] }),
|
|
187
|
+
selector: S.string("CSS selector"),
|
|
188
|
+
text: S.string("Visible text fallback"),
|
|
189
|
+
value: S.string("Text to enter for type"),
|
|
190
|
+
moveDurationMs: S.number("Real mouse travel duration (default 520)"),
|
|
191
|
+
useSystemCursor: S.bool(
|
|
192
|
+
"Move the real system cursor (default true). false = never touch the user's pointer; clicks are sent synthetically.",
|
|
193
|
+
),
|
|
141
194
|
},
|
|
142
|
-
["
|
|
195
|
+
["captureWindowId", "action"],
|
|
143
196
|
),
|
|
144
197
|
}),
|
|
145
198
|
|
|
199
|
+
bridgeTool({
|
|
200
|
+
name: "creavit_browser_scroll",
|
|
201
|
+
command: "browser.scroll",
|
|
202
|
+
description: "Scrolls a clean capture window directly, or activates and scrolls an external browser.",
|
|
203
|
+
inputSchema: schema({
|
|
204
|
+
captureWindowId: S.number("Clean capture window ID; preferred over browser"),
|
|
205
|
+
browser: S.string("External browser name"),
|
|
206
|
+
direction: S.string("Scroll direction", { enum: ["down", "up", "top", "bottom"] }),
|
|
207
|
+
style: S.string("page or smooth", { enum: ["page", "smooth"] }),
|
|
208
|
+
count: S.number("Number of scroll steps (1-50)"),
|
|
209
|
+
intervalMs: S.number("Time for each scroll step in milliseconds"),
|
|
210
|
+
}),
|
|
211
|
+
}),
|
|
212
|
+
|
|
213
|
+
bridgeTool({
|
|
214
|
+
name: "creavit_browser_close",
|
|
215
|
+
command: "browser.close",
|
|
216
|
+
description: "Closes an isolated clean capture window.",
|
|
217
|
+
inputSchema: schema({ captureWindowId: S.number("Capture window ID") }, ["captureWindowId"]),
|
|
218
|
+
}),
|
|
219
|
+
|
|
146
220
|
{
|
|
147
221
|
name: "creavit_record_url_walkthrough",
|
|
148
222
|
description:
|
|
149
|
-
"
|
|
223
|
+
"Records a site in a clean configurable-ratio window after real page readiness. It can execute ordered semantic steps using the real system mouse, trims to an exact duration, adds zooms, optional perspective, and optionally mutes it.",
|
|
150
224
|
inputSchema: schema(
|
|
151
225
|
{
|
|
152
226
|
url: S.string("http(s) page to record"),
|
|
153
|
-
browser: S.string("
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
),
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
227
|
+
browser: S.string("External browser fallback (default Zen Browser)"),
|
|
228
|
+
cleanWindow: S.bool("Use a frameless site-only capture window (default true)"),
|
|
229
|
+
aspectRatio: S.string("Capture ratio: 16:9, 4:3, 1:1, 9:16, or another W:H value"),
|
|
230
|
+
width: S.number("Optional capture content width; height is derived from aspectRatio"),
|
|
231
|
+
height: S.number("Optional capture content height when width is omitted"),
|
|
232
|
+
titleContains: S.string("Optional native window title hint"),
|
|
233
|
+
loadWaitMs: S.number("Extra settle time after real page readiness (default 1500)"),
|
|
234
|
+
durationMs: S.number("Exact final timeline duration in milliseconds"),
|
|
235
|
+
leadInMs: S.number("Still time before scrolling (default 1000)"),
|
|
236
|
+
scrollCount: S.number("Number of smooth page scrolls (default 4, max 20)"),
|
|
237
|
+
scrollIntervalMs: S.number("Dwell after each section (default 1400)"),
|
|
238
|
+
steps: S.array("Ordered actions: {action:'click|hover|scrollTo|scroll|wait|type',selector?,text?,value?,scrollPages?,dwellMs?,zoom?,scale?,x?,y?}", { type: "object" }),
|
|
239
|
+
sections: S.array("Deprecated alias for steps", { type: "object" }),
|
|
240
|
+
tailMs: S.number("Still time after the last scroll (default 800)"),
|
|
241
|
+
controlSystemMouse: S.bool("Move/click the real macOS mouse so Creavit records native cursor data (default true)"),
|
|
242
|
+
mute: S.bool("Disable recording audio and mute export clips (default true)"),
|
|
243
|
+
autoZoom: S.bool("Add zooms around section destinations (default true)"),
|
|
166
244
|
replaceZooms: S.bool("Remove recorder-created zooms first (default true)"),
|
|
167
245
|
zoomScale: S.number("Automatic zoom scale (default 1.7)"),
|
|
168
|
-
perspective: S.bool("Apply perspective to the complete result
|
|
246
|
+
perspective: S.bool("Apply perspective to the complete result"),
|
|
169
247
|
perspectiveTemplate: S.string("Perspective template id (default minimal-tilt)"),
|
|
170
248
|
save: S.bool("Save the generated project after editing (default true)"),
|
|
171
249
|
},
|
|
@@ -173,163 +251,185 @@ export function browserTools() {
|
|
|
173
251
|
),
|
|
174
252
|
run: async (args) => {
|
|
175
253
|
const browser = canonicalBrowser(args.browser || "Zen Browser");
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
182
|
-
const
|
|
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));
|
|
254
|
+
const cleanWindow = args.cleanWindow !== false;
|
|
255
|
+
const loadWaitMs = Math.max(0, Math.min(60_000, Number(args.loadWaitMs) || 1500));
|
|
256
|
+
const durationMs = args.durationMs === undefined ? null : Math.max(1000, Math.min(3_600_000, Number(args.durationMs) || 10_000));
|
|
257
|
+
const leadInMs = Math.max(200, Math.min(30_000, Number(args.leadInMs) || 1000));
|
|
258
|
+
const scrollCount = Math.max(0, Math.min(20, Math.round(args.scrollCount === undefined ? 4 : Number(args.scrollCount))));
|
|
259
|
+
const scrollIntervalMs = Math.max(300, Math.min(15_000, Number(args.scrollIntervalMs) || 1400));
|
|
260
|
+
const tailMs = Math.max(200, Math.min(30_000, Number(args.tailMs) || 800));
|
|
187
261
|
const muted = args.mute !== false;
|
|
188
262
|
const autoZoom = args.autoZoom !== false;
|
|
189
263
|
const replaceZooms = args.replaceZooms !== false;
|
|
264
|
+
const controlSystemMouse = args.controlSystemMouse !== false;
|
|
190
265
|
const zoomScale = Math.max(1, Math.min(5, Number(args.zoomScale) || 1.7));
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
})
|
|
266
|
+
const requestedSteps = Array.isArray(args.steps) && args.steps.length ? args.steps : args.sections;
|
|
267
|
+
const sectionPlan = Array.isArray(requestedSteps) && requestedSteps.length
|
|
268
|
+
? requestedSteps.slice(0, 30).map((section) => {
|
|
269
|
+
const action = section?.action || (section?.selector || section?.text ? "hover" : "scroll");
|
|
270
|
+
const defaultScrollPages = action === "scroll" ? 1 : 0;
|
|
271
|
+
return {
|
|
272
|
+
action,
|
|
273
|
+
scrollPages: Math.max(0, Math.min(5, Math.round(Number.isFinite(Number(section?.scrollPages)) ? Number(section.scrollPages) : defaultScrollPages))),
|
|
274
|
+
dwellMs: Math.max(300, Math.min(15_000, Number(section?.dwellMs) || scrollIntervalMs)),
|
|
275
|
+
zoom: section?.zoom !== false,
|
|
276
|
+
scale: Math.max(1, Math.min(5, Number(section?.scale) || zoomScale)),
|
|
277
|
+
x: Math.max(0.01, Math.min(0.99, Number.isFinite(Number(section?.x)) ? Number(section.x) : 0.5)),
|
|
278
|
+
y: Math.max(0.01, Math.min(0.99, Number.isFinite(Number(section?.y)) ? Number(section.y) : 0.48)),
|
|
279
|
+
selector: section?.selector || null,
|
|
280
|
+
text: section?.text || null,
|
|
281
|
+
value: section?.value || "",
|
|
282
|
+
};
|
|
283
|
+
})
|
|
223
284
|
: Array.from({ length: scrollCount }, (_, index) => ({
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
285
|
+
scrollPages: 1,
|
|
286
|
+
dwellMs: scrollIntervalMs,
|
|
287
|
+
zoom: true,
|
|
288
|
+
scale: zoomScale,
|
|
289
|
+
x: index % 2 === 0 ? 0.42 : 0.58,
|
|
290
|
+
y: 0.48,
|
|
291
|
+
selector: null,
|
|
292
|
+
text: null,
|
|
293
|
+
action: "hover",
|
|
294
|
+
value: "",
|
|
295
|
+
}));
|
|
231
296
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
297
|
+
const previousEditor = await readEditorState();
|
|
298
|
+
let captureWindowId = null;
|
|
299
|
+
let recordingStarted = false;
|
|
300
|
+
let openResult;
|
|
301
|
+
const zoomWindows = [];
|
|
302
|
+
try {
|
|
303
|
+
openResult = await callCommand("browser.open", {
|
|
304
|
+
url: args.url,
|
|
305
|
+
browser,
|
|
306
|
+
cleanWindow,
|
|
307
|
+
aspectRatio: args.aspectRatio || "16:9",
|
|
308
|
+
width: Number.isFinite(Number(args.width)) ? Number(args.width) : undefined,
|
|
309
|
+
height: Number.isFinite(Number(args.height)) ? Number(args.height) : undefined,
|
|
310
|
+
loadWaitMs,
|
|
311
|
+
}, 90_000);
|
|
312
|
+
captureWindowId = openResult?.captureWindowId ?? null;
|
|
313
|
+
const titleHint = openResult?.windowTitle || args.titleContains;
|
|
314
|
+
if (!cleanWindow) await sleep(loadWaitMs);
|
|
315
|
+
await callCommand("browser.scroll", {
|
|
316
|
+
captureWindowId,
|
|
317
|
+
browser: captureWindowId ? undefined : browser,
|
|
318
|
+
direction: "top",
|
|
319
|
+
style: "page",
|
|
320
|
+
count: 1,
|
|
321
|
+
intervalMs: 150,
|
|
322
|
+
});
|
|
323
|
+
if (captureWindowId && controlSystemMouse) {
|
|
324
|
+
await callCommand("browser.focusPoint", { captureWindowId, x: 0.5, y: 0.46 });
|
|
325
|
+
}
|
|
326
|
+
const browserWindow = await waitForBrowserWindow(captureWindowId ? "Creavit Studio" : browser, titleHint, 20_000);
|
|
236
327
|
|
|
237
|
-
|
|
238
|
-
"recording.start",
|
|
239
|
-
{
|
|
328
|
+
await callCommand("recording.start", {
|
|
240
329
|
options: {
|
|
241
330
|
startScreen: true,
|
|
242
331
|
cameraEnabled: false,
|
|
243
332
|
micEnabled: !muted,
|
|
244
333
|
systemAudioEnabled: !muted,
|
|
245
|
-
recordingSource: {
|
|
246
|
-
type: "window",
|
|
247
|
-
windowId: browserWindow.id,
|
|
248
|
-
windowInfo: browserWindow,
|
|
249
|
-
},
|
|
334
|
+
recordingSource: { type: "window", windowId: browserWindow.id, windowInfo: browserWindow },
|
|
250
335
|
},
|
|
251
|
-
},
|
|
252
|
-
|
|
253
|
-
|
|
336
|
+
}, 180_000);
|
|
337
|
+
recordingStarted = true;
|
|
338
|
+
const recordingStartedAt = Date.now();
|
|
339
|
+
const contentDeadline = durationMs ? recordingStartedAt + durationMs - tailMs : Number.POSITIVE_INFINITY;
|
|
340
|
+
await sleep(Math.min(leadInMs, Math.max(0, contentDeadline - Date.now())));
|
|
254
341
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
342
|
+
for (const section of sectionPlan) {
|
|
343
|
+
if (Date.now() >= contentDeadline) break;
|
|
344
|
+
let focus = { x: section.x, y: section.y };
|
|
345
|
+
if (section.action === "wait") {
|
|
346
|
+
// Dwell-only step; the current page and cursor stay untouched.
|
|
347
|
+
} else if (captureWindowId && (section.selector || section.text)) {
|
|
348
|
+
const interaction = await callCommand("browser.interact", {
|
|
349
|
+
captureWindowId,
|
|
350
|
+
action: section.action === "scrollTo" ? "scrollTo" : section.action,
|
|
351
|
+
selector: section.selector,
|
|
352
|
+
text: section.text,
|
|
353
|
+
value: section.value,
|
|
354
|
+
}, 30_000);
|
|
355
|
+
if (Number.isFinite(interaction?.x)) focus = { x: interaction.x, y: interaction.y };
|
|
356
|
+
} else {
|
|
357
|
+
if (section.scrollPages > 0) {
|
|
358
|
+
await callCommand("browser.scroll", {
|
|
359
|
+
captureWindowId,
|
|
360
|
+
browser: captureWindowId ? undefined : browser,
|
|
361
|
+
direction: "down",
|
|
362
|
+
style: "smooth",
|
|
363
|
+
count: section.scrollPages,
|
|
364
|
+
intervalMs: 650,
|
|
365
|
+
}, 30_000);
|
|
366
|
+
}
|
|
367
|
+
if (captureWindowId && controlSystemMouse) {
|
|
368
|
+
await callCommand("browser.focusPoint", { captureWindowId, x: focus.x, y: focus.y });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const arrivedAt = (Date.now() - recordingStartedAt) / 1000;
|
|
372
|
+
const dwell = Math.min(section.dwellMs, Math.max(0, contentDeadline - Date.now()));
|
|
373
|
+
if (section.zoom && dwell >= 250) {
|
|
374
|
+
zoomWindows.push({ start: Math.max(0, arrivedAt + 0.08), end: Math.max(arrivedAt + 0.3, arrivedAt + dwell / 1000 - 0.08), scale: section.scale, x: focus.x, y: focus.y });
|
|
375
|
+
}
|
|
376
|
+
if (dwell > 0) await sleep(dwell);
|
|
272
377
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
scale: section.scale,
|
|
279
|
-
x: section.x,
|
|
280
|
-
y: section.y,
|
|
281
|
-
});
|
|
378
|
+
if (durationMs) {
|
|
379
|
+
const stopAt = recordingStartedAt + durationMs + 1500;
|
|
380
|
+
if (Date.now() < stopAt) await sleep(stopAt - Date.now());
|
|
381
|
+
} else {
|
|
382
|
+
await sleep(tailMs);
|
|
282
383
|
}
|
|
283
|
-
await
|
|
284
|
-
|
|
285
|
-
await sleep(tailMs);
|
|
286
|
-
await callCommand("recording.stop", {}, 600_000);
|
|
384
|
+
await callCommand("recording.stop", {}, 600_000);
|
|
385
|
+
recordingStarted = false;
|
|
287
386
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
387
|
+
const editorState = await waitForEditor(previousEditor?.projectFilePath, 600_000);
|
|
388
|
+
const applied = { muted: false, zooms: [], perspective: null, saved: false, trimmedTo: null };
|
|
389
|
+
let finalDuration = Number(editorState.duration);
|
|
390
|
+
if (durationMs) {
|
|
391
|
+
const targetDuration = durationMs / 1000;
|
|
392
|
+
const listed = await callCommand("editor.listSegments", {});
|
|
393
|
+
const segments = Array.isArray(listed?.segments) ? listed.segments : [];
|
|
394
|
+
const trimmed = trimSegmentsToDuration(segments, targetDuration);
|
|
395
|
+
if (!trimmed.length || finalDuration + 0.02 < targetDuration) {
|
|
396
|
+
throw new Error(`Recording was too short for the requested ${targetDuration}s timeline (captured ${finalDuration}s)`);
|
|
397
|
+
}
|
|
398
|
+
await callCommand("editor.setSegments", { segments: trimmed });
|
|
399
|
+
finalDuration = targetDuration;
|
|
400
|
+
applied.trimmedTo = targetDuration;
|
|
401
|
+
}
|
|
402
|
+
if (muted) {
|
|
403
|
+
await callCommand("editor.setMuted", { muted: true });
|
|
404
|
+
applied.muted = true;
|
|
405
|
+
}
|
|
406
|
+
if (autoZoom) {
|
|
407
|
+
if (replaceZooms) applied.removedZooms = await clearZooms();
|
|
408
|
+
for (const zoomWindow of zoomWindows) {
|
|
409
|
+
const start = Math.min(zoomWindow.start, Math.max(0, finalDuration - 0.25));
|
|
410
|
+
const end = Math.min(zoomWindow.end, finalDuration);
|
|
411
|
+
if (end - start < 0.25) continue;
|
|
412
|
+
const result = await callCommand("editor.addZoom", { start, end, scale: zoomWindow.scale, x: zoomWindow.x, y: zoomWindow.y });
|
|
413
|
+
applied.zooms.push(result?.added || result);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (args.perspective === true) {
|
|
417
|
+
const result = await callCommand("editor.addPerspective", { fullDuration: true, templateId: args.perspectiveTemplate || "minimal-tilt" });
|
|
418
|
+
applied.perspective = result?.added || result;
|
|
419
|
+
}
|
|
420
|
+
if (args.save !== false) {
|
|
421
|
+
await callCommand("editor.saveProject", {}, 180_000);
|
|
422
|
+
applied.saved = true;
|
|
423
|
+
}
|
|
424
|
+
return textResult({ ok: true, url: args.url, browser: cleanWindow ? "Creavit Capture" : browser, cleanWindow, page: openResult, window: browserWindow, duration: finalDuration, rawDuration: editorState.duration, projectFilePath: editorState.projectFilePath, applied });
|
|
425
|
+
} finally {
|
|
426
|
+
if (recordingStarted) {
|
|
427
|
+
try { await callCommand("recording.stop", {}, 600_000); } catch (_) {}
|
|
428
|
+
}
|
|
429
|
+
if (captureWindowId) {
|
|
430
|
+
try { await callCommand("browser.close", { captureWindowId }); } catch (_) {}
|
|
310
431
|
}
|
|
311
432
|
}
|
|
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
433
|
},
|
|
334
434
|
},
|
|
335
435
|
];
|
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
// MCP araçları — uygulama durumu, kayıt akışı, cihazlar ve olay akışı.
|
|
2
2
|
|
|
3
3
|
import { bridgeTool, schema, S, textResult } from "./defineTool.mjs";
|
|
4
|
-
import { callCommand } from "../bridgeClient.mjs";
|
|
4
|
+
import { callCommand, isAppRunning } from "../bridgeClient.mjs";
|
|
5
|
+
import {
|
|
6
|
+
ensureAppRunning,
|
|
7
|
+
resolveAppPath,
|
|
8
|
+
DEFAULT_LAUNCH_TIMEOUT_MS,
|
|
9
|
+
} from "../appLauncher.mjs";
|
|
5
10
|
|
|
6
11
|
export function systemTools() {
|
|
7
12
|
return [
|
|
13
|
+
{
|
|
14
|
+
name: "creavit_app_launch",
|
|
15
|
+
description:
|
|
16
|
+
"Starts Creavit Studio if it is not already running, then waits until its agent bridge is ready. Looks for the app in /Applications and ~/Applications (override with CREAVIT_APP_PATH). Other tools also auto-launch on first use, so call this only when you want to start the app explicitly or check where it was found.",
|
|
17
|
+
inputSchema: schema({
|
|
18
|
+
timeoutMs: S.number(
|
|
19
|
+
`How long to wait for the bridge after launching (default ${DEFAULT_LAUNCH_TIMEOUT_MS})`,
|
|
20
|
+
),
|
|
21
|
+
}),
|
|
22
|
+
run: async (args) => {
|
|
23
|
+
const appPath = resolveAppPath();
|
|
24
|
+
const { launched } = await ensureAppRunning({
|
|
25
|
+
isRunning: isAppRunning,
|
|
26
|
+
timeoutMs: args?.timeoutMs || DEFAULT_LAUNCH_TIMEOUT_MS,
|
|
27
|
+
});
|
|
28
|
+
return textResult({
|
|
29
|
+
launched,
|
|
30
|
+
alreadyRunning: !launched,
|
|
31
|
+
appPath,
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
|
|
8
36
|
bridgeTool({
|
|
9
37
|
name: "creavit_app_info",
|
|
10
38
|
description:
|
|
@@ -49,7 +77,7 @@ export function systemTools() {
|
|
|
49
77
|
"Starts a screen recording. Get source IDs from creavit_devices_list first. Monitor with creavit_recording_status, and finish with creavit_recording_stop.",
|
|
50
78
|
inputSchema: schema({
|
|
51
79
|
options: S.object(
|
|
52
|
-
"Recording options: {sourceType:'display'|'window'|'area', sourceId, cameraEnabled, micEnabled, systemAudioEnabled, delayMs, area:{x,y,width,height}}",
|
|
80
|
+
"Recording options: {sourceType:'display'|'window'|'area', sourceId, cameraEnabled, micEnabled, systemAudioEnabled, delayMs, area:{x,y,width,height}}. cameraEnabled:false also turns the camera off and hides its window, so it does not appear in a screen recording; cameraEnabled:true opens and starts it.",
|
|
53
81
|
),
|
|
54
82
|
}),
|
|
55
83
|
timeoutMs: 180_000,
|