@deppon/deppon-prd-mcp 0.1.2 → 2.5.10

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
@@ -6,7 +6,7 @@
6
6
 
7
7
  1. **Web 控制台**(`http://127.0.0.1:3847`):填写 PRD 要素(标题、背景、筛选字段、列表列等)→ 生成标准化 `prd.md`
8
8
  2. **原型生成**:根据同一套配置生成 Tailwind 高保真 `prototype.html`(含 PRD § 标注弹框)
9
- 3. **在线动态调整**:原型 iframe 支持 `?edit=1` 直接改标题等文案;Studio 侧可改工具栏/行操作并实时预览
9
+ 3. **在线动态调整(类墨刀)**:设计模式 `?design=1` 下可 **拖拽** 区块/筛选项/列表列/工具栏按钮排序,改文案,移除不合适项,保存后写入 `project.config.json` 并同步 PRD
10
10
  4. **MCP 工具**:供 Cursor Agent 调用 `generate_prd`、`generate_prototype`、`open_prd_studio` 等
11
11
 
12
12
  ## 安装(消费方)
@@ -28,20 +28,27 @@ npx @deppon/deppon-prd-mcp
28
28
 
29
29
  浏览器打开 Web 控制台:**http://127.0.0.1:3847**
30
30
 
31
+ ### 设计模式(拖拽调整布局)
32
+
33
+ 1. Web Studio → **③ 原型编辑** → 点 **🎨 设计模式(拖拽)**
34
+ 2. 或直接在原型 URL 加 `?design=1`
35
+ 3. 拖拽 **⠿** 手柄调整:筛选区/列表区块顺序、筛选项、表头列、工具栏按钮
36
+ 4. 点击 **保存布局** → 写入 `project.config.json`,并重新生成 `prd.md` + `prototype.html`
37
+
31
38
  ## Cursor / Claude Desktop MCP 配置
32
39
 
33
40
  ```json
34
41
  {
35
- "mcpServers": {
36
- "deppon-prd": {
37
- "command": "npx",
38
- "args": ["-y", "@deppon/deppon-prd-mcp@latest"],
39
- "env": {
40
- "DEPPON_PRD_WORKSPACE": "/绝对路径/到你的项目根目录",
41
- "DEPPON_PRD_HTTP_PORT": "3847"
42
- }
42
+ "mcpServers": {
43
+ "deppon-prd": {
44
+ "command": "npx",
45
+ "args": ["-y", "@deppon/deppon-prd-mcp@latest"],
46
+ "env": {
47
+ "DEPPON_PRD_WORKSPACE": "/绝对路径/到你的项目根目录",
48
+ "DEPPON_PRD_HTTP_PORT": "3847"
49
+ }
50
+ }
43
51
  }
44
- }
45
52
  }
46
53
  ```
47
54
 
@@ -58,9 +65,20 @@ npm run publish:registry # MCP 官方 Registry(需先 mcp-publisher login
58
65
 
59
66
  ### MCP 官方 Registry
60
67
 
