@taole/dd-playwright 0.1.0 → 0.1.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/SKILL.md CHANGED
@@ -1,109 +1,28 @@
1
1
  ---
2
2
  name: dd-playwright
3
3
  description: >-
4
- 使用 dd-playwright (别名: dd-pw, dd-browser) 进行点点前端业务页面与 H5 活动的自动化测试、免登导航、页面截图、表单填写、键盘按键、DOM 提取、JS 评估与 Whistle 代理规则调试。
5
- 支持多账号免登与游客匿名态模拟,通过本地扫码控制台绑定点点账号。
6
- 当用户提到点点页面测试、免登访问、浏览器截图、控制台报错排查、本地 dev server 代理映射、dd-playwright、dd-pw、dd-browser 时使用。
4
+ 通过 dd-playwright MCP 服务(提供 browser_* 工具集)进行点点前端业务页面与 H5 活动的自动化测试、免登导航、页面截图、表单交互与 Whistle 代理调试。
5
+ 当用户提到点点页面测试、免登访问、浏览器截图、控制台报错排查、本地 dev server 代理映射、dd-playwright、dd-pw 时使用。
7
6
  ---
8
7
 
9
- # dd-playwright (dd-pw)
8
+ # dd-playwright 自动化指南
10
9
 
11
- 增强型 Playwright 浏览器自动化 MCP 服务与扫码控制台,专为点点业务前端调试和 AI Agent 设计。
10
+ 本技能必须通过 **`dd-playwright` MCP 服务**提供的 `browser_*` 系列工具执行。
12
11
 
13
- ## 核心特性
12
+ ## 1. 登录态与身份切换
13
+ - **默认免登**:访问点点业务页面必须调用本 MCP 的 `browser_navigate` 工具,自动注入当前激活账号的凭据(包含 `Tuwan_Passport` 等 Cookie)。
14
+ - **测试游客态/未登录拦截**:调用 `browser_navigate` 时必须显式指定 `anonymous: true`,用于验证未登录弹窗、游客导流或未鉴权状态。
15
+ - **指定测试账号**:传入特定账号的 `uid`(如 `browser_navigate({ url, uid: "28689781" })`),自动隔离并切换独立浏览器上下文。
14
16
 
15
- 1. **自动免登注入**:访问 `*.tuwan.com` 时自动注入扫码绑定的点点测试账号凭据(`Tuwan_Passport` 等 Cookie)。
16
- 2. **多账号与匿名切换**:
17
- - 默认使用控制台设为「默认账号」的凭据。
18
- - `browser_navigate` 支持显式指定 `uid: "12345"` 切换不同测试身份。
19
- - 支持 `anonymous: true` 模拟未登录的游客态访问,测试未登录拦截与导流逻辑。
20
- 3. **独立 Whistle 代理托管**:
21
- - `browser_set_proxy_rule` 可声明 `本地端口 -> 干净 *.tuwan.com 域名` 的代理规则。
22
- - 进程内完全隔离临时 Whistle 配置,绝不污染系统或用户目录 `~/.WhistleAppData`。
23
- 4. **一键 Web 扫码绑定**:
24
- - 内置 Web 控制台(默认监听 `12309` 端口),手机扫码即刻完成账号绑定并同步至 MCP。
17
+ ## 2. 本地开发服务联调(Dev Server 代理映射)
18
+ - 当本地启动了前端开发服务器(如 Vite/Webpack 监听本地端口)时,**严禁修改系统 hosts**。
19
+ - 必须先调用本 MCP 的 `browser_set_proxy_rule` 将本地端口映射到合法的点点子域名(子域名不要求在线上真实存在,支持任意自定义前缀)。
20
+ - **示例**:
21
+ - 本地 dev server 运行在 `127.0.0.1:9527` 时,可映射为自定义域名 `p9527-test.tuwan.com`:
22
+ `browser_set_proxy_rule({ domain: "p9527-test.tuwan.com", port: 9527 })`
23
+ - 随后调用 `browser_navigate({ url: "http://p9527-test.tuwan.com/" })` 访问,进程内隔离 Whistle 代理会自动转发流量并正确携带点点线上 Cookie。
25
24
 
