"use strict";
|
var __create = Object.create;
|
var __defProp = Object.defineProperty;
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
var __getProtoOf = Object.getPrototypeOf;
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
var __export = (target, all) => {
|
for (var name in all)
|
__defProp(target, name, { get: all[name], enumerable: true });
|
};
|
var __copyProps = (to, from, except, desc) => {
|
if (from && typeof from === "object" || typeof from === "function") {
|
for (let key of __getOwnPropNames(from))
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
}
|
return to;
|
};
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
// If the importer is in node compatibility mode or this is not an ESM
|
// file that has been converted to a CommonJS file using a Babel-
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
mod
|
));
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
var program_exports = {};
|
__export(program_exports, {
|
calculateSha1: () => calculateSha1,
|
program: () => program
|
});
|
module.exports = __toCommonJS(program_exports);
|
var import_child_process = require("child_process");
|
var import_crypto = __toESM(require("crypto"));
|
var import_os = __toESM(require("os"));
|
var import_path = __toESM(require("path"));
|
var import_channelSessions = require("./channelSessions");
|
var import_output = require("./output");
|
var import_registry = require("./registry");
|
var import_session = require("./session");
|
var import_package = require("../../package");
|
var import_serverRegistry = require("../../serverRegistry");
|
var import_minimist = require("./minimist");
|
const globalOptions = [
|
"json",
|
"raw",
|
"session"
|
];
|
const booleanOptions = [
|
"all",
|
"help",
|
"json",
|
"raw",
|
"version"
|
];
|
async function program(options) {
|
const clientInfo = (0, import_registry.createClientInfo)();
|
const help = require((0, import_package.libPath)("tools", "cli-client", "help.json"));
|
const argv = process.argv.slice(2);
|
const boolean = [...help.booleanOptions, ...booleanOptions];
|
const args = (0, import_minimist.minimist)(argv, { boolean, string: ["_"] });
|
if (args.s) {
|
args.session = args.s;
|
delete args.s;
|
}
|
const output = args.json ? new import_output.JsonOutput() : new import_output.TextOutput();
|
const commandName = args._?.[0];
|
if (args.version || args.v) {
|
output.version(options?.embedderVersion ?? clientInfo.version);
|
process.exit(0);
|
}
|
const command = commandName && help.commands[commandName];
|
if (args.help || args.h || !commandName) {
|
if (command) {
|
output.help(command.help);
|
} else {
|
const lines = ["playwright-cli - run playwright mcp commands from terminal"];
|
if (process.env.CLAUDECODE || process.env.COPILOT_CLI)
|
lines.push(`Agent skill: ${import_path.default.relative(process.cwd(), (0, import_package.libPath)("tools", "cli-client", "skill", "SKILL.md"))}`);
|
lines.push(help.global);
|
output.help(lines.join("\n\n"));
|
}
|
process.exit(0);
|
}
|
if (!command)
|
output.errorUnknownCommand(commandName, help.global);
|
validateFlags(args, command, output);
|
validateArgs(args, command, output);
|
const registry = await import_registry.Registry.load();
|
const sessionName = (0, import_registry.resolveSessionName)(args.session);
|
switch (commandName) {
|
case "list": {
|
const data = await collectList(registry, clientInfo, !!args.all);
|
output.list(data);
|
return;
|
}
|
case "close-all": {
|
const entries = registry.entries(clientInfo);
|
const closed = [];
|
for (const entry of entries) {
|
await new import_session.Session(entry).stop();
|
closed.push(entry.config.name);
|
}
|
output.closeAll(closed);
|
return;
|
}
|
case "delete-data": {
|
const entry = registry.entry(clientInfo, sessionName);
|
if (!entry) {
|
output.deleteData(sessionName, { existed: false, deletedUserDataDir: false });
|
return;
|
}
|
const result = await new import_session.Session(entry).deleteData();
|
output.deleteData(sessionName, result);
|
return;
|
}
|
case "kill-all": {
|
const pids = await killAllDaemons();
|
output.killAll(pids);
|
return;
|
}
|
case "open": {
|
const { pid } = await startSession(sessionName, registry, clientInfo, args, "open");
|
const newEntry = await registry.loadEntry(clientInfo, sessionName);
|
const params = args._.slice(1);
|
const toolText = await runInSessionOrStop(newEntry, clientInfo, { _: ["goto", ...params.length ? params : ["about:blank"]] }, output);
|
output.open(sessionName, pid, toolText);
|
return;
|
}
|
case "attach": {
|
const attachTarget = args._[1];
|
const targetCount = (attachTarget ? 1 : 0) + (args.cdp ? 1 : 0) + (args.endpoint ? 1 : 0) + (args.extension ? 1 : 0);
|
if (targetCount > 1)
|
output.errorAttachConflict();
|
if (attachTarget)
|
args.endpoint = attachTarget;
|
const extensionChannel = typeof args.extension === "string" ? args.extension : void 0;
|
if (extensionChannel) {
|
args.browser = extensionChannel;
|
args.extension = true;
|
}
|
const cdpChannel = typeof args.cdp === "string" && (0, import_channelSessions.isKnownChannel)(args.cdp) ? args.cdp : void 0;
|
const targetName = attachTarget ?? cdpChannel ?? extensionChannel ?? args.endpoint ?? args.cdp;
|
if (!targetName)
|
output.errorAttachNoTarget();
|
const attachSessionName = (0, import_registry.explicitSessionName)(args.session) ?? attachTarget ?? cdpChannel ?? extensionChannel ?? sessionName;
|
args.session = attachSessionName;
|
const { pid } = await startSession(attachSessionName, registry, clientInfo, args, "attach");
|
const newEntry = await registry.loadEntry(clientInfo, attachSessionName);
|
const toolText = await runInSessionOrStop(newEntry, clientInfo, { _: ["snapshot"], filename: "<auto>" }, output);
|
output.attach(attachSessionName, pid, targetName, toolText);
|
return;
|
}
|
case "close": {
|
const closeEntry = registry.entry(clientInfo, sessionName);
|
const { wasOpen } = closeEntry ? await new import_session.Session(closeEntry).stop() : { wasOpen: false };
|
output.close(sessionName, wasOpen);
|
return;
|
}
|
case "detach": {
|
const detachEntry = registry.entry(clientInfo, sessionName);
|
if (detachEntry && !detachEntry.config.attached)
|
output.errorDetachNotAttached(sessionName);
|
const { wasOpen } = detachEntry ? await new import_session.Session(detachEntry).stop() : { wasOpen: false };
|
output.detach(sessionName, wasOpen);
|
return;
|
}
|
case "install":
|
await runInitWorkspace(args, output);
|
output.installed();
|
return;
|
case "install-browser":
|
await installBrowser();
|
output.installed();
|
return;
|
case "show": {
|
const daemonScript = (0, import_package.libPath)("entry", "dashboardApp.js");
|
const daemonArgs = [
|
daemonScript,
|
`--workspaceDir=${clientInfo.workspaceDir ?? ""}`
|
];
|
const explicit = (0, import_registry.explicitSessionName)(args.session);
|
if (explicit)
|
daemonArgs.push(`--sessionName=${explicit}`);
|
if (args.port !== void 0)
|
daemonArgs.push(`--port=${args.port}`);
|
if (args.host !== void 0)
|
daemonArgs.push(`--host=${args.host}`);
|
if (args.kill) {
|
daemonArgs.push(`--kill`);
|
const child2 = (0, import_child_process.spawn)(process.execPath, daemonArgs, { stdio: "ignore" });
|
await new Promise((resolve) => child2.on("exit", () => resolve()));
|
return;
|
}
|
if (args.annotate) {
|
const entry = registry.entry(clientInfo, sessionName);
|
if (!entry)
|
output.errorBrowserNotOpenForTool(sessionName);
|
args.raw = true;
|
const text = await runInSession(entry, clientInfo, args, output);
|
output.toolResult(text);
|
return;
|
}
|
const foreground = args.port !== void 0;
|
const child = (0, import_child_process.spawn)(process.execPath, daemonArgs, {
|
detached: !foreground,
|
stdio: foreground ? "inherit" : ["pipe", "pipe", "ignore"]
|
});
|
if (foreground) {
|
await new Promise((resolve) => child.on("exit", () => resolve()));
|
return;
|
}
|
const timer = setTimeout(() => child.stdin.destroy(), 6e4);
|
child.unref();
|
let daemonPid;
|
try {
|
await new Promise((resolve, reject) => {
|
let outLog = "";
|
child.stdout.on("data", (data) => {
|
outLog += data.toString();
|
const match = outLog.match(/Dashboard is running pid=(\d+)/);
|
if (match) {
|
daemonPid = Number(match[1]);
|
resolve();
|
}
|
});
|
child.once("exit", (code, signal) => reject(new Error(`Dashboard daemon exited (code=${code}, signal=${signal}) before signaling READY${outLog ? "\n" + outLog : ""}`)));
|
});
|
} finally {
|
clearTimeout(timer);
|
child.removeAllListeners("exit");
|
child.stdin.destroy();
|
child.stdout.destroy();
|
}
|
output.show(sessionName, daemonPid);
|
return;
|
}
|
default: {
|
const entry = registry.entry(clientInfo, sessionName);
|
if (!entry)
|
output.errorBrowserNotOpenForTool(sessionName);
|
if (command.raw)
|
args.raw = true;
|
const text = await runInSession(entry, clientInfo, args, output);
|
output.toolResult(text);
|
}
|
}
|
}
|
async function startSession(sessionName, registry, clientInfo, args, mode) {
|
const entry = registry.entry(clientInfo, sessionName);
|
if (entry)
|
await new import_session.Session(entry).stop();
|
return await import_session.Session.startDaemon(clientInfo, args, mode);
|
}
|
async function runInSession(entry, clientInfo, args, output) {
|
const raw = !!args.raw;
|
for (const globalOption of globalOptions)
|
delete args[globalOption];
|
const session = new import_session.Session(entry);
|
const result = await session.run(clientInfo, args, { raw, json: output.json });
|
return result.text;
|
}
|
async function runInSessionOrStop(entry, clientInfo, args, output) {
|
try {
|
return await runInSession(entry, clientInfo, args, output);
|
} catch (e) {
|
await new import_session.Session(entry).stop().catch(() => {
|
});
|
throw e;
|
}
|
}
|
async function runInitWorkspace(args, output) {
|
const cliPath = (0, import_package.libPath)("entry", "cliDaemon.js");
|
const daemonArgs = [cliPath, "--init-workspace", ...args.skills ? ["--init-skills", String(args.skills)] : []];
|
await new Promise((resolve, reject) => {
|
const child = (0, import_child_process.spawn)(process.execPath, daemonArgs, {
|
stdio: output.installStdio(),
|
cwd: process.cwd()
|
});
|
child.on("close", (code) => {
|
if (code === 0)
|
resolve();
|
else
|
reject(new Error(`Workspace initialization failed with exit code ${code}`));
|
});
|
});
|
}
|
async function installBrowser() {
|
const argv = process.argv.map((arg) => arg === "install-browser" ? "install" : arg);
|
const { libCli } = require("../../coreBundle.js");
|
const { program: program2 } = require("../../utilsBundle.js");
|
if (!program2.version())
|
libCli.decorateProgram(program2);
|
program2.parse(argv);
|
}
|
const daemonProcessPatterns = ["run-mcp-server", "run-cli-server", "cli-daemon", "cliDaemon.js", "dashboardApp.js"];
|
async function killAllDaemons() {
|
const platform = import_os.default.platform();
|
const pidFilterEnv = process.env.PWTEST_KILL_ALL_PID_FILTER_FOR_TEST;
|
const pidFilter = pidFilterEnv ? new Set(pidFilterEnv.split(",").map((p) => parseInt(p, 10)).filter((n) => !isNaN(n))) : void 0;
|
const killed = [];
|
try {
|
if (platform === "win32") {
|
const clauses = [`(${daemonProcessPatterns.map((p) => `$_.CommandLine -like '*${p}*'`).join(" -or ")})`];
|
if (pidFilter)
|
clauses.push(`(${[...pidFilter].map((p) => `$_.ProcessId -eq ${p}`).join(" -or ")})`);
|
const whereClause = clauses.join(" -and ");
|
const result = (0, import_child_process.execSync)(
|
`powershell -NoProfile -NonInteractive -Command "Get-CimInstance Win32_Process | Where-Object { ${whereClause} } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue; $_.ProcessId }"`,
|
{ encoding: "utf-8" }
|
);
|
const pids = result.split("\n").map((line) => line.trim()).filter((line) => /^\d+$/.test(line));
|
for (const pid of pids)
|
killed.push(parseInt(pid, 10));
|
} else {
|
const result = (0, import_child_process.execSync)("ps auxww", { encoding: "utf-8" });
|
const lines = result.split("\n");
|
for (const line of lines) {
|
if (daemonProcessPatterns.some((p) => line.includes(p))) {
|
const parts = line.trim().split(/\s+/);
|
const pid = parts[1];
|
if (pid && /^\d+$/.test(pid)) {
|
const numericPid = parseInt(pid, 10);
|
if (pidFilter && !pidFilter.has(numericPid))
|
continue;
|
try {
|
process.kill(numericPid, "SIGKILL");
|
killed.push(numericPid);
|
} catch {
|
}
|
}
|
}
|
}
|
}
|
} catch (e) {
|
}
|
return killed;
|
}
|
async function collectList(registry, clientInfo, all) {
|
const browsers = [];
|
const entries = registry.entryMap();
|
const serverEntries = await import_serverRegistry.serverRegistry.list();
|
const key = (0, import_registry.clientKey)(clientInfo);
|
for (const [workspaceKey, list] of entries) {
|
if (!all && workspaceKey !== key)
|
continue;
|
for (const entry of list) {
|
const session = new import_session.Session(entry);
|
const canConnect = await session.canConnect();
|
if (!canConnect) {
|
await session.deleteSessionConfig();
|
continue;
|
}
|
const config = session.config;
|
const channel = config.browser?.launchOptions.channel ?? config.browser?.browserName;
|
browsers.push({
|
name: session.name,
|
workspace: workspaceKey,
|
status: canConnect ? "open" : "closed",
|
browserType: channel,
|
userDataDir: config.browser?.userDataDir ?? null,
|
headed: config.browser ? !config.browser.launchOptions.headless : void 0,
|
persistent: !!config.cli.persistent,
|
attached: !!config.attached,
|
compatible: session.isCompatible(clientInfo),
|
version: config.version
|
});
|
}
|
}
|
if (!all)
|
return { all, browsers };
|
const servers = [...serverEntries.values()].flat();
|
return { all, browsers, servers, channelSessions: await (0, import_channelSessions.listChannelSessions)() };
|
}
|
function validateFlags(args, command, output) {
|
const unknownFlags = [];
|
for (const key of Object.keys(args)) {
|
if (key === "_")
|
continue;
|
if (globalOptions.includes(key))
|
continue;
|
if (!(key in command.flags))
|
unknownFlags.push(key);
|
}
|
if (unknownFlags.length)
|
output.errorUnknownOption(unknownFlags, command.help);
|
}
|
function validateArgs(args, command, output) {
|
const positional = args._.slice(1);
|
if (positional.length > command.args.length)
|
output.errorTooManyArguments(command.args.length, positional.length, command.help);
|
}
|
function calculateSha1(buffer) {
|
const hash = import_crypto.default.createHash("sha1");
|
hash.update(buffer);
|
return hash.digest("hex");
|
}
|
// Annotate the CommonJS export names for ESM import in node:
|
0 && (module.exports = {
|
calculateSha1,
|
program
|
});
|