@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/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 = z.object({
12
- id: z.string().describe(
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: z.string().describe(
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: z.string().describe(
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: z.string().describe(
125
+ pageType: z2.string().describe(
22
126
  "PrimeUI page type classification (for example landing, docs, pricing, blogIndex, legal)."
23
127
  ),
24
- isReadyToExport: z.boolean().describe(
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: z.string().describe(
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: z.string().describe(
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 = z.enum(["in_progress", "completed", "failed"]).describe(
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 = z.object({
40
- id: z.string().describe(
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: z.string().describe("Export creation timestamp in ISO-8601 UTC format.")
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 = z.object({
47
- sourcePath: z.string().describe("Relative source file path inside downloaded export root."),
48
- targetPath: z.string().describe("Relative target file path inside user project root.")
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: z.string().describe(
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: z.boolean().describe("True if conflict comparison was treated as binary data.")
158
+ isBinary: z2.boolean().describe("True if conflict comparison was treated as binary data.")
55
159
  });
56
- var dependencySectionSchema = z.enum([
160
+ var dependencySectionSchema = z2.enum([
57
161
  "dependencies",
58
162
  "devDependencies",
59
163
  "peerDependencies"
60
164
  ]);
61
- var dependencyToAddSchema = z.object({
62
- packageName: z.string().describe("Dependency package name that was missing in user package.json."),
63
- version: z.string().describe("Version from exported project's package.json."),
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 = z.object({
69
- packageName: z.string().describe("Dependency package name with version mismatch."),
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: z.string().describe("Version found in exported project's package.json."),
74
- userVersion: z.string().describe("Version currently present in user's package.json.")
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. primeui_create_export -> generate export snapshot for agreed pages/analysis scope
91
- 4. primeui_download_export -> download to temp directory
92
- 5. primeui_copy_page -> copy one page safely (repeat for each selected page)
93
- 6. Reconcile integration points and resolve reported conflicts, if any
94
- 7. primeui_clear_temp -> cleanup temp files
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: z.string().describe(
282
+ projectId: z2.string().describe(
149
283
  "PrimeUI project identifier associated with the API key and current import session context."
150
284
  ),
151
- projectName: z.string().describe(
285
+ projectName: z2.string().describe(
152
286
  "Human-readable PrimeUI project name for confirmations in chat."
153
287
  ),
154
- metadata: z.record(z.unknown()).describe(
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: z.array(pageSchema).describe(
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: z.array(exportItemSchema).describe(
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: z.object({
211
- id: z.string().describe(
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: z.array(pageSchema).describe(
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: z.string().describe(
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: z.string().describe(
433
+ exportId: z2.string().describe(
255
434
  "Downloaded export identifier. Should match the requested input.id for traceability."
256
435
  ),
257
- projectPath: z.string().describe(
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: z.string().describe(
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: z.array(pageSchema).describe(
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: z.string().describe(
477
+ originPageSlug: z2.string().describe(
299
478
  "Source page slug from PrimeUI project pages list, for example '/', '/pricing', '/docs'."
300
479
  ),
301
- actualPageSlug: z.string().optional().describe(
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: z.string().describe("Resolved local export identifier used for copy operation."),
307
- exportPath: z.string().describe("Absolute path to local extracted export project root."),
308
- originPageSlug: z.string().describe("Normalized source page slug requested for copy."),
309
- actualPageSlug: z.string().describe("Normalized destination slug used for target route paths."),
310
- sourcePagePath: z.string().describe("Source page path relative to export root."),
311
- targetPagePath: z.string().describe("Destination page path relative to user project root."),
312
- sourceComponentsPath: z.string().describe("Source components directory relative to export root."),
313
- targetComponentsPath: z.string().describe(
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: z.array(fileTransferSchema).describe("New files copied into user project without conflicts."),
317
- identicalFiles: z.array(fileTransferSchema).describe(
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: z.array(conflictFileSchema).describe(
499
+ conflictFiles: z2.array(conflictFileSchema).describe(
321
500
  "Files that already exist and differ from export version. Never overwritten."
322
501
  ),
323
- addedDependencies: z.array(dependencyToAddSchema).describe(
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: z.array(dependencyVersionConflictSchema).describe(
505
+ dependenciesVersionConflicts: z2.array(dependencyVersionConflictSchema).describe(
327
506
  "Dependencies with version mismatch between export and user project. Report only."
328
507
  ),
329
- summary: z.object({
330
- totalCandidateFiles: z.number().describe("Total number of candidate files considered for page copy."),
331
- copiedFiles: z.number().describe("Number of files copied as new."),
332
- identicalFiles: z.number().describe("Number of files detected as identical."),
333
- conflictFiles: z.number().describe("Number of conflicting files not copied."),
334
- addedDependencies: z.number().describe("Count of dependencies added to package.json."),
335
- dependenciesVersionConflicts: z.number().describe("Count of dependency version mismatches reported.")
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: z.boolean().describe(
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 path4 from "path";
474
- import { readFile as readFile3 } from "fs/promises";
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 path from "path";
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(path.dirname(filePath));
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 readFile2, readdir, stat as stat2, writeFile as writeFile2 } from "fs/promises";
503
- import path3 from "path";
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 path2 from "path";
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 stat(filePath);
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 = path2.extname(candidateBase);
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 stat(candidateBase);
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 = path2.join(candidateBase, `index${extension}`);
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 = path2.join(projectRoot, "src", specifier.slice(2));
817
+ candidateBase = path3.join(projectRoot, "src", specifier.slice(2));
627
818
  } else if (specifier.startsWith("@root/")) {
628
- candidateBase = path2.join(projectRoot, specifier.slice(6));
819
+ candidateBase = path3.join(projectRoot, specifier.slice(6));
629
820
  } else if (specifier.startsWith(".")) {
630
- candidateBase = path2.resolve(path2.dirname(importerFilePath), specifier);
821
+ candidateBase = path3.resolve(path3.dirname(importerFilePath), specifier);
631
822
  } else if (specifier.startsWith("/")) {
632
- candidateBase = path2.join(projectRoot, specifier.slice(1));
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(path2.extname(filePath).toLowerCase());
837
+ return PARSEABLE_EXTENSIONS.has(path3.extname(filePath).toLowerCase());
647
838
  }
648
839
  async function buildImportGraph(input) {
649
- const projectRoot = path2.resolve(input.projectRoot);
650
- const queue = input.entryFiles.map((filePath) => path2.resolve(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 readFile(currentFilePath, "utf-8");
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(path3.sep).join("/");
1094
+ return value.split(path4.sep).join("/");
904
1095
  }
905
1096
  function toProjectRelative(rootPath, absolutePath) {
906
- return toPosixPath(path3.relative(rootPath, absolutePath));
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 = path3.extname(sourceFilePath).toLowerCase();
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(sourceText, importRewritePlan);
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 readFile2(filePath, "utf-8");
1172
+ const content = await readFile3(filePath, "utf-8");
979
1173
  return JSON.parse(content);
980
1174
  }
981
- async function fileExists(filePath) {
1175
+ async function fileExists2(filePath) {
982
1176
  try {
983
- await stat2(filePath);
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 = path3.join(current, entry.name);
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: path3.join(exportsRoot, exportId)
1247
+ exportPath: path4.join(exportsRoot, exportId)
1054
1248
  };
1055
1249
  }
1056
1250
  function ensureSafeTargetPath(projectRoot, targetPath) {
1057
- const relative = path3.relative(projectRoot, targetPath);
1058
- if (relative.startsWith("..") || path3.isAbsolute(relative)) {
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}${path3.sep}`;
1310
+ const componentsPrefix = `${sourceComponentsPath}${path4.sep}`;
1117
1311
  if (sourceFilePath === sourceComponentsPath || sourceFilePath.startsWith(componentsPrefix)) {
1118
- const relativeToComponents = path3.relative(
1312
+ const relativeToComponents = path4.relative(
1119
1313
  sourceComponentsPath,
1120
1314
  sourceFilePath
1121
1315
  );
1122
- return path3.join(targetComponentsPath, relativeToComponents);
1316
+ return path4.join(targetComponentsPath, relativeToComponents);
1123
1317
  }
1124
- const relativeToExport = path3.relative(exportRoot, sourceFilePath);
1125
- if (relativeToExport.startsWith("..") || path3.isAbsolute(relativeToExport)) {
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 path3.join(projectRoot, relativeToExport);
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 = path3.join(
1333
+ const manifestPath = path4.join(
1140
1334
  input.exportsRoot,
1141
1335
  `${exportId}.manifest.json`
1142
1336
  );
1143
- if (!await fileExists(manifestPath)) {
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 = path3.join(exportPath, page.pagePath);
1163
- const sourceComponentsPath = path3.join(exportPath, page.componentsPath);
1164
- const sourcePageStats = await stat2(sourcePagePath).catch(() => null);
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 stat2(sourceComponentsPath).catch(
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 = path3.join(input.projectRoot, targetPaths.pagePath);
1184
- const targetComponentsPath = path3.join(
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 readFile2(sourceFilePath);
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 readFile2(targetFilePath);
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(path3.dirname(targetFilePath));
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 = path3.join(exportPath, "package.json");
1259
- const userPackageJsonPath = path3.join(input.projectRoot, "package.json");
1260
- if (!await fileExists(exportPackageJsonPath)) {
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 fileExists(userPackageJsonPath)) {
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 path4.join(exportsRoot, `${exportId}.pages.snapshot.json`);
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 = path4.join(this.projectRoot, ".primeui");
1380
- this.tempRoot = path4.join(this.primeUiRoot, "temp");
1381
- this.exportsRoot = path4.join(this.tempRoot, "exports");
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 = path4.join(this.exportsRoot, `${id}.zip`);
1419
- const targetProjectPath = path4.join(this.exportsRoot, id);
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 readFile3(pagesSnapshotPath, "utf-8")
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 = path4.join(this.exportsRoot, `${id}.manifest.json`);
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.projectRoot,
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(path4.join(this.tempRoot, ".gitkeep"), "");
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(path4.join(this.tempRoot, ".gitkeep"), "");
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 path5 from "path";
1781
+ import path6 from "path";
1486
1782
  import { Readable, Transform } from "stream";
1487
1783
  import { pipeline } from "stream/promises";
1488
- import { z as z2 } from "zod";
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 = z2.enum(["in_progress", "completed", "failed"]);
1496
- var projectPageSchema = z2.object({
1497
- id: z2.string(),
1498
- title: z2.string(),
1499
- slug: z2.string(),
1500
- pageType: z2.string(),
1501
- isReadyToExport: z2.boolean(),
1502
- pagePath: z2.string(),
1503
- componentsPath: z2.string()
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 projectInfoSchema = z2.object({
1506
- projectId: z2.string(),
1507
- projectName: z2.string(),
1508
- metadata: z2.record(z2.unknown()),
1509
- pages: z2.array(projectPageSchema)
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 = z2.object({
1512
- exports: z2.array(
1513
- z2.object({
1514
- id: z2.string(),
1825
+ var exportsResponseSchema = z3.object({
1826
+ exports: z3.array(
1827
+ z3.object({
1828
+ id: z3.string(),
1515
1829
  status: exportStatusSchema2,
1516
- createdAt: z2.string().datetime({ offset: true })
1830
+ createdAt: z3.string().datetime({ offset: true })
1517
1831
  })
1518
1832
  )
1519
1833
  });
1520
- var createExportResponseSchema = z2.object({
1521
- export: z2.object({
1522
- id: z2.string(),
1834
+ var createExportResponseSchema = z3.object({
1835
+ export: z3.object({
1836
+ id: z3.string(),
1523
1837
  status: exportStatusSchema2
1524
1838
  }),
1525
- pages: z2.array(projectPageSchema)
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(path5.dirname(destinationPath));
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 projectRoot = process.env.PRIMEUI_PROJECT_ROOT || process.cwd();
1743
- const apiKey = process.env.PRIMEUI_API_KEY;
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
- if (!apiKey) {
1749
- console.error(
1750
- "[primeui-mcp] warning: PRIMEUI_API_KEY is not set; API-backed tools will fail until it is configured."
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(`[primeui-mcp] running for root: ${projectRoot}`);
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);