Skip to content

Add pagx preview CLI and MCP service with file watching, integrated into pagx CLI - #3642

Merged
shlzxjp merged 15 commits into
mainfrom
feature/ashstarli-preview
Aug 1, 2026
Merged

Add pagx preview CLI and MCP service with file watching, integrated into pagx CLI#3642
shlzxjp merged 15 commits into
mainfrom
feature/ashstarli-preview

Conversation

@Ashlllx

@Ashlllx Ashlllx commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

概述

新增 pagx-preview 工具,为 PAGX 动画文件提供本地实时预览能力,并作为 pagx preview 子命令集成进 @libpag/pagx CLI。终端用户无需单独安装,直接通过主 CLI 即可使用。

工具提供两种使用形态:

  1. 浏览器实时预览:一行命令启动本地 HTTP 服务,自动打开浏览器,文件变更时自动重新渲染。
  2. MCP 服务:作为 MCP (Model Context Protocol) stdio 服务器接入 AI 编码助手(CodeBuddy / Claude Desktop / VS Code Copilot),在对话中直接预览 .pagx 文件。

主要改动

新增 playground/pagx-preview/ 工具

  • CLI + 后台守护进程:一行命令启动预览,后台常驻服务多文件复用,支持 stop / --log 等参数。
  • 文件监听与实时刷新:基于 chokidar 监听 .pagx 文件变更,通过 SSE 推送到浏览器实时重新渲染。
  • 浏览器预览客户端:内置播放控制、支持拖放 .pagx 文件到窗口预览。
  • MCP 服务模式--mcp):通过 stdio 通信 MCP 协议,自动启动本地 HTTP 服务用于 WASM 渲染,提供 preview_pagxpreview_pagx_widgetreload_fileget_document 四个工具。
  • 字体自动下载:首次运行懒下载 NotoSansSC + NotoColorEmoji 到 ~/.pagx/fonts/