61
- 1. 安装 CLI:`brew install mcp-publisher` 或见 [MCP Registry Quickstart](https://modelcontextprotocol.io/quickstart)
62
- 2. 登录:`mcp-publisher login github`(namespace 须为 `io.github.<你的GitHub用户名>/deppon-prd-mcp`)
63
- 3. 发布:`npm run publish:registry`
68
+ 1. **安装 CLI**(Homebrew **没有** `mcp-publisher` formula,须从 GitHub Releases 下载):
69
+
70
+ ```bash
71
+ # Apple Silicon Mac
72
+ curl -fL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_darwin_arm64.tar.gz" \
73
+ | tar xz mcp-publisher
74
+ sudo mv mcp-publisher /usr/local/bin/
75
+ # 若无 sudo,可放到 ~/.local/bin 并确保在 PATH 中
76
+
77
+ mcp-publisher --help
78
+ ```
79
+
80
+ 2. 登录:`mcp-publisher login github`(须用 GitHub 账号 **chriswong1103**)
81
+ 3. 发布:`cd packages/deppon-prd-mcp && mcp-publisher publish`
64
82
 
65
83
  Registry 名称:`io.github.chriswong1103/deppon-prd-mcp`(与 `package.json` 的 `mcpName` 一致,须用 GitHub 账号 **chriswong1103** 登录)
66
84
 
@@ -75,18 +93,27 @@ src/prototypes/<slug>/
75
93
 
76
94
  ## MCP 工具
77
95
 
78
- | 工具 | 说明 |
79
- |------|------|
80
- | `generate_prd` | 从 pageName 等要素生成 PRD 并落盘 |
81
- | `generate_prototype` | 从已有 slug 重新生成 prototype.html |
82
- | `list_prd_projects` | 列出已生成项目 |
83
- | `get_prd_project` | 读取项目配置与 PRD 摘要 |
84
- | `open_prd_studio` | 返回 Web 控制台 URL |
96
+ | 工具 | 说明 |
97
+ | ----------------------- | ------------------------------------------------------------- |
98
+ | `generate_prd` | 从 pageName 等要素生成 PRD 并落盘 |
99
+ | `generate_prototype` | 从已有 slug 重新生成 prototype.html |
100
+ | `list_prd_projects` | 列出已生成项目 |
101
+ | `get_prd_project` | 读取项目配置与 PRD 摘要 |
102
+ | `open_prd_studio` | 返回 Web 控制台 URL |
103
+ | `export_canvas_handoff` | 导出 Penpot/Reframe/**tldraw** handoff(含 `wireframe.tldr`) |
85
104
 
86
105
  ## 环境变量
87
106
 
88
- | 变量 | 默认 | 说明 |
89
- |------|------|------|
90
- | `DEPPON_PRD_WORKSPACE` | `process.cwd()` | 仓库根目录 |
91
- | `DEPPON_PRD_HTTP_PORT` | `3847` | Web 控制台端口 |
92
- | `DEPPON_PRD_HTTP_HOST` | `127.0.0.1` | 绑定地址 |
107
+ | 变量 | 默认 | 说明 |
108
+ | ---------------------- | --------------- | -------------- |
109
+ | `DEPPON_PRD_WORKSPACE` | `process.cwd()` | 仓库根目录 |
110
+ | `DEPPON_PRD_HTTP_PORT` | `3847` | Web 控制台端口 |
111
+ | `DEPPON_PRD_HTTP_HOST` | `127.0.0.1` | 绑定地址 |
112
+
113
+ ## 文档
114
+
115
+ | 文档 | 说明 |
116
+ | ---------------------------------------------------------------- | --------------------------------------------------- |
117
+ | [Cursor + MCP 使用指南](./docs/cursor-mcp-workflow.md) | Agent 生成 PRD/原型、设计模式、常见问题 |
118
+ | [**Penpot/Reframe 画布联调**](./docs/canvas-handoff-workflow.md) | PRD 走 deppon-prd,原型交 Penpot/Reframe MCP |
119
+ | [墨刀分析 & 演进路线图](./docs/modao-analysis-and-roadmap.md) | 墨刀 Proto2 技术反推、scene.json 规划、Phase 1 ~ 3 |
@@ -0,0 +1,72 @@
1
+ import type { PrdProject } from './types.js';
2
+ export interface CanvasHandoff {
3
+ version: '1.0';
4
+ source: 'deppon-prd-mcp';
5
+ slug: string;
6
+ pageName: string;
7
+ pageType: PrdProject['pageType'];
8
+ generatedAt: string;
9
+ paths: {
10
+ prd: string;
11
+ config: string;
12
+ referencePrototype: string;
13
+ handoff: string;
14
+ tldrawWireframe?: string;
15
+ };
16
+ device: {
17
+ name: string;
18
+ width: number;
19
+ height: number;
20
+ };
21
+ spec: {
22
+ background: string;
23
+ menuPath: string;
24
+ filters: PrdProject['filters'];
25
+ columns: PrdProject['columns'];
26
+ toolbarButtons: string[];
27
+ rowActions: string[];
28
+ features: PrdProject['features'];
29
+ };
30
+ widgets: Array<{
31
+ id: string;
32
+ type: string;
33
+ x: number;
34
+ y: number;
35
+ w: number;
36
+ h: number;
37
+ props: Record<string, unknown>;
38
+ }>;
39
+ penpot: {
40
+ frameName: string;
41
+ width: number;
42
+ height: number;
43
+ prompt: string;
44
+ };
45
+ reframe: {
46
+ projectSlug: string;
47
+ brandHint: string;
48
+ prompt: string;
49
+ };
50
+ tldraw: {
51
+ relativePath: string;
52
+ prompt: string;
53
+ };
54
+ }
55
+ export declare function buildCanvasHandoff(project: PrdProject): CanvasHandoff;
56
+ export declare function saveCanvasHandoff(project: PrdProject): {
57
+ handoffPath: string;
58
+ penpotPromptPath: string;
59
+ reframePromptPath: string;
60
+ tldrawPromptPath: string;
61
+ tldrawWireframePath: string;
62
+ handoff: CanvasHandoff;
63
+ nextSteps: {
64
+ penpot: string[];
65
+ reframe: string[];
66
+ tldraw: string[];
67
+ };
68
+ };
69
+ export declare function getHandoffSummary(slug: string): {
70
+ exists: boolean;
71
+ paths: string[];
72
+ };
@@ -0,0 +1,238 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getPrototypesRoot } from '../config.js';
4
+ import { getProjectDir } from './project-store.js';
5
+ import { buildTldrawFile, buildTldrawPrompt } from './tldraw-wireframe.js';
6
+ function buildWidgets(project) {
7
+ const sidebarW = 220;
8
+ const headerH = 56;
9
+ const pad = 24;
10
+ const contentX = sidebarW + pad;
11
+ const filterH = project.filters.length > 0 ? 160 : 0;
12
+ const tableTop = headerH + pad + filterH + (filterH ? 16 : 0);
13
+ const widgets = [
14
+ {
15
+ id: 'shell_sidebar',
16
+ type: 'ShellSidebar',
17
+ x: 0,
18
+ y: 0,
19
+ w: sidebarW,
20
+ h: 900,
21
+ props: { menuPath: project.menuPath, activeItem: project.pageName },
22
+ },
23
+ {
24
+ id: 'shell_header',
25
+ type: 'PageHeader',
26
+ x: sidebarW,
27
+ y: 0,
28
+ w: 1440 - sidebarW,
29
+ h: headerH,
30
+ props: { title: project.pageName, breadcrumb: project.menuPath },
31
+ },
32
+ ];
33
+ if (project.filters.length > 0) {
34
+ widgets.push({
35
+ id: 'filter_bar',
36
+ type: 'FilterBar',
37
+ x: contentX,
38
+ y: headerH + pad,
39
+ w: 1440 - sidebarW - pad * 2,
40
+ h: filterH,
41
+ props: {
42
+ title: '筛选条件',
43
+ fields: project.filters.map(f => ({
44
+ name: f.name,
45
+ type: f.type,
46
+ matchRule: f.matchRule,
47
+ })),
48
+ },
49
+ });
50
+ }
51
+ widgets.push({
52
+ id: 'data_table',
53
+ type: 'DataTable',
54
+ x: contentX,
55
+ y: tableTop,
56
+ w: 1440 - sidebarW - pad * 2,
57
+ h: 900 - tableTop - pad,
58
+ props: {
59
+ title: `${project.pageName.replace(/列表|管理/g, '').trim() || project.pageName}列表`,
60
+ columns: project.columns.map(c => c.name),
61
+ toolbarButtons: project.toolbarButtons,
62
+ rowActions: project.rowActions,
63
+ mockRows: project.mockRows?.length ? project.mockRows : undefined,
64
+ },
65
+ });
66
+ return widgets;
67
+ }
68
+ function buildPenpotPrompt(project, handoff) {
69
+ const filterLines = project.filters.map(f => `- ${f.name}(${f.type},${f.matchRule})`).join('\n');
70
+ const columnLines = project.columns.map(c => `- ${c.name}${c.description ? `:${c.description}` : ''}`).join('\n');
71
+ return `# Penpot 原型任务:${project.pageName}
72
+
73
+ 请在当前 Penpot 文件中创建一帧 **${handoff.penpot.frameName}**(${handoff.penpot.width}×${handoff.penpot.height}),绘制 B 端后台列表页高保真原型。
74
+
75
+ ## 页面背景
76
+ ${project.background}
77
+
78
+ ## 布局结构(绝对坐标参考)
79
+ ${handoff.widgets.map(w => `- **${w.type}** @ (${w.x}, ${w.y}) ${w.w}×${w.h}`).join('\n')}
80
+
81
+ ## 筛选区
82
+ ${filterLines || '(无)'}
83
+
84
+ ## 列表列
85
+ ${columnLines}
86
+
87
+ ## 工具栏按钮
88
+ ${project.toolbarButtons.join('、')}
89
+
90
+ ## 行操作
91
+ ${project.rowActions.join('、')}
92
+
93
+ ## 视觉规范
94
+ - 风格:Arco Design / 企业后台(白底、slate 边框、blue-600 主色)
95
+ - 左侧固定侧栏 220px,顶栏面包屑
96
+ - 参考 HTML 原型:${handoff.paths.referencePrototype}
97
+
98
+ ## 关联 PRD
99
+ ${handoff.paths.prd}
100
+ `;
101
+ }
102
+ function buildReframePrompt(project, handoff) {
103
+ const filterSummary = project.filters.map(f => f.name).join('、') || '无';
104
+ const columnSummary = project.columns.map(c => c.name).join('、');
105
+ return `# Reframe 原型任务:${project.pageName}
106
+
107
+ 基于 deppon-prd-mcp 已生成的 PRD,在 Reframe 画布上创建 B 端后台 **${project.pageName}** 页面。
108
+
109
+ ## 设计要求
110
+ - 品牌/风格:${handoff.reframe.brandHint}
111
+ - 设备:${handoff.device.width}×${handoff.device.height}(MacBook Pro 视口)
112
+ - 页面类型:${project.pageType}
113
+
114
+ ## 内容规格
115
+ - 背景:${project.background}
116
+ - 菜单路径:${project.menuPath}
117
+ - 筛选项:${filterSummary}
118
+ - 表格列:${columnSummary}
119
+ - 工具栏:${project.toolbarButtons.join('、')}
120
+ - 行操作:${project.rowActions.join('、')}
121
+
122
+ ## 布局区块
123
+ ${handoff.widgets.map(w => `- ${w.type}:${JSON.stringify(w.props)}`).join('\n')}
124
+
125
+ ## 工作流
126
+ 1. 读取参考 HTML:${handoff.paths.referencePrototype}
127
+ 2. 使用 reframe_compile 将结构编译为 INode 场景
128
+ 3. reframe_inspect 检查对比度与布局
129
+ 4. reframe_export format=html 导出
130
+
131
+ ## PRD 文档
132
+ ${handoff.paths.prd}
133
+
134
+ 项目 slug:\`${handoff.reframe.projectSlug}\`
135
+ `;
136
+ }
137
+ export function buildCanvasHandoff(project) {
138
+ const dir = getProjectDir(project.slug);
139
+ const widgets = buildWidgets(project);
140
+ const handoff = {
141
+ version: '1.0',
142
+ source: 'deppon-prd-mcp',
143
+ slug: project.slug,
144
+ pageName: project.pageName,
145
+ pageType: project.pageType,
146
+ generatedAt: new Date().toISOString(),
147
+ paths: {
148
+ prd: path.join(dir, 'prd.md'),
149
+ config: path.join(dir, 'project.config.json'),
150
+ referencePrototype: path.join(dir, 'prototype.html'),
151
+ handoff: path.join(dir, 'canvas-handoff.json'),
152
+ tldrawWireframe: path.join(dir, 'wireframe.tldr'),
153
+ },
154
+ device: { name: 'MacBook Pro', width: 1440, height: 900 },
155
+ spec: {
156
+ background: project.background,
157
+ menuPath: project.menuPath,
158
+ filters: project.filters,
159
+ columns: project.columns,
160
+ toolbarButtons: project.toolbarButtons,
161
+ rowActions: project.rowActions,
162
+ features: project.features,
163
+ },
164
+ widgets,
165
+ penpot: {
166
+ frameName: `${project.pageName} · 原型`,
167
+ width: 1440,
168
+ height: 900,
169
+ prompt: '',
170
+ },
171
+ reframe: {
172
+ projectSlug: project.slug,
173
+ brandHint: 'Arco Design Pro · 企业 B 端后台',
174
+ prompt: '',
175
+ },
176
+ tldraw: {
177
+ relativePath: `${project.slug}/wireframe.tldr`,
178
+ prompt: '',
179
+ },
180
+ };
181
+ handoff.penpot.prompt = buildPenpotPrompt(project, handoff);
182
+ handoff.reframe.prompt = buildReframePrompt(project, handoff);
183
+ handoff.tldraw.prompt = buildTldrawPrompt(project, handoff, handoff.tldraw.relativePath);
184
+ return handoff;
185
+ }
186
+ export function saveCanvasHandoff(project) {
187
+ const handoff = buildCanvasHandoff(project);
188
+ const dir = getProjectDir(project.slug);
189
+ fs.mkdirSync(dir, { recursive: true });
190
+ const handoffPath = path.join(dir, 'canvas-handoff.json');
191
+ const penpotPromptPath = path.join(dir, 'penpot-prompt.md');
192
+ const reframePromptPath = path.join(dir, 'reframe-prompt.md');
193
+ const tldrawPromptPath = path.join(dir, 'tldraw-prompt.md');
194
+ const tldrawWireframePath = path.join(dir, 'wireframe.tldr');
195
+ fs.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2), 'utf8');
196
+ fs.writeFileSync(penpotPromptPath, handoff.penpot.prompt, 'utf8');
197
+ fs.writeFileSync(reframePromptPath, handoff.reframe.prompt, 'utf8');
198
+ fs.writeFileSync(tldrawPromptPath, handoff.tldraw.prompt, 'utf8');
199
+ fs.writeFileSync(tldrawWireframePath, JSON.stringify(buildTldrawFile(project, handoff.widgets), null, 2), 'utf8');
200
+ return {
201
+ handoffPath,
202
+ penpotPromptPath,
203
+ reframePromptPath,
204
+ tldrawPromptPath,
205
+ tldrawWireframePath,
206
+ handoff,
207
+ nextSteps: {
208
+ penpot: [
209
+ '终端:yarn penpot:mcp',
210
+ '浏览器 design.penpot.app → 插件 → http://localhost:4400/manifest.json → Connect',
211
+ 'Agent 读 penpot-prompt.md,用 penpot MCP 绘制高保真原型',
212
+ ],
213
+ reframe: [
214
+ '终端:yarn reframe:platform(需 yarn reframe:setup)',
215
+ 'Agent 读 reframe-prompt.md,调用 reframe MCP',
216
+ `预览:http://127.0.0.1:4100/platform/project/${project.slug}`,
217
+ ],
218
+ tldraw: [
219
+ 'Cursor 已配置 tldraw MCP(TLDRAW_DIR=src/prototypes)',
220
+ `打开线框:src/prototypes/${project.slug}/wireframe.tldr(tldraw.com 或 VS Code)`,
221
+ 'Agent 读 tldraw-prompt.md,用 tldraw_add_shape 补充箭头/备注',
222
+ ],
223
+ },
224
+ };
225
+ }
226
+ export function getHandoffSummary(slug) {
227
+ const dir = path.join(getPrototypesRoot(), slug);
228
+ const files = [
229
+ 'canvas-handoff.json',
230
+ 'penpot-prompt.md',
231
+ 'reframe-prompt.md',
232
+ 'tldraw-prompt.md',
233
+ 'wireframe.tldr',
234
+ ];
235
+ const paths = files.map(f => path.join(dir, f));
236
+ const exists = paths.every(p => fs.existsSync(p));
237
+ return { exists, paths };
238
+ }
@@ -6,15 +6,9 @@ export function generatePrdMarkdown(project) {
6
6
  const title = project.useDpItTitle ? buildDpItTitle(project) : `${project.pageName} PRD(产品需求文档)`;
7
7
  const moduleName = project.pageName;
8
8
  const listTitle = `${moduleName.replace(/列表|管理/g, '').trim() || moduleName}列表`;
9
- const featureRows = project.features
10
- .map(f => `| ${f.module} | ${f.feature} | ${f.description} |`)
11
- .join('\n');
12
- const permissionRows = project.permissions
13
- .map(p => `| ${p.point} | ${p.scope} | ${p.dataScope} |`)
14
- .join('\n');
15
- const filterRows = project.filters
16
- .map(f => `| ${f.name} | ${f.type} | ${f.limit} | ${f.matchRule} |`)
17
- .join('\n');
9
+ const featureRows = project.features.map(f => `| ${f.module} | ${f.feature} | ${f.description} |`).join('\n');
10
+ const permissionRows = project.permissions.map(p => `| ${p.point} | ${p.scope} | ${p.dataScope} |`).join('\n');
11
+ const filterRows = project.filters.map(f => `| ${f.name} | ${f.type} | ${f.limit} | ${f.matchRule} |`).join('\n');
18
12
  const columnRows = project.columns
19
13
  .map(c => `| ${c.name} | ${c.description || '—'} | ${c.sortable ? '是' : '否'} |`)
20
14
  .join('\n');
@@ -16,6 +16,18 @@ export declare function applyOverrides(slug: string, overrides: Partial<Pick<Prd
16
16
  listTitle?: string;
17
17
  filterTitle?: string;
18
18
  }): PrdProject | null;
19
+ export interface DesignSavePayload {
20
+ layout?: PrdProject['layout'];
21
+ filters?: PrdProject['filters'];
22
+ columns?: PrdProject['columns'];
23
+ toolbarButtons?: string[];
24
+ overrides?: {
25
+ pageName?: string;
26
+ listTitle?: string;
27
+ filterTitle?: string;
28
+ };
29
+ }
30
+ export declare function saveDesignLayout(slug: string, payload: DesignSavePayload): PrdProject | null;
19
31
  export declare function readProjectFiles(slug: string): {
20
32
  prd: string;
21
33
  prototype: string;
@@ -64,6 +64,23 @@ export function applyOverrides(slug, overrides) {
64
64
  saveProject(project);
65
65
  return project;
66
66
  }
67
+ export function saveDesignLayout(slug, payload) {
68
+ const project = loadProject(slug);
69
+ if (!project)
70
+ return null;
71
+ if (payload.layout)
72
+ project.layout = payload.layout;
73
+ if (payload.filters?.length)
74
+ project.filters = payload.filters;
75
+ if (payload.columns?.length)
76
+ project.columns = payload.columns;
77
+ if (payload.toolbarButtons?.length)
78
+ project.toolbarButtons = payload.toolbarButtons;
79
+ if (payload.overrides?.pageName)
80
+ project.pageName = payload.overrides.pageName;
81
+ saveProject(project);
82
+ return project;
83
+ }
67
84
  export function readProjectFiles(slug) {
68
85
  const project = loadProject(slug);
69
86
  if (!project)
@@ -1,2 +1,2 @@
1
1
  import { type PrdProject } from './types.js';
2
- export declare function generatePrototypeHtml(project: PrdProject): string;
2
+ export declare function generatePrototypeHtml(rawProject: PrdProject): string;