@xiaou66/vite-plugin-vue-mcp-next 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -55,6 +55,11 @@ var DEFAULT_MASK_HEADERS = [
55
55
  var DEFAULT_MCP_PATH = "/__mcp";
56
56
  var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
57
57
  var DEFAULT_SCREENSHOT_SAVE_DIR = ".vite-mcp/screenshot";
58
+ var DEFAULT_PERFORMANCE_SAVE_DIR = ".vite-mcp/performance";
59
+ var DEFAULT_PERFORMANCE_MAX_REPORTS = 100;
60
+ var DEFAULT_PERFORMANCE_MAX_DURATION_MS = 3e4;
61
+ var DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS = 250;
62
+ var DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS = 50;
58
63
  var MCP_TOOL_NAMES = {
59
64
  listPages: "list_pages",
60
65
  reloadPage: "reload_page",
@@ -67,6 +72,11 @@ var MCP_TOOL_NAMES = {
67
72
  getNetworkRequests: "get_network_requests",
68
73
  getNetworkRequestDetail: "get_network_request_detail",
69
74
  clearNetworkRequests: "clear_network_requests",
75
+ recordPerformance: "record_performance",
76
+ startPerformanceRecording: "start_performance_recording",
77
+ stopPerformanceRecording: "stop_performance_recording",
78
+ getPerformanceReport: "get_performance_report",
79
+ takeHeapSnapshot: "take_heap_snapshot",
70
80
  getComponentTree: "get_component_tree",
71
81
  getComponentState: "get_component_state",
72
82
  editComponentState: "edit_component_state",
@@ -135,6 +145,19 @@ var DEFAULT_OPTIONS = {
135
145
  options: {},
136
146
  plugins: []
137
147
  }
148
+ },
149
+ performance: {
150
+ mode: "auto",
151
+ maxDurationMs: DEFAULT_PERFORMANCE_MAX_DURATION_MS,
152
+ sampleIntervalMs: DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS,
153
+ longTaskThresholdMs: DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS,
154
+ saveDir: DEFAULT_PERFORMANCE_SAVE_DIR,
155
+ memory: {
156
+ enabled: true
157
+ },
158
+ stacks: {
159
+ enabled: true
160
+ }
138
161
  }
139
162
  };
140
163
  function mergeMcpClientOptions(cursorConfig, mcpClients) {
@@ -204,6 +227,18 @@ function mergeOptions(options = {}) {
204
227
  ...DEFAULT_OPTIONS.screenshot.snapdom.plugins
205
228
  ]
206
229
  }
230
+ },
231
+ performance: {
232
+ ...DEFAULT_OPTIONS.performance,
233
+ ...options.performance,
234
+ memory: {
235
+ ...DEFAULT_OPTIONS.performance.memory,
236
+ ...options.performance?.memory
237
+ },
238
+ stacks: {
239
+ ...DEFAULT_OPTIONS.performance.stacks,
240
+ ...options.performance?.stacks
241
+ }
207
242
  }
208
243
  };
209
244
  }
@@ -299,7 +334,11 @@ function createVueMcpNextContext(options) {
299
334
  rpcServer: void 0,
300
335
  pages: createPageTargetRegistry(),
301
336
  consoleRecords: createRingBuffer(options.console.maxRecords),
302
- networkRecords: createRingBuffer(options.network.maxRecords)
337
+ networkRecords: createRingBuffer(options.network.maxRecords),
338
+ performanceReports: createRingBuffer(
339
+ DEFAULT_PERFORMANCE_MAX_REPORTS
340
+ ),
341
+ performanceSessions: /* @__PURE__ */ new Map()
303
342
  };
304
343
  }
305
344
 
@@ -417,13 +456,13 @@ async function requestRuntimeData(ctx, trigger) {
417
456
  return { ok: false, error: "runtime bridge is not connected" };
418
457
  }
419
458
  const event = (0, import_nanoid.nanoid)();