26
- ---
27
-
28
- ## 快速配置 MCP 客户端
29
-
30
- 在你的 AI 客户端(如 **omp**, **Cursor**, **Claude Desktop** 等)的 MCP 配置文件中添加:
31
-
32
- ### 1. 全局命令方式(推荐)
33
- ```json
34
- {
35
- "mcpServers": {
36
- "dd-playwright": {
37
- "command": "dd-playwright-mcp"
38
- }
39
- }
40
- }
41
- ```
42
-
43
- ### 2. 源码路径方式(本地调试)
44
- ```json
45
- {
46
- "mcpServers": {
47
- "dd-playwright": {
48
- "command": "node",
49
- "args": ["D:/tools/dd-browser-mcp/bin/mcp-server.mjs"]
50
- }
51
- }
52
- }
53
- ```
54
-
55
- ---
56
-
57
- ## 工具集 (Tools) 契约
58
-
59
- | 工具名称 | 关键参数 | 说明 |
60
- |---|---|---|
61
- | `browser_navigate` | `url` (必填)<br>`uid` (可选): 指定点点账号 UID<br>`anonymous` (可选): `true` 模拟游客态<br>`waitFor` (可选): 等待的 DOM 选择器<br>`timeout` (可选, 默认 15000ms) | 访问目标页面,返回 HTTP 状态码、页面标题与控制台报错统计。自动注入点点 storageState。不同身份调用自动隔离上下文。 |
62
- | `browser_screenshot` | `selector` (可选): 指定元素选择器<br>`fullPage` (可选, 默认 false): 整页滚动截图 | 截取页面或特定元素,落盘到数据目录,返回本地图片路径。 |
63
- | `browser_get_console_errors` | 无 | 收集当前页面自导航以来的 console.error 与未捕获异常。 |
64
- | `browser_click_element` | `selector` (必填)<br>`timeout` (可选, 默认 10000ms) | 模拟用户点击指定 DOM 元素(按钮、选项卡等)。 |
65
- | `browser_fill_text` | `selector` (必填)<br>`text` (必填)<br>`timeout` (可选) | 向目标输入框/文本域填入文本,原生触发 input 与 change 事件。 |
66
- | `browser_press_key` | `key` (必填, 如 "Enter", "Tab", "Escape")<br>`selector` (可选): 目标聚焦元素 | 模拟键盘单键或组合键按下。 |
67
- | `browser_hover` | `selector` (必填)<br>`timeout` (可选) | 鼠标悬停在指定 DOM 元素上,触发 hover 样式或 Tooltip 浮层。 |
68
- | `browser_scroll` | `direction` ("down" 或 "up")<br>`deltaY` (可选数值)<br>`selector` (可选): 指定滚动元素 | 滚动视口或将指定元素滚动至可视区域。 |
69
- | `browser_wait_for` | `selector` (可选)<br>`state` ("visible", "hidden", "attached", "detached")<br>`timeout` (可选) | 等待页面加载状态或特定 DOM 元素出现/隐藏。 |
70
- | `browser_get_dom` | `interactiveOnly` (可选, 默认 true)<br>`maxDepth` (可选, 默认 10) | 结构化提取页面可见交互元素清单(含文本与选择器),免去盲猜或截大图。 |
71
- | `browser_eval_js` | `script` (必填): JS 代码字符串 | 在当前浏览器页面上下文中执行任意 JavaScript 脚本并返回结果。 |
72
- | `browser_set_proxy_rule` | `domain` (必填, 需匹配 `*.tuwan.com`)<br>`port` (必填, 本地服务端口)<br>`protocol` (可选, "http" 或 "https") | 声明本地 dev server 代理映射,使本地开发服务可通过合法的点点域名访问并正常携带登录 Cookie。 |
73
- | `browser_status` | 无 | 查询当前服务版本、激活点点账号、代理状态与数据目录。 |
74
-
75
- ---
76
-
77
- ## Web 账号控制台 (扫码绑定)
78
-
79
- 在终端运行以下命令启动管理控制台:
80
-
81
- ```bash
82
- # 后台静默启动(推荐,不占控制台终端):
83
- dd-pw startweb
84
-
85
- # 查看后台运行状态与 PID:
86
- dd-pw statusweb
87
-
88
- # 停止后台服务:
89
- dd-pw stopweb
90
-
91
- # 前台阻塞运行(Ctrl+C 退出):
92
- dd-pw serve
93
- ```
94
-
95
- - 打开浏览器访问:`http://127.0.0.1:12309`
96
- - 点击 **「+ 添加绑定账号」**,使用微信或点点 App 扫描二维码即可。
97
- - 控制台支持多账号查看、设为默认账号或解绑账号。
98
-
99
- ---
100
-
101
- ## Agent 使用最佳实践
102
-
103
- 1. **先排查状态**:在执行复杂的业务排查或测试前,先调用 `browser_status` 确认是否已加载登录态或默认账号 UID。
104
- 2. **测试登录态与未登录态对比**:
105
- - 先调用 `browser_navigate({ url, anonymous: true })` 查看游客视角行为(如是否弹出登录拦截、展示游客文案)。
106
- - 再调用 `browser_navigate({ url })` 查看默认登录用户视角;或 `browser_navigate({ url, uid: "..." })` 验证特定业务角色。
107
- 3. **本地 dev 调试**:
108
- - 本地前端开发服务启动后(例如 `127.0.0.1:9527`),先调用 `browser_set_proxy_rule({ domain: "act-dev.tuwan.com", port: 9527 })`。
109
- - 随后调用 `browser_navigate({ url: "http://act-dev.tuwan.com/..." })`,不仅不改 hosts,且浏览器能正常携带点点线上 Cookie。
25
+ ## 3. 页面交互规范
26
+ - **元素定位**:在动态页面或选择器不确定时,优先调用 `browser_get_dom` 提取当前可见的交互元素结构(包含按钮、输入框、链接及其选择器),严禁盲猜选择器。
27
+ - **表单与按键**:表单输入调用 `browser_fill_text`(原生触发 `input` 与 `change` 事件);键盘按键/回车提交调用 `browser_press_key`。
28
+ - **状态排查**:排查免登异常前先调用 `browser_status` 确认 MCP 服务凭据挂载状态与当前 UID。
@@ -10,7 +10,7 @@ import { resolveConfig } from "../src/config.mjs";
10
10
  import { PlaywrightBrowserManager } from "../src/browser-manager.mjs";
