@cotestdev/mcp_playwright 0.0.48 → 0.0.49
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/lib/mcp/browser/browserContextFactory.js +1 -1
- package/lib/mcp/browser/browserServerBackend.js +3 -5
- package/lib/mcp/browser/config.js +4 -2
- package/lib/mcp/browser/context.js +1 -0
- package/lib/mcp/browser/response.js +21 -38
- package/lib/mcp/browser/tools/console.js +1 -1
- package/lib/mcp/browser/tools/network.js +1 -1
- package/lib/mcp/browser/tools/pdf.js +1 -1
- package/lib/mcp/browser/tools/screenshot.js +1 -1
- package/lib/mcp/browser/tools/storage.js +68 -0
- package/lib/mcp/browser/tools/tracing.js +2 -2
- package/lib/mcp/browser/tools/video.js +71 -0
- package/lib/mcp/browser/tools.js +7 -3
- package/lib/mcp/extension/cdpRelay.js +3 -0
- package/lib/mcp/program.js +15 -12
- package/lib/mcp/terminal/command.js +13 -0
- package/lib/mcp/terminal/commands.js +32 -12
- package/lib/mcp/terminal/daemon.js +41 -7
- package/lib/mcp/terminal/helpGenerator.js +1 -0
- package/lib/mcp/terminal/program.js +187 -193
- package/lib/mcp/terminal/socketConnection.js +9 -3
- package/package.json +2 -2
|
@@ -28,7 +28,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var program_exports = {};
|
|
30
30
|
__export(program_exports, {
|
|
31
|
-
printResponse: () => printResponse,
|
|
32
31
|
program: () => program
|
|
33
32
|
});
|
|
34
33
|
module.exports = __toCommonJS(program_exports);
|
|
@@ -40,102 +39,66 @@ var import_os = __toESM(require("os"));
|
|
|
40
39
|
var import_path = __toESM(require("path"));
|
|
41
40
|
var import_socketConnection = require("./socketConnection");
|
|
42
41
|
class Session {
|
|
43
|
-
constructor(name,
|
|
42
|
+
constructor(name, options) {
|
|
44
43
|
this._nextMessageId = 1;
|
|
45
44
|
this._callbacks = /* @__PURE__ */ new Map();
|
|
46
45
|
this.name = name;
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
49
|
-
this._connection.onclose = () => this.close();
|
|
46
|
+
this._socketPath = this._daemonSocketPath();
|
|
47
|
+
this._options = options;
|
|
50
48
|
}
|
|
51
49
|
async run(args) {
|
|
52
|
-
return await this._send("run", { args });
|
|
50
|
+
return await this._send("run", { args, cwd: process.cwd() });
|
|
53
51
|
}
|
|
54
52
|
async stop() {
|
|
55
|
-
await this.
|
|
53
|
+
if (!await this.canConnect()) {
|
|
54
|
+
console.log(`Session '${this.name}' is not running.`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await this._send("stop").catch((e) => {
|
|
58
|
+
if (e.message !== "Session closed")
|
|
59
|
+
throw e;
|
|
60
|
+
});
|
|
56
61
|
this.close();
|
|
62
|
+
if (import_os.default.platform() !== "win32")
|
|
63
|
+
await import_fs.default.promises.unlink(this._socketPath).catch(() => {
|
|
64
|
+
});
|
|
65
|
+
console.log(`Session '${this.name}' stopped.`);
|
|
66
|
+
}
|
|
67
|
+
async restart(options) {
|
|
68
|
+
await this.stop();
|
|
69
|
+
this._options = options;
|
|
70
|
+
await this._startDaemonIfNeeded();
|
|
57
71
|
}
|
|
58
72
|
async _send(method, params = {}) {
|
|
73
|
+
const connection = await this._startDaemonIfNeeded();
|
|
59
74
|
const messageId = this._nextMessageId++;
|
|
60
75
|
const message = {
|
|
61
76
|
id: messageId,
|
|
62
77
|
method,
|
|
63
|
-
params
|
|
78
|
+
params,
|
|
79
|
+
version: this._options.daemonVersion
|
|
64
80
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this._callbacks.set(messageId, { resolve, reject });
|
|
81
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
82
|
+
this._callbacks.set(messageId, { resolve, reject, method, params });
|
|
68
83
|
});
|
|
84
|
+
const [result] = await Promise.all([responsePromise, connection.send(message)]);
|
|
85
|
+
return result;
|
|
69
86
|
}
|
|
70
87
|
close() {
|
|
88
|
+
if (!this._connection)
|
|
89
|
+
return;
|
|
71
90
|
for (const callback of this._callbacks.values())
|
|
72
91
|
callback.reject(new Error("Session closed"));
|
|
73
92
|
this._callbacks.clear();
|
|
74
93
|
this._connection.close();
|
|
94
|
+
this._connection = void 0;
|
|
75
95
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const callback = this._callbacks.get(object.id);
|
|
79
|
-
this._callbacks.delete(object.id);
|
|
80
|
-
if (object.error)
|
|
81
|
-
callback.reject(new Error(object.error));
|
|
82
|
-
else
|
|
83
|
-
callback.resolve(object.result);
|
|
84
|
-
} else if (object.id) {
|
|
85
|
-
throw new Error(`Unexpected message id: ${object.id}`);
|
|
86
|
-
} else {
|
|
87
|
-
throw new Error(`Unexpected message without id: ${JSON.stringify(object)}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
class SessionManager {
|
|
92
|
-
constructor(options) {
|
|
93
|
-
this._options = options;
|
|
94
|
-
}
|
|
95
|
-
async list() {
|
|
96
|
-
const dir = daemonProfilesDir;
|
|
97
|
-
try {
|
|
98
|
-
const files = await import_fs.default.promises.readdir(dir);
|
|
99
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
100
|
-
for (const file of files) {
|
|
101
|
-
if (file.startsWith("ud-")) {
|
|
102
|
-
const sessionName = file.split("-")[1];
|
|
103
|
-
const live = await this._canConnect(sessionName);
|
|
104
|
-
sessions.set(sessionName, live);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return sessions;
|
|
108
|
-
} catch {
|
|
109
|
-
return /* @__PURE__ */ new Map();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
async run(args) {
|
|
113
|
-
const sessionName = this._resolveSessionName(args.session);
|
|
114
|
-
const session = await this._connect(sessionName);
|
|
115
|
-
const result = await session.run(args);
|
|
116
|
-
await printResponse(result);
|
|
117
|
-
session.close();
|
|
118
|
-
}
|
|
119
|
-
async stop(sessionName) {
|
|
120
|
-
sessionName = this._resolveSessionName(sessionName);
|
|
121
|
-
if (!await this._canConnect(sessionName)) {
|
|
122
|
-
console.log(`Session '${sessionName}' is not running.`);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const session = await this._connect(sessionName);
|
|
126
|
-
await session.stop();
|
|
127
|
-
console.log(`Session '${sessionName}' stopped.`);
|
|
128
|
-
}
|
|
129
|
-
async delete(sessionName) {
|
|
130
|
-
sessionName = this._resolveSessionName(sessionName);
|
|
131
|
-
if (await this._canConnect(sessionName)) {
|
|
132
|
-
const session = await this._connect(sessionName);
|
|
133
|
-
await session.stop();
|
|
134
|
-
}
|
|
96
|
+
async delete() {
|
|
97
|
+
await this.stop();
|
|
135
98
|
const dataDirs = await import_fs.default.promises.readdir(daemonProfilesDir).catch(() => []);
|
|
136
|
-
const matchingDirs = dataDirs.filter((dir) => dir.startsWith(`ud-${
|
|
99
|
+
const matchingDirs = dataDirs.filter((dir) => dir.startsWith(`ud-${this.name}-`));
|
|
137
100
|
if (matchingDirs.length === 0) {
|
|
138
|
-
console.log(`No user data found for session '${
|
|
101
|
+
console.log(`No user data found for session '${this.name}'.`);
|
|
139
102
|
return;
|
|
140
103
|
}
|
|
141
104
|
for (const dir of matchingDirs) {
|
|
@@ -143,11 +106,11 @@ class SessionManager {
|
|
|
143
106
|
for (let i = 0; i < 5; i++) {
|
|
144
107
|
try {
|
|
145
108
|
await import_fs.default.promises.rm(userDataDir, { recursive: true });
|
|
146
|
-
console.log(`Deleted user data for session '${
|
|
109
|
+
console.log(`Deleted user data for session '${this.name}'.`);
|
|
147
110
|
break;
|
|
148
111
|
} catch (e) {
|
|
149
112
|
if (e.code === "ENOENT") {
|
|
150
|
-
console.log(`No user data found for session '${
|
|
113
|
+
console.log(`No user data found for session '${this.name}'.`);
|
|
151
114
|
break;
|
|
152
115
|
}
|
|
153
116
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
@@ -156,39 +119,70 @@ class SessionManager {
|
|
|
156
119
|
}
|
|
157
120
|
}
|
|
158
121
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
122
|
+
}
|
|
123
|
+
async _connect() {
|
|
124
|
+
return await new Promise((resolve) => {
|
|
125
|
+
const socket = import_net.default.createConnection(this._socketPath, () => {
|
|
126
|
+
resolve({ socket });
|
|
162
127
|
});
|
|
163
|
-
|
|
128
|
+
socket.on("error", (error) => {
|
|
129
|
+
if (import_os.default.platform() !== "win32")
|
|
130
|
+
void import_fs.default.promises.unlink(this._socketPath).catch(() => {
|
|
131
|
+
}).then(() => resolve({ error }));
|
|
132
|
+
else
|
|
133
|
+
resolve({ error });
|
|
134
|
+
});
|
|
135
|
+
});
|
|
164
136
|
}
|
|
165
|
-
async
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
137
|
+
async canConnect() {
|
|
138
|
+
const { socket } = await this._connect();
|
|
139
|
+
if (socket) {
|
|
140
|
+
socket.destroy();
|
|
141
|
+
return true;
|
|
170
142
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
async _startDaemonIfNeeded() {
|
|
146
|
+
if (this._connection)
|
|
147
|
+
return this._connection;
|
|
148
|
+
let { socket } = await this._connect();
|
|
149
|
+
if (!socket)
|
|
150
|
+
socket = await this._startDaemon();
|
|
151
|
+
this._connection = new import_socketConnection.SocketConnection(socket, this._options.daemonVersion);
|
|
152
|
+
this._connection.onmessage = (message) => this._onMessage(message);
|
|
153
|
+
this._connection.onversionerror = (id, e) => {
|
|
154
|
+
if (e.received && e.received !== "undefined-for-test") {
|
|
155
|
+
return false;
|
|
184
156
|
}
|
|
157
|
+
console.error(`Daemon is older than client, killing it.`);
|
|
158
|
+
this.stop().then(() => process.exit(1)).catch(() => process.exit(1));
|
|
159
|
+
return true;
|
|
160
|
+
};
|
|
161
|
+
this._connection.onclose = () => this.close();
|
|
162
|
+
return this._connection;
|
|
163
|
+
}
|
|
164
|
+
_onMessage(object) {
|
|
165
|
+
if (object.id && this._callbacks.has(object.id)) {
|
|
166
|
+
const callback = this._callbacks.get(object.id);
|
|
167
|
+
this._callbacks.delete(object.id);
|
|
168
|
+
if (object.error)
|
|
169
|
+
callback.reject(new Error(object.error));
|
|
170
|
+
else
|
|
171
|
+
callback.resolve(object.result);
|
|
172
|
+
} else if (object.id) {
|
|
173
|
+
throw new Error(`Unexpected message id: ${object.id}`);
|
|
174
|
+
} else {
|
|
175
|
+
throw new Error(`Unexpected message without id: ${JSON.stringify(object)}`);
|
|
185
176
|
}
|
|
177
|
+
}
|
|
178
|
+
async _startDaemon() {
|
|
186
179
|
await import_fs.default.promises.mkdir(daemonProfilesDir, { recursive: true });
|
|
187
|
-
const userDataDir = import_path.default.resolve(daemonProfilesDir, `ud-${
|
|
180
|
+
const userDataDir = import_path.default.resolve(daemonProfilesDir, `ud-${this.name}`);
|
|
188
181
|
const cliPath = import_path.default.join(__dirname, "../../../cli.js");
|
|
189
182
|
const configFile = resolveConfigFile(this._options.config);
|
|
190
183
|
const configArg = configFile !== void 0 ? [`--config=${configFile}`] : [];
|
|
191
184
|
const headedArg = this._options.headed ? [`--daemon-headed`] : [];
|
|
185
|
+
const extensionArg = this._options.extension ? [`--extension`] : [];
|
|
192
186
|
const outLog = import_path.default.join(daemonProfilesDir, "out.log");
|
|
193
187
|
const errLog = import_path.default.join(daemonProfilesDir, "err.log");
|
|
194
188
|
const out = import_fs.default.openSync(outLog, "w");
|
|
@@ -196,10 +190,13 @@ class SessionManager {
|
|
|
196
190
|
const child = (0, import_child_process.spawn)(process.execPath, [
|
|
197
191
|
cliPath,
|
|
198
192
|
"run-mcp-server",
|
|
199
|
-
`--
|
|
193
|
+
`--output-dir=${outputDir}`,
|
|
194
|
+
`--daemon=${this._socketPath}`,
|
|
200
195
|
`--daemon-data-dir=${userDataDir}`,
|
|
196
|
+
`--daemon-version=${this._options.daemonVersion}`,
|
|
201
197
|
...configArg,
|
|
202
|
-
...headedArg
|
|
198
|
+
...headedArg,
|
|
199
|
+
...extensionArg
|
|
203
200
|
], {
|
|
204
201
|
detached: true,
|
|
205
202
|
stdio: ["ignore", out, err],
|
|
@@ -207,53 +204,104 @@ class SessionManager {
|
|
|
207
204
|
// Will be used as root.
|
|
208
205
|
});
|
|
209
206
|
child.unref();
|
|
210
|
-
console.log(`<!-- Daemon for \`${
|
|
207
|
+
console.log(`<!-- Daemon for \`${this.name}\` session started with pid ${child.pid}.`);
|
|
211
208
|
if (configFile)
|
|
212
209
|
console.log(`- Using config file at \`${import_path.default.relative(process.cwd(), configFile)}\`.`);
|
|
213
|
-
const sessionSuffix =
|
|
210
|
+
const sessionSuffix = this.name !== "default" ? ` "${this.name}"` : "";
|
|
214
211
|
console.log(`- You can stop the session daemon with \`playwright-cli session-stop${sessionSuffix}\` when done.`);
|
|
215
212
|
console.log(`- You can delete the session data with \`playwright-cli session-delete${sessionSuffix}\` when done.`);
|
|
216
213
|
console.log("-->");
|
|
217
214
|
const maxRetries = 50;
|
|
218
215
|
const retryDelay = 100;
|
|
219
216
|
for (let i = 0; i < maxRetries; i++) {
|
|
220
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
217
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
221
218
|
try {
|
|
222
|
-
|
|
219
|
+
const { socket } = await this._connect();
|
|
220
|
+
if (socket)
|
|
221
|
+
return socket;
|
|
223
222
|
} catch (e) {
|
|
224
|
-
if (e.code !== "ENOENT")
|
|
223
|
+
if (e.code !== "ENOENT" && e.code !== "ECONNREFUSED")
|
|
225
224
|
throw e;
|
|
226
225
|
}
|
|
227
226
|
}
|
|
228
227
|
const outData = await import_fs.default.promises.readFile(outLog, "utf-8").catch(() => "");
|
|
229
228
|
const errData = await import_fs.default.promises.readFile(errLog, "utf-8").catch(() => "");
|
|
230
|
-
console.error(`Failed to connect to daemon at ${
|
|
229
|
+
console.error(`Failed to connect to daemon at ${this._socketPath} after ${maxRetries * retryDelay}ms`);
|
|
231
230
|
if (outData.length)
|
|
232
231
|
console.log(outData);
|
|
233
232
|
if (errData.length)
|
|
234
233
|
console.error(errData);
|
|
235
234
|
process.exit(1);
|
|
236
235
|
}
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
236
|
+
_daemonSocketPath() {
|
|
237
|
+
const socketName = `${this.name}.sock`;
|
|
238
|
+
if (import_os.default.platform() === "win32")
|
|
239
|
+
return `\\\\.\\pipe\\${installationDirHash}-${socketName}`;
|
|
240
|
+
const socketsDir = process.env.PLAYWRIGHT_DAEMON_SOCKETS_DIR || import_path.default.join(import_os.default.tmpdir(), "playwright-cli");
|
|
241
|
+
return import_path.default.join(socketsDir, installationDirHash, socketName);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
class SessionManager {
|
|
245
|
+
constructor(sessions, options) {
|
|
246
|
+
this.sessions = sessions;
|
|
247
|
+
this.options = options;
|
|
248
|
+
}
|
|
249
|
+
static async create(options) {
|
|
250
|
+
const dir = daemonProfilesDir;
|
|
251
|
+
const sessions = /* @__PURE__ */ new Map([
|
|
252
|
+
["default", new Session("default", options)]
|
|
253
|
+
]);
|
|
254
|
+
try {
|
|
255
|
+
const files = await import_fs.default.promises.readdir(dir);
|
|
256
|
+
for (const file of files) {
|
|
257
|
+
if (file.startsWith("ud-")) {
|
|
258
|
+
const sessionName = file.split("-")[1];
|
|
259
|
+
sessions.set(sessionName, new Session(sessionName, options));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
return new SessionManager(sessions, options);
|
|
265
|
+
}
|
|
266
|
+
async run(args) {
|
|
267
|
+
const sessionName = this._resolveSessionName(args.session);
|
|
268
|
+
let session = this.sessions.get(sessionName);
|
|
269
|
+
if (!session) {
|
|
270
|
+
session = new Session(sessionName, { ...this.options, ...args });
|
|
271
|
+
this.sessions.set(sessionName, session);
|
|
272
|
+
}
|
|
273
|
+
const result = await session.run({ ...args, outputDir });
|
|
274
|
+
console.log(result.text);
|
|
275
|
+
session.close();
|
|
276
|
+
}
|
|
277
|
+
async stop(sessionName) {
|
|
278
|
+
sessionName = this._resolveSessionName(sessionName);
|
|
279
|
+
const session = this.sessions.get(sessionName);
|
|
280
|
+
if (!session || !await session.canConnect()) {
|
|
281
|
+
console.log(`Session '${sessionName}' is not running.`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
await session.stop();
|
|
285
|
+
}
|
|
286
|
+
async delete(sessionName) {
|
|
287
|
+
sessionName = this._resolveSessionName(sessionName);
|
|
288
|
+
const session = this.sessions.get(sessionName);
|
|
289
|
+
if (!session) {
|
|
290
|
+
console.log(`No user data found for session '${sessionName}'.`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
await session.delete();
|
|
294
|
+
this.sessions.delete(sessionName);
|
|
295
|
+
}
|
|
296
|
+
async configure(args) {
|
|
297
|
+
const sessionName = this._resolveSessionName(args.session);
|
|
298
|
+
let session = this.sessions.get(sessionName);
|
|
299
|
+
if (!session) {
|
|
300
|
+
session = new Session(sessionName, this.options);
|
|
301
|
+
this.sessions.set(sessionName, session);
|
|
302
|
+
}
|
|
303
|
+
await session.restart({ ...this.options, ...args, config: args._[1] });
|
|
304
|
+
session.close();
|
|
257
305
|
}
|
|
258
306
|
_resolveSessionName(sessionName) {
|
|
259
307
|
if (sessionName)
|
|
@@ -262,21 +310,14 @@ class SessionManager {
|
|
|
262
310
|
return process.env.PLAYWRIGHT_CLI_SESSION;
|
|
263
311
|
return "default";
|
|
264
312
|
}
|
|
265
|
-
_daemonSocketPath(sessionName) {
|
|
266
|
-
const socketName = `${sessionName}.sock`;
|
|
267
|
-
if (import_os.default.platform() === "win32")
|
|
268
|
-
return `\\\\.\\pipe\\${installationDirHash}-${socketName}`;
|
|
269
|
-
const socketsDir = process.env.PLAYWRIGHT_DAEMON_SOCKETS_DIR || import_path.default.join(import_os.default.tmpdir(), "playwright-cli");
|
|
270
|
-
return import_path.default.join(socketsDir, installationDirHash, socketName);
|
|
271
|
-
}
|
|
272
313
|
}
|
|
273
314
|
async function handleSessionCommand(sessionManager, subcommand, args) {
|
|
274
315
|
if (subcommand === "list") {
|
|
275
|
-
const sessions =
|
|
316
|
+
const sessions = sessionManager.sessions;
|
|
276
317
|
console.log("Sessions:");
|
|
277
|
-
for (const
|
|
278
|
-
const liveMarker =
|
|
279
|
-
console.log(` ${
|
|
318
|
+
for (const session of sessions.values()) {
|
|
319
|
+
const liveMarker = await session.canConnect() ? " (live)" : "";
|
|
320
|
+
console.log(` ${session.name}${liveMarker}`);
|
|
280
321
|
}
|
|
281
322
|
if (sessions.size === 0)
|
|
282
323
|
console.log(" (no sessions)");
|
|
@@ -287,9 +328,9 @@ async function handleSessionCommand(sessionManager, subcommand, args) {
|
|
|
287
328
|
return;
|
|
288
329
|
}
|
|
289
330
|
if (subcommand === "stop-all") {
|
|
290
|
-
const sessions =
|
|
291
|
-
for (const
|
|
292
|
-
await
|
|
331
|
+
const sessions = sessionManager.sessions;
|
|
332
|
+
for (const session of sessions.values())
|
|
333
|
+
await session.stop();
|
|
293
334
|
return;
|
|
294
335
|
}
|
|
295
336
|
if (subcommand === "delete") {
|
|
@@ -325,10 +366,12 @@ const daemonProfilesDir = (() => {
|
|
|
325
366
|
async function program(options) {
|
|
326
367
|
const argv = process.argv.slice(2);
|
|
327
368
|
const args = require("minimist")(argv, {
|
|
328
|
-
boolean: ["help", "version", "headed"]
|
|
369
|
+
boolean: ["help", "version", "headed", "extension"]
|
|
329
370
|
});
|
|
330
371
|
if (!argv.includes("--headed") && !argv.includes("--no-headed"))
|
|
331
372
|
delete args.headed;
|
|
373
|
+
if (!argv.includes("--extension"))
|
|
374
|
+
delete args.extension;
|
|
332
375
|
const help = require("./help.json");
|
|
333
376
|
const commandName = args._[0];
|
|
334
377
|
if (args.version || args.v) {
|
|
@@ -351,7 +394,7 @@ async function program(options) {
|
|
|
351
394
|
console.log(help.global);
|
|
352
395
|
process.exit(1);
|
|
353
396
|
}
|
|
354
|
-
const sessionManager =
|
|
397
|
+
const sessionManager = await SessionManager.create({ daemonVersion: options.version, ...args });
|
|
355
398
|
if (commandName.startsWith("session")) {
|
|
356
399
|
const subcommand = args._[0].split("-").slice(1).join("-");
|
|
357
400
|
await handleSessionCommand(sessionManager, subcommand, args);
|
|
@@ -361,61 +404,13 @@ async function program(options) {
|
|
|
361
404
|
await handleSessionCommand(sessionManager, "config", args);
|
|
362
405
|
return;
|
|
363
406
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
async function printResponse(response) {
|
|
367
|
-
const { sections } = response;
|
|
368
|
-
if (!sections) {
|
|
369
|
-
console.log("### Error\n" + response.text);
|
|
407
|
+
if (commandName === "close") {
|
|
408
|
+
await handleSessionCommand(sessionManager, "stop", args);
|
|
370
409
|
return;
|
|
371
410
|
}
|
|
372
|
-
|
|
373
|
-
for (const section of sections) {
|
|
374
|
-
text.push(`### ${section.title}`);
|
|
375
|
-
for (const result of section.content) {
|
|
376
|
-
if (!result.file) {
|
|
377
|
-
if (result.text !== void 0)
|
|
378
|
-
text.push(result.text);
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
const generatedFileName = await outputFile(dateAsFileName(result.file.prefix, result.file.ext), { origin: "code" });
|
|
382
|
-
const fileName = result.file.suggestedFilename ? await outputFile(result.file.suggestedFilename, { origin: "llm" }) : generatedFileName;
|
|
383
|
-
text.push(`- [${result.title}](${import_path.default.relative(process.cwd(), fileName)})`);
|
|
384
|
-
if (result.data)
|
|
385
|
-
await import_fs.default.promises.writeFile(fileName, result.data);
|
|
386
|
-
else if (result.isBase64)
|
|
387
|
-
await import_fs.default.promises.writeFile(fileName, Buffer.from(result.text, "base64"));
|
|
388
|
-
else
|
|
389
|
-
await import_fs.default.promises.writeFile(fileName, result.text);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
console.log(text.join("\n"));
|
|
393
|
-
}
|
|
394
|
-
function dateAsFileName(prefix, extension) {
|
|
395
|
-
const date = /* @__PURE__ */ new Date();
|
|
396
|
-
return `${prefix}-${date.toISOString().replace(/[:.]/g, "-")}.${extension}`;
|
|
411
|
+
await sessionManager.run(args);
|
|
397
412
|
}
|
|
398
413
|
const outputDir = import_path.default.join(process.cwd(), ".playwright-cli");
|
|
399
|
-
async function outputFile(fileName, options) {
|
|
400
|
-
await import_fs.default.promises.mkdir(outputDir, { recursive: true });
|
|
401
|
-
if (options.origin === "code")
|
|
402
|
-
return import_path.default.resolve(outputDir, fileName);
|
|
403
|
-
if (options.origin === "llm") {
|
|
404
|
-
fileName = fileName.split("\\").join("/");
|
|
405
|
-
const resolvedFile = import_path.default.resolve(outputDir, fileName);
|
|
406
|
-
if (!resolvedFile.startsWith(import_path.default.resolve(outputDir) + import_path.default.sep))
|
|
407
|
-
throw new Error(`Resolved file path ${resolvedFile} is outside of the output directory ${outputDir}. Use relative file names to stay within the output directory.`);
|
|
408
|
-
return resolvedFile;
|
|
409
|
-
}
|
|
410
|
-
return import_path.default.join(outputDir, sanitizeForFilePath(fileName));
|
|
411
|
-
}
|
|
412
|
-
function sanitizeForFilePath(s) {
|
|
413
|
-
const sanitize = (s2) => s2.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, "-");
|
|
414
|
-
const separator = s.lastIndexOf(".");
|
|
415
|
-
if (separator === -1)
|
|
416
|
-
return sanitize(s);
|
|
417
|
-
return sanitize(s.substring(0, separator)) + "." + sanitize(s.substring(separator + 1));
|
|
418
|
-
}
|
|
419
414
|
function resolveConfigFile(configParam) {
|
|
420
415
|
const configFile = configParam || process.env.PLAYWRIGHT_CLI_CONFIG;
|
|
421
416
|
if (configFile)
|
|
@@ -429,6 +424,5 @@ function resolveConfigFile(configParam) {
|
|
|
429
424
|
}
|
|
430
425
|
// Annotate the CommonJS export names for ESM import in node:
|
|
431
426
|
0 && (module.exports = {
|
|
432
|
-
printResponse,
|
|
433
427
|
program
|
|
434
428
|
});
|
|
@@ -22,9 +22,10 @@ __export(socketConnection_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(socketConnection_exports);
|
|
24
24
|
class SocketConnection {
|
|
25
|
-
constructor(socket) {
|
|
25
|
+
constructor(socket, version) {
|
|
26
26
|
this._pendingBuffers = [];
|
|
27
27
|
this._socket = socket;
|
|
28
|
+
this._version = version;
|
|
28
29
|
socket.on("data", (buffer) => this._onData(buffer));
|
|
29
30
|
socket.on("close", () => {
|
|
30
31
|
this.onclose?.();
|
|
@@ -33,7 +34,7 @@ class SocketConnection {
|
|
|
33
34
|
}
|
|
34
35
|
async send(message) {
|
|
35
36
|
await new Promise((resolve, reject) => {
|
|
36
|
-
this._socket.write(`${JSON.stringify(message)}
|
|
37
|
+
this._socket.write(`${JSON.stringify({ ...message, version: this._version })}
|
|
37
38
|
`, (error) => {
|
|
38
39
|
if (error)
|
|
39
40
|
reject(error);
|
|
@@ -66,7 +67,12 @@ class SocketConnection {
|
|
|
66
67
|
}
|
|
67
68
|
_dispatchMessage(message) {
|
|
68
69
|
try {
|
|
69
|
-
|
|
70
|
+
const parsedMessage = JSON.parse(message);
|
|
71
|
+
if (parsedMessage.version !== this._version) {
|
|
72
|
+
if (this.onversionerror?.(parsedMessage.id, { expected: this._version, received: parsedMessage.version }))
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.onmessage?.(parsedMessage);
|
|
70
76
|
} catch (e) {
|
|
71
77
|
console.error("failed to dispatch message", e);
|
|
72
78
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cotestdev/mcp_playwright",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.49",
|
|
4
4
|
"description": "Playwright MCP (Model Context Protocol) tools for browser automation",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"author": "",
|
|
26
26
|
"license": "Apache-2.0",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@cotestdev/ai-runner-fake": "^0.0.
|
|
28
|
+
"@cotestdev/ai-runner-fake": "^0.0.9",
|
|
29
29
|
"playwright": "^1.40.0",
|
|
30
30
|
"playwright-core": "^1.40.0"
|
|
31
31
|
},
|