420
- return new Promise((resolve) => {
459
+ return new Promise((resolve2) => {
421
460
  const timeout = setTimeout(() => {
422
- resolve({ ok: false, error: "runtime bridge response timed out" });
461
+ resolve2({ ok: false, error: "runtime bridge response timed out" });
423
462
  }, 5e3);
424
463
  ctx.hooks.hookOnce(event, (data) => {
425
464
  clearTimeout(timeout);
426
- resolve(data);
465
+ resolve2(data);
427
466
  });
428
467
  trigger(event);
429
468
  });
@@ -702,12 +741,598 @@ function registerNetworkTools(server, ctx) {
702
741
  );
703
742
  }
704
743
 
705
- // src/mcp/tools/pages.ts
744
+ // src/mcp/tools/performance.ts
706
745
  var import_zod5 = require("zod");
707
746
 
747
+ // src/performance/summary.ts
748
+ function buildPerformanceSummary(input) {
749
+ const memory = buildMemorySummary(input.memorySamples);
750
+ const blockedTimeMs = input.longTasks.reduce((total, task) => {
751
+ return total + Math.max(0, task.durationMs - DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS);
752
+ }, 0);
753
+ const longTaskCount = input.longTasks.length;
754
+ const durations = input.longTasks.map((task) => task.durationMs);
755
+ const maxTaskDurationMs = durations.length ? Math.max(...durations) : 0;
756
+ const averageTaskDurationMs = durations.length ? Math.round(
757
+ durations.reduce((total, duration) => total + duration, 0) / durations.length
758
+ ) : void 0;
759
+ const suspectedJank = blockedTimeMs > 0 || memory.trend === "growing" || longTaskCount > 0;
760
+ const severity = resolveSeverity({
761
+ blockedTimeMs,
762
+ longTaskCount,
763
+ memoryTrend: memory.trend
764
+ });
765
+ return {
766
+ blockedTimeMs,
767
+ longTaskCount,
768
+ maxTaskDurationMs,
769
+ averageTaskDurationMs,
770
+ suspectedJank,
771
+ severity
772
+ };
773
+ }
774
+ function buildMemorySummary(samples) {
775
+ const first = samples[0]?.usedJSHeapSize;
776
+ const last = samples.at(-1)?.usedJSHeapSize;
777
+ const peak = samples.reduce((currentPeak, sample) => {
778
+ if (typeof sample.usedJSHeapSize !== "number") {
779
+ return currentPeak;
780
+ }
781
+ return Math.max(currentPeak, sample.usedJSHeapSize);
782
+ }, 0);
783
+ const trend = resolveMemoryTrend(first, last);
784
+ return {
785
+ samples,
786
+ initialUsedJSHeapSize: first,
787
+ finalUsedJSHeapSize: last,
788
+ peakUsedJSHeapSize: peak || void 0,
789
+ deltaUsedJSHeapSize: typeof first === "number" && typeof last === "number" ? last - first : void 0,
790
+ trend
791
+ };
792
+ }
793
+ function buildStackSummary(frames, options = {}) {
794
+ return {
795
+ topFrames: [...frames].sort(sortByHotness).slice(0, 10),
796
+ rawProfilePath: options.rawProfilePath,
797
+ limitation: options.limitation
798
+ };
799
+ }
800
+ function resolveSeverity(input) {
801
+ if (input.blockedTimeMs >= 1e3 || input.longTaskCount >= 10) {
802
+ return "critical";
803
+ }
804
+ if (input.blockedTimeMs > 0 || input.memoryTrend === "growing") {
805
+ return "warning";
806
+ }
807
+ return "ok";
808
+ }
809
+ function resolveMemoryTrend(first, last) {
810
+ if (typeof first !== "number" || typeof last !== "number") {
811
+ return "unknown";
812
+ }
813
+ if (last > first) {
814
+ return "growing";
815
+ }
816
+ return "stable";
817
+ }
818
+ function sortByHotness(left, right) {
819
+ return compareNumber(right.totalTimeMs, left.totalTimeMs) || compareNumber(right.selfTimeMs, left.selfTimeMs) || compareNumber(right.hitCount, left.hitCount);
820
+ }
821
+ function compareNumber(left, right) {
822
+ return (left ?? -1) - (right ?? -1);
823
+ }
824
+
825
+ // src/performance/output.ts
826
+ var import_promises = require("fs/promises");
827
+ var import_node_path = require("path");
828
+ var import_nanoid2 = require("nanoid");
829
+ async function writePerformanceArtifact(options) {
830
+ const root = (0, import_node_path.resolve)(options.root ?? process.cwd());
831
+ const dir = resolveOutputDirectory(root, options.saveDir);
832
+ const path8 = (0, import_node_path.resolve)(dir, createSafeFileName(options.fileName));
833
+ const data = typeof options.data === "string" ? Buffer.from(options.data) : options.data;
834
+ await (0, import_promises.mkdir)(dir, { recursive: true });
835
+ await (0, import_promises.writeFile)(path8, data);
836
+ return {
837
+ kind: options.kind,
838
+ path: path8,
839
+ relativePath: (0, import_node_path.relative)(root, path8),
840
+ byteLength: data.byteLength,
841
+ source: "cdp"
842
+ };
843
+ }
844
+ function resolveOutputDirectory(root, saveDir) {
845
+ return (0, import_node_path.resolve)(root, saveDir);
846
+ }
847
+ function createSafeFileName(fileName) {
848
+ const trimmed = fileName.trim();
849
+ if (!trimmed) {
850
+ return `${String(Date.now())}-${(0, import_nanoid2.nanoid)()}`;
851
+ }
852
+ const suffix = (0, import_nanoid2.nanoid)(6);
853
+ const dotIndex = trimmed.lastIndexOf(".");
854
+ if (dotIndex === -1) {
855
+ return `${trimmed}-${suffix}`;
856
+ }
857
+ const base = trimmed.slice(0, dotIndex);
858
+ const extension = trimmed.slice(dotIndex);
859
+ return `${base}-${suffix}${extension}`;
860
+ }
861
+
862
+ // src/cdp/cdpPerformance.ts
863
+ async function recordCdpPerformance(options) {
864
+ const session = await startCdpPerformanceRecording(options);
865
+ await waitForDuration(options.durationMs);
866
+ return stopCdpPerformanceRecording({
867
+ client: options.client,
868
+ session
869
+ });
870
+ }
871
+ async function startCdpPerformanceRecording(options) {
872
+ await options.client.Performance.enable();
873
+ await options.client.Profiler.enable();
874
+ await options.client.Profiler.start();
875
+ return {
876
+ recordingId: createRecordingId(options.pageId),
877
+ pageId: options.pageId,
878
+ startedAt: Date.now(),
879
+ includeMemory: options.includeMemory,
880
+ includeStacks: options.includeStacks,
881
+ saveDir: options.saveDir
882
+ };
883
+ }
884
+ async function stopCdpPerformanceRecording(options) {
885
+ const endedAt = Date.now();
886
+ const [metricsResult, profileResult] = await Promise.all([
887
+ options.client.Performance.getMetrics(),
888
+ options.client.Profiler.stop()
889
+ ]);
890
+ const metrics = toMetricMap(metricsResult.metrics);
891
+ const longTasks = toLongTaskRecords(metrics);
892
+ const memorySamples = options.session.includeMemory ? [toMemorySample(metrics)] : [];
893
+ const memory = options.session.includeMemory ? buildMemorySummary(memorySamples) : void 0;
894
+ const stacks = options.session.includeStacks ? buildStackSummary(aggregateCpuProfile(profileResult.profile)) : void 0;
895
+ const artifact = await writeCpuProfileArtifact(
896
+ {
897
+ pageId: options.session.pageId,
898
+ saveDir: options.session.saveDir
899
+ },
900
+ profileResult.profile
901
+ );
902
+ const report = {
903
+ recordingId: options.session.recordingId,
904
+ pageId: options.session.pageId,
905
+ source: "cdp",
906
+ startedAt: options.session.startedAt,
907
+ endedAt,
908
+ durationMs: endedAt - options.session.startedAt,
909
+ summary: buildPerformanceSummary({
910
+ longTasks,
911
+ memorySamples
912
+ }),
913
+ longTasks,
914
+ memory,
915
+ stacks,
916
+ artifacts: [artifact],
917
+ limitations: [
918
+ "CDP path returns sampled CPU profile data, not a full instruction trace"
919
+ ]
920
+ };
921
+ return report;
922
+ }
923
+ async function takeHeapSnapshot(options) {
924
+ const chunks = [];
925
+ await options.client.HeapProfiler.enable();
926
+ options.client.HeapProfiler.addHeapSnapshotChunk((event) => {
927
+ const payload = event;
928
+ if (payload.chunk) {
929
+ chunks.push(payload.chunk);
930
+ }
931
+ });
932
+ await options.client.HeapProfiler.takeHeapSnapshot({
933
+ reportProgress: false
934
+ });
935
+ const artifact = await writePerformanceArtifact({
936
+ root: process.cwd(),
937
+ saveDir: options.saveDir,
938
+ fileName: `${options.pageId}-heap-snapshot.heapsnapshot`,
939
+ kind: "heap-snapshot",
940
+ data: Buffer.from(chunks.join(""))
941
+ });
942
+ return artifact;
943
+ }
944
+ function createRecordingId(pageId) {
945
+ return `cdp-${pageId}-${String(Date.now())}`;
946
+ }
947
+ function waitForDuration(durationMs) {
948
+ return new Promise((resolve2) => {
949
+ setTimeout(() => {
950
+ resolve2();
951
+ }, durationMs);
952
+ });
953
+ }
954
+ function toMetricMap(metrics) {
955
+ return new Map(metrics.map((metric) => [metric.name, metric.value]));
956
+ }
957
+ function toLongTaskRecords(metrics) {
958
+ const taskDuration = metrics.get("TaskDuration") ?? 0;
959
+ if (taskDuration <= 0) {
960
+ return [];
961
+ }
962
+ return [
963
+ {
964
+ startTime: 0,
965
+ durationMs: taskDuration,
966
+ name: "TaskDuration",
967
+ source: "cpu-profile"
968
+ }
969
+ ];
970
+ }
971
+ function toMemorySample(metrics) {
972
+ return {
973
+ timestamp: Date.now(),
974
+ usedJSHeapSize: metrics.get("JSHeapUsedSize"),
975
+ totalJSHeapSize: metrics.get("JSHeapTotalSize"),
976
+ jsHeapSizeLimit: metrics.get("JSHeapSizeLimit")
977
+ };
978
+ }
979
+ function aggregateCpuProfile(profile) {
980
+ const nodes = profile.nodes ?? [];
981
+ const nodeMap = /* @__PURE__ */ new Map();
982
+ const nodeIndex = /* @__PURE__ */ new Map();
983
+ nodes.forEach((node) => nodeIndex.set(node.id, node));
984
+ (profile.samples ?? []).forEach((nodeId, index) => {
985
+ const node = nodeIndex.get(nodeId);
986
+ if (!node) {
987
+ return;
988
+ }
989
+ const delta = profile.timeDeltas?.[index] ?? 0;
990
+ const current = nodeMap.get(nodeId) ?? {
991
+ functionName: node.callFrame.functionName || "<anonymous>",
992
+ url: node.callFrame.url,
993
+ lineNumber: node.callFrame.lineNumber,
994
+ columnNumber: node.callFrame.columnNumber,
995
+ selfTimeMs: 0,
996
+ totalTimeMs: 0,
997
+ hitCount: 0
998
+ };
999
+ nodeMap.set(nodeId, {
1000
+ ...current,
1001
+ selfTimeMs: current.selfTimeMs + delta,
1002
+ totalTimeMs: current.totalTimeMs + delta,
1003
+ hitCount: current.hitCount + 1
1004
+ });
1005
+ });
1006
+ return [...nodeMap.values()];
1007
+ }
1008
+ async function writeCpuProfileArtifact(options, profile) {
1009
+ return writePerformanceArtifact({
1010
+ root: process.cwd(),
1011
+ saveDir: options.saveDir ?? ".vite-mcp/performance",
1012
+ fileName: `${options.pageId}-cpu-profile.cpuprofile`,
1013
+ kind: "cpu-profile",
1014
+ data: Buffer.from(JSON.stringify(profile, null, 2))
1015
+ });
1016
+ }
1017
+
1018
+ // src/mcp/tools/performance.ts
1019
+ var PERFORMANCE_NOT_AVAILABLE_ERROR = "Performance diagnostics are disabled by configuration";
1020
+ var activeCdpPerformanceClients = /* @__PURE__ */ new Map();
1021
+ function registerPerformanceTools(server, ctx) {
1022
+ server.registerTool(
1023
+ MCP_TOOL_NAMES.recordPerformance,
1024
+ {
1025
+ description: "Record a performance sample for the selected page.",
1026
+ inputSchema: {
1027
+ pageId: import_zod5.z.string().optional(),
1028
+ durationMs: import_zod5.z.number().optional(),
1029
+ includeMemory: import_zod5.z.boolean().optional(),
1030
+ includeStacks: import_zod5.z.boolean().optional()
1031
+ }
1032
+ },
1033
+ async (input) => handleRecordPerformance(ctx, input)
1034
+ );
1035
+ server.registerTool(
1036
+ MCP_TOOL_NAMES.startPerformanceRecording,
1037
+ {
1038
+ description: "Start a performance recording session.",
1039
+ inputSchema: {
1040
+ pageId: import_zod5.z.string().optional(),
1041
+ includeMemory: import_zod5.z.boolean().optional(),
1042
+ includeStacks: import_zod5.z.boolean().optional()
1043
+ }
1044
+ },
1045
+ async (input) => handleStartPerformanceRecording(ctx, input)
1046
+ );
1047
+ server.registerTool(
1048
+ MCP_TOOL_NAMES.stopPerformanceRecording,
1049
+ {
1050
+ description: "Stop a performance recording session.",
1051
+ inputSchema: {
1052
+ recordingId: import_zod5.z.string()
1053
+ }
1054
+ },
1055
+ async (input) => handleStopPerformanceRecording(ctx, input)
1056
+ );
1057
+ server.registerTool(
1058
+ MCP_TOOL_NAMES.getPerformanceReport,
1059
+ {
1060
+ description: "Get cached performance reports and active sessions.",
1061
+ inputSchema: {
1062
+ pageId: import_zod5.z.string().optional(),
1063
+ recordingId: import_zod5.z.string().optional(),
1064
+ limit: import_zod5.z.number().optional()
1065
+ }
1066
+ },
1067
+ (input) => handleGetPerformanceReport(ctx, input)
1068
+ );
1069
+ server.registerTool(
1070
+ MCP_TOOL_NAMES.takeHeapSnapshot,
1071
+ {
1072
+ description: "Take a heap snapshot with CDP.",
1073
+ inputSchema: {
1074
+ pageId: import_zod5.z.string().optional()
1075
+ }
1076
+ },
1077
+ async (input) => handleTakeHeapSnapshot(ctx, input)
1078
+ );
1079
+ }
1080
+ function appendPerformanceReport(ctx, report) {
1081
+ if (ctx.performanceReports.all().some((item) => item.recordingId === report.recordingId)) {
1082
+ return;
1083
+ }
1084
+ ctx.performanceReports.push(report);
1085
+ }
1086
+ async function handleRecordPerformance(ctx, input) {
1087
+ const durationMs = input.durationMs ?? ctx.options.performance.maxDurationMs;
1088
+ const includeMemory = input.includeMemory ?? ctx.options.performance.memory.enabled;
1089
+ const includeStacks = input.includeStacks ?? ctx.options.performance.stacks.enabled;
1090
+ if (ctx.options.performance.mode === "off") {
1091
+ return createToolError(PERFORMANCE_NOT_AVAILABLE_ERROR);
1092
+ }
1093
+ if (ctx.options.performance.mode !== "hook") {
1094
+ const cdpResult = await connectPerformanceCdp(ctx, input.pageId);
1095
+ const cdp = cdpResult.cdp;
1096
+ if (cdp) {
1097
+ try {
1098
+ const report = await recordCdpPerformance({
1099
+ client: cdp.client,
1100
+ pageId: input.pageId ?? "cdp",
1101
+ durationMs,
1102
+ includeMemory,
1103
+ includeStacks,
1104
+ saveDir: ctx.options.performance.saveDir
1105
+ });
1106
+ appendPerformanceReport(ctx, report);
1107
+ return createToolResponse(toStructuredRecord(report));
1108
+ } finally {
1109
+ await closeCdpClient(cdp.client);
1110
+ }
1111
+ }
1112
+ if (ctx.options.performance.mode === "cdp") {
1113
+ return createToolError(
1114
+ formatCdpUnavailableError(
1115
+ "CDP performance collection is unavailable",
1116
+ cdpResult.error
1117
+ )
1118
+ );
1119
+ }
1120
+ }
1121
+ const result = await requestRuntimeData(ctx, (event) => {
1122
+ void ctx.rpcServer?.recordPerformance({
1123
+ event,
1124
+ durationMs,
1125
+ includeMemory,
1126
+ includeStacks
1127
+ });
1128
+ });
1129
+ if (isPerformanceReport(result)) {
1130
+ appendPerformanceReport(ctx, result);
1131
+ }
1132
+ if (isPlainRecord(result)) {
1133
+ return createToolResponse(toStructuredRecord(result));
1134
+ }
1135
+ return createToolError("runtime bridge returned an invalid response");
1136
+ }
1137
+ async function handleStartPerformanceRecording(ctx, input) {
1138
+ const includeMemory = input.includeMemory ?? ctx.options.performance.memory.enabled;
1139
+ const includeStacks = input.includeStacks ?? ctx.options.performance.stacks.enabled;
1140
+ if (ctx.options.performance.mode === "off") {
1141
+ return createToolError(PERFORMANCE_NOT_AVAILABLE_ERROR);
1142
+ }
1143
+ if (ctx.options.performance.mode !== "hook") {
1144
+ const cdpResult = await connectPerformanceCdp(ctx, input.pageId);
1145
+ const cdp = cdpResult.cdp;
1146
+ if (cdp) {
1147
+ try {
1148
+ const session = await startCdpPerformanceRecording({
1149
+ client: cdp.client,
1150
+ pageId: input.pageId ?? "cdp",
1151
+ includeMemory,
1152
+ includeStacks,
1153
+ saveDir: ctx.options.performance.saveDir
1154
+ });
1155
+ ctx.performanceSessions.set(session.recordingId, {
1156
+ recordingId: session.recordingId,
1157
+ pageId: session.pageId,
1158
+ source: "cdp",
1159
+ startedAt: session.startedAt,
1160
+ includeMemory,
1161
+ includeStacks,
1162
+ mode: ctx.options.performance.mode
1163
+ });
1164
+ activeCdpPerformanceClients.set(session.recordingId, cdp.client);
1165
+ return createToolResponse(
1166
+ toStructuredRecord({
1167
+ ...session,
1168
+ source: "cdp"
1169
+ })
1170
+ );
1171
+ } catch (error) {
1172
+ await closeCdpClient(cdp.client);
1173
+ return createToolError(
1174
+ error instanceof Error ? error.message : String(error)
1175
+ );
1176
+ }
1177
+ }
1178
+ if (ctx.options.performance.mode === "cdp") {
1179
+ return createToolError(
1180
+ formatCdpUnavailableError(
1181
+ "CDP performance collection is unavailable",
1182
+ cdpResult.error
1183
+ )
1184
+ );
1185
+ }
1186
+ }
1187
+ const result = await requestRuntimeData(ctx, (event) => {
1188
+ void ctx.rpcServer?.startPerformanceRecording({
1189
+ event,
1190
+ includeMemory,
1191
+ includeStacks
1192
+ });
1193
+ });
1194
+ if (isPerformanceStartResult(result)) {
1195
+ ctx.performanceSessions.set(result.recordingId, {
1196
+ recordingId: result.recordingId,
1197
+ pageId: input.pageId ?? "runtime",
1198
+ source: "hook",
1199
+ startedAt: result.startedAt,
1200
+ includeMemory,
1201
+ includeStacks,
1202
+ mode: ctx.options.performance.mode
1203
+ });
1204
+ }
1205
+ if (isPlainRecord(result)) {
1206
+ return createToolResponse(toStructuredRecord(result));
1207
+ }
1208
+ return createToolError("runtime bridge returned an invalid response");
1209
+ }
1210
+ async function handleStopPerformanceRecording(ctx, input) {
1211
+ const session = ctx.performanceSessions.get(input.recordingId);
1212
+ if (!session) {
1213
+ return createToolError(`Performance recording not found: ${input.recordingId}`);
1214
+ }
1215
+ try {
1216
+ if (session.source === "cdp") {
1217
+ const client = activeCdpPerformanceClients.get(input.recordingId);
1218
+ if (!client) {
1219
+ return createToolError(
1220
+ `CDP client not found for recording: ${input.recordingId}`
1221
+ );
1222
+ }
1223
+ const report = await stopCdpPerformanceRecording({
1224
+ client,
1225
+ session
1226
+ });
1227
+ appendPerformanceReport(ctx, report);
1228
+ return createToolResponse(toStructuredRecord(report));
1229
+ }
1230
+ const result = await requestRuntimeData(ctx, (event) => {
1231
+ void ctx.rpcServer?.stopPerformanceRecording({
1232
+ event,
1233
+ recordingId: input.recordingId
1234
+ });
1235
+ });
1236
+ if (isPerformanceReport(result)) {
1237
+ appendPerformanceReport(ctx, result);
1238
+ }
1239
+ if (isPlainRecord(result)) {
1240
+ return createToolResponse(toStructuredRecord(result));
1241
+ }
1242
+ return createToolError("runtime bridge returned an invalid response");
1243
+ } finally {
1244
+ ctx.performanceSessions.delete(input.recordingId);
1245
+ const client = activeCdpPerformanceClients.get(input.recordingId);
1246
+ if (client) {
1247
+ activeCdpPerformanceClients.delete(input.recordingId);
1248
+ await closeCdpClient(client);
1249
+ }
1250
+ }
1251
+ }
1252
+ function handleGetPerformanceReport(ctx, input) {
1253
+ const reports = ctx.performanceReports.all().filter((report) => !input.pageId || report.pageId === input.pageId).filter(
1254
+ (report) => !input.recordingId || report.recordingId === input.recordingId
1255
+ );
1256
+ const limit = input.limit ?? reports.length;
1257
+ return createToolResponse(toStructuredRecord({
1258
+ report: reports.at(-1) ?? null,
1259
+ reports: reports.slice(-limit),
1260
+ sessions: [...ctx.performanceSessions.values()].filter(
1261
+ (session) => (!input.pageId || session.pageId === input.pageId) && (!input.recordingId || session.recordingId === input.recordingId)
1262
+ )
1263
+ }));
1264
+ }
1265
+ async function handleTakeHeapSnapshot(ctx, input) {
1266
+ if (ctx.options.performance.mode === "off" || ctx.options.performance.mode === "hook") {
1267
+ return createToolError(PERFORMANCE_NOT_AVAILABLE_ERROR);
1268
+ }
1269
+ const cdpResult = await connectPerformanceCdp(ctx, input.pageId);
1270
+ const cdp = cdpResult.cdp;
1271
+ if (!cdp) {
1272
+ return createToolError(
1273
+ formatCdpUnavailableError(
1274
+ "CDP heap snapshot is unavailable",
1275
+ cdpResult.error
1276
+ )
1277
+ );
1278
+ }
1279
+ try {
1280
+ return createToolResponse(
1281
+ toStructuredRecord(await takeHeapSnapshot({
1282
+ client: cdp.client,
1283
+ pageId: input.pageId ?? "cdp",
1284
+ saveDir: ctx.options.performance.saveDir
1285
+ }))
1286
+ );
1287
+ } finally {
1288
+ await closeCdpClient(cdp.client);
1289
+ }
1290
+ }
1291
+ async function connectPerformanceCdp(ctx, pageId) {
1292
+ try {
1293
+ return { cdp: await connectCdpForPage(ctx, pageId) };
1294
+ } catch (error) {
1295
+ return {
1296
+ error: error instanceof Error ? error.message : String(error)
1297
+ };
1298
+ }
1299
+ }
1300
+ function formatCdpUnavailableError(message, reason) {
1301
+ if (!reason) {
1302
+ return message;
1303
+ }
1304
+ return `${message}: ${reason}`;
1305
+ }
1306
+ function isPerformanceReport(value) {
1307
+ if (!value || typeof value !== "object") {
1308
+ return false;
1309
+ }
1310
+ const report = value;
1311
+ return typeof report.recordingId === "string" && typeof report.pageId === "string" && (report.source === "hook" || report.source === "cdp") && typeof report.startedAt === "number" && typeof report.endedAt === "number" && typeof report.durationMs === "number" && Boolean(report.summary) && Array.isArray(report.longTasks) && Array.isArray(report.limitations);
1312
+ }
1313
+ function isPerformanceStartResult(value) {
1314
+ if (!value || typeof value !== "object") {
1315
+ return false;
1316
+ }
1317
+ const result = value;
1318
+ return typeof result.recordingId === "string" && typeof result.startedAt === "number";
1319
+ }
1320
+ function isPlainRecord(value) {
1321
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1322
+ }
1323
+ function toStructuredRecord(value) {
1324
+ if (isPlainRecord(value)) {
1325
+ return value;
1326
+ }
1327
+ return {};
1328
+ }
1329
+
1330
+ // src/mcp/tools/pages.ts
1331
+ var import_zod6 = require("zod");
1332
+
708
1333
  // src/plugin/entryDiscovery.ts
709
1334
  var import_node_fs = __toESM(require("fs"), 1);
710
- var import_node_path = __toESM(require("path"), 1);
1335
+ var import_node_path2 = __toESM(require("path"), 1);
711
1336
  var import_vite = require("vite");
712
1337
  function discoverHtmlEntries(server) {
713
1338
  const root = server.config.root;
@@ -720,7 +1345,7 @@ function walkHtmlEntries(root, dir, entries) {
720
1345
  if (item.name === "node_modules" || item.name.startsWith(".")) {
721
1346
  continue;
722
1347
  }
723
- const fullPath = import_node_path.default.join(dir, item.name);
1348
+ const fullPath = import_node_path2.default.join(dir, item.name);
724
1349
  if (item.isDirectory()) {
725
1350
  walkHtmlEntries(root, fullPath, entries);
726
1351
  continue;
@@ -728,10 +1353,10 @@ function walkHtmlEntries(root, dir, entries) {
728
1353
  if (!item.isFile() || !item.name.endsWith(".html")) {
729
1354
  continue;
730
1355
  }
731
- const relative = (0, import_vite.normalizePath)(import_node_path.default.relative(root, fullPath));
1356
+ const relative2 = (0, import_vite.normalizePath)(import_node_path2.default.relative(root, fullPath));
732
1357
  entries.push({
733
- file: relative,
734
- pathname: relative === "index.html" ? "/" : `/${relative}`
1358
+ file: relative2,
1359
+ pathname: relative2 === "index.html" ? "/" : `/${relative2}`
735
1360
  });
736
1361
  }
737
1362
  }
@@ -743,7 +1368,7 @@ function registerPageTools(server, ctx, vite) {
743
1368
  {
744
1369
  description: "List Vite page entries and connected runtime/CDP targets.",
745
1370
  inputSchema: {
746
- includeDisconnected: import_zod5.z.boolean().optional()
1371
+ includeDisconnected: import_zod6.z.boolean().optional()
747
1372
  }
748
1373
  },
749
1374
  async (input) => {
@@ -766,8 +1391,8 @@ function registerPageTools(server, ctx, vite) {
766
1391
  {
767
1392
  description: "Reload the selected page. CDP uses ignoreCache; Runtime Hook falls back to normal reload.",
768
1393
  inputSchema: {
769
- pageId: import_zod5.z.string().optional(),
770
- ignoreCache: import_zod5.z.boolean().optional()
1394
+ pageId: import_zod6.z.string().optional(),
1395
+ ignoreCache: import_zod6.z.boolean().optional()
771
1396
  }
772
1397
  },
773
1398
  async (input) => {
@@ -826,16 +1451,16 @@ function resolveRuntimeReloadTarget(ctx, pageId) {
826
1451
  function waitForRuntimePageReconnect(ctx) {
827
1452
  let timeout;
828
1453
  let cleanup;
829
- const promise = new Promise((resolve) => {
1454
+ const promise = new Promise((resolve2) => {
830
1455
  timeout = setTimeout(() => {
831
1456
  cleanup?.();
832
- resolve(null);
1457
+ resolve2(null);
833
1458
  }, 5e3);
834
1459
  cleanup = ctx.hooks.hookOnce(RUNTIME_PAGE_RECONNECTED_EVENT, (payload) => {
835
1460
  if (timeout) {
836
1461
  clearTimeout(timeout);
837
1462
  }
838
- resolve(isPageTarget(payload) ? payload : null);
1463
+ resolve2(isPageTarget(payload) ? payload : null);
839
1464
  });
840
1465
  });
841
1466
  return {
@@ -919,7 +1544,7 @@ function getPathname(url) {
919
1544
  }
920
1545
 
921
1546
  // src/mcp/tools/screenshot.ts
922
- var import_zod6 = require("zod");
1547
+ var import_zod7 = require("zod");
923
1548
 
924
1549
  // src/cdp/cdpScreenshot.ts
925
1550
  async function cdpCaptureScreenshot(options) {
@@ -1025,16 +1650,16 @@ function isElementRect(value) {
1025
1650
 
1026
1651
  // src/mcp/tools/screenshotOutput.ts
1027
1652
  var import_node_crypto = require("crypto");
1028
- var import_promises = require("fs/promises");
1029
- var import_node_path2 = __toESM(require("path"), 1);
1653
+ var import_promises2 = require("fs/promises");
1654
+ var import_node_path3 = __toESM(require("path"), 1);
1030
1655
  async function createScreenshotOutput(ctx, payload) {
1031
1656
  if (ctx.options.screenshot.type === "base64") {
1032
1657
  return payload;
1033
1658
  }
1034
1659
  const saveDir = resolveScreenshotSaveDir(ctx);
1035
- await (0, import_promises.mkdir)(saveDir, { recursive: true });
1036
- const filePath = import_node_path2.default.join(saveDir, createScreenshotFileName(payload));
1037
- await (0, import_promises.writeFile)(filePath, Buffer.from(payload.data, "base64"));
1660
+ await (0, import_promises2.mkdir)(saveDir, { recursive: true });
1661
+ const filePath = import_node_path3.default.join(saveDir, createScreenshotFileName(payload));
1662
+ await (0, import_promises2.writeFile)(filePath, Buffer.from(payload.data, "base64"));
1038
1663
  return {
1039
1664
  source: payload.source,
1040
1665
  target: payload.target,
@@ -1057,10 +1682,10 @@ function resolveScreenshotSaveDir(ctx) {
1057
1682
  if (!root) {
1058
1683
  throw new Error("Vite server root is required for screenshot path output");
1059
1684
  }
1060
- if (import_node_path2.default.isAbsolute(saveDir)) {
1685
+ if (import_node_path3.default.isAbsolute(saveDir)) {
1061
1686
  return saveDir;
1062
1687
  }
1063
- return import_node_path2.default.resolve(root, saveDir);
1688
+ return import_node_path3.default.resolve(root, saveDir);
1064
1689
  }
1065
1690
  function createScreenshotFileName(payload) {
1066
1691
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -1072,21 +1697,21 @@ function createProjectRelativePath(ctx, filePath) {
1072
1697
  if (!root) {
1073
1698
  throw new Error("Vite server root is required for screenshot path output");
1074
1699
  }
1075
- return import_node_path2.default.relative(root, filePath).split(import_node_path2.default.sep).join("/");
1700
+ return import_node_path3.default.relative(root, filePath).split(import_node_path3.default.sep).join("/");
1076
1701
  }
1077
1702
 
1078
1703
  // src/mcp/tools/screenshot.ts
1079
1704
  var DEFAULT_SCREENSHOT_TARGET = "viewport";
1080
1705
  var DEFAULT_SCREENSHOT_FORMAT = "png";
1081
1706
  var screenshotInputSchema = {
1082
- pageId: import_zod6.z.string().optional(),
1083
- target: import_zod6.z.enum(["viewport", "fullPage", "element"]).optional(),
1084
- selector: import_zod6.z.string().optional(),
1085
- format: import_zod6.z.enum(["png", "jpeg", "webp"]).optional(),
1086
- prefer: import_zod6.z.enum(["auto", "cdp", "runtime"]).optional(),
1087
- quality: import_zod6.z.number().optional(),
1088
- scale: import_zod6.z.number().optional(),
1089
- snapdom: import_zod6.z.record(import_zod6.z.string(), import_zod6.z.unknown()).optional()
1707
+ pageId: import_zod7.z.string().optional(),
1708
+ target: import_zod7.z.enum(["viewport", "fullPage", "element"]).optional(),
1709
+ selector: import_zod7.z.string().optional(),
1710
+ format: import_zod7.z.enum(["png", "jpeg", "webp"]).optional(),
1711
+ prefer: import_zod7.z.enum(["auto", "cdp", "runtime"]).optional(),
1712
+ quality: import_zod7.z.number().optional(),
1713
+ scale: import_zod7.z.number().optional(),
1714
+ snapdom: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.unknown()).optional()
1090
1715
  };
1091
1716
  function registerScreenshotTools(server, ctx) {
1092
1717
  server.registerTool(
@@ -1159,7 +1784,7 @@ async function createRuntimeScreenshot(ctx, input, normalized) {
1159
1784
  `screenshot is too large: ${String(result.byteLength)} bytes`
1160
1785
  );
1161
1786
  }
1162
- if (!isPlainRecord(result)) {
1787
+ if (!isPlainRecord2(result)) {
1163
1788
  return createToolError("runtime screenshot returned an invalid response");
1164
1789
  }
1165
1790
  if (result.ok === false) {
@@ -1188,9 +1813,9 @@ async function createScreenshotResponse(ctx, result) {
1188
1813
  }
1189
1814
  }
1190
1815
  function isScreenshotTooLarge(ctx, result) {
1191
- return isPlainRecord(result) && "byteLength" in result && typeof result.byteLength === "number" && result.byteLength > ctx.options.screenshot.maxBytes;
1816
+ return isPlainRecord2(result) && "byteLength" in result && typeof result.byteLength === "number" && result.byteLength > ctx.options.screenshot.maxBytes;
1192
1817
  }
1193
- function isPlainRecord(value) {
1818
+ function isPlainRecord2(value) {
1194
1819
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1195
1820
  }
1196
1821
  function createMimeType(format) {
@@ -1205,8 +1830,8 @@ function isScreenshotImagePayload(result) {
1205
1830
  }
1206
1831
 
1207
1832
  // src/mcp/tools/vue.ts
1208
- var import_nanoid2 = require("nanoid");
1209
- var import_zod7 = require("zod");
1833
+ var import_nanoid3 = require("nanoid");
1834
+ var import_zod8 = require("zod");
1210
1835
  function registerVueTools(server, ctx) {
1211
1836
  server.registerTool(
1212
1837
  MCP_TOOL_NAMES.getComponentTree,
@@ -1219,7 +1844,7 @@ function registerVueTools(server, ctx) {
1219
1844
  MCP_TOOL_NAMES.getComponentState,
1220
1845
  {
1221
1846
  description: "Get Vue component state.",
1222
- inputSchema: { componentName: import_zod7.z.string() }
1847
+ inputSchema: { componentName: import_zod8.z.string() }
1223
1848
  },
1224
1849
  async ({ componentName }) => requestVueData(ctx, (event) => {
1225
1850
  void ctx.rpcServer?.getInspectorState({ event, componentName });
@@ -1230,10 +1855,10 @@ function registerVueTools(server, ctx) {
1230
1855
  {
1231
1856
  description: "Edit Vue component state.",
1232
1857
  inputSchema: {
1233
- componentName: import_zod7.z.string(),
1234
- path: import_zod7.z.array(import_zod7.z.string()),
1235
- value: import_zod7.z.string(),
1236
- valueType: import_zod7.z.enum(["string", "number", "boolean", "object", "array"])
1858
+ componentName: import_zod8.z.string(),
1859
+ path: import_zod8.z.array(import_zod8.z.string()),
1860
+ value: import_zod8.z.string(),
1861
+ valueType: import_zod8.z.enum(["string", "number", "boolean", "object", "array"])
1237
1862
  }
1238
1863
  },
1239
1864
  ({ componentName, path: path8, value, valueType }) => {
@@ -1253,7 +1878,7 @@ function registerVueTools(server, ctx) {
1253
1878
  MCP_TOOL_NAMES.highlightComponent,
1254
1879
  {
1255
1880
  description: "Highlight a Vue component.",
1256
- inputSchema: { componentName: import_zod7.z.string() }
1881
+ inputSchema: { componentName: import_zod8.z.string() }
1257
1882
  },
1258
1883
  ({ componentName }) => {
1259
1884
  if (!ctx.rpcServer) {
@@ -1281,7 +1906,7 @@ function registerVueTools(server, ctx) {
1281
1906
  MCP_TOOL_NAMES.getPiniaState,
1282
1907
  {
1283
1908
  description: "Get Pinia store state.",
1284
- inputSchema: { storeName: import_zod7.z.string() }
1909
+ inputSchema: { storeName: import_zod8.z.string() }
1285
1910
  },
1286
1911
  async ({ storeName }) => requestVueData(ctx, (event) => {
1287
1912
  void ctx.rpcServer?.getPiniaState({ event, storeName });
@@ -1292,7 +1917,7 @@ async function requestVueData(ctx, trigger) {
1292
1917
  if (!ctx.rpcServer) {
1293
1918
  return vueBridgeUnavailable();
1294
1919
  }
1295
- const event = (0, import_nanoid2.nanoid)();
1920
+ const event = (0, import_nanoid3.nanoid)();
1296
1921
  const data = await waitForVueHook(ctx, event, () => {
1297
1922
  trigger(event);
1298
1923
  });
@@ -1301,13 +1926,13 @@ async function requestVueData(ctx, trigger) {
1301
1926
  };
1302
1927
  }
1303
1928
  function waitForVueHook(ctx, event, trigger) {
1304
- return new Promise((resolve) => {
1929
+ return new Promise((resolve2) => {
1305
1930
  const timeout = setTimeout(() => {
1306
- resolve({ ok: false, error: "Vue runtime bridge response timed out" });
1931
+ resolve2({ ok: false, error: "Vue runtime bridge response timed out" });
1307
1932
  }, 5e3);
1308
1933
  ctx.hooks.hookOnce(event, (data) => {
1309
1934
  clearTimeout(timeout);
1310
- resolve(data);
1935
+ resolve2(data);
1311
1936
  });
1312
1937
  trigger();
1313
1938
  });
@@ -1328,6 +1953,7 @@ function createMcpServer(ctx, vite) {
1328
1953
  registerConsoleTools(server, ctx);
1329
1954
  registerEvaluateTools(server, ctx);
1330
1955
  registerNetworkTools(server, ctx);
1956
+ registerPerformanceTools(server, ctx);
1331
1957
  registerVueTools(server, ctx);
1332
1958
  return server;
1333
1959
  }
@@ -1415,6 +2041,18 @@ function createServerVueRuntimeRpc(ctx) {
1415
2041
  onScreenshotTaken: (event, data) => {
1416
2042
  void ctx.hooks.callHook(event, data);
1417
2043
  },
2044
+ recordPerformance: () => void 0,
2045
+ onPerformanceRecorded: (event, data) => {
2046
+ void ctx.hooks.callHook(event, data);
2047
+ },
2048
+ startPerformanceRecording: () => void 0,
2049
+ onPerformanceRecordingStarted: (event, data) => {
2050
+ void ctx.hooks.callHook(event, data);
2051
+ },
2052
+ stopPerformanceRecording: () => void 0,
2053
+ onPerformanceRecordingStopped: (event, data) => {
2054
+ void ctx.hooks.callHook(event, data);
2055
+ },
1418
2056
  getInspectorTree: () => void 0,
1419
2057
  onInspectorTreeUpdated: (event, data) => {
1420
2058
  void ctx.hooks.callHook(event, data);
@@ -1441,7 +2079,7 @@ function createServerVueRuntimeRpc(ctx) {
1441
2079
  }
1442
2080
 
1443
2081
  // src/cdp/cdpConsole.ts
1444
- var import_nanoid3 = require("nanoid");
2082
+ var import_nanoid4 = require("nanoid");
1445
2083
 
1446
2084
  // src/shared/serialization.ts
1447
2085
  function safeStringify(value) {
@@ -1470,7 +2108,7 @@ async function startCdpConsole(options) {
1470
2108
  await options.client.Runtime.enable();
1471
2109
  options.client.Runtime.consoleAPICalled((event) => {
1472
2110
  options.push({
1473
- id: (0, import_nanoid3.nanoid)(),
2111
+ id: (0, import_nanoid4.nanoid)(),
1474
2112
  pageId: options.pageId,
1475
2113
  source: "cdp",
1476
2114
  level: normalizeConsoleLevel(event.type),
@@ -1490,7 +2128,7 @@ function normalizeConsoleLevel(level) {
1490
2128
  }
1491
2129
 
1492
2130
  // src/cdp/cdpNetwork.ts
1493
- var import_nanoid4 = require("nanoid");
2131
+ var import_nanoid5 = require("nanoid");
1494
2132
 
1495
2133
  // src/shared/sanitize.ts
1496
2134
  function maskHeaders(headers = {}, maskNames = []) {
@@ -1530,7 +2168,7 @@ async function startCdpNetwork(options) {
1530
2168
  await options.client.Network.enable();
1531
2169
  options.client.Network.requestWillBeSent((event) => {
1532
2170
  records.set(event.requestId, {
1533
- id: (0, import_nanoid4.nanoid)(),
2171
+ id: (0, import_nanoid5.nanoid)(),
1534
2172
  pageId: options.pageId,
1535
2173
  source: "cdp",
1536
2174
  url: event.request.url,
@@ -1701,7 +2339,7 @@ async function startCdpObservers(ctx, target, client) {
1701
2339
 
1702
2340
  // src/plugin/injectRuntime.ts
1703
2341
  var import_node_module = require("module");
1704
- var import_node_path3 = require("path");
2342
+ var import_node_path4 = require("path");
1705
2343
  function createRuntimeInjectionController(options, getConfig) {
1706
2344
  return {
1707
2345
  resolveId(importee) {
@@ -1785,7 +2423,7 @@ function createSnapdomLoaderModule(root) {
1785
2423
  }
1786
2424
  function canResolveSnapdomFromProject(root) {
1787
2425
  try {
1788
- (0, import_node_module.createRequire)((0, import_node_path3.join)(root ?? process.cwd(), "package.json")).resolve(
2426
+ (0, import_node_module.createRequire)((0, import_node_path4.join)(root ?? process.cwd(), "package.json")).resolve(
1789
2427
  "@zumer/snapdom"
1790
2428
  );
1791
2429
  return true;
@@ -1824,18 +2462,18 @@ function getPluginPath(plugin) {
1824
2462
  }
1825
2463
 
1826
2464
  // src/plugin/mcpClientConfig/index.ts
1827
- var import_promises4 = __toESM(require("fs/promises"), 1);
1828
- var import_node_path6 = __toESM(require("path"), 1);
2465
+ var import_promises5 = __toESM(require("fs/promises"), 1);
2466
+ var import_node_path7 = __toESM(require("path"), 1);
1829
2467
 
1830
2468
  // src/plugin/mcpClientConfig/codexConfig.ts
1831
- var import_promises2 = __toESM(require("fs/promises"), 1);
1832
- var import_node_path4 = __toESM(require("path"), 1);
2469
+ var import_promises3 = __toESM(require("fs/promises"), 1);
2470
+ var import_node_path5 = __toESM(require("path"), 1);
1833
2471
  async function updateCodexMcpClientConfig(options) {
1834
2472
  try {
1835
2473
  const current = await readOptionalTextFile(options.configPath);
1836
2474
  const next = replaceOrAppendOwnedBlock(current, options);
1837
- await import_promises2.default.mkdir(import_node_path4.default.dirname(options.configPath), { recursive: true });
1838
- await import_promises2.default.writeFile(options.configPath, next);
2475
+ await import_promises3.default.mkdir(import_node_path5.default.dirname(options.configPath), { recursive: true });
2476
+ await import_promises3.default.writeFile(options.configPath, next);
1839
2477
  } catch (error) {
1840
2478
  console.warn(
1841
2479
  `[vite-plugin-vue-mcp-next] Failed to update Codex MCP config at ${options.configPath}: ${formatError(error)}`
@@ -1907,7 +2545,7 @@ function renameServerTableHeader(line, fromServerName, toServerName) {
1907
2545
  }
1908
2546
  async function readOptionalTextFile(filePath) {
1909
2547
  try {
1910
- return await import_promises2.default.readFile(filePath, "utf-8");
2548
+ return await import_promises3.default.readFile(filePath, "utf-8");
1911
2549
  } catch (error) {
1912
2550
  if (isNodeError(error) && error.code === "ENOENT") {
1913
2551
  return "";
@@ -1933,16 +2571,16 @@ function isNodeError(error) {
1933
2571
  }
1934
2572
 
1935
2573
  // src/plugin/mcpClientConfig/jsonConfig.ts
1936
- var import_promises3 = __toESM(require("fs/promises"), 1);
1937
- var import_node_path5 = __toESM(require("path"), 1);
2574
+ var import_promises4 = __toESM(require("fs/promises"), 1);
2575
+ var import_node_path6 = __toESM(require("path"), 1);
1938
2576
  async function updateJsonMcpClientConfig(options) {
1939
2577
  try {
1940
2578
  const config = await readJsonConfig(options.configPath);
1941
- if (!isPlainRecord2(config)) {
2579
+ if (!isPlainRecord3(config)) {
1942
2580
  warnConfigFailure(options, "config root must be a JSON object");
1943
2581
  return;
1944
2582
  }
1945
- const mcpServers = isPlainRecord2(config.mcpServers) ? config.mcpServers : {};
2583
+ const mcpServers = isPlainRecord3(config.mcpServers) ? config.mcpServers : {};
1946
2584
  if (Object.hasOwn(mcpServers, options.serverName)) {
1947
2585
  return;
1948
2586
  }
@@ -1974,8 +2612,8 @@ function renameLegacyServer(mcpServers, options) {
1974
2612
  );
1975
2613
  }
1976
2614
  async function writeJsonConfig(configPath, config) {
1977
- await import_promises3.default.mkdir(import_node_path5.default.dirname(configPath), { recursive: true });
1978
- await import_promises3.default.writeFile(configPath, `${JSON.stringify(config, null, 2)}
2615
+ await import_promises4.default.mkdir(import_node_path6.default.dirname(configPath), { recursive: true });
2616
+ await import_promises4.default.writeFile(configPath, `${JSON.stringify(config, null, 2)}
1979
2617
  `);
1980
2618
  }
1981
2619
  async function readJsonConfig(configPath) {
@@ -1987,7 +2625,7 @@ async function readJsonConfig(configPath) {
1987
2625
  }
1988
2626
  async function readOptionalTextFile2(filePath) {
1989
2627
  try {
1990
- return await import_promises3.default.readFile(filePath, "utf-8");
2628
+ return await import_promises4.default.readFile(filePath, "utf-8");
1991
2629
  } catch (error) {
1992
2630
  if (isNodeError2(error) && error.code === "ENOENT") {
1993
2631
  return "{}";
@@ -1995,7 +2633,7 @@ async function readOptionalTextFile2(filePath) {
1995
2633
  throw error;
1996
2634
  }
1997
2635
  }
1998
- function isPlainRecord2(value) {
2636
+ function isPlainRecord3(value) {
1999
2637
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2000
2638
  }
2001
2639
  function warnConfigFailure(options, reason) {
@@ -2019,12 +2657,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
2019
2657
  root,
2020
2658
  clientName: "cursor",
2021
2659
  enabled: options.cursor,
2022
- entryPath: import_node_path6.default.join(root, ".cursor"),
2660
+ entryPath: import_node_path7.default.join(root, ".cursor"),
2023
2661
  entryKind: "directory",
2024
2662
  userOptions,
2025
2663
  createJob: () => updateJsonMcpClientConfig({
2026
2664
  clientName: "Cursor",
2027
- configPath: import_node_path6.default.join(root, ".cursor", "mcp.json"),
2665
+ configPath: import_node_path7.default.join(root, ".cursor", "mcp.json"),
2028
2666
  mcpUrl: sseUrl,
2029
2667
  serverName,
2030
2668
  legacyServerNames
@@ -2034,11 +2672,11 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
2034
2672
  root,
2035
2673
  clientName: "codex",
2036
2674
  enabled: options.codex,
2037
- entryPath: import_node_path6.default.join(root, ".codex"),
2675
+ entryPath: import_node_path7.default.join(root, ".codex"),
2038
2676
  entryKind: "directory",
2039
2677
  userOptions,
2040
2678
  createJob: () => updateCodexMcpClientConfig({
2041
- configPath: import_node_path6.default.join(root, ".codex", "config.toml"),
2679
+ configPath: import_node_path7.default.join(root, ".codex", "config.toml"),
2042
2680
  mcpUrl: streamableHttpUrl,
2043
2681
  serverName,
2044
2682
  legacyServerNames
@@ -2048,12 +2686,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
2048
2686
  root,
2049
2687
  clientName: "claudeCode",
2050
2688
  enabled: options.claudeCode,
2051
- entryPath: import_node_path6.default.join(root, ".mcp.json"),
2689
+ entryPath: import_node_path7.default.join(root, ".mcp.json"),
2052
2690
  entryKind: "file",
2053
2691
  userOptions,
2054
2692
  createJob: () => updateJsonMcpClientConfig({
2055
2693
  clientName: "Claude Code",
2056
- configPath: import_node_path6.default.join(root, ".mcp.json"),
2694
+ configPath: import_node_path7.default.join(root, ".mcp.json"),
2057
2695
  mcpUrl: sseUrl,
2058
2696
  serverName,
2059
2697
  legacyServerNames
@@ -2063,12 +2701,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
2063
2701
  root,
2064
2702
  clientName: "trae",
2065
2703
  enabled: options.trae,
2066
- entryPath: import_node_path6.default.join(root, ".trae"),
2704
+ entryPath: import_node_path7.default.join(root, ".trae"),
2067
2705
  entryKind: "directory",
2068
2706
  userOptions,
2069
2707
  createJob: () => updateJsonMcpClientConfig({
2070
2708
  clientName: "Trae",
2071
- configPath: import_node_path6.default.join(root, ".trae", "mcp.json"),
2709
+ configPath: import_node_path7.default.join(root, ".trae", "mcp.json"),
2072
2710
  mcpUrl: sseUrl,
2073
2711
  serverName,
2074
2712
  legacyServerNames
@@ -2107,7 +2745,7 @@ function isClientExplicitlyConfigured(clientName, userOptions) {
2107
2745
  }
2108
2746
  async function hasExpectedEntry(entryPath, entryKind) {
2109
2747
  try {
2110
- const stat = await import_promises4.default.stat(entryPath);
2748
+ const stat = await import_promises5.default.stat(entryPath);
2111
2749
  return entryKind === "directory" ? stat.isDirectory() : stat.isFile();
2112
2750
  } catch (error) {
2113
2751
  if (isNodeError3(error) && error.code === "ENOENT") {
@@ -2127,12 +2765,12 @@ function isNodeError3(error) {
2127
2765
  }
2128
2766
 
2129
2767
  // src/plugin/skillConfig/index.ts
2130
- var import_promises6 = __toESM(require("fs/promises"), 1);
2131
- var import_node_path8 = __toESM(require("path"), 1);
2768
+ var import_promises7 = __toESM(require("fs/promises"), 1);
2769
+ var import_node_path9 = __toESM(require("path"), 1);
2132
2770
 
2133
2771
  // src/plugin/skillConfig/writers.ts
2134
- var import_promises5 = __toESM(require("fs/promises"), 1);
2135
- var import_node_path7 = __toESM(require("path"), 1);
2772
+ var import_promises6 = __toESM(require("fs/promises"), 1);
2773
+ var import_node_path8 = __toESM(require("path"), 1);
2136
2774
  var GENERATED_SKILL_CONFIG_MARKER = "<!-- Generated by vite-plugin-vue-mcp-next. Safe to edit, but automatic updates only apply while this marker remains. -->";
2137
2775
  async function writeGeneratedTextFile(options) {
2138
2776
  try {
@@ -2143,8 +2781,8 @@ async function writeGeneratedTextFile(options) {
2143
2781
  );
2144
2782
  return;
2145
2783
  }
2146
- await import_promises5.default.mkdir(import_node_path7.default.dirname(options.filePath), { recursive: true });
2147
- await import_promises5.default.writeFile(options.filePath, options.content);
2784
+ await import_promises6.default.mkdir(import_node_path8.default.dirname(options.filePath), { recursive: true });
2785
+ await import_promises6.default.writeFile(options.filePath, options.content);
2148
2786
  } catch (error) {
2149
2787
  console.warn(
2150
2788
  `[vite-plugin-vue-mcp-next] Failed to update ${options.targetName} at ${options.filePath}: ${formatError4(error)}`
@@ -2153,7 +2791,7 @@ async function writeGeneratedTextFile(options) {
2153
2791
  }
2154
2792
  async function readOptionalTextFile3(filePath) {
2155
2793
  try {
2156
- return await import_promises5.default.readFile(filePath, "utf-8");
2794
+ return await import_promises6.default.readFile(filePath, "utf-8");
2157
2795
  } catch (error) {
2158
2796
  if (isNodeError4(error) && error.code === "ENOENT") {
2159
2797
  return "";
@@ -2170,7 +2808,7 @@ function isNodeError4(error) {
2170
2808
 
2171
2809
  // src/plugin/skillConfig/index.ts
2172
2810
  var PACKAGE_NAME = "@xiaou66/vite-plugin-vue-mcp-next";
2173
- var PACKAGED_SKILL_PATH = import_node_path8.default.join("skills", "vite-mcp-next", "SKILL.md");
2811
+ var PACKAGED_SKILL_PATH = import_node_path9.default.join("skills", "vite-mcp-next", "SKILL.md");
2174
2812
  async function updateSkillConfigs(root, options) {
2175
2813
  if (!options.autoConfig) {
2176
2814
  return;
@@ -2186,13 +2824,13 @@ async function updateSkillConfigs(root, options) {
2186
2824
  function createSkillConfigDescriptors(root) {
2187
2825
  return [
2188
2826
  {
2189
- entryPath: import_node_path8.default.join(root, ".codex"),
2190
- filePath: import_node_path8.default.join(root, ".codex", "skills", "vite-mcp-next", "SKILL.md"),
2827
+ entryPath: import_node_path9.default.join(root, ".codex"),
2828
+ filePath: import_node_path9.default.join(root, ".codex", "skills", "vite-mcp-next", "SKILL.md"),
2191
2829
  targetName: "Codex skill"
2192
2830
  },
2193
2831
  {
2194
- entryPath: import_node_path8.default.join(root, ".claude"),
2195
- filePath: import_node_path8.default.join(
2832
+ entryPath: import_node_path9.default.join(root, ".claude"),
2833
+ filePath: import_node_path9.default.join(
2196
2834
  root,
2197
2835
  ".claude",
2198
2836
  "skills",
@@ -2202,8 +2840,8 @@ function createSkillConfigDescriptors(root) {
2202
2840
  targetName: "Claude Code skill"
2203
2841
  },
2204
2842
  {
2205
- entryPath: import_node_path8.default.join(root, ".cursor"),
2206
- filePath: import_node_path8.default.join(root, ".cursor", "rules", "vite-mcp-next.mdc"),
2843
+ entryPath: import_node_path9.default.join(root, ".cursor"),
2844
+ filePath: import_node_path9.default.join(root, ".cursor", "rules", "vite-mcp-next.mdc"),
2207
2845
  targetName: "Cursor rule"
2208
2846
  }
2209
2847
  ];
@@ -2227,7 +2865,7 @@ async function readPackagedSkillContent(root) {
2227
2865
  const candidates = getPackagedSkillCandidates(root);
2228
2866
  for (const candidate of candidates) {
2229
2867
  try {
2230
- return await import_promises6.default.readFile(candidate, "utf-8");
2868
+ return await import_promises7.default.readFile(candidate, "utf-8");
2231
2869
  } catch (error) {
2232
2870
  if (isNodeError5(error) && error.code === "ENOENT") {
2233
2871
  continue;
@@ -2251,14 +2889,14 @@ async function safelyReadPackagedSkillContent(root) {
2251
2889
  }
2252
2890
  function getPackagedSkillCandidates(root) {
2253
2891
  return [
2254
- import_node_path8.default.resolve(root, "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
2255
- import_node_path8.default.resolve(process.cwd(), "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
2256
- import_node_path8.default.resolve(process.cwd(), PACKAGED_SKILL_PATH)
2892
+ import_node_path9.default.resolve(root, "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
2893
+ import_node_path9.default.resolve(process.cwd(), "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
2894
+ import_node_path9.default.resolve(process.cwd(), PACKAGED_SKILL_PATH)
2257
2895
  ];
2258
2896
  }
2259
2897
  async function hasDirectoryEntry(entryPath) {
2260
2898
  try {
2261
- const stat = await import_promises6.default.stat(entryPath);
2899
+ const stat = await import_promises7.default.stat(entryPath);
2262
2900
  return stat.isDirectory();
2263
2901
  } catch (error) {
2264
2902
  if (isNodeError5(error) && error.code === "ENOENT") {
@@ -2337,6 +2975,14 @@ function vueMcpNext(userOptions = {}) {
2337
2975
  }
2338
2976
  }
2339
2977
  );
2978
+ server.ws.on(
2979
+ "vite-plugin-vue-mcp-next:performance-record",
2980
+ (payload) => {
2981
+ if (isPerformanceReport2(payload)) {
2982
+ appendPerformanceReport(ctx, payload);
2983
+ }
2984
+ }
2985
+ );
2340
2986
  const port = String(server.config.server.port || 5173);
2341
2987
  const mcpSseUrl = `http://${options.host}:${port}${options.mcpPath}/sse`;
2342
2988
  const mcpStreamableHttpUrl = `http://${options.host}:${port}${options.mcpPath}/mcp`;
@@ -2399,6 +3045,13 @@ function isNetworkRecord(payload) {
2399
3045
  const record = payload;
2400
3046
  return typeof record.id === "string" && typeof record.pageId === "string" && record.source === "hook" && typeof record.url === "string" && typeof record.method === "string" && typeof record.startedAt === "number";
2401
3047
  }
3048
+ function isPerformanceReport2(payload) {
3049
+ if (!payload || typeof payload !== "object") {
3050
+ return false;
3051
+ }
3052
+ const report = payload;
3053
+ return typeof report.recordingId === "string" && typeof report.pageId === "string" && (report.source === "hook" || report.source === "cdp") && Boolean(report.summary) && Array.isArray(report.longTasks) && Array.isArray(report.limitations);
3054
+ }
2402
3055
  // Annotate the CommonJS export names for ESM import in node:
2403
3056
  0 && (module.exports = {
2404
3057
  vueMcpNext