11
11
  import { pathToFileURL } from "node:url";
12
12
 
13
- export const MCP_VERSION = "0.1.0";
13
+ export const MCP_VERSION = "0.1.1";
14
14
 
15
15
  function textResult(payload) {
16
16
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taole/dd-playwright",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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",
@@ -300,7 +300,7 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
300
300
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/><path d="M15 9h6"/></svg>
301
301
  dd-playwright 账号管理控制台
302
302
  </h1>
303
- <span style="font-size: 13px; color: var(--muted);">v0.1.0</span>
303
+ <span style="font-size: 13px; color: var(--muted);">v0.1.1</span>
304
304
  </header>
305
305
 
306
306
  <div class="nav-tabs">
@@ -326,13 +326,18 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
326
326
  <!-- TAB 2: MCP 配置说明 -->
327
327
  <div id="pane-mcp" class="tab-pane">
328
328
  <div class="card">
329
- <div class="card-title">MCP 挂载配置指南</div>
329
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
330
+ <div class="card-title" style="margin-bottom: 0;">MCP 挂载配置指南</div>
331
+ <button class="btn" onclick="installToCursor()" style="background: #0284c7; color: white; display: flex; align-items: center; gap: 7px; font-weight: 600; padding: 7px 14px;">
332
+ <svg width="14" height="16" viewBox="0 0 467 533" fill="currentColor"><path d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/></svg>
333
+ 一键安装到 Cursor
334
+ </button>
335
+ </div>
330
336
 
331
337
  <!-- 动态运行环境提示卡片 -->
332
338
  <div id="envBanner" style="padding: 12px 16px; border-radius: 6px; margin-bottom: 20px; font-size: 13px; line-height: 1.5; {{BANNER_STYLE}}">
333
339
  {{BANNER_CONTENT}}
334
340
  </div>
335
-
336
341
  <!-- 配置建议区块 -->
337
342
  {{CONFIG_SECTIONS}}
338
343
 
@@ -347,93 +352,42 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
347
352
  <!-- TAB 3: Skill 规范与工具说明 -->
348
353
  <div id="pane-skill" class="tab-pane">
349
354
  <div class="card">
350
- <div class="card-title">Skill 规范说明 (SKILL.md)</div>
351
-
355
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
356
+ <div class="card-title" style="margin-bottom: 0;">Skill 规范说明 (SKILL.md)</div>
357
+ <div style="display: flex; gap: 8px;">
358
+ <a href="/api/skill/download" download="SKILL.md" class="btn" style="background: #334155; color: white; display: flex; align-items: center; gap: 6px; font-weight: 500; padding: 7px 12px; text-decoration: none;">
359
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
360
+ 下载 SKILL.md
361
+ </a>
362
+ <button class="btn" onclick="installSkillToCursor()" style="background: #0284c7; color: white; display: flex; align-items: center; gap: 7px; font-weight: 600; padding: 7px 14px;">
363
+ <svg width="14" height="16" viewBox="0 0 467 533" fill="currentColor"><path d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/></svg>
364
+ 一键安装到 Cursor
365
+ </button>
366
+ </div>
367
+ </div>
352
368
  <div class="doc-section">
353
369
  <h3>适用场景</h3>
354
- <p>进行点点业务页面/活动 H5 自动化测试、免登导航、页面截图、控制台报错排查、本地 dev server 代理映射时由 AI Agent 自动调用。</p>
370
+ <p>适用于点点业务页面/活动 H5 自动化测试、免登导航、页面截图、控制台报错排查、本地 dev server 代理映射等任务。</p>
355
371
  </div>
356
372
 
357
373
  <div class="doc-section">
358
- <h3>工具列表契约</h3>
359
- <table class="doc-table">
360
- <thead>
361
- <tr>
362
- <th style="width: 24%;">工具名</th>
363
- <th style="width: 38%;">输入参数</th>
364
- <th style="width: 38%;">功能与行为契约</th>
365
- </tr>
366
- </thead>
367
- <tbody>
368
- <tr>
369
- <td><code>browser_navigate</code></td>
370
- <td><code>url</code> (必填)<br><code>uid</code> (可选,指定点点账号)<br><code>anonymous</code> (可选,true 为游客态)<br><code>waitFor</code>, <code>timeout</code></td>
371
- <td>访问 URL。不传 uid 取当前默认账号免登;指定 uid 切换特定身份;传 anonymous: true 模拟未登录状态。切换时自动隔离并重建 Context。</td>
372
- </tr>
373
- <tr>
374
- <td><code>browser_screenshot</code></td>
375
- <td><code>selector</code> (可选)<br><code>fullPage</code> (可选,默认 false)</td>
376
- <td>截取指定元素或当前整页,保存至本地图片文件并返回绝对路径。</td>
377
- </tr>
378
- <tr>
379
- <td><code>browser_get_console_errors</code></td>
380
- <td>(无参数)</td>
381
- <td>返回自导航以来的 console.error 与未捕获异常列表,方便诊断页面 JS 报错。</td>
382
- </tr>
383
- <tr>
384
- <td><code>browser_click_element</code></td>
385
- <td><code>selector</code> (必填)<br><code>timeout</code> (可选)</td>
386
- <td>在页面中按 CSS/XPath 选择器模拟点击元素(按钮、Tab、确认框等)。</td>
387
- </tr>
388
- <tr>
389
- <td><code>browser_fill_text</code></td>
390
- <td><code>selector</code> (必填)<br><code>text</code> (必填)<br><code>timeout</code> (可选)</td>
391
- <td>向输入框填入文本,触发 input 与 change 事件。</td>
392
- </tr>
393
- <tr>
394
- <td><code>browser_press_key</code></td>
395
- <td><code>key</code> (必填, 如 Enter, Tab, Escape)<br><code>selector</code> (可选)</td>
396
- <td>模拟键盘按键,可聚焦特定元素按键或向全局页面按键。</td>
397
- </tr>
398
- <tr>
399
- <td><code>browser_hover</code></td>
400
- <td><code>selector</code> (必填)<br><code>timeout</code> (可选)</td>
401
- <td>鼠标悬停在指定 DOM 元素上,触发 hover 菜单或 Tooltip 浮层。</td>
402
- </tr>
403
- <tr>
404
- <td><code>browser_scroll</code></td>
405
- <td><code>direction</code> ("down" / "up")<br><code>deltaY</code> (可选)<br><code>selector</code> (可选)</td>
406
- <td>视口滚动或滚动指定元素进入视图(触底加载等)。</td>
407
- </tr>
408
- <tr>
409
- <td><code>browser_wait_for</code></td>
410
- <td><code>selector</code> (可选)<br><code>state</code> ("visible" 等)<br><code>timeout</code> (可选)</td>
411
- <td>等待页面整页加载或特定 DOM 元素出现/隐藏。</td>
412
- </tr>
413
- <tr>
414
- <td><code>browser_get_dom</code></td>
415
- <td><code>interactiveOnly</code> (默认 true)<br><code>maxDepth</code> (默认 10)</td>
416
- <td>提取当前页面结构化可交互 DOM 树(按钮/输入框/链接及其选择器),免去盲猜。</td>
417
- </tr>
418
- <tr>
419
- <td><code>browser_eval_js</code></td>
420
- <td><code>script</code> (必填,JS 字符串)</td>
421
- <td>在当前页面上下文中执行任意 JavaScript 脚本并返回计算结果。</td>
422
- </tr>
423
- <tr>
424
- <td><code>browser_set_proxy_rule</code></td>
425
- <td><code>domain</code> (需匹配 *.tuwan.com)<br><code>port</code> (本地服务端口)<br><code>protocol</code> (http/https)</td>
426
- <td>启动/设置本进程内隔离 Whistle 代理,将本地端口映射到干净点点域名,免改 hosts 携带 Cookie。</td>
427
- </tr>
428
- <tr>
429
- <td><code>browser_status</code></td>
430
- <td>(无参数)</td>
431
- <td>查询当前 MCP 状态:版本号、默认账号 UID、storageState 加载情况、代理规则与数据目录。</td>
432
- </tr>
433
- </tbody>
434
- </table>
374
+ <h3>自动化执行指导准则</h3>
375
+ <p>本技能必须通过 <b><code>dd-playwright</code> MCP 服务</b>(提供 <code>browser_*</code> 工具集)执行:</p>
376
+
377
+ <div style="background: #0f172a; border: 1px solid var(--border); border-radius: 6px; padding: 14px; margin-top: 10px; font-size: 13px; line-height: 1.8; color: #cbd5e1;">
378
+ <b>1. 登录态与身份切换:</b><br>
379
+ • 访问点点业务页面必须调用本 MCP 的 <code>browser_navigate</code> 工具,自动注入当前激活账号的 storageState(含 <code>Tuwan_Passport</code> 鉴权 Cookie)免登。<br>
380
+ • 多角色或指定账号测试时传入特定 <code>uid</code>,自动隔离并重建独立浏览器上下文。<br><br>
381
+ <b>2. 本地开发服务联调(Dev Server 代理映射):</b><br>
382
+ • 本地服务启动后严禁改动系统 hosts,必须先调用 <code>browser_set_proxy_rule</code> 映射到点点子域名。<br>
383
+ • <b>示例</b>:本地 dev server 运行在 <code>127.0.0.1:9527</code> 时,可映射为自定义域名 <code>p9527-test.tuwan.com</code>(域名无需线上真实存在):<br>
384
+ <code style="color: #38bdf8;">browser_set_proxy_rule({ domain: "p9527-test.tuwan.com", port: 9527 })</code><br>
385
+ 随后访问该域名,内部隔离 Whistle 代理会自动转发流量并正确携带线上 Cookie。<br><br>
386
+ <b>3. 页面交互规范:</b><br>
387
+ • 复杂或动态页面定位困难时,优先调用 <code>browser_get_dom</code> 获取可见元素清单,严禁盲猜选择器。<br>
388
+ • 表单填写使用 <code>browser_fill_text</code>,按键使用 <code>browser_press_key</code>。
389
+ </div>
435
390
  </div>
436
- </div>
437
391
  </div>
438
392
  <!-- TAB 4: 全局配置 -->
439
393
  <div id="pane-settings" class="tab-pane">
@@ -628,6 +582,53 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
628
582
  });
