@taole/dd-playwright 0.1.12 → 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 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`。
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. 支持多账号管理、一键设为当前激活账号或解绑账号。
@@ -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.12";
14
+ export const MCP_VERSION = "0.1.14";
15
15
 
16
16
  function textResult(payload) {
17
17
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taole/dd-playwright",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Standalone stdio MCP & CLI: Playwright browser automation + isolated whistle proxy + diandian account storageState injection",
5
5
  "type": "module",
6
6
  "license": "ISC",
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import { AccountStore } from "../account-store.mjs";
5
5
  import { DiandianAuthManager } from "../auth/diandian-auth.mjs";
6
6
  import { getChromiumMetadata } from "../browser-check.mjs";
7
+ import { createWebUpdater } from "./web-update.mjs";
7
8
 
8
9
  function sendJson(res, statusCode, payload) {
9
10
  res.writeHead(statusCode, {
@@ -336,18 +337,7 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
336
337
  </button>
337
338
  </div>
338
339
 
339
- <!-- 动态运行环境提示卡片 -->
340
- <div id="envBanner" style="padding: 12px 16px; border-radius: 6px; margin-bottom: 20px; font-size: 13px; line-height: 1.5; {{BANNER_STYLE}}">
341
- {{BANNER_CONTENT}}
342
- </div>
343
- <!-- 配置建议区块 -->
344
340
  {{CONFIG_SECTIONS}}
345
-
346
- <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;">
347
- 💡 <b>配置生效小提示:</b><br>
348
- 1. 挂载后启动 AI 客户端,AI 会自动识别 <code>browser_navigate</code>、<code>browser_screenshot</code> 等 13 个完整 Playwright 工具。<br>
349
- 2. 在本页面扫码绑定的账号,MCP 会自动读取同一个 SQLite 账号库免登运行。
350
- </div>
351
341
  </div>
352
342
  </div>
353
343
 
@@ -478,6 +468,12 @@ browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "desktop", userAg
478
468
  本服务所有数据与账号库已默认固化在用户主目录下的 <code>~/.dd-browser-mcp/runtime.db</code>,无需配置任何复杂环境变量,开箱即用。
479
469
  </div>
480
470
 
471
+ <div style="margin-top: 20px; padding: 16px; background: #0f172a; border-radius: 8px; border: 1px solid var(--border);">
472
+ <div style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">软件更新</div>
473
+ <div id="updateInfo" style="font-size: 12px; color: var(--muted); line-height: 1.8; margin-bottom: 12px;" aria-live="polite">点击检查更新,查看是否有新版本。</div>
474
+ <button id="checkUpdateBtn" class="btn btn-primary" onclick="checkUpdates()">检查更新</button>
475
+ </div>
476
+
481
477
  <!-- Chromium 浏览器内核状态展示卡片 -->
482
478
  <div style="margin-top: 20px; padding: 16px; background: #0f172a; border-radius: 8px; border: 1px solid var(--border);">
483
479
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
@@ -774,6 +770,25 @@ browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "desktop", userAg
774
770
  }
775
771
 
776
772
  // 初次加载账号、配置与内核状态
773
+ async function checkUpdates() {
774
+ const info = document.getElementById('updateInfo');
775
+ const check = document.getElementById('checkUpdateBtn');
776
+ check.disabled = true;
777
+ info.textContent = '正在查询 npm 最新版本…';
778
+ try {
779
+ const response = await fetch('/api/update/check', { cache: 'no-store' });
780
+ const data = await response.json();
781
+ if (!data.ok) throw new Error(data.error || data.message || '检查失败');
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
+ }
787
+ } catch (err) {
788
+ info.textContent = '检查更新失败:' + err.message;
789
+ } finally { check.disabled = false; }
790
+ }
791
+
777
792
  loadAccounts();
778
793
  loadSettings();
779
794
  loadChromiumMeta();
@@ -781,8 +796,34 @@ browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "desktop", userAg
781
796
  </body>
782
797
  </html>`;
783
798
 