集成进 @libpag/pagx CLI(cli/npm/

  • bin/pagx.js 拦截 preview 子命令,委托给内置的 preview 模块运行;并将 preview 注入到 pagx --help 输出。
  • package.json 新增 @modelcontextprotocol/sdkchokidarexpress 依赖,并把 preview/ 目录加入发布文件列表。

⚠️ 小窗(内联 widget)预览支持有限

当前对话内小窗预览(preview_pagx_widget)的宿主支持并不完善,这是本 PR 的已知限制:

  • 内联 widget 已在官方 ext-apps basic-host 参考宿主中验证通过,但各桌面端宿主对 MCP Apps 内联 widget 的支持情况不一:

    宿主 内联 widget 状态
    Claude Desktop widget iframe 可能不被宿主挂载(已知 host 侧 bug)
    CodeBuddy IDE 暂不支持 MCP Apps 内联 widget
    VS Code Copilot 在 Simple Browser(编辑器标签)中打开,而非真正的内联 widget
  • 因此 preview_pagx(在 IDE webview 面板 / 浏览器中打开)被设为默认工具preview_pagx_widget 仅在用户明确要求「内联 / 小窗」预览时才使用。

  • 无论哪种宿主,session URL(http://127.0.0.1:<端口>/session/<id>/)都能提供带实时重载的完整预览作为可靠回退。

后续待各宿主对 MCP Apps 内联渲染的支持成熟后,再完善小窗体验。

测试情况

  • 浏览器预览:文件监听、实时重载、拖放、播放控制均正常。
  • MCP 服务:stdio 启动、四个工具调用、HTTP 渲染服务正常。
  • 独立命令(pagx-preview)与主 CLI 子命令(pagx preview)两种入口均验证通过。

备注

  • 发布方式:本工具随 @libpag/pagxpagx preview 子命令分发,终端用户无需单独安装。
  • 详细使用与开发说明见 playground/pagx-preview/README.zh_CN.md

@codecov-commenter

codecov-commenter commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.03%. Comparing base (05aebeb) to head (79fb37b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3642      +/-   ##
==========================================
- Coverage   83.04%   83.03%   -0.01%     
==========================================
  Files         712      712              
  Lines       94876    94876              
  Branches    26597    26597              
==========================================
- Hits        78793    78784       -9     
- Misses      10410    10413       +3     
- Partials     5673     5679       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread cli/npm/package.json
"files": [
"bin/",
"html-snapshot/",
"preview/",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[发布流程] 打包未自动拷贝 preview/,依赖手工发布

files 声明了 preview/,但仓库里的 pack.shprepack(仅跑 build-html-snapshot.js)都没有把 playground/pagx-preview/(含已构建的 wasm/ 产物)拷进 cli/npm/preview/ 的步骤。

目前能发出可用的包依赖发布者手工拷贝(已验证已发布的 0.4.39 里 preview/ 存在,但该拷贝逻辑不在本仓脚本中)。换人发布或 CI 自动发布时,只跑仓库脚本会漏掉 preview/ 与其 wasm/ 产物,发出无法使用 pagx preview 的坏包。

建议:在 pack.sh/prepack 中固化「构建 pagx-preview → 拷贝到 cli/npm/preview/(含 wasm 产物,剔除 devDeps/map)」,并把 preview 产物校验接入 prepublishOnly

Comment thread cli/npm/bin/pagx.js Outdated
process.exit(1);
}
const { spawn } = require('child_process');
const child = spawn('node', [previewEntry, ...process.argv.slice(3)], { stdio: 'inherit' });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[严重] 硬编码 spawn('node') 且缺少 error 事件处理

两个隐患:

  1. 硬编码 'node':依赖 PATH 中存在 node。用户经 nvm/Volta/asdf 切换、Windows 未加入 PATH、或通过打包运行时启动 pagx 时,node 未必可解析。应使用当前解释器 process.execPath —— 同 PR 的 playground/pagx-preview/src/daemon.jsspawn(process.execPath, ...) 正是这样做的,两处不一致。
  2. child.on('error'):spawn 失败(ENOENT/EACCES)会触发 error 事件,未监听将抛未捕获异常使进程崩溃(stack trace),而非 bin/pagx.js:77 那样的友好提示。

建议:

const child = spawn(process.execPath, [previewEntry, ...process.argv.slice(3)], { stdio: 'inherit' });
child.on('error', (err) => { console.error(`pagx: failed to start preview: ${err.message}`); process.exit(1); });
child.on('exit', (code) => process.exit(code != null ? code : 1));

// The write itself will trigger the session's file watcher, which broadcasts a `reload` SSE
// event that all connected tabs pick up naturally - so this endpoint stops at persistence and
// leaves refresh to the existing reload path.
app.put(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[安全] 写盘接口 PUT /session/:id/pagx 缺 CSRF 校验(配合全局 CORS *)

POST /sessions 已通过 Sec-Fetch-Site/Origin 校验防跨站(index.js:183-187,很好),但这个会覆盖磁盘文件的写接口没有同类校验;而 index.js:96-105 对所有路由发 Access-Control-Allow-Origin: * 且允许所有方法。

服务器也未校验 Host 头,127.0.0.1 绑定挡不住 DNS rebinding:恶意页面把域名重绑到 127.0.0.1、扫到端口后,对可猜的 session id(文件会话 id = sha1(绝对路径).slice(0,10),低熵)发起 PUT,即可用攻击者字节覆盖用户正在预览的 .pagx。虽为本地工具、前置条件较多,但确实暴露了一个跨站可达的写文件原语

建议:对 PUT / POST /session/:id/{resources,document} 等写类接口施加与 /sessions 相同的 Sec-Fetch-Site 校验;并增加 Host 头白名单(仅 127.0.0.1/localhost)以防 rebinding。

{
name: 'get_document',
description:
'Get a summary of the loaded pagx document: dimensions and animation duration. The summary is uploaded by the client after load; it may be null if the client has not finished loading yet. If it keeps returning "not loaded" for several consecutive calls, the inline widget is likely not rendering in this host and the result will switch to a fallback that returns a browser-openable url (fallbackToWebview: true) — open that url in a webview / browser instead of polling further.',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[对外接口] get_document 描述宣称返回 nodeCount,但实际恒为 0

该工具描述与相关注释宣称返回文档节点信息,但客户端上传的 summary.nodeCount 恒为 0 —— 见 static/mcp-widget.js:135src/server/index.js:391 的注释均写明「nodeCount is not exposed by the viewer yet; we send 0」。对外工具描述承诺了未实现的字段,会误导调用它的 LLM(例如据此判断文档复杂度)。

建议:在 viewer 真正暴露节点数之前,从描述中删除节点数相关措辞(只保留 dimensions + duration),或在 viewer 暴露真实值后再宣称。

// that support MCP Apps. It is the default "open in a webview panel / browser" path.
},
{
name: 'preview_pagx_widget',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[对外接口精简度] preview_pagx_widgetpreview_pagx 高度重叠,且 widget 在主流宿主均不可用

两个工具入参 schema 完全相同,实现共用 handlePreviewPagx(tools.js:102-107),仅差一个 _meta.ui 和返回文案。而据 PR 描述,inline widget 在 Claude Desktop / CodeBuddy IDE / VS Code Copilot 三大宿主均不可用,PR 也已把它降级为非默认。

为一个「多数宿主跑不起来」的能力长期占用一个独立对外工具位,会增加 LLM 的选择成本与误用概率(需靠冗长 description 反复引导「仅在用户明确要小窗时用」)。

建议:合并为单个 preview_pagx + 可选布尔参数(如 inline),由一处逻辑分派;或在各宿主对 MCP Apps inline widget 支持成熟前先不暴露 widget 工具,仅保留可靠的 webview/browser 路径。

@Ashlllx
Ashlllx requested a review from kevingpqi123 as a code owner July 31, 2026 07:32
@shlzxjp
shlzxjp merged commit 2a359b4 into main Aug 1, 2026
9 checks passed
@shlzxjp
shlzxjp deleted the feature/ashstarli-preview branch August 1, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants