@taole/dd-playwright 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/mcp-server.mjs +1 -1
- package/package.json +1 -1
- package/src/server/web-server.mjs +10 -77
- package/src/server/web-update.mjs +5 -59
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ dd-pw update
|
|
|
74
74
|
dd-pw serve [--port 12309]
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
1. 启动后在浏览器打开 `http://127.0.0.1:12309`。在「全局运行配置」的「软件更新」中可检查 npm
|
|
77
|
+
1. 启动后在浏览器打开 `http://127.0.0.1:12309`。在「全局运行配置」的「软件更新」中可检查 npm 最新版本;发现新版本时,可在终端执行 `dd-pw update`,更新后在 Agent 或 IDE 中重载 MCP。源码或项目依赖运行时请按对应安装方式手动更新。
|
|
78
78
|
2. 点击右上角 **「+ 添加绑定账号」**,后台会以独立的 Playwright 实例拉起扫码页并渲染二维码。
|
|
79
79
|
3. 使用微信或点点 App 扫码登录,完成后会自动捕获 Cookie 与 `storageState`,提取点点账号 UID、昵称与头像,并落库至 `$BROWSER_MCP_DATA_DIR/runtime.db`。
|
|
80
80
|
4. 支持多账号管理、一键设为当前激活账号或解绑账号。
|
package/bin/mcp-server.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { resolveConfig } from "../src/config.mjs";
|
|
|
11
11
|
import { PlaywrightBrowserManager } from "../src/browser-manager.mjs";
|
|
12
12
|
import { pathToFileURL } from "node:url";
|
|
13
13
|
|
|
14
|
-
export const MCP_VERSION = "0.1.
|
|
14
|
+
export const MCP_VERSION = "0.1.14";
|
|
15
15
|
|
|
16
16
|
function textResult(payload) {
|
|
17
17
|
return {
|
package/package.json
CHANGED
|
@@ -337,18 +337,7 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
337
337
|
</button>
|
|
338
338
|
</div>
|
|
339
339
|
|
|
340
|
-
<!-- 动态运行环境提示卡片 -->
|
|
341
|
-
<div id="envBanner" style="padding: 12px 16px; border-radius: 6px; margin-bottom: 20px; font-size: 13px; line-height: 1.5; {{BANNER_STYLE}}">
|
|
342
|
-
{{BANNER_CONTENT}}
|
|
343
|
-
</div>
|
|
344
|
-
<!-- 配置建议区块 -->
|
|
345
340
|
{{CONFIG_SECTIONS}}
|
|
346
|
-
|
|
347
|
-
<div style="font-size: 12px; color: var(--muted); line-height: 1.6; background: #0f172a; padding: 12px; border-radius: 6px; border: 1px solid var(--border); margin-top: 20px;">
|
|
348
|
-
💡 <b>配置生效小提示:</b><br>
|
|
349
|
-
1. 挂载后启动 AI 客户端,AI 会自动识别 <code>browser_navigate</code>、<code>browser_screenshot</code> 等 13 个完整 Playwright 工具。<br>
|
|
350
|
-
2. 在本页面扫码绑定的账号,MCP 会自动读取同一个 SQLite 账号库免登运行。
|
|
351
|
-
</div>
|
|
352
341
|
</div>
|
|
353
342
|
</div>
|
|
354
343
|
|
|
@@ -481,10 +470,8 @@ browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "desktop", userAg
|
|
|
481
470
|
|
|
482
471
|
<div style="margin-top: 20px; padding: 16px; background: #0f172a; border-radius: 8px; border: 1px solid var(--border);">
|
|
483
472
|
<div style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">软件更新</div>
|
|
484
|
-
<div id="updateInfo" style="font-size: 12px; color: var(--muted); line-height: 1.8; margin-bottom: 12px;" aria-live="polite"
|
|
473
|
+
<div id="updateInfo" style="font-size: 12px; color: var(--muted); line-height: 1.8; margin-bottom: 12px;" aria-live="polite">点击检查更新,查看是否有新版本。</div>
|
|
485
474
|
<button id="checkUpdateBtn" class="btn btn-primary" onclick="checkUpdates()">检查更新</button>
|
|
486
|
-
<button id="installUpdateBtn" class="btn btn-primary" onclick="installUpdate()" style="display:none; margin-left: 8px;">立即更新</button>
|
|
487
|
-
<div style="font-size: 12px; color: var(--muted); margin-top: 10px;">更新仅针对全局安装的 npm 包。更新完成后需重启 Web 服务及 MCP 客户端;源码运行请手动更新。</div>
|
|
488
475
|
</div>
|
|
489
476
|
|
|
490
477
|
<!-- Chromium 浏览器内核状态展示卡片 -->
|
|
@@ -786,63 +773,31 @@ browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "desktop", userAg
|
|
|
786
773
|
async function checkUpdates() {
|
|
787
774
|
const info = document.getElementById('updateInfo');
|
|
788
775
|
const check = document.getElementById('checkUpdateBtn');
|
|
789
|
-
const install = document.getElementById('installUpdateBtn');
|
|
790
776
|
check.disabled = true;
|
|
791
|
-
install.style.display = 'none';
|
|
792
777
|
info.textContent = '正在查询 npm 最新版本…';
|
|
793
778
|
try {
|
|
794
779
|
const response = await fetch('/api/update/check', { cache: 'no-store' });
|
|
795
780
|
const data = await response.json();
|
|
796
781
|
if (!data.ok) throw new Error(data.error || data.message || '检查失败');
|
|
797
|
-
|
|
798
|
-
|
|
782
|
+
if (!data.updateAvailable) {
|
|
783
|
+
info.textContent = '当前已是最新版本(v' + data.currentVersion + ')。';
|
|
784
|
+
} else {
|
|
785
|
+
info.textContent = '发现新版本:v' + data.currentVersion + ' → v' + data.latestVersion + '。请在终端执行 dd-pw update,更新后在 Agent 或 IDE 中重载 MCP。';
|
|
786
|
+
}
|
|
799
787
|
} catch (err) {
|
|
800
788
|
info.textContent = '检查更新失败:' + err.message;
|
|
801
789
|
} finally { check.disabled = false; }
|
|
802
790
|
}
|
|
803
791
|
|
|
804
|
-
async function installUpdate() {
|
|
805
|
-
if (!confirm('确认更新全局安装的 dd-playwright?完成后需要重启 Web 服务和 MCP 客户端。')) return;
|
|
806
|
-
const info = document.getElementById('updateInfo');
|
|
807
|
-
const install = document.getElementById('installUpdateBtn');
|
|
808
|
-
const check = document.getElementById('checkUpdateBtn');
|
|
809
|
-
install.disabled = true;
|
|
810
|
-
check.disabled = true;
|
|
811
|
-
try {
|
|
812
|
-
const response = await fetch('/api/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' });
|
|
813
|
-
const data = await response.json();
|
|
814
|
-
if (!data.ok) throw new Error(data.error || data.message || '无法启动更新');
|
|
815
|
-
info.textContent = data.message || '正在更新,请稍候…';
|
|
816
|
-
const poll = async () => {
|
|
817
|
-
try {
|
|
818
|
-
const state = await (await fetch('/api/update/status', { cache: 'no-store' })).json();
|
|
819
|
-
if (!state.ok) throw new Error(state.error || '查询更新状态失败');
|
|
820
|
-
info.textContent = (state.message || '正在更新…') + (state.error ? ':' + state.error : '');
|
|
821
|
-
if (state.status === 'running') { setTimeout(poll, 1500); return; }
|
|
822
|
-
if (state.status === 'success') install.style.display = 'none';
|
|
823
|
-
install.disabled = false;
|
|
824
|
-
check.disabled = false;
|
|
825
|
-
} catch (err) {
|
|
826
|
-
info.textContent = '查询更新状态失败:' + err.message + ';请稍后重新检查。';
|
|
827
|
-
install.disabled = false; check.disabled = false;
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
setTimeout(poll, 1000);
|
|
831
|
-
} catch (err) {
|
|
832
|
-
info.textContent = '更新失败:' + err.message;
|
|
833
|
-
install.disabled = false; check.disabled = false;
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
|
|
837
792
|
loadAccounts();
|
|
838
793
|
loadSettings();
|
|
839
794
|
loadChromiumMeta();
|
|
840
|
-
checkUpdates();
|
|
841
795
|
</script>
|
|
842
796
|
</body>
|
|
843
797
|
</html>`;
|
|
844
798
|
|
|
845
|
-
export function createWebServer({ accountStore, authManager, updater =
|
|
799
|
+
export function createWebServer({ accountStore, authManager, updater, port = 12309 }) {
|
|
800
|
+
updater ??= createWebUpdater({ port });
|
|
846
801
|
const server = createServer(async (req, res) => {
|
|
847
802
|
// Update endpoints are privileged even when the listener binds 0.0.0.0.
|
|
848
803
|
if (req.url?.split("?")[0].startsWith("/api/update")) {
|
|
@@ -866,21 +821,6 @@ export function createWebServer({ accountStore, authManager, updater = createWeb
|
|
|
866
821
|
if (req.method === "GET" && path === "/api/update/check") {
|
|
867
822
|
try { sendUpdate(200, { ok: true, ...await updater.check() }); }
|
|
868
823
|
catch (err) { sendUpdate(502, { ok: false, error: err.message }); }
|
|
869
|
-
} else if (req.method === "GET" && path === "/api/update/status") {
|
|
870
|
-
sendUpdate(200, { ok: true, ...updater.status() });
|
|
871
|
-
} else if (req.method === "POST" && path === "/api/update") {
|
|
872
|
-
if (!/^application\/json(?:\s*;\s*charset=utf-8)?\s*$/i.test(req.headers["content-type"] || "")) {
|
|
873
|
-
sendUpdate(415, { ok: false, error: "需要 application/json" }); return;
|
|
874
|
-
}
|
|
875
|
-
let body = "";
|
|
876
|
-
for await (const chunk of req) {
|
|
877
|
-
body += chunk;
|
|
878
|
-
if (body.length > 4096) { sendUpdate(413, { ok: false, error: "请求过大" }); return; }
|
|
879
|
-
}
|
|
880
|
-
try { const data = JSON.parse(body); if (!data || typeof data !== "object" || Array.isArray(data)) throw new Error(); }
|
|
881
|
-
catch { sendUpdate(400, { ok: false, error: "需要 JSON 对象" }); return; }
|
|
882
|
-
const { started, ...result } = await updater.start();
|
|
883
|
-
sendUpdate(started ? 202 : 409, { ok: started, ...result });
|
|
884
824
|
} else { sendUpdate(404, { ok: false, error: "Not Found" }); }
|
|
885
825
|
return;
|
|
886
826
|
}
|
|
@@ -899,8 +839,6 @@ export function createWebServer({ accountStore, authManager, updater = createWeb
|
|
|
899
839
|
|
|
900
840
|
// 1. 首页控制台
|
|
901
841
|
if (pathname === "/" || pathname === "/index.html") {
|
|
902
|
-
const currentFilePath = fileURLToPath(import.meta.url).replaceAll("\\", "/");
|
|
903
|
-
const isInstalledViaNpm = /\/node_modules\/(@taole\/)?dd-playwright\//i.test(currentFilePath);
|
|
904
842
|
const serverScriptPath = fileURLToPath(
|
|
905
843
|
new URL("../../bin/mcp-server.mjs", import.meta.url),
|
|
906
844
|
).replaceAll("\\", "/");
|
|
@@ -918,12 +856,9 @@ export function createWebServer({ accountStore, authManager, updater = createWeb
|
|
|
918
856
|
2,
|
|
919
857
|
);
|
|
920
858
|
|
|
921
|
-
let bannerStyle = "background: rgba(59, 130, 246, 0.12); border: 1px solid rgba(59, 130, 246, 0.3); color: #60a5fa;";
|
|
922
|
-
let bannerContent = `💻 <b>当前代码位置:</b><code style="background: rgba(0,0,0,0.3); padding: 2px 6px; border-radius: 4px; color: #93c5fd;">${serverScriptPath}</code><br><span style="color:#cbd5e1; font-size:12px; margin-top: 4px; display: inline-block;">使用 Node 绝对路径方式挂载 MCP,不受系统 PATH 或环境切换影响,100% 稳定运行。</span>`;
|
|
923
|
-
|
|
924
859
|
let configSections = `
|
|
925
860
|
<div class="code-title">
|
|
926
|
-
<span
|
|
861
|
+
<span>MCP 配置</span>
|
|
927
862
|
<button class="copy-btn" onclick="copyCode('code-mcp-local')">复制代码</button>
|
|
928
863
|
</div>
|
|
929
864
|
<div id="code-mcp-local" class="code-block">${localConfigStr}</div>
|
|
@@ -938,8 +873,6 @@ export function createWebServer({ accountStore, authManager, updater = createWeb
|
|
|
938
873
|
|
|
939
874
|
const renderedHtml = DASHBOARD_HTML
|
|
940
875
|
.replace("{{VERSION}}", pkgVersion)
|
|
941
|
-
.replace("{{BANNER_STYLE}}", bannerStyle)
|
|
942
|
-
.replace("{{BANNER_CONTENT}}", bannerContent)
|
|
943
876
|
.replace("{{CONFIG_SECTIONS}}", configSections);
|
|
944
877
|
sendHtml(res, renderedHtml);
|
|
945
878
|
return;
|
|
@@ -1122,7 +1055,7 @@ export async function startWebServer({
|
|
|
1122
1055
|
} = {}) {
|
|
1123
1056
|
const store = accountStore || new AccountStore(dbPath);
|
|
1124
1057
|
const authManager = new DiandianAuthManager({ accountStore: store });
|
|
1125
|
-
const server = createWebServer({ accountStore: store, authManager });
|
|
1058
|
+
const server = createWebServer({ accountStore: store, authManager, port });
|
|
1126
1059
|
|
|
1127
1060
|
return new Promise((resolve, reject) => {
|
|
1128
1061
|
server.listen(port, "0.0.0.0", () => {
|
|
@@ -1,67 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { realpath } from "node:fs/promises";
|
|
3
|
-
import { promisify } from "node:util";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { PACKAGE_NAME, compareSemver, getLocalVersion, getRemoteLatestVersion } from "../updater.mjs";
|
|
1
|
+
import { compareSemver, getLocalVersion, getRemoteLatestVersion } from "../updater.mjs";
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
async function isGlobalInstall() {
|
|
12
|
-
try {
|
|
13
|
-
const windows = process.platform === "win32";
|
|
14
|
-
const { stdout } = await execFileAsync(windows ? process.env.ComSpec || "cmd.exe" : "npm", windows ? ["/d", "/s", "/c", "npm.cmd root -g"] : ["root", "-g"], { windowsHide: true, timeout: 10000 });
|
|
15
|
-
const [actual, global] = await Promise.all([realpath(packageDir), realpath(join(stdout.trim(), PACKAGE_NAME))]);
|
|
16
|
-
return windows ? actual.toLowerCase() === global.toLowerCase() : actual === global;
|
|
17
|
-
} catch { return false; }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function installGlobal() {
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
const windows = process.platform === "win32";
|
|
23
|
-
const child = spawn(windows ? process.env.ComSpec || "cmd.exe" : "npm", windows
|
|
24
|
-
? ["/d", "/s", "/c", `npm.cmd install -g ${PACKAGE_NAME}@latest`]
|
|
25
|
-
: ["install", "-g", `${PACKAGE_NAME}@latest`],
|
|
26
|
-
{ windowsHide: true, stdio: ["ignore", "ignore", "pipe"] });
|
|
27
|
-
let stderr = "";
|
|
28
|
-
child.stderr?.on("data", chunk => { stderr = (stderr + chunk.toString()).slice(-4000); });
|
|
29
|
-
child.once("error", reject);
|
|
30
|
-
child.once("close", (code, signal) => code === 0 ? resolve() : reject(new Error(stderr.trim() || `npm 退出: ${signal || code}`)));
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function createWebUpdater({ getVersion = getLocalVersion, getLatest = getRemoteLatestVersion, isGlobal = isGlobalInstall, install = installGlobal } = {}) {
|
|
35
|
-
let state = { status: "idle", error: null, message: "尚未开始更新", restartRequired: false };
|
|
3
|
+
/** 仅检查版本,不执行安装或重启。 */
|
|
4
|
+
export function createWebUpdater({ getVersion = getLocalVersion, getLatest = getRemoteLatestVersion } = {}) {
|
|
36
5
|
return {
|
|
37
6
|
async check() {
|
|
38
7
|
const currentVersion = getVersion();
|
|
39
|
-
const
|
|
8
|
+
const latestVersion = await getLatest();
|
|
40
9
|
if (!latestVersion) throw new Error("无法查询 npm 最新版本,请检查网络");
|
|
41
|
-
return { currentVersion, latestVersion, updateAvailable: compareSemver(latestVersion, currentVersion) > 0
|
|
42
|
-
installType: global ? "global" : "local", canUpdate: global,
|
|
43
|
-
message: global ? "更新完成后请手动重启服务" : "当前运行的不是 npm 全局安装,更新全局包不会更新此服务,请按当前安装方式手动更新" };
|
|
44
|
-
},
|
|
45
|
-
status() { return { ...state }; },
|
|
46
|
-
async start() {
|
|
47
|
-
if (state.status === "running") return { started: false, ...state };
|
|
48
|
-
state = { status: "running", error: null, message: "正在确认安装位置", restartRequired: false };
|
|
49
|
-
try {
|
|
50
|
-
if (!await isGlobal()) {
|
|
51
|
-
state = { status: "error", error: "当前服务不是 npm 全局安装,请手动更新当前安装", message: "无法自动更新", restartRequired: false };
|
|
52
|
-
return { started: false, ...state };
|
|
53
|
-
}
|
|
54
|
-
state.message = "正在安装更新,完成后请手动重启服务";
|
|
55
|
-
Promise.resolve().then(() => install()).then(() => {
|
|
56
|
-
state = { status: "success", error: null, message: "安装完成,请手动重启服务以使用新版本", restartRequired: true };
|
|
57
|
-
}, err => {
|
|
58
|
-
state = { status: "error", error: String(err?.message || err), message: "安装失败,服务仍在运行", restartRequired: false };
|
|
59
|
-
});
|
|
60
|
-
return { started: true, ...state };
|
|
61
|
-
} catch (err) {
|
|
62
|
-
state = { status: "error", error: String(err?.message || err), message: "更新启动失败", restartRequired: false };
|
|
63
|
-
return { started: false, ...state };
|
|
64
|
-
}
|
|
10
|
+
return { currentVersion, latestVersion, updateAvailable: compareSemver(latestVersion, currentVersion) > 0 };
|
|
65
11
|
},
|
|
66
12
|
};
|
|
67
13
|
}
|