629
583
  }
630
584
  }
585
+ function installToCursor() {
586
+ const serverName = 'dd-playwright';
587
+ let mcpConfigObj = {
588
+ command: 'node',
589
+ args: []
590
+ };
591
+ const localCode = document.getElementById('code-mcp-local')?.innerText;
592
+ if (localCode) {
593
+ try {
594
+ const parsed = JSON.parse(localCode);
595
+ const cfg = parsed.mcpServers?.[serverName];
596
+ if (cfg) {
597
+ mcpConfigObj = cfg;
598
+ }
599
+ } catch {}
600
+ }
601
+
602
+ // Cursor 官方标准格式:
603
+ // 路由为 anysphere.cursor-deeplink/mcp/install
604
+ // config 参数需为 Base64 编码的 JSON 字符串
605
+ const jsonStr = JSON.stringify(mcpConfigObj);
606
+ // 处理 UTF-8 字符的 Base64 编码
607
+ const base64Config = btoa(encodeURIComponent(jsonStr).replace(/%([0-9A-F]{2})/g, (match, p1) => {
608
+ return String.fromCharCode('0x' + p1);
609
+ }));
610
+ const cursorUrl = 'cursor://anysphere.cursor-deeplink/mcp/install?name=' + encodeURIComponent(serverName) + '&config=' + encodeURIComponent(base64Config);
611
+ // 唤起 Cursor
612
+ window.location.href = cursorUrl;
613
+ }
614
+ function installSkillToCursor() {
615
+ const ruleName = 'dd-playwright';
616
+ const compactRule = [
617
+ '---',
618
+ 'description: 通过 dd-playwright MCP 服务(提供 browser_* 工具集)进行点点前端业务自动化测试、免登调试、页面截图排查与 dev server 代理映射',
619
+ 'globs: *',
620
+ '---',
621
+ '# dd-playwright 自动化指南',
622
+ '本技能必须通过 dd-playwright MCP 服务提供的 browser_* 系列工具执行:',
623
+ '1. 登录态与身份切换:访问点点页面调用本 MCP 的 browser_navigate 自动注入激活账号免登;排查游客态或未登录拦截必须传 anonymous: true;指定测试账号传特定 uid。',
624
+ '2. 本地 Dev Server 联调:严禁修改系统 hosts。先调用本 MCP 的 browser_set_proxy_rule 将本地端口映射到点点子域名(如 127.0.0.1:9527 映射为 p9527-test.tuwan.com),免改 hosts 携带线上 Cookie。',
625
+ '3. 页面交互规范:定位困难时优先调用 browser_get_dom 提取可见元素结构,严禁盲猜选择器;表单填写使用 browser_fill_text,按键使用 browser_press_key。'
626
+ ].join('\\n');
627
+
628
+ const cursorUrl = 'cursor://anysphere.cursor-deeplink/rule?name=' + encodeURIComponent(ruleName) + '&text=' + encodeURIComponent(compactRule);
629
+ window.location.href = cursorUrl;
630
+ }
631
+
631
632
 