784
- export function createWebServer({ accountStore, authManager }) {
799
+ export function createWebServer({ accountStore, authManager, updater, port = 12309 }) {
800
+ updater ??= createWebUpdater({ port });
785
801
  const server = createServer(async (req, res) => {
802
+ // Update endpoints are privileged even when the listener binds 0.0.0.0.
803
+ if (req.url?.split("?")[0].startsWith("/api/update")) {
804
+ const address = req.socket.remoteAddress || "";
805
+ const local = address === "::1" || /^127(?:\.\d{1,3}){3}$/.test(address) || /^::ffff:127(?:\.\d{1,3}){3}$/i.test(address);
806
+ const host = req.headers.host;
807
+ let originOk = false;
808
+ try {
809
+ const origin = new URL(req.headers.origin);
810
+ const target = new URL(`http://${host}`);
811
+ originOk = origin.origin === target.origin && origin.username === "" && origin.password === "" && req.headers.origin === origin.origin &&
812
+ (target.hostname === "localhost" || target.hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(target.hostname));
813
+ } catch {}
814
+ if (!local || (req.headers.origin && !originOk) || (req.method === "POST" && !originOk)) {
815
+ res.writeHead(403, { "Content-Type": "application/json", "Cache-Control": "no-store" });
816
+ res.end(JSON.stringify({ ok: false, error: "仅允许本机同源请求" }));
817
+ return;
818
+ }
819
+ const sendUpdate = (code, body) => { res.writeHead(code, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); res.end(JSON.stringify(body)); };
820
+ const path = req.url.split("?")[0];
821
+ if (req.method === "GET" && path === "/api/update/check") {
822
+ try { sendUpdate(200, { ok: true, ...await updater.check() }); }
823
+ catch (err) { sendUpdate(502, { ok: false, error: err.message }); }
824
+ } else { sendUpdate(404, { ok: false, error: "Not Found" }); }
825
+ return;
826
+ }
786
827
  const urlObj = new URL(req.url, `http://${req.headers.host || "127.0.0.1"}`);
787
828
  const pathname = urlObj.pathname;
788
829
  // CORS preflight
@@ -798,8 +839,6 @@ export function createWebServer({ accountStore, authManager }) {
798
839
 
799
840
  // 1. 首页控制台
800
841
  if (pathname === "/" || pathname === "/index.html") {
801
- const currentFilePath = fileURLToPath(import.meta.url).replaceAll("\\", "/");
802
- const isInstalledViaNpm = /\/node_modules\/(@taole\/)?dd-playwright\//i.test(currentFilePath);
803
842
  const serverScriptPath = fileURLToPath(
804
843
  new URL("../../bin/mcp-server.mjs", import.meta.url),
805
844
  ).replaceAll("\\", "/");
@@ -817,12 +856,9 @@ export function createWebServer({ accountStore, authManager }) {
817
856
  2,
818
857
  );
819
858
 
820
- let bannerStyle = "background: rgba(59, 130, 246, 0.12); border: 1px solid rgba(59, 130, 246, 0.3); color: #60a5fa;";
821
- 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>`;
822
-
823
859
  let configSections = `
824
860
  <div class="code-title">
825
- <span>本地绝对路径配置(推荐,开箱即用)</span>
861
+ <span>MCP 配置</span>
826
862
  <button class="copy-btn" onclick="copyCode('code-mcp-local')">复制代码</button>
827
863
  </div>
828
864
  <div id="code-mcp-local" class="code-block">${localConfigStr}</div>
@@ -837,8 +873,6 @@ export function createWebServer({ accountStore, authManager }) {
837
873
 
838
874
  const renderedHtml = DASHBOARD_HTML
839
875
  .replace("{{VERSION}}", pkgVersion)
840
- .replace("{{BANNER_STYLE}}", bannerStyle)
841
- .replace("{{BANNER_CONTENT}}", bannerContent)
842
876
  .replace("{{CONFIG_SECTIONS}}", configSections);
843
877
  sendHtml(res, renderedHtml);
844
878
  return;
@@ -1021,7 +1055,7 @@ export async function startWebServer({
1021
1055
  } = {}) {
1022
1056
  const store = accountStore || new AccountStore(dbPath);
1023
1057
  const authManager = new DiandianAuthManager({ accountStore: store });
1024
- const server = createWebServer({ accountStore: store, authManager });
1058
+ const server = createWebServer({ accountStore: store, authManager, port });
1025
1059
 
1026
1060
  return new Promise((resolve, reject) => {
1027
1061
  server.listen(port, "0.0.0.0", () => {
@@ -0,0 +1,13 @@
1
+ import { compareSemver, getLocalVersion, getRemoteLatestVersion } from "../updater.mjs";
2
+
3
+ /** 仅检查版本,不执行安装或重启。 */
4
+ export function createWebUpdater({ getVersion = getLocalVersion, getLatest = getRemoteLatestVersion } = {}) {
5
+ return {
6
+ async check() {
7
+ const currentVersion = getVersion();
8
+ const latestVersion = await getLatest();
9
+ if (!latestVersion) throw new Error("无法查询 npm 最新版本,请检查网络");
10
+ return { currentVersion, latestVersion, updateAvailable: compareSemver(latestVersion, currentVersion) > 0 };
11
+ },
12
+ };
13
+ }