@primeuicom/mcp 0.1.11 → 0.1.13
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 +21 -9
- package/dist/service.js +498 -165
- package/dist/service.js.map +1 -1
- package/package.json +1 -1
package/dist/service.js
CHANGED
|
@@ -1,77 +1,207 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/service.ts
|
|
4
|
+
import path7 from "path";
|
|
4
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
6
|
|
|
7
|
+
// src/lib/project-link-config.ts
|
|
8
|
+
import { readFile, stat } from "fs/promises";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
var PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH = ".primeui/project.json";
|
|
12
|
+
var primeUiProjectConfigSchema = z.object({
|
|
13
|
+
projectId: z.string().trim().min(1),
|
|
14
|
+
apiKey: z.string().trim().min(1),
|
|
15
|
+
targetProjectPath: z.string().trim().regex(/^\.\/.*$/)
|
|
16
|
+
}).passthrough();
|
|
17
|
+
async function fileExists(filePath) {
|
|
18
|
+
try {
|
|
19
|
+
const fileStats = await stat(filePath);
|
|
20
|
+
return fileStats.isFile();
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function ancestors(startPath) {
|
|
26
|
+
const resolvedStartPath = path.resolve(startPath);
|
|
27
|
+
const directories = [];
|
|
28
|
+
let currentPath = resolvedStartPath;
|
|
29
|
+
while (true) {
|
|
30
|
+
directories.push(currentPath);
|
|
31
|
+
const parentPath = path.dirname(currentPath);
|
|
32
|
+
if (parentPath === currentPath) {
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
currentPath = parentPath;
|
|
36
|
+
}
|
|
37
|
+
return directories;
|
|
38
|
+
}
|
|
39
|
+
async function findPrimeUiProjectConfigPath(startPath) {
|
|
40
|
+
for (const currentPath of ancestors(startPath)) {
|
|
41
|
+
const candidatePath = path.join(
|
|
42
|
+
currentPath,
|
|
43
|
+
PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
|
|
44
|
+
);
|
|
45
|
+
if (await fileExists(candidatePath)) {
|
|
46
|
+
return candidatePath;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
async function readPrimeUiProjectConfig(projectConfigPath) {
|
|
52
|
+
let projectConfigRaw;
|
|
53
|
+
try {
|
|
54
|
+
projectConfigRaw = await readFile(projectConfigPath, "utf-8");
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`[primeui-mcp] failed to read ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH}: ${error instanceof Error ? error.message : String(error)}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
let projectConfigJson;
|
|
61
|
+
try {
|
|
62
|
+
projectConfigJson = JSON.parse(projectConfigRaw);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`[primeui-mcp] invalid JSON in ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH}: ${error instanceof Error ? error.message : String(error)}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const parsedConfig = primeUiProjectConfigSchema.safeParse(projectConfigJson);
|
|
69
|
+
if (!parsedConfig.success) {
|
|
70
|
+
const details = parsedConfig.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
|
|
71
|
+
throw new Error(
|
|
72
|
+
`[primeui-mcp] invalid ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} format: ${details}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return parsedConfig.data;
|
|
76
|
+
}
|
|
77
|
+
async function resolvePrimeUiProjectConfig(options) {
|
|
78
|
+
const envProjectRoot = options.projectRootFromEnv?.trim();
|
|
79
|
+
const projectConfigPath = envProjectRoot ? path.join(
|
|
80
|
+
path.resolve(envProjectRoot),
|
|
81
|
+
PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
|
|
82
|
+
) : await findPrimeUiProjectConfigPath(options.cwd);
|
|
83
|
+
if (!projectConfigPath) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`[primeui-mcp] ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} not found from cwd (${options.cwd}).`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const projectRoot = path.dirname(path.dirname(projectConfigPath));
|
|
89
|
+
const projectConfig = await readPrimeUiProjectConfig(projectConfigPath);
|
|
90
|
+
return {
|
|
91
|
+
projectRoot,
|
|
92
|
+
projectConfigPath,
|
|
93
|
+
projectConfig
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function resolvePrimeUiApiKey(options) {
|
|
97
|
+
const envApiKey = options.apiKeyFromEnv?.trim();
|
|
98
|
+
if (envApiKey) {
|
|
99
|
+
return envApiKey;
|
|
100
|
+
}
|
|
101
|
+
const projectConfigApiKey = options.projectConfig.apiKey.trim();
|
|
102
|
+
if (!projectConfigApiKey) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
"[primeui-mcp] PRIMEUI_API_KEY is missing in env and .primeui/project.json"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return projectConfigApiKey;
|
|
108
|
+
}
|
|
109
|
+
|
|
6
110
|
// src/server.ts
|
|
7
111
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
112
|
|
|
9
113
|
// src/instructions.ts
|
|
10
|
-
import { z } from "zod/v3";
|
|
11
|
-
var pageSchema =
|
|
12
|
-
id:
|
|
114
|
+
import { z as z2 } from "zod/v3";
|
|
115
|
+
var pageSchema = z2.object({
|
|
116
|
+
id: z2.string().describe(
|
|
13
117
|
"Stable PrimeUI page identifier. Use this to match the same page across tool responses."
|
|
14
118
|
),
|
|
15
|
-
title:
|
|
119
|
+
title: z2.string().describe(
|
|
16
120
|
"Human-readable page title. Show this to the user when confirming which pages to import."
|
|
17
121
|
),
|
|
18
|
-
slug:
|
|
122
|
+
slug: z2.string().describe(
|
|
19
123
|
"PrimeUI route slug, for example '/', '/pricing', '/docs/getting-started'. Use to match user intent."
|
|
20
124
|
),
|
|
21
|
-
pageType:
|
|
125
|
+
pageType: z2.string().describe(
|
|
22
126
|
"PrimeUI page type classification (for example landing, docs, pricing, blogIndex, legal)."
|
|
23
127
|
),
|
|
24
|
-
isReadyToExport:
|
|
128
|
+
isReadyToExport: z2.boolean().describe(
|
|
25
129
|
"True when this page has an active export-ready variant. Only ready pages are expected in export output."
|
|
26
130
|
),
|
|
27
|
-
pagePath:
|
|
131
|
+
pagePath: z2.string().describe(
|
|
28
132
|
"Relative file path to the page source inside downloaded export root. Copy page code from this exact path."
|
|
29
133
|
),
|
|
30
|
-
componentsPath:
|
|
134
|
+
componentsPath: z2.string().describe(
|
|
31
135
|
"Relative directory path inside downloaded export root where page-level components live. Use as dependency copy start."
|
|
32
136
|
)
|
|
33
137
|
}).describe(
|
|
34
138
|
"PrimeUI page descriptor used by project and export tools. Paths are always relative to export project root."
|
|
35
139
|
);
|
|
36
|
-
var exportStatusSchema =
|
|
140
|
+
var exportStatusSchema = z2.enum(["in_progress", "completed", "failed"]).describe(
|
|
37
141
|
"Export lifecycle state. Use 'completed' before calling primeui_download_export."
|
|
38
142
|
);
|
|
39
|
-
var exportItemSchema =
|
|
40
|
-
id:
|
|
143
|
+
var exportItemSchema = z2.object({
|
|
144
|
+
id: z2.string().describe(
|
|
41
145
|
"Export identifier. Pass this value to primeui_download_export input.id."
|
|
42
146
|
),
|
|
43
147
|
status: exportStatusSchema,
|
|
44
|
-
createdAt:
|
|
148
|
+
createdAt: z2.string().describe("Export creation timestamp in ISO-8601 UTC format.")
|
|
45
149
|
}).describe("Single export record from PrimeUI export history.");
|
|
46
|
-
var fileTransferSchema =
|
|
47
|
-
sourcePath:
|
|
48
|
-
targetPath:
|
|
150
|
+
var fileTransferSchema = z2.object({
|
|
151
|
+
sourcePath: z2.string().describe("Relative source file path inside downloaded export root."),
|
|
152
|
+
targetPath: z2.string().describe("Relative target file path inside user project root.")
|
|
49
153
|
});
|
|
50
154
|
var conflictFileSchema = fileTransferSchema.extend({
|
|
51
|
-
diff:
|
|
155
|
+
diff: z2.string().describe(
|
|
52
156
|
"Unified diff between user file and export file. For binary files the diff contains a plain message."
|
|
53
157
|
),
|
|
54
|
-
isBinary:
|
|
158
|
+
isBinary: z2.boolean().describe("True if conflict comparison was treated as binary data.")
|
|
55
159
|
});
|
|
56
|
-
var dependencySectionSchema =
|
|
160
|
+
var dependencySectionSchema = z2.enum([
|
|
57
161
|
"dependencies",
|
|
58
162
|
"devDependencies",
|
|
59
163
|
"peerDependencies"
|
|
60
164
|
]);
|
|
61
|
-
var dependencyToAddSchema =
|
|
62
|
-
packageName:
|
|
63
|
-
version:
|
|
165
|
+
var dependencyToAddSchema = z2.object({
|
|
166
|
+
packageName: z2.string().describe("Dependency package name that was missing in user package.json."),
|
|
167
|
+
version: z2.string().describe("Version from exported project's package.json."),
|
|
64
168
|
section: dependencySectionSchema.describe(
|
|
65
169
|
"package.json section where dependency should be added."
|
|
66
170
|
)
|
|
67
171
|
});
|
|
68
|
-
var dependencyVersionConflictSchema =
|
|
69
|
-
packageName:
|
|
172
|
+
var dependencyVersionConflictSchema = z2.object({
|
|
173
|
+
packageName: z2.string().describe("Dependency package name with version mismatch."),
|
|
70
174
|
section: dependencySectionSchema.describe(
|
|
71
175
|
"Section where export expects this dependency."
|
|
72
176
|
),
|
|
73
|
-
exportVersion:
|
|
74
|
-
userVersion:
|
|
177
|
+
exportVersion: z2.string().describe("Version found in exported project's package.json."),
|
|
178
|
+
userVersion: z2.string().describe("Version currently present in user's package.json.")
|
|
179
|
+
});
|
|
180
|
+
var pageDetailsSchema = pageSchema.extend({
|
|
181
|
+
pageInstruction: z2.string().nullable().describe(
|
|
182
|
+
"Current instruction text for the active page variant. Null when no active variant is set."
|
|
183
|
+
)
|
|
184
|
+
});
|
|
185
|
+
var pageVariantSchema = z2.object({
|
|
186
|
+
id: z2.string().describe("Active variant identifier."),
|
|
187
|
+
name: z2.string().describe("Active variant display name.")
|
|
188
|
+
}).nullable().describe(
|
|
189
|
+
"Active page variant. Null when page has no active variant and components are unavailable."
|
|
190
|
+
);
|
|
191
|
+
var pageComponentSchema = z2.object({
|
|
192
|
+
blockId: z2.string().describe("Stable block identifier. Use as primary component instance ID."),
|
|
193
|
+
componentId: z2.string().describe("Component key from variant blocks payload (block.key)."),
|
|
194
|
+
componentGroup: z2.string().describe("Component family/group normalized from component key."),
|
|
195
|
+
slot: z2.string().nullable().describe("Optional slot value from block payload."),
|
|
196
|
+
props: z2.record(z2.unknown()).nullable().describe("Raw block props payload from PrimeUI.")
|
|
197
|
+
});
|
|
198
|
+
var inspectPageReportRowSchema = z2.object({
|
|
199
|
+
number: z2.number().describe("1-based row number matching report order."),
|
|
200
|
+
componentGroup: z2.string().describe("Component family/group label."),
|
|
201
|
+
componentId: z2.string().describe("Component key (block key)."),
|
|
202
|
+
blockId: z2.string().describe("Primary component instance identifier for future operations."),
|
|
203
|
+
title: z2.string().nullable().describe("Title extracted by MCP from component props."),
|
|
204
|
+
description: z2.string().nullable().describe("Description extracted by MCP from component props.")
|
|
75
205
|
});
|
|
76
206
|
var TRIGGER_PHRASES = [
|
|
77
207
|
"import page from PrimeUI",
|
|
@@ -87,11 +217,12 @@ var WORKFLOW_SUMMARY = `
|
|
|
87
217
|
WORKFLOW ORDER (always follow this sequence):
|
|
88
218
|
1. primeui_get_project_info -> discover PrimeUI pages
|
|
89
219
|
2. Reconcile PrimeUI pages with local project pages/routes/paths, then confirm target pages with user
|
|
90
|
-
3.
|
|
91
|
-
4.
|
|
92
|
-
5.
|
|
93
|
-
6.
|
|
94
|
-
7.
|
|
220
|
+
3. primeui_inspect_page -> inspect selected page components and produce a structured comparison table
|
|
221
|
+
4. primeui_create_export -> generate export snapshot for agreed pages/analysis scope
|
|
222
|
+
5. primeui_download_export -> download to temp directory
|
|
223
|
+
6. primeui_copy_page -> copy one page safely (repeat for each selected page)
|
|
224
|
+
7. Reconcile integration points and resolve reported conflicts, if any
|
|
225
|
+
8. primeui_clear_temp -> cleanup temp files
|
|
95
226
|
`.trim();
|
|
96
227
|
var initialInstructions = `
|
|
97
228
|
PrimeUI MCP enables importing pages from PrimeUI Studio into your local project.
|
|
@@ -109,6 +240,9 @@ CRITICAL RULES FOR PAGE IMPORT:
|
|
|
109
240
|
- PrimeUI exists, local missing -> import candidate.
|
|
110
241
|
- Local exists, PrimeUI missing -> custom local page, no action unless user asks.
|
|
111
242
|
- Both exist but path mismatch -> ambiguous, export + compare before deciding.
|
|
243
|
+
- For targeted component-level analysis on a specific page, call primeui_inspect_page before export/copy.
|
|
244
|
+
- After primeui_inspect_page returns report table, supplement that table with local project page state:
|
|
245
|
+
mark what already exists, what is missing, and what differs.
|
|
112
246
|
- Before creating export, ask user which pages to add/update (specific pages or all missing pages) and keep that choice in thread context.
|
|
113
247
|
- The downloaded export in .primeui/temp/ contains a full standalone Next.js project.
|
|
114
248
|
Do NOT copy it wholesale.
|
|
@@ -145,16 +279,16 @@ AFTER CALLING:
|
|
|
145
279
|
${WORKFLOW_SUMMARY}`,
|
|
146
280
|
inputSchema: {},
|
|
147
281
|
outputSchema: {
|
|
148
|
-
projectId:
|
|
282
|
+
projectId: z2.string().describe(
|
|
149
283
|
"PrimeUI project identifier associated with the API key and current import session context."
|
|
150
284
|
),
|
|
151
|
-
projectName:
|
|
285
|
+
projectName: z2.string().describe(
|
|
152
286
|
"Human-readable PrimeUI project name for confirmations in chat."
|
|
153
287
|
),
|
|
154
|
-
metadata:
|
|
288
|
+
metadata: z2.record(z2.unknown()).describe(
|
|
155
289
|
"Additional project metadata from PrimeUI. May include project-description and other context fields."
|
|
156
290
|
),
|
|
157
|
-
pages:
|
|
291
|
+
pages: z2.array(pageSchema).describe(
|
|
158
292
|
"All pages visible for this project context. Filter by user request and isReadyToExport before creating export."
|
|
159
293
|
)
|
|
160
294
|
},
|
|
@@ -165,6 +299,51 @@ ${WORKFLOW_SUMMARY}`,
|
|
|
165
299
|
openWorldHint: true
|
|
166
300
|
}
|
|
167
301
|
};
|
|
302
|
+
var toolInspectPage = {
|
|
303
|
+
title: "PrimeUI Inspect Page",
|
|
304
|
+
description: `Targeted inspection tool for one PrimeUI page. Returns page details, active variant (if present), raw components payload, and a formatted report table.
|
|
305
|
+
|
|
306
|
+
WHEN TO USE:
|
|
307
|
+
- Call this after primeui_get_project_info when user wants component-level analysis for a specific page.
|
|
308
|
+
- Use this before export/copy when user asks to compare or selectively update page sections.
|
|
309
|
+
|
|
310
|
+
AFTER CALLING:
|
|
311
|
+
- Use report + reportRows to present the component table:
|
|
312
|
+
Number (1-based), ComponentGroup (ComponentId), Title/Description, blockId.
|
|
313
|
+
- Then supplement this table with the CURRENT local project page state:
|
|
314
|
+
what already exists, what is missing, and what differs.
|
|
315
|
+
- Do not assume componentId is unique. Use blockId as the primary component instance identifier.
|
|
316
|
+
|
|
317
|
+
If page has no active variant, components is null (not empty array), and report explains why.
|
|
318
|
+
|
|
319
|
+
${WORKFLOW_SUMMARY}`,
|
|
320
|
+
inputSchema: {
|
|
321
|
+
pageSlug: z2.string().describe(
|
|
322
|
+
"PrimeUI page slug to inspect, for example '/', '/pricing', '/docs/getting-started'."
|
|
323
|
+
)
|
|
324
|
+
},
|
|
325
|
+
outputSchema: {
|
|
326
|
+
page: pageDetailsSchema.describe(
|
|
327
|
+
"Single PrimeUI page details resolved by slug with pageInstruction."
|
|
328
|
+
),
|
|
329
|
+
variant: pageVariantSchema,
|
|
330
|
+
components: z2.array(pageComponentSchema).nullable().describe(
|
|
331
|
+
"Raw component instances from active variant. Null when page has no active variant."
|
|
332
|
+
),
|
|
333
|
+
report: z2.string().describe(
|
|
334
|
+
"Formatted report table generated by MCP from component payload (includes extracted title/description summary)."
|
|
335
|
+
),
|
|
336
|
+
reportRows: z2.array(inspectPageReportRowSchema).describe(
|
|
337
|
+
"Structured report rows with 1-based numbering and extracted title/description fields."
|
|
338
|
+
)
|
|
339
|
+
},
|
|
340
|
+
annotations: {
|
|
341
|
+
readOnlyHint: true,
|
|
342
|
+
destructiveHint: false,
|
|
343
|
+
idempotentHint: true,
|
|
344
|
+
openWorldHint: true
|
|
345
|
+
}
|
|
346
|
+
};
|
|
168
347
|
var toolListExports = {
|
|
169
348
|
title: "PrimeUI Export List",
|
|
170
349
|
description: `Auxiliary tool to list previously created exports with their IDs, statuses, and creation dates. This is NOT the starting point for page import - start with primeui_get_project_info instead.
|
|
@@ -181,7 +360,7 @@ Do NOT use this tool as a substitute for primeui_create_export. Always create a
|
|
|
181
360
|
${WORKFLOW_SUMMARY}`,
|
|
182
361
|
inputSchema: {},
|
|
183
362
|
outputSchema: {
|
|
184
|
-
exports:
|
|
363
|
+
exports: z2.array(exportItemSchema).describe(
|
|
185
364
|
"Available exports sorted by API policy. Use item.id to download a specific prior export if user asks."
|
|
186
365
|
)
|
|
187
366
|
},
|
|
@@ -207,13 +386,13 @@ AFTER CALLING:
|
|
|
207
386
|
${WORKFLOW_SUMMARY}`,
|
|
208
387
|
inputSchema: {},
|
|
209
388
|
outputSchema: {
|
|
210
|
-
export:
|
|
211
|
-
id:
|
|
389
|
+
export: z2.object({
|
|
390
|
+
id: z2.string().describe(
|
|
212
391
|
"Freshly created export identifier. Use this as primeui_download_export input.id."
|
|
213
392
|
),
|
|
214
393
|
status: exportStatusSchema
|
|
215
394
|
}).describe("Created export summary."),
|
|
216
|
-
pages:
|
|
395
|
+
pages: z2.array(pageSchema).describe(
|
|
217
396
|
"Page snapshot associated with this export operation. Validate requested pages here before download/import."
|
|
218
397
|
)
|
|
219
398
|
},
|
|
@@ -246,21 +425,21 @@ AFTER DOWNLOAD:
|
|
|
246
425
|
|
|
247
426
|
${WORKFLOW_SUMMARY}`,
|
|
248
427
|
inputSchema: {
|
|
249
|
-
id:
|
|
428
|
+
id: z2.string().describe(
|
|
250
429
|
"Export identifier to download. Prefer export.id from primeui_create_export; use primeui_list_exports only on explicit user request."
|
|
251
430
|
)
|
|
252
431
|
},
|
|
253
432
|
outputSchema: {
|
|
254
|
-
exportId:
|
|
433
|
+
exportId: z2.string().describe(
|
|
255
434
|
"Downloaded export identifier. Should match the requested input.id for traceability."
|
|
256
435
|
),
|
|
257
|
-
projectPath:
|
|
436
|
+
projectPath: z2.string().describe(
|
|
258
437
|
"Absolute local path to extracted export root in .primeui/temp/exports/[exportId]. Read files from here only."
|
|
259
438
|
),
|
|
260
|
-
manifestPath:
|
|
439
|
+
manifestPath: z2.string().describe(
|
|
261
440
|
"Absolute path to sidecar export manifest file (.primeui/temp/exports/[exportId].manifest.json). primeui_copy_page depends on this file."
|
|
262
441
|
),
|
|
263
|
-
pages:
|
|
442
|
+
pages: z2.array(pageSchema).describe(
|
|
264
443
|
"Page descriptors for import decisions. Use pagePath/componentsPath from each item when copying code into user project."
|
|
265
444
|
)
|
|
266
445
|
},
|
|
@@ -295,44 +474,44 @@ BEHAVIOR:
|
|
|
295
474
|
|
|
296
475
|
${WORKFLOW_SUMMARY}`,
|
|
297
476
|
inputSchema: {
|
|
298
|
-
originPageSlug:
|
|
477
|
+
originPageSlug: z2.string().describe(
|
|
299
478
|
"Source page slug from PrimeUI project pages list, for example '/', '/pricing', '/docs'."
|
|
300
479
|
),
|
|
301
|
-
actualPageSlug:
|
|
480
|
+
actualPageSlug: z2.string().optional().describe(
|
|
302
481
|
"Optional destination slug in user's project. If omitted, originPageSlug is used."
|
|
303
482
|
)
|
|
304
483
|
},
|
|
305
484
|
outputSchema: {
|
|
306
|
-
exportId:
|
|
307
|
-
exportPath:
|
|
308
|
-
originPageSlug:
|
|
309
|
-
actualPageSlug:
|
|
310
|
-
sourcePagePath:
|
|
311
|
-
targetPagePath:
|
|
312
|
-
sourceComponentsPath:
|
|
313
|
-
targetComponentsPath:
|
|
485
|
+
exportId: z2.string().describe("Resolved local export identifier used for copy operation."),
|
|
486
|
+
exportPath: z2.string().describe("Absolute path to local extracted export project root."),
|
|
487
|
+
originPageSlug: z2.string().describe("Normalized source page slug requested for copy."),
|
|
488
|
+
actualPageSlug: z2.string().describe("Normalized destination slug used for target route paths."),
|
|
489
|
+
sourcePagePath: z2.string().describe("Source page path relative to export root."),
|
|
490
|
+
targetPagePath: z2.string().describe("Destination page path relative to user project root."),
|
|
491
|
+
sourceComponentsPath: z2.string().describe("Source components directory relative to export root."),
|
|
492
|
+
targetComponentsPath: z2.string().describe(
|
|
314
493
|
"Destination components directory relative to user project root."
|
|
315
494
|
),
|
|
316
|
-
newFiles:
|
|
317
|
-
identicalFiles:
|
|
495
|
+
newFiles: z2.array(fileTransferSchema).describe("New files copied into user project without conflicts."),
|
|
496
|
+
identicalFiles: z2.array(fileTransferSchema).describe(
|
|
318
497
|
"Existing files in user project that are byte-identical to export files."
|
|
319
498
|
),
|
|
320
|
-
conflictFiles:
|
|
499
|
+
conflictFiles: z2.array(conflictFileSchema).describe(
|
|
321
500
|
"Files that already exist and differ from export version. Never overwritten."
|
|
322
501
|
),
|
|
323
|
-
addedDependencies:
|
|
502
|
+
addedDependencies: z2.array(dependencyToAddSchema).describe(
|
|
324
503
|
"Dependencies that were missing and have been added to user's package.json."
|
|
325
504
|
),
|
|
326
|
-
dependenciesVersionConflicts:
|
|
505
|
+
dependenciesVersionConflicts: z2.array(dependencyVersionConflictSchema).describe(
|
|
327
506
|
"Dependencies with version mismatch between export and user project. Report only."
|
|
328
507
|
),
|
|
329
|
-
summary:
|
|
330
|
-
totalCandidateFiles:
|
|
331
|
-
copiedFiles:
|
|
332
|
-
identicalFiles:
|
|
333
|
-
conflictFiles:
|
|
334
|
-
addedDependencies:
|
|
335
|
-
dependenciesVersionConflicts:
|
|
508
|
+
summary: z2.object({
|
|
509
|
+
totalCandidateFiles: z2.number().describe("Total number of candidate files considered for page copy."),
|
|
510
|
+
copiedFiles: z2.number().describe("Number of files copied as new."),
|
|
511
|
+
identicalFiles: z2.number().describe("Number of files detected as identical."),
|
|
512
|
+
conflictFiles: z2.number().describe("Number of conflicting files not copied."),
|
|
513
|
+
addedDependencies: z2.number().describe("Count of dependencies added to package.json."),
|
|
514
|
+
dependenciesVersionConflicts: z2.number().describe("Count of dependency version mismatches reported.")
|
|
336
515
|
})
|
|
337
516
|
},
|
|
338
517
|
annotations: {
|
|
@@ -355,7 +534,7 @@ Safe to call multiple times. Only affects .primeui/temp/ directory - never touch
|
|
|
355
534
|
${WORKFLOW_SUMMARY}`,
|
|
356
535
|
inputSchema: {},
|
|
357
536
|
outputSchema: {
|
|
358
|
-
success:
|
|
537
|
+
success: z2.boolean().describe(
|
|
359
538
|
"True when temp cleanup completed and baseline temp structure was recreated."
|
|
360
539
|
)
|
|
361
540
|
},
|
|
@@ -403,6 +582,18 @@ function createPrimeUiMcpServer(source) {
|
|
|
403
582
|
}
|
|
404
583
|
}
|
|
405
584
|
);
|
|
585
|
+
server.registerTool(
|
|
586
|
+
"primeui_inspect_page",
|
|
587
|
+
toolInspectPage,
|
|
588
|
+
async ({ pageSlug }) => {
|
|
589
|
+
try {
|
|
590
|
+
const result = await source.inspectPage(pageSlug);
|
|
591
|
+
return okResult("primeui_inspect_page", result);
|
|
592
|
+
} catch (error) {
|
|
593
|
+
return errorResult(error);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
);
|
|
406
597
|
server.registerTool(
|
|
407
598
|
"primeui_list_exports",
|
|
408
599
|
toolListExports,
|
|
@@ -470,12 +661,12 @@ function createPrimeUiMcpServer(source) {
|
|
|
470
661
|
}
|
|
471
662
|
|
|
472
663
|
// src/services/project-sync-service.ts
|
|
473
|
-
import
|
|
474
|
-
import { readFile as
|
|
664
|
+
import path5 from "path";
|
|
665
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
475
666
|
|
|
476
667
|
// src/lib/fs.ts
|
|
477
668
|
import { mkdir, rm, writeFile } from "fs/promises";
|
|
478
|
-
import
|
|
669
|
+
import path2 from "path";
|
|
479
670
|
import extractZipArchive from "extract-zip";
|
|
480
671
|
async function ensureDir(dirPath) {
|
|
481
672
|
await mkdir(dirPath, { recursive: true });
|
|
@@ -485,7 +676,7 @@ async function resetDir(dirPath) {
|
|
|
485
676
|
await mkdir(dirPath, { recursive: true });
|
|
486
677
|
}
|
|
487
678
|
async function writeUtf8(filePath, content) {
|
|
488
|
-
await ensureDir(
|
|
679
|
+
await ensureDir(path2.dirname(filePath));
|
|
489
680
|
await writeFile(filePath, content, "utf-8");
|
|
490
681
|
}
|
|
491
682
|
async function extractZip(zipPath, targetDir) {
|
|
@@ -499,13 +690,13 @@ async function extractZip(zipPath, targetDir) {
|
|
|
499
690
|
}
|
|
500
691
|
|
|
501
692
|
// src/services/page-copy-service.ts
|
|
502
|
-
import { readFile as
|
|
503
|
-
import
|
|
693
|
+
import { readFile as readFile3, readdir, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
694
|
+
import path4 from "path";
|
|
504
695
|
|
|
505
696
|
// src/lib/import-graph.ts
|
|
506
|
-
import { readFile, stat } from "fs/promises";
|
|
697
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
507
698
|
import { builtinModules } from "module";
|
|
508
|
-
import
|
|
699
|
+
import path3 from "path";
|
|
509
700
|
var INTERNAL_EXTENSIONS = [
|
|
510
701
|
".ts",
|
|
511
702
|
".tsx",
|
|
@@ -574,14 +765,14 @@ function getExternalPackageName(specifier) {
|
|
|
574
765
|
}
|
|
575
766
|
async function pathExists(filePath) {
|
|
576
767
|
try {
|
|
577
|
-
await
|
|
768
|
+
await stat2(filePath);
|
|
578
769
|
return true;
|
|
579
770
|
} catch {
|
|
580
771
|
return false;
|
|
581
772
|
}
|
|
582
773
|
}
|
|
583
774
|
async function resolveFileCandidate(candidateBase) {
|
|
584
|
-
const ext =
|
|
775
|
+
const ext = path3.extname(candidateBase);
|
|
585
776
|
if (ext) {
|
|
586
777
|
if (await pathExists(candidateBase)) {
|
|
587
778
|
return candidateBase;
|
|
@@ -595,13 +786,13 @@ async function resolveFileCandidate(candidateBase) {
|
|
|
595
786
|
}
|
|
596
787
|
}
|
|
597
788
|
if (await pathExists(candidateBase)) {
|
|
598
|
-
const stats = await
|
|
789
|
+
const stats = await stat2(candidateBase);
|
|
599
790
|
if (stats.isFile()) {
|
|
600
791
|
return candidateBase;
|
|
601
792
|
}
|
|
602
793
|
if (stats.isDirectory()) {
|
|
603
794
|
for (const extension of INTERNAL_EXTENSIONS) {
|
|
604
|
-
const indexCandidate =
|
|
795
|
+
const indexCandidate = path3.join(candidateBase, `index${extension}`);
|
|
605
796
|
if (await pathExists(indexCandidate)) {
|
|
606
797
|
return indexCandidate;
|
|
607
798
|
}
|
|
@@ -623,13 +814,13 @@ async function resolveImportToFile(projectRoot, importerFilePath, specifier) {
|
|
|
623
814
|
}
|
|
624
815
|
let candidateBase = null;
|
|
625
816
|
if (specifier.startsWith("@/")) {
|
|
626
|
-
candidateBase =
|
|
817
|
+
candidateBase = path3.join(projectRoot, "src", specifier.slice(2));
|
|
627
818
|
} else if (specifier.startsWith("@root/")) {
|
|
628
|
-
candidateBase =
|
|
819
|
+
candidateBase = path3.join(projectRoot, specifier.slice(6));
|
|
629
820
|
} else if (specifier.startsWith(".")) {
|
|
630
|
-
candidateBase =
|
|
821
|
+
candidateBase = path3.resolve(path3.dirname(importerFilePath), specifier);
|
|
631
822
|
} else if (specifier.startsWith("/")) {
|
|
632
|
-
candidateBase =
|
|
823
|
+
candidateBase = path3.join(projectRoot, specifier.slice(1));
|
|
633
824
|
} else {
|
|
634
825
|
return { kind: "unknown" };
|
|
635
826
|
}
|
|
@@ -643,11 +834,11 @@ async function resolveImportToFile(projectRoot, importerFilePath, specifier) {
|
|
|
643
834
|
};
|
|
644
835
|
}
|
|
645
836
|
function shouldParseFile(filePath) {
|
|
646
|
-
return PARSEABLE_EXTENSIONS.has(
|
|
837
|
+
return PARSEABLE_EXTENSIONS.has(path3.extname(filePath).toLowerCase());
|
|
647
838
|
}
|
|
648
839
|
async function buildImportGraph(input) {
|
|
649
|
-
const projectRoot =
|
|
650
|
-
const queue = input.entryFiles.map((filePath) =>
|
|
840
|
+
const projectRoot = path3.resolve(input.projectRoot);
|
|
841
|
+
const queue = input.entryFiles.map((filePath) => path3.resolve(filePath));
|
|
651
842
|
const visited = /* @__PURE__ */ new Set();
|
|
652
843
|
const internalFiles = /* @__PURE__ */ new Set();
|
|
653
844
|
const externalPackages = /* @__PURE__ */ new Set();
|
|
@@ -663,7 +854,7 @@ async function buildImportGraph(input) {
|
|
|
663
854
|
}
|
|
664
855
|
let content = "";
|
|
665
856
|
try {
|
|
666
|
-
content = await
|
|
857
|
+
content = await readFile2(currentFilePath, "utf-8");
|
|
667
858
|
} catch {
|
|
668
859
|
continue;
|
|
669
860
|
}
|
|
@@ -900,10 +1091,10 @@ function normalizeSlug2(slug) {
|
|
|
900
1091
|
return withLeadingSlash.replace(/\/+$/, "") || "/";
|
|
901
1092
|
}
|
|
902
1093
|
function toPosixPath(value) {
|
|
903
|
-
return value.split(
|
|
1094
|
+
return value.split(path4.sep).join("/");
|
|
904
1095
|
}
|
|
905
1096
|
function toProjectRelative(rootPath, absolutePath) {
|
|
906
|
-
return toPosixPath(
|
|
1097
|
+
return toPosixPath(path4.relative(rootPath, absolutePath));
|
|
907
1098
|
}
|
|
908
1099
|
function escapeRegExp(value) {
|
|
909
1100
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -960,7 +1151,7 @@ function buildPlannedSourceBuffer(sourceBuffer, sourceFilePath, importRewritePla
|
|
|
960
1151
|
if (!importRewritePlan) {
|
|
961
1152
|
return sourceBuffer;
|
|
962
1153
|
}
|
|
963
|
-
const extension =
|
|
1154
|
+
const extension = path4.extname(sourceFilePath).toLowerCase();
|
|
964
1155
|
if (!REWRITABLE_IMPORT_EXTENSIONS.has(extension)) {
|
|
965
1156
|
return sourceBuffer;
|
|
966
1157
|
}
|
|
@@ -968,19 +1159,22 @@ function buildPlannedSourceBuffer(sourceBuffer, sourceFilePath, importRewritePla
|
|
|
968
1159
|
return sourceBuffer;
|
|
969
1160
|
}
|
|
970
1161
|
const sourceText = sourceBuffer.toString("utf-8");
|
|
971
|
-
const rewritten = rewriteImportsForRemappedSlug(
|
|
1162
|
+
const rewritten = rewriteImportsForRemappedSlug(
|
|
1163
|
+
sourceText,
|
|
1164
|
+
importRewritePlan
|
|
1165
|
+
);
|
|
972
1166
|
if (rewritten === sourceText) {
|
|
973
1167
|
return sourceBuffer;
|
|
974
1168
|
}
|
|
975
1169
|
return Buffer.from(rewritten, "utf-8");
|
|
976
1170
|
}
|
|
977
1171
|
async function readJsonFile(filePath) {
|
|
978
|
-
const content = await
|
|
1172
|
+
const content = await readFile3(filePath, "utf-8");
|
|
979
1173
|
return JSON.parse(content);
|
|
980
1174
|
}
|
|
981
|
-
async function
|
|
1175
|
+
async function fileExists2(filePath) {
|
|
982
1176
|
try {
|
|
983
|
-
await
|
|
1177
|
+
await stat3(filePath);
|
|
984
1178
|
return true;
|
|
985
1179
|
} catch {
|
|
986
1180
|
return false;
|
|
@@ -1022,7 +1216,7 @@ async function listFilesRecursively(dirPath) {
|
|
|
1022
1216
|
}
|
|
1023
1217
|
const entries = await readdir(current, { withFileTypes: true });
|
|
1024
1218
|
for (const entry of entries) {
|
|
1025
|
-
const absolutePath =
|
|
1219
|
+
const absolutePath = path4.join(current, entry.name);
|
|
1026
1220
|
if (entry.isDirectory()) {
|
|
1027
1221
|
stack.push(absolutePath);
|
|
1028
1222
|
continue;
|
|
@@ -1050,12 +1244,12 @@ async function resolveSingleExportDirectory(exportsRoot) {
|
|
|
1050
1244
|
const exportId = exportDirectories[0] ?? "";
|
|
1051
1245
|
return {
|
|
1052
1246
|
exportId,
|
|
1053
|
-
exportPath:
|
|
1247
|
+
exportPath: path4.join(exportsRoot, exportId)
|
|
1054
1248
|
};
|
|
1055
1249
|
}
|
|
1056
1250
|
function ensureSafeTargetPath(projectRoot, targetPath) {
|
|
1057
|
-
const relative =
|
|
1058
|
-
if (relative.startsWith("..") ||
|
|
1251
|
+
const relative = path4.relative(projectRoot, targetPath);
|
|
1252
|
+
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
1059
1253
|
throw new Error(
|
|
1060
1254
|
`Refusing to write outside project root. Computed target: ${targetPath}`
|
|
1061
1255
|
);
|
|
@@ -1113,19 +1307,19 @@ function resolveTargetFilePath(input) {
|
|
|
1113
1307
|
if (sourceFilePath === sourcePagePath) {
|
|
1114
1308
|
return targetPagePath;
|
|
1115
1309
|
}
|
|
1116
|
-
const componentsPrefix = `${sourceComponentsPath}${
|
|
1310
|
+
const componentsPrefix = `${sourceComponentsPath}${path4.sep}`;
|
|
1117
1311
|
if (sourceFilePath === sourceComponentsPath || sourceFilePath.startsWith(componentsPrefix)) {
|
|
1118
|
-
const relativeToComponents =
|
|
1312
|
+
const relativeToComponents = path4.relative(
|
|
1119
1313
|
sourceComponentsPath,
|
|
1120
1314
|
sourceFilePath
|
|
1121
1315
|
);
|
|
1122
|
-
return
|
|
1316
|
+
return path4.join(targetComponentsPath, relativeToComponents);
|
|
1123
1317
|
}
|
|
1124
|
-
const relativeToExport =
|
|
1125
|
-
if (relativeToExport.startsWith("..") ||
|
|
1318
|
+
const relativeToExport = path4.relative(exportRoot, sourceFilePath);
|
|
1319
|
+
if (relativeToExport.startsWith("..") || path4.isAbsolute(relativeToExport)) {
|
|
1126
1320
|
throw new Error(`Source file is outside export root: ${sourceFilePath}`);
|
|
1127
1321
|
}
|
|
1128
|
-
return
|
|
1322
|
+
return path4.join(projectRoot, relativeToExport);
|
|
1129
1323
|
}
|
|
1130
1324
|
async function copyPageFromExport(input) {
|
|
1131
1325
|
const normalizedOriginSlug = normalizeSlug2(input.originPageSlug);
|
|
@@ -1136,11 +1330,11 @@ async function copyPageFromExport(input) {
|
|
|
1136
1330
|
const { exportId, exportPath } = await resolveSingleExportDirectory(
|
|
1137
1331
|
input.exportsRoot
|
|
1138
1332
|
);
|
|
1139
|
-
const manifestPath =
|
|
1333
|
+
const manifestPath = path4.join(
|
|
1140
1334
|
input.exportsRoot,
|
|
1141
1335
|
`${exportId}.manifest.json`
|
|
1142
1336
|
);
|
|
1143
|
-
if (!await
|
|
1337
|
+
if (!await fileExists2(manifestPath)) {
|
|
1144
1338
|
throw new Error(
|
|
1145
1339
|
`Export manifest is missing at ${manifestPath}. Run primeui_download_export to regenerate it.`
|
|
1146
1340
|
);
|
|
@@ -1159,13 +1353,13 @@ async function copyPageFromExport(input) {
|
|
|
1159
1353
|
`Page not found in manifest for slug: ${normalizedOriginSlug}`
|
|
1160
1354
|
);
|
|
1161
1355
|
}
|
|
1162
|
-
const sourcePagePath =
|
|
1163
|
-
const sourceComponentsPath =
|
|
1164
|
-
const sourcePageStats = await
|
|
1356
|
+
const sourcePagePath = path4.join(exportPath, page.pagePath);
|
|
1357
|
+
const sourceComponentsPath = path4.join(exportPath, page.componentsPath);
|
|
1358
|
+
const sourcePageStats = await stat3(sourcePagePath).catch(() => null);
|
|
1165
1359
|
if (!sourcePageStats?.isFile()) {
|
|
1166
1360
|
throw new Error(`Source page file not found: ${sourcePagePath}`);
|
|
1167
1361
|
}
|
|
1168
|
-
const sourceComponentsStats = await
|
|
1362
|
+
const sourceComponentsStats = await stat3(sourceComponentsPath).catch(
|
|
1169
1363
|
() => null
|
|
1170
1364
|
);
|
|
1171
1365
|
if (!sourceComponentsStats?.isDirectory()) {
|
|
@@ -1180,8 +1374,8 @@ async function copyPageFromExport(input) {
|
|
|
1180
1374
|
pageType: page.pageType,
|
|
1181
1375
|
slug: normalizedActualSlug
|
|
1182
1376
|
});
|
|
1183
|
-
const targetPagePath =
|
|
1184
|
-
const targetComponentsPath =
|
|
1377
|
+
const targetPagePath = path4.join(input.projectRoot, targetPaths.pagePath);
|
|
1378
|
+
const targetComponentsPath = path4.join(
|
|
1185
1379
|
input.projectRoot,
|
|
1186
1380
|
targetPaths.componentsPath
|
|
1187
1381
|
);
|
|
@@ -1211,7 +1405,7 @@ async function copyPageFromExport(input) {
|
|
|
1211
1405
|
projectRoot: input.projectRoot
|
|
1212
1406
|
});
|
|
1213
1407
|
ensureSafeTargetPath(input.projectRoot, targetFilePath);
|
|
1214
|
-
const sourceBuffer = await
|
|
1408
|
+
const sourceBuffer = await readFile3(sourceFilePath);
|
|
1215
1409
|
const plannedSourceBuffer = buildPlannedSourceBuffer(
|
|
1216
1410
|
sourceBuffer,
|
|
1217
1411
|
sourceFilePath,
|
|
@@ -1219,14 +1413,14 @@ async function copyPageFromExport(input) {
|
|
|
1219
1413
|
);
|
|
1220
1414
|
let targetBuffer = null;
|
|
1221
1415
|
try {
|
|
1222
|
-
targetBuffer = await
|
|
1416
|
+
targetBuffer = await readFile3(targetFilePath);
|
|
1223
1417
|
} catch {
|
|
1224
1418
|
targetBuffer = null;
|
|
1225
1419
|
}
|
|
1226
1420
|
const sourceRelative = toProjectRelative(exportPath, sourceFilePath);
|
|
1227
1421
|
const targetRelative = toProjectRelative(input.projectRoot, targetFilePath);
|
|
1228
1422
|
if (!targetBuffer) {
|
|
1229
|
-
await ensureDir(
|
|
1423
|
+
await ensureDir(path4.dirname(targetFilePath));
|
|
1230
1424
|
await writeFile2(targetFilePath, plannedSourceBuffer);
|
|
1231
1425
|
newFiles.push({
|
|
1232
1426
|
sourcePath: sourceRelative,
|
|
@@ -1255,12 +1449,12 @@ async function copyPageFromExport(input) {
|
|
|
1255
1449
|
isBinary
|
|
1256
1450
|
});
|
|
1257
1451
|
}
|
|
1258
|
-
const exportPackageJsonPath =
|
|
1259
|
-
const userPackageJsonPath =
|
|
1260
|
-
if (!await
|
|
1452
|
+
const exportPackageJsonPath = path4.join(exportPath, "package.json");
|
|
1453
|
+
const userPackageJsonPath = path4.join(input.projectRoot, "package.json");
|
|
1454
|
+
if (!await fileExists2(exportPackageJsonPath)) {
|
|
1261
1455
|
throw new Error(`Export package.json not found: ${exportPackageJsonPath}`);
|
|
1262
1456
|
}
|
|
1263
|
-
if (!await
|
|
1457
|
+
if (!await fileExists2(userPackageJsonPath)) {
|
|
1264
1458
|
throw new Error(`User package.json not found: ${userPackageJsonPath}`);
|
|
1265
1459
|
}
|
|
1266
1460
|
const exportPackageJson = await readJsonFile(
|
|
@@ -1338,6 +1532,96 @@ async function copyPageFromExport(input) {
|
|
|
1338
1532
|
};
|
|
1339
1533
|
}
|
|
1340
1534
|
|
|
1535
|
+
// src/services/page-inspect-report.ts
|
|
1536
|
+
function normalizeTextValue(value) {
|
|
1537
|
+
if (typeof value !== "string") {
|
|
1538
|
+
return null;
|
|
1539
|
+
}
|
|
1540
|
+
const trimmed = value.trim();
|
|
1541
|
+
return trimmed ? trimmed : null;
|
|
1542
|
+
}
|
|
1543
|
+
function extractFirstStringByKey(value, key, seen) {
|
|
1544
|
+
if (!value || typeof value !== "object") {
|
|
1545
|
+
return null;
|
|
1546
|
+
}
|
|
1547
|
+
if (seen.has(value)) {
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
seen.add(value);
|
|
1551
|
+
if (Array.isArray(value)) {
|
|
1552
|
+
for (const item of value) {
|
|
1553
|
+
const nested = extractFirstStringByKey(item, key, seen);
|
|
1554
|
+
if (nested) {
|
|
1555
|
+
return nested;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
const record = value;
|
|
1561
|
+
for (const [recordKey, recordValue] of Object.entries(record)) {
|
|
1562
|
+
if (recordKey.toLowerCase() === key) {
|
|
1563
|
+
const normalized = normalizeTextValue(recordValue);
|
|
1564
|
+
if (normalized) {
|
|
1565
|
+
return normalized;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
for (const nestedValue of Object.values(record)) {
|
|
1570
|
+
const nested = extractFirstStringByKey(nestedValue, key, seen);
|
|
1571
|
+
if (nested) {
|
|
1572
|
+
return nested;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
return null;
|
|
1576
|
+
}
|
|
1577
|
+
function extractTitleAndDescription(props) {
|
|
1578
|
+
if (!props) {
|
|
1579
|
+
return {
|
|
1580
|
+
title: null,
|
|
1581
|
+
description: null
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
return {
|
|
1585
|
+
title: extractFirstStringByKey(props, "title", /* @__PURE__ */ new Set()),
|
|
1586
|
+
description: extractFirstStringByKey(props, "description", /* @__PURE__ */ new Set())
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
function resolveSummaryText(row) {
|
|
1590
|
+
return row.title ?? row.description ?? "";
|
|
1591
|
+
}
|
|
1592
|
+
function buildInspectPageReport(input) {
|
|
1593
|
+
if (input.components === null) {
|
|
1594
|
+
return {
|
|
1595
|
+
report: "Components are unavailable for this page because no active variant is set (isReadyToExport=false).",
|
|
1596
|
+
reportRows: []
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
if (input.components.length === 0) {
|
|
1600
|
+
return {
|
|
1601
|
+
report: "Active variant has no components.",
|
|
1602
|
+
reportRows: []
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
const reportRows = input.components.map((component, index) => {
|
|
1606
|
+
const titleDescription = extractTitleAndDescription(component.props);
|
|
1607
|
+
return {
|
|
1608
|
+
number: index + 1,
|
|
1609
|
+
componentGroup: component.componentGroup,
|
|
1610
|
+
componentId: component.componentId,
|
|
1611
|
+
blockId: component.blockId,
|
|
1612
|
+
title: titleDescription.title,
|
|
1613
|
+
description: titleDescription.description
|
|
1614
|
+
};
|
|
1615
|
+
});
|
|
1616
|
+
const report = reportRows.map(
|
|
1617
|
+
(row) => `${row.number}. ${row.componentGroup} (${row.componentId}) | ${resolveSummaryText(row)} | ${row.blockId}`
|
|
1618
|
+
).join("\n");
|
|
1619
|
+
return {
|
|
1620
|
+
report,
|
|
1621
|
+
reportRows
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1341
1625
|
// src/services/project-sync-service.ts
|
|
1342
1626
|
function isProjectPage(value) {
|
|
1343
1627
|
if (!value || typeof value !== "object") {
|
|
@@ -1347,7 +1631,7 @@ function isProjectPage(value) {
|
|
|
1347
1631
|
return typeof maybe.id === "string" && typeof maybe.title === "string" && typeof maybe.slug === "string" && typeof maybe.pageType === "string" && typeof maybe.isReadyToExport === "boolean" && typeof maybe.pagePath === "string" && typeof maybe.componentsPath === "string";
|
|
1348
1632
|
}
|
|
1349
1633
|
function buildPagesSnapshotPath(exportsRoot, exportId) {
|
|
1350
|
-
return
|
|
1634
|
+
return path5.join(exportsRoot, `${exportId}.pages.snapshot.json`);
|
|
1351
1635
|
}
|
|
1352
1636
|
function parsePagesSnapshot(value) {
|
|
1353
1637
|
if (!value || typeof value !== "object") {
|
|
@@ -1370,15 +1654,17 @@ function parsePagesSnapshot(value) {
|
|
|
1370
1654
|
var ProjectSyncService = class {
|
|
1371
1655
|
provider;
|
|
1372
1656
|
projectRoot;
|
|
1657
|
+
targetProjectRoot;
|
|
1373
1658
|
primeUiRoot;
|
|
1374
1659
|
tempRoot;
|
|
1375
1660
|
exportsRoot;
|
|
1376
1661
|
constructor(options) {
|
|
1377
1662
|
this.projectRoot = options.projectRoot;
|
|
1663
|
+
this.targetProjectRoot = options.targetProjectRoot;
|
|
1378
1664
|
this.provider = options.provider;
|
|
1379
|
-
this.primeUiRoot =
|
|
1380
|
-
this.tempRoot =
|
|
1381
|
-
this.exportsRoot =
|
|
1665
|
+
this.primeUiRoot = path5.join(this.projectRoot, ".primeui");
|
|
1666
|
+
this.tempRoot = path5.join(this.primeUiRoot, "temp");
|
|
1667
|
+
this.exportsRoot = path5.join(this.tempRoot, "exports");
|
|
1382
1668
|
}
|
|
1383
1669
|
async getProjectInfo() {
|
|
1384
1670
|
await this.ensureTempLayout();
|
|
@@ -1415,8 +1701,8 @@ var ProjectSyncService = class {
|
|
|
1415
1701
|
if (!selected) {
|
|
1416
1702
|
throw new Error(`Export not found: ${id}`);
|
|
1417
1703
|
}
|
|
1418
|
-
const targetZipPath =
|
|
1419
|
-
const targetProjectPath =
|
|
1704
|
+
const targetZipPath = path5.join(this.exportsRoot, `${id}.zip`);
|
|
1705
|
+
const targetProjectPath = path5.join(this.exportsRoot, id);
|
|
1420
1706
|
await ensureDir(this.exportsRoot);
|
|
1421
1707
|
await this.provider.downloadExportArchive(id, targetZipPath);
|
|
1422
1708
|
await resetDir(targetProjectPath);
|
|
@@ -1425,7 +1711,7 @@ var ProjectSyncService = class {
|
|
|
1425
1711
|
let pagesSnapshot;
|
|
1426
1712
|
try {
|
|
1427
1713
|
const snapshotRaw = JSON.parse(
|
|
1428
|
-
await
|
|
1714
|
+
await readFile4(pagesSnapshotPath, "utf-8")
|
|
1429
1715
|
);
|
|
1430
1716
|
pagesSnapshot = parsePagesSnapshot(snapshotRaw);
|
|
1431
1717
|
} catch (error) {
|
|
@@ -1440,7 +1726,7 @@ var ProjectSyncService = class {
|
|
|
1440
1726
|
);
|
|
1441
1727
|
}
|
|
1442
1728
|
const pages = pagesSnapshot.pages;
|
|
1443
|
-
const manifestPath =
|
|
1729
|
+
const manifestPath = path5.join(this.exportsRoot, `${id}.manifest.json`);
|
|
1444
1730
|
const manifest = {
|
|
1445
1731
|
schemaVersion: 1,
|
|
1446
1732
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1457,10 +1743,20 @@ var ProjectSyncService = class {
|
|
|
1457
1743
|
pages
|
|
1458
1744
|
};
|
|
1459
1745
|
}
|
|
1746
|
+
async inspectPage(slug) {
|
|
1747
|
+
await this.ensureTempLayout();
|
|
1748
|
+
const pageDetails = await this.provider.getProjectPageBySlug(slug);
|
|
1749
|
+
const reportPayload = buildInspectPageReport(pageDetails);
|
|
1750
|
+
return {
|
|
1751
|
+
...pageDetails,
|
|
1752
|
+
report: reportPayload.report,
|
|
1753
|
+
reportRows: reportPayload.reportRows
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1460
1756
|
async copyPage(originPageSlug, actualPageSlug) {
|
|
1461
1757
|
await this.ensureTempLayout();
|
|
1462
1758
|
return copyPageFromExport({
|
|
1463
|
-
projectRoot: this.
|
|
1759
|
+
projectRoot: this.targetProjectRoot,
|
|
1464
1760
|
exportsRoot: this.exportsRoot,
|
|
1465
1761
|
originPageSlug,
|
|
1466
1762
|
actualPageSlug
|
|
@@ -1468,61 +1764,79 @@ var ProjectSyncService = class {
|
|
|
1468
1764
|
}
|
|
1469
1765
|
async clearTemp() {
|
|
1470
1766
|
await resetDir(this.tempRoot);
|
|
1471
|
-
await writeUtf8(
|
|
1767
|
+
await writeUtf8(path5.join(this.tempRoot, ".gitkeep"), "");
|
|
1472
1768
|
await ensureDir(this.exportsRoot);
|
|
1473
1769
|
}
|
|
1474
1770
|
async ensureTempLayout() {
|
|
1475
1771
|
await ensureDir(this.primeUiRoot);
|
|
1476
1772
|
await ensureDir(this.tempRoot);
|
|
1477
1773
|
await ensureDir(this.exportsRoot);
|
|
1478
|
-
await writeUtf8(
|
|
1774
|
+
await writeUtf8(path5.join(this.tempRoot, ".gitkeep"), "");
|
|
1479
1775
|
}
|
|
1480
1776
|
};
|
|
1481
1777
|
|
|
1482
1778
|
// src/sources/api-provider.ts
|
|
1483
1779
|
import { createWriteStream } from "fs";
|
|
1484
1780
|
import { unlink } from "fs/promises";
|
|
1485
|
-
import
|
|
1781
|
+
import path6 from "path";
|
|
1486
1782
|
import { Readable, Transform } from "stream";
|
|
1487
1783
|
import { pipeline } from "stream/promises";
|
|
1488
|
-
import { z as
|
|
1784
|
+
import { z as z3 } from "zod";
|
|
1489
1785
|
var DEFAULT_API_BASE_URL = "https://app.primeui.com/";
|
|
1490
1786
|
var ZIP_CONTENT_TYPES = [
|
|
1491
1787
|
"application/zip",
|
|
1492
1788
|
"application/octet-stream",
|
|
1493
1789
|
"application/x-zip-compressed"
|
|
1494
1790
|
];
|
|
1495
|
-
var exportStatusSchema2 =
|
|
1496
|
-
var
|
|
1497
|
-
id:
|
|
1498
|
-
title:
|
|
1499
|
-
slug:
|
|
1500
|
-
pageType:
|
|
1501
|
-
isReadyToExport:
|
|
1502
|
-
pagePath:
|
|
1503
|
-
componentsPath:
|
|
1791
|
+
var exportStatusSchema2 = z3.enum(["in_progress", "completed", "failed"]);
|
|
1792
|
+
var projectPageObjectSchema = z3.object({
|
|
1793
|
+
id: z3.string(),
|
|
1794
|
+
title: z3.string(),
|
|
1795
|
+
slug: z3.string(),
|
|
1796
|
+
pageType: z3.string(),
|
|
1797
|
+
isReadyToExport: z3.boolean(),
|
|
1798
|
+
pagePath: z3.string(),
|
|
1799
|
+
componentsPath: z3.string()
|
|
1800
|
+
});
|
|
1801
|
+
var projectPageSchema = projectPageObjectSchema;
|
|
1802
|
+
var projectInfoSchema = z3.object({
|
|
1803
|
+
projectId: z3.string(),
|
|
1804
|
+
projectName: z3.string(),
|
|
1805
|
+
metadata: z3.record(z3.unknown()),
|
|
1806
|
+
pages: z3.array(projectPageSchema)
|
|
1504
1807
|
});
|
|
1505
|
-
var
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1808
|
+
var projectPageComponentSchema = z3.object({
|
|
1809
|
+
blockId: z3.string(),
|
|
1810
|
+
componentId: z3.string(),
|
|
1811
|
+
componentGroup: z3.string(),
|
|
1812
|
+
slot: z3.string().nullable(),
|
|
1813
|
+
props: z3.record(z3.unknown()).nullable()
|
|
1814
|
+
});
|
|
1815
|
+
var projectPageDetailsSchema = z3.object({
|
|
1816
|
+
page: projectPageObjectSchema.extend({
|
|
1817
|
+
pageInstruction: z3.string().nullable()
|
|
1818
|
+
}),
|
|
1819
|
+
variant: z3.object({
|
|
1820
|
+
id: z3.string(),
|
|
1821
|
+
name: z3.string()
|
|
1822
|
+
}).nullable(),
|
|
1823
|
+
components: z3.array(projectPageComponentSchema).nullable()
|
|
1510
1824
|
});
|
|
1511
|
-
var exportsResponseSchema =
|
|
1512
|
-
exports:
|
|
1513
|
-
|
|
1514
|
-
id:
|
|
1825
|
+
var exportsResponseSchema = z3.object({
|
|
1826
|
+
exports: z3.array(
|
|
1827
|
+
z3.object({
|
|
1828
|
+
id: z3.string(),
|
|
1515
1829
|
status: exportStatusSchema2,
|
|
1516
|
-
createdAt:
|
|
1830
|
+
createdAt: z3.string().datetime({ offset: true })
|
|
1517
1831
|
})
|
|
1518
1832
|
)
|
|
1519
1833
|
});
|
|
1520
|
-
var createExportResponseSchema =
|
|
1521
|
-
export:
|
|
1522
|
-
id:
|
|
1834
|
+
var createExportResponseSchema = z3.object({
|
|
1835
|
+
export: z3.object({
|
|
1836
|
+
id: z3.string(),
|
|
1523
1837
|
status: exportStatusSchema2
|
|
1524
1838
|
}),
|
|
1525
|
-
pages:
|
|
1839
|
+
pages: z3.array(projectPageSchema)
|
|
1526
1840
|
});
|
|
1527
1841
|
var PrimeUiApiContractError = class extends Error {
|
|
1528
1842
|
constructor(endpoint, details) {
|
|
@@ -1609,6 +1923,13 @@ var ApiProjectDataProvider = class {
|
|
|
1609
1923
|
async getProjectInfo() {
|
|
1610
1924
|
return this.requestJson("project", projectInfoSchema);
|
|
1611
1925
|
}
|
|
1926
|
+
async getProjectPageBySlug(slug) {
|
|
1927
|
+
const encodedSlug = encodeURIComponent(slug);
|
|
1928
|
+
return this.requestJson(
|
|
1929
|
+
`project/page?slug=${encodedSlug}`,
|
|
1930
|
+
projectPageDetailsSchema
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1612
1933
|
async listExports() {
|
|
1613
1934
|
const response = await this.requestJson(
|
|
1614
1935
|
"project/exports",
|
|
@@ -1651,7 +1972,7 @@ var ApiProjectDataProvider = class {
|
|
|
1651
1972
|
if (!response.body) {
|
|
1652
1973
|
throw new PrimeUiApiContractError(endpoint, "response body is empty");
|
|
1653
1974
|
}
|
|
1654
|
-
await ensureDir(
|
|
1975
|
+
await ensureDir(path6.dirname(destinationPath));
|
|
1655
1976
|
const zipStream = Readable.fromWeb(
|
|
1656
1977
|
response.body
|
|
1657
1978
|
);
|
|
@@ -1739,22 +2060,34 @@ var ApiProjectDataProvider = class {
|
|
|
1739
2060
|
|
|
1740
2061
|
// src/service.ts
|
|
1741
2062
|
async function main() {
|
|
1742
|
-
const
|
|
1743
|
-
|
|
2063
|
+
const resolvedProjectConfig = await resolvePrimeUiProjectConfig({
|
|
2064
|
+
cwd: process.cwd(),
|
|
2065
|
+
projectRootFromEnv: process.env.PRIMEUI_PROJECT_ROOT
|
|
2066
|
+
});
|
|
2067
|
+
const projectRoot = resolvedProjectConfig.projectRoot;
|
|
2068
|
+
const targetProjectRoot = path7.resolve(
|
|
2069
|
+
projectRoot,
|
|
2070
|
+
resolvedProjectConfig.projectConfig.targetProjectPath
|
|
2071
|
+
);
|
|
2072
|
+
const apiKey = await resolvePrimeUiApiKey({
|
|
2073
|
+
projectConfig: resolvedProjectConfig.projectConfig,
|
|
2074
|
+
apiKeyFromEnv: process.env.PRIMEUI_API_KEY
|
|
2075
|
+
});
|
|
1744
2076
|
const provider = new ApiProjectDataProvider({
|
|
1745
2077
|
apiKey,
|
|
1746
2078
|
baseUrl: process.env.PRIMEUI_API_BASE_URL
|
|
1747
2079
|
});
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
}
|
|
1753
|
-
const syncService = new ProjectSyncService({ projectRoot, provider });
|
|
2080
|
+
const syncService = new ProjectSyncService({
|
|
2081
|
+
projectRoot,
|
|
2082
|
+
targetProjectRoot,
|
|
2083
|
+
provider
|
|
2084
|
+
});
|
|
1754
2085
|
const server = createPrimeUiMcpServer(syncService);
|
|
1755
2086
|
const transport = new StdioServerTransport();
|
|
1756
2087
|
await server.connect(transport);
|
|
1757
|
-
console.error(
|
|
2088
|
+
console.error(
|
|
2089
|
+
`[primeui-mcp] running for root: ${projectRoot}; target project: ${targetProjectRoot}`
|
|
2090
|
+
);
|
|
1758
2091
|
}
|
|
1759
2092
|
main().catch((error) => {
|
|
1760
2093
|
console.error("[primeui-mcp] failed to start", error);
|