@xiaou66/vite-plugin-vue-mcp-next 1.0.3 → 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
  }
@@ -231,24 +266,64 @@ function createRingBuffer(capacity) {
231
266
  }
232
267
 
233
268
  // src/context.ts
269
+ var MINUTE_MS = 60 * 1e3;
270
+ var DISCONNECTED_RUNTIME_TARGET_RETENTION_MS = 5 * MINUTE_MS;
271
+ function shouldPruneDisconnectedRuntimeTarget(target, now) {
272
+ return target.source === "runtime" && !target.connected && typeof target.disconnectedAt === "number" && now - target.disconnectedAt > DISCONNECTED_RUNTIME_TARGET_RETENTION_MS;
273
+ }
274
+ function pruneDisconnectedRuntimeTargets(targets, now) {
275
+ for (const [pageId, target] of targets) {
276
+ if (shouldPruneDisconnectedRuntimeTarget(target, now)) {
277
+ targets.delete(pageId);
278
+ }
279
+ }
280
+ }
281
+ function markTargetDisconnected(target, now) {
282
+ return {
283
+ ...target,
284
+ connected: false,
285
+ disconnectedAt: target.disconnectedAt ?? now
286
+ };
287
+ }
288
+ function shouldListPageTarget(target, options) {
289
+ return options.includeDisconnected === true || target.source !== "runtime" || target.connected;
290
+ }
291
+ function disconnectPreviousRuntimeClientTarget(targets, target, now) {
292
+ if (target.source !== "runtime" || !target.runtimeClientId) {
293
+ return;
294
+ }
295
+ for (const [pageId, currentTarget] of targets) {
296
+ if (pageId === target.pageId || currentTarget.source !== "runtime" || currentTarget.runtimeClientId !== target.runtimeClientId || !currentTarget.connected) {
297
+ continue;
298
+ }
299
+ targets.set(pageId, markTargetDisconnected(currentTarget, now));
300
+ }
301
+ }
234
302
  function createPageTargetRegistry() {
235
303
  const targets = /* @__PURE__ */ new Map();
236
304
  return {
237
- upsert(target) {
305
+ upsert(target, now = Date.now()) {
306
+ pruneDisconnectedRuntimeTargets(targets, now);
307
+ disconnectPreviousRuntimeClientTarget(targets, target, now);
238
308
  targets.set(target.pageId, target);
239
309
  },
240
310
  get(pageId) {
241
311
  return targets.get(pageId);
242
312
  },
243
- list() {
244
- return [...targets.values()];
313
+ list(options = {}) {
314
+ const now = options.now ?? Date.now();
315
+ pruneDisconnectedRuntimeTargets(targets, now);
316
+ return [...targets.values()].filter(
317
+ (target) => shouldListPageTarget(target, options)
318
+ );
245
319
  },
246
- disconnect(pageId) {
320
+ disconnect(pageId, now = Date.now()) {
321
+ pruneDisconnectedRuntimeTargets(targets, now);
247
322
  const target = targets.get(pageId);
248
323
  if (!target) {
249
324
  return;
250
325
  }
251
- targets.set(pageId, { ...target, connected: false });
326
+ targets.set(pageId, markTargetDisconnected(target, now));
252
327
  }
253
328
  };
254
329
  }
@@ -259,7 +334,11 @@ function createVueMcpNextContext(options) {
259
334
  rpcServer: void 0,
260
335
  pages: createPageTargetRegistry(),
261
336
  consoleRecords: createRingBuffer(options.console.maxRecords),
262
- 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()
263
342
  };
264
343
  }
265
344
 
@@ -377,13 +456,13 @@ async function requestRuntimeData(ctx, trigger) {
377
456
  return { ok: false, error: "runtime bridge is not connected" };
378
457
  }
379
458
  const event = (0, import_nanoid.nanoid)();
380
- return new Promise((resolve) => {
459
+ return new Promise((resolve2) => {
381
460
  const timeout = setTimeout(() => {
382
- resolve({ ok: false, error: "runtime bridge response timed out" });
461
+ resolve2({ ok: false, error: "runtime bridge response timed out" });
383
462
  }, 5e3);
384
463
  ctx.hooks.hookOnce(event, (data) => {
385
464
  clearTimeout(timeout);
386
- resolve(data);
465
+ resolve2(data);
387
466
  });
388
467
  trigger(event);
389
468
  });
@@ -662,12 +741,598 @@ function registerNetworkTools(server, ctx) {
662
741
  );
663
742
  }
664
743
 
665
- // src/mcp/tools/pages.ts
744
+ // src/mcp/tools/performance.ts
666
745
  var import_zod5 = require("zod");
667
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
+
668
1333
  // src/plugin/entryDiscovery.ts
669
1334
  var import_node_fs = __toESM(require("fs"), 1);
670
- var import_node_path = __toESM(require("path"), 1);
1335
+ var import_node_path2 = __toESM(require("path"), 1);
671
1336
  var import_vite = require("vite");
672
1337
  function discoverHtmlEntries(server) {
673
1338
  const root = server.config.root;
@@ -680,7 +1345,7 @@ function walkHtmlEntries(root, dir, entries) {
680
1345
  if (item.name === "node_modules" || item.name.startsWith(".")) {
681
1346
  continue;
682
1347
  }
683
- const fullPath = import_node_path.default.join(dir, item.name);
1348
+ const fullPath = import_node_path2.default.join(dir, item.name);
684
1349
  if (item.isDirectory()) {
685
1350
  walkHtmlEntries(root, fullPath, entries);
686
1351
  continue;
@@ -688,10 +1353,10 @@ function walkHtmlEntries(root, dir, entries) {
688
1353
  if (!item.isFile() || !item.name.endsWith(".html")) {
689
1354
  continue;
690
1355
  }
691
- 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));
692
1357
  entries.push({
693
- file: relative,
694
- pathname: relative === "index.html" ? "/" : `/${relative}`
1358
+ file: relative2,
1359
+ pathname: relative2 === "index.html" ? "/" : `/${relative2}`
695
1360
  });
696
1361
  }
697
1362
  }
@@ -701,9 +1366,12 @@ function registerPageTools(server, ctx, vite) {
701
1366
  server.registerTool(
702
1367
  MCP_TOOL_NAMES.listPages,
703
1368
  {
704
- description: "List Vite page entries and connected runtime/CDP targets."
1369
+ description: "List Vite page entries and connected runtime/CDP targets.",
1370
+ inputSchema: {
1371
+ includeDisconnected: import_zod6.z.boolean().optional()
1372
+ }
705
1373
  },
706
- async () => {
1374
+ async (input) => {
707
1375
  const cdpResult = await listCdpPageTargets(ctx);
708
1376
  for (const target of cdpResult.pages) {
709
1377
  ctx.pages.upsert(target);
@@ -711,7 +1379,9 @@ function registerPageTools(server, ctx, vite) {
711
1379
  }
712
1380
  return createToolResponse({
713
1381
  entries: discoverHtmlEntries(vite),
714
- pages: ctx.pages.list(),
1382
+ pages: ctx.pages.list({
1383
+ includeDisconnected: input.includeDisconnected
1384
+ }),
715
1385
  cdpError: cdpResult.error
716
1386
  });
717
1387
  }
@@ -721,8 +1391,8 @@ function registerPageTools(server, ctx, vite) {
721
1391
  {
722
1392
  description: "Reload the selected page. CDP uses ignoreCache; Runtime Hook falls back to normal reload.",
723
1393
  inputSchema: {
724
- pageId: import_zod5.z.string().optional(),
725
- ignoreCache: import_zod5.z.boolean().optional()
1394
+ pageId: import_zod6.z.string().optional(),
1395
+ ignoreCache: import_zod6.z.boolean().optional()
726
1396
  }
727
1397
  },
728
1398
  async (input) => {
@@ -781,16 +1451,16 @@ function resolveRuntimeReloadTarget(ctx, pageId) {
781
1451
  function waitForRuntimePageReconnect(ctx) {
782
1452
  let timeout;
783
1453
  let cleanup;
784
- const promise = new Promise((resolve) => {
1454
+ const promise = new Promise((resolve2) => {
785
1455
  timeout = setTimeout(() => {
786
1456
  cleanup?.();
787
- resolve(null);
1457
+ resolve2(null);
788
1458
  }, 5e3);
789
1459
  cleanup = ctx.hooks.hookOnce(RUNTIME_PAGE_RECONNECTED_EVENT, (payload) => {
790
1460
  if (timeout) {
791
1461
  clearTimeout(timeout);
792
1462
  }
793
- resolve(isPageTarget(payload) ? payload : null);
1463
+ resolve2(isPageTarget(payload) ? payload : null);
794
1464
  });
795
1465
  });
796
1466
  return {
@@ -874,7 +1544,7 @@ function getPathname(url) {
874
1544
  }
875
1545
 
876
1546
  // src/mcp/tools/screenshot.ts
877
- var import_zod6 = require("zod");
1547
+ var import_zod7 = require("zod");
878
1548
 
879
1549
  // src/cdp/cdpScreenshot.ts
880
1550
  async function cdpCaptureScreenshot(options) {
@@ -980,16 +1650,16 @@ function isElementRect(value) {
980
1650
 
981
1651
  // src/mcp/tools/screenshotOutput.ts
982
1652
  var import_node_crypto = require("crypto");
983
- var import_promises = require("fs/promises");
984
- var import_node_path2 = __toESM(require("path"), 1);
1653
+ var import_promises2 = require("fs/promises");
1654
+ var import_node_path3 = __toESM(require("path"), 1);
985
1655
  async function createScreenshotOutput(ctx, payload) {
986
1656
  if (ctx.options.screenshot.type === "base64") {
987
1657
  return payload;
988
1658
  }
989
1659
  const saveDir = resolveScreenshotSaveDir(ctx);
990
- await (0, import_promises.mkdir)(saveDir, { recursive: true });
991
- const filePath = import_node_path2.default.join(saveDir, createScreenshotFileName(payload));
992
- 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"));
993
1663
  return {
994
1664
  source: payload.source,
995
1665
  target: payload.target,
@@ -1012,10 +1682,10 @@ function resolveScreenshotSaveDir(ctx) {
1012
1682
  if (!root) {
1013
1683
  throw new Error("Vite server root is required for screenshot path output");
1014
1684
  }
1015
- if (import_node_path2.default.isAbsolute(saveDir)) {
1685
+ if (import_node_path3.default.isAbsolute(saveDir)) {
1016
1686
  return saveDir;
1017
1687
  }
1018
- return import_node_path2.default.resolve(root, saveDir);
1688
+ return import_node_path3.default.resolve(root, saveDir);
1019
1689
  }
1020
1690
  function createScreenshotFileName(payload) {
1021
1691
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -1027,21 +1697,21 @@ function createProjectRelativePath(ctx, filePath) {
1027
1697
  if (!root) {
1028
1698
  throw new Error("Vite server root is required for screenshot path output");
1029
1699
  }
1030
- 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("/");
1031
1701
  }
1032
1702
 
1033
1703
  // src/mcp/tools/screenshot.ts
1034
1704
  var DEFAULT_SCREENSHOT_TARGET = "viewport";
1035
1705
  var DEFAULT_SCREENSHOT_FORMAT = "png";
1036
1706
  var screenshotInputSchema = {
1037
- pageId: import_zod6.z.string().optional(),
1038
- target: import_zod6.z.enum(["viewport", "fullPage", "element"]).optional(),
1039
- selector: import_zod6.z.string().optional(),
1040
- format: import_zod6.z.enum(["png", "jpeg", "webp"]).optional(),
1041
- prefer: import_zod6.z.enum(["auto", "cdp", "runtime"]).optional(),
1042
- quality: import_zod6.z.number().optional(),
1043
- scale: import_zod6.z.number().optional(),
1044
- 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()
1045
1715
  };
1046
1716
  function registerScreenshotTools(server, ctx) {
1047
1717
  server.registerTool(
@@ -1114,7 +1784,7 @@ async function createRuntimeScreenshot(ctx, input, normalized) {
1114
1784
  `screenshot is too large: ${String(result.byteLength)} bytes`
1115
1785
  );
1116
1786
  }
1117
- if (!isPlainRecord(result)) {
1787
+ if (!isPlainRecord2(result)) {
1118
1788
  return createToolError("runtime screenshot returned an invalid response");
1119
1789
  }
1120
1790
  if (result.ok === false) {
@@ -1143,9 +1813,9 @@ async function createScreenshotResponse(ctx, result) {
1143
1813
  }
1144
1814
  }
1145
1815
  function isScreenshotTooLarge(ctx, result) {
1146
- 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;
1147
1817
  }
1148
- function isPlainRecord(value) {
1818
+ function isPlainRecord2(value) {
1149
1819
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1150
1820
  }
1151
1821
  function createMimeType(format) {
@@ -1160,8 +1830,8 @@ function isScreenshotImagePayload(result) {
1160
1830
  }
1161
1831
 
1162
1832
  // src/mcp/tools/vue.ts
1163
- var import_nanoid2 = require("nanoid");
1164
- var import_zod7 = require("zod");
1833
+ var import_nanoid3 = require("nanoid");
1834
+ var import_zod8 = require("zod");
1165
1835
  function registerVueTools(server, ctx) {
1166
1836
  server.registerTool(
1167
1837
  MCP_TOOL_NAMES.getComponentTree,
@@ -1174,7 +1844,7 @@ function registerVueTools(server, ctx) {
1174
1844
  MCP_TOOL_NAMES.getComponentState,
1175
1845
  {
1176
1846
  description: "Get Vue component state.",
1177
- inputSchema: { componentName: import_zod7.z.string() }
1847
+ inputSchema: { componentName: import_zod8.z.string() }
1178
1848
  },
1179
1849
  async ({ componentName }) => requestVueData(ctx, (event) => {
1180
1850
  void ctx.rpcServer?.getInspectorState({ event, componentName });
@@ -1185,10 +1855,10 @@ function registerVueTools(server, ctx) {
1185
1855
  {
1186
1856
  description: "Edit Vue component state.",
1187
1857
  inputSchema: {
1188
- componentName: import_zod7.z.string(),
1189
- path: import_zod7.z.array(import_zod7.z.string()),
1190
- value: import_zod7.z.string(),
1191
- 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"])
1192
1862
  }
1193
1863
  },
1194
1864
  ({ componentName, path: path8, value, valueType }) => {
@@ -1208,7 +1878,7 @@ function registerVueTools(server, ctx) {
1208
1878
  MCP_TOOL_NAMES.highlightComponent,
1209
1879
  {
1210
1880
  description: "Highlight a Vue component.",
1211
- inputSchema: { componentName: import_zod7.z.string() }
1881
+ inputSchema: { componentName: import_zod8.z.string() }
1212
1882
  },
1213
1883
  ({ componentName }) => {
1214
1884
  if (!ctx.rpcServer) {
@@ -1236,7 +1906,7 @@ function registerVueTools(server, ctx) {
1236
1906
  MCP_TOOL_NAMES.getPiniaState,
1237
1907
  {
1238
1908
  description: "Get Pinia store state.",
1239
- inputSchema: { storeName: import_zod7.z.string() }
1909
+ inputSchema: { storeName: import_zod8.z.string() }
1240
1910
  },
1241
1911
  async ({ storeName }) => requestVueData(ctx, (event) => {
1242
1912
  void ctx.rpcServer?.getPiniaState({ event, storeName });
@@ -1247,7 +1917,7 @@ async function requestVueData(ctx, trigger) {
1247
1917
  if (!ctx.rpcServer) {
1248
1918
  return vueBridgeUnavailable();
1249
1919
  }
1250
- const event = (0, import_nanoid2.nanoid)();
1920
+ const event = (0, import_nanoid3.nanoid)();
1251
1921
  const data = await waitForVueHook(ctx, event, () => {
1252
1922
  trigger(event);
1253
1923
  });
@@ -1256,13 +1926,13 @@ async function requestVueData(ctx, trigger) {
1256
1926
  };
1257
1927
  }
1258
1928
  function waitForVueHook(ctx, event, trigger) {
1259
- return new Promise((resolve) => {
1929
+ return new Promise((resolve2) => {
1260
1930
  const timeout = setTimeout(() => {
1261
- resolve({ ok: false, error: "Vue runtime bridge response timed out" });
1931
+ resolve2({ ok: false, error: "Vue runtime bridge response timed out" });
1262
1932
  }, 5e3);
1263
1933
  ctx.hooks.hookOnce(event, (data) => {
1264
1934
  clearTimeout(timeout);
1265
- resolve(data);
1935
+ resolve2(data);
1266
1936
  });
1267
1937
  trigger();
1268
1938
  });
@@ -1283,6 +1953,7 @@ function createMcpServer(ctx, vite) {
1283
1953
  registerConsoleTools(server, ctx);
1284
1954
  registerEvaluateTools(server, ctx);
1285
1955
  registerNetworkTools(server, ctx);
1956
+ registerPerformanceTools(server, ctx);
1286
1957
  registerVueTools(server, ctx);
1287
1958
  return server;
1288
1959
  }
@@ -1370,6 +2041,18 @@ function createServerVueRuntimeRpc(ctx) {
1370
2041
  onScreenshotTaken: (event, data) => {
1371
2042
  void ctx.hooks.callHook(event, data);
1372
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
+ },
1373
2056
  getInspectorTree: () => void 0,
1374
2057
  onInspectorTreeUpdated: (event, data) => {
1375
2058
  void ctx.hooks.callHook(event, data);
@@ -1396,7 +2079,7 @@ function createServerVueRuntimeRpc(ctx) {
1396
2079
  }
1397
2080
 
1398
2081
  // src/cdp/cdpConsole.ts
1399
- var import_nanoid3 = require("nanoid");
2082
+ var import_nanoid4 = require("nanoid");
1400
2083
 
1401
2084
  // src/shared/serialization.ts
1402
2085
  function safeStringify(value) {
@@ -1425,7 +2108,7 @@ async function startCdpConsole(options) {
1425
2108
  await options.client.Runtime.enable();
1426
2109
  options.client.Runtime.consoleAPICalled((event) => {
1427
2110
  options.push({
1428
- id: (0, import_nanoid3.nanoid)(),
2111
+ id: (0, import_nanoid4.nanoid)(),
1429
2112
  pageId: options.pageId,
1430
2113
  source: "cdp",
1431
2114
  level: normalizeConsoleLevel(event.type),
@@ -1445,7 +2128,7 @@ function normalizeConsoleLevel(level) {
1445
2128
  }
1446
2129
 
1447
2130
  // src/cdp/cdpNetwork.ts
1448
- var import_nanoid4 = require("nanoid");
2131
+ var import_nanoid5 = require("nanoid");
1449
2132
 
1450
2133
  // src/shared/sanitize.ts
1451
2134
  function maskHeaders(headers = {}, maskNames = []) {
@@ -1485,7 +2168,7 @@ async function startCdpNetwork(options) {
1485
2168
  await options.client.Network.enable();
1486
2169
  options.client.Network.requestWillBeSent((event) => {
1487
2170
  records.set(event.requestId, {
1488
- id: (0, import_nanoid4.nanoid)(),
2171
+ id: (0, import_nanoid5.nanoid)(),
1489
2172
  pageId: options.pageId,
1490
2173
  source: "cdp",
1491
2174
  url: event.request.url,
@@ -1656,7 +2339,7 @@ async function startCdpObservers(ctx, target, client) {
1656
2339
 
1657
2340
  // src/plugin/injectRuntime.ts
1658
2341
  var import_node_module = require("module");
1659
- var import_node_path3 = require("path");
2342
+ var import_node_path4 = require("path");
1660
2343
  function createRuntimeInjectionController(options, getConfig) {
1661
2344
  return {
1662
2345
  resolveId(importee) {
@@ -1740,7 +2423,7 @@ function createSnapdomLoaderModule(root) {
1740
2423
  }
1741
2424
  function canResolveSnapdomFromProject(root) {
1742
2425
  try {
1743
- (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(
1744
2427
  "@zumer/snapdom"
1745
2428
  );
1746
2429
  return true;
@@ -1779,18 +2462,18 @@ function getPluginPath(plugin) {
1779
2462
  }
1780
2463
 
1781
2464
  // src/plugin/mcpClientConfig/index.ts
1782
- var import_promises4 = __toESM(require("fs/promises"), 1);
1783
- 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);
1784
2467
 
1785
2468
  // src/plugin/mcpClientConfig/codexConfig.ts
1786
- var import_promises2 = __toESM(require("fs/promises"), 1);
1787
- 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);
1788
2471
  async function updateCodexMcpClientConfig(options) {
1789
2472
  try {
1790
2473
  const current = await readOptionalTextFile(options.configPath);
1791
2474
  const next = replaceOrAppendOwnedBlock(current, options);
1792
- await import_promises2.default.mkdir(import_node_path4.default.dirname(options.configPath), { recursive: true });
1793
- 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);
1794
2477
  } catch (error) {
1795
2478
  console.warn(
1796
2479
  `[vite-plugin-vue-mcp-next] Failed to update Codex MCP config at ${options.configPath}: ${formatError(error)}`
@@ -1862,7 +2545,7 @@ function renameServerTableHeader(line, fromServerName, toServerName) {
1862
2545
  }
1863
2546
  async function readOptionalTextFile(filePath) {
1864
2547
  try {
1865
- return await import_promises2.default.readFile(filePath, "utf-8");
2548
+ return await import_promises3.default.readFile(filePath, "utf-8");
1866
2549
  } catch (error) {
1867
2550
  if (isNodeError(error) && error.code === "ENOENT") {
1868
2551
  return "";
@@ -1888,16 +2571,16 @@ function isNodeError(error) {
1888
2571
  }
1889
2572
 
1890
2573
  // src/plugin/mcpClientConfig/jsonConfig.ts
1891
- var import_promises3 = __toESM(require("fs/promises"), 1);
1892
- 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);
1893
2576
  async function updateJsonMcpClientConfig(options) {
1894
2577
  try {
1895
2578
  const config = await readJsonConfig(options.configPath);
1896
- if (!isPlainRecord2(config)) {
2579
+ if (!isPlainRecord3(config)) {
1897
2580
  warnConfigFailure(options, "config root must be a JSON object");
1898
2581
  return;
1899
2582
  }
1900
- const mcpServers = isPlainRecord2(config.mcpServers) ? config.mcpServers : {};
2583
+ const mcpServers = isPlainRecord3(config.mcpServers) ? config.mcpServers : {};
1901
2584
  if (Object.hasOwn(mcpServers, options.serverName)) {
1902
2585
  return;
1903
2586
  }
@@ -1929,8 +2612,8 @@ function renameLegacyServer(mcpServers, options) {
1929
2612
  );
1930
2613
  }
1931
2614
  async function writeJsonConfig(configPath, config) {
1932
- await import_promises3.default.mkdir(import_node_path5.default.dirname(configPath), { recursive: true });
1933
- 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)}
1934
2617
  `);
1935
2618
  }
1936
2619
  async function readJsonConfig(configPath) {
@@ -1942,7 +2625,7 @@ async function readJsonConfig(configPath) {
1942
2625
  }
1943
2626
  async function readOptionalTextFile2(filePath) {
1944
2627
  try {
1945
- return await import_promises3.default.readFile(filePath, "utf-8");
2628
+ return await import_promises4.default.readFile(filePath, "utf-8");
1946
2629
  } catch (error) {
1947
2630
  if (isNodeError2(error) && error.code === "ENOENT") {
1948
2631
  return "{}";
@@ -1950,7 +2633,7 @@ async function readOptionalTextFile2(filePath) {
1950
2633
  throw error;
1951
2634
  }
1952
2635
  }
1953
- function isPlainRecord2(value) {
2636
+ function isPlainRecord3(value) {
1954
2637
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1955
2638
  }
1956
2639
  function warnConfigFailure(options, reason) {
@@ -1974,12 +2657,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
1974
2657
  root,
1975
2658
  clientName: "cursor",
1976
2659
  enabled: options.cursor,
1977
- entryPath: import_node_path6.default.join(root, ".cursor"),
2660
+ entryPath: import_node_path7.default.join(root, ".cursor"),
1978
2661
  entryKind: "directory",
1979
2662
  userOptions,
1980
2663
  createJob: () => updateJsonMcpClientConfig({
1981
2664
  clientName: "Cursor",
1982
- configPath: import_node_path6.default.join(root, ".cursor", "mcp.json"),
2665
+ configPath: import_node_path7.default.join(root, ".cursor", "mcp.json"),
1983
2666
  mcpUrl: sseUrl,
1984
2667
  serverName,
1985
2668
  legacyServerNames
@@ -1989,11 +2672,11 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
1989
2672
  root,
1990
2673
  clientName: "codex",
1991
2674
  enabled: options.codex,
1992
- entryPath: import_node_path6.default.join(root, ".codex"),
2675
+ entryPath: import_node_path7.default.join(root, ".codex"),
1993
2676
  entryKind: "directory",
1994
2677
  userOptions,
1995
2678
  createJob: () => updateCodexMcpClientConfig({
1996
- configPath: import_node_path6.default.join(root, ".codex", "config.toml"),
2679
+ configPath: import_node_path7.default.join(root, ".codex", "config.toml"),
1997
2680
  mcpUrl: streamableHttpUrl,
1998
2681
  serverName,
1999
2682
  legacyServerNames
@@ -2003,12 +2686,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
2003
2686
  root,
2004
2687
  clientName: "claudeCode",
2005
2688
  enabled: options.claudeCode,
2006
- entryPath: import_node_path6.default.join(root, ".mcp.json"),
2689
+ entryPath: import_node_path7.default.join(root, ".mcp.json"),
2007
2690
  entryKind: "file",
2008
2691
  userOptions,
2009
2692
  createJob: () => updateJsonMcpClientConfig({
2010
2693
  clientName: "Claude Code",
2011
- configPath: import_node_path6.default.join(root, ".mcp.json"),
2694
+ configPath: import_node_path7.default.join(root, ".mcp.json"),
2012
2695
  mcpUrl: sseUrl,
2013
2696
  serverName,
2014
2697
  legacyServerNames
@@ -2018,12 +2701,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
2018
2701
  root,
2019
2702
  clientName: "trae",
2020
2703
  enabled: options.trae,
2021
- entryPath: import_node_path6.default.join(root, ".trae"),
2704
+ entryPath: import_node_path7.default.join(root, ".trae"),
2022
2705
  entryKind: "directory",
2023
2706
  userOptions,
2024
2707
  createJob: () => updateJsonMcpClientConfig({
2025
2708
  clientName: "Trae",
2026
- configPath: import_node_path6.default.join(root, ".trae", "mcp.json"),
2709
+ configPath: import_node_path7.default.join(root, ".trae", "mcp.json"),
2027
2710
  mcpUrl: sseUrl,
2028
2711
  serverName,
2029
2712
  legacyServerNames
@@ -2062,7 +2745,7 @@ function isClientExplicitlyConfigured(clientName, userOptions) {
2062
2745
  }
2063
2746
  async function hasExpectedEntry(entryPath, entryKind) {
2064
2747
  try {
2065
- const stat = await import_promises4.default.stat(entryPath);
2748
+ const stat = await import_promises5.default.stat(entryPath);
2066
2749
  return entryKind === "directory" ? stat.isDirectory() : stat.isFile();
2067
2750
  } catch (error) {
2068
2751
  if (isNodeError3(error) && error.code === "ENOENT") {
@@ -2082,12 +2765,12 @@ function isNodeError3(error) {
2082
2765
  }
2083
2766
 
2084
2767
  // src/plugin/skillConfig/index.ts
2085
- var import_promises6 = __toESM(require("fs/promises"), 1);
2086
- 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);
2087
2770
 
2088
2771
  // src/plugin/skillConfig/writers.ts
2089
- var import_promises5 = __toESM(require("fs/promises"), 1);
2090
- 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);
2091
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. -->";
2092
2775
  async function writeGeneratedTextFile(options) {
2093
2776
  try {
@@ -2098,8 +2781,8 @@ async function writeGeneratedTextFile(options) {
2098
2781
  );
2099
2782
  return;
2100
2783
  }
2101
- await import_promises5.default.mkdir(import_node_path7.default.dirname(options.filePath), { recursive: true });
2102
- 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);
2103
2786
  } catch (error) {
2104
2787
  console.warn(
2105
2788
  `[vite-plugin-vue-mcp-next] Failed to update ${options.targetName} at ${options.filePath}: ${formatError4(error)}`
@@ -2108,7 +2791,7 @@ async function writeGeneratedTextFile(options) {
2108
2791
  }
2109
2792
  async function readOptionalTextFile3(filePath) {
2110
2793
  try {
2111
- return await import_promises5.default.readFile(filePath, "utf-8");
2794
+ return await import_promises6.default.readFile(filePath, "utf-8");
2112
2795
  } catch (error) {
2113
2796
  if (isNodeError4(error) && error.code === "ENOENT") {
2114
2797
  return "";
@@ -2125,7 +2808,7 @@ function isNodeError4(error) {
2125
2808
 
2126
2809
  // src/plugin/skillConfig/index.ts
2127
2810
  var PACKAGE_NAME = "@xiaou66/vite-plugin-vue-mcp-next";
2128
- 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");
2129
2812
  async function updateSkillConfigs(root, options) {
2130
2813
  if (!options.autoConfig) {
2131
2814
  return;
@@ -2141,13 +2824,13 @@ async function updateSkillConfigs(root, options) {
2141
2824
  function createSkillConfigDescriptors(root) {
2142
2825
  return [
2143
2826
  {
2144
- entryPath: import_node_path8.default.join(root, ".codex"),
2145
- 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"),
2146
2829
  targetName: "Codex skill"
2147
2830
  },
2148
2831
  {
2149
- entryPath: import_node_path8.default.join(root, ".claude"),
2150
- filePath: import_node_path8.default.join(
2832
+ entryPath: import_node_path9.default.join(root, ".claude"),
2833
+ filePath: import_node_path9.default.join(
2151
2834
  root,
2152
2835
  ".claude",
2153
2836
  "skills",
@@ -2157,8 +2840,8 @@ function createSkillConfigDescriptors(root) {
2157
2840
  targetName: "Claude Code skill"
2158
2841
  },
2159
2842
  {
2160
- entryPath: import_node_path8.default.join(root, ".cursor"),
2161
- 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"),
2162
2845
  targetName: "Cursor rule"
2163
2846
  }
2164
2847
  ];
@@ -2182,7 +2865,7 @@ async function readPackagedSkillContent(root) {
2182
2865
  const candidates = getPackagedSkillCandidates(root);
2183
2866
  for (const candidate of candidates) {
2184
2867
  try {
2185
- return await import_promises6.default.readFile(candidate, "utf-8");
2868
+ return await import_promises7.default.readFile(candidate, "utf-8");
2186
2869
  } catch (error) {
2187
2870
  if (isNodeError5(error) && error.code === "ENOENT") {
2188
2871
  continue;
@@ -2206,14 +2889,14 @@ async function safelyReadPackagedSkillContent(root) {
2206
2889
  }
2207
2890
  function getPackagedSkillCandidates(root) {
2208
2891
  return [
2209
- import_node_path8.default.resolve(root, "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
2210
- import_node_path8.default.resolve(process.cwd(), "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
2211
- 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)
2212
2895
  ];
2213
2896
  }
2214
2897
  async function hasDirectoryEntry(entryPath) {
2215
2898
  try {
2216
- const stat = await import_promises6.default.stat(entryPath);
2899
+ const stat = await import_promises7.default.stat(entryPath);
2217
2900
  return stat.isDirectory();
2218
2901
  } catch (error) {
2219
2902
  if (isNodeError5(error) && error.code === "ENOENT") {
@@ -2292,6 +2975,14 @@ function vueMcpNext(userOptions = {}) {
2292
2975
  }
2293
2976
  }
2294
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
+ );
2295
2986
  const port = String(server.config.server.port || 5173);
2296
2987
  const mcpSseUrl = `http://${options.host}:${port}${options.mcpPath}/sse`;
2297
2988
  const mcpStreamableHttpUrl = `http://${options.host}:${port}${options.mcpPath}/mcp`;
@@ -2335,7 +3026,7 @@ function isRuntimePageTarget(payload) {
2335
3026
  return false;
2336
3027
  }
2337
3028
  const target = payload;
2338
- return target.source === "runtime" && typeof target.pageId === "string" && typeof target.url === "string" && typeof target.pathname === "string" && typeof target.connected === "boolean";
3029
+ return target.source === "runtime" && typeof target.pageId === "string" && typeof target.url === "string" && typeof target.pathname === "string" && typeof target.connected === "boolean" && (target.runtimeClientId === void 0 || typeof target.runtimeClientId === "string");
2339
3030
  }
2340
3031
  function isConsoleRecord(payload) {
2341
3032
  if (!payload || typeof payload !== "object") {
@@ -2354,6 +3045,13 @@ function isNetworkRecord(payload) {
2354
3045
  const record = payload;
2355
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";
2356
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
+ }
2357
3055
  // Annotate the CommonJS export names for ESM import in node:
2358
3056
  0 && (module.exports = {
2359
3057
  vueMcpNext