632
633
  async function loadSettings() {
633
634
  try {
@@ -722,45 +723,16 @@ export function createWebServer({ accountStore, authManager }) {
722
723
  2,
723
724
  );
724
725
 
725
- let bannerStyle = "";
726
- let bannerContent = "";
727
- let configSections = "";
728
-
729
- if (isInstalledViaNpm) {
730
- bannerStyle = "background: rgba(16, 185, 129, 0.12); border: 1px solid rgba(16, 185, 129, 0.3); color: #34d399;";
731
- bannerContent = "📦 <b>环境检测结果:已通过 npm 安装分发包</b><br><span style='color:#cbd5e1; font-size:12px;'>已自动为您推荐「全局命令配置」,直接配置命令即可被系统全局识别并运行。</span>";
732
-
733
- configSections = `
734
- <div class="code-title">
735
- <span>推荐方式:全局命令方式(最推荐)</span>
736
- <button class="copy-btn" onclick="copyCode('code-mcp-global')">复制代码</button>
737
- </div>
738
- <div id="code-mcp-global" class="code-block">${globalConfigStr}</div>
739
-
740
- <div class="code-title" style="margin-top: 24px;">
741
- <span style="color: var(--muted);">备用方式:模块绝对路径方式</span>
742
- <button class="copy-btn" onclick="copyCode('code-mcp-local')">复制代码</button>
743
- </div>
744
- <div id="code-mcp-local" class="code-block">${localConfigStr}</div>
745
- `;
746
- } else {
747
- bannerStyle = "background: rgba(59, 130, 246, 0.12); border: 1px solid rgba(59, 130, 246, 0.3); color: #60a5fa;";
748
- bannerContent = "💻 <b>环境检测结果:本地源码目录运行(开发/克隆模式)</b><br><span style='color:#cbd5e1; font-size:12px;'>已实时解析您当前的本地绝对路径,无需全局安装即可直接挂载;若希望使用全局命令,可在根目录执行一次 <code>npm link</code>。</span>";
726
+ let bannerStyle = "background: rgba(59, 130, 246, 0.12); border: 1px solid rgba(59, 130, 246, 0.3); color: #60a5fa;";
727
+ 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>`;
749
728
 
750
- configSections = `
751
- <div class="code-title">
752
- <span>推荐方式:本地源码绝对路径方式(免全局安装,当前代码实时生效)</span>
753
- <button class="copy-btn" onclick="copyCode('code-mcp-local')">复制代码</button>
754
- </div>
755
- <div id="code-mcp-local" class="code-block">${localConfigStr}</div>
756
-
757
- <div class="code-title" style="margin-top: 24px;">
758
- <span style="color: var(--muted);">备用方式:全局命令方式(需在项目根目录下先执行 npm link)</span>
759
- <button class="copy-btn" onclick="copyCode('code-mcp-global')">复制代码</button>
760
- </div>
761
- <div id="code-mcp-global" class="code-block">${globalConfigStr}</div>
762
- `;
763
- }
729
+ let configSections = `
730
+ <div class="code-title">
731
+ <span>本地绝对路径配置(推荐,开箱即用)</span>
732
+ <button class="copy-btn" onclick="copyCode('code-mcp-local')">复制代码</button>
733
+ </div>
734
+ <div id="code-mcp-local" class="code-block">${localConfigStr}</div>
735
+ `;
764
736
 
765
737
  const renderedHtml = DASHBOARD_HTML
766
738
  .replace("{{BANNER_STYLE}}", bannerStyle)
@@ -782,6 +754,37 @@ export function createWebServer({ accountStore, authManager }) {
782
754
  }
783
755
  return;
784
756
  }
757
+ // 2.1 获取 SKILL.md 原始内容
758
+ if (pathname === "/api/skill" && req.method === "GET") {
759
+ try {
760
+ const { readFileSync } = await import("node:fs");
761
+ const skillPath = fileURLToPath(new URL("../../SKILL.md", import.meta.url));
762
+ const content = readFileSync(skillPath, "utf8");
763
+ sendJson(res, 200, { ok: true, content });
764
+ } catch (err) {
765
+ sendJson(res, 500, { ok: false, error: err.message });
766
+ }
767
+ return;
768
+ }
769
+
770
+ // 2.2 下载 SKILL.md 附件
771
+ if (pathname === "/api/skill/download" && req.method === "GET") {
772
+ try {
773
+ const { readFileSync } = await import("node:fs");
774
+ const skillPath = fileURLToPath(new URL("../../SKILL.md", import.meta.url));
775
+ const content = readFileSync(skillPath, "utf8");
776
+ res.writeHead(200, {
777
+ "Content-Type": "text/markdown; charset=utf-8",
778
+ "Content-Disposition": 'attachment; filename="SKILL.md"',
779
+ "Cache-Control": "no-cache",
780
+ });
781
+ res.end(content);
782
+ } catch (err) {
783
+ sendJson(res, 500, { ok: false, error: err.message });
784
+ }
785
+ return;
786
+ }
787
+
785
788
 
786
789
  // 3. 切换激活账号
787
790
  if (pathname === "/api/switch" && req.method === "POST") {