@primeuicom/mcp 0.1.19 → 0.1.21

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,15 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/service.ts
4
- import path8 from "path";
5
- import { fileURLToPath } from "url";
6
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
5
 
6
+ // src/runtime.ts
7
+ import path7 from "path";
8
+
8
9
  // src/lib/project-link-config.ts
9
10
  import { readFile, stat } from "fs/promises";
10
11
  import path from "path";
11
12
  import { z } from "zod";
12
13
  var PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH = ".primeui/project.json";
14
+ var PrimeUiProjectConfigError = class extends Error {
15
+ code;
16
+ hint;
17
+ details;
18
+ constructor(options) {
19
+ super(options.message);
20
+ this.name = "PrimeUiProjectConfigError";
21
+ this.code = options.code;
22
+ this.hint = options.hint;
23
+ this.details = options.details ?? {};
24
+ }
25
+ };
26
+ function buildProjectRootHint() {
27
+ return "Pass an absolute projectRoot in tool input (for example: /path/to/project) or set PRIMEUI_PROJECT_ROOT.";
28
+ }
13
29
  var primeUiProjectConfigSchema = z.object({
14
30
  projectId: z.string().trim().min(1),
15
31
  apiKey: z.string().trim().min(1),
@@ -54,24 +70,39 @@ async function readPrimeUiProjectConfig(projectConfigPath) {
54
70
  try {
55
71
  projectConfigRaw = await readFile(projectConfigPath, "utf-8");
56
72
  } catch (error) {
57
- throw new Error(
58
- `[primeui-mcp] failed to read ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH}: ${error instanceof Error ? error.message : String(error)}`
59
- );
73
+ throw new PrimeUiProjectConfigError({
74
+ code: "PROJECT_CONFIG_READ_FAILED",
75
+ message: `[primeui-mcp] failed to read ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH}: ${error instanceof Error ? error.message : String(error)}`,
76
+ details: {
77
+ projectConfigPath
78
+ },
79
+ hint: buildProjectRootHint()
80
+ });
60
81
  }
61
82
  let projectConfigJson;
62
83
  try {
63
84
  projectConfigJson = JSON.parse(projectConfigRaw);
64
85
  } catch (error) {
65
- throw new Error(
66
- `[primeui-mcp] invalid JSON in ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH}: ${error instanceof Error ? error.message : String(error)}`
67
- );
86
+ throw new PrimeUiProjectConfigError({
87
+ code: "PROJECT_CONFIG_INVALID_JSON",
88
+ message: `[primeui-mcp] invalid JSON in ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH}: ${error instanceof Error ? error.message : String(error)}`,
89
+ details: {
90
+ projectConfigPath
91
+ },
92
+ hint: buildProjectRootHint()
93
+ });
68
94
  }
69
95
  const parsedConfig = primeUiProjectConfigSchema.safeParse(projectConfigJson);
70
96
  if (!parsedConfig.success) {
71
97
  const details = parsedConfig.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
72
- throw new Error(
73
- `[primeui-mcp] invalid ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} format: ${details}`
74
- );
98
+ throw new PrimeUiProjectConfigError({
99
+ code: "PROJECT_CONFIG_INVALID_FORMAT",
100
+ message: `[primeui-mcp] invalid ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} format: ${details}`,
101
+ details: {
102
+ projectConfigPath
103
+ },
104
+ hint: buildProjectRootHint()
105
+ });
75
106
  }
76
107
  return parsedConfig.data;
77
108
  }
@@ -87,38 +118,107 @@ function toUniqueResolvedDirs(directories) {
87
118
  return [...resolved];
88
119
  }
89
120
  function buildPrimeUiProjectSearchRoots(options) {
90
- return toUniqueResolvedDirs([options.cwd, ...options.fallbackCwds ?? []]);
121
+ return toUniqueResolvedDirs([...options.fallbackCwds ?? [], options.cwd]);
91
122
  }
92
123
  async function resolvePrimeUiProjectConfig(options) {
124
+ const toolProjectRoot = options.projectRootFromTool?.trim();
125
+ if (toolProjectRoot) {
126
+ const resolvedToolRoot = path.resolve(toolProjectRoot);
127
+ const toolProjectConfigPath = path.join(
128
+ resolvedToolRoot,
129
+ PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
130
+ );
131
+ if (!await fileExists(toolProjectConfigPath)) {
132
+ throw new PrimeUiProjectConfigError({
133
+ code: "PROJECT_CONFIG_NOT_FOUND",
134
+ message: `[primeui-mcp] ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} not found in provided projectRoot (${resolvedToolRoot}).`,
135
+ details: {
136
+ source: "tool",
137
+ projectRoot: resolvedToolRoot,
138
+ expectedConfigPath: toolProjectConfigPath,
139
+ cwd: options.cwd
140
+ },
141
+ hint: buildProjectRootHint()
142
+ });
143
+ }
144
+ const projectConfig2 = await readPrimeUiProjectConfig(toolProjectConfigPath);
145
+ return {
146
+ projectRoot: resolvedToolRoot,
147
+ projectConfigPath: toolProjectConfigPath,
148
+ projectConfig: projectConfig2
149
+ };
150
+ }
151
+ const stickyProjectRoot = options.projectRootFromSticky?.trim();
152
+ if (stickyProjectRoot) {
153
+ const resolvedStickyRoot = path.resolve(stickyProjectRoot);
154
+ const stickyProjectConfigPath = path.join(
155
+ resolvedStickyRoot,
156
+ PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
157
+ );
158
+ if (await fileExists(stickyProjectConfigPath)) {
159
+ const projectConfig2 = await readPrimeUiProjectConfig(
160
+ stickyProjectConfigPath
161
+ );
162
+ return {
163
+ projectRoot: resolvedStickyRoot,
164
+ projectConfigPath: stickyProjectConfigPath,
165
+ projectConfig: projectConfig2
166
+ };
167
+ }
168
+ }
93
169
  const envProjectRoot = options.projectRootFromEnv?.trim();
94
- let projectConfigPath;
95
170
  if (envProjectRoot) {
96
- projectConfigPath = path.join(
97
- path.resolve(envProjectRoot),
171
+ const resolvedEnvRoot = path.resolve(envProjectRoot);
172
+ const envProjectConfigPath = path.join(
173
+ resolvedEnvRoot,
98
174
  PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
99
175
  );
100
- } else {
101
- const searchRoots = buildPrimeUiProjectSearchRoots({
102
- cwd: options.cwd,
103
- fallbackCwds: options.fallbackCwds
104
- });
105
- for (const searchRoot of searchRoots) {
106
- const foundProjectConfigPath = await findPrimeUiProjectConfigPath(searchRoot);
107
- if (foundProjectConfigPath) {
108
- projectConfigPath = foundProjectConfigPath;
109
- break;
110
- }
176
+ if (!await fileExists(envProjectConfigPath)) {
177
+ throw new PrimeUiProjectConfigError({
178
+ code: "PROJECT_CONFIG_NOT_FOUND",
179
+ message: `[primeui-mcp] ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} not found in PRIMEUI_PROJECT_ROOT (${resolvedEnvRoot}).`,
180
+ details: {
181
+ source: "env",
182
+ envVar: "PRIMEUI_PROJECT_ROOT",
183
+ projectRoot: resolvedEnvRoot,
184
+ expectedConfigPath: envProjectConfigPath,
185
+ cwd: options.cwd
186
+ },
187
+ hint: buildProjectRootHint()
188
+ });
111
189
  }
112
- if (!projectConfigPath) {
113
- throw new Error(
114
- `[primeui-mcp] ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} not found. Searched from: ${searchRoots.join(", ")}. Set PRIMEUI_PROJECT_ROOT.`
115
- );
190
+ const projectConfig2 = await readPrimeUiProjectConfig(envProjectConfigPath);
191
+ return {
192
+ projectRoot: resolvedEnvRoot,
193
+ projectConfigPath: envProjectConfigPath,
194
+ projectConfig: projectConfig2
195
+ };
196
+ }
197
+ const searchRoots = buildPrimeUiProjectSearchRoots({
198
+ cwd: options.cwd,
199
+ fallbackCwds: options.fallbackCwds
200
+ });
201
+ let projectConfigPath;
202
+ for (const searchRoot of searchRoots) {
203
+ const foundProjectConfigPath = await findPrimeUiProjectConfigPath(searchRoot);
204
+ if (foundProjectConfigPath) {
205
+ projectConfigPath = foundProjectConfigPath;
206
+ break;
116
207
  }
117
208
  }
118
209
  if (!projectConfigPath) {
119
- throw new Error(
120
- `[primeui-mcp] ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} not found. Set PRIMEUI_PROJECT_ROOT.`
121
- );
210
+ throw new PrimeUiProjectConfigError({
211
+ code: "PROJECT_CONFIG_NOT_FOUND",
212
+ message: `[primeui-mcp] ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} not found. Searched from: ${searchRoots.join(
213
+ ", "
214
+ )}.`,
215
+ details: {
216
+ source: "search",
217
+ cwd: options.cwd,
218
+ searchedRoots: searchRoots
219
+ },
220
+ hint: buildProjectRootHint()
221
+ });
122
222
  }
123
223
  const projectRoot = path.dirname(path.dirname(projectConfigPath));
124
224
  const projectConfig = await readPrimeUiProjectConfig(projectConfigPath);
@@ -135,846 +235,52 @@ async function resolvePrimeUiApiKey(options) {
135
235
  }
136
236
  const projectConfigApiKey = options.projectConfig.apiKey.trim();
137
237
  if (!projectConfigApiKey) {
138
- throw new Error(
139
- "[primeui-mcp] PRIMEUI_API_KEY is missing in env and .primeui/project.json"
140
- );
238
+ throw new PrimeUiProjectConfigError({
239
+ code: "PROJECT_API_KEY_MISSING",
240
+ message: "[primeui-mcp] PRIMEUI_API_KEY is missing in env and .primeui/project.json",
241
+ hint: "Set PRIMEUI_API_KEY or ensure apiKey is present in .primeui/project.json."
242
+ });
141
243
  }
142
244
  return projectConfigApiKey;
143
245
  }
144
246
 
145
- // src/server.ts
146
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
147
-
148
- // src/instructions.ts
149
- import { z as z2 } from "zod/v3";
150
- var pageSchema = z2.object({
151
- id: z2.string().describe(
152
- "Stable PrimeUI page identifier. Use this to match the same page across tool responses."
153
- ),
154
- title: z2.string().describe(
155
- "Human-readable page title. Show this to the user when confirming which pages to import."
156
- ),
157
- slug: z2.string().describe(
158
- "PrimeUI route slug, for example '/', '/pricing', '/docs/getting-started'. Use to match user intent."
159
- ),
160
- pageType: z2.string().describe(
161
- "PrimeUI page type classification (for example landing, docs, pricing, blogIndex, legal)."
162
- ),
163
- isReadyToExport: z2.boolean().describe(
164
- "True when this page has an active export-ready variant. Only ready pages are expected in export output."
165
- ),
166
- pagePath: z2.string().describe(
167
- "Relative file path to the page source inside downloaded export root. Copy page code from this exact path."
168
- ),
169
- componentsPath: z2.string().describe(
170
- "Relative directory path inside downloaded export root where page-level components live. Use as dependency copy start."
171
- )
172
- }).describe(
173
- "PrimeUI page descriptor used by project and export tools. Paths are always relative to export project root."
174
- );
175
- var exportStatusSchema = z2.enum(["in_progress", "completed", "failed"]).describe(
176
- "Export lifecycle state. Use 'completed' before calling download_export."
177
- );
178
- var exportItemSchema = z2.object({
179
- id: z2.string().describe(
180
- "Export identifier. Pass this value to download_export input.id."
181
- ),
182
- status: exportStatusSchema,
183
- createdAt: z2.string().describe("Export creation timestamp in ISO-8601 UTC format.")
184
- }).describe("Single export record from PrimeUI export history.");
185
- var exportSummarySchema = z2.object({
186
- total: z2.number().describe("Total pages processed in this export run."),
187
- successful: z2.number().describe("Number of pages exported successfully."),
188
- failed: z2.number().describe("Number of pages that failed export.")
189
- });
190
- var exportComponentSchema = z2.object({
191
- componentKey: z2.string().describe("Global component key from export manifest."),
192
- enabled: z2.boolean().describe("True when this global component was exported."),
193
- files: z2.array(z2.string()).describe("Files associated with this global component."),
194
- message: z2.string().describe("Export status message for this global component.")
195
- });
196
- var exportPageManifestSchema = z2.object({
197
- success: z2.boolean().describe("True when page export succeeded in this export run."),
198
- message: z2.string().describe("Export status message for this page."),
199
- files: z2.array(z2.string()).describe(
200
- "Authoritative list of page-related files from PrimeUI export manifest, including shared project files required by this page."
201
- )
202
- });
203
- var exportPageSchema = z2.object({
204
- id: z2.string().describe("Stable PrimeUI page identifier for this export entry."),
205
- title: z2.string().optional().describe(
206
- "Optional page title from export manifest. Can be undefined for some legacy records."
207
- ),
208
- slug: z2.string().describe("PrimeUI page slug included in this export."),
209
- pageType: z2.string().describe("PrimeUI page type for routing/path resolution."),
210
- isReadyToExport: z2.literal(true).describe("Always true for pages included in export payload."),
211
- pagePath: z2.string().describe("Page source file path relative to export root."),
212
- componentsPath: z2.string().describe("Page components directory path relative to export root."),
213
- manifest: exportPageManifestSchema
214
- });
215
- var fileTransferSchema = z2.object({
216
- sourcePath: z2.string().describe("Relative source file path inside downloaded export root."),
217
- targetPath: z2.string().describe("Relative target file path inside user project root.")
218
- });
219
- var conflictFileSchema = fileTransferSchema.extend({
220
- diff: z2.string().describe(
221
- "Unified diff between user file and export file. For binary files the diff contains a plain message."
222
- ),
223
- isBinary: z2.boolean().describe("True if conflict comparison was treated as binary data.")
224
- });
225
- var dependencySectionSchema = z2.enum([
226
- "dependencies",
227
- "devDependencies",
228
- "peerDependencies"
229
- ]);
230
- var dependencyToAddSchema = z2.object({
231
- packageName: z2.string().describe("Dependency package name that was missing in user package.json."),
232
- version: z2.string().describe("Version from exported project's package.json."),
233
- section: dependencySectionSchema.describe(
234
- "package.json section where dependency should be added."
235
- )
236
- });
237
- var dependencyVersionConflictSchema = z2.object({
238
- packageName: z2.string().describe("Dependency package name with version mismatch."),
239
- section: dependencySectionSchema.describe(
240
- "Section where export expects this dependency."
241
- ),
242
- exportVersion: z2.string().describe("Version found in exported project's package.json."),
243
- userVersion: z2.string().describe("Version currently present in user's package.json.")
244
- });
245
- var pageDetailsSchema = pageSchema.extend({
246
- pageInstruction: z2.string().nullable().describe(
247
- "Current instruction text for the active page variant. Null when no active variant is set."
248
- )
249
- });
250
- var pageVariantSchema = z2.object({
251
- id: z2.string().describe("Active variant identifier."),
252
- name: z2.string().describe("Active variant display name.")
253
- }).nullable().describe(
254
- "Active page variant. Null when page has no active variant and components are unavailable."
255
- );
256
- var pageComponentSchema = z2.object({
257
- blockId: z2.string().describe("Stable block identifier. Use as primary component instance ID."),
258
- componentId: z2.string().describe("Component key from variant blocks payload (block.key)."),
259
- componentGroup: z2.string().describe("Component family/group normalized from component key."),
260
- slot: z2.string().nullable().describe("Optional slot value from block payload."),
261
- props: z2.record(z2.unknown()).nullable().describe("Raw block props payload from PrimeUI.")
262
- });
263
- var inspectPageReportRowSchema = z2.object({
264
- number: z2.number().describe("1-based row number matching report order."),
265
- componentGroup: z2.string().describe("Component family/group label."),
266
- componentId: z2.string().describe("Component key (block key)."),
267
- blockId: z2.string().describe("Primary component instance identifier for future operations."),
268
- title: z2.string().nullable().describe("Title extracted by MCP from component props."),
269
- description: z2.string().nullable().describe("Description extracted by MCP from component props.")
270
- });
271
- var healthOptionSchema = z2.object({
272
- key: z2.string().describe("Environment variable or diagnostic option name."),
273
- description: z2.string().describe("Human-readable explanation of what this option controls."),
274
- currentValue: z2.string().nullable().describe(
275
- "Current effective value for this option. Null when not set or intentionally hidden."
276
- )
277
- });
278
- var healthRuntimeSchema = z2.object({
279
- cwd: z2.string().describe("process.cwd() observed by the MCP server process."),
280
- initCwd: z2.string().nullable().describe("INIT_CWD environment value, if provided by the client runtime."),
281
- pwd: z2.string().nullable().describe("PWD environment value, if provided by the client runtime."),
282
- searchRoots: z2.array(z2.string()).describe(
283
- "Resolved directories used when searching for .primeui/project.json."
284
- ),
285
- rootsListFallbackCwds: z2.array(z2.string()).describe(
286
- "File-system roots derived from MCP roots/list and used as search fallbacks."
287
- )
288
- });
289
- var healthConfigSchema = z2.object({
290
- found: z2.boolean().describe("True when .primeui/project.json was found and parsed successfully."),
291
- projectRoot: z2.string().nullable().describe("Resolved PrimeUI project root directory (parent of .primeui)."),
292
- projectConfigPath: z2.string().nullable().describe("Absolute path to .primeui/project.json when found."),
293
- targetProjectPath: z2.string().nullable().describe("targetProjectPath value loaded from project config."),
294
- targetProjectRoot: z2.string().nullable().describe("Absolute target project root derived from targetProjectPath."),
295
- source: z2.enum(["env.PRIMEUI_PROJECT_ROOT", "search"]).describe("How project config lookup was resolved."),
296
- error: z2.string().nullable().describe("Human-readable resolution error when config is not available.")
297
- });
298
- var TRIGGER_PHRASES = [
299
- "import page from PrimeUI",
300
- "add page from PrimeUI",
301
- "sync pages",
302
- "pull page",
303
- "transfer page",
304
- "get page from PrimeUI",
305
- "bring page from PrimeUI",
306
- "export from PrimeUI"
307
- ].join(", ");
308
- var WORKFLOW_SUMMARY = `
309
- WORKFLOW ORDER (always follow this sequence):
310
- 1. clear_temp -> cleanup stale temp files from previous runs before starting new import flow
311
- 2. get_project_info -> discover PrimeUI pages
312
- 3. Reconcile PrimeUI pages with local project pages/routes/paths, then confirm target pages with user
313
- 4. inspect_page -> inspect selected page components and produce a structured comparison table
314
- 5. create_export -> generate export snapshot and local manifest with page file lists + global components
315
- 6. download_export -> download to temp directory
316
- 7. copy_page -> copy one page safely (repeat for each selected page)
317
- 8. Reconcile integration points and resolve reported conflicts, if any
318
- `.trim();
319
- var initialInstructions = `
320
- PrimeUI MCP enables importing pages from PrimeUI Studio into your local project.
321
-
322
- TRIGGER EXAMPLES (semantic intent, not exact phrase matching): ${TRIGGER_PHRASES}
323
-
324
- ${WORKFLOW_SUMMARY}
325
-
326
- CRITICAL RULES FOR PAGE IMPORT:
327
- - Import is PAGE-BY-PAGE only. Never import the entire project at once.
328
- - Use health_check to inspect runtime path/config resolution when setup issues occur.
329
- - Always call clear_temp at the very beginning of a new import flow to avoid stale export state.
330
- - After get_project_info, always compare PrimeUI pages against the user's existing local project pages before proposing imports.
331
- - Reconciliation report MUST include: page presence match, slug/pagePath/componentsPath path match, and proposed action per page.
332
- - Decision matrix:
333
- - PrimeUI + local page exists -> candidate for update/review.
334
- - PrimeUI exists, local missing -> import candidate.
335
- - Local exists, PrimeUI missing -> custom local page, no action unless user asks.
336
- - Both exist but path mismatch -> ambiguous, export + compare before deciding.
337
- - For targeted component-level analysis on a specific page, call inspect_page before export/copy.
338
- - After inspect_page returns report table, supplement that table with local project page state:
339
- mark what already exists, what is missing, and what differs.
340
- - Before creating export, ask user which pages to add/update (specific pages or all missing pages) and keep that choice in thread context.
341
- - create_export response includes:
342
- - page-level manifest files per page ('pages[].manifest.files'),
343
- - global components list ('export.components').
344
- Mention both to user after create_export, because this is the source of truth for copy decisions.
345
- - The downloaded export in .primeui/temp/ contains a full standalone Next.js project.
346
- Do NOT copy it wholesale.
347
- - Always call copy_page for each confirmed page instead of manual file copy.
348
- - copy_page performs safe copy only:
349
- - new files are copied,
350
- - identical files are reported,
351
- - conflicting files are NEVER overwritten and are returned with diff.
352
- - copy_page reads and validates files only from 'pages[].manifest.files' in local sidecar manifest.
353
- - copy_page may add only missing dependencies to user's package.json.
354
- It never upgrades existing dependency versions and never runs dependency installation.
355
- - After page copy operations, inspect integration points (navigation configs, link menus, route registries, page indexes).
356
- - If integration pattern is obvious, apply it and explicitly mark it in the report with "\u26A0 integration update".
357
- - If integration pattern is unclear, ask the user before editing integration files.
358
- `.trim();
359
- var toolHealthCheck = {
360
- title: "PrimeUI Health Check",
361
- description: `Diagnostic tool for MCP runtime setup. This tool MUST NOT fail: it always returns structured diagnostics even when project config is missing.
362
-
363
- WHEN TO USE:
364
- - Use this first when PrimeUI MCP tools fail due to project path/config issues.
365
- - Use this to verify cwd/env/roots behavior in different MCP clients (Codex, Claude Code, Inspector).
247
+ // src/services/project-sync-service.ts
248
+ import path5 from "path";
249
+ import { readFile as readFile4 } from "fs/promises";
366
250
 
367
- RETURNS:
368
- - Runtime paths (cwd, INIT_CWD, PWD, search roots),
369
- - roots/list fallback paths from client (if available),
370
- - Whether .primeui/project.json was found and parsed,
371
- - Effective resolved project root/config path (when available),
372
- - Current option values and fallback guidance.`,
373
- inputSchema: {},
374
- outputSchema: {
375
- status: z2.enum(["ok", "degraded"]).describe("Overall setup health. 'degraded' means config is unresolved."),
376
- runtime: healthRuntimeSchema,
377
- config: healthConfigSchema,
378
- options: z2.array(healthOptionSchema).describe("Environment and runtime options relevant for configuration.")
379
- },
380
- annotations: {
381
- readOnlyHint: true,
382
- destructiveHint: false,
383
- idempotentHint: true,
384
- openWorldHint: false
251
+ // src/lib/fs.ts
252
+ import { mkdir, rm, writeFile } from "fs/promises";
253
+ import path2 from "path";
254
+ import extractZipArchive from "extract-zip";
255
+ async function ensureDir(dirPath) {
256
+ await mkdir(dirPath, { recursive: true });
257
+ }
258
+ async function resetDir(dirPath) {
259
+ await rm(dirPath, { recursive: true, force: true });
260
+ await mkdir(dirPath, { recursive: true });
261
+ }
262
+ async function writeUtf8(filePath, content) {
263
+ await ensureDir(path2.dirname(filePath));
264
+ await writeFile(filePath, content, "utf-8");
265
+ }
266
+ async function extractZip(zipPath, targetDir) {
267
+ await ensureDir(targetDir);
268
+ try {
269
+ await extractZipArchive(zipPath, { dir: targetDir });
270
+ } catch (error) {
271
+ const details = error instanceof Error ? error.message : String(error);
272
+ throw new Error(`Failed to extract zip at "${zipPath}". ${details}`);
385
273
  }
386
- };
387
- var toolGetProjectInfo = {
388
- title: "PrimeUI Project Info",
389
- description: `ENTRY POINT for all PrimeUI import operations. Always start here. Returns project metadata and the full list of available pages.
390
-
391
- 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}.
274
+ }
392
275
 
393
- AFTER CALLING:
394
- - Scan the user's local project to discover existing pages/routes and related page component folders.
395
- - Build a reconciliation report between PrimeUI and local project with action labels:
396
- - Page exists in both PrimeUI and local project.
397
- - Page exists in PrimeUI but not in local project -> import candidate.
398
- - Page exists in local project but not in PrimeUI -> custom local page, no action by default.
399
- - Page exists in both but slug/pagePath/componentsPath mapping does not match -> ambiguous, requires export-based comparison before decision.
400
- - If user already requested specific pages, first verify those pages exist in PrimeUI and are isReadyToExport: true.
401
- - If any requested page is not found or not ready, report this immediately and ask for replacement page choices.
402
- - Ask the user which pages to add or update now (specific page list or all missing pages) and keep this decision in thread context.
403
- - Import works per page. Multiple pages are allowed, but each page transfer is handled explicitly.
404
- - Only after user decision is clear, proceed to create_export.
405
-
406
- ${WORKFLOW_SUMMARY}`,
407
- inputSchema: {},
408
- outputSchema: {
409
- projectId: z2.string().describe(
410
- "PrimeUI project identifier associated with the API key and current import session context."
411
- ),
412
- projectName: z2.string().describe(
413
- "Human-readable PrimeUI project name for confirmations in chat."
414
- ),
415
- metadata: z2.record(z2.unknown()).describe(
416
- "Additional project metadata from PrimeUI. May include project-description and other context fields."
417
- ),
418
- pages: z2.array(pageSchema).describe(
419
- "All pages visible for this project context. Filter by user request and isReadyToExport before creating export."
420
- )
421
- },
422
- annotations: {
423
- readOnlyHint: true,
424
- destructiveHint: false,
425
- idempotentHint: true,
426
- openWorldHint: true
427
- }
428
- };
429
- var toolInspectPage = {
430
- title: "PrimeUI Inspect Page",
431
- description: `Targeted inspection tool for one PrimeUI page. Returns page details, active variant (if present), raw components payload, and a formatted report table.
432
-
433
- WHEN TO USE:
434
- - Call this after get_project_info when user wants component-level analysis for a specific page.
435
- - Use this before export/copy when user asks to compare or selectively update page sections.
436
-
437
- AFTER CALLING:
438
- - Use report + reportRows to present the component table:
439
- Number (1-based), ComponentGroup (ComponentId), Title/Description, blockId.
440
- - Then supplement this table with the CURRENT local project page state:
441
- what already exists, what is missing, and what differs.
442
- - Do not assume componentId is unique. Use blockId as the primary component instance identifier.
443
-
444
- If page has no active variant, components is null (not empty array), and report explains why.
445
-
446
- ${WORKFLOW_SUMMARY}`,
447
- inputSchema: {
448
- pageSlug: z2.string().describe(
449
- "PrimeUI page slug to inspect, for example '/', '/pricing', '/docs/getting-started'."
450
- )
451
- },
452
- outputSchema: {
453
- page: pageDetailsSchema.describe(
454
- "Single PrimeUI page details resolved by slug with pageInstruction."
455
- ),
456
- variant: pageVariantSchema,
457
- components: z2.array(pageComponentSchema).nullable().describe(
458
- "Raw component instances from active variant. Null when page has no active variant."
459
- ),
460
- report: z2.string().describe(
461
- "Formatted report table generated by MCP from component payload (includes extracted title/description summary)."
462
- ),
463
- reportRows: z2.array(inspectPageReportRowSchema).describe(
464
- "Structured report rows with 1-based numbering and extracted title/description fields."
465
- )
466
- },
467
- annotations: {
468
- readOnlyHint: true,
469
- destructiveHint: false,
470
- idempotentHint: true,
471
- openWorldHint: true
472
- }
473
- };
474
- var toolListExports = {
475
- title: "PrimeUI Export List",
476
- 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 get_project_info instead.
477
-
478
- WHEN TO USE: This is an OPTIONAL helper tool, NOT part of the main import flow. Use it only when:
479
- - Something went wrong with an export and you need to check its status.
480
- - The user explicitly references a previous export by date or context.
481
- - You need to verify whether an export initiated via PrimeUI Studio UI has completed.
482
-
483
- Note: exports can be created both through create_export AND by the user directly in PrimeUI Studio UI. This tool shows all of them.
484
-
485
- Do NOT use this tool as a substitute for create_export. Always create a fresh export unless the user explicitly asks to use a previous one.
486
-
487
- ${WORKFLOW_SUMMARY}`,
488
- inputSchema: {},
489
- outputSchema: {
490
- exports: z2.array(exportItemSchema).describe(
491
- "Available exports sorted by API policy. Use item.id to download a specific prior export if user asks."
492
- )
493
- },
494
- annotations: {
495
- readOnlyHint: true,
496
- destructiveHint: false,
497
- idempotentHint: true,
498
- openWorldHint: true
499
- }
500
- };
501
- var toolCreateExport = {
502
- title: "PrimeUI Create Export",
503
- description: `Create a new export snapshot from the current PrimeUI project state and wait for completion. Returns strict export manifest payload from API, including page-level file manifests and global components.
504
-
505
- WHEN TO USE: Call this AFTER the user has confirmed which pages they want to import via get_project_info. This tool is REQUIRED before download_export, unless the user explicitly and directly asked to download a specific previous export.
506
-
507
- AFTER CALLING:
508
- - Verify that all user-selected pages are present in the export result AND have isReadyToExport: true.
509
- - If path mismatch pages were flagged during project-info reconciliation, ensure they are included for deeper comparison after download.
510
- - If any required page is missing or not ready, report and pause for user decision before proceeding.
511
- - Mention that global components are available in export.components and can be transferred separately if needed.
512
- - Local sidecar manifest is saved to .primeui/temp/exports/[exportId].manifest.json from this API payload.
513
- - Use the returned export ID to call download_export.
514
-
515
- ${WORKFLOW_SUMMARY}`,
516
- inputSchema: {},
517
- outputSchema: {
518
- export: z2.object({
519
- id: z2.string().describe(
520
- "Freshly created export identifier. Use this as download_export input.id."
521
- ),
522
- status: exportStatusSchema,
523
- createdAt: z2.string().describe("Export creation timestamp in ISO-8601 UTC format."),
524
- expiresAt: z2.string().nullable().describe(
525
- "Export expiration timestamp in ISO-8601 UTC format, or null when export has no explicit expiration."
526
- ),
527
- summary: exportSummarySchema.describe(
528
- "Export run summary with total/successful/failed counters."
529
- ),
530
- components: z2.array(exportComponentSchema).describe(
531
- "Global component export manifest. These components are independent from page-level manifests."
532
- )
533
- }).describe("Created export summary."),
534
- pages: z2.array(exportPageSchema).describe(
535
- "Strict page payload associated with this export, including per-page manifest files list."
536
- )
537
- },
538
- annotations: {
539
- readOnlyHint: false,
540
- destructiveHint: false,
541
- idempotentHint: false,
542
- openWorldHint: true
543
- }
544
- };
545
- var toolDownloadExport = {
546
- title: "PrimeUI Download Export",
547
- 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/.
548
-
549
- Do NOT call this as the first step. Requires export ID from create_export. Start with get_project_info instead.
550
-
551
- WHEN TO USE: Call this AFTER create_export has returned a completed export. Requires export ID from create_export so MCP can use the matching local sidecar manifest.
552
-
553
- NOTE:
554
- - Download of an export ID without local sidecar manifest will fail.
555
- - For previous exports, create a fresh export first when possible.
556
-
557
- AFTER DOWNLOAD:
558
- - Use manifestPath from this response as the contract for page slug/path mapping and manifest file lists in copy operations.
559
- - For each selected page, call copy_page with:
560
- - originPageSlug (required),
561
- - actualPageSlug (optional, if route remap is needed).
562
- - If there are unresolved conflicts in copy report, stop and ask the user.
563
- - Do not force cleanup at flow end; keep downloaded export files available for validation and follow-up checks.
564
-
565
- ${WORKFLOW_SUMMARY}`,
566
- inputSchema: {
567
- id: z2.string().describe(
568
- "Export identifier to download. Prefer export.id from create_export; use list_exports only on explicit user request."
569
- )
570
- },
571
- outputSchema: {
572
- exportId: z2.string().describe(
573
- "Downloaded export identifier. Should match the requested input.id for traceability."
574
- ),
575
- projectPath: z2.string().describe(
576
- "Absolute local path to extracted export root in .primeui/temp/exports/[exportId]. Read files from here only."
577
- ),
578
- manifestPath: z2.string().describe(
579
- "Absolute path to sidecar export manifest file (.primeui/temp/exports/[exportId].manifest.json) saved by create_export and required by copy_page."
580
- ),
581
- pages: z2.array(exportPageSchema).describe(
582
- "Page descriptors loaded from local sidecar export manifest for import decisions."
583
- )
584
- },
585
- annotations: {
586
- readOnlyHint: false,
587
- destructiveHint: false,
588
- idempotentHint: true,
589
- openWorldHint: true
590
- }
591
- };
592
- var toolCopyPage = {
593
- title: "PrimeUI Copy Page",
594
- 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.
595
-
596
- WHEN TO USE:
597
- - Call this AFTER download_export.
598
- - Call once per selected page.
599
- - Use actualPageSlug only when user wants to remap route during import.
600
-
601
- BEHAVIOR:
602
- - Reads export sidecar manifest to resolve originPageSlug -> pagePath/componentsPath.
603
- - Copies and validates files strictly from pages[].manifest.files for the selected page.
604
- - Never overwrites existing conflicting files.
605
- - Reports:
606
- a) copied new files,
607
- b) identical existing files,
608
- c) conflicting files with diff,
609
- d) missing dependencies added to package.json,
610
- e) dependency version conflicts (report-only, no auto upgrade).
611
- - Adds only missing dependencies to package.json (dependencies/devDependencies/peerDependencies).
612
- - Never updates existing dependency versions and never runs install commands.
613
-
614
- ${WORKFLOW_SUMMARY}`,
615
- inputSchema: {
616
- originPageSlug: z2.string().describe(
617
- "Source page slug from PrimeUI project pages list, for example '/', '/pricing', '/docs'."
618
- ),
619
- actualPageSlug: z2.string().optional().describe(
620
- "Optional destination slug in user's project. If omitted, originPageSlug is used."
621
- )
622
- },
623
- outputSchema: {
624
- exportId: z2.string().describe("Resolved local export identifier used for copy operation."),
625
- exportPath: z2.string().describe("Absolute path to local extracted export project root."),
626
- originPageSlug: z2.string().describe("Normalized source page slug requested for copy."),
627
- actualPageSlug: z2.string().describe("Normalized destination slug used for target route paths."),
628
- sourcePagePath: z2.string().describe("Source page path relative to export root."),
629
- targetPagePath: z2.string().describe("Destination page path relative to user project root."),
630
- sourceComponentsPath: z2.string().describe("Source components directory relative to export root."),
631
- targetComponentsPath: z2.string().describe(
632
- "Destination components directory relative to user project root."
633
- ),
634
- newFiles: z2.array(fileTransferSchema).describe("New files copied into user project without conflicts."),
635
- identicalFiles: z2.array(fileTransferSchema).describe(
636
- "Existing files in user project that are byte-identical to export files."
637
- ),
638
- conflictFiles: z2.array(conflictFileSchema).describe(
639
- "Files that already exist and differ from export version. Never overwritten."
640
- ),
641
- addedDependencies: z2.array(dependencyToAddSchema).describe(
642
- "Dependencies that were missing and have been added to user's package.json."
643
- ),
644
- dependenciesVersionConflicts: z2.array(dependencyVersionConflictSchema).describe(
645
- "Dependencies with version mismatch between export and user project. Report only."
646
- ),
647
- summary: z2.object({
648
- totalCandidateFiles: z2.number().describe(
649
- "Total number of candidate files considered for page copy from pages[].manifest.files."
650
- ),
651
- copiedFiles: z2.number().describe("Number of files copied as new."),
652
- identicalFiles: z2.number().describe("Number of files detected as identical."),
653
- conflictFiles: z2.number().describe("Number of conflicting files not copied."),
654
- addedDependencies: z2.number().describe("Count of dependencies added to package.json."),
655
- dependenciesVersionConflicts: z2.number().describe("Count of dependency version mismatches reported.")
656
- })
657
- },
658
- annotations: {
659
- readOnlyHint: false,
660
- destructiveHint: false,
661
- idempotentHint: false,
662
- openWorldHint: false
663
- }
664
- };
665
- var toolClearTemp = {
666
- title: "PrimeUI Clear Temp",
667
- description: `Delete all files in .primeui/temp/ and recreate empty temp structure.
668
-
669
- Call this at the START of a new import flow, before get_project_info/create_export, to reset stale temp state from previous runs.
670
-
671
- WHEN TO USE: Call once at the beginning of each new import flow. Do not call this between page imports in the same flow.
672
-
673
- Safe to call multiple times. Only affects .primeui/temp/ directory - never touches the user's project files.
674
-
675
- ${WORKFLOW_SUMMARY}`,
676
- inputSchema: {},
677
- outputSchema: {
678
- success: z2.boolean().describe(
679
- "True when temp cleanup completed and baseline temp structure was recreated."
680
- )
681
- },
682
- annotations: {
683
- readOnlyHint: false,
684
- destructiveHint: false,
685
- idempotentHint: true,
686
- openWorldHint: false
687
- }
688
- };
689
-
690
- // src/server.ts
691
- function okResult(title, data) {
692
- return {
693
- content: [{ type: "text", text: `${title} completed` }],
694
- structuredContent: data
695
- };
696
- }
697
- function errorResult(error) {
698
- const message = error instanceof Error ? error.message : String(error);
699
- return {
700
- isError: true,
701
- content: [{ type: "text", text: `primeui mcp error: ${message}` }]
702
- };
703
- }
704
- function createPrimeUiMcpServer(source) {
705
- const server = new McpServer(
706
- {
707
- name: "primeui-mcp-server",
708
- version: "0.1.0"
709
- },
710
- {
711
- instructions: initialInstructions
712
- }
713
- );
714
- server.registerTool(
715
- "health_check",
716
- toolHealthCheck,
717
- async () => {
718
- try {
719
- const health = await source.healthCheck();
720
- return okResult("health_check", health);
721
- } catch (error) {
722
- const message = error instanceof Error ? error.message : String(error);
723
- return okResult("health_check", {
724
- status: "degraded",
725
- runtime: {
726
- cwd: process.cwd(),
727
- initCwd: process.env.INIT_CWD?.trim() || null,
728
- pwd: process.env.PWD?.trim() || null,
729
- searchRoots: [process.cwd()],
730
- rootsListFallbackCwds: []
731
- },
732
- config: {
733
- found: false,
734
- projectRoot: null,
735
- projectConfigPath: null,
736
- targetProjectPath: null,
737
- targetProjectRoot: null,
738
- source: "search",
739
- error: `health_check fallback: ${message}`
740
- },
741
- options: []
742
- });
743
- }
744
- }
745
- );
746
- server.registerTool(
747
- "get_project_info",
748
- toolGetProjectInfo,
749
- async () => {
750
- try {
751
- const info = await source.getProjectInfo();
752
- return okResult("get_project_info", info);
753
- } catch (error) {
754
- return errorResult(error);
755
- }
756
- }
757
- );
758
- server.registerTool(
759
- "inspect_page",
760
- toolInspectPage,
761
- async ({ pageSlug }) => {
762
- try {
763
- const result = await source.inspectPage(pageSlug);
764
- return okResult("inspect_page", result);
765
- } catch (error) {
766
- return errorResult(error);
767
- }
768
- }
769
- );
770
- server.registerTool(
771
- "list_exports",
772
- toolListExports,
773
- async () => {
774
- try {
775
- const exportsList = await source.listExports();
776
- return okResult("list_exports", { exports: exportsList });
777
- } catch (error) {
778
- return errorResult(error);
779
- }
780
- }
781
- );
782
- server.registerTool(
783
- "create_export",
784
- toolCreateExport,
785
- async () => {
786
- try {
787
- const result = await source.createExport();
788
- return okResult("create_export", result);
789
- } catch (error) {
790
- return errorResult(error);
791
- }
792
- }
793
- );
794
- server.registerTool(
795
- "download_export",
796
- toolDownloadExport,
797
- async ({ id }) => {
798
- try {
799
- const result = await source.downloadExportById(id);
800
- return okResult("download_export", result);
801
- } catch (error) {
802
- return errorResult(error);
803
- }
804
- }
805
- );
806
- server.registerTool(
807
- "clear_temp",
808
- toolClearTemp,
809
- async () => {
810
- try {
811
- await source.clearTemp();
812
- return okResult("clear_temp", { success: true });
813
- } catch (error) {
814
- return errorResult(error);
815
- }
816
- }
817
- );
818
- server.registerTool(
819
- "copy_page",
820
- toolCopyPage,
821
- async ({ originPageSlug, actualPageSlug }) => {
822
- try {
823
- const result = await source.copyPage(
824
- originPageSlug,
825
- actualPageSlug
826
- );
827
- return okResult("copy_page", result);
828
- } catch (error) {
829
- return errorResult(error);
830
- }
831
- }
832
- );
833
- return server;
834
- }
835
-
836
- // src/services/health-check-service.ts
837
- import path2 from "path";
838
- function normalizeOptional(value) {
839
- const trimmed = value?.trim();
840
- return trimmed ? trimmed : null;
841
- }
842
- function buildHealthOptions(input) {
843
- return [
844
- {
845
- key: "PRIMEUI_PROJECT_ROOT",
846
- description: "Optional absolute root containing .primeui/project.json. Use as explicit override.",
847
- currentValue: input.projectRootFromEnv
848
- },
849
- {
850
- key: "PRIMEUI_API_BASE_URL",
851
- description: "Optional PrimeUI API base URL override.",
852
- currentValue: input.apiBaseUrlFromEnv
853
- },
854
- {
855
- key: "PRIMEUI_API_KEY",
856
- description: "Optional API key override. If missing, MCP uses apiKey from .primeui/project.json.",
857
- currentValue: input.apiKeyFromEnv ? "set" : null
858
- },
859
- {
860
- key: "Fallback search",
861
- description: "When PRIMEUI_PROJECT_ROOT is not set, MCP searches from roots/list file roots, INIT_CWD, PWD, then process.cwd().",
862
- currentValue: null
863
- }
864
- ];
865
- }
866
- async function runPrimeUiHealthCheck(input) {
867
- const projectRootFromEnv = normalizeOptional(input.projectRootFromEnv);
868
- const apiBaseUrlFromEnv = normalizeOptional(input.apiBaseUrlFromEnv);
869
- const apiKeyFromEnv = normalizeOptional(input.apiKeyFromEnv);
870
- const initCwd = normalizeOptional(input.initCwd);
871
- const pwd = normalizeOptional(input.pwd);
872
- const rootsListFallbackCwds = (input.rootsListFallbackCwds ?? []).map((value) => value.trim()).filter(Boolean).map((value) => path2.resolve(value));
873
- const fallbackCwds = (input.fallbackCwds ?? []).map((value) => value.trim()).filter(Boolean);
874
- const searchRoots = buildPrimeUiProjectSearchRoots({
875
- cwd: input.cwd,
876
- fallbackCwds
877
- });
878
- try {
879
- const resolvedProjectConfig = await resolvePrimeUiProjectConfig({
880
- cwd: input.cwd,
881
- projectRootFromEnv: projectRootFromEnv ?? void 0,
882
- fallbackCwds
883
- });
884
- const targetProjectPath = resolvedProjectConfig.projectConfig.targetProjectPath;
885
- return {
886
- status: "ok",
887
- runtime: {
888
- cwd: path2.resolve(input.cwd),
889
- initCwd,
890
- pwd,
891
- searchRoots,
892
- rootsListFallbackCwds
893
- },
894
- config: {
895
- found: true,
896
- projectRoot: resolvedProjectConfig.projectRoot,
897
- projectConfigPath: resolvedProjectConfig.projectConfigPath,
898
- targetProjectPath,
899
- targetProjectRoot: path2.resolve(
900
- resolvedProjectConfig.projectRoot,
901
- targetProjectPath
902
- ),
903
- source: projectRootFromEnv ? "env.PRIMEUI_PROJECT_ROOT" : "search",
904
- error: null
905
- },
906
- options: buildHealthOptions({
907
- projectRootFromEnv,
908
- apiBaseUrlFromEnv,
909
- apiKeyFromEnv
910
- })
911
- };
912
- } catch (error) {
913
- const message = error instanceof Error ? error.message : String(error);
914
- return {
915
- status: "degraded",
916
- runtime: {
917
- cwd: path2.resolve(input.cwd),
918
- initCwd,
919
- pwd,
920
- searchRoots,
921
- rootsListFallbackCwds
922
- },
923
- config: {
924
- found: false,
925
- projectRoot: null,
926
- projectConfigPath: null,
927
- targetProjectPath: null,
928
- targetProjectRoot: null,
929
- source: projectRootFromEnv ? "env.PRIMEUI_PROJECT_ROOT" : "search",
930
- error: message
931
- },
932
- options: buildHealthOptions({
933
- projectRootFromEnv,
934
- apiBaseUrlFromEnv,
935
- apiKeyFromEnv
936
- })
937
- };
938
- }
939
- }
940
-
941
- // src/services/project-sync-service.ts
942
- import path6 from "path";
943
- import { readFile as readFile4 } from "fs/promises";
944
-
945
- // src/lib/fs.ts
946
- import { mkdir, rm, writeFile } from "fs/promises";
947
- import path3 from "path";
948
- import extractZipArchive from "extract-zip";
949
- async function ensureDir(dirPath) {
950
- await mkdir(dirPath, { recursive: true });
951
- }
952
- async function resetDir(dirPath) {
953
- await rm(dirPath, { recursive: true, force: true });
954
- await mkdir(dirPath, { recursive: true });
955
- }
956
- async function writeUtf8(filePath, content) {
957
- await ensureDir(path3.dirname(filePath));
958
- await writeFile(filePath, content, "utf-8");
959
- }
960
- async function extractZip(zipPath, targetDir) {
961
- await ensureDir(targetDir);
962
- try {
963
- await extractZipArchive(zipPath, { dir: targetDir });
964
- } catch (error) {
965
- const details = error instanceof Error ? error.message : String(error);
966
- throw new Error(`Failed to extract zip at "${zipPath}". ${details}`);
967
- }
968
- }
969
-
970
- // src/services/page-copy-service.ts
971
- import { readFile as readFile3, readdir, stat as stat3, writeFile as writeFile2 } from "fs/promises";
972
- import path5 from "path";
276
+ // src/services/page-copy-service.ts
277
+ import { readFile as readFile3, readdir, stat as stat3, writeFile as writeFile2 } from "fs/promises";
278
+ import path4 from "path";
973
279
 
974
280
  // src/lib/import-graph.ts
975
281
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
976
282
  import { builtinModules } from "module";
977
- import path4 from "path";
283
+ import path3 from "path";
978
284
  var INTERNAL_EXTENSIONS = [
979
285
  ".ts",
980
286
  ".tsx",
@@ -1050,7 +356,7 @@ async function pathExists(filePath) {
1050
356
  }
1051
357
  }
1052
358
  async function resolveFileCandidate(candidateBase) {
1053
- const ext = path4.extname(candidateBase);
359
+ const ext = path3.extname(candidateBase);
1054
360
  if (ext) {
1055
361
  if (await pathExists(candidateBase)) {
1056
362
  return candidateBase;
@@ -1070,7 +376,7 @@ async function resolveFileCandidate(candidateBase) {
1070
376
  }
1071
377
  if (stats.isDirectory()) {
1072
378
  for (const extension of INTERNAL_EXTENSIONS) {
1073
- const indexCandidate = path4.join(candidateBase, `index${extension}`);
379
+ const indexCandidate = path3.join(candidateBase, `index${extension}`);
1074
380
  if (await pathExists(indexCandidate)) {
1075
381
  return indexCandidate;
1076
382
  }
@@ -1092,13 +398,13 @@ async function resolveImportToFile(projectRoot, importerFilePath, specifier) {
1092
398
  }
1093
399
  let candidateBase = null;
1094
400
  if (specifier.startsWith("@/")) {
1095
- candidateBase = path4.join(projectRoot, "src", specifier.slice(2));
401
+ candidateBase = path3.join(projectRoot, "src", specifier.slice(2));
1096
402
  } else if (specifier.startsWith("@root/")) {
1097
- candidateBase = path4.join(projectRoot, specifier.slice(6));
403
+ candidateBase = path3.join(projectRoot, specifier.slice(6));
1098
404
  } else if (specifier.startsWith(".")) {
1099
- candidateBase = path4.resolve(path4.dirname(importerFilePath), specifier);
405
+ candidateBase = path3.resolve(path3.dirname(importerFilePath), specifier);
1100
406
  } else if (specifier.startsWith("/")) {
1101
- candidateBase = path4.join(projectRoot, specifier.slice(1));
407
+ candidateBase = path3.join(projectRoot, specifier.slice(1));
1102
408
  } else {
1103
409
  return { kind: "unknown" };
1104
410
  }
@@ -1112,12 +418,12 @@ async function resolveImportToFile(projectRoot, importerFilePath, specifier) {
1112
418
  };
1113
419
  }
1114
420
  function shouldParseFile(filePath) {
1115
- return PARSEABLE_EXTENSIONS.has(path4.extname(filePath).toLowerCase());
421
+ return PARSEABLE_EXTENSIONS.has(path3.extname(filePath).toLowerCase());
1116
422
  }
1117
423
  async function buildImportGraph(input) {
1118
424
  const shouldFollowInternalImports = input.followInternalImports ?? true;
1119
- const projectRoot = path4.resolve(input.projectRoot);
1120
- const queue = input.entryFiles.map((filePath) => path4.resolve(filePath));
425
+ const projectRoot = path3.resolve(input.projectRoot);
426
+ const queue = input.entryFiles.map((filePath) => path3.resolve(filePath));
1121
427
  const visited = /* @__PURE__ */ new Set();
1122
428
  const internalFiles = /* @__PURE__ */ new Set();
1123
429
  const externalPackages = /* @__PURE__ */ new Set();
@@ -1370,10 +676,10 @@ function normalizeSlug2(slug) {
1370
676
  return withLeadingSlash.replace(/\/+$/, "") || "/";
1371
677
  }
1372
678
  function toPosixPath(value) {
1373
- return value.split(path5.sep).join("/");
679
+ return value.split(path4.sep).join("/");
1374
680
  }
1375
681
  function toProjectRelative(rootPath, absolutePath) {
1376
- return toPosixPath(path5.relative(rootPath, absolutePath));
682
+ return toPosixPath(path4.relative(rootPath, absolutePath));
1377
683
  }
1378
684
  function escapeRegExp(value) {
1379
685
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -1430,7 +736,7 @@ function buildPlannedSourceBuffer(sourceBuffer, sourceFilePath, importRewritePla
1430
736
  if (!importRewritePlan) {
1431
737
  return sourceBuffer;
1432
738
  }
1433
- const extension = path5.extname(sourceFilePath).toLowerCase();
739
+ const extension = path4.extname(sourceFilePath).toLowerCase();
1434
740
  if (!REWRITABLE_IMPORT_EXTENSIONS.has(extension)) {
1435
741
  return sourceBuffer;
1436
742
  }
@@ -1532,9 +838,9 @@ async function resolveManifestCandidateFiles(input) {
1532
838
  );
1533
839
  }
1534
840
  const normalizedRelative = toPosixPath(trimmed).replace(/^\.\/+/, "");
1535
- const absolutePath = path5.resolve(input.exportPath, normalizedRelative);
1536
- const relative = path5.relative(input.exportPath, absolutePath);
1537
- if (relative.startsWith("..") || path5.isAbsolute(relative)) {
841
+ const absolutePath = path4.resolve(input.exportPath, normalizedRelative);
842
+ const relative = path4.relative(input.exportPath, absolutePath);
843
+ if (relative.startsWith("..") || path4.isAbsolute(relative)) {
1538
844
  throw new Error(
1539
845
  `Export manifest for page "${input.pageSlug}" contains path outside export root: ${trimmed}`
1540
846
  );
@@ -1565,12 +871,12 @@ async function resolveSingleExportDirectory(exportsRoot) {
1565
871
  const exportId = exportDirectories[0] ?? "";
1566
872
  return {
1567
873
  exportId,
1568
- exportPath: path5.join(exportsRoot, exportId)
874
+ exportPath: path4.join(exportsRoot, exportId)
1569
875
  };
1570
876
  }
1571
877
  function ensureSafeTargetPath(projectRoot, targetPath) {
1572
- const relative = path5.relative(projectRoot, targetPath);
1573
- if (relative.startsWith("..") || path5.isAbsolute(relative)) {
878
+ const relative = path4.relative(projectRoot, targetPath);
879
+ if (relative.startsWith("..") || path4.isAbsolute(relative)) {
1574
880
  throw new Error(
1575
881
  `Refusing to write outside project root. Computed target: ${targetPath}`
1576
882
  );
@@ -1628,19 +934,19 @@ function resolveTargetFilePath(input) {
1628
934
  if (sourceFilePath === sourcePagePath) {
1629
935
  return targetPagePath;
1630
936
  }
1631
- const componentsPrefix = `${sourceComponentsPath}${path5.sep}`;
937
+ const componentsPrefix = `${sourceComponentsPath}${path4.sep}`;
1632
938
  if (sourceFilePath === sourceComponentsPath || sourceFilePath.startsWith(componentsPrefix)) {
1633
- const relativeToComponents = path5.relative(
939
+ const relativeToComponents = path4.relative(
1634
940
  sourceComponentsPath,
1635
941
  sourceFilePath
1636
942
  );
1637
- return path5.join(targetComponentsPath, relativeToComponents);
943
+ return path4.join(targetComponentsPath, relativeToComponents);
1638
944
  }
1639
- const relativeToExport = path5.relative(exportRoot, sourceFilePath);
1640
- if (relativeToExport.startsWith("..") || path5.isAbsolute(relativeToExport)) {
945
+ const relativeToExport = path4.relative(exportRoot, sourceFilePath);
946
+ if (relativeToExport.startsWith("..") || path4.isAbsolute(relativeToExport)) {
1641
947
  throw new Error(`Source file is outside export root: ${sourceFilePath}`);
1642
948
  }
1643
- return path5.join(projectRoot, relativeToExport);
949
+ return path4.join(projectRoot, relativeToExport);
1644
950
  }
1645
951
  async function copyPageFromExport(input) {
1646
952
  const normalizedOriginSlug = normalizeSlug2(input.originPageSlug);
@@ -1651,7 +957,7 @@ async function copyPageFromExport(input) {
1651
957
  const { exportId, exportPath } = await resolveSingleExportDirectory(
1652
958
  input.exportsRoot
1653
959
  );
1654
- const manifestPath = path5.join(
960
+ const manifestPath = path4.join(
1655
961
  input.exportsRoot,
1656
962
  `${exportId}.manifest.json`
1657
963
  );
@@ -1674,8 +980,8 @@ async function copyPageFromExport(input) {
1674
980
  `Page not found in manifest for slug: ${normalizedOriginSlug}`
1675
981
  );
1676
982
  }
1677
- const sourcePagePath = path5.join(exportPath, page.pagePath);
1678
- const sourceComponentsPath = path5.join(exportPath, page.componentsPath);
983
+ const sourcePagePath = path4.join(exportPath, page.pagePath);
984
+ const sourceComponentsPath = path4.join(exportPath, page.componentsPath);
1679
985
  const sourcePageStats = await stat3(sourcePagePath).catch(() => null);
1680
986
  if (!sourcePageStats?.isFile()) {
1681
987
  throw new Error(`Source page file not found: ${sourcePagePath}`);
@@ -1695,8 +1001,8 @@ async function copyPageFromExport(input) {
1695
1001
  pageType: page.pageType,
1696
1002
  slug: normalizedActualSlug
1697
1003
  });
1698
- const targetPagePath = path5.join(input.projectRoot, targetPaths.pagePath);
1699
- const targetComponentsPath = path5.join(
1004
+ const targetPagePath = path4.join(input.projectRoot, targetPaths.pagePath);
1005
+ const targetComponentsPath = path4.join(
1700
1006
  input.projectRoot,
1701
1007
  targetPaths.componentsPath
1702
1008
  );
@@ -1742,7 +1048,7 @@ async function copyPageFromExport(input) {
1742
1048
  const sourceRelative = toProjectRelative(exportPath, sourceFilePath);
1743
1049
  const targetRelative = toProjectRelative(input.projectRoot, targetFilePath);
1744
1050
  if (!targetBuffer) {
1745
- await ensureDir(path5.dirname(targetFilePath));
1051
+ await ensureDir(path4.dirname(targetFilePath));
1746
1052
  await writeFile2(targetFilePath, plannedSourceBuffer);
1747
1053
  newFiles.push({
1748
1054
  sourcePath: sourceRelative,
@@ -1771,8 +1077,8 @@ async function copyPageFromExport(input) {
1771
1077
  isBinary
1772
1078
  });
1773
1079
  }
1774
- const exportPackageJsonPath = path5.join(exportPath, "package.json");
1775
- const userPackageJsonPath = path5.join(input.projectRoot, "package.json");
1080
+ const exportPackageJsonPath = path4.join(exportPath, "package.json");
1081
+ const userPackageJsonPath = path4.join(input.projectRoot, "package.json");
1776
1082
  if (!await fileExists2(exportPackageJsonPath)) {
1777
1083
  throw new Error(`Export package.json not found: ${exportPackageJsonPath}`);
1778
1084
  }
@@ -1978,7 +1284,7 @@ function isExportPage(value) {
1978
1284
  return typeof maybe.id === "string" && (typeof title === "string" || typeof title === "undefined") && typeof maybe.slug === "string" && typeof maybe.pageType === "string" && maybe.isReadyToExport === true && typeof maybe.pagePath === "string" && typeof maybe.componentsPath === "string" && isExportPageManifest(maybe.manifest);
1979
1285
  }
1980
1286
  function buildManifestPath(exportsRoot, exportId) {
1981
- return path6.join(exportsRoot, `${exportId}.manifest.json`);
1287
+ return path5.join(exportsRoot, `${exportId}.manifest.json`);
1982
1288
  }
1983
1289
  function parseExportManifest(value) {
1984
1290
  if (!value || typeof value !== "object") {
@@ -1989,609 +1295,1202 @@ function parseExportManifest(value) {
1989
1295
  if (!exportPayload || typeof exportPayload !== "object") {
1990
1296
  throw new Error("Export manifest export payload is invalid.");
1991
1297
  }
1992
- const exportObject = exportPayload;
1993
- const status = exportObject.status;
1994
- const summary = exportObject.summary;
1995
- const components = exportObject.components;
1996
- if (typeof exportObject.id !== "string" || !isExportStatus2(status) || typeof exportObject.createdAt !== "string" || !("expiresAt" in exportObject) || !(typeof exportObject.expiresAt === "string" || exportObject.expiresAt === null) || !isExportSummary(summary) || !Array.isArray(components) || !components.every(isExportedComponent) || !Array.isArray(maybe.pages)) {
1997
- throw new Error("Export manifest does not match expected schema.");
1298
+ const exportObject = exportPayload;
1299
+ const status = exportObject.status;
1300
+ const summary = exportObject.summary;
1301
+ const components = exportObject.components;
1302
+ if (typeof exportObject.id !== "string" || !isExportStatus2(status) || typeof exportObject.createdAt !== "string" || !("expiresAt" in exportObject) || !(typeof exportObject.expiresAt === "string" || exportObject.expiresAt === null) || !isExportSummary(summary) || !Array.isArray(components) || !components.every(isExportedComponent) || !Array.isArray(maybe.pages)) {
1303
+ throw new Error("Export manifest does not match expected schema.");
1304
+ }
1305
+ if (!maybe.pages.every(isExportPage)) {
1306
+ throw new Error("Export manifest pages payload is invalid.");
1307
+ }
1308
+ return {
1309
+ export: {
1310
+ id: exportObject.id,
1311
+ status,
1312
+ createdAt: exportObject.createdAt,
1313
+ expiresAt: exportObject.expiresAt,
1314
+ summary,
1315
+ components
1316
+ },
1317
+ pages: maybe.pages
1318
+ };
1319
+ }
1320
+ var ProjectSyncService = class {
1321
+ provider;
1322
+ projectRoot;
1323
+ targetProjectRoot;
1324
+ primeUiRoot;
1325
+ tempRoot;
1326
+ exportsRoot;
1327
+ constructor(options) {
1328
+ this.projectRoot = options.projectRoot;
1329
+ this.targetProjectRoot = options.targetProjectRoot;
1330
+ this.provider = options.provider;
1331
+ this.primeUiRoot = path5.join(this.projectRoot, ".primeui");
1332
+ this.tempRoot = path5.join(this.primeUiRoot, "temp");
1333
+ this.exportsRoot = path5.join(this.tempRoot, "exports");
1334
+ }
1335
+ async getProjectInfo(_context) {
1336
+ await this.ensureTempLayout();
1337
+ return this.provider.getProjectInfo();
1338
+ }
1339
+ async listExports(_context) {
1340
+ await this.ensureTempLayout();
1341
+ return this.provider.listExports();
1342
+ }
1343
+ async createExport(_context) {
1344
+ await this.ensureTempLayout();
1345
+ const result = await this.provider.createExport();
1346
+ const manifestPath = buildManifestPath(this.exportsRoot, result.export.id);
1347
+ await writeUtf8(manifestPath, `${JSON.stringify(result, null, 2)}
1348
+ `);
1349
+ return result;
1350
+ }
1351
+ async downloadExportById(id, _context) {
1352
+ await this.ensureTempLayout();
1353
+ const exportsList = await this.provider.listExports();
1354
+ const selected = exportsList.find((item) => item.id === id);
1355
+ if (!selected) {
1356
+ throw new Error(`Export not found: ${id}`);
1357
+ }
1358
+ const targetZipPath = path5.join(this.exportsRoot, `${id}.zip`);
1359
+ const targetProjectPath = path5.join(this.exportsRoot, id);
1360
+ const manifestPath = buildManifestPath(this.exportsRoot, id);
1361
+ let manifest;
1362
+ try {
1363
+ manifest = parseExportManifest(
1364
+ JSON.parse(await readFile4(manifestPath, "utf-8"))
1365
+ );
1366
+ } catch (error) {
1367
+ const reason = error instanceof Error ? error.message : String(error);
1368
+ throw new Error(
1369
+ `Export manifest is required for download id "${id}". Run create_export for this export before downloading. Details: ${reason}`
1370
+ );
1371
+ }
1372
+ if (manifest.export.id !== id) {
1373
+ throw new Error(
1374
+ `Export manifest mismatch for id "${id}". Run create_export and retry download.`
1375
+ );
1376
+ }
1377
+ await ensureDir(this.exportsRoot);
1378
+ await this.provider.downloadExportArchive(id, targetZipPath);
1379
+ await resetDir(targetProjectPath);
1380
+ await extractZip(targetZipPath, targetProjectPath);
1381
+ const pages = manifest.pages;
1382
+ return {
1383
+ exportId: id,
1384
+ projectPath: targetProjectPath,
1385
+ manifestPath,
1386
+ pages
1387
+ };
1388
+ }
1389
+ async inspectPage(slug, _context) {
1390
+ await this.ensureTempLayout();
1391
+ const pageDetails = await this.provider.getProjectPageBySlug(slug);
1392
+ const reportPayload = buildInspectPageReport(pageDetails);
1393
+ return {
1394
+ ...pageDetails,
1395
+ report: reportPayload.report,
1396
+ reportRows: reportPayload.reportRows
1397
+ };
1398
+ }
1399
+ async copyPage(originPageSlug, actualPageSlug, _context) {
1400
+ await this.ensureTempLayout();
1401
+ return copyPageFromExport({
1402
+ projectRoot: this.targetProjectRoot,
1403
+ exportsRoot: this.exportsRoot,
1404
+ originPageSlug,
1405
+ actualPageSlug
1406
+ });
1407
+ }
1408
+ async clearTemp(_context) {
1409
+ await resetDir(this.tempRoot);
1410
+ await ensureDir(this.exportsRoot);
1411
+ }
1412
+ async ensureTempLayout() {
1413
+ await ensureDir(this.primeUiRoot);
1414
+ await ensureDir(this.tempRoot);
1415
+ await ensureDir(this.exportsRoot);
1416
+ }
1417
+ };
1418
+
1419
+ // src/sources/api-provider.ts
1420
+ import { createWriteStream } from "fs";
1421
+ import { unlink } from "fs/promises";
1422
+ import path6 from "path";
1423
+ import { Readable, Transform } from "stream";
1424
+ import { pipeline } from "stream/promises";
1425
+ import { z as z2 } from "zod";
1426
+ var DEFAULT_API_BASE_URL = "https://app.primeui.com/";
1427
+ var ZIP_CONTENT_TYPES = [
1428
+ "application/zip",
1429
+ "application/octet-stream",
1430
+ "application/x-zip-compressed"
1431
+ ];
1432
+ var exportStatusSchema = z2.enum(["in_progress", "completed", "failed"]);
1433
+ var projectPageObjectSchema = z2.object({
1434
+ id: z2.string(),
1435
+ title: z2.string(),
1436
+ slug: z2.string(),
1437
+ pageType: z2.string(),
1438
+ isReadyToExport: z2.boolean(),
1439
+ pagePath: z2.string(),
1440
+ componentsPath: z2.string()
1441
+ });
1442
+ var projectPageSchema = projectPageObjectSchema;
1443
+ var exportSummarySchema = z2.object({
1444
+ total: z2.number(),
1445
+ successful: z2.number(),
1446
+ failed: z2.number()
1447
+ });
1448
+ var exportedComponentSchema = z2.object({
1449
+ componentKey: z2.string(),
1450
+ enabled: z2.boolean(),
1451
+ files: z2.array(z2.string()),
1452
+ message: z2.string()
1453
+ });
1454
+ var exportPageManifestSchema = z2.object({
1455
+ success: z2.boolean(),
1456
+ message: z2.string(),
1457
+ files: z2.array(z2.string())
1458
+ });
1459
+ var exportPageSchema = z2.object({
1460
+ id: z2.string(),
1461
+ title: z2.string().optional(),
1462
+ slug: z2.string(),
1463
+ pageType: z2.string(),
1464
+ isReadyToExport: z2.literal(true),
1465
+ pagePath: z2.string(),
1466
+ componentsPath: z2.string(),
1467
+ manifest: exportPageManifestSchema
1468
+ });
1469
+ var projectInfoSchema = z2.object({
1470
+ projectId: z2.string(),
1471
+ projectName: z2.string(),
1472
+ metadata: z2.record(z2.unknown()),
1473
+ pages: z2.array(projectPageSchema)
1474
+ });
1475
+ var projectPageComponentSchema = z2.object({
1476
+ blockId: z2.string(),
1477
+ componentId: z2.string(),
1478
+ componentGroup: z2.string(),
1479
+ slot: z2.string().nullable(),
1480
+ props: z2.record(z2.unknown()).nullable()
1481
+ });
1482
+ var projectPageDetailsSchema = z2.object({
1483
+ page: projectPageObjectSchema.extend({
1484
+ pageInstruction: z2.string().nullable()
1485
+ }),
1486
+ variant: z2.object({
1487
+ id: z2.string(),
1488
+ name: z2.string()
1489
+ }).nullable(),
1490
+ components: z2.array(projectPageComponentSchema).nullable()
1491
+ });
1492
+ var exportsResponseSchema = z2.object({
1493
+ exports: z2.array(
1494
+ z2.object({
1495
+ id: z2.string(),
1496
+ status: exportStatusSchema,
1497
+ createdAt: z2.string().datetime({ offset: true })
1498
+ })
1499
+ )
1500
+ });
1501
+ var createExportResponseSchema = z2.object({
1502
+ export: z2.object({
1503
+ id: z2.string(),
1504
+ status: exportStatusSchema,
1505
+ createdAt: z2.string().datetime({ offset: true }),
1506
+ expiresAt: z2.string().datetime({ offset: true }).nullable(),
1507
+ summary: exportSummarySchema,
1508
+ components: z2.array(exportedComponentSchema)
1509
+ }),
1510
+ pages: z2.array(exportPageSchema)
1511
+ });
1512
+ var PrimeUiApiContractError = class extends Error {
1513
+ constructor(endpoint, details) {
1514
+ super(`PrimeUI API contract mismatch for "${endpoint}": ${details}`);
1515
+ this.name = "PrimeUiApiContractError";
1516
+ }
1517
+ };
1518
+ var normalizeApiRoot = (rawBaseUrl) => {
1519
+ const normalizedBase = rawBaseUrl.trim() || DEFAULT_API_BASE_URL;
1520
+ const parsed = new URL(normalizedBase);
1521
+ const normalizedPath = parsed.pathname.replace(/\/+$/, "");
1522
+ if (normalizedPath.endsWith("/api/v1")) {
1523
+ parsed.pathname = `${normalizedPath}/`;
1524
+ return parsed.toString();
1525
+ }
1526
+ parsed.pathname = `${normalizedPath}/api/v1/`.replace(
1527
+ "//api/v1/",
1528
+ "/api/v1/"
1529
+ );
1530
+ return parsed.toString();
1531
+ };
1532
+ var isJsonContentType = (contentType) => contentType.includes("application/json") || contentType.includes("+json");
1533
+ var isZipContentType = (contentType) => ZIP_CONTENT_TYPES.some((allowedType) => contentType.includes(allowedType));
1534
+ var looksLikeZipArchive = (buffer) => {
1535
+ if (buffer.length < 4) {
1536
+ return false;
1998
1537
  }
1999
- if (!maybe.pages.every(isExportPage)) {
2000
- throw new Error("Export manifest pages payload is invalid.");
1538
+ if (buffer[0] !== 80 || buffer[1] !== 75) {
1539
+ return false;
2001
1540
  }
2002
- return {
2003
- export: {
2004
- id: exportObject.id,
2005
- status,
2006
- createdAt: exportObject.createdAt,
2007
- expiresAt: exportObject.expiresAt,
2008
- summary,
2009
- components
1541
+ const signature = `${buffer[2]}:${buffer[3]}`;
1542
+ return signature === "3:4" || signature === "5:6" || signature === "7:8";
1543
+ };
1544
+ var createZipSignatureGuard = (endpoint) => {
1545
+ let signature = Buffer.alloc(0);
1546
+ let validated = false;
1547
+ return new Transform({
1548
+ transform(chunk, _encoding, callback) {
1549
+ const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1550
+ if (!validated) {
1551
+ const requiredBytes = Math.max(0, 4 - signature.length);
1552
+ if (requiredBytes > 0) {
1553
+ signature = Buffer.concat([
1554
+ signature,
1555
+ chunkBuffer.subarray(0, requiredBytes)
1556
+ ]);
1557
+ }
1558
+ if (signature.length >= 4) {
1559
+ if (!looksLikeZipArchive(signature)) {
1560
+ callback(
1561
+ new PrimeUiApiContractError(
1562
+ endpoint,
1563
+ "response body is not a valid zip archive"
1564
+ )
1565
+ );
1566
+ return;
1567
+ }
1568
+ validated = true;
1569
+ }
1570
+ }
1571
+ callback(null, chunkBuffer);
2010
1572
  },
2011
- pages: maybe.pages
2012
- };
2013
- }
2014
- var ProjectSyncService = class {
2015
- provider;
2016
- projectRoot;
2017
- targetProjectRoot;
2018
- primeUiRoot;
2019
- tempRoot;
2020
- exportsRoot;
1573
+ flush(callback) {
1574
+ if (!validated) {
1575
+ callback(
1576
+ new PrimeUiApiContractError(
1577
+ endpoint,
1578
+ "response body is not a valid zip archive"
1579
+ )
1580
+ );
1581
+ return;
1582
+ }
1583
+ callback();
1584
+ }
1585
+ });
1586
+ };
1587
+ var ApiProjectDataProvider = class {
1588
+ apiKey;
1589
+ apiRoot;
2021
1590
  constructor(options) {
2022
- this.projectRoot = options.projectRoot;
2023
- this.targetProjectRoot = options.targetProjectRoot;
2024
- this.provider = options.provider;
2025
- this.primeUiRoot = path6.join(this.projectRoot, ".primeui");
2026
- this.tempRoot = path6.join(this.primeUiRoot, "temp");
2027
- this.exportsRoot = path6.join(this.tempRoot, "exports");
1591
+ this.apiKey = options.apiKey;
1592
+ this.apiRoot = normalizeApiRoot(options.baseUrl ?? DEFAULT_API_BASE_URL);
2028
1593
  }
2029
1594
  async getProjectInfo() {
2030
- await this.ensureTempLayout();
2031
- return this.provider.getProjectInfo();
1595
+ return this.requestJson("project", projectInfoSchema);
1596
+ }
1597
+ async getProjectPageBySlug(slug) {
1598
+ const encodedSlug = encodeURIComponent(slug);
1599
+ return this.requestJson(
1600
+ `project/page?slug=${encodedSlug}`,
1601
+ projectPageDetailsSchema
1602
+ );
2032
1603
  }
2033
1604
  async listExports() {
2034
- await this.ensureTempLayout();
2035
- return this.provider.listExports();
1605
+ const response = await this.requestJson(
1606
+ "project/exports",
1607
+ exportsResponseSchema
1608
+ );
1609
+ return response.exports;
2036
1610
  }
2037
1611
  async createExport() {
2038
- await this.ensureTempLayout();
2039
- const result = await this.provider.createExport();
2040
- const manifestPath = buildManifestPath(this.exportsRoot, result.export.id);
2041
- await writeUtf8(manifestPath, `${JSON.stringify(result, null, 2)}
2042
- `);
2043
- return result;
1612
+ return this.requestJson("project/exports", createExportResponseSchema, {
1613
+ method: "POST"
1614
+ });
2044
1615
  }
2045
- async downloadExportById(id) {
2046
- await this.ensureTempLayout();
2047
- const exportsList = await this.provider.listExports();
2048
- const selected = exportsList.find((item) => item.id === id);
2049
- if (!selected) {
2050
- throw new Error(`Export not found: ${id}`);
1616
+ /**
1617
+ * Consumer-side runtime contract for GET /api/v1/project/exports/:exportId/download.
1618
+ * Producer source of truth: apps/studio/src/app/api/v1/project/exports/[exportId]/download/route.ts
1619
+ * Keep content-type and archive format checks synchronized with the producer response.
1620
+ */
1621
+ async downloadExportArchive(exportId, destinationPath) {
1622
+ const endpoint = `project/exports/${encodeURIComponent(exportId)}/download`;
1623
+ const apiKey = this.requireApiKey();
1624
+ const response = await fetch(this.buildUrl(endpoint), {
1625
+ method: "GET",
1626
+ headers: {
1627
+ Authorization: `Bearer ${apiKey}`
1628
+ }
1629
+ });
1630
+ if (!response.ok) {
1631
+ const details = await this.readError(response);
1632
+ throw new Error(
1633
+ `PrimeUI API request failed (${response.status}) while downloading export "${exportId}": ${details}`
1634
+ );
2051
1635
  }
2052
- const targetZipPath = path6.join(this.exportsRoot, `${id}.zip`);
2053
- const targetProjectPath = path6.join(this.exportsRoot, id);
2054
- const manifestPath = buildManifestPath(this.exportsRoot, id);
2055
- let manifest;
2056
- try {
2057
- manifest = parseExportManifest(
2058
- JSON.parse(await readFile4(manifestPath, "utf-8"))
1636
+ const contentType = response.headers.get("content-type") ?? "";
1637
+ if (!isZipContentType(contentType)) {
1638
+ throw new PrimeUiApiContractError(
1639
+ endpoint,
1640
+ `expected zip content-type but got "${contentType || "unknown"}"`
2059
1641
  );
1642
+ }
1643
+ if (!response.body) {
1644
+ throw new PrimeUiApiContractError(endpoint, "response body is empty");
1645
+ }
1646
+ await ensureDir(path6.dirname(destinationPath));
1647
+ const zipStream = Readable.fromWeb(
1648
+ response.body
1649
+ );
1650
+ const signatureGuard = createZipSignatureGuard(endpoint);
1651
+ const fileStream = createWriteStream(destinationPath);
1652
+ try {
1653
+ await pipeline(zipStream, signatureGuard, fileStream);
2060
1654
  } catch (error) {
2061
- const reason = error instanceof Error ? error.message : String(error);
1655
+ await unlink(destinationPath).catch(() => void 0);
1656
+ throw error;
1657
+ }
1658
+ }
1659
+ buildUrl(endpoint) {
1660
+ return new URL(endpoint, this.apiRoot).toString();
1661
+ }
1662
+ async requestJson(endpoint, schema, requestInit = {}) {
1663
+ const apiKey = this.requireApiKey();
1664
+ const method = requestInit.method ?? "GET";
1665
+ const response = await fetch(this.buildUrl(endpoint), {
1666
+ ...requestInit,
1667
+ method,
1668
+ headers: {
1669
+ ...requestInit.headers ?? {},
1670
+ Authorization: `Bearer ${apiKey}`
1671
+ }
1672
+ });
1673
+ if (!response.ok) {
1674
+ const details = await this.readError(response);
2062
1675
  throw new Error(
2063
- `Export manifest is required for download id "${id}". Run create_export for this export before downloading. Details: ${reason}`
1676
+ `PrimeUI API request failed (${response.status}) for "${endpoint}": ${details}`
2064
1677
  );
2065
1678
  }
2066
- if (manifest.export.id !== id) {
2067
- throw new Error(
2068
- `Export manifest mismatch for id "${id}". Run create_export and retry download.`
1679
+ const contentType = response.headers.get("content-type") ?? "";
1680
+ if (!isJsonContentType(contentType)) {
1681
+ throw new PrimeUiApiContractError(
1682
+ endpoint,
1683
+ `expected JSON content-type but got "${contentType || "unknown"}"`
2069
1684
  );
2070
1685
  }
2071
- await ensureDir(this.exportsRoot);
2072
- await this.provider.downloadExportArchive(id, targetZipPath);
2073
- await resetDir(targetProjectPath);
2074
- await extractZip(targetZipPath, targetProjectPath);
2075
- const pages = manifest.pages;
2076
- return {
2077
- exportId: id,
2078
- projectPath: targetProjectPath,
2079
- manifestPath,
2080
- pages
2081
- };
1686
+ let payload;
1687
+ try {
1688
+ payload = await response.json();
1689
+ } catch {
1690
+ throw new PrimeUiApiContractError(
1691
+ endpoint,
1692
+ "response body is not valid JSON"
1693
+ );
1694
+ }
1695
+ const parsed = schema.safeParse(payload);
1696
+ if (!parsed.success) {
1697
+ const details = parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
1698
+ throw new PrimeUiApiContractError(endpoint, details);
1699
+ }
1700
+ return parsed.data;
2082
1701
  }
2083
- async inspectPage(slug) {
2084
- await this.ensureTempLayout();
2085
- const pageDetails = await this.provider.getProjectPageBySlug(slug);
2086
- const reportPayload = buildInspectPageReport(pageDetails);
2087
- return {
2088
- ...pageDetails,
2089
- report: reportPayload.report,
2090
- reportRows: reportPayload.reportRows
2091
- };
1702
+ async readError(response) {
1703
+ const contentType = response.headers.get("content-type") ?? "";
1704
+ try {
1705
+ if (isJsonContentType(contentType)) {
1706
+ const body = await response.json();
1707
+ if (body?.message && body?.code) {
1708
+ return `${body.code}: ${body.message}`;
1709
+ }
1710
+ if (body?.message) {
1711
+ return body.message;
1712
+ }
1713
+ } else {
1714
+ const bodyText = await response.text();
1715
+ if (bodyText.trim()) {
1716
+ return bodyText.trim();
1717
+ }
1718
+ }
1719
+ } catch {
1720
+ }
1721
+ return response.statusText || "Unknown error";
1722
+ }
1723
+ requireApiKey() {
1724
+ const apiKey = this.apiKey?.trim();
1725
+ if (!apiKey) {
1726
+ throw new Error("PRIMEUI_API_KEY is required to call PrimeUI API tools.");
1727
+ }
1728
+ return apiKey;
1729
+ }
1730
+ };
1731
+
1732
+ // src/runtime.ts
1733
+ function defaultCreateProvider(options) {
1734
+ return new ApiProjectDataProvider(options);
1735
+ }
1736
+ var LazyProjectSyncSource = class {
1737
+ env;
1738
+ getCwd;
1739
+ createProvider;
1740
+ stickyProjectRoot;
1741
+ constructor(options = {}) {
1742
+ this.env = options.env ?? process.env;
1743
+ this.getCwd = options.getCwd ?? (() => process.cwd());
1744
+ this.createProvider = options.createProvider ?? defaultCreateProvider;
1745
+ }
1746
+ async withService(context, operation) {
1747
+ const service = await this.createProjectSyncService(context);
1748
+ return operation(service);
2092
1749
  }
2093
- async copyPage(originPageSlug, actualPageSlug) {
2094
- await this.ensureTempLayout();
2095
- return copyPageFromExport({
2096
- projectRoot: this.targetProjectRoot,
2097
- exportsRoot: this.exportsRoot,
2098
- originPageSlug,
2099
- actualPageSlug
1750
+ async withMutationService(context, operation) {
1751
+ const service = await this.createProjectSyncService(context);
1752
+ return operation(service);
1753
+ }
1754
+ async createProjectSyncService(context) {
1755
+ const resolvedProjectConfig = await resolvePrimeUiProjectConfig({
1756
+ cwd: this.getCwd(),
1757
+ projectRootFromTool: context?.projectRoot,
1758
+ projectRootFromSticky: this.stickyProjectRoot,
1759
+ projectRootFromEnv: this.env.PRIMEUI_PROJECT_ROOT
1760
+ });
1761
+ const projectRoot = resolvedProjectConfig.projectRoot;
1762
+ const targetProjectRoot = path7.resolve(
1763
+ projectRoot,
1764
+ resolvedProjectConfig.projectConfig.targetProjectPath
1765
+ );
1766
+ const apiKey = await resolvePrimeUiApiKey({
1767
+ projectConfig: resolvedProjectConfig.projectConfig,
1768
+ apiKeyFromEnv: this.env.PRIMEUI_API_KEY
1769
+ });
1770
+ this.stickyProjectRoot = projectRoot;
1771
+ const provider = this.createProvider({
1772
+ apiKey,
1773
+ baseUrl: this.env.PRIMEUI_API_BASE_URL
1774
+ });
1775
+ return new ProjectSyncService({
1776
+ projectRoot,
1777
+ targetProjectRoot,
1778
+ provider
2100
1779
  });
2101
1780
  }
2102
- async clearTemp() {
2103
- await resetDir(this.tempRoot);
2104
- await ensureDir(this.exportsRoot);
1781
+ async getProjectInfo(context) {
1782
+ return this.withService(
1783
+ context,
1784
+ (service) => service.getProjectInfo(context)
1785
+ );
2105
1786
  }
2106
- async healthCheck() {
2107
- return {
2108
- status: "ok",
2109
- runtime: {
2110
- cwd: process.cwd(),
2111
- initCwd: process.env.INIT_CWD?.trim() || null,
2112
- pwd: process.env.PWD?.trim() || null,
2113
- searchRoots: [process.cwd()],
2114
- rootsListFallbackCwds: []
2115
- },
2116
- config: {
2117
- found: true,
2118
- projectRoot: this.projectRoot,
2119
- projectConfigPath: path6.join(this.projectRoot, ".primeui", "project.json"),
2120
- targetProjectPath: path6.relative(
2121
- this.projectRoot,
2122
- this.targetProjectRoot
2123
- ).startsWith(".") ? "./" : `./${path6.relative(this.projectRoot, this.targetProjectRoot)}`,
2124
- targetProjectRoot: this.targetProjectRoot,
2125
- source: process.env.PRIMEUI_PROJECT_ROOT?.trim() ? "env.PRIMEUI_PROJECT_ROOT" : "search",
2126
- error: null
2127
- },
2128
- options: [
2129
- {
2130
- key: "PRIMEUI_PROJECT_ROOT",
2131
- description: "Optional absolute root containing .primeui/project.json. Use as explicit override.",
2132
- currentValue: process.env.PRIMEUI_PROJECT_ROOT?.trim() || null
2133
- },
2134
- {
2135
- key: "PRIMEUI_API_BASE_URL",
2136
- description: "Optional PrimeUI API base URL override.",
2137
- currentValue: process.env.PRIMEUI_API_BASE_URL?.trim() || null
2138
- },
2139
- {
2140
- key: "PRIMEUI_API_KEY",
2141
- description: "Optional API key override. If missing, MCP uses apiKey from .primeui/project.json.",
2142
- currentValue: process.env.PRIMEUI_API_KEY?.trim() ? "set" : null
2143
- }
2144
- ]
2145
- };
1787
+ async listExports(context) {
1788
+ return this.withService(context, (service) => service.listExports(context));
2146
1789
  }
2147
- async ensureTempLayout() {
2148
- await ensureDir(this.primeUiRoot);
2149
- await ensureDir(this.tempRoot);
2150
- await ensureDir(this.exportsRoot);
1790
+ async createExport(context) {
1791
+ return this.withService(
1792
+ context,
1793
+ (service) => service.createExport(context)
1794
+ );
1795
+ }
1796
+ async downloadExportById(id, context) {
1797
+ return this.withService(
1798
+ context,
1799
+ (service) => service.downloadExportById(id, context)
1800
+ );
1801
+ }
1802
+ async inspectPage(slug, context) {
1803
+ return this.withService(
1804
+ context,
1805
+ (service) => service.inspectPage(slug, context)
1806
+ );
1807
+ }
1808
+ async copyPage(originPageSlug, actualPageSlug, context) {
1809
+ return this.withMutationService(
1810
+ context,
1811
+ (service) => service.copyPage(originPageSlug, actualPageSlug, context)
1812
+ );
1813
+ }
1814
+ async clearTemp(context) {
1815
+ return this.withMutationService(
1816
+ context,
1817
+ (service) => service.clearTemp(context)
1818
+ );
2151
1819
  }
2152
1820
  };
2153
1821
 
2154
- // src/sources/api-provider.ts
2155
- import { createWriteStream } from "fs";
2156
- import { unlink } from "fs/promises";
2157
- import path7 from "path";
2158
- import { Readable, Transform } from "stream";
2159
- import { pipeline } from "stream/promises";
2160
- import { z as z3 } from "zod";
2161
- var DEFAULT_API_BASE_URL = "https://app.primeui.com/";
2162
- var ZIP_CONTENT_TYPES = [
2163
- "application/zip",
2164
- "application/octet-stream",
2165
- "application/x-zip-compressed"
2166
- ];
2167
- var exportStatusSchema2 = z3.enum(["in_progress", "completed", "failed"]);
2168
- var projectPageObjectSchema = z3.object({
2169
- id: z3.string(),
2170
- title: z3.string(),
2171
- slug: z3.string(),
2172
- pageType: z3.string(),
2173
- isReadyToExport: z3.boolean(),
2174
- pagePath: z3.string(),
2175
- componentsPath: z3.string()
2176
- });
2177
- var projectPageSchema = projectPageObjectSchema;
1822
+ // src/server.ts
1823
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1824
+
1825
+ // src/instructions.ts
1826
+ import { z as z3 } from "zod/v3";
1827
+ var pageSchema = z3.object({
1828
+ id: z3.string().describe(
1829
+ "Stable PrimeUI page identifier. Use this to match the same page across tool responses."
1830
+ ),
1831
+ title: z3.string().describe(
1832
+ "Human-readable page title. Show this to the user when confirming which pages to import."
1833
+ ),
1834
+ slug: z3.string().describe(
1835
+ "PrimeUI route slug, for example '/', '/pricing', '/docs/getting-started'. Use to match user intent."
1836
+ ),
1837
+ pageType: z3.string().describe(
1838
+ "PrimeUI page type classification (for example landing, docs, pricing, blogIndex, legal)."
1839
+ ),
1840
+ isReadyToExport: z3.boolean().describe(
1841
+ "True when this page has an active export-ready variant. Only ready pages are expected in export output."
1842
+ ),
1843
+ pagePath: z3.string().describe(
1844
+ "Relative file path to the page source inside downloaded export root. Copy page code from this exact path."
1845
+ ),
1846
+ componentsPath: z3.string().describe(
1847
+ "Relative directory path inside downloaded export root where page-level components live. Use as dependency copy start."
1848
+ )
1849
+ }).describe(
1850
+ "PrimeUI page descriptor used by project and export tools. Paths are always relative to export project root."
1851
+ );
1852
+ var exportStatusSchema2 = z3.enum(["in_progress", "completed", "failed"]).describe(
1853
+ "Export lifecycle state. Use 'completed' before calling download_export."
1854
+ );
1855
+ var exportItemSchema = z3.object({
1856
+ id: z3.string().describe(
1857
+ "Export identifier. Pass this value to download_export input.id."
1858
+ ),
1859
+ status: exportStatusSchema2,
1860
+ createdAt: z3.string().describe("Export creation timestamp in ISO-8601 UTC format.")
1861
+ }).describe("Single export record from PrimeUI export history.");
2178
1862
  var exportSummarySchema2 = z3.object({
2179
- total: z3.number(),
2180
- successful: z3.number(),
2181
- failed: z3.number()
1863
+ total: z3.number().describe("Total pages processed in this export run."),
1864
+ successful: z3.number().describe("Number of pages exported successfully."),
1865
+ failed: z3.number().describe("Number of pages that failed export.")
2182
1866
  });
2183
- var exportedComponentSchema = z3.object({
2184
- componentKey: z3.string(),
2185
- enabled: z3.boolean(),
2186
- files: z3.array(z3.string()),
2187
- message: z3.string()
1867
+ var exportComponentSchema = z3.object({
1868
+ componentKey: z3.string().describe("Global component key from export manifest."),
1869
+ enabled: z3.boolean().describe("True when this global component was exported."),
1870
+ files: z3.array(z3.string()).describe("Files associated with this global component."),
1871
+ message: z3.string().describe("Export status message for this global component.")
2188
1872
  });
2189
1873
  var exportPageManifestSchema2 = z3.object({
2190
- success: z3.boolean(),
2191
- message: z3.string(),
2192
- files: z3.array(z3.string())
1874
+ success: z3.boolean().describe("True when page export succeeded in this export run."),
1875
+ message: z3.string().describe("Export status message for this page."),
1876
+ files: z3.array(z3.string()).describe(
1877
+ "Authoritative list of page-related files from PrimeUI export manifest, including shared project files required by this page."
1878
+ )
2193
1879
  });
2194
1880
  var exportPageSchema2 = z3.object({
2195
- id: z3.string(),
2196
- title: z3.string().optional(),
2197
- slug: z3.string(),
2198
- pageType: z3.string(),
2199
- isReadyToExport: z3.literal(true),
2200
- pagePath: z3.string(),
2201
- componentsPath: z3.string(),
1881
+ id: z3.string().describe("Stable PrimeUI page identifier for this export entry."),
1882
+ title: z3.string().optional().describe(
1883
+ "Optional page title from export manifest. Can be undefined for some legacy records."
1884
+ ),
1885
+ slug: z3.string().describe("PrimeUI page slug included in this export."),
1886
+ pageType: z3.string().describe("PrimeUI page type for routing/path resolution."),
1887
+ isReadyToExport: z3.literal(true).describe("Always true for pages included in export payload."),
1888
+ pagePath: z3.string().describe("Page source file path relative to export root."),
1889
+ componentsPath: z3.string().describe("Page components directory path relative to export root."),
2202
1890
  manifest: exportPageManifestSchema2
2203
1891
  });
2204
- var projectInfoSchema = z3.object({
2205
- projectId: z3.string(),
2206
- projectName: z3.string(),
2207
- metadata: z3.record(z3.unknown()),
2208
- pages: z3.array(projectPageSchema)
1892
+ var fileTransferSchema = z3.object({
1893
+ sourcePath: z3.string().describe("Relative source file path inside downloaded export root."),
1894
+ targetPath: z3.string().describe("Relative target file path inside user project root.")
2209
1895
  });
2210
- var projectPageComponentSchema = z3.object({
2211
- blockId: z3.string(),
2212
- componentId: z3.string(),
2213
- componentGroup: z3.string(),
2214
- slot: z3.string().nullable(),
2215
- props: z3.record(z3.unknown()).nullable()
1896
+ var conflictFileSchema = fileTransferSchema.extend({
1897
+ diff: z3.string().describe(
1898
+ "Unified diff between user file and export file. For binary files the diff contains a plain message."
1899
+ ),
1900
+ isBinary: z3.boolean().describe("True if conflict comparison was treated as binary data.")
2216
1901
  });
2217
- var projectPageDetailsSchema = z3.object({
2218
- page: projectPageObjectSchema.extend({
2219
- pageInstruction: z3.string().nullable()
1902
+ var dependencySectionSchema = z3.enum([
1903
+ "dependencies",
1904
+ "devDependencies",
1905
+ "peerDependencies"
1906
+ ]);
1907
+ var dependencyToAddSchema = z3.object({
1908
+ packageName: z3.string().describe("Dependency package name that was missing in user package.json."),
1909
+ version: z3.string().describe("Version from exported project's package.json."),
1910
+ section: dependencySectionSchema.describe(
1911
+ "package.json section where dependency should be added."
1912
+ )
1913
+ });
1914
+ var dependencyVersionConflictSchema = z3.object({
1915
+ packageName: z3.string().describe("Dependency package name with version mismatch."),
1916
+ section: dependencySectionSchema.describe(
1917
+ "Section where export expects this dependency."
1918
+ ),
1919
+ exportVersion: z3.string().describe("Version found in exported project's package.json."),
1920
+ userVersion: z3.string().describe("Version currently present in user's package.json.")
1921
+ });
1922
+ var pageDetailsSchema = pageSchema.extend({
1923
+ pageInstruction: z3.string().nullable().describe(
1924
+ "Current instruction text for the active page variant. Null when no active variant is set."
1925
+ )
1926
+ });
1927
+ var pageVariantSchema = z3.object({
1928
+ id: z3.string().describe("Active variant identifier."),
1929
+ name: z3.string().describe("Active variant display name.")
1930
+ }).nullable().describe(
1931
+ "Active page variant. Null when page has no active variant and components are unavailable."
1932
+ );
1933
+ var pageComponentSchema = z3.object({
1934
+ blockId: z3.string().describe("Stable block identifier. Use as primary component instance ID."),
1935
+ componentId: z3.string().describe("Component key from variant blocks payload (block.key)."),
1936
+ componentGroup: z3.string().describe("Component family/group normalized from component key."),
1937
+ slot: z3.string().nullable().describe("Optional slot value from block payload."),
1938
+ props: z3.record(z3.unknown()).nullable().describe("Raw block props payload from PrimeUI.")
1939
+ });
1940
+ var inspectPageReportRowSchema = z3.object({
1941
+ number: z3.number().describe("1-based row number matching report order."),
1942
+ componentGroup: z3.string().describe("Component family/group label."),
1943
+ componentId: z3.string().describe("Component key (block key)."),
1944
+ blockId: z3.string().describe("Primary component instance identifier for future operations."),
1945
+ title: z3.string().nullable().describe("Title extracted by MCP from component props."),
1946
+ description: z3.string().nullable().describe("Description extracted by MCP from component props.")
1947
+ });
1948
+ var projectRootInputSchema = z3.string().optional().describe(
1949
+ "Optional absolute project root path. Use when .primeui/project.json cannot be auto-resolved from current working directory."
1950
+ );
1951
+ var toolErrorOutputSchema = z3.object({
1952
+ error: z3.object({
1953
+ code: z3.string().describe("Machine-readable error code."),
1954
+ message: z3.string().describe("Human-readable error message."),
1955
+ hint: z3.string().optional().describe("Actionable guidance to fix the error.")
1956
+ }).catchall(z3.unknown()).describe("Structured error payload for failed tool execution.")
1957
+ });
1958
+ function withToolErrorOutputSchema(successShape) {
1959
+ return z3.union([z3.object(successShape), toolErrorOutputSchema]);
1960
+ }
1961
+ var TRIGGER_PHRASES = [
1962
+ "import page from PrimeUI",
1963
+ "add page from PrimeUI",
1964
+ "sync pages",
1965
+ "pull page",
1966
+ "transfer page",
1967
+ "get page from PrimeUI",
1968
+ "bring page from PrimeUI",
1969
+ "export from PrimeUI"
1970
+ ].join(", ");
1971
+ var WORKFLOW_SUMMARY = `
1972
+ WORKFLOW ORDER (always follow this sequence):
1973
+ 1. clear_temp -> cleanup stale temp files from previous runs before starting new import flow
1974
+ 2. get_project_info -> discover PrimeUI pages
1975
+ 3. Reconcile PrimeUI pages with local project pages/routes/paths, then confirm target pages with user
1976
+ 4. inspect_page -> inspect selected page components and produce a structured comparison table
1977
+ 5. create_export -> generate export snapshot and local manifest with page file lists + global components
1978
+ 6. download_export -> download to temp directory
1979
+ 7. copy_page -> copy one page safely (repeat for each selected page)
1980
+ 8. Reconcile integration points and resolve reported conflicts, if any
1981
+ `.trim();
1982
+ var initialInstructions = `
1983
+ PrimeUI MCP enables importing pages from PrimeUI Studio into your local project.
1984
+
1985
+ TRIGGER EXAMPLES (semantic intent, not exact phrase matching): ${TRIGGER_PHRASES}
1986
+
1987
+ ${WORKFLOW_SUMMARY}
1988
+
1989
+ CRITICAL RULES FOR PAGE IMPORT:
1990
+ - Import is PAGE-BY-PAGE only. Never import the entire project at once.
1991
+ - Always call clear_temp at the very beginning of a new import flow to avoid stale export state.
1992
+ - After get_project_info, always compare PrimeUI pages against the user's existing local project pages before proposing imports.
1993
+ - Reconciliation report MUST include: page presence match, slug/pagePath/componentsPath path match, and proposed action per page.
1994
+ - Decision matrix:
1995
+ - PrimeUI + local page exists -> candidate for update/review.
1996
+ - PrimeUI exists, local missing -> import candidate.
1997
+ - Local exists, PrimeUI missing -> custom local page, no action unless user asks.
1998
+ - Both exist but path mismatch -> ambiguous, export + compare before deciding.
1999
+ - For targeted component-level analysis on a specific page, call inspect_page before export/copy.
2000
+ - After inspect_page returns report table, supplement that table with local project page state:
2001
+ mark what already exists, what is missing, and what differs.
2002
+ - Before creating export, ask user which pages to add/update (specific pages or all missing pages) and keep that choice in thread context.
2003
+ - create_export response includes:
2004
+ - page-level manifest files per page ('pages[].manifest.files'),
2005
+ - global components list ('export.components').
2006
+ Mention both to user after create_export, because this is the source of truth for copy decisions.
2007
+ - The downloaded export in .primeui/temp/ contains a full standalone Next.js project.
2008
+ Do NOT copy it wholesale.
2009
+ - Always call copy_page for each confirmed page instead of manual file copy.
2010
+ - copy_page performs safe copy only:
2011
+ - new files are copied,
2012
+ - identical files are reported,
2013
+ - conflicting files are NEVER overwritten and are returned with diff.
2014
+ - copy_page reads and validates files only from 'pages[].manifest.files' in local sidecar manifest.
2015
+ - copy_page may add only missing dependencies to user's package.json.
2016
+ It never upgrades existing dependency versions and never runs dependency installation.
2017
+ - After page copy operations, inspect integration points (navigation configs, link menus, route registries, page indexes).
2018
+ - If integration pattern is obvious, apply it and explicitly mark it in the report with "\u26A0 integration update".
2019
+ - If integration pattern is unclear, ask the user before editing integration files.
2020
+ `.trim();
2021
+ var toolGetProjectInfo = {
2022
+ title: "PrimeUI Project Info",
2023
+ description: `ENTRY POINT for all PrimeUI import operations. Always start here. Returns project metadata and the full list of available pages.
2024
+
2025
+ 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}.
2026
+
2027
+ AFTER CALLING:
2028
+ - Scan the user's local project to discover existing pages/routes and related page component folders.
2029
+ - Build a reconciliation report between PrimeUI and local project with action labels:
2030
+ - Page exists in both PrimeUI and local project.
2031
+ - Page exists in PrimeUI but not in local project -> import candidate.
2032
+ - Page exists in local project but not in PrimeUI -> custom local page, no action by default.
2033
+ - Page exists in both but slug/pagePath/componentsPath mapping does not match -> ambiguous, requires export-based comparison before decision.
2034
+ - If user already requested specific pages, first verify those pages exist in PrimeUI and are isReadyToExport: true.
2035
+ - If any requested page is not found or not ready, report this immediately and ask for replacement page choices.
2036
+ - Ask the user which pages to add or update now (specific page list or all missing pages) and keep this decision in thread context.
2037
+ - Import works per page. Multiple pages are allowed, but each page transfer is handled explicitly.
2038
+ - Only after user decision is clear, proceed to create_export.
2039
+
2040
+ ${WORKFLOW_SUMMARY}`,
2041
+ inputSchema: {
2042
+ projectRoot: projectRootInputSchema
2043
+ },
2044
+ outputSchema: withToolErrorOutputSchema({
2045
+ projectId: z3.string().describe(
2046
+ "PrimeUI project identifier associated with the API key and current import session context."
2047
+ ),
2048
+ projectName: z3.string().describe(
2049
+ "Human-readable PrimeUI project name for confirmations in chat."
2050
+ ),
2051
+ metadata: z3.record(z3.unknown()).describe(
2052
+ "Additional project metadata from PrimeUI. May include project-description and other context fields."
2053
+ ),
2054
+ pages: z3.array(pageSchema).describe(
2055
+ "All pages visible for this project context. Filter by user request and isReadyToExport before creating export."
2056
+ )
2057
+ }),
2058
+ annotations: {
2059
+ readOnlyHint: true,
2060
+ destructiveHint: false,
2061
+ idempotentHint: true,
2062
+ openWorldHint: true
2063
+ }
2064
+ };
2065
+ var toolInspectPage = {
2066
+ title: "PrimeUI Inspect Page",
2067
+ description: `Targeted inspection tool for one PrimeUI page. Returns page details, active variant (if present), raw components payload, and a formatted report table.
2068
+
2069
+ WHEN TO USE:
2070
+ - Call this after get_project_info when user wants component-level analysis for a specific page.
2071
+ - Use this before export/copy when user asks to compare or selectively update page sections.
2072
+
2073
+ AFTER CALLING:
2074
+ - Use report + reportRows to present the component table:
2075
+ Number (1-based), ComponentGroup (ComponentId), Title/Description, blockId.
2076
+ - Then supplement this table with the CURRENT local project page state:
2077
+ what already exists, what is missing, and what differs.
2078
+ - Do not assume componentId is unique. Use blockId as the primary component instance identifier.
2079
+
2080
+ If page has no active variant, components is null (not empty array), and report explains why.
2081
+
2082
+ ${WORKFLOW_SUMMARY}`,
2083
+ inputSchema: {
2084
+ projectRoot: projectRootInputSchema,
2085
+ pageSlug: z3.string().describe(
2086
+ "PrimeUI page slug to inspect, for example '/', '/pricing', '/docs/getting-started'."
2087
+ )
2088
+ },
2089
+ outputSchema: withToolErrorOutputSchema({
2090
+ page: pageDetailsSchema.describe(
2091
+ "Single PrimeUI page details resolved by slug with pageInstruction."
2092
+ ),
2093
+ variant: pageVariantSchema,
2094
+ components: z3.array(pageComponentSchema).nullable().describe(
2095
+ "Raw component instances from active variant. Null when page has no active variant."
2096
+ ),
2097
+ report: z3.string().describe(
2098
+ "Formatted report table generated by MCP from component payload (includes extracted title/description summary)."
2099
+ ),
2100
+ reportRows: z3.array(inspectPageReportRowSchema).describe(
2101
+ "Structured report rows with 1-based numbering and extracted title/description fields."
2102
+ )
2103
+ }),
2104
+ annotations: {
2105
+ readOnlyHint: true,
2106
+ destructiveHint: false,
2107
+ idempotentHint: true,
2108
+ openWorldHint: true
2109
+ }
2110
+ };
2111
+ var toolListExports = {
2112
+ title: "PrimeUI Export List",
2113
+ 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 get_project_info instead.
2114
+
2115
+ WHEN TO USE: This is an OPTIONAL helper tool, NOT part of the main import flow. Use it only when:
2116
+ - Something went wrong with an export and you need to check its status.
2117
+ - The user explicitly references a previous export by date or context.
2118
+ - You need to verify whether an export initiated via PrimeUI Studio UI has completed.
2119
+
2120
+ Note: exports can be created both through create_export AND by the user directly in PrimeUI Studio UI. This tool shows all of them.
2121
+
2122
+ Do NOT use this tool as a substitute for create_export. Always create a fresh export unless the user explicitly asks to use a previous one.
2123
+
2124
+ ${WORKFLOW_SUMMARY}`,
2125
+ inputSchema: {
2126
+ projectRoot: projectRootInputSchema
2127
+ },
2128
+ outputSchema: withToolErrorOutputSchema({
2129
+ exports: z3.array(exportItemSchema).describe(
2130
+ "Available exports sorted by API policy. Use item.id to download a specific prior export if user asks."
2131
+ )
2220
2132
  }),
2221
- variant: z3.object({
2222
- id: z3.string(),
2223
- name: z3.string()
2224
- }).nullable(),
2225
- components: z3.array(projectPageComponentSchema).nullable()
2226
- });
2227
- var exportsResponseSchema = z3.object({
2228
- exports: z3.array(
2229
- z3.object({
2230
- id: z3.string(),
2133
+ annotations: {
2134
+ readOnlyHint: true,
2135
+ destructiveHint: false,
2136
+ idempotentHint: true,
2137
+ openWorldHint: true
2138
+ }
2139
+ };
2140
+ var toolCreateExport = {
2141
+ title: "PrimeUI Create Export",
2142
+ description: `Create a new export snapshot from the current PrimeUI project state and wait for completion. Returns strict export manifest payload from API, including page-level file manifests and global components.
2143
+
2144
+ WHEN TO USE: Call this AFTER the user has confirmed which pages they want to import via get_project_info. This tool is REQUIRED before download_export, unless the user explicitly and directly asked to download a specific previous export.
2145
+
2146
+ AFTER CALLING:
2147
+ - Verify that all user-selected pages are present in the export result AND have isReadyToExport: true.
2148
+ - If path mismatch pages were flagged during project-info reconciliation, ensure they are included for deeper comparison after download.
2149
+ - If any required page is missing or not ready, report and pause for user decision before proceeding.
2150
+ - Mention that global components are available in export.components and can be transferred separately if needed.
2151
+ - Local sidecar manifest is saved to .primeui/temp/exports/[exportId].manifest.json from this API payload.
2152
+ - Use the returned export ID to call download_export.
2153
+
2154
+ ${WORKFLOW_SUMMARY}`,
2155
+ inputSchema: {
2156
+ projectRoot: projectRootInputSchema
2157
+ },
2158
+ outputSchema: withToolErrorOutputSchema({
2159
+ export: z3.object({
2160
+ id: z3.string().describe(
2161
+ "Freshly created export identifier. Use this as download_export input.id."
2162
+ ),
2231
2163
  status: exportStatusSchema2,
2232
- createdAt: z3.string().datetime({ offset: true })
2233
- })
2234
- )
2235
- });
2236
- var createExportResponseSchema = z3.object({
2237
- export: z3.object({
2238
- id: z3.string(),
2239
- status: exportStatusSchema2,
2240
- createdAt: z3.string().datetime({ offset: true }),
2241
- expiresAt: z3.string().datetime({ offset: true }).nullable(),
2242
- summary: exportSummarySchema2,
2243
- components: z3.array(exportedComponentSchema)
2164
+ createdAt: z3.string().describe("Export creation timestamp in ISO-8601 UTC format."),
2165
+ expiresAt: z3.string().nullable().describe(
2166
+ "Export expiration timestamp in ISO-8601 UTC format, or null when export has no explicit expiration."
2167
+ ),
2168
+ summary: exportSummarySchema2.describe(
2169
+ "Export run summary with total/successful/failed counters."
2170
+ ),
2171
+ components: z3.array(exportComponentSchema).describe(
2172
+ "Global component export manifest. These components are independent from page-level manifests."
2173
+ )
2174
+ }).describe("Created export summary."),
2175
+ pages: z3.array(exportPageSchema2).describe(
2176
+ "Strict page payload associated with this export, including per-page manifest files list."
2177
+ )
2244
2178
  }),
2245
- pages: z3.array(exportPageSchema2)
2246
- });
2247
- var PrimeUiApiContractError = class extends Error {
2248
- constructor(endpoint, details) {
2249
- super(`PrimeUI API contract mismatch for "${endpoint}": ${details}`);
2250
- this.name = "PrimeUiApiContractError";
2179
+ annotations: {
2180
+ readOnlyHint: false,
2181
+ destructiveHint: false,
2182
+ idempotentHint: false,
2183
+ openWorldHint: true
2251
2184
  }
2252
2185
  };
2253
- var normalizeApiRoot = (rawBaseUrl) => {
2254
- const normalizedBase = rawBaseUrl.trim() || DEFAULT_API_BASE_URL;
2255
- const parsed = new URL(normalizedBase);
2256
- const normalizedPath = parsed.pathname.replace(/\/+$/, "");
2257
- if (normalizedPath.endsWith("/api/v1")) {
2258
- parsed.pathname = `${normalizedPath}/`;
2259
- return parsed.toString();
2186
+ var toolDownloadExport = {
2187
+ title: "PrimeUI Download Export",
2188
+ 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/.
2189
+
2190
+ Do NOT call this as the first step. Requires export ID from create_export. Start with get_project_info instead.
2191
+
2192
+ WHEN TO USE: Call this AFTER create_export has returned a completed export. Requires export ID from create_export so MCP can use the matching local sidecar manifest.
2193
+
2194
+ NOTE:
2195
+ - Download of an export ID without local sidecar manifest will fail.
2196
+ - For previous exports, create a fresh export first when possible.
2197
+
2198
+ AFTER DOWNLOAD:
2199
+ - Use manifestPath from this response as the contract for page slug/path mapping and manifest file lists in copy operations.
2200
+ - For each selected page, call copy_page with:
2201
+ - originPageSlug (required),
2202
+ - actualPageSlug (optional, if route remap is needed).
2203
+ - If there are unresolved conflicts in copy report, stop and ask the user.
2204
+ - Do not force cleanup at flow end; keep downloaded export files available for validation and follow-up checks.
2205
+
2206
+ ${WORKFLOW_SUMMARY}`,
2207
+ inputSchema: {
2208
+ projectRoot: projectRootInputSchema,
2209
+ id: z3.string().describe(
2210
+ "Export identifier to download. Prefer export.id from create_export; use list_exports only on explicit user request."
2211
+ )
2212
+ },
2213
+ outputSchema: withToolErrorOutputSchema({
2214
+ exportId: z3.string().describe(
2215
+ "Downloaded export identifier. Should match the requested input.id for traceability."
2216
+ ),
2217
+ projectPath: z3.string().describe(
2218
+ "Absolute local path to extracted export root in .primeui/temp/exports/[exportId]. Read files from here only."
2219
+ ),
2220
+ manifestPath: z3.string().describe(
2221
+ "Absolute path to sidecar export manifest file (.primeui/temp/exports/[exportId].manifest.json) saved by create_export and required by copy_page."
2222
+ ),
2223
+ pages: z3.array(exportPageSchema2).describe(
2224
+ "Page descriptors loaded from local sidecar export manifest for import decisions."
2225
+ )
2226
+ }),
2227
+ annotations: {
2228
+ readOnlyHint: false,
2229
+ destructiveHint: false,
2230
+ idempotentHint: true,
2231
+ openWorldHint: true
2260
2232
  }
2261
- parsed.pathname = `${normalizedPath}/api/v1/`.replace(
2262
- "//api/v1/",
2263
- "/api/v1/"
2264
- );
2265
- return parsed.toString();
2266
2233
  };
2267
- var isJsonContentType = (contentType) => contentType.includes("application/json") || contentType.includes("+json");
2268
- var isZipContentType = (contentType) => ZIP_CONTENT_TYPES.some((allowedType) => contentType.includes(allowedType));
2269
- var looksLikeZipArchive = (buffer) => {
2270
- if (buffer.length < 4) {
2271
- return false;
2272
- }
2273
- if (buffer[0] !== 80 || buffer[1] !== 75) {
2274
- return false;
2234
+ var toolCopyPage = {
2235
+ title: "PrimeUI Copy Page",
2236
+ 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.
2237
+
2238
+ WHEN TO USE:
2239
+ - Call this AFTER download_export.
2240
+ - Call once per selected page.
2241
+ - Use actualPageSlug only when user wants to remap route during import.
2242
+
2243
+ BEHAVIOR:
2244
+ - Reads export sidecar manifest to resolve originPageSlug -> pagePath/componentsPath.
2245
+ - Copies and validates files strictly from pages[].manifest.files for the selected page.
2246
+ - Never overwrites existing conflicting files.
2247
+ - Reports:
2248
+ a) copied new files,
2249
+ b) identical existing files,
2250
+ c) conflicting files with diff,
2251
+ d) missing dependencies added to package.json,
2252
+ e) dependency version conflicts (report-only, no auto upgrade).
2253
+ - Adds only missing dependencies to package.json (dependencies/devDependencies/peerDependencies).
2254
+ - Never updates existing dependency versions and never runs install commands.
2255
+
2256
+ ${WORKFLOW_SUMMARY}`,
2257
+ inputSchema: {
2258
+ projectRoot: projectRootInputSchema,
2259
+ originPageSlug: z3.string().describe(
2260
+ "Source page slug from PrimeUI project pages list, for example '/', '/pricing', '/docs'."
2261
+ ),
2262
+ actualPageSlug: z3.string().optional().describe(
2263
+ "Optional destination slug in user's project. If omitted, originPageSlug is used."
2264
+ )
2265
+ },
2266
+ outputSchema: withToolErrorOutputSchema({
2267
+ exportId: z3.string().describe("Resolved local export identifier used for copy operation."),
2268
+ exportPath: z3.string().describe("Absolute path to local extracted export project root."),
2269
+ originPageSlug: z3.string().describe("Normalized source page slug requested for copy."),
2270
+ actualPageSlug: z3.string().describe("Normalized destination slug used for target route paths."),
2271
+ sourcePagePath: z3.string().describe("Source page path relative to export root."),
2272
+ targetPagePath: z3.string().describe("Destination page path relative to user project root."),
2273
+ sourceComponentsPath: z3.string().describe("Source components directory relative to export root."),
2274
+ targetComponentsPath: z3.string().describe(
2275
+ "Destination components directory relative to user project root."
2276
+ ),
2277
+ newFiles: z3.array(fileTransferSchema).describe("New files copied into user project without conflicts."),
2278
+ identicalFiles: z3.array(fileTransferSchema).describe(
2279
+ "Existing files in user project that are byte-identical to export files."
2280
+ ),
2281
+ conflictFiles: z3.array(conflictFileSchema).describe(
2282
+ "Files that already exist and differ from export version. Never overwritten."
2283
+ ),
2284
+ addedDependencies: z3.array(dependencyToAddSchema).describe(
2285
+ "Dependencies that were missing and have been added to user's package.json."
2286
+ ),
2287
+ dependenciesVersionConflicts: z3.array(dependencyVersionConflictSchema).describe(
2288
+ "Dependencies with version mismatch between export and user project. Report only."
2289
+ ),
2290
+ summary: z3.object({
2291
+ totalCandidateFiles: z3.number().describe(
2292
+ "Total number of candidate files considered for page copy from pages[].manifest.files."
2293
+ ),
2294
+ copiedFiles: z3.number().describe("Number of files copied as new."),
2295
+ identicalFiles: z3.number().describe("Number of files detected as identical."),
2296
+ conflictFiles: z3.number().describe("Number of conflicting files not copied."),
2297
+ addedDependencies: z3.number().describe("Count of dependencies added to package.json."),
2298
+ dependenciesVersionConflicts: z3.number().describe("Count of dependency version mismatches reported.")
2299
+ })
2300
+ }),
2301
+ annotations: {
2302
+ readOnlyHint: false,
2303
+ destructiveHint: false,
2304
+ idempotentHint: false,
2305
+ openWorldHint: false
2275
2306
  }
2276
- const signature = `${buffer[2]}:${buffer[3]}`;
2277
- return signature === "3:4" || signature === "5:6" || signature === "7:8";
2278
- };
2279
- var createZipSignatureGuard = (endpoint) => {
2280
- let signature = Buffer.alloc(0);
2281
- let validated = false;
2282
- return new Transform({
2283
- transform(chunk, _encoding, callback) {
2284
- const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2285
- if (!validated) {
2286
- const requiredBytes = Math.max(0, 4 - signature.length);
2287
- if (requiredBytes > 0) {
2288
- signature = Buffer.concat([
2289
- signature,
2290
- chunkBuffer.subarray(0, requiredBytes)
2291
- ]);
2292
- }
2293
- if (signature.length >= 4) {
2294
- if (!looksLikeZipArchive(signature)) {
2295
- callback(
2296
- new PrimeUiApiContractError(
2297
- endpoint,
2298
- "response body is not a valid zip archive"
2299
- )
2300
- );
2301
- return;
2302
- }
2303
- validated = true;
2304
- }
2305
- }
2306
- callback(null, chunkBuffer);
2307
- },
2308
- flush(callback) {
2309
- if (!validated) {
2310
- callback(
2311
- new PrimeUiApiContractError(
2312
- endpoint,
2313
- "response body is not a valid zip archive"
2314
- )
2315
- );
2316
- return;
2317
- }
2318
- callback();
2319
- }
2320
- });
2321
2307
  };
2322
- var ApiProjectDataProvider = class {
2323
- apiKey;
2324
- apiRoot;
2325
- constructor(options) {
2326
- this.apiKey = options.apiKey;
2327
- this.apiRoot = normalizeApiRoot(options.baseUrl ?? DEFAULT_API_BASE_URL);
2328
- }
2329
- async getProjectInfo() {
2330
- return this.requestJson("project", projectInfoSchema);
2331
- }
2332
- async getProjectPageBySlug(slug) {
2333
- const encodedSlug = encodeURIComponent(slug);
2334
- return this.requestJson(
2335
- `project/page?slug=${encodedSlug}`,
2336
- projectPageDetailsSchema
2337
- );
2338
- }
2339
- async listExports() {
2340
- const response = await this.requestJson(
2341
- "project/exports",
2342
- exportsResponseSchema
2343
- );
2344
- return response.exports;
2345
- }
2346
- async createExport() {
2347
- return this.requestJson("project/exports", createExportResponseSchema, {
2348
- method: "POST"
2349
- });
2308
+ var toolClearTemp = {
2309
+ title: "PrimeUI Clear Temp",
2310
+ description: `Delete all files in .primeui/temp/ and recreate empty temp structure.
2311
+
2312
+ Call this at the START of a new import flow, before get_project_info/create_export, to reset stale temp state from previous runs.
2313
+
2314
+ WHEN TO USE: Call once at the beginning of each new import flow. Do not call this between page imports in the same flow.
2315
+
2316
+ Safe to call multiple times. Only affects .primeui/temp/ directory - never touches the user's project files.
2317
+
2318
+ ${WORKFLOW_SUMMARY}`,
2319
+ inputSchema: {
2320
+ projectRoot: projectRootInputSchema
2321
+ },
2322
+ outputSchema: withToolErrorOutputSchema({
2323
+ success: z3.boolean().describe(
2324
+ "True when temp cleanup completed and baseline temp structure was recreated."
2325
+ )
2326
+ }),
2327
+ annotations: {
2328
+ readOnlyHint: false,
2329
+ destructiveHint: false,
2330
+ idempotentHint: true,
2331
+ openWorldHint: false
2350
2332
  }
2351
- /**
2352
- * Consumer-side runtime contract for GET /api/v1/project/exports/:exportId/download.
2353
- * Producer source of truth: apps/studio/src/app/api/v1/project/exports/[exportId]/download/route.ts
2354
- * Keep content-type and archive format checks synchronized with the producer response.
2355
- */
2356
- async downloadExportArchive(exportId, destinationPath) {
2357
- const endpoint = `project/exports/${encodeURIComponent(exportId)}/download`;
2358
- const apiKey = this.requireApiKey();
2359
- const response = await fetch(this.buildUrl(endpoint), {
2360
- method: "GET",
2361
- headers: {
2362
- Authorization: `Bearer ${apiKey}`
2333
+ };
2334
+
2335
+ // src/server.ts
2336
+ function okResult(title, data) {
2337
+ return {
2338
+ content: [{ type: "text", text: `${title} completed` }],
2339
+ structuredContent: data
2340
+ };
2341
+ }
2342
+ function errorResult(error) {
2343
+ if (error instanceof PrimeUiProjectConfigError) {
2344
+ const text = error.hint ? `primeui mcp error: ${error.message}
2345
+ Hint: ${error.hint}` : `primeui mcp error: ${error.message}`;
2346
+ return {
2347
+ isError: true,
2348
+ content: [{ type: "text", text }],
2349
+ structuredContent: {
2350
+ error: {
2351
+ code: error.code,
2352
+ message: error.message,
2353
+ ...error.hint ? { hint: error.hint } : {},
2354
+ ...error.details
2355
+ }
2363
2356
  }
2364
- });
2365
- if (!response.ok) {
2366
- const details = await this.readError(response);
2367
- throw new Error(
2368
- `PrimeUI API request failed (${response.status}) while downloading export "${exportId}": ${details}`
2369
- );
2370
- }
2371
- const contentType = response.headers.get("content-type") ?? "";
2372
- if (!isZipContentType(contentType)) {
2373
- throw new PrimeUiApiContractError(
2374
- endpoint,
2375
- `expected zip content-type but got "${contentType || "unknown"}"`
2376
- );
2377
- }
2378
- if (!response.body) {
2379
- throw new PrimeUiApiContractError(endpoint, "response body is empty");
2380
- }
2381
- await ensureDir(path7.dirname(destinationPath));
2382
- const zipStream = Readable.fromWeb(
2383
- response.body
2384
- );
2385
- const signatureGuard = createZipSignatureGuard(endpoint);
2386
- const fileStream = createWriteStream(destinationPath);
2387
- try {
2388
- await pipeline(zipStream, signatureGuard, fileStream);
2389
- } catch (error) {
2390
- await unlink(destinationPath).catch(() => void 0);
2391
- throw error;
2392
- }
2393
- }
2394
- buildUrl(endpoint) {
2395
- return new URL(endpoint, this.apiRoot).toString();
2357
+ };
2396
2358
  }
2397
- async requestJson(endpoint, schema, requestInit = {}) {
2398
- const apiKey = this.requireApiKey();
2399
- const method = requestInit.method ?? "GET";
2400
- const response = await fetch(this.buildUrl(endpoint), {
2401
- ...requestInit,
2402
- method,
2403
- headers: {
2404
- ...requestInit.headers ?? {},
2405
- Authorization: `Bearer ${apiKey}`
2359
+ const message = error instanceof Error ? error.message : String(error);
2360
+ return {
2361
+ isError: true,
2362
+ content: [{ type: "text", text: `primeui mcp error: ${message}` }],
2363
+ structuredContent: {
2364
+ error: {
2365
+ code: "UNKNOWN_ERROR",
2366
+ message
2406
2367
  }
2407
- });
2408
- if (!response.ok) {
2409
- const details = await this.readError(response);
2410
- throw new Error(
2411
- `PrimeUI API request failed (${response.status}) for "${endpoint}": ${details}`
2412
- );
2413
2368
  }
2414
- const contentType = response.headers.get("content-type") ?? "";
2415
- if (!isJsonContentType(contentType)) {
2416
- throw new PrimeUiApiContractError(
2417
- endpoint,
2418
- `expected JSON content-type but got "${contentType || "unknown"}"`
2419
- );
2369
+ };
2370
+ }
2371
+ function createPrimeUiMcpServer(source) {
2372
+ const server = new McpServer(
2373
+ {
2374
+ name: "primeui-mcp-server",
2375
+ version: "0.1.0"
2376
+ },
2377
+ {
2378
+ instructions: initialInstructions
2420
2379
  }
2421
- let payload;
2422
- try {
2423
- payload = await response.json();
2424
- } catch {
2425
- throw new PrimeUiApiContractError(
2426
- endpoint,
2427
- "response body is not valid JSON"
2428
- );
2380
+ );
2381
+ server.registerTool(
2382
+ "get_project_info",
2383
+ toolGetProjectInfo,
2384
+ async ({ projectRoot }) => {
2385
+ try {
2386
+ const info = await source.getProjectInfo({
2387
+ projectRoot
2388
+ });
2389
+ return okResult("get_project_info", info);
2390
+ } catch (error) {
2391
+ return errorResult(error);
2392
+ }
2429
2393
  }
2430
- const parsed = schema.safeParse(payload);
2431
- if (!parsed.success) {
2432
- const details = parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
2433
- throw new PrimeUiApiContractError(endpoint, details);
2394
+ );
2395
+ server.registerTool(
2396
+ "inspect_page",
2397
+ toolInspectPage,
2398
+ async ({ pageSlug, projectRoot }) => {
2399
+ try {
2400
+ const result = await source.inspectPage(
2401
+ pageSlug,
2402
+ { projectRoot }
2403
+ );
2404
+ return okResult("inspect_page", result);
2405
+ } catch (error) {
2406
+ return errorResult(error);
2407
+ }
2434
2408
  }
2435
- return parsed.data;
2436
- }
2437
- async readError(response) {
2438
- const contentType = response.headers.get("content-type") ?? "";
2439
- try {
2440
- if (isJsonContentType(contentType)) {
2441
- const body = await response.json();
2442
- if (body?.message && body?.code) {
2443
- return `${body.code}: ${body.message}`;
2444
- }
2445
- if (body?.message) {
2446
- return body.message;
2447
- }
2448
- } else {
2449
- const bodyText = await response.text();
2450
- if (bodyText.trim()) {
2451
- return bodyText.trim();
2452
- }
2409
+ );
2410
+ server.registerTool(
2411
+ "list_exports",
2412
+ toolListExports,
2413
+ async ({ projectRoot }) => {
2414
+ try {
2415
+ const exportsList = await source.listExports({
2416
+ projectRoot
2417
+ });
2418
+ return okResult("list_exports", { exports: exportsList });
2419
+ } catch (error) {
2420
+ return errorResult(error);
2453
2421
  }
2454
- } catch {
2455
2422
  }
2456
- return response.statusText || "Unknown error";
2457
- }
2458
- requireApiKey() {
2459
- const apiKey = this.apiKey?.trim();
2460
- if (!apiKey) {
2461
- throw new Error("PRIMEUI_API_KEY is required to call PrimeUI API tools.");
2423
+ );
2424
+ server.registerTool(
2425
+ "create_export",
2426
+ toolCreateExport,
2427
+ async ({ projectRoot }) => {
2428
+ try {
2429
+ const result = await source.createExport({
2430
+ projectRoot
2431
+ });
2432
+ return okResult("create_export", result);
2433
+ } catch (error) {
2434
+ return errorResult(error);
2435
+ }
2462
2436
  }
2463
- return apiKey;
2464
- }
2465
- };
2466
-
2467
- // src/service.ts
2468
- function getProjectConfigFallbackCwds() {
2469
- return [process.env.INIT_CWD, process.env.PWD].map((value) => value?.trim()).filter((value) => Boolean(value));
2470
- }
2471
- async function createProjectSyncService(rootsFallbackCwds) {
2472
- const resolvedProjectConfig = await resolvePrimeUiProjectConfig({
2473
- cwd: process.cwd(),
2474
- projectRootFromEnv: process.env.PRIMEUI_PROJECT_ROOT,
2475
- fallbackCwds: [...getProjectConfigFallbackCwds(), ...rootsFallbackCwds]
2476
- });
2477
- const projectRoot = resolvedProjectConfig.projectRoot;
2478
- const targetProjectRoot = path8.resolve(
2479
- projectRoot,
2480
- resolvedProjectConfig.projectConfig.targetProjectPath
2481
2437
  );
2482
- console.error("[primeui-mcp] RESOLVED_PROJECT_ROOT:", projectRoot);
2483
- console.error(
2484
- "[primeui-mcp] RESOLVED_PROJECT_CONFIG:",
2485
- resolvedProjectConfig.projectConfigPath
2438
+ server.registerTool(
2439
+ "download_export",
2440
+ toolDownloadExport,
2441
+ async ({ id, projectRoot }) => {
2442
+ try {
2443
+ const result = await source.downloadExportById(
2444
+ id,
2445
+ { projectRoot }
2446
+ );
2447
+ return okResult("download_export", result);
2448
+ } catch (error) {
2449
+ return errorResult(error);
2450
+ }
2451
+ }
2486
2452
  );
2487
- const apiKey = await resolvePrimeUiApiKey({
2488
- projectConfig: resolvedProjectConfig.projectConfig,
2489
- apiKeyFromEnv: process.env.PRIMEUI_API_KEY
2490
- });
2491
- const provider = new ApiProjectDataProvider({
2492
- apiKey,
2493
- baseUrl: process.env.PRIMEUI_API_BASE_URL
2494
- });
2495
- return new ProjectSyncService({
2496
- projectRoot,
2497
- targetProjectRoot,
2498
- provider
2499
- });
2500
- }
2501
- var LazyProjectSyncSource = class {
2502
- constructor(getRootsFallbackCwds) {
2503
- this.getRootsFallbackCwds = getRootsFallbackCwds;
2504
- }
2505
- async withService(operation) {
2506
- const service = await createProjectSyncService(this.getRootsFallbackCwds());
2507
- return operation(service);
2508
- }
2509
- async getProjectInfo() {
2510
- return this.withService((service) => service.getProjectInfo());
2511
- }
2512
- async listExports() {
2513
- return this.withService((service) => service.listExports());
2514
- }
2515
- async createExport() {
2516
- return this.withService((service) => service.createExport());
2517
- }
2518
- async downloadExportById(id) {
2519
- return this.withService((service) => service.downloadExportById(id));
2520
- }
2521
- async inspectPage(slug) {
2522
- return this.withService((service) => service.inspectPage(slug));
2523
- }
2524
- async copyPage(originPageSlug, actualPageSlug) {
2525
- return this.withService(
2526
- (service) => service.copyPage(originPageSlug, actualPageSlug)
2527
- );
2528
- }
2529
- async clearTemp() {
2530
- return this.withService((service) => service.clearTemp());
2531
- }
2532
- async healthCheck() {
2533
- const rootsFallbackCwds = this.getRootsFallbackCwds();
2534
- const result = await runPrimeUiHealthCheck({
2535
- cwd: process.cwd(),
2536
- initCwd: process.env.INIT_CWD,
2537
- pwd: process.env.PWD,
2538
- projectRootFromEnv: process.env.PRIMEUI_PROJECT_ROOT,
2539
- apiBaseUrlFromEnv: process.env.PRIMEUI_API_BASE_URL,
2540
- apiKeyFromEnv: process.env.PRIMEUI_API_KEY,
2541
- fallbackCwds: [...getProjectConfigFallbackCwds(), ...rootsFallbackCwds],
2542
- rootsListFallbackCwds: rootsFallbackCwds
2543
- });
2544
- if (result.config.found) {
2545
- console.error("[primeui-mcp] RESOLVED_PROJECT_ROOT:", result.config.projectRoot);
2546
- console.error(
2547
- "[primeui-mcp] RESOLVED_PROJECT_CONFIG:",
2548
- result.config.projectConfigPath
2549
- );
2550
- } else {
2551
- console.error("[primeui-mcp] RESOLUTION_FAILED:", result.config.error);
2453
+ server.registerTool(
2454
+ "clear_temp",
2455
+ toolClearTemp,
2456
+ async ({ projectRoot }) => {
2457
+ try {
2458
+ await source.clearTemp({ projectRoot });
2459
+ return okResult("clear_temp", { success: true });
2460
+ } catch (error) {
2461
+ return errorResult(error);
2462
+ }
2552
2463
  }
2553
- return result;
2554
- }
2555
- };
2556
- function toFileRootPath(rootUri) {
2557
- try {
2558
- const parsed = new URL(rootUri);
2559
- if (parsed.protocol !== "file:") {
2560
- return null;
2464
+ );
2465
+ server.registerTool(
2466
+ "copy_page",
2467
+ toolCopyPage,
2468
+ async ({
2469
+ originPageSlug,
2470
+ actualPageSlug,
2471
+ projectRoot
2472
+ }) => {
2473
+ try {
2474
+ const result = await source.copyPage(
2475
+ originPageSlug,
2476
+ actualPageSlug,
2477
+ { projectRoot }
2478
+ );
2479
+ return okResult("copy_page", result);
2480
+ } catch (error) {
2481
+ return errorResult(error);
2482
+ }
2561
2483
  }
2562
- return fileURLToPath(parsed);
2563
- } catch {
2564
- return null;
2565
- }
2566
- }
2567
- async function loadClientRootFallbackCwds(server) {
2568
- try {
2569
- const response = await server.server.listRoots(void 0, { timeout: 1500 });
2570
- const roots = response.roots.map((root) => root.uri);
2571
- const resolvedRoots = roots.map((rootUri) => toFileRootPath(rootUri)).filter((rootPath) => Boolean(rootPath));
2572
- console.error("[primeui-mcp] roots/list supported:", JSON.stringify(roots));
2573
- console.error(
2574
- "[primeui-mcp] roots/list file roots:",
2575
- JSON.stringify(resolvedRoots)
2576
- );
2577
- return resolvedRoots;
2578
- } catch (error) {
2579
- const message = error instanceof Error ? error.message : String(error);
2580
- console.error("[primeui-mcp] roots/list unavailable:", message);
2581
- return [];
2582
- }
2484
+ );
2485
+ return server;
2583
2486
  }
2487
+
2488
+ // src/service.ts
2584
2489
  async function main() {
2585
- console.error("CWD:", process.cwd());
2586
- console.error("INIT_CWD:", process.env.INIT_CWD);
2587
- console.error("PWD:", process.env.PWD);
2588
- let rootsFallbackCwds = [];
2589
- const server = createPrimeUiMcpServer(
2590
- new LazyProjectSyncSource(() => rootsFallbackCwds)
2591
- );
2490
+ const source = new LazyProjectSyncSource();
2491
+ const server = createPrimeUiMcpServer(source);
2592
2492
  const transport = new StdioServerTransport();
2593
2493
  await server.connect(transport);
2594
- rootsFallbackCwds = await loadClientRootFallbackCwds(server);
2595
2494
  }
2596
2495
  main().catch((error) => {
2597
2496
  console.error("[primeui-mcp] failed to start", error);