@primeuicom/mcp 0.1.20 → 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/README.md +15 -10
- package/dist/service.js +1324 -1508
- package/dist/service.js.map +1 -1
- package/package.json +2 -5
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
|
|
58
|
-
|
|
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
|
|
66
|
-
|
|
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
|
|
73
|
-
|
|
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
|
}
|
|
@@ -90,35 +121,104 @@ function buildPrimeUiProjectSearchRoots(options) {
|
|
|
90
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
|
-
|
|
97
|
-
|
|
171
|
+
const resolvedEnvRoot = path.resolve(envProjectRoot);
|
|
172
|
+
const envProjectConfigPath = path.join(
|
|
173
|
+
resolvedEnvRoot,
|
|
98
174
|
PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
|
|
99
175
|
);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
120
|
-
|
|
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,886 +235,52 @@ async function resolvePrimeUiApiKey(options) {
|
|
|
135
235
|
}
|
|
136
236
|
const projectConfigApiKey = options.projectConfig.apiKey.trim();
|
|
137
237
|
if (!projectConfigApiKey) {
|
|
138
|
-
throw new
|
|
139
|
-
|
|
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/
|
|
146
|
-
import
|
|
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
|
-
codexWorkspaceRoot: z2.string().nullable().describe(
|
|
281
|
-
"CODEX_WORKSPACE_ROOT provided by Codex clients (tool-call scoped in some clients)."
|
|
282
|
-
),
|
|
283
|
-
vscodeCwd: z2.string().nullable().describe("VSCODE_CWD environment value, if provided by the runtime."),
|
|
284
|
-
initCwd: z2.string().nullable().describe("INIT_CWD environment value, if provided by the client runtime."),
|
|
285
|
-
pwd: z2.string().nullable().describe("PWD environment value, if provided by the client runtime."),
|
|
286
|
-
searchRoots: z2.array(z2.string()).describe(
|
|
287
|
-
"Resolved directories used when searching for .primeui/project.json."
|
|
288
|
-
),
|
|
289
|
-
rootsListFallbackCwds: z2.array(z2.string()).describe(
|
|
290
|
-
"File-system roots derived from MCP roots/list and used as search fallbacks."
|
|
291
|
-
)
|
|
292
|
-
});
|
|
293
|
-
var healthConfigSchema = z2.object({
|
|
294
|
-
found: z2.boolean().describe("True when .primeui/project.json was found and parsed successfully."),
|
|
295
|
-
projectRoot: z2.string().nullable().describe("Resolved PrimeUI project root directory (parent of .primeui)."),
|
|
296
|
-
projectConfigPath: z2.string().nullable().describe("Absolute path to .primeui/project.json when found."),
|
|
297
|
-
targetProjectPath: z2.string().nullable().describe("targetProjectPath value loaded from project config."),
|
|
298
|
-
targetProjectRoot: z2.string().nullable().describe("Absolute target project root derived from targetProjectPath."),
|
|
299
|
-
source: z2.enum(["env.PRIMEUI_PROJECT_ROOT", "search"]).describe("How project config lookup was resolved."),
|
|
300
|
-
error: z2.string().nullable().describe("Human-readable resolution error when config is not available.")
|
|
301
|
-
});
|
|
302
|
-
var TRIGGER_PHRASES = [
|
|
303
|
-
"import page from PrimeUI",
|
|
304
|
-
"add page from PrimeUI",
|
|
305
|
-
"sync pages",
|
|
306
|
-
"pull page",
|
|
307
|
-
"transfer page",
|
|
308
|
-
"get page from PrimeUI",
|
|
309
|
-
"bring page from PrimeUI",
|
|
310
|
-
"export from PrimeUI"
|
|
311
|
-
].join(", ");
|
|
312
|
-
var WORKFLOW_SUMMARY = `
|
|
313
|
-
WORKFLOW ORDER (always follow this sequence):
|
|
314
|
-
1. clear_temp -> cleanup stale temp files from previous runs before starting new import flow
|
|
315
|
-
2. get_project_info -> discover PrimeUI pages
|
|
316
|
-
3. Reconcile PrimeUI pages with local project pages/routes/paths, then confirm target pages with user
|
|
317
|
-
4. inspect_page -> inspect selected page components and produce a structured comparison table
|
|
318
|
-
5. create_export -> generate export snapshot and local manifest with page file lists + global components
|
|
319
|
-
6. download_export -> download to temp directory
|
|
320
|
-
7. copy_page -> copy one page safely (repeat for each selected page)
|
|
321
|
-
8. Reconcile integration points and resolve reported conflicts, if any
|
|
322
|
-
`.trim();
|
|
323
|
-
var initialInstructions = `
|
|
324
|
-
PrimeUI MCP enables importing pages from PrimeUI Studio into your local project.
|
|
325
|
-
|
|
326
|
-
TRIGGER EXAMPLES (semantic intent, not exact phrase matching): ${TRIGGER_PHRASES}
|
|
327
|
-
|
|
328
|
-
${WORKFLOW_SUMMARY}
|
|
329
|
-
|
|
330
|
-
CRITICAL RULES FOR PAGE IMPORT:
|
|
331
|
-
- Import is PAGE-BY-PAGE only. Never import the entire project at once.
|
|
332
|
-
- Use health_check to inspect runtime path/config resolution when setup issues occur.
|
|
333
|
-
- Always call clear_temp at the very beginning of a new import flow to avoid stale export state.
|
|
334
|
-
- After get_project_info, always compare PrimeUI pages against the user's existing local project pages before proposing imports.
|
|
335
|
-
- Reconciliation report MUST include: page presence match, slug/pagePath/componentsPath path match, and proposed action per page.
|
|
336
|
-
- Decision matrix:
|
|
337
|
-
- PrimeUI + local page exists -> candidate for update/review.
|
|
338
|
-
- PrimeUI exists, local missing -> import candidate.
|
|
339
|
-
- Local exists, PrimeUI missing -> custom local page, no action unless user asks.
|
|
340
|
-
- Both exist but path mismatch -> ambiguous, export + compare before deciding.
|
|
341
|
-
- For targeted component-level analysis on a specific page, call inspect_page before export/copy.
|
|
342
|
-
- After inspect_page returns report table, supplement that table with local project page state:
|
|
343
|
-
mark what already exists, what is missing, and what differs.
|
|
344
|
-
- Before creating export, ask user which pages to add/update (specific pages or all missing pages) and keep that choice in thread context.
|
|
345
|
-
- create_export response includes:
|
|
346
|
-
- page-level manifest files per page ('pages[].manifest.files'),
|
|
347
|
-
- global components list ('export.components').
|
|
348
|
-
Mention both to user after create_export, because this is the source of truth for copy decisions.
|
|
349
|
-
- The downloaded export in .primeui/temp/ contains a full standalone Next.js project.
|
|
350
|
-
Do NOT copy it wholesale.
|
|
351
|
-
- Always call copy_page for each confirmed page instead of manual file copy.
|
|
352
|
-
- copy_page performs safe copy only:
|
|
353
|
-
- new files are copied,
|
|
354
|
-
- identical files are reported,
|
|
355
|
-
- conflicting files are NEVER overwritten and are returned with diff.
|
|
356
|
-
- copy_page reads and validates files only from 'pages[].manifest.files' in local sidecar manifest.
|
|
357
|
-
- copy_page may add only missing dependencies to user's package.json.
|
|
358
|
-
It never upgrades existing dependency versions and never runs dependency installation.
|
|
359
|
-
- After page copy operations, inspect integration points (navigation configs, link menus, route registries, page indexes).
|
|
360
|
-
- If integration pattern is obvious, apply it and explicitly mark it in the report with "\u26A0 integration update".
|
|
361
|
-
- If integration pattern is unclear, ask the user before editing integration files.
|
|
362
|
-
`.trim();
|
|
363
|
-
var toolHealthCheck = {
|
|
364
|
-
title: "PrimeUI Health Check",
|
|
365
|
-
description: `Diagnostic tool for MCP runtime setup. This tool MUST NOT fail: it always returns structured diagnostics even when project config is missing.
|
|
366
|
-
|
|
367
|
-
WHEN TO USE:
|
|
368
|
-
- Use this first when PrimeUI MCP tools fail due to project path/config issues.
|
|
369
|
-
- 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";
|
|
370
250
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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}`);
|
|
389
273
|
}
|
|
390
|
-
}
|
|
391
|
-
var toolGetProjectInfo = {
|
|
392
|
-
title: "PrimeUI Project Info",
|
|
393
|
-
description: `ENTRY POINT for all PrimeUI import operations. Always start here. Returns project metadata and the full list of available pages.
|
|
394
|
-
|
|
395
|
-
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
|
+
}
|
|
396
275
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
- Page exists in both PrimeUI and local project.
|
|
401
|
-
- Page exists in PrimeUI but not in local project -> import candidate.
|
|
402
|
-
- Page exists in local project but not in PrimeUI -> custom local page, no action by default.
|
|
403
|
-
- Page exists in both but slug/pagePath/componentsPath mapping does not match -> ambiguous, requires export-based comparison before decision.
|
|
404
|
-
- If user already requested specific pages, first verify those pages exist in PrimeUI and are isReadyToExport: true.
|
|
405
|
-
- If any requested page is not found or not ready, report this immediately and ask for replacement page choices.
|
|
406
|
-
- Ask the user which pages to add or update now (specific page list or all missing pages) and keep this decision in thread context.
|
|
407
|
-
- Import works per page. Multiple pages are allowed, but each page transfer is handled explicitly.
|
|
408
|
-
- Only after user decision is clear, proceed to create_export.
|
|
409
|
-
|
|
410
|
-
${WORKFLOW_SUMMARY}`,
|
|
411
|
-
inputSchema: {},
|
|
412
|
-
outputSchema: {
|
|
413
|
-
projectId: z2.string().describe(
|
|
414
|
-
"PrimeUI project identifier associated with the API key and current import session context."
|
|
415
|
-
),
|
|
416
|
-
projectName: z2.string().describe(
|
|
417
|
-
"Human-readable PrimeUI project name for confirmations in chat."
|
|
418
|
-
),
|
|
419
|
-
metadata: z2.record(z2.unknown()).describe(
|
|
420
|
-
"Additional project metadata from PrimeUI. May include project-description and other context fields."
|
|
421
|
-
),
|
|
422
|
-
pages: z2.array(pageSchema).describe(
|
|
423
|
-
"All pages visible for this project context. Filter by user request and isReadyToExport before creating export."
|
|
424
|
-
)
|
|
425
|
-
},
|
|
426
|
-
annotations: {
|
|
427
|
-
readOnlyHint: true,
|
|
428
|
-
destructiveHint: false,
|
|
429
|
-
idempotentHint: true,
|
|
430
|
-
openWorldHint: true
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
var toolInspectPage = {
|
|
434
|
-
title: "PrimeUI Inspect Page",
|
|
435
|
-
description: `Targeted inspection tool for one PrimeUI page. Returns page details, active variant (if present), raw components payload, and a formatted report table.
|
|
436
|
-
|
|
437
|
-
WHEN TO USE:
|
|
438
|
-
- Call this after get_project_info when user wants component-level analysis for a specific page.
|
|
439
|
-
- Use this before export/copy when user asks to compare or selectively update page sections.
|
|
440
|
-
|
|
441
|
-
AFTER CALLING:
|
|
442
|
-
- Use report + reportRows to present the component table:
|
|
443
|
-
Number (1-based), ComponentGroup (ComponentId), Title/Description, blockId.
|
|
444
|
-
- Then supplement this table with the CURRENT local project page state:
|
|
445
|
-
what already exists, what is missing, and what differs.
|
|
446
|
-
- Do not assume componentId is unique. Use blockId as the primary component instance identifier.
|
|
447
|
-
|
|
448
|
-
If page has no active variant, components is null (not empty array), and report explains why.
|
|
449
|
-
|
|
450
|
-
${WORKFLOW_SUMMARY}`,
|
|
451
|
-
inputSchema: {
|
|
452
|
-
pageSlug: z2.string().describe(
|
|
453
|
-
"PrimeUI page slug to inspect, for example '/', '/pricing', '/docs/getting-started'."
|
|
454
|
-
)
|
|
455
|
-
},
|
|
456
|
-
outputSchema: {
|
|
457
|
-
page: pageDetailsSchema.describe(
|
|
458
|
-
"Single PrimeUI page details resolved by slug with pageInstruction."
|
|
459
|
-
),
|
|
460
|
-
variant: pageVariantSchema,
|
|
461
|
-
components: z2.array(pageComponentSchema).nullable().describe(
|
|
462
|
-
"Raw component instances from active variant. Null when page has no active variant."
|
|
463
|
-
),
|
|
464
|
-
report: z2.string().describe(
|
|
465
|
-
"Formatted report table generated by MCP from component payload (includes extracted title/description summary)."
|
|
466
|
-
),
|
|
467
|
-
reportRows: z2.array(inspectPageReportRowSchema).describe(
|
|
468
|
-
"Structured report rows with 1-based numbering and extracted title/description fields."
|
|
469
|
-
)
|
|
470
|
-
},
|
|
471
|
-
annotations: {
|
|
472
|
-
readOnlyHint: true,
|
|
473
|
-
destructiveHint: false,
|
|
474
|
-
idempotentHint: true,
|
|
475
|
-
openWorldHint: true
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
var toolListExports = {
|
|
479
|
-
title: "PrimeUI Export List",
|
|
480
|
-
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.
|
|
481
|
-
|
|
482
|
-
WHEN TO USE: This is an OPTIONAL helper tool, NOT part of the main import flow. Use it only when:
|
|
483
|
-
- Something went wrong with an export and you need to check its status.
|
|
484
|
-
- The user explicitly references a previous export by date or context.
|
|
485
|
-
- You need to verify whether an export initiated via PrimeUI Studio UI has completed.
|
|
486
|
-
|
|
487
|
-
Note: exports can be created both through create_export AND by the user directly in PrimeUI Studio UI. This tool shows all of them.
|
|
488
|
-
|
|
489
|
-
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.
|
|
490
|
-
|
|
491
|
-
${WORKFLOW_SUMMARY}`,
|
|
492
|
-
inputSchema: {},
|
|
493
|
-
outputSchema: {
|
|
494
|
-
exports: z2.array(exportItemSchema).describe(
|
|
495
|
-
"Available exports sorted by API policy. Use item.id to download a specific prior export if user asks."
|
|
496
|
-
)
|
|
497
|
-
},
|
|
498
|
-
annotations: {
|
|
499
|
-
readOnlyHint: true,
|
|
500
|
-
destructiveHint: false,
|
|
501
|
-
idempotentHint: true,
|
|
502
|
-
openWorldHint: true
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
var toolCreateExport = {
|
|
506
|
-
title: "PrimeUI Create Export",
|
|
507
|
-
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.
|
|
508
|
-
|
|
509
|
-
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.
|
|
510
|
-
|
|
511
|
-
AFTER CALLING:
|
|
512
|
-
- Verify that all user-selected pages are present in the export result AND have isReadyToExport: true.
|
|
513
|
-
- If path mismatch pages were flagged during project-info reconciliation, ensure they are included for deeper comparison after download.
|
|
514
|
-
- If any required page is missing or not ready, report and pause for user decision before proceeding.
|
|
515
|
-
- Mention that global components are available in export.components and can be transferred separately if needed.
|
|
516
|
-
- Local sidecar manifest is saved to .primeui/temp/exports/[exportId].manifest.json from this API payload.
|
|
517
|
-
- Use the returned export ID to call download_export.
|
|
518
|
-
|
|
519
|
-
${WORKFLOW_SUMMARY}`,
|
|
520
|
-
inputSchema: {},
|
|
521
|
-
outputSchema: {
|
|
522
|
-
export: z2.object({
|
|
523
|
-
id: z2.string().describe(
|
|
524
|
-
"Freshly created export identifier. Use this as download_export input.id."
|
|
525
|
-
),
|
|
526
|
-
status: exportStatusSchema,
|
|
527
|
-
createdAt: z2.string().describe("Export creation timestamp in ISO-8601 UTC format."),
|
|
528
|
-
expiresAt: z2.string().nullable().describe(
|
|
529
|
-
"Export expiration timestamp in ISO-8601 UTC format, or null when export has no explicit expiration."
|
|
530
|
-
),
|
|
531
|
-
summary: exportSummarySchema.describe(
|
|
532
|
-
"Export run summary with total/successful/failed counters."
|
|
533
|
-
),
|
|
534
|
-
components: z2.array(exportComponentSchema).describe(
|
|
535
|
-
"Global component export manifest. These components are independent from page-level manifests."
|
|
536
|
-
)
|
|
537
|
-
}).describe("Created export summary."),
|
|
538
|
-
pages: z2.array(exportPageSchema).describe(
|
|
539
|
-
"Strict page payload associated with this export, including per-page manifest files list."
|
|
540
|
-
)
|
|
541
|
-
},
|
|
542
|
-
annotations: {
|
|
543
|
-
readOnlyHint: false,
|
|
544
|
-
destructiveHint: false,
|
|
545
|
-
idempotentHint: false,
|
|
546
|
-
openWorldHint: true
|
|
547
|
-
}
|
|
548
|
-
};
|
|
549
|
-
var toolDownloadExport = {
|
|
550
|
-
title: "PrimeUI Download Export",
|
|
551
|
-
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/.
|
|
552
|
-
|
|
553
|
-
Do NOT call this as the first step. Requires export ID from create_export. Start with get_project_info instead.
|
|
554
|
-
|
|
555
|
-
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.
|
|
556
|
-
|
|
557
|
-
NOTE:
|
|
558
|
-
- Download of an export ID without local sidecar manifest will fail.
|
|
559
|
-
- For previous exports, create a fresh export first when possible.
|
|
560
|
-
|
|
561
|
-
AFTER DOWNLOAD:
|
|
562
|
-
- Use manifestPath from this response as the contract for page slug/path mapping and manifest file lists in copy operations.
|
|
563
|
-
- For each selected page, call copy_page with:
|
|
564
|
-
- originPageSlug (required),
|
|
565
|
-
- actualPageSlug (optional, if route remap is needed).
|
|
566
|
-
- If there are unresolved conflicts in copy report, stop and ask the user.
|
|
567
|
-
- Do not force cleanup at flow end; keep downloaded export files available for validation and follow-up checks.
|
|
568
|
-
|
|
569
|
-
${WORKFLOW_SUMMARY}`,
|
|
570
|
-
inputSchema: {
|
|
571
|
-
id: z2.string().describe(
|
|
572
|
-
"Export identifier to download. Prefer export.id from create_export; use list_exports only on explicit user request."
|
|
573
|
-
)
|
|
574
|
-
},
|
|
575
|
-
outputSchema: {
|
|
576
|
-
exportId: z2.string().describe(
|
|
577
|
-
"Downloaded export identifier. Should match the requested input.id for traceability."
|
|
578
|
-
),
|
|
579
|
-
projectPath: z2.string().describe(
|
|
580
|
-
"Absolute local path to extracted export root in .primeui/temp/exports/[exportId]. Read files from here only."
|
|
581
|
-
),
|
|
582
|
-
manifestPath: z2.string().describe(
|
|
583
|
-
"Absolute path to sidecar export manifest file (.primeui/temp/exports/[exportId].manifest.json) saved by create_export and required by copy_page."
|
|
584
|
-
),
|
|
585
|
-
pages: z2.array(exportPageSchema).describe(
|
|
586
|
-
"Page descriptors loaded from local sidecar export manifest for import decisions."
|
|
587
|
-
)
|
|
588
|
-
},
|
|
589
|
-
annotations: {
|
|
590
|
-
readOnlyHint: false,
|
|
591
|
-
destructiveHint: false,
|
|
592
|
-
idempotentHint: true,
|
|
593
|
-
openWorldHint: true
|
|
594
|
-
}
|
|
595
|
-
};
|
|
596
|
-
var toolCopyPage = {
|
|
597
|
-
title: "PrimeUI Copy Page",
|
|
598
|
-
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.
|
|
599
|
-
|
|
600
|
-
WHEN TO USE:
|
|
601
|
-
- Call this AFTER download_export.
|
|
602
|
-
- Call once per selected page.
|
|
603
|
-
- Use actualPageSlug only when user wants to remap route during import.
|
|
604
|
-
|
|
605
|
-
BEHAVIOR:
|
|
606
|
-
- Reads export sidecar manifest to resolve originPageSlug -> pagePath/componentsPath.
|
|
607
|
-
- Copies and validates files strictly from pages[].manifest.files for the selected page.
|
|
608
|
-
- Never overwrites existing conflicting files.
|
|
609
|
-
- Reports:
|
|
610
|
-
a) copied new files,
|
|
611
|
-
b) identical existing files,
|
|
612
|
-
c) conflicting files with diff,
|
|
613
|
-
d) missing dependencies added to package.json,
|
|
614
|
-
e) dependency version conflicts (report-only, no auto upgrade).
|
|
615
|
-
- Adds only missing dependencies to package.json (dependencies/devDependencies/peerDependencies).
|
|
616
|
-
- Never updates existing dependency versions and never runs install commands.
|
|
617
|
-
|
|
618
|
-
${WORKFLOW_SUMMARY}`,
|
|
619
|
-
inputSchema: {
|
|
620
|
-
originPageSlug: z2.string().describe(
|
|
621
|
-
"Source page slug from PrimeUI project pages list, for example '/', '/pricing', '/docs'."
|
|
622
|
-
),
|
|
623
|
-
actualPageSlug: z2.string().optional().describe(
|
|
624
|
-
"Optional destination slug in user's project. If omitted, originPageSlug is used."
|
|
625
|
-
)
|
|
626
|
-
},
|
|
627
|
-
outputSchema: {
|
|
628
|
-
exportId: z2.string().describe("Resolved local export identifier used for copy operation."),
|
|
629
|
-
exportPath: z2.string().describe("Absolute path to local extracted export project root."),
|
|
630
|
-
originPageSlug: z2.string().describe("Normalized source page slug requested for copy."),
|
|
631
|
-
actualPageSlug: z2.string().describe("Normalized destination slug used for target route paths."),
|
|
632
|
-
sourcePagePath: z2.string().describe("Source page path relative to export root."),
|
|
633
|
-
targetPagePath: z2.string().describe("Destination page path relative to user project root."),
|
|
634
|
-
sourceComponentsPath: z2.string().describe("Source components directory relative to export root."),
|
|
635
|
-
targetComponentsPath: z2.string().describe(
|
|
636
|
-
"Destination components directory relative to user project root."
|
|
637
|
-
),
|
|
638
|
-
newFiles: z2.array(fileTransferSchema).describe("New files copied into user project without conflicts."),
|
|
639
|
-
identicalFiles: z2.array(fileTransferSchema).describe(
|
|
640
|
-
"Existing files in user project that are byte-identical to export files."
|
|
641
|
-
),
|
|
642
|
-
conflictFiles: z2.array(conflictFileSchema).describe(
|
|
643
|
-
"Files that already exist and differ from export version. Never overwritten."
|
|
644
|
-
),
|
|
645
|
-
addedDependencies: z2.array(dependencyToAddSchema).describe(
|
|
646
|
-
"Dependencies that were missing and have been added to user's package.json."
|
|
647
|
-
),
|
|
648
|
-
dependenciesVersionConflicts: z2.array(dependencyVersionConflictSchema).describe(
|
|
649
|
-
"Dependencies with version mismatch between export and user project. Report only."
|
|
650
|
-
),
|
|
651
|
-
summary: z2.object({
|
|
652
|
-
totalCandidateFiles: z2.number().describe(
|
|
653
|
-
"Total number of candidate files considered for page copy from pages[].manifest.files."
|
|
654
|
-
),
|
|
655
|
-
copiedFiles: z2.number().describe("Number of files copied as new."),
|
|
656
|
-
identicalFiles: z2.number().describe("Number of files detected as identical."),
|
|
657
|
-
conflictFiles: z2.number().describe("Number of conflicting files not copied."),
|
|
658
|
-
addedDependencies: z2.number().describe("Count of dependencies added to package.json."),
|
|
659
|
-
dependenciesVersionConflicts: z2.number().describe("Count of dependency version mismatches reported.")
|
|
660
|
-
})
|
|
661
|
-
},
|
|
662
|
-
annotations: {
|
|
663
|
-
readOnlyHint: false,
|
|
664
|
-
destructiveHint: false,
|
|
665
|
-
idempotentHint: false,
|
|
666
|
-
openWorldHint: false
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
var toolClearTemp = {
|
|
670
|
-
title: "PrimeUI Clear Temp",
|
|
671
|
-
description: `Delete all files in .primeui/temp/ and recreate empty temp structure.
|
|
672
|
-
|
|
673
|
-
Call this at the START of a new import flow, before get_project_info/create_export, to reset stale temp state from previous runs.
|
|
674
|
-
|
|
675
|
-
WHEN TO USE: Call once at the beginning of each new import flow. Do not call this between page imports in the same flow.
|
|
676
|
-
|
|
677
|
-
Safe to call multiple times. Only affects .primeui/temp/ directory - never touches the user's project files.
|
|
678
|
-
|
|
679
|
-
${WORKFLOW_SUMMARY}`,
|
|
680
|
-
inputSchema: {},
|
|
681
|
-
outputSchema: {
|
|
682
|
-
success: z2.boolean().describe(
|
|
683
|
-
"True when temp cleanup completed and baseline temp structure was recreated."
|
|
684
|
-
)
|
|
685
|
-
},
|
|
686
|
-
annotations: {
|
|
687
|
-
readOnlyHint: false,
|
|
688
|
-
destructiveHint: false,
|
|
689
|
-
idempotentHint: true,
|
|
690
|
-
openWorldHint: false
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
// src/server.ts
|
|
695
|
-
function okResult(title, data) {
|
|
696
|
-
return {
|
|
697
|
-
content: [{ type: "text", text: `${title} completed` }],
|
|
698
|
-
structuredContent: data
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
function errorResult(error) {
|
|
702
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
703
|
-
return {
|
|
704
|
-
isError: true,
|
|
705
|
-
content: [{ type: "text", text: `primeui mcp error: ${message}` }]
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
function createPrimeUiMcpServer(source) {
|
|
709
|
-
const server = new McpServer(
|
|
710
|
-
{
|
|
711
|
-
name: "primeui-mcp-server",
|
|
712
|
-
version: "0.1.0"
|
|
713
|
-
},
|
|
714
|
-
{
|
|
715
|
-
instructions: initialInstructions
|
|
716
|
-
}
|
|
717
|
-
);
|
|
718
|
-
server.registerTool(
|
|
719
|
-
"health_check",
|
|
720
|
-
toolHealthCheck,
|
|
721
|
-
async () => {
|
|
722
|
-
try {
|
|
723
|
-
const health = await source.healthCheck();
|
|
724
|
-
return okResult("health_check", health);
|
|
725
|
-
} catch (error) {
|
|
726
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
727
|
-
return okResult("health_check", {
|
|
728
|
-
status: "degraded",
|
|
729
|
-
runtime: {
|
|
730
|
-
cwd: process.cwd(),
|
|
731
|
-
codexWorkspaceRoot: process.env.CODEX_WORKSPACE_ROOT?.trim() || null,
|
|
732
|
-
vscodeCwd: process.env.VSCODE_CWD?.trim() || null,
|
|
733
|
-
initCwd: process.env.INIT_CWD?.trim() || null,
|
|
734
|
-
pwd: process.env.PWD?.trim() || null,
|
|
735
|
-
searchRoots: [process.cwd()],
|
|
736
|
-
rootsListFallbackCwds: []
|
|
737
|
-
},
|
|
738
|
-
config: {
|
|
739
|
-
found: false,
|
|
740
|
-
projectRoot: null,
|
|
741
|
-
projectConfigPath: null,
|
|
742
|
-
targetProjectPath: null,
|
|
743
|
-
targetProjectRoot: null,
|
|
744
|
-
source: "search",
|
|
745
|
-
error: `health_check fallback: ${message}`
|
|
746
|
-
},
|
|
747
|
-
options: []
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
);
|
|
752
|
-
server.registerTool(
|
|
753
|
-
"get_project_info",
|
|
754
|
-
toolGetProjectInfo,
|
|
755
|
-
async () => {
|
|
756
|
-
try {
|
|
757
|
-
const info = await source.getProjectInfo();
|
|
758
|
-
return okResult("get_project_info", info);
|
|
759
|
-
} catch (error) {
|
|
760
|
-
return errorResult(error);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
);
|
|
764
|
-
server.registerTool(
|
|
765
|
-
"inspect_page",
|
|
766
|
-
toolInspectPage,
|
|
767
|
-
async ({ pageSlug }) => {
|
|
768
|
-
try {
|
|
769
|
-
const result = await source.inspectPage(pageSlug);
|
|
770
|
-
return okResult("inspect_page", result);
|
|
771
|
-
} catch (error) {
|
|
772
|
-
return errorResult(error);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
);
|
|
776
|
-
server.registerTool(
|
|
777
|
-
"list_exports",
|
|
778
|
-
toolListExports,
|
|
779
|
-
async () => {
|
|
780
|
-
try {
|
|
781
|
-
const exportsList = await source.listExports();
|
|
782
|
-
return okResult("list_exports", { exports: exportsList });
|
|
783
|
-
} catch (error) {
|
|
784
|
-
return errorResult(error);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
);
|
|
788
|
-
server.registerTool(
|
|
789
|
-
"create_export",
|
|
790
|
-
toolCreateExport,
|
|
791
|
-
async () => {
|
|
792
|
-
try {
|
|
793
|
-
const result = await source.createExport();
|
|
794
|
-
return okResult("create_export", result);
|
|
795
|
-
} catch (error) {
|
|
796
|
-
return errorResult(error);
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
);
|
|
800
|
-
server.registerTool(
|
|
801
|
-
"download_export",
|
|
802
|
-
toolDownloadExport,
|
|
803
|
-
async ({ id }) => {
|
|
804
|
-
try {
|
|
805
|
-
const result = await source.downloadExportById(id);
|
|
806
|
-
return okResult("download_export", result);
|
|
807
|
-
} catch (error) {
|
|
808
|
-
return errorResult(error);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
);
|
|
812
|
-
server.registerTool(
|
|
813
|
-
"clear_temp",
|
|
814
|
-
toolClearTemp,
|
|
815
|
-
async () => {
|
|
816
|
-
try {
|
|
817
|
-
await source.clearTemp();
|
|
818
|
-
return okResult("clear_temp", { success: true });
|
|
819
|
-
} catch (error) {
|
|
820
|
-
return errorResult(error);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
);
|
|
824
|
-
server.registerTool(
|
|
825
|
-
"copy_page",
|
|
826
|
-
toolCopyPage,
|
|
827
|
-
async ({ originPageSlug, actualPageSlug }) => {
|
|
828
|
-
try {
|
|
829
|
-
const result = await source.copyPage(
|
|
830
|
-
originPageSlug,
|
|
831
|
-
actualPageSlug
|
|
832
|
-
);
|
|
833
|
-
return okResult("copy_page", result);
|
|
834
|
-
} catch (error) {
|
|
835
|
-
return errorResult(error);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
);
|
|
839
|
-
return server;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// src/services/health-check-service.ts
|
|
843
|
-
import path2 from "path";
|
|
844
|
-
function normalizeOptional(value) {
|
|
845
|
-
const trimmed = value?.trim();
|
|
846
|
-
return trimmed ? trimmed : null;
|
|
847
|
-
}
|
|
848
|
-
function buildHealthOptions(input) {
|
|
849
|
-
return [
|
|
850
|
-
{
|
|
851
|
-
key: "CODEX_WORKSPACE_ROOT",
|
|
852
|
-
description: "Preferred workspace root from Codex clients. May appear only during tool calls.",
|
|
853
|
-
currentValue: input.codexWorkspaceRoot
|
|
854
|
-
},
|
|
855
|
-
{
|
|
856
|
-
key: "VSCODE_CWD",
|
|
857
|
-
description: "Workspace cwd hint set by some VS Code runtimes. Used as project search fallback.",
|
|
858
|
-
currentValue: input.vscodeCwd
|
|
859
|
-
},
|
|
860
|
-
{
|
|
861
|
-
key: "INIT_CWD",
|
|
862
|
-
description: "Install/runtime cwd hint. Used as fallback when workspace-specific env is unavailable.",
|
|
863
|
-
currentValue: input.initCwd
|
|
864
|
-
},
|
|
865
|
-
{
|
|
866
|
-
key: "PWD",
|
|
867
|
-
description: "Shell working directory hint. Used as fallback when workspace-specific env is unavailable.",
|
|
868
|
-
currentValue: input.pwd
|
|
869
|
-
},
|
|
870
|
-
{
|
|
871
|
-
key: "PRIMEUI_PROJECT_ROOT",
|
|
872
|
-
description: "Optional absolute root containing .primeui/project.json. Use as explicit override.",
|
|
873
|
-
currentValue: input.projectRootFromEnv
|
|
874
|
-
},
|
|
875
|
-
{
|
|
876
|
-
key: "PRIMEUI_API_BASE_URL",
|
|
877
|
-
description: "Optional PrimeUI API base URL override.",
|
|
878
|
-
currentValue: input.apiBaseUrlFromEnv
|
|
879
|
-
},
|
|
880
|
-
{
|
|
881
|
-
key: "PRIMEUI_API_KEY",
|
|
882
|
-
description: "Optional API key override. If missing, MCP uses apiKey from .primeui/project.json.",
|
|
883
|
-
currentValue: input.apiKeyFromEnv ? "set" : null
|
|
884
|
-
},
|
|
885
|
-
{
|
|
886
|
-
key: "Fallback search",
|
|
887
|
-
description: "When PRIMEUI_PROJECT_ROOT is not set, MCP prioritizes CODEX_WORKSPACE_ROOT/VSCODE_CWD, then roots/list file roots, INIT_CWD, PWD, and finally process.cwd().",
|
|
888
|
-
currentValue: null
|
|
889
|
-
}
|
|
890
|
-
];
|
|
891
|
-
}
|
|
892
|
-
async function runPrimeUiHealthCheck(input) {
|
|
893
|
-
const projectRootFromEnv = normalizeOptional(input.projectRootFromEnv);
|
|
894
|
-
const apiBaseUrlFromEnv = normalizeOptional(input.apiBaseUrlFromEnv);
|
|
895
|
-
const apiKeyFromEnv = normalizeOptional(input.apiKeyFromEnv);
|
|
896
|
-
const codexWorkspaceRoot = normalizeOptional(input.codexWorkspaceRoot);
|
|
897
|
-
const vscodeCwd = normalizeOptional(input.vscodeCwd);
|
|
898
|
-
const initCwd = normalizeOptional(input.initCwd);
|
|
899
|
-
const pwd = normalizeOptional(input.pwd);
|
|
900
|
-
const rootsListFallbackCwds = (input.rootsListFallbackCwds ?? []).map((value) => value.trim()).filter(Boolean).map((value) => path2.resolve(value));
|
|
901
|
-
const fallbackCwds = (input.fallbackCwds ?? []).map((value) => value.trim()).filter(Boolean);
|
|
902
|
-
const searchRoots = buildPrimeUiProjectSearchRoots({
|
|
903
|
-
cwd: input.cwd,
|
|
904
|
-
fallbackCwds
|
|
905
|
-
});
|
|
906
|
-
try {
|
|
907
|
-
const resolvedProjectConfig = await resolvePrimeUiProjectConfig({
|
|
908
|
-
cwd: input.cwd,
|
|
909
|
-
projectRootFromEnv: projectRootFromEnv ?? void 0,
|
|
910
|
-
fallbackCwds
|
|
911
|
-
});
|
|
912
|
-
const targetProjectPath = resolvedProjectConfig.projectConfig.targetProjectPath;
|
|
913
|
-
return {
|
|
914
|
-
status: "ok",
|
|
915
|
-
runtime: {
|
|
916
|
-
cwd: path2.resolve(input.cwd),
|
|
917
|
-
codexWorkspaceRoot,
|
|
918
|
-
vscodeCwd,
|
|
919
|
-
initCwd,
|
|
920
|
-
pwd,
|
|
921
|
-
searchRoots,
|
|
922
|
-
rootsListFallbackCwds
|
|
923
|
-
},
|
|
924
|
-
config: {
|
|
925
|
-
found: true,
|
|
926
|
-
projectRoot: resolvedProjectConfig.projectRoot,
|
|
927
|
-
projectConfigPath: resolvedProjectConfig.projectConfigPath,
|
|
928
|
-
targetProjectPath,
|
|
929
|
-
targetProjectRoot: path2.resolve(
|
|
930
|
-
resolvedProjectConfig.projectRoot,
|
|
931
|
-
targetProjectPath
|
|
932
|
-
),
|
|
933
|
-
source: projectRootFromEnv ? "env.PRIMEUI_PROJECT_ROOT" : "search",
|
|
934
|
-
error: null
|
|
935
|
-
},
|
|
936
|
-
options: buildHealthOptions({
|
|
937
|
-
projectRootFromEnv,
|
|
938
|
-
apiBaseUrlFromEnv,
|
|
939
|
-
apiKeyFromEnv,
|
|
940
|
-
codexWorkspaceRoot,
|
|
941
|
-
vscodeCwd,
|
|
942
|
-
initCwd,
|
|
943
|
-
pwd
|
|
944
|
-
})
|
|
945
|
-
};
|
|
946
|
-
} catch (error) {
|
|
947
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
948
|
-
return {
|
|
949
|
-
status: "degraded",
|
|
950
|
-
runtime: {
|
|
951
|
-
cwd: path2.resolve(input.cwd),
|
|
952
|
-
codexWorkspaceRoot,
|
|
953
|
-
vscodeCwd,
|
|
954
|
-
initCwd,
|
|
955
|
-
pwd,
|
|
956
|
-
searchRoots,
|
|
957
|
-
rootsListFallbackCwds
|
|
958
|
-
},
|
|
959
|
-
config: {
|
|
960
|
-
found: false,
|
|
961
|
-
projectRoot: null,
|
|
962
|
-
projectConfigPath: null,
|
|
963
|
-
targetProjectPath: null,
|
|
964
|
-
targetProjectRoot: null,
|
|
965
|
-
source: projectRootFromEnv ? "env.PRIMEUI_PROJECT_ROOT" : "search",
|
|
966
|
-
error: message
|
|
967
|
-
},
|
|
968
|
-
options: buildHealthOptions({
|
|
969
|
-
projectRootFromEnv,
|
|
970
|
-
apiBaseUrlFromEnv,
|
|
971
|
-
apiKeyFromEnv,
|
|
972
|
-
codexWorkspaceRoot,
|
|
973
|
-
vscodeCwd,
|
|
974
|
-
initCwd,
|
|
975
|
-
pwd
|
|
976
|
-
})
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// src/services/project-sync-service.ts
|
|
982
|
-
import path6 from "path";
|
|
983
|
-
import { readFile as readFile4 } from "fs/promises";
|
|
984
|
-
|
|
985
|
-
// src/lib/fs.ts
|
|
986
|
-
import { mkdir, rm, writeFile } from "fs/promises";
|
|
987
|
-
import path3 from "path";
|
|
988
|
-
import extractZipArchive from "extract-zip";
|
|
989
|
-
async function ensureDir(dirPath) {
|
|
990
|
-
await mkdir(dirPath, { recursive: true });
|
|
991
|
-
}
|
|
992
|
-
async function resetDir(dirPath) {
|
|
993
|
-
await rm(dirPath, { recursive: true, force: true });
|
|
994
|
-
await mkdir(dirPath, { recursive: true });
|
|
995
|
-
}
|
|
996
|
-
async function writeUtf8(filePath, content) {
|
|
997
|
-
await ensureDir(path3.dirname(filePath));
|
|
998
|
-
await writeFile(filePath, content, "utf-8");
|
|
999
|
-
}
|
|
1000
|
-
async function extractZip(zipPath, targetDir) {
|
|
1001
|
-
await ensureDir(targetDir);
|
|
1002
|
-
try {
|
|
1003
|
-
await extractZipArchive(zipPath, { dir: targetDir });
|
|
1004
|
-
} catch (error) {
|
|
1005
|
-
const details = error instanceof Error ? error.message : String(error);
|
|
1006
|
-
throw new Error(`Failed to extract zip at "${zipPath}". ${details}`);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// src/services/page-copy-service.ts
|
|
1011
|
-
import { readFile as readFile3, readdir, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
1012
|
-
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";
|
|
1013
279
|
|
|
1014
280
|
// src/lib/import-graph.ts
|
|
1015
281
|
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
1016
282
|
import { builtinModules } from "module";
|
|
1017
|
-
import
|
|
283
|
+
import path3 from "path";
|
|
1018
284
|
var INTERNAL_EXTENSIONS = [
|
|
1019
285
|
".ts",
|
|
1020
286
|
".tsx",
|
|
@@ -1090,7 +356,7 @@ async function pathExists(filePath) {
|
|
|
1090
356
|
}
|
|
1091
357
|
}
|
|
1092
358
|
async function resolveFileCandidate(candidateBase) {
|
|
1093
|
-
const ext =
|
|
359
|
+
const ext = path3.extname(candidateBase);
|
|
1094
360
|
if (ext) {
|
|
1095
361
|
if (await pathExists(candidateBase)) {
|
|
1096
362
|
return candidateBase;
|
|
@@ -1110,7 +376,7 @@ async function resolveFileCandidate(candidateBase) {
|
|
|
1110
376
|
}
|
|
1111
377
|
if (stats.isDirectory()) {
|
|
1112
378
|
for (const extension of INTERNAL_EXTENSIONS) {
|
|
1113
|
-
const indexCandidate =
|
|
379
|
+
const indexCandidate = path3.join(candidateBase, `index${extension}`);
|
|
1114
380
|
if (await pathExists(indexCandidate)) {
|
|
1115
381
|
return indexCandidate;
|
|
1116
382
|
}
|
|
@@ -1132,13 +398,13 @@ async function resolveImportToFile(projectRoot, importerFilePath, specifier) {
|
|
|
1132
398
|
}
|
|
1133
399
|
let candidateBase = null;
|
|
1134
400
|
if (specifier.startsWith("@/")) {
|
|
1135
|
-
candidateBase =
|
|
401
|
+
candidateBase = path3.join(projectRoot, "src", specifier.slice(2));
|
|
1136
402
|
} else if (specifier.startsWith("@root/")) {
|
|
1137
|
-
candidateBase =
|
|
403
|
+
candidateBase = path3.join(projectRoot, specifier.slice(6));
|
|
1138
404
|
} else if (specifier.startsWith(".")) {
|
|
1139
|
-
candidateBase =
|
|
405
|
+
candidateBase = path3.resolve(path3.dirname(importerFilePath), specifier);
|
|
1140
406
|
} else if (specifier.startsWith("/")) {
|
|
1141
|
-
candidateBase =
|
|
407
|
+
candidateBase = path3.join(projectRoot, specifier.slice(1));
|
|
1142
408
|
} else {
|
|
1143
409
|
return { kind: "unknown" };
|
|
1144
410
|
}
|
|
@@ -1152,12 +418,12 @@ async function resolveImportToFile(projectRoot, importerFilePath, specifier) {
|
|
|
1152
418
|
};
|
|
1153
419
|
}
|
|
1154
420
|
function shouldParseFile(filePath) {
|
|
1155
|
-
return PARSEABLE_EXTENSIONS.has(
|
|
421
|
+
return PARSEABLE_EXTENSIONS.has(path3.extname(filePath).toLowerCase());
|
|
1156
422
|
}
|
|
1157
423
|
async function buildImportGraph(input) {
|
|
1158
424
|
const shouldFollowInternalImports = input.followInternalImports ?? true;
|
|
1159
|
-
const projectRoot =
|
|
1160
|
-
const queue = input.entryFiles.map((filePath) =>
|
|
425
|
+
const projectRoot = path3.resolve(input.projectRoot);
|
|
426
|
+
const queue = input.entryFiles.map((filePath) => path3.resolve(filePath));
|
|
1161
427
|
const visited = /* @__PURE__ */ new Set();
|
|
1162
428
|
const internalFiles = /* @__PURE__ */ new Set();
|
|
1163
429
|
const externalPackages = /* @__PURE__ */ new Set();
|
|
@@ -1410,10 +676,10 @@ function normalizeSlug2(slug) {
|
|
|
1410
676
|
return withLeadingSlash.replace(/\/+$/, "") || "/";
|
|
1411
677
|
}
|
|
1412
678
|
function toPosixPath(value) {
|
|
1413
|
-
return value.split(
|
|
679
|
+
return value.split(path4.sep).join("/");
|
|
1414
680
|
}
|
|
1415
681
|
function toProjectRelative(rootPath, absolutePath) {
|
|
1416
|
-
return toPosixPath(
|
|
682
|
+
return toPosixPath(path4.relative(rootPath, absolutePath));
|
|
1417
683
|
}
|
|
1418
684
|
function escapeRegExp(value) {
|
|
1419
685
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -1470,7 +736,7 @@ function buildPlannedSourceBuffer(sourceBuffer, sourceFilePath, importRewritePla
|
|
|
1470
736
|
if (!importRewritePlan) {
|
|
1471
737
|
return sourceBuffer;
|
|
1472
738
|
}
|
|
1473
|
-
const extension =
|
|
739
|
+
const extension = path4.extname(sourceFilePath).toLowerCase();
|
|
1474
740
|
if (!REWRITABLE_IMPORT_EXTENSIONS.has(extension)) {
|
|
1475
741
|
return sourceBuffer;
|
|
1476
742
|
}
|
|
@@ -1572,9 +838,9 @@ async function resolveManifestCandidateFiles(input) {
|
|
|
1572
838
|
);
|
|
1573
839
|
}
|
|
1574
840
|
const normalizedRelative = toPosixPath(trimmed).replace(/^\.\/+/, "");
|
|
1575
|
-
const absolutePath =
|
|
1576
|
-
const relative =
|
|
1577
|
-
if (relative.startsWith("..") ||
|
|
841
|
+
const absolutePath = path4.resolve(input.exportPath, normalizedRelative);
|
|
842
|
+
const relative = path4.relative(input.exportPath, absolutePath);
|
|
843
|
+
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
1578
844
|
throw new Error(
|
|
1579
845
|
`Export manifest for page "${input.pageSlug}" contains path outside export root: ${trimmed}`
|
|
1580
846
|
);
|
|
@@ -1605,12 +871,12 @@ async function resolveSingleExportDirectory(exportsRoot) {
|
|
|
1605
871
|
const exportId = exportDirectories[0] ?? "";
|
|
1606
872
|
return {
|
|
1607
873
|
exportId,
|
|
1608
|
-
exportPath:
|
|
874
|
+
exportPath: path4.join(exportsRoot, exportId)
|
|
1609
875
|
};
|
|
1610
876
|
}
|
|
1611
877
|
function ensureSafeTargetPath(projectRoot, targetPath) {
|
|
1612
|
-
const relative =
|
|
1613
|
-
if (relative.startsWith("..") ||
|
|
878
|
+
const relative = path4.relative(projectRoot, targetPath);
|
|
879
|
+
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
1614
880
|
throw new Error(
|
|
1615
881
|
`Refusing to write outside project root. Computed target: ${targetPath}`
|
|
1616
882
|
);
|
|
@@ -1668,19 +934,19 @@ function resolveTargetFilePath(input) {
|
|
|
1668
934
|
if (sourceFilePath === sourcePagePath) {
|
|
1669
935
|
return targetPagePath;
|
|
1670
936
|
}
|
|
1671
|
-
const componentsPrefix = `${sourceComponentsPath}${
|
|
937
|
+
const componentsPrefix = `${sourceComponentsPath}${path4.sep}`;
|
|
1672
938
|
if (sourceFilePath === sourceComponentsPath || sourceFilePath.startsWith(componentsPrefix)) {
|
|
1673
|
-
const relativeToComponents =
|
|
939
|
+
const relativeToComponents = path4.relative(
|
|
1674
940
|
sourceComponentsPath,
|
|
1675
941
|
sourceFilePath
|
|
1676
942
|
);
|
|
1677
|
-
return
|
|
943
|
+
return path4.join(targetComponentsPath, relativeToComponents);
|
|
1678
944
|
}
|
|
1679
|
-
const relativeToExport =
|
|
1680
|
-
if (relativeToExport.startsWith("..") ||
|
|
945
|
+
const relativeToExport = path4.relative(exportRoot, sourceFilePath);
|
|
946
|
+
if (relativeToExport.startsWith("..") || path4.isAbsolute(relativeToExport)) {
|
|
1681
947
|
throw new Error(`Source file is outside export root: ${sourceFilePath}`);
|
|
1682
948
|
}
|
|
1683
|
-
return
|
|
949
|
+
return path4.join(projectRoot, relativeToExport);
|
|
1684
950
|
}
|
|
1685
951
|
async function copyPageFromExport(input) {
|
|
1686
952
|
const normalizedOriginSlug = normalizeSlug2(input.originPageSlug);
|
|
@@ -1691,7 +957,7 @@ async function copyPageFromExport(input) {
|
|
|
1691
957
|
const { exportId, exportPath } = await resolveSingleExportDirectory(
|
|
1692
958
|
input.exportsRoot
|
|
1693
959
|
);
|
|
1694
|
-
const manifestPath =
|
|
960
|
+
const manifestPath = path4.join(
|
|
1695
961
|
input.exportsRoot,
|
|
1696
962
|
`${exportId}.manifest.json`
|
|
1697
963
|
);
|
|
@@ -1714,8 +980,8 @@ async function copyPageFromExport(input) {
|
|
|
1714
980
|
`Page not found in manifest for slug: ${normalizedOriginSlug}`
|
|
1715
981
|
);
|
|
1716
982
|
}
|
|
1717
|
-
const sourcePagePath =
|
|
1718
|
-
const sourceComponentsPath =
|
|
983
|
+
const sourcePagePath = path4.join(exportPath, page.pagePath);
|
|
984
|
+
const sourceComponentsPath = path4.join(exportPath, page.componentsPath);
|
|
1719
985
|
const sourcePageStats = await stat3(sourcePagePath).catch(() => null);
|
|
1720
986
|
if (!sourcePageStats?.isFile()) {
|
|
1721
987
|
throw new Error(`Source page file not found: ${sourcePagePath}`);
|
|
@@ -1735,8 +1001,8 @@ async function copyPageFromExport(input) {
|
|
|
1735
1001
|
pageType: page.pageType,
|
|
1736
1002
|
slug: normalizedActualSlug
|
|
1737
1003
|
});
|
|
1738
|
-
const targetPagePath =
|
|
1739
|
-
const targetComponentsPath =
|
|
1004
|
+
const targetPagePath = path4.join(input.projectRoot, targetPaths.pagePath);
|
|
1005
|
+
const targetComponentsPath = path4.join(
|
|
1740
1006
|
input.projectRoot,
|
|
1741
1007
|
targetPaths.componentsPath
|
|
1742
1008
|
);
|
|
@@ -1782,7 +1048,7 @@ async function copyPageFromExport(input) {
|
|
|
1782
1048
|
const sourceRelative = toProjectRelative(exportPath, sourceFilePath);
|
|
1783
1049
|
const targetRelative = toProjectRelative(input.projectRoot, targetFilePath);
|
|
1784
1050
|
if (!targetBuffer) {
|
|
1785
|
-
await ensureDir(
|
|
1051
|
+
await ensureDir(path4.dirname(targetFilePath));
|
|
1786
1052
|
await writeFile2(targetFilePath, plannedSourceBuffer);
|
|
1787
1053
|
newFiles.push({
|
|
1788
1054
|
sourcePath: sourceRelative,
|
|
@@ -1811,8 +1077,8 @@ async function copyPageFromExport(input) {
|
|
|
1811
1077
|
isBinary
|
|
1812
1078
|
});
|
|
1813
1079
|
}
|
|
1814
|
-
const exportPackageJsonPath =
|
|
1815
|
-
const userPackageJsonPath =
|
|
1080
|
+
const exportPackageJsonPath = path4.join(exportPath, "package.json");
|
|
1081
|
+
const userPackageJsonPath = path4.join(input.projectRoot, "package.json");
|
|
1816
1082
|
if (!await fileExists2(exportPackageJsonPath)) {
|
|
1817
1083
|
throw new Error(`Export package.json not found: ${exportPackageJsonPath}`);
|
|
1818
1084
|
}
|
|
@@ -2018,7 +1284,7 @@ function isExportPage(value) {
|
|
|
2018
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);
|
|
2019
1285
|
}
|
|
2020
1286
|
function buildManifestPath(exportsRoot, exportId) {
|
|
2021
|
-
return
|
|
1287
|
+
return path5.join(exportsRoot, `${exportId}.manifest.json`);
|
|
2022
1288
|
}
|
|
2023
1289
|
function parseExportManifest(value) {
|
|
2024
1290
|
if (!value || typeof value !== "object") {
|
|
@@ -2036,645 +1302,1195 @@ function parseExportManifest(value) {
|
|
|
2036
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)) {
|
|
2037
1303
|
throw new Error("Export manifest does not match expected schema.");
|
|
2038
1304
|
}
|
|
2039
|
-
if (!maybe.pages.every(isExportPage)) {
|
|
2040
|
-
throw new Error("Export manifest pages payload is invalid.");
|
|
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;
|
|
1537
|
+
}
|
|
1538
|
+
if (buffer[0] !== 80 || buffer[1] !== 75) {
|
|
1539
|
+
return false;
|
|
2041
1540
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
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);
|
|
2050
1572
|
},
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
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;
|
|
2061
1590
|
constructor(options) {
|
|
2062
|
-
this.
|
|
2063
|
-
this.
|
|
2064
|
-
this.provider = options.provider;
|
|
2065
|
-
this.primeUiRoot = path6.join(this.projectRoot, ".primeui");
|
|
2066
|
-
this.tempRoot = path6.join(this.primeUiRoot, "temp");
|
|
2067
|
-
this.exportsRoot = path6.join(this.tempRoot, "exports");
|
|
1591
|
+
this.apiKey = options.apiKey;
|
|
1592
|
+
this.apiRoot = normalizeApiRoot(options.baseUrl ?? DEFAULT_API_BASE_URL);
|
|
2068
1593
|
}
|
|
2069
1594
|
async getProjectInfo() {
|
|
2070
|
-
|
|
2071
|
-
|
|
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
|
+
);
|
|
2072
1603
|
}
|
|
2073
1604
|
async listExports() {
|
|
2074
|
-
await this.
|
|
2075
|
-
|
|
1605
|
+
const response = await this.requestJson(
|
|
1606
|
+
"project/exports",
|
|
1607
|
+
exportsResponseSchema
|
|
1608
|
+
);
|
|
1609
|
+
return response.exports;
|
|
2076
1610
|
}
|
|
2077
1611
|
async createExport() {
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
await writeUtf8(manifestPath, `${JSON.stringify(result, null, 2)}
|
|
2082
|
-
`);
|
|
2083
|
-
return result;
|
|
1612
|
+
return this.requestJson("project/exports", createExportResponseSchema, {
|
|
1613
|
+
method: "POST"
|
|
1614
|
+
});
|
|
2084
1615
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
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
|
+
);
|
|
2091
1635
|
}
|
|
2092
|
-
const
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
manifest = parseExportManifest(
|
|
2098
|
-
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"}"`
|
|
2099
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);
|
|
2100
1654
|
} catch (error) {
|
|
2101
|
-
|
|
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);
|
|
2102
1675
|
throw new Error(
|
|
2103
|
-
`
|
|
1676
|
+
`PrimeUI API request failed (${response.status}) for "${endpoint}": ${details}`
|
|
2104
1677
|
);
|
|
2105
1678
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
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"}"`
|
|
2109
1684
|
);
|
|
2110
1685
|
}
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
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;
|
|
2122
1701
|
}
|
|
2123
|
-
async
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
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);
|
|
1749
|
+
}
|
|
1750
|
+
async withMutationService(context, operation) {
|
|
1751
|
+
const service = await this.createProjectSyncService(context);
|
|
1752
|
+
return operation(service);
|
|
2132
1753
|
}
|
|
2133
|
-
async
|
|
2134
|
-
await
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
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
|
|
2140
1779
|
});
|
|
2141
1780
|
}
|
|
2142
|
-
async
|
|
2143
|
-
|
|
2144
|
-
|
|
1781
|
+
async getProjectInfo(context) {
|
|
1782
|
+
return this.withService(
|
|
1783
|
+
context,
|
|
1784
|
+
(service) => service.getProjectInfo(context)
|
|
1785
|
+
);
|
|
2145
1786
|
}
|
|
2146
|
-
async
|
|
2147
|
-
return
|
|
2148
|
-
status: "ok",
|
|
2149
|
-
runtime: {
|
|
2150
|
-
cwd: process.cwd(),
|
|
2151
|
-
codexWorkspaceRoot: process.env.CODEX_WORKSPACE_ROOT?.trim() || null,
|
|
2152
|
-
vscodeCwd: process.env.VSCODE_CWD?.trim() || null,
|
|
2153
|
-
initCwd: process.env.INIT_CWD?.trim() || null,
|
|
2154
|
-
pwd: process.env.PWD?.trim() || null,
|
|
2155
|
-
searchRoots: [process.cwd()],
|
|
2156
|
-
rootsListFallbackCwds: []
|
|
2157
|
-
},
|
|
2158
|
-
config: {
|
|
2159
|
-
found: true,
|
|
2160
|
-
projectRoot: this.projectRoot,
|
|
2161
|
-
projectConfigPath: path6.join(this.projectRoot, ".primeui", "project.json"),
|
|
2162
|
-
targetProjectPath: path6.relative(
|
|
2163
|
-
this.projectRoot,
|
|
2164
|
-
this.targetProjectRoot
|
|
2165
|
-
).startsWith(".") ? "./" : `./${path6.relative(this.projectRoot, this.targetProjectRoot)}`,
|
|
2166
|
-
targetProjectRoot: this.targetProjectRoot,
|
|
2167
|
-
source: process.env.PRIMEUI_PROJECT_ROOT?.trim() ? "env.PRIMEUI_PROJECT_ROOT" : "search",
|
|
2168
|
-
error: null
|
|
2169
|
-
},
|
|
2170
|
-
options: [
|
|
2171
|
-
{
|
|
2172
|
-
key: "CODEX_WORKSPACE_ROOT",
|
|
2173
|
-
description: "Preferred workspace root from Codex clients. May appear only during tool calls.",
|
|
2174
|
-
currentValue: process.env.CODEX_WORKSPACE_ROOT?.trim() || null
|
|
2175
|
-
},
|
|
2176
|
-
{
|
|
2177
|
-
key: "VSCODE_CWD",
|
|
2178
|
-
description: "Workspace cwd hint set by some VS Code runtimes. Used as project search fallback.",
|
|
2179
|
-
currentValue: process.env.VSCODE_CWD?.trim() || null
|
|
2180
|
-
},
|
|
2181
|
-
{
|
|
2182
|
-
key: "INIT_CWD",
|
|
2183
|
-
description: "Install/runtime cwd hint. Used as fallback when workspace-specific env is unavailable.",
|
|
2184
|
-
currentValue: process.env.INIT_CWD?.trim() || null
|
|
2185
|
-
},
|
|
2186
|
-
{
|
|
2187
|
-
key: "PWD",
|
|
2188
|
-
description: "Shell working directory hint. Used as fallback when workspace-specific env is unavailable.",
|
|
2189
|
-
currentValue: process.env.PWD?.trim() || null
|
|
2190
|
-
},
|
|
2191
|
-
{
|
|
2192
|
-
key: "PRIMEUI_PROJECT_ROOT",
|
|
2193
|
-
description: "Optional absolute root containing .primeui/project.json. Use as explicit override.",
|
|
2194
|
-
currentValue: process.env.PRIMEUI_PROJECT_ROOT?.trim() || null
|
|
2195
|
-
},
|
|
2196
|
-
{
|
|
2197
|
-
key: "PRIMEUI_API_BASE_URL",
|
|
2198
|
-
description: "Optional PrimeUI API base URL override.",
|
|
2199
|
-
currentValue: process.env.PRIMEUI_API_BASE_URL?.trim() || null
|
|
2200
|
-
},
|
|
2201
|
-
{
|
|
2202
|
-
key: "PRIMEUI_API_KEY",
|
|
2203
|
-
description: "Optional API key override. If missing, MCP uses apiKey from .primeui/project.json.",
|
|
2204
|
-
currentValue: process.env.PRIMEUI_API_KEY?.trim() ? "set" : null
|
|
2205
|
-
}
|
|
2206
|
-
]
|
|
2207
|
-
};
|
|
1787
|
+
async listExports(context) {
|
|
1788
|
+
return this.withService(context, (service) => service.listExports(context));
|
|
2208
1789
|
}
|
|
2209
|
-
async
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
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
|
+
);
|
|
2213
1819
|
}
|
|
2214
1820
|
};
|
|
2215
1821
|
|
|
2216
|
-
// src/
|
|
2217
|
-
import {
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
import {
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
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.");
|
|
2240
1862
|
var exportSummarySchema2 = z3.object({
|
|
2241
|
-
total: z3.number(),
|
|
2242
|
-
successful: z3.number(),
|
|
2243
|
-
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.")
|
|
2244
1866
|
});
|
|
2245
|
-
var
|
|
2246
|
-
componentKey: z3.string(),
|
|
2247
|
-
enabled: z3.boolean(),
|
|
2248
|
-
files: z3.array(z3.string()),
|
|
2249
|
-
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.")
|
|
2250
1872
|
});
|
|
2251
1873
|
var exportPageManifestSchema2 = z3.object({
|
|
2252
|
-
success: z3.boolean(),
|
|
2253
|
-
message: z3.string(),
|
|
2254
|
-
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
|
+
)
|
|
2255
1879
|
});
|
|
2256
1880
|
var exportPageSchema2 = z3.object({
|
|
2257
|
-
id: z3.string(),
|
|
2258
|
-
title: z3.string().optional()
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
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."),
|
|
2264
1890
|
manifest: exportPageManifestSchema2
|
|
2265
1891
|
});
|
|
2266
|
-
var
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
metadata: z3.record(z3.unknown()),
|
|
2270
|
-
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.")
|
|
2271
1895
|
});
|
|
2272
|
-
var
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
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.")
|
|
2278
1901
|
});
|
|
2279
|
-
var
|
|
2280
|
-
|
|
2281
|
-
|
|
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
|
+
)
|
|
2282
2132
|
}),
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
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
|
+
),
|
|
2293
2163
|
status: exportStatusSchema2,
|
|
2294
|
-
createdAt: z3.string().
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
summary
|
|
2305
|
-
|
|
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
|
+
)
|
|
2306
2178
|
}),
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
this.name = "PrimeUiApiContractError";
|
|
2179
|
+
annotations: {
|
|
2180
|
+
readOnlyHint: false,
|
|
2181
|
+
destructiveHint: false,
|
|
2182
|
+
idempotentHint: false,
|
|
2183
|
+
openWorldHint: true
|
|
2313
2184
|
}
|
|
2314
2185
|
};
|
|
2315
|
-
var
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
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
|
|
2322
2232
|
}
|
|
2323
|
-
parsed.pathname = `${normalizedPath}/api/v1/`.replace(
|
|
2324
|
-
"//api/v1/",
|
|
2325
|
-
"/api/v1/"
|
|
2326
|
-
);
|
|
2327
|
-
return parsed.toString();
|
|
2328
2233
|
};
|
|
2329
|
-
var
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
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
|
|
2337
2306
|
}
|
|
2338
|
-
const signature = `${buffer[2]}:${buffer[3]}`;
|
|
2339
|
-
return signature === "3:4" || signature === "5:6" || signature === "7:8";
|
|
2340
|
-
};
|
|
2341
|
-
var createZipSignatureGuard = (endpoint) => {
|
|
2342
|
-
let signature = Buffer.alloc(0);
|
|
2343
|
-
let validated = false;
|
|
2344
|
-
return new Transform({
|
|
2345
|
-
transform(chunk, _encoding, callback) {
|
|
2346
|
-
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2347
|
-
if (!validated) {
|
|
2348
|
-
const requiredBytes = Math.max(0, 4 - signature.length);
|
|
2349
|
-
if (requiredBytes > 0) {
|
|
2350
|
-
signature = Buffer.concat([
|
|
2351
|
-
signature,
|
|
2352
|
-
chunkBuffer.subarray(0, requiredBytes)
|
|
2353
|
-
]);
|
|
2354
|
-
}
|
|
2355
|
-
if (signature.length >= 4) {
|
|
2356
|
-
if (!looksLikeZipArchive(signature)) {
|
|
2357
|
-
callback(
|
|
2358
|
-
new PrimeUiApiContractError(
|
|
2359
|
-
endpoint,
|
|
2360
|
-
"response body is not a valid zip archive"
|
|
2361
|
-
)
|
|
2362
|
-
);
|
|
2363
|
-
return;
|
|
2364
|
-
}
|
|
2365
|
-
validated = true;
|
|
2366
|
-
}
|
|
2367
|
-
}
|
|
2368
|
-
callback(null, chunkBuffer);
|
|
2369
|
-
},
|
|
2370
|
-
flush(callback) {
|
|
2371
|
-
if (!validated) {
|
|
2372
|
-
callback(
|
|
2373
|
-
new PrimeUiApiContractError(
|
|
2374
|
-
endpoint,
|
|
2375
|
-
"response body is not a valid zip archive"
|
|
2376
|
-
)
|
|
2377
|
-
);
|
|
2378
|
-
return;
|
|
2379
|
-
}
|
|
2380
|
-
callback();
|
|
2381
|
-
}
|
|
2382
|
-
});
|
|
2383
2307
|
};
|
|
2384
|
-
var
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
)
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
async createExport() {
|
|
2409
|
-
return this.requestJson("project/exports", createExportResponseSchema, {
|
|
2410
|
-
method: "POST"
|
|
2411
|
-
});
|
|
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
|
|
2412
2332
|
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
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
|
+
}
|
|
2425
2356
|
}
|
|
2426
|
-
}
|
|
2427
|
-
if (!response.ok) {
|
|
2428
|
-
const details = await this.readError(response);
|
|
2429
|
-
throw new Error(
|
|
2430
|
-
`PrimeUI API request failed (${response.status}) while downloading export "${exportId}": ${details}`
|
|
2431
|
-
);
|
|
2432
|
-
}
|
|
2433
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
2434
|
-
if (!isZipContentType(contentType)) {
|
|
2435
|
-
throw new PrimeUiApiContractError(
|
|
2436
|
-
endpoint,
|
|
2437
|
-
`expected zip content-type but got "${contentType || "unknown"}"`
|
|
2438
|
-
);
|
|
2439
|
-
}
|
|
2440
|
-
if (!response.body) {
|
|
2441
|
-
throw new PrimeUiApiContractError(endpoint, "response body is empty");
|
|
2442
|
-
}
|
|
2443
|
-
await ensureDir(path7.dirname(destinationPath));
|
|
2444
|
-
const zipStream = Readable.fromWeb(
|
|
2445
|
-
response.body
|
|
2446
|
-
);
|
|
2447
|
-
const signatureGuard = createZipSignatureGuard(endpoint);
|
|
2448
|
-
const fileStream = createWriteStream(destinationPath);
|
|
2449
|
-
try {
|
|
2450
|
-
await pipeline(zipStream, signatureGuard, fileStream);
|
|
2451
|
-
} catch (error) {
|
|
2452
|
-
await unlink(destinationPath).catch(() => void 0);
|
|
2453
|
-
throw error;
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
buildUrl(endpoint) {
|
|
2457
|
-
return new URL(endpoint, this.apiRoot).toString();
|
|
2357
|
+
};
|
|
2458
2358
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
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
|
|
2468
2367
|
}
|
|
2469
|
-
});
|
|
2470
|
-
if (!response.ok) {
|
|
2471
|
-
const details = await this.readError(response);
|
|
2472
|
-
throw new Error(
|
|
2473
|
-
`PrimeUI API request failed (${response.status}) for "${endpoint}": ${details}`
|
|
2474
|
-
);
|
|
2475
|
-
}
|
|
2476
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
2477
|
-
if (!isJsonContentType(contentType)) {
|
|
2478
|
-
throw new PrimeUiApiContractError(
|
|
2479
|
-
endpoint,
|
|
2480
|
-
`expected JSON content-type but got "${contentType || "unknown"}"`
|
|
2481
|
-
);
|
|
2482
2368
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
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
|
|
2491
2379
|
}
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
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
|
+
}
|
|
2496
2393
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
const
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
} else {
|
|
2511
|
-
const bodyText = await response.text();
|
|
2512
|
-
if (bodyText.trim()) {
|
|
2513
|
-
return bodyText.trim();
|
|
2514
|
-
}
|
|
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);
|
|
2515
2407
|
}
|
|
2516
|
-
} catch {
|
|
2517
2408
|
}
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
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);
|
|
2421
|
+
}
|
|
2524
2422
|
}
|
|
2525
|
-
return apiKey;
|
|
2526
|
-
}
|
|
2527
|
-
};
|
|
2528
|
-
|
|
2529
|
-
// src/service.ts
|
|
2530
|
-
function getProjectConfigFallbackCwds(rootsFallbackCwds = []) {
|
|
2531
|
-
return [
|
|
2532
|
-
process.env.CODEX_WORKSPACE_ROOT,
|
|
2533
|
-
process.env.VSCODE_CWD,
|
|
2534
|
-
...rootsFallbackCwds,
|
|
2535
|
-
process.env.INIT_CWD,
|
|
2536
|
-
process.env.PWD
|
|
2537
|
-
].map((value) => value?.trim()).filter((value) => Boolean(value));
|
|
2538
|
-
}
|
|
2539
|
-
async function createProjectSyncService(rootsFallbackCwds) {
|
|
2540
|
-
const fallbackCwds = getProjectConfigFallbackCwds(rootsFallbackCwds);
|
|
2541
|
-
console.error(
|
|
2542
|
-
"[primeui-mcp] TOOL_CALL_ENV:",
|
|
2543
|
-
JSON.stringify({
|
|
2544
|
-
CODEX_WORKSPACE_ROOT: process.env.CODEX_WORKSPACE_ROOT ?? null,
|
|
2545
|
-
VSCODE_CWD: process.env.VSCODE_CWD ?? null,
|
|
2546
|
-
INIT_CWD: process.env.INIT_CWD ?? null,
|
|
2547
|
-
PWD: process.env.PWD ?? null
|
|
2548
|
-
})
|
|
2549
2423
|
);
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
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
|
+
}
|
|
2436
|
+
}
|
|
2559
2437
|
);
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
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
|
+
}
|
|
2564
2452
|
);
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
targetProjectRoot,
|
|
2576
|
-
provider
|
|
2577
|
-
});
|
|
2578
|
-
}
|
|
2579
|
-
var LazyProjectSyncSource = class {
|
|
2580
|
-
constructor(getRootsFallbackCwds) {
|
|
2581
|
-
this.getRootsFallbackCwds = getRootsFallbackCwds;
|
|
2582
|
-
}
|
|
2583
|
-
async withService(operation) {
|
|
2584
|
-
const service = await createProjectSyncService(this.getRootsFallbackCwds());
|
|
2585
|
-
return operation(service);
|
|
2586
|
-
}
|
|
2587
|
-
async getProjectInfo() {
|
|
2588
|
-
return this.withService((service) => service.getProjectInfo());
|
|
2589
|
-
}
|
|
2590
|
-
async listExports() {
|
|
2591
|
-
return this.withService((service) => service.listExports());
|
|
2592
|
-
}
|
|
2593
|
-
async createExport() {
|
|
2594
|
-
return this.withService((service) => service.createExport());
|
|
2595
|
-
}
|
|
2596
|
-
async downloadExportById(id) {
|
|
2597
|
-
return this.withService((service) => service.downloadExportById(id));
|
|
2598
|
-
}
|
|
2599
|
-
async inspectPage(slug) {
|
|
2600
|
-
return this.withService((service) => service.inspectPage(slug));
|
|
2601
|
-
}
|
|
2602
|
-
async copyPage(originPageSlug, actualPageSlug) {
|
|
2603
|
-
return this.withService(
|
|
2604
|
-
(service) => service.copyPage(originPageSlug, actualPageSlug)
|
|
2605
|
-
);
|
|
2606
|
-
}
|
|
2607
|
-
async clearTemp() {
|
|
2608
|
-
return this.withService((service) => service.clearTemp());
|
|
2609
|
-
}
|
|
2610
|
-
async healthCheck() {
|
|
2611
|
-
const rootsFallbackCwds = this.getRootsFallbackCwds();
|
|
2612
|
-
const fallbackCwds = getProjectConfigFallbackCwds(rootsFallbackCwds);
|
|
2613
|
-
const result = await runPrimeUiHealthCheck({
|
|
2614
|
-
cwd: process.cwd(),
|
|
2615
|
-
codexWorkspaceRoot: process.env.CODEX_WORKSPACE_ROOT,
|
|
2616
|
-
vscodeCwd: process.env.VSCODE_CWD,
|
|
2617
|
-
initCwd: process.env.INIT_CWD,
|
|
2618
|
-
pwd: process.env.PWD,
|
|
2619
|
-
projectRootFromEnv: process.env.PRIMEUI_PROJECT_ROOT,
|
|
2620
|
-
apiBaseUrlFromEnv: process.env.PRIMEUI_API_BASE_URL,
|
|
2621
|
-
apiKeyFromEnv: process.env.PRIMEUI_API_KEY,
|
|
2622
|
-
fallbackCwds,
|
|
2623
|
-
rootsListFallbackCwds: rootsFallbackCwds
|
|
2624
|
-
});
|
|
2625
|
-
if (result.config.found) {
|
|
2626
|
-
console.error("[primeui-mcp] RESOLVED_PROJECT_ROOT:", result.config.projectRoot);
|
|
2627
|
-
console.error(
|
|
2628
|
-
"[primeui-mcp] RESOLVED_PROJECT_CONFIG:",
|
|
2629
|
-
result.config.projectConfigPath
|
|
2630
|
-
);
|
|
2631
|
-
} else {
|
|
2632
|
-
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
|
+
}
|
|
2633
2463
|
}
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
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
|
+
}
|
|
2642
2483
|
}
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
return null;
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
|
-
async function loadClientRootFallbackCwds(server) {
|
|
2649
|
-
try {
|
|
2650
|
-
const response = await server.server.listRoots(void 0, { timeout: 1500 });
|
|
2651
|
-
const roots = response.roots.map((root) => root.uri);
|
|
2652
|
-
const resolvedRoots = roots.map((rootUri) => toFileRootPath(rootUri)).filter((rootPath) => Boolean(rootPath));
|
|
2653
|
-
console.error("[primeui-mcp] roots/list supported:", JSON.stringify(roots));
|
|
2654
|
-
console.error(
|
|
2655
|
-
"[primeui-mcp] roots/list file roots:",
|
|
2656
|
-
JSON.stringify(resolvedRoots)
|
|
2657
|
-
);
|
|
2658
|
-
return resolvedRoots;
|
|
2659
|
-
} catch (error) {
|
|
2660
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2661
|
-
console.error("[primeui-mcp] roots/list unavailable:", message);
|
|
2662
|
-
return [];
|
|
2663
|
-
}
|
|
2484
|
+
);
|
|
2485
|
+
return server;
|
|
2664
2486
|
}
|
|
2487
|
+
|
|
2488
|
+
// src/service.ts
|
|
2665
2489
|
async function main() {
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
console.error("VSCODE_CWD:", process.env.VSCODE_CWD);
|
|
2669
|
-
console.error("INIT_CWD:", process.env.INIT_CWD);
|
|
2670
|
-
console.error("PWD:", process.env.PWD);
|
|
2671
|
-
let rootsFallbackCwds = [];
|
|
2672
|
-
const server = createPrimeUiMcpServer(
|
|
2673
|
-
new LazyProjectSyncSource(() => rootsFallbackCwds)
|
|
2674
|
-
);
|
|
2490
|
+
const source = new LazyProjectSyncSource();
|
|
2491
|
+
const server = createPrimeUiMcpServer(source);
|
|
2675
2492
|
const transport = new StdioServerTransport();
|
|
2676
2493
|
await server.connect(transport);
|
|
2677
|
-
rootsFallbackCwds = await loadClientRootFallbackCwds(server);
|
|
2678
2494
|
}
|
|
2679
2495
|
main().catch((error) => {
|
|
2680
2496
|
console.error("[primeui-mcp] failed to start", error);
|