@primeuicom/mcp 0.1.9 → 0.1.11

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,26 +1,1763 @@
1
1
  #!/usr/bin/env node
2
+
3
+ // src/service.ts
2
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { createPrimeUiMcpServer } from "./server.js";
4
- import { ProjectSyncService } from "./services/project-sync-service.js";
5
- import { ApiProjectDataProvider } from "./sources/api-provider.js";
6
- async function main() {
7
- const projectRoot = process.env.PRIMEUI_PROJECT_ROOT || process.cwd();
8
- const apiKey = process.env.PRIMEUI_API_KEY;
9
- const provider = new ApiProjectDataProvider({
10
- apiKey,
11
- baseUrl: process.env.PRIMEUI_API_BASE_URL,
5
+
6
+ // src/server.ts
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+
9
+ // src/instructions.ts
10
+ import { z } from "zod/v3";
11
+ var pageSchema = z.object({
12
+ id: z.string().describe(
13
+ "Stable PrimeUI page identifier. Use this to match the same page across tool responses."
14
+ ),
15
+ title: z.string().describe(
16
+ "Human-readable page title. Show this to the user when confirming which pages to import."
17
+ ),
18
+ slug: z.string().describe(
19
+ "PrimeUI route slug, for example '/', '/pricing', '/docs/getting-started'. Use to match user intent."
20
+ ),
21
+ pageType: z.string().describe(
22
+ "PrimeUI page type classification (for example landing, docs, pricing, blogIndex, legal)."
23
+ ),
24
+ isReadyToExport: z.boolean().describe(
25
+ "True when this page has an active export-ready variant. Only ready pages are expected in export output."
26
+ ),
27
+ pagePath: z.string().describe(
28
+ "Relative file path to the page source inside downloaded export root. Copy page code from this exact path."
29
+ ),
30
+ componentsPath: z.string().describe(
31
+ "Relative directory path inside downloaded export root where page-level components live. Use as dependency copy start."
32
+ )
33
+ }).describe(
34
+ "PrimeUI page descriptor used by project and export tools. Paths are always relative to export project root."
35
+ );
36
+ var exportStatusSchema = z.enum(["in_progress", "completed", "failed"]).describe(
37
+ "Export lifecycle state. Use 'completed' before calling primeui_download_export."
38
+ );
39
+ var exportItemSchema = z.object({
40
+ id: z.string().describe(
41
+ "Export identifier. Pass this value to primeui_download_export input.id."
42
+ ),
43
+ status: exportStatusSchema,
44
+ createdAt: z.string().describe("Export creation timestamp in ISO-8601 UTC format.")
45
+ }).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.")
49
+ });
50
+ var conflictFileSchema = fileTransferSchema.extend({
51
+ diff: z.string().describe(
52
+ "Unified diff between user file and export file. For binary files the diff contains a plain message."
53
+ ),
54
+ isBinary: z.boolean().describe("True if conflict comparison was treated as binary data.")
55
+ });
56
+ var dependencySectionSchema = z.enum([
57
+ "dependencies",
58
+ "devDependencies",
59
+ "peerDependencies"
60
+ ]);
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."),
64
+ section: dependencySectionSchema.describe(
65
+ "package.json section where dependency should be added."
66
+ )
67
+ });
68
+ var dependencyVersionConflictSchema = z.object({
69
+ packageName: z.string().describe("Dependency package name with version mismatch."),
70
+ section: dependencySectionSchema.describe(
71
+ "Section where export expects this dependency."
72
+ ),
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.")
75
+ });
76
+ var TRIGGER_PHRASES = [
77
+ "import page from PrimeUI",
78
+ "add page from PrimeUI",
79
+ "sync pages",
80
+ "pull page",
81
+ "transfer page",
82
+ "get page from PrimeUI",
83
+ "bring page from PrimeUI",
84
+ "export from PrimeUI"
85
+ ].join(", ");
86
+ var WORKFLOW_SUMMARY = `
87
+ WORKFLOW ORDER (always follow this sequence):
88
+ 1. primeui_get_project_info -> discover PrimeUI pages
89
+ 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
95
+ `.trim();
96
+ var initialInstructions = `
97
+ PrimeUI MCP enables importing pages from PrimeUI Studio into your local project.
98
+
99
+ TRIGGER EXAMPLES (semantic intent, not exact phrase matching): ${TRIGGER_PHRASES}
100
+
101
+ ${WORKFLOW_SUMMARY}
102
+
103
+ CRITICAL RULES FOR PAGE IMPORT:
104
+ - Import is PAGE-BY-PAGE only. Never import the entire project at once.
105
+ - After primeui_get_project_info, always compare PrimeUI pages against the user's existing local project pages before proposing imports.
106
+ - Reconciliation report MUST include: page presence match, slug/pagePath/componentsPath path match, and proposed action per page.
107
+ - Decision matrix:
108
+ - PrimeUI + local page exists -> candidate for update/review.
109
+ - PrimeUI exists, local missing -> import candidate.
110
+ - Local exists, PrimeUI missing -> custom local page, no action unless user asks.
111
+ - Both exist but path mismatch -> ambiguous, export + compare before deciding.
112
+ - Before creating export, ask user which pages to add/update (specific pages or all missing pages) and keep that choice in thread context.
113
+ - The downloaded export in .primeui/temp/ contains a full standalone Next.js project.
114
+ Do NOT copy it wholesale.
115
+ - Always call primeui_copy_page for each confirmed page instead of manual file copy.
116
+ - primeui_copy_page performs safe copy only:
117
+ - new files are copied,
118
+ - identical files are reported,
119
+ - conflicting files are NEVER overwritten and are returned with diff.
120
+ - primeui_copy_page may add only missing dependencies to user's package.json.
121
+ It never upgrades existing dependency versions and never runs dependency installation.
122
+ - After page copy operations, inspect integration points (navigation configs, link menus, route registries, page indexes).
123
+ - If integration pattern is obvious, apply it and explicitly mark it in the report with "\u26A0 integration update".
124
+ - If integration pattern is unclear, ask the user before editing integration files.
125
+ `.trim();
126
+ var toolGetProjectInfo = {
127
+ title: "PrimeUI Project Info",
128
+ description: `ENTRY POINT for all PrimeUI import operations. Always start here. Returns project metadata and the full list of available pages.
129
+
130
+ WHEN TO USE: Call this FIRST when user wants to import, add, sync, pull, or transfer a page from PrimeUI. Intent examples (semantic intent, not exact phrase matching): ${TRIGGER_PHRASES}.
131
+
132
+ AFTER CALLING:
133
+ - Scan the user's local project to discover existing pages/routes and related page component folders.
134
+ - Build a reconciliation report between PrimeUI and local project with action labels:
135
+ - Page exists in both PrimeUI and local project.
136
+ - Page exists in PrimeUI but not in local project -> import candidate.
137
+ - Page exists in local project but not in PrimeUI -> custom local page, no action by default.
138
+ - Page exists in both but slug/pagePath/componentsPath mapping does not match -> ambiguous, requires export-based comparison before decision.
139
+ - If user already requested specific pages, first verify those pages exist in PrimeUI and are isReadyToExport: true.
140
+ - If any requested page is not found or not ready, report this immediately and ask for replacement page choices.
141
+ - Ask the user which pages to add or update now (specific page list or all missing pages) and keep this decision in thread context.
142
+ - Import works per page. Multiple pages are allowed, but each page transfer is handled explicitly.
143
+ - Only after user decision is clear, proceed to primeui_create_export.
144
+
145
+ ${WORKFLOW_SUMMARY}`,
146
+ inputSchema: {},
147
+ outputSchema: {
148
+ projectId: z.string().describe(
149
+ "PrimeUI project identifier associated with the API key and current import session context."
150
+ ),
151
+ projectName: z.string().describe(
152
+ "Human-readable PrimeUI project name for confirmations in chat."
153
+ ),
154
+ metadata: z.record(z.unknown()).describe(
155
+ "Additional project metadata from PrimeUI. May include project-description and other context fields."
156
+ ),
157
+ pages: z.array(pageSchema).describe(
158
+ "All pages visible for this project context. Filter by user request and isReadyToExport before creating export."
159
+ )
160
+ },
161
+ annotations: {
162
+ readOnlyHint: true,
163
+ destructiveHint: false,
164
+ idempotentHint: true,
165
+ openWorldHint: true
166
+ }
167
+ };
168
+ var toolListExports = {
169
+ title: "PrimeUI Export List",
170
+ 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.
171
+
172
+ WHEN TO USE: This is an OPTIONAL helper tool, NOT part of the main import flow. Use it only when:
173
+ - Something went wrong with an export and you need to check its status.
174
+ - The user explicitly references a previous export by date or context.
175
+ - You need to verify whether an export initiated via PrimeUI Studio UI has completed.
176
+
177
+ Note: exports can be created both through primeui_create_export AND by the user directly in PrimeUI Studio UI. This tool shows all of them.
178
+
179
+ Do NOT use this tool as a substitute for primeui_create_export. Always create a fresh export unless the user explicitly asks to use a previous one.
180
+
181
+ ${WORKFLOW_SUMMARY}`,
182
+ inputSchema: {},
183
+ outputSchema: {
184
+ exports: z.array(exportItemSchema).describe(
185
+ "Available exports sorted by API policy. Use item.id to download a specific prior export if user asks."
186
+ )
187
+ },
188
+ annotations: {
189
+ readOnlyHint: true,
190
+ destructiveHint: false,
191
+ idempotentHint: true,
192
+ openWorldHint: true
193
+ }
194
+ };
195
+ var toolCreateExport = {
196
+ title: "PrimeUI Create Export",
197
+ description: `Create a new export snapshot from the current PrimeUI project state and wait for completion. Returns export ID and the list of pages included in the export.
198
+
199
+ WHEN TO USE: Call this AFTER the user has confirmed which pages they want to import via primeui_get_project_info. This tool is REQUIRED before primeui_download_export, unless the user explicitly and directly asked to download a specific previous export.
200
+
201
+ AFTER CALLING:
202
+ - Verify that all user-selected pages are present in the export result AND have isReadyToExport: true.
203
+ - If path mismatch pages were flagged during project-info reconciliation, ensure they are included for deeper comparison after download.
204
+ - If any required page is missing or not ready, report and pause for user decision before proceeding.
205
+ - Use the returned export ID to call primeui_download_export.
206
+
207
+ ${WORKFLOW_SUMMARY}`,
208
+ inputSchema: {},
209
+ outputSchema: {
210
+ export: z.object({
211
+ id: z.string().describe(
212
+ "Freshly created export identifier. Use this as primeui_download_export input.id."
213
+ ),
214
+ status: exportStatusSchema
215
+ }).describe("Created export summary."),
216
+ pages: z.array(pageSchema).describe(
217
+ "Page snapshot associated with this export operation. Validate requested pages here before download/import."
218
+ )
219
+ },
220
+ annotations: {
221
+ readOnlyHint: false,
222
+ destructiveHint: false,
223
+ idempotentHint: false,
224
+ openWorldHint: true
225
+ }
226
+ };
227
+ var toolDownloadExport = {
228
+ title: "PrimeUI Download Export",
229
+ description: `Download a completed export into .primeui/temp/exports/[exportId]. Downloads and extracts the full export as a standalone Next.js project into the temp directory. This does NOT modify the user's project - files land only in .primeui/temp/.
230
+
231
+ Do NOT call this as the first step. Requires export ID from primeui_create_export. Start with primeui_get_project_info instead.
232
+
233
+ WHEN TO USE: Call this AFTER primeui_create_export has returned a completed export. Requires export ID from primeui_create_export so MCP can use the matching local pages snapshot for sidecar manifest generation.
234
+
235
+ NOTE:
236
+ - Download of an export ID without local pages snapshot may fail.
237
+ - For previous exports, create a fresh export first when possible.
238
+
239
+ AFTER DOWNLOAD:
240
+ - Use manifestPath from this response as the contract for page slug/path mapping in copy operations.
241
+ - For each selected page, call primeui_copy_page with:
242
+ - originPageSlug (required),
243
+ - actualPageSlug (optional, if route remap is needed).
244
+ - If there are unresolved conflicts in copy report, stop and ask the user.
245
+ - Only after all requested pages are copied and reconciled, call primeui_clear_temp.
246
+
247
+ ${WORKFLOW_SUMMARY}`,
248
+ inputSchema: {
249
+ id: z.string().describe(
250
+ "Export identifier to download. Prefer export.id from primeui_create_export; use primeui_list_exports only on explicit user request."
251
+ )
252
+ },
253
+ outputSchema: {
254
+ exportId: z.string().describe(
255
+ "Downloaded export identifier. Should match the requested input.id for traceability."
256
+ ),
257
+ projectPath: z.string().describe(
258
+ "Absolute local path to extracted export root in .primeui/temp/exports/[exportId]. Read files from here only."
259
+ ),
260
+ manifestPath: z.string().describe(
261
+ "Absolute path to sidecar export manifest file (.primeui/temp/exports/[exportId].manifest.json). primeui_copy_page depends on this file."
262
+ ),
263
+ pages: z.array(pageSchema).describe(
264
+ "Page descriptors for import decisions. Use pagePath/componentsPath from each item when copying code into user project."
265
+ )
266
+ },
267
+ annotations: {
268
+ readOnlyHint: false,
269
+ destructiveHint: false,
270
+ idempotentHint: true,
271
+ openWorldHint: true
272
+ }
273
+ };
274
+ var toolCopyPage = {
275
+ title: "PrimeUI Copy Page",
276
+ description: `Safely copy one page from downloaded export into the user's local project. Works only with local files in .primeui/temp/exports and does NOT call PrimeUI API.
277
+
278
+ WHEN TO USE:
279
+ - Call this AFTER primeui_download_export.
280
+ - Call once per selected page.
281
+ - Use actualPageSlug only when user wants to remap route during import.
282
+
283
+ BEHAVIOR:
284
+ - Reads export sidecar manifest to resolve originPageSlug -> pagePath/componentsPath.
285
+ - Copies page file + full page components folder + recursively discovered internal dependencies.
286
+ - Never overwrites existing conflicting files.
287
+ - Reports:
288
+ a) copied new files,
289
+ b) identical existing files,
290
+ c) conflicting files with diff,
291
+ d) missing dependencies added to package.json,
292
+ e) dependency version conflicts (report-only, no auto upgrade).
293
+ - Adds only missing dependencies to package.json (dependencies/devDependencies/peerDependencies).
294
+ - Never updates existing dependency versions and never runs install commands.
295
+
296
+ ${WORKFLOW_SUMMARY}`,
297
+ inputSchema: {
298
+ originPageSlug: z.string().describe(
299
+ "Source page slug from PrimeUI project pages list, for example '/', '/pricing', '/docs'."
300
+ ),
301
+ actualPageSlug: z.string().optional().describe(
302
+ "Optional destination slug in user's project. If omitted, originPageSlug is used."
303
+ )
304
+ },
305
+ 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(
314
+ "Destination components directory relative to user project root."
315
+ ),
316
+ newFiles: z.array(fileTransferSchema).describe("New files copied into user project without conflicts."),
317
+ identicalFiles: z.array(fileTransferSchema).describe(
318
+ "Existing files in user project that are byte-identical to export files."
319
+ ),
320
+ conflictFiles: z.array(conflictFileSchema).describe(
321
+ "Files that already exist and differ from export version. Never overwritten."
322
+ ),
323
+ addedDependencies: z.array(dependencyToAddSchema).describe(
324
+ "Dependencies that were missing and have been added to user's package.json."
325
+ ),
326
+ dependenciesVersionConflicts: z.array(dependencyVersionConflictSchema).describe(
327
+ "Dependencies with version mismatch between export and user project. Report only."
328
+ ),
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.")
336
+ })
337
+ },
338
+ annotations: {
339
+ readOnlyHint: false,
340
+ destructiveHint: false,
341
+ idempotentHint: false,
342
+ openWorldHint: false
343
+ }
344
+ };
345
+ var toolClearTemp = {
346
+ title: "PrimeUI Clear Temp",
347
+ description: `Delete all files in .primeui/temp/ and recreate empty temp structure.
348
+
349
+ Do NOT call this unless ALL requested pages have been successfully imported into the user's project.
350
+
351
+ WHEN TO USE: Call this as the FINAL step after ALL requested pages have been successfully imported into the user's project. Do not call this between page imports - wait until all pages are done.
352
+
353
+ Safe to call multiple times. Only affects .primeui/temp/ directory - never touches the user's project files.
354
+
355
+ ${WORKFLOW_SUMMARY}`,
356
+ inputSchema: {},
357
+ outputSchema: {
358
+ success: z.boolean().describe(
359
+ "True when temp cleanup completed and baseline temp structure was recreated."
360
+ )
361
+ },
362
+ annotations: {
363
+ readOnlyHint: false,
364
+ destructiveHint: false,
365
+ idempotentHint: true,
366
+ openWorldHint: false
367
+ }
368
+ };
369
+
370
+ // src/server.ts
371
+ function okResult(title, data) {
372
+ return {
373
+ content: [{ type: "text", text: `${title} completed` }],
374
+ structuredContent: data
375
+ };
376
+ }
377
+ function errorResult(error) {
378
+ const message = error instanceof Error ? error.message : String(error);
379
+ return {
380
+ isError: true,
381
+ content: [{ type: "text", text: `primeui mcp error: ${message}` }]
382
+ };
383
+ }
384
+ function createPrimeUiMcpServer(source) {
385
+ const server = new McpServer(
386
+ {
387
+ name: "primeui-mcp-server",
388
+ version: "0.1.0"
389
+ },
390
+ {
391
+ instructions: initialInstructions
392
+ }
393
+ );
394
+ server.registerTool(
395
+ "primeui_get_project_info",
396
+ toolGetProjectInfo,
397
+ async () => {
398
+ try {
399
+ const info = await source.getProjectInfo();
400
+ return okResult("primeui_get_project_info", info);
401
+ } catch (error) {
402
+ return errorResult(error);
403
+ }
404
+ }
405
+ );
406
+ server.registerTool(
407
+ "primeui_list_exports",
408
+ toolListExports,
409
+ async () => {
410
+ try {
411
+ const exportsList = await source.listExports();
412
+ return okResult("primeui_list_exports", { exports: exportsList });
413
+ } catch (error) {
414
+ return errorResult(error);
415
+ }
416
+ }
417
+ );
418
+ server.registerTool(
419
+ "primeui_create_export",
420
+ toolCreateExport,
421
+ async () => {
422
+ try {
423
+ const result = await source.createExport();
424
+ return okResult("primeui_create_export", result);
425
+ } catch (error) {
426
+ return errorResult(error);
427
+ }
428
+ }
429
+ );
430
+ server.registerTool(
431
+ "primeui_download_export",
432
+ toolDownloadExport,
433
+ async ({ id }) => {
434
+ try {
435
+ const result = await source.downloadExportById(id);
436
+ return okResult("primeui_download_export", result);
437
+ } catch (error) {
438
+ return errorResult(error);
439
+ }
440
+ }
441
+ );
442
+ server.registerTool(
443
+ "primeui_clear_temp",
444
+ toolClearTemp,
445
+ async () => {
446
+ try {
447
+ await source.clearTemp();
448
+ return okResult("primeui_clear_temp", { success: true });
449
+ } catch (error) {
450
+ return errorResult(error);
451
+ }
452
+ }
453
+ );
454
+ server.registerTool(
455
+ "primeui_copy_page",
456
+ toolCopyPage,
457
+ async ({ originPageSlug, actualPageSlug }) => {
458
+ try {
459
+ const result = await source.copyPage(
460
+ originPageSlug,
461
+ actualPageSlug
462
+ );
463
+ return okResult("primeui_copy_page", result);
464
+ } catch (error) {
465
+ return errorResult(error);
466
+ }
467
+ }
468
+ );
469
+ return server;
470
+ }
471
+
472
+ // src/services/project-sync-service.ts
473
+ import path4 from "path";
474
+ import { readFile as readFile3 } from "fs/promises";
475
+
476
+ // src/lib/fs.ts
477
+ import { mkdir, rm, writeFile } from "fs/promises";
478
+ import path from "path";
479
+ import extractZipArchive from "extract-zip";
480
+ async function ensureDir(dirPath) {
481
+ await mkdir(dirPath, { recursive: true });
482
+ }
483
+ async function resetDir(dirPath) {
484
+ await rm(dirPath, { recursive: true, force: true });
485
+ await mkdir(dirPath, { recursive: true });
486
+ }
487
+ async function writeUtf8(filePath, content) {
488
+ await ensureDir(path.dirname(filePath));
489
+ await writeFile(filePath, content, "utf-8");
490
+ }
491
+ async function extractZip(zipPath, targetDir) {
492
+ await ensureDir(targetDir);
493
+ try {
494
+ await extractZipArchive(zipPath, { dir: targetDir });
495
+ } catch (error) {
496
+ const details = error instanceof Error ? error.message : String(error);
497
+ throw new Error(`Failed to extract zip at "${zipPath}". ${details}`);
498
+ }
499
+ }
500
+
501
+ // 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";
504
+
505
+ // src/lib/import-graph.ts
506
+ import { readFile, stat } from "fs/promises";
507
+ import { builtinModules } from "module";
508
+ import path2 from "path";
509
+ var INTERNAL_EXTENSIONS = [
510
+ ".ts",
511
+ ".tsx",
512
+ ".js",
513
+ ".jsx",
514
+ ".mjs",
515
+ ".cjs",
516
+ ".json"
517
+ ];
518
+ var PARSEABLE_EXTENSIONS = /* @__PURE__ */ new Set([
519
+ ".ts",
520
+ ".tsx",
521
+ ".js",
522
+ ".jsx",
523
+ ".mjs",
524
+ ".cjs"
525
+ ]);
526
+ var BUILTIN_MODULES = /* @__PURE__ */ new Set([
527
+ ...builtinModules,
528
+ ...builtinModules.map((item) => `node:${item}`)
529
+ ]);
530
+ var STATIC_IMPORT_RE = /\bimport\s+(?:type\s+)?(?:[\s\S]*?\sfrom\s+)?["']([^"']+)["']/g;
531
+ var EXPORT_FROM_RE = /\bexport\s+(?:[\s\S]*?\sfrom\s+)["']([^"']+)["']/g;
532
+ var DYNAMIC_IMPORT_RE = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
533
+ var REQUIRE_RE = /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g;
534
+ function collectSpecifiers(content) {
535
+ const results = /* @__PURE__ */ new Set();
536
+ let match = null;
537
+ for (const re of [
538
+ STATIC_IMPORT_RE,
539
+ EXPORT_FROM_RE,
540
+ DYNAMIC_IMPORT_RE,
541
+ REQUIRE_RE
542
+ ]) {
543
+ re.lastIndex = 0;
544
+ while ((match = re.exec(content)) !== null) {
545
+ const specifier = match[1]?.trim();
546
+ if (specifier) {
547
+ results.add(specifier);
548
+ }
549
+ }
550
+ }
551
+ return [...results];
552
+ }
553
+ function getExternalPackageName(specifier) {
554
+ if (!specifier || specifier.startsWith(".") || specifier.startsWith("/")) {
555
+ return null;
556
+ }
557
+ if (specifier.startsWith("node:")) {
558
+ return null;
559
+ }
560
+ if (specifier.startsWith("@/") || specifier.startsWith("@root/")) {
561
+ return null;
562
+ }
563
+ const parts = specifier.split("/");
564
+ if (parts.length === 0) {
565
+ return null;
566
+ }
567
+ if (specifier.startsWith("@")) {
568
+ if (parts.length < 2) {
569
+ return null;
570
+ }
571
+ return `${parts[0]}/${parts[1]}`;
572
+ }
573
+ return parts[0] ?? null;
574
+ }
575
+ async function pathExists(filePath) {
576
+ try {
577
+ await stat(filePath);
578
+ return true;
579
+ } catch {
580
+ return false;
581
+ }
582
+ }
583
+ async function resolveFileCandidate(candidateBase) {
584
+ const ext = path2.extname(candidateBase);
585
+ if (ext) {
586
+ if (await pathExists(candidateBase)) {
587
+ return candidateBase;
588
+ }
589
+ return null;
590
+ }
591
+ for (const extension of INTERNAL_EXTENSIONS) {
592
+ const withExtension = `${candidateBase}${extension}`;
593
+ if (await pathExists(withExtension)) {
594
+ return withExtension;
595
+ }
596
+ }
597
+ if (await pathExists(candidateBase)) {
598
+ const stats = await stat(candidateBase);
599
+ if (stats.isFile()) {
600
+ return candidateBase;
601
+ }
602
+ if (stats.isDirectory()) {
603
+ for (const extension of INTERNAL_EXTENSIONS) {
604
+ const indexCandidate = path2.join(candidateBase, `index${extension}`);
605
+ if (await pathExists(indexCandidate)) {
606
+ return indexCandidate;
607
+ }
608
+ }
609
+ }
610
+ }
611
+ return null;
612
+ }
613
+ async function resolveImportToFile(projectRoot, importerFilePath, specifier) {
614
+ const externalPackageName = getExternalPackageName(specifier);
615
+ if (externalPackageName) {
616
+ if (!BUILTIN_MODULES.has(externalPackageName)) {
617
+ return {
618
+ kind: "external",
619
+ packageName: externalPackageName
620
+ };
621
+ }
622
+ return { kind: "unknown" };
623
+ }
624
+ let candidateBase = null;
625
+ if (specifier.startsWith("@/")) {
626
+ candidateBase = path2.join(projectRoot, "src", specifier.slice(2));
627
+ } else if (specifier.startsWith("@root/")) {
628
+ candidateBase = path2.join(projectRoot, specifier.slice(6));
629
+ } else if (specifier.startsWith(".")) {
630
+ candidateBase = path2.resolve(path2.dirname(importerFilePath), specifier);
631
+ } else if (specifier.startsWith("/")) {
632
+ candidateBase = path2.join(projectRoot, specifier.slice(1));
633
+ } else {
634
+ return { kind: "unknown" };
635
+ }
636
+ const resolved = await resolveFileCandidate(candidateBase);
637
+ if (!resolved) {
638
+ return { kind: "unknown" };
639
+ }
640
+ return {
641
+ kind: "internal",
642
+ filePath: resolved
643
+ };
644
+ }
645
+ function shouldParseFile(filePath) {
646
+ return PARSEABLE_EXTENSIONS.has(path2.extname(filePath).toLowerCase());
647
+ }
648
+ async function buildImportGraph(input) {
649
+ const projectRoot = path2.resolve(input.projectRoot);
650
+ const queue = input.entryFiles.map((filePath) => path2.resolve(filePath));
651
+ const visited = /* @__PURE__ */ new Set();
652
+ const internalFiles = /* @__PURE__ */ new Set();
653
+ const externalPackages = /* @__PURE__ */ new Set();
654
+ while (queue.length > 0) {
655
+ const currentFilePath = queue.shift();
656
+ if (!currentFilePath || visited.has(currentFilePath)) {
657
+ continue;
658
+ }
659
+ visited.add(currentFilePath);
660
+ internalFiles.add(currentFilePath);
661
+ if (!shouldParseFile(currentFilePath)) {
662
+ continue;
663
+ }
664
+ let content = "";
665
+ try {
666
+ content = await readFile(currentFilePath, "utf-8");
667
+ } catch {
668
+ continue;
669
+ }
670
+ const specifiers = collectSpecifiers(content);
671
+ for (const specifier of specifiers) {
672
+ const resolved = await resolveImportToFile(
673
+ projectRoot,
674
+ currentFilePath,
675
+ specifier
676
+ );
677
+ if (resolved.kind === "internal") {
678
+ if (!visited.has(resolved.filePath)) {
679
+ queue.push(resolved.filePath);
680
+ }
681
+ continue;
682
+ }
683
+ if (resolved.kind === "external") {
684
+ externalPackages.add(resolved.packageName);
685
+ }
686
+ }
687
+ }
688
+ return {
689
+ internalFiles: [...internalFiles].sort(),
690
+ externalPackages: [...externalPackages].sort()
691
+ };
692
+ }
693
+
694
+ // src/lib/page-paths.ts
695
+ var WEBSITE_APP_ROOT = "src/app/(website)";
696
+ var PAGE_COMPONENTS_ROOT = "src/components/pages";
697
+ var normalizeSlug = (slug) => {
698
+ const trimmed = slug.trim();
699
+ if (!trimmed || trimmed === "/") {
700
+ return "/";
701
+ }
702
+ const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
703
+ const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, "");
704
+ return withoutTrailingSlash || "/";
705
+ };
706
+ var slugToFolderPath = (normalizedSlug) => {
707
+ if (normalizedSlug === "/") {
708
+ return "";
709
+ }
710
+ return normalizedSlug.slice(1);
711
+ };
712
+ var toPathSegment = (value) => {
713
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9/-]+/g, "-");
714
+ const compact = normalized.replace(/-+/g, "-").replace(/^-|-$/g, "");
715
+ return compact || "page";
716
+ };
717
+ var buildWebsitePagePath = (normalizedSlug) => {
718
+ if (normalizedSlug === "/") {
719
+ return `${WEBSITE_APP_ROOT}/page.tsx`;
720
+ }
721
+ return `${WEBSITE_APP_ROOT}${normalizedSlug}/page.tsx`;
722
+ };
723
+ var buildComponentsPath = (folderPath) => `${PAGE_COMPONENTS_ROOT}/${folderPath}`;
724
+ var resolveBlogSectionRoot = (slugFolderPath) => {
725
+ if (!slugFolderPath) {
726
+ return "blog";
727
+ }
728
+ const segments = slugFolderPath.split("/").filter(Boolean);
729
+ if (segments.length <= 1) {
730
+ return "blog";
731
+ }
732
+ return segments.slice(0, -1).join("/");
733
+ };
734
+ function resolvePrimeUiPageExportPaths({
735
+ pageType,
736
+ slug
737
+ }) {
738
+ const normalizedSlug = normalizeSlug(slug);
739
+ const slugFolderPath = slugToFolderPath(normalizedSlug);
740
+ switch (pageType) {
741
+ case "landing": {
742
+ const componentsFolder = slugFolderPath || "home";
743
+ return {
744
+ pagePath: buildWebsitePagePath(normalizedSlug),
745
+ componentsPath: buildComponentsPath(componentsFolder)
746
+ };
747
+ }
748
+ case "pricing":
749
+ case "contact-us": {
750
+ const componentsFolder = slugFolderPath || pageType;
751
+ return {
752
+ pagePath: buildWebsitePagePath(normalizedSlug),
753
+ componentsPath: buildComponentsPath(componentsFolder)
754
+ };
755
+ }
756
+ case "docs": {
757
+ const docsFolder = slugFolderPath || "docs";
758
+ return {
759
+ pagePath: `${WEBSITE_APP_ROOT}/${docsFolder}/[[...slug]]/page.tsx`,
760
+ componentsPath: buildComponentsPath("docs")
761
+ };
762
+ }
763
+ case "blogIndex": {
764
+ const blogFolder = slugFolderPath || "blog";
765
+ return {
766
+ pagePath: `${WEBSITE_APP_ROOT}/${blogFolder}/page.tsx`,
767
+ componentsPath: buildComponentsPath("blog")
768
+ };
769
+ }
770
+ case "blogPost": {
771
+ const blogSectionRoot = resolveBlogSectionRoot(slugFolderPath);
772
+ return {
773
+ pagePath: `${WEBSITE_APP_ROOT}/${blogSectionRoot}/[slug]/page.tsx`,
774
+ componentsPath: buildComponentsPath("blog")
775
+ };
776
+ }
777
+ case "legal":
778
+ return {
779
+ pagePath: buildWebsitePagePath(normalizedSlug),
780
+ componentsPath: buildComponentsPath("legal")
781
+ };
782
+ default: {
783
+ const fallbackFolder = slugFolderPath || toPathSegment(pageType);
784
+ return {
785
+ pagePath: buildWebsitePagePath(normalizedSlug),
786
+ componentsPath: buildComponentsPath(fallbackFolder)
787
+ };
788
+ }
789
+ }
790
+ }
791
+
792
+ // src/lib/text-diff.ts
793
+ var DEFAULT_MAX_DIFF_CHARS = 16e3;
794
+ var MAX_LCS_CELLS = 2e6;
795
+ function computeOps(oldLines, newLines) {
796
+ const oldLength = oldLines.length;
797
+ const newLength = newLines.length;
798
+ const table = Array.from(
799
+ { length: oldLength + 1 },
800
+ () => Array(newLength + 1).fill(0)
801
+ );
802
+ for (let i2 = oldLength - 1; i2 >= 0; i2 -= 1) {
803
+ for (let j2 = newLength - 1; j2 >= 0; j2 -= 1) {
804
+ if (oldLines[i2] === newLines[j2]) {
805
+ table[i2][j2] = table[i2 + 1][j2 + 1] + 1;
806
+ } else {
807
+ table[i2][j2] = Math.max(table[i2 + 1][j2], table[i2][j2 + 1]);
808
+ }
809
+ }
810
+ }
811
+ const ops = [];
812
+ let i = 0;
813
+ let j = 0;
814
+ while (i < oldLength && j < newLength) {
815
+ if (oldLines[i] === newLines[j]) {
816
+ ops.push({ kind: "equal", value: oldLines[i] ?? "" });
817
+ i += 1;
818
+ j += 1;
819
+ continue;
820
+ }
821
+ if (table[i + 1][j] >= table[i][j + 1]) {
822
+ ops.push({ kind: "delete", value: oldLines[i] ?? "" });
823
+ i += 1;
824
+ continue;
825
+ }
826
+ ops.push({ kind: "insert", value: newLines[j] ?? "" });
827
+ j += 1;
828
+ }
829
+ while (i < oldLength) {
830
+ ops.push({ kind: "delete", value: oldLines[i] ?? "" });
831
+ i += 1;
832
+ }
833
+ while (j < newLength) {
834
+ ops.push({ kind: "insert", value: newLines[j] ?? "" });
835
+ j += 1;
836
+ }
837
+ return ops;
838
+ }
839
+ function buildUnifiedDiff(input) {
840
+ const maxChars = input.maxChars ?? DEFAULT_MAX_DIFF_CHARS;
841
+ if (input.oldText === input.newText) {
842
+ return "";
843
+ }
844
+ const oldLines = input.oldText.split(/\r?\n/);
845
+ const newLines = input.newText.split(/\r?\n/);
846
+ const cells = (oldLines.length + 1) * (newLines.length + 1);
847
+ if (cells > MAX_LCS_CELLS) {
848
+ return [
849
+ `--- ${input.oldLabel}`,
850
+ `+++ ${input.newLabel}`,
851
+ "@@ diff omitted @@",
852
+ "Diff is too large to render safely."
853
+ ].join("\n");
854
+ }
855
+ const ops = computeOps(oldLines, newLines);
856
+ const output = [
857
+ `--- ${input.oldLabel}`,
858
+ `+++ ${input.newLabel}`,
859
+ `@@ -1,${oldLines.length} +1,${newLines.length} @@`
860
+ ];
861
+ for (const op of ops) {
862
+ if (op.kind === "equal") {
863
+ output.push(` ${op.value}`);
864
+ continue;
865
+ }
866
+ if (op.kind === "delete") {
867
+ output.push(`-${op.value}`);
868
+ continue;
869
+ }
870
+ output.push(`+${op.value}`);
871
+ }
872
+ const diff = output.join("\n");
873
+ if (diff.length <= maxChars) {
874
+ return diff;
875
+ }
876
+ return `${diff.slice(0, maxChars)}
877
+ ... [diff truncated]`;
878
+ }
879
+
880
+ // src/services/page-copy-service.ts
881
+ var DEPENDENCY_SECTIONS = [
882
+ "dependencies",
883
+ "devDependencies",
884
+ "peerDependencies"
885
+ ];
886
+ var REWRITABLE_IMPORT_EXTENSIONS = /* @__PURE__ */ new Set([
887
+ ".ts",
888
+ ".tsx",
889
+ ".js",
890
+ ".jsx",
891
+ ".mjs",
892
+ ".cjs"
893
+ ]);
894
+ function normalizeSlug2(slug) {
895
+ const trimmed = slug.trim();
896
+ if (!trimmed || trimmed === "/") {
897
+ return "/";
898
+ }
899
+ const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
900
+ return withLeadingSlash.replace(/\/+$/, "") || "/";
901
+ }
902
+ function toPosixPath(value) {
903
+ return value.split(path3.sep).join("/");
904
+ }
905
+ function toProjectRelative(rootPath, absolutePath) {
906
+ return toPosixPath(path3.relative(rootPath, absolutePath));
907
+ }
908
+ function escapeRegExp(value) {
909
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
910
+ }
911
+ function isBinaryBuffer(buffer) {
912
+ const limit = Math.min(buffer.length, 8e3);
913
+ for (let index = 0; index < limit; index += 1) {
914
+ if (buffer[index] === 0) {
915
+ return true;
916
+ }
917
+ }
918
+ return false;
919
+ }
920
+ function stripSrcPrefix(relativePath) {
921
+ return relativePath.replace(/^src\//, "");
922
+ }
923
+ function buildImportRewritePlan(sourceComponentsPath, targetComponentsPath) {
924
+ const normalizedSource = toPosixPath(sourceComponentsPath);
925
+ const normalizedTarget = toPosixPath(targetComponentsPath);
926
+ if (normalizedSource === normalizedTarget) {
927
+ return null;
928
+ }
929
+ const sourceAtAliasPrefix = `@/${stripSrcPrefix(normalizedSource)}`;
930
+ const targetAtAliasPrefix = `@/${stripSrcPrefix(normalizedTarget)}`;
931
+ const sourceRootAliasPrefix = `@root/${normalizedSource}`;
932
+ const targetRootAliasPrefix = `@root/${normalizedTarget}`;
933
+ return {
934
+ sourceAtAliasPrefix,
935
+ targetAtAliasPrefix,
936
+ sourceRootAliasPrefix,
937
+ targetRootAliasPrefix
938
+ };
939
+ }
940
+ function replaceAliasPrefixInImportStrings(content, sourcePrefix, targetPrefix) {
941
+ const pattern = new RegExp(
942
+ `(["'\`])${escapeRegExp(sourcePrefix)}(?=\\/|\\1)`,
943
+ "g"
944
+ );
945
+ return content.replace(pattern, `$1${targetPrefix}`);
946
+ }
947
+ function rewriteImportsForRemappedSlug(content, plan) {
948
+ const afterAtAlias = replaceAliasPrefixInImportStrings(
949
+ content,
950
+ plan.sourceAtAliasPrefix,
951
+ plan.targetAtAliasPrefix
952
+ );
953
+ return replaceAliasPrefixInImportStrings(
954
+ afterAtAlias,
955
+ plan.sourceRootAliasPrefix,
956
+ plan.targetRootAliasPrefix
957
+ );
958
+ }
959
+ function buildPlannedSourceBuffer(sourceBuffer, sourceFilePath, importRewritePlan) {
960
+ if (!importRewritePlan) {
961
+ return sourceBuffer;
962
+ }
963
+ const extension = path3.extname(sourceFilePath).toLowerCase();
964
+ if (!REWRITABLE_IMPORT_EXTENSIONS.has(extension)) {
965
+ return sourceBuffer;
966
+ }
967
+ if (isBinaryBuffer(sourceBuffer)) {
968
+ return sourceBuffer;
969
+ }
970
+ const sourceText = sourceBuffer.toString("utf-8");
971
+ const rewritten = rewriteImportsForRemappedSlug(sourceText, importRewritePlan);
972
+ if (rewritten === sourceText) {
973
+ return sourceBuffer;
974
+ }
975
+ return Buffer.from(rewritten, "utf-8");
976
+ }
977
+ async function readJsonFile(filePath) {
978
+ const content = await readFile2(filePath, "utf-8");
979
+ return JSON.parse(content);
980
+ }
981
+ async function fileExists(filePath) {
982
+ try {
983
+ await stat2(filePath);
984
+ return true;
985
+ } catch {
986
+ return false;
987
+ }
988
+ }
989
+ function isPageRecord(value) {
990
+ if (!value || typeof value !== "object") {
991
+ return false;
992
+ }
993
+ const maybe = value;
994
+ 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";
995
+ }
996
+ function parseManifest(value) {
997
+ if (!value || typeof value !== "object") {
998
+ throw new Error("Invalid export manifest format.");
999
+ }
1000
+ const maybe = value;
1001
+ if (maybe.schemaVersion !== 1 || typeof maybe.generatedAt !== "string" || typeof maybe.exportId !== "string" || typeof maybe.projectPath !== "string" || !Array.isArray(maybe.pages)) {
1002
+ throw new Error("Export manifest does not match expected schema.");
1003
+ }
1004
+ if (!maybe.pages.every(isPageRecord)) {
1005
+ throw new Error("Export manifest pages payload is invalid.");
1006
+ }
1007
+ return {
1008
+ schemaVersion: 1,
1009
+ generatedAt: maybe.generatedAt,
1010
+ exportId: maybe.exportId,
1011
+ projectPath: maybe.projectPath,
1012
+ pages: maybe.pages
1013
+ };
1014
+ }
1015
+ async function listFilesRecursively(dirPath) {
1016
+ const files = [];
1017
+ const stack = [dirPath];
1018
+ while (stack.length > 0) {
1019
+ const current = stack.pop();
1020
+ if (!current) {
1021
+ continue;
1022
+ }
1023
+ const entries = await readdir(current, { withFileTypes: true });
1024
+ for (const entry of entries) {
1025
+ const absolutePath = path3.join(current, entry.name);
1026
+ if (entry.isDirectory()) {
1027
+ stack.push(absolutePath);
1028
+ continue;
1029
+ }
1030
+ if (entry.isFile()) {
1031
+ files.push(absolutePath);
1032
+ }
1033
+ }
1034
+ }
1035
+ return files;
1036
+ }
1037
+ async function resolveSingleExportDirectory(exportsRoot) {
1038
+ const entries = await readdir(exportsRoot, { withFileTypes: true });
1039
+ const exportDirectories = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
1040
+ if (exportDirectories.length === 0) {
1041
+ throw new Error(
1042
+ "No downloaded exports found in .primeui/temp/exports. Run primeui_download_export first."
1043
+ );
1044
+ }
1045
+ if (exportDirectories.length > 1) {
1046
+ throw new Error(
1047
+ "Multiple export folders found in .primeui/temp/exports. Clear temp with primeui_clear_temp and download one export."
1048
+ );
1049
+ }
1050
+ const exportId = exportDirectories[0] ?? "";
1051
+ return {
1052
+ exportId,
1053
+ exportPath: path3.join(exportsRoot, exportId)
1054
+ };
1055
+ }
1056
+ function ensureSafeTargetPath(projectRoot, targetPath) {
1057
+ const relative = path3.relative(projectRoot, targetPath);
1058
+ if (relative.startsWith("..") || path3.isAbsolute(relative)) {
1059
+ throw new Error(
1060
+ `Refusing to write outside project root. Computed target: ${targetPath}`
1061
+ );
1062
+ }
1063
+ }
1064
+ function getDependencyVersion(packageJson, packageName) {
1065
+ for (const section of DEPENDENCY_SECTIONS) {
1066
+ const sectionData = packageJson[section];
1067
+ if (!sectionData || typeof sectionData !== "object") {
1068
+ continue;
1069
+ }
1070
+ const value = sectionData[packageName];
1071
+ if (typeof value === "string") {
1072
+ return {
1073
+ section,
1074
+ version: value
1075
+ };
1076
+ }
1077
+ }
1078
+ return null;
1079
+ }
1080
+ function getOrCreateDependencySection(packageJson, section) {
1081
+ const existing = packageJson[section];
1082
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
1083
+ const created = {};
1084
+ packageJson[section] = created;
1085
+ return created;
1086
+ }
1087
+ return existing;
1088
+ }
1089
+ function sortDependencySections(packageJson) {
1090
+ for (const section of DEPENDENCY_SECTIONS) {
1091
+ const sectionData = packageJson[section];
1092
+ if (!sectionData || typeof sectionData !== "object" || Array.isArray(sectionData)) {
1093
+ continue;
1094
+ }
1095
+ const sorted = Object.fromEntries(
1096
+ Object.entries(sectionData).sort(
1097
+ ([a], [b]) => a.localeCompare(b)
1098
+ )
1099
+ );
1100
+ packageJson[section] = sorted;
1101
+ }
1102
+ }
1103
+ function resolveTargetFilePath(input) {
1104
+ const {
1105
+ sourceFilePath,
1106
+ sourcePagePath,
1107
+ sourceComponentsPath,
1108
+ targetPagePath,
1109
+ targetComponentsPath,
1110
+ exportRoot,
1111
+ projectRoot
1112
+ } = input;
1113
+ if (sourceFilePath === sourcePagePath) {
1114
+ return targetPagePath;
1115
+ }
1116
+ const componentsPrefix = `${sourceComponentsPath}${path3.sep}`;
1117
+ if (sourceFilePath === sourceComponentsPath || sourceFilePath.startsWith(componentsPrefix)) {
1118
+ const relativeToComponents = path3.relative(
1119
+ sourceComponentsPath,
1120
+ sourceFilePath
1121
+ );
1122
+ return path3.join(targetComponentsPath, relativeToComponents);
1123
+ }
1124
+ const relativeToExport = path3.relative(exportRoot, sourceFilePath);
1125
+ if (relativeToExport.startsWith("..") || path3.isAbsolute(relativeToExport)) {
1126
+ throw new Error(`Source file is outside export root: ${sourceFilePath}`);
1127
+ }
1128
+ return path3.join(projectRoot, relativeToExport);
1129
+ }
1130
+ async function copyPageFromExport(input) {
1131
+ const normalizedOriginSlug = normalizeSlug2(input.originPageSlug);
1132
+ const normalizedActualSlug = normalizeSlug2(
1133
+ input.actualPageSlug ?? input.originPageSlug
1134
+ );
1135
+ const isSlugRemapped = normalizedActualSlug !== normalizedOriginSlug;
1136
+ const { exportId, exportPath } = await resolveSingleExportDirectory(
1137
+ input.exportsRoot
1138
+ );
1139
+ const manifestPath = path3.join(
1140
+ input.exportsRoot,
1141
+ `${exportId}.manifest.json`
1142
+ );
1143
+ if (!await fileExists(manifestPath)) {
1144
+ throw new Error(
1145
+ `Export manifest is missing at ${manifestPath}. Run primeui_download_export to regenerate it.`
1146
+ );
1147
+ }
1148
+ const manifest = parseManifest(await readJsonFile(manifestPath));
1149
+ if (manifest.exportId !== exportId) {
1150
+ throw new Error(
1151
+ `Manifest exportId mismatch: expected ${exportId}, got ${manifest.exportId}. Re-download export.`
1152
+ );
1153
+ }
1154
+ const page = manifest.pages.find(
1155
+ (item) => normalizeSlug2(item.slug) === normalizedOriginSlug
1156
+ );
1157
+ if (!page) {
1158
+ throw new Error(
1159
+ `Page not found in manifest for slug: ${normalizedOriginSlug}`
1160
+ );
1161
+ }
1162
+ const sourcePagePath = path3.join(exportPath, page.pagePath);
1163
+ const sourceComponentsPath = path3.join(exportPath, page.componentsPath);
1164
+ const sourcePageStats = await stat2(sourcePagePath).catch(() => null);
1165
+ if (!sourcePageStats?.isFile()) {
1166
+ throw new Error(`Source page file not found: ${sourcePagePath}`);
1167
+ }
1168
+ const sourceComponentsStats = await stat2(sourceComponentsPath).catch(
1169
+ () => null
1170
+ );
1171
+ if (!sourceComponentsStats?.isDirectory()) {
1172
+ throw new Error(
1173
+ `Source components folder not found: ${sourceComponentsPath}`
1174
+ );
1175
+ }
1176
+ const targetPaths = normalizedActualSlug === normalizedOriginSlug ? {
1177
+ pagePath: page.pagePath,
1178
+ componentsPath: page.componentsPath
1179
+ } : resolvePrimeUiPageExportPaths({
1180
+ pageType: page.pageType,
1181
+ slug: normalizedActualSlug
1182
+ });
1183
+ const targetPagePath = path3.join(input.projectRoot, targetPaths.pagePath);
1184
+ const targetComponentsPath = path3.join(
1185
+ input.projectRoot,
1186
+ targetPaths.componentsPath
1187
+ );
1188
+ const importRewritePlan = isSlugRemapped ? buildImportRewritePlan(page.componentsPath, targetPaths.componentsPath) : null;
1189
+ ensureSafeTargetPath(input.projectRoot, targetPagePath);
1190
+ ensureSafeTargetPath(input.projectRoot, targetComponentsPath);
1191
+ const componentFiles = await listFilesRecursively(sourceComponentsPath);
1192
+ const seedFiles = [sourcePagePath, ...componentFiles];
1193
+ const importGraph = await buildImportGraph({
1194
+ projectRoot: exportPath,
1195
+ entryFiles: seedFiles
1196
+ });
1197
+ const candidateFiles = [...new Set(importGraph.internalFiles)].sort(
1198
+ (a, b) => a.localeCompare(b)
1199
+ );
1200
+ const newFiles = [];
1201
+ const identicalFiles = [];
1202
+ const conflictFiles = [];
1203
+ for (const sourceFilePath of candidateFiles) {
1204
+ const targetFilePath = resolveTargetFilePath({
1205
+ sourceFilePath,
1206
+ sourcePagePath,
1207
+ sourceComponentsPath,
1208
+ targetPagePath,
1209
+ targetComponentsPath,
1210
+ exportRoot: exportPath,
1211
+ projectRoot: input.projectRoot
1212
+ });
1213
+ ensureSafeTargetPath(input.projectRoot, targetFilePath);
1214
+ const sourceBuffer = await readFile2(sourceFilePath);
1215
+ const plannedSourceBuffer = buildPlannedSourceBuffer(
1216
+ sourceBuffer,
1217
+ sourceFilePath,
1218
+ importRewritePlan
1219
+ );
1220
+ let targetBuffer = null;
1221
+ try {
1222
+ targetBuffer = await readFile2(targetFilePath);
1223
+ } catch {
1224
+ targetBuffer = null;
1225
+ }
1226
+ const sourceRelative = toProjectRelative(exportPath, sourceFilePath);
1227
+ const targetRelative = toProjectRelative(input.projectRoot, targetFilePath);
1228
+ if (!targetBuffer) {
1229
+ await ensureDir(path3.dirname(targetFilePath));
1230
+ await writeFile2(targetFilePath, plannedSourceBuffer);
1231
+ newFiles.push({
1232
+ sourcePath: sourceRelative,
1233
+ targetPath: targetRelative
1234
+ });
1235
+ continue;
1236
+ }
1237
+ if (plannedSourceBuffer.equals(targetBuffer)) {
1238
+ identicalFiles.push({
1239
+ sourcePath: sourceRelative,
1240
+ targetPath: targetRelative
1241
+ });
1242
+ continue;
1243
+ }
1244
+ const isBinary = isBinaryBuffer(plannedSourceBuffer) || isBinaryBuffer(targetBuffer);
1245
+ const diff = isBinary ? "Binary files differ." : buildUnifiedDiff({
1246
+ oldText: targetBuffer.toString("utf-8"),
1247
+ newText: plannedSourceBuffer.toString("utf-8"),
1248
+ oldLabel: `user/${targetRelative}`,
1249
+ newLabel: `export/${sourceRelative}`
1250
+ });
1251
+ conflictFiles.push({
1252
+ sourcePath: sourceRelative,
1253
+ targetPath: targetRelative,
1254
+ diff,
1255
+ isBinary
1256
+ });
1257
+ }
1258
+ const exportPackageJsonPath = path3.join(exportPath, "package.json");
1259
+ const userPackageJsonPath = path3.join(input.projectRoot, "package.json");
1260
+ if (!await fileExists(exportPackageJsonPath)) {
1261
+ throw new Error(`Export package.json not found: ${exportPackageJsonPath}`);
1262
+ }
1263
+ if (!await fileExists(userPackageJsonPath)) {
1264
+ throw new Error(`User package.json not found: ${userPackageJsonPath}`);
1265
+ }
1266
+ const exportPackageJson = await readJsonFile(
1267
+ exportPackageJsonPath
1268
+ );
1269
+ const userPackageJson = await readJsonFile(userPackageJsonPath);
1270
+ const addedDependencies = [];
1271
+ const dependenciesVersionConflicts = [];
1272
+ for (const packageName of importGraph.externalPackages) {
1273
+ const exportDependency = getDependencyVersion(
1274
+ exportPackageJson,
1275
+ packageName
1276
+ );
1277
+ if (!exportDependency) {
1278
+ continue;
1279
+ }
1280
+ const userDependency = getDependencyVersion(userPackageJson, packageName);
1281
+ if (!userDependency) {
1282
+ const sectionData = getOrCreateDependencySection(
1283
+ userPackageJson,
1284
+ exportDependency.section
1285
+ );
1286
+ sectionData[packageName] = exportDependency.version;
1287
+ addedDependencies.push({
1288
+ packageName,
1289
+ version: exportDependency.version,
1290
+ section: exportDependency.section
1291
+ });
1292
+ continue;
1293
+ }
1294
+ if (userDependency.version !== exportDependency.version) {
1295
+ dependenciesVersionConflicts.push({
1296
+ packageName,
1297
+ section: exportDependency.section,
1298
+ exportVersion: exportDependency.version,
1299
+ userVersion: userDependency.version
1300
+ });
1301
+ }
1302
+ }
1303
+ if (addedDependencies.length > 0) {
1304
+ sortDependencySections(userPackageJson);
1305
+ await writeFile2(
1306
+ userPackageJsonPath,
1307
+ `${JSON.stringify(userPackageJson, null, 2)}
1308
+ `,
1309
+ "utf-8"
1310
+ );
1311
+ }
1312
+ return {
1313
+ exportId,
1314
+ exportPath,
1315
+ originPageSlug: normalizedOriginSlug,
1316
+ actualPageSlug: normalizedActualSlug,
1317
+ sourcePagePath: page.pagePath,
1318
+ targetPagePath: targetPaths.pagePath,
1319
+ sourceComponentsPath: page.componentsPath,
1320
+ targetComponentsPath: targetPaths.componentsPath,
1321
+ newFiles,
1322
+ identicalFiles,
1323
+ conflictFiles,
1324
+ addedDependencies: addedDependencies.sort(
1325
+ (a, b) => a.packageName.localeCompare(b.packageName)
1326
+ ),
1327
+ dependenciesVersionConflicts: dependenciesVersionConflicts.sort(
1328
+ (a, b) => a.packageName.localeCompare(b.packageName)
1329
+ ),
1330
+ summary: {
1331
+ totalCandidateFiles: candidateFiles.length,
1332
+ copiedFiles: newFiles.length,
1333
+ identicalFiles: identicalFiles.length,
1334
+ conflictFiles: conflictFiles.length,
1335
+ addedDependencies: addedDependencies.length,
1336
+ dependenciesVersionConflicts: dependenciesVersionConflicts.length
1337
+ }
1338
+ };
1339
+ }
1340
+
1341
+ // src/services/project-sync-service.ts
1342
+ function isProjectPage(value) {
1343
+ if (!value || typeof value !== "object") {
1344
+ return false;
1345
+ }
1346
+ const maybe = value;
1347
+ 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
+ }
1349
+ function buildPagesSnapshotPath(exportsRoot, exportId) {
1350
+ return path4.join(exportsRoot, `${exportId}.pages.snapshot.json`);
1351
+ }
1352
+ function parsePagesSnapshot(value) {
1353
+ if (!value || typeof value !== "object") {
1354
+ throw new Error("Export pages snapshot is invalid.");
1355
+ }
1356
+ const maybe = value;
1357
+ if (maybe.schemaVersion !== 1 || typeof maybe.generatedAt !== "string" || typeof maybe.exportId !== "string" || !Array.isArray(maybe.pages)) {
1358
+ throw new Error("Export pages snapshot does not match expected schema.");
1359
+ }
1360
+ if (!maybe.pages.every(isProjectPage)) {
1361
+ throw new Error("Export pages snapshot has invalid pages payload.");
1362
+ }
1363
+ return {
1364
+ schemaVersion: 1,
1365
+ generatedAt: maybe.generatedAt,
1366
+ exportId: maybe.exportId,
1367
+ pages: maybe.pages
1368
+ };
1369
+ }
1370
+ var ProjectSyncService = class {
1371
+ provider;
1372
+ projectRoot;
1373
+ primeUiRoot;
1374
+ tempRoot;
1375
+ exportsRoot;
1376
+ constructor(options) {
1377
+ this.projectRoot = options.projectRoot;
1378
+ 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");
1382
+ }
1383
+ async getProjectInfo() {
1384
+ await this.ensureTempLayout();
1385
+ return this.provider.getProjectInfo();
1386
+ }
1387
+ async listExports() {
1388
+ await this.ensureTempLayout();
1389
+ return this.provider.listExports();
1390
+ }
1391
+ async createExport() {
1392
+ await this.ensureTempLayout();
1393
+ const result = await this.provider.createExport();
1394
+ const pagesSnapshotPath = buildPagesSnapshotPath(
1395
+ this.exportsRoot,
1396
+ result.export.id
1397
+ );
1398
+ const snapshot = {
1399
+ schemaVersion: 1,
1400
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1401
+ exportId: result.export.id,
1402
+ pages: result.pages
1403
+ };
1404
+ await writeUtf8(
1405
+ pagesSnapshotPath,
1406
+ `${JSON.stringify(snapshot, null, 2)}
1407
+ `
1408
+ );
1409
+ return result;
1410
+ }
1411
+ async downloadExportById(id) {
1412
+ await this.ensureTempLayout();
1413
+ const exportsList = await this.provider.listExports();
1414
+ const selected = exportsList.find((item) => item.id === id);
1415
+ if (!selected) {
1416
+ throw new Error(`Export not found: ${id}`);
1417
+ }
1418
+ const targetZipPath = path4.join(this.exportsRoot, `${id}.zip`);
1419
+ const targetProjectPath = path4.join(this.exportsRoot, id);
1420
+ await ensureDir(this.exportsRoot);
1421
+ await this.provider.downloadExportArchive(id, targetZipPath);
1422
+ await resetDir(targetProjectPath);
1423
+ await extractZip(targetZipPath, targetProjectPath);
1424
+ const pagesSnapshotPath = buildPagesSnapshotPath(this.exportsRoot, id);
1425
+ let pagesSnapshot;
1426
+ try {
1427
+ const snapshotRaw = JSON.parse(
1428
+ await readFile3(pagesSnapshotPath, "utf-8")
1429
+ );
1430
+ pagesSnapshot = parsePagesSnapshot(snapshotRaw);
1431
+ } catch (error) {
1432
+ const reason = error instanceof Error ? error.message : String(error);
1433
+ throw new Error(
1434
+ `Export pages snapshot is required for download id "${id}". Run primeui_create_export for this export before downloading. Details: ${reason}`
1435
+ );
1436
+ }
1437
+ if (pagesSnapshot.exportId !== id) {
1438
+ throw new Error(
1439
+ `Export pages snapshot mismatch for id "${id}". Run primeui_create_export and retry download.`
1440
+ );
1441
+ }
1442
+ const pages = pagesSnapshot.pages;
1443
+ const manifestPath = path4.join(this.exportsRoot, `${id}.manifest.json`);
1444
+ const manifest = {
1445
+ schemaVersion: 1,
1446
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1447
+ exportId: id,
1448
+ projectPath: targetProjectPath,
1449
+ pages
1450
+ };
1451
+ await writeUtf8(manifestPath, `${JSON.stringify(manifest, null, 2)}
1452
+ `);
1453
+ return {
1454
+ exportId: id,
1455
+ projectPath: targetProjectPath,
1456
+ manifestPath,
1457
+ pages
1458
+ };
1459
+ }
1460
+ async copyPage(originPageSlug, actualPageSlug) {
1461
+ await this.ensureTempLayout();
1462
+ return copyPageFromExport({
1463
+ projectRoot: this.projectRoot,
1464
+ exportsRoot: this.exportsRoot,
1465
+ originPageSlug,
1466
+ actualPageSlug
1467
+ });
1468
+ }
1469
+ async clearTemp() {
1470
+ await resetDir(this.tempRoot);
1471
+ await writeUtf8(path4.join(this.tempRoot, ".gitkeep"), "");
1472
+ await ensureDir(this.exportsRoot);
1473
+ }
1474
+ async ensureTempLayout() {
1475
+ await ensureDir(this.primeUiRoot);
1476
+ await ensureDir(this.tempRoot);
1477
+ await ensureDir(this.exportsRoot);
1478
+ await writeUtf8(path4.join(this.tempRoot, ".gitkeep"), "");
1479
+ }
1480
+ };
1481
+
1482
+ // src/sources/api-provider.ts
1483
+ import { createWriteStream } from "fs";
1484
+ import { unlink } from "fs/promises";
1485
+ import path5 from "path";
1486
+ import { Readable, Transform } from "stream";
1487
+ import { pipeline } from "stream/promises";
1488
+ import { z as z2 } from "zod";
1489
+ var DEFAULT_API_BASE_URL = "https://app.primeui.com/";
1490
+ var ZIP_CONTENT_TYPES = [
1491
+ "application/zip",
1492
+ "application/octet-stream",
1493
+ "application/x-zip-compressed"
1494
+ ];
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()
1504
+ });
1505
+ var projectInfoSchema = z2.object({
1506
+ projectId: z2.string(),
1507
+ projectName: z2.string(),
1508
+ metadata: z2.record(z2.unknown()),
1509
+ pages: z2.array(projectPageSchema)
1510
+ });
1511
+ var exportsResponseSchema = z2.object({
1512
+ exports: z2.array(
1513
+ z2.object({
1514
+ id: z2.string(),
1515
+ status: exportStatusSchema2,
1516
+ createdAt: z2.string().datetime({ offset: true })
1517
+ })
1518
+ )
1519
+ });
1520
+ var createExportResponseSchema = z2.object({
1521
+ export: z2.object({
1522
+ id: z2.string(),
1523
+ status: exportStatusSchema2
1524
+ }),
1525
+ pages: z2.array(projectPageSchema)
1526
+ });
1527
+ var PrimeUiApiContractError = class extends Error {
1528
+ constructor(endpoint, details) {
1529
+ super(`PrimeUI API contract mismatch for "${endpoint}": ${details}`);
1530
+ this.name = "PrimeUiApiContractError";
1531
+ }
1532
+ };
1533
+ var normalizeApiRoot = (rawBaseUrl) => {
1534
+ const normalizedBase = rawBaseUrl.trim() || DEFAULT_API_BASE_URL;
1535
+ const parsed = new URL(normalizedBase);
1536
+ const normalizedPath = parsed.pathname.replace(/\/+$/, "");
1537
+ if (normalizedPath.endsWith("/api/v1")) {
1538
+ parsed.pathname = `${normalizedPath}/`;
1539
+ return parsed.toString();
1540
+ }
1541
+ parsed.pathname = `${normalizedPath}/api/v1/`.replace(
1542
+ "//api/v1/",
1543
+ "/api/v1/"
1544
+ );
1545
+ return parsed.toString();
1546
+ };
1547
+ var isJsonContentType = (contentType) => contentType.includes("application/json") || contentType.includes("+json");
1548
+ var isZipContentType = (contentType) => ZIP_CONTENT_TYPES.some((allowedType) => contentType.includes(allowedType));
1549
+ var looksLikeZipArchive = (buffer) => {
1550
+ if (buffer.length < 4) {
1551
+ return false;
1552
+ }
1553
+ if (buffer[0] !== 80 || buffer[1] !== 75) {
1554
+ return false;
1555
+ }
1556
+ const signature = `${buffer[2]}:${buffer[3]}`;
1557
+ return signature === "3:4" || signature === "5:6" || signature === "7:8";
1558
+ };
1559
+ var createZipSignatureGuard = (endpoint) => {
1560
+ let signature = Buffer.alloc(0);
1561
+ let validated = false;
1562
+ return new Transform({
1563
+ transform(chunk, _encoding, callback) {
1564
+ const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1565
+ if (!validated) {
1566
+ const requiredBytes = Math.max(0, 4 - signature.length);
1567
+ if (requiredBytes > 0) {
1568
+ signature = Buffer.concat([
1569
+ signature,
1570
+ chunkBuffer.subarray(0, requiredBytes)
1571
+ ]);
1572
+ }
1573
+ if (signature.length >= 4) {
1574
+ if (!looksLikeZipArchive(signature)) {
1575
+ callback(
1576
+ new PrimeUiApiContractError(
1577
+ endpoint,
1578
+ "response body is not a valid zip archive"
1579
+ )
1580
+ );
1581
+ return;
1582
+ }
1583
+ validated = true;
1584
+ }
1585
+ }
1586
+ callback(null, chunkBuffer);
1587
+ },
1588
+ flush(callback) {
1589
+ if (!validated) {
1590
+ callback(
1591
+ new PrimeUiApiContractError(
1592
+ endpoint,
1593
+ "response body is not a valid zip archive"
1594
+ )
1595
+ );
1596
+ return;
1597
+ }
1598
+ callback();
1599
+ }
1600
+ });
1601
+ };
1602
+ var ApiProjectDataProvider = class {
1603
+ apiKey;
1604
+ apiRoot;
1605
+ constructor(options) {
1606
+ this.apiKey = options.apiKey;
1607
+ this.apiRoot = normalizeApiRoot(options.baseUrl ?? DEFAULT_API_BASE_URL);
1608
+ }
1609
+ async getProjectInfo() {
1610
+ return this.requestJson("project", projectInfoSchema);
1611
+ }
1612
+ async listExports() {
1613
+ const response = await this.requestJson(
1614
+ "project/exports",
1615
+ exportsResponseSchema
1616
+ );
1617
+ return response.exports;
1618
+ }
1619
+ async createExport() {
1620
+ return this.requestJson("project/exports", createExportResponseSchema, {
1621
+ method: "POST"
1622
+ });
1623
+ }
1624
+ /**
1625
+ * Consumer-side runtime contract for GET /api/v1/project/exports/:exportId/download.
1626
+ * Producer source of truth: apps/studio/src/app/api/v1/project/exports/[exportId]/download/route.ts
1627
+ * Keep content-type and archive format checks synchronized with the producer response.
1628
+ */
1629
+ async downloadExportArchive(exportId, destinationPath) {
1630
+ const endpoint = `project/exports/${encodeURIComponent(exportId)}/download`;
1631
+ const apiKey = this.requireApiKey();
1632
+ const response = await fetch(this.buildUrl(endpoint), {
1633
+ method: "GET",
1634
+ headers: {
1635
+ Authorization: `Bearer ${apiKey}`
1636
+ }
1637
+ });
1638
+ if (!response.ok) {
1639
+ const details = await this.readError(response);
1640
+ throw new Error(
1641
+ `PrimeUI API request failed (${response.status}) while downloading export "${exportId}": ${details}`
1642
+ );
1643
+ }
1644
+ const contentType = response.headers.get("content-type") ?? "";
1645
+ if (!isZipContentType(contentType)) {
1646
+ throw new PrimeUiApiContractError(
1647
+ endpoint,
1648
+ `expected zip content-type but got "${contentType || "unknown"}"`
1649
+ );
1650
+ }
1651
+ if (!response.body) {
1652
+ throw new PrimeUiApiContractError(endpoint, "response body is empty");
1653
+ }
1654
+ await ensureDir(path5.dirname(destinationPath));
1655
+ const zipStream = Readable.fromWeb(
1656
+ response.body
1657
+ );
1658
+ const signatureGuard = createZipSignatureGuard(endpoint);
1659
+ const fileStream = createWriteStream(destinationPath);
1660
+ try {
1661
+ await pipeline(zipStream, signatureGuard, fileStream);
1662
+ } catch (error) {
1663
+ await unlink(destinationPath).catch(() => void 0);
1664
+ throw error;
1665
+ }
1666
+ }
1667
+ buildUrl(endpoint) {
1668
+ return new URL(endpoint, this.apiRoot).toString();
1669
+ }
1670
+ async requestJson(endpoint, schema, requestInit = {}) {
1671
+ const apiKey = this.requireApiKey();
1672
+ const method = requestInit.method ?? "GET";
1673
+ const response = await fetch(this.buildUrl(endpoint), {
1674
+ ...requestInit,
1675
+ method,
1676
+ headers: {
1677
+ ...requestInit.headers ?? {},
1678
+ Authorization: `Bearer ${apiKey}`
1679
+ }
12
1680
  });
1681
+ if (!response.ok) {
1682
+ const details = await this.readError(response);
1683
+ throw new Error(
1684
+ `PrimeUI API request failed (${response.status}) for "${endpoint}": ${details}`
1685
+ );
1686
+ }
1687
+ const contentType = response.headers.get("content-type") ?? "";
1688
+ if (!isJsonContentType(contentType)) {
1689
+ throw new PrimeUiApiContractError(
1690
+ endpoint,
1691
+ `expected JSON content-type but got "${contentType || "unknown"}"`
1692
+ );
1693
+ }
1694
+ let payload;
1695
+ try {
1696
+ payload = await response.json();
1697
+ } catch {
1698
+ throw new PrimeUiApiContractError(
1699
+ endpoint,
1700
+ "response body is not valid JSON"
1701
+ );
1702
+ }
1703
+ const parsed = schema.safeParse(payload);
1704
+ if (!parsed.success) {
1705
+ const details = parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
1706
+ throw new PrimeUiApiContractError(endpoint, details);
1707
+ }
1708
+ return parsed.data;
1709
+ }
1710
+ async readError(response) {
1711
+ const contentType = response.headers.get("content-type") ?? "";
1712
+ try {
1713
+ if (isJsonContentType(contentType)) {
1714
+ const body = await response.json();
1715
+ if (body?.message && body?.code) {
1716
+ return `${body.code}: ${body.message}`;
1717
+ }
1718
+ if (body?.message) {
1719
+ return body.message;
1720
+ }
1721
+ } else {
1722
+ const bodyText = await response.text();
1723
+ if (bodyText.trim()) {
1724
+ return bodyText.trim();
1725
+ }
1726
+ }
1727
+ } catch {
1728
+ }
1729
+ return response.statusText || "Unknown error";
1730
+ }
1731
+ requireApiKey() {
1732
+ const apiKey = this.apiKey?.trim();
13
1733
  if (!apiKey) {
14
- console.error("[primeui-mcp] warning: PRIMEUI_API_KEY is not set; API-backed tools will fail until it is configured.");
1734
+ throw new Error("PRIMEUI_API_KEY is required to call PrimeUI API tools.");
15
1735
  }
16
- const syncService = new ProjectSyncService({ projectRoot, provider });
17
- const server = createPrimeUiMcpServer(syncService);
18
- const transport = new StdioServerTransport();
19
- await server.connect(transport);
20
- console.error(`[primeui-mcp] running for root: ${projectRoot}`);
1736
+ return apiKey;
1737
+ }
1738
+ };
1739
+
1740
+ // src/service.ts
1741
+ async function main() {
1742
+ const projectRoot = process.env.PRIMEUI_PROJECT_ROOT || process.cwd();
1743
+ const apiKey = process.env.PRIMEUI_API_KEY;
1744
+ const provider = new ApiProjectDataProvider({
1745
+ apiKey,
1746
+ baseUrl: process.env.PRIMEUI_API_BASE_URL
1747
+ });
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 });
1754
+ const server = createPrimeUiMcpServer(syncService);
1755
+ const transport = new StdioServerTransport();
1756
+ await server.connect(transport);
1757
+ console.error(`[primeui-mcp] running for root: ${projectRoot}`);
21
1758
  }
22
1759
  main().catch((error) => {
23
- console.error("[primeui-mcp] failed to start", error);
24
- process.exit(1);
1760
+ console.error("[primeui-mcp] failed to start", error);
1761
+ process.exit(1);
25
1762
  });
26
1763
  //# sourceMappingURL=service.js.map