@presto1314w/vite-devtools-browser 0.1.4 → 0.2.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/README.md CHANGED
@@ -1,10 +1,60 @@
1
1
  # vite-browser
2
2
 
3
- `vite-browser` is a debugging toolchain for Vite apps:
3
+ `vite-browser` is a runtime diagnostics toolchain for Vite applications.
4
+
5
+ It gives agents and developers structured access to:
6
+
7
+ - Vue, React, and Svelte runtime state
8
+ - Vite HMR activity and runtime health
9
+ - event-window correlation between current errors and recent hot updates
10
+ - rule-based HMR diagnosis with confidence levels
11
+ - module graph snapshots and diffs
12
+ - mapped error output with optional source snippets
13
+ - network, logs, screenshots, and page evaluation
14
+
15
+ It ships in two forms:
16
+
4
17
  - Agent Skill: scenario-based debugging workflows for coding assistants
5
- - CLI Runtime (`@presto1314w/vite-devtools-browser`): structured inspection of Vue/React/Svelte runtime state
18
+ - CLI Runtime (`@presto1314w/vite-devtools-browser`): structured shell commands for local Vite debugging
19
+
20
+ Current documented baseline: `v0.2.0`.
21
+
22
+ ## What's New In v0.2
23
+
24
+ `v0.2.0` moves `vite-browser` from snapshot-style inspection toward runtime diagnosis:
25
+
26
+ - browser/runtime events are captured into a daemon-side event queue
27
+ - `correlate errors` links the current error to recent HMR-updated modules
28
+ - `diagnose hmr` turns runtime, trace, and error signals into structured findings
29
+ - skills and CLI flows now route more directly to runtime triage instead of raw log inspection
30
+
31
+ ## Built For Agents
32
+
33
+ `vite-browser` is designed for agent workflows as much as local debugging.
34
+
35
+ Models do not work well with a DevTools panel that has to be visually inspected step by step. They work much better when runtime signals are exposed as structured commands that can be queried, compared, and chained in a loop. `vite-browser` turns framework state, Vite runtime status, HMR activity, module graph changes, mapped errors, logs, and network activity into terminal output that an agent can actually reason about.
36
+
37
+ Under the hood, each command is a one-shot request against a long-lived browser daemon. That keeps the CLI simple for users while letting agent loops inspect a running app repeatedly without having to manage browser lifecycle on every step.
38
+
39
+ ## Why vite-browser
40
+
41
+ Most browser CLIs are optimized for automation. Most framework devtools are optimized for humans in a GUI.
42
+
43
+ `vite-browser` is optimized for structured Vite runtime debugging:
44
+
45
+ - it can inspect framework state like a devtools bridge
46
+ - it can explain Vite-specific behavior like HMR updates and module graph changes
47
+ - it can correlate recent updates with current failures
48
+ - it returns structured text that AI agents can consume directly in loops
49
+
50
+ ## Positioning
6
51
 
7
- Current documented baseline: `v0.1.4`.
52
+ | Tool | Best for | Notable gap compared with `vite-browser` |
53
+ | --- | --- | --- |
54
+ | `agent-browser` | general browser automation | not focused on Vite runtime diagnostics |
55
+ | `next-browser` | Next.js + React debugging | not designed as a Vite runtime tool |
56
+ | `vite-plugin-vue-mcp` | Vue MCP integration inside Vite | plugin/MCP-first, not a standalone diagnostics CLI |
57
+ | `vite-browser` | Vite runtime diagnostics for agents and developers | browser lifecycle coverage still being expanded |
8
58
 
9
59
  ## Install
10
60
 
@@ -31,16 +81,69 @@ npm run dev
31
81
  # terminal B: inspect runtime
32
82
  vite-browser open http://localhost:5173
33
83
  vite-browser detect
34
- vite-browser vue tree
35
- vite-browser vue pinia
36
84
  vite-browser vite runtime
85
+ vite-browser errors --mapped --inline-source
86
+ vite-browser correlate errors --mapped --window 5000
87
+ vite-browser diagnose hmr --limit 50
37
88
  vite-browser vite hmr trace --limit 20
38
89
  vite-browser vite module-graph trace --limit 50
39
- vite-browser errors --mapped --inline-source
40
90
  vite-browser network
41
91
  vite-browser close
42
92
  ```
43
93
 
94
+ For component/state debugging, then branch into framework-specific commands:
95
+
96
+ ```bash
97
+ vite-browser vue tree
98
+ vite-browser vue pinia
99
+ vite-browser vue router
100
+ vite-browser react tree
101
+ vite-browser svelte tree
102
+ ```
103
+
104
+ ## What It Looks Like
105
+
106
+ ```bash
107
+ $ vite-browser vite runtime
108
+ # Vite Runtime
109
+ URL: http://localhost:5173/
110
+ Framework: vue
111
+ Vite Client: loaded
112
+ HMR Socket: open
113
+ Error Overlay: none
114
+ Tracked HMR Events: 3
115
+
116
+ $ vite-browser vite hmr trace --limit 5
117
+ # HMR Trace
118
+ [12:34:10] connected [vite] connected.
119
+ [12:34:15] update /src/App.vue
120
+
121
+ $ vite-browser errors --mapped --inline-source
122
+ Failed to resolve import "./missing"
123
+
124
+ # Mapped Stack
125
+ - http://localhost:5173/src/main.ts:12:4 -> /src/main.ts:12:4
126
+ 12 | import "./missing"
127
+
128
+ $ vite-browser correlate errors --mapped --window 5000
129
+ # Error Correlation
130
+ ## Current Error
131
+ TypeError: boom at /src/App.tsx:4:2
132
+
133
+ ## Correlation
134
+ Confidence: high
135
+ HMR update observed within 5000ms of the current error
136
+ Matching modules: /src/App.tsx
137
+
138
+ $ vite-browser diagnose hmr --limit 50
139
+ # HMR Diagnosis
140
+ ## missing-module
141
+ Status: fail
142
+ Confidence: high
143
+ A module import failed to resolve during HMR.
144
+ Suggestion: Verify the import path, file extension, alias configuration, and whether the module exists on disk.
145
+ ```
146
+
44
147
  ## Core Capabilities
45
148
 
46
149
  - Framework detection: Vue/React/Svelte best-effort detection and version hinting
@@ -51,9 +154,55 @@ vite-browser close
51
154
  - runtime status summary
52
155
  - HMR summary/timeline/clear
53
156
  - module-graph snapshot/diff/clear
157
+ - error/HMR correlation over recent event windows
158
+ - rule-based HMR diagnosis with confidence levels
54
159
  - source-mapped errors with optional inline source snippet
55
160
  - Debug utilities: console logs, network tracing, screenshot, page `eval`
56
161
 
162
+ ## Recommended Workflows
163
+
164
+ ### Runtime/HMR triage
165
+
166
+ ```bash
167
+ vite-browser vite runtime
168
+ vite-browser errors --mapped --inline-source
169
+ vite-browser correlate errors --mapped --window 5000
170
+ vite-browser diagnose hmr --limit 50
171
+ vite-browser vite hmr trace --limit 50
172
+ vite-browser vite module-graph trace --limit 200
173
+ ```
174
+
175
+ ### Data/API triage
176
+
177
+ ```bash
178
+ vite-browser errors --mapped
179
+ vite-browser logs
180
+ vite-browser network
181
+ vite-browser network <idx>
182
+ vite-browser eval '<state probe>'
183
+ ```
184
+
185
+ ### Component/state triage
186
+
187
+ ```bash
188
+ vite-browser detect
189
+ vite-browser vue tree
190
+ vite-browser vue pinia
191
+ vite-browser vue router
192
+ vite-browser react tree
193
+ vite-browser svelte tree
194
+ ```
195
+
196
+ ## Current Boundaries
197
+
198
+ `vite-browser` v0.2 is strong at:
199
+
200
+ - surfacing runtime state as structured shell output
201
+ - linking current errors to recent HMR/module activity
202
+ - detecting several common HMR failure patterns quickly
203
+
204
+ It is not yet a full propagation-trace engine. In particular, it does not reliably infer deep chains like `store -> component A -> component B -> error` across arbitrary component graphs.
205
+
57
206
  ## Command Reference
58
207
 
59
208
  ### Browser
@@ -91,6 +240,9 @@ vite-browser vite module-graph clear
91
240
  vite-browser errors
92
241
  vite-browser errors --mapped
93
242
  vite-browser errors --mapped --inline-source
243
+ vite-browser correlate errors [--window <ms>]
244
+ vite-browser correlate errors --mapped --inline-source
245
+ vite-browser diagnose hmr [--window <ms>] [--limit <n>]
94
246
  ```
95
247
 
96
248
  ### Utilities
@@ -124,6 +276,10 @@ pnpm test:evals
124
276
  pnpm test:evals:e2e
125
277
  ```
126
278
 
279
+ ## Discovery
280
+
281
+ If you want to introduce the project to new users, start with the launch kit in [docs/launch-kit.md](./docs/launch-kit.md).
282
+
127
283
  ## Requirements
128
284
 
129
285
  - Node.js `>=20`
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { type BrowserContext } from "playwright";
1
+ import { type BrowserContext, type Page } from "playwright";
2
2
  import { resolveViaSourceMap } from "./sourcemap.js";
3
+ import { EventQueue } from "./event-queue.js";
3
4
  type HmrEventType = "connecting" | "connected" | "update" | "full-reload" | "error" | "log";
4
5
  export type HmrEvent = {
5
6
  timestamp: number;
@@ -20,6 +21,13 @@ export type ModuleRow = {
20
21
  durationMs: number;
21
22
  };
22
23
  export type ModuleGraphMode = "snapshot" | "trace" | "clear";
24
+ export declare function setEventQueue(queue: EventQueue): void;
25
+ export declare function getEventQueue(): EventQueue | null;
26
+ export declare function getCurrentPage(): Page | null;
27
+ /**
28
+ * Flush browser events into daemon event queue
29
+ */
30
+ export declare function flushBrowserEvents(currentPage: Page, queue: EventQueue): Promise<void>;
23
31
  export declare function open(url: string | undefined): Promise<void>;
24
32
  export declare function cookies(cookies: {
25
33
  name: string;
package/dist/browser.js CHANGED
@@ -17,16 +17,107 @@ let context = null;
17
17
  let page = null;
18
18
  let framework = "unknown";
19
19
  let extensionModeDisabled = false;
20
+ let eventQueue = null;
20
21
  const consoleLogs = [];
21
22
  const MAX_LOGS = 200;
22
23
  const MAX_HMR_EVENTS = 500;
23
24
  let lastReactSnapshot = [];
24
25
  const hmrEvents = [];
25
26
  let lastModuleGraphUrls = null;
27
+ export function setEventQueue(queue) {
28
+ eventQueue = queue;
29
+ }
30
+ export function getEventQueue() {
31
+ return eventQueue;
32
+ }
33
+ export function getCurrentPage() {
34
+ if (!contextUsable(context))
35
+ return null;
36
+ if (!page || page.isClosed())
37
+ return null;
38
+ return page;
39
+ }
40
+ /**
41
+ * Inject browser-side event collector into the page
42
+ */
43
+ async function injectEventCollector(currentPage) {
44
+ await currentPage.evaluate(() => {
45
+ if (window.__vb_events)
46
+ return; // already injected
47
+ window.__vb_events = [];
48
+ window.__vb_push = (event) => {
49
+ const q = window.__vb_events;
50
+ q.push(event);
51
+ if (q.length > 1000)
52
+ q.shift();
53
+ };
54
+ // Subscribe to Vite HMR WebSocket
55
+ function attachViteListener() {
56
+ const hot = window.__vite_hot;
57
+ if (hot?.ws) {
58
+ hot.ws.addEventListener('message', (e) => {
59
+ try {
60
+ const data = JSON.parse(e.data);
61
+ window.__vb_push({
62
+ timestamp: Date.now(),
63
+ type: data.type === 'error' ? 'hmr-error' : 'hmr-update',
64
+ payload: data
65
+ });
66
+ }
67
+ catch { }
68
+ });
69
+ return true;
70
+ }
71
+ return false;
72
+ }
73
+ // Retry if __vite_hot not ready yet
74
+ if (!attachViteListener()) {
75
+ let attempts = 0;
76
+ const timer = setInterval(() => {
77
+ attempts++;
78
+ if (attachViteListener() || attempts >= 50) {
79
+ clearInterval(timer);
80
+ }
81
+ }, 100);
82
+ }
83
+ // Hook window.onerror for runtime errors
84
+ const origOnError = window.onerror;
85
+ window.onerror = (msg, src, line, col, err) => {
86
+ window.__vb_push({
87
+ timestamp: Date.now(),
88
+ type: 'error',
89
+ payload: { message: String(msg), source: src, line, col, stack: err?.stack }
90
+ });
91
+ return origOnError ? origOnError(msg, src, line, col, err) : false;
92
+ };
93
+ // Hook unhandledrejection
94
+ window.addEventListener('unhandledrejection', (e) => {
95
+ window.__vb_push({
96
+ timestamp: Date.now(),
97
+ type: 'error',
98
+ payload: { message: e.reason?.message, stack: e.reason?.stack }
99
+ });
100
+ });
101
+ });
102
+ }
103
+ /**
104
+ * Flush browser events into daemon event queue
105
+ */
106
+ export async function flushBrowserEvents(currentPage, queue) {
107
+ const raw = await currentPage.evaluate(() => {
108
+ const events = window.__vb_events ?? [];
109
+ window.__vb_events = []; // clear after flush
110
+ return events;
111
+ });
112
+ for (const e of raw) {
113
+ queue.push(e);
114
+ }
115
+ }
26
116
  export async function open(url) {
27
117
  const currentPage = await ensurePage();
28
118
  if (url) {
29
119
  await currentPage.goto(url, { waitUntil: "domcontentloaded" });
120
+ await injectEventCollector(currentPage);
30
121
  await detectFramework();
31
122
  }
32
123
  }
@@ -256,6 +347,7 @@ export function formatModuleGraphTrace(currentUrls, previousUrls, filter, limit
256
347
  export async function goto(url) {
257
348
  const currentPage = await ensurePage();
258
349
  await currentPage.goto(url, { waitUntil: "domcontentloaded" });
350
+ await injectEventCollector(currentPage);
259
351
  await detectFramework();
260
352
  return currentPage.url();
261
353
  }
package/dist/cli.js CHANGED
@@ -136,6 +136,21 @@ export async function runCli(argv, io) {
136
136
  const res = await io.send("errors", { mapped, inlineSource });
137
137
  exit(io, res, res.ok && res.data ? String(res.data) : "no errors");
138
138
  }
139
+ if (cmd === "correlate" && arg === "errors") {
140
+ const mapped = args.includes("--mapped");
141
+ const inlineSource = args.includes("--inline-source");
142
+ const windowMs = parseNumberFlag(args, "--window", 5000);
143
+ const res = await io.send("correlate-errors", { mapped, inlineSource, windowMs });
144
+ exit(io, res, res.ok && res.data ? String(res.data) : "");
145
+ }
146
+ if (cmd === "diagnose" && arg === "hmr") {
147
+ const mapped = args.includes("--mapped");
148
+ const inlineSource = args.includes("--inline-source");
149
+ const windowMs = parseNumberFlag(args, "--window", 5000);
150
+ const limit = parseNumberFlag(args, "--limit", 50);
151
+ const res = await io.send("diagnose-hmr", { mapped, inlineSource, windowMs, limit });
152
+ exit(io, res, res.ok && res.data ? String(res.data) : "");
153
+ }
139
154
  if (cmd === "logs") {
140
155
  const res = await io.send("logs");
141
156
  exit(io, res, res.ok && res.data ? String(res.data) : "");
@@ -211,6 +226,10 @@ VITE COMMANDS
211
226
  errors Show build/runtime errors
212
227
  errors --mapped Show errors with source-map mapping
213
228
  errors --mapped --inline-source Include mapped source snippets
229
+ correlate errors [--window <ms>] Correlate current errors with recent HMR events
230
+ correlate errors --mapped Correlate mapped errors with recent HMR events
231
+ diagnose hmr [--window <ms>] Diagnose HMR failures from runtime, errors, and trace data
232
+ diagnose hmr [--limit <n>] Control how many recent HMR trace entries are inspected
214
233
  logs Show dev server logs
215
234
 
216
235
  UTILITIES
@@ -0,0 +1,20 @@
1
+ import type { VBEvent } from "./event-queue.js";
2
+ export type CorrelationConfidence = "high" | "medium" | "low";
3
+ export type ErrorCorrelation = {
4
+ summary: string;
5
+ detail: string;
6
+ confidence: CorrelationConfidence;
7
+ windowMs: number;
8
+ matchingModules: string[];
9
+ relatedEvents: VBEvent[];
10
+ };
11
+ export type RenderNetworkCorrelation = {
12
+ summary: string;
13
+ detail: string;
14
+ confidence: CorrelationConfidence;
15
+ requestCount: number;
16
+ urls: string[];
17
+ };
18
+ export declare function correlateErrorWithHMR(errorText: string, events: VBEvent[], windowMs?: number): ErrorCorrelation | null;
19
+ export declare function correlateRenderWithNetwork(events: VBEvent[], requestThreshold?: number): RenderNetworkCorrelation | null;
20
+ export declare function formatErrorCorrelationReport(errorText: string, correlation: ErrorCorrelation | null): string;
@@ -0,0 +1,110 @@
1
+ const MODULE_PATTERNS = [
2
+ /\/src\/[^\s"'`):]+/g,
3
+ /\/@fs\/[^\s"'`):]+/g,
4
+ /[A-Za-z]:\\[^:\n]+/g,
5
+ ];
6
+ export function correlateErrorWithHMR(errorText, events, windowMs = 5000) {
7
+ const recentEvents = events.filter((event) => event.type === "hmr-update" || event.type === "hmr-error");
8
+ if (recentEvents.length === 0)
9
+ return null;
10
+ const errorModules = extractModules(errorText);
11
+ const matchedEvents = recentEvents.filter((event) => {
12
+ const modules = extractModulesFromEvent(event);
13
+ if (errorModules.length === 0)
14
+ return event.type === "hmr-error";
15
+ return modules.some((module) => errorModules.includes(module));
16
+ });
17
+ const relatedEvents = matchedEvents.length > 0 ? matchedEvents : recentEvents;
18
+ const matchingModules = unique(relatedEvents.flatMap((event) => extractModulesFromEvent(event)).filter((module) => errorModules.includes(module)));
19
+ const confidence = inferConfidence(errorModules, matchingModules, relatedEvents);
20
+ const eventKind = relatedEvents.some((event) => event.type === "hmr-error") ? "HMR error" : "HMR update";
21
+ const moduleText = matchingModules.length > 0
22
+ ? `Matching modules: ${matchingModules.join(", ")}`
23
+ : errorModules.length > 0
24
+ ? `Error modules: ${errorModules.join(", ")}`
25
+ : "No module path overlap; correlation is time-window based.";
26
+ return {
27
+ summary: `${eventKind} observed within ${windowMs}ms of the current error`,
28
+ detail: `${moduleText}\nRecent events considered: ${relatedEvents.length}`,
29
+ confidence,
30
+ windowMs,
31
+ matchingModules,
32
+ relatedEvents,
33
+ };
34
+ }
35
+ export function correlateRenderWithNetwork(events, requestThreshold = 3) {
36
+ const renderEvents = events.filter((event) => event.type === "render");
37
+ const networkEvents = events.filter((event) => event.type === "network");
38
+ if (renderEvents.length === 0 || networkEvents.length === 0)
39
+ return null;
40
+ const latestRender = renderEvents[renderEvents.length - 1];
41
+ const start = latestRender.timestamp - 1000;
42
+ const end = latestRender.timestamp + 1000;
43
+ const overlappingNetwork = networkEvents.filter((event) => event.timestamp >= start && event.timestamp <= end);
44
+ const urls = unique(overlappingNetwork
45
+ .map((event) => event.payload.url)
46
+ .filter((url) => typeof url === "string"));
47
+ if (overlappingNetwork.length < requestThreshold)
48
+ return null;
49
+ return {
50
+ summary: "Repeated network activity detected around a render event",
51
+ detail: `Observed ${overlappingNetwork.length} network requests near the latest render. URLs: ${urls.join(", ")}`,
52
+ confidence: overlappingNetwork.length >= requestThreshold + 2 ? "high" : "medium",
53
+ requestCount: overlappingNetwork.length,
54
+ urls,
55
+ };
56
+ }
57
+ export function formatErrorCorrelationReport(errorText, correlation) {
58
+ const lines = ["# Error Correlation", "", "## Current Error", errorText.trim() || "(empty error)"];
59
+ if (!correlation) {
60
+ lines.push("", "## Correlation", "No recent HMR events correlated with the current error.");
61
+ return lines.join("\n");
62
+ }
63
+ lines.push("", "## Correlation", `Confidence: ${correlation.confidence}`, correlation.summary, correlation.detail, "", "## Related Events", ...correlation.relatedEvents.map((event) => formatEventLine(event)));
64
+ return lines.join("\n");
65
+ }
66
+ function formatEventLine(event) {
67
+ const payload = event.payload;
68
+ const path = payload.path;
69
+ const message = payload.message;
70
+ if (typeof path === "string")
71
+ return `- ${event.type}: ${path}`;
72
+ if (typeof message === "string")
73
+ return `- ${event.type}: ${message}`;
74
+ return `- ${event.type}: ${JSON.stringify(payload)}`;
75
+ }
76
+ function inferConfidence(errorModules, matchingModules, events) {
77
+ if (matchingModules.length > 0)
78
+ return "high";
79
+ if (events.some((event) => event.type === "hmr-error"))
80
+ return "medium";
81
+ if (errorModules.length === 0 && events.length > 0)
82
+ return "low";
83
+ return "medium";
84
+ }
85
+ function extractModulesFromEvent(event) {
86
+ const payload = event.payload;
87
+ const candidates = [];
88
+ if (typeof payload.path === "string")
89
+ candidates.push(payload.path);
90
+ if (typeof payload.message === "string")
91
+ candidates.push(payload.message);
92
+ if (Array.isArray(payload.updates)) {
93
+ for (const update of payload.updates) {
94
+ if (update && typeof update === "object" && typeof update.path === "string") {
95
+ candidates.push(update.path);
96
+ }
97
+ }
98
+ }
99
+ return unique(candidates.flatMap((candidate) => extractModules(candidate)));
100
+ }
101
+ function extractModules(text) {
102
+ const matches = MODULE_PATTERNS.flatMap((pattern) => text.match(pattern) ?? []);
103
+ return unique(matches.map(normalizeModulePath).filter(Boolean));
104
+ }
105
+ function normalizeModulePath(value) {
106
+ return value.replace(/[),.:]+$/, "");
107
+ }
108
+ function unique(values) {
109
+ return [...new Set(values)];
110
+ }
package/dist/daemon.d.ts CHANGED
@@ -9,6 +9,7 @@ export type Cmd = {
9
9
  idx?: number;
10
10
  mode?: "summary" | "trace" | "clear" | "snapshot";
11
11
  limit?: number;
12
+ windowMs?: number;
12
13
  filter?: string;
13
14
  mapped?: boolean;
14
15
  inlineSource?: boolean;
package/dist/daemon.js CHANGED
@@ -2,7 +2,11 @@ import { createServer } from "node:net";
2
2
  import { mkdirSync, writeFileSync, rmSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import * as browser from "./browser.js";
5
+ import { correlateErrorWithHMR, formatErrorCorrelationReport } from "./correlate.js";
6
+ import { diagnoseHMR, formatDiagnosisReport } from "./diagnose.js";
5
7
  import { socketDir, socketPath, pidFile } from "./paths.js";
8
+ import { EventQueue } from "./event-queue.js";
9
+ import * as networkLog from "./network.js";
6
10
  export function cleanError(err) {
7
11
  if (!(err instanceof Error))
8
12
  return String(err);
@@ -12,6 +16,19 @@ export function cleanError(err) {
12
16
  }
13
17
  export function createRunner(api = browser) {
14
18
  return async function run(cmd) {
19
+ // Flush browser events to daemon queue before processing command
20
+ const queue = api.getEventQueue();
21
+ if (queue) {
22
+ try {
23
+ const currentPage = api.getCurrentPage();
24
+ if (currentPage) {
25
+ await api.flushBrowserEvents(currentPage, queue);
26
+ }
27
+ }
28
+ catch {
29
+ // Ignore flush errors (page might not be open yet)
30
+ }
31
+ }
15
32
  // Browser control
16
33
  if (cmd.action === "open") {
17
34
  await api.open(cmd.url);
@@ -88,6 +105,21 @@ export function createRunner(api = browser) {
88
105
  const data = await api.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource));
89
106
  return { ok: true, data };
90
107
  }
108
+ if (cmd.action === "correlate-errors") {
109
+ const errorText = String(await api.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource)));
110
+ const events = queue ? queue.window(cmd.windowMs ?? 5000) : [];
111
+ const data = formatErrorCorrelationReport(errorText, errorText === "no errors" ? null : correlateErrorWithHMR(errorText, events, cmd.windowMs ?? 5000));
112
+ return { ok: true, data };
113
+ }
114
+ if (cmd.action === "diagnose-hmr") {
115
+ const errorText = String(await api.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource)));
116
+ const runtimeText = String(await api.viteRuntimeStatus());
117
+ const hmrTraceText = String(await api.viteHMRTrace("trace", cmd.limit ?? 50));
118
+ const events = queue ? queue.window(cmd.windowMs ?? 5000) : [];
119
+ const correlation = errorText === "no errors" ? null : correlateErrorWithHMR(errorText, events, cmd.windowMs ?? 5000);
120
+ const data = formatDiagnosisReport(diagnoseHMR({ errorText, runtimeText, hmrTraceText, correlation }));
121
+ return { ok: true, data };
122
+ }
91
123
  if (cmd.action === "logs") {
92
124
  const data = await api.logs();
93
125
  return { ok: true, data };
@@ -123,6 +155,10 @@ export async function dispatchLine(line, socket, run = createRunner(), onClose)
123
155
  setImmediate(() => onClose?.());
124
156
  }
125
157
  export function startDaemon() {
158
+ // Initialize event queue
159
+ const eventQueue = new EventQueue(1000);
160
+ browser.setEventQueue(eventQueue);
161
+ networkLog.setEventQueue(eventQueue);
126
162
  const run = createRunner();
127
163
  mkdirSync(socketDir, { recursive: true, mode: 0o700 });
128
164
  removeSocketFile();
@@ -0,0 +1,19 @@
1
+ import type { ErrorCorrelation } from "./correlate.js";
2
+ export type DiagnosisStatus = "pass" | "warn" | "fail";
3
+ export type DiagnosisConfidence = "high" | "medium" | "low";
4
+ export type DiagnosisResult = {
5
+ code: "circular-dependency" | "missing-module" | "hmr-websocket-closed" | "repeated-full-reload";
6
+ status: DiagnosisStatus;
7
+ confidence: DiagnosisConfidence;
8
+ summary: string;
9
+ detail: string;
10
+ suggestion: string;
11
+ };
12
+ export type DiagnoseInput = {
13
+ errorText: string;
14
+ runtimeText: string;
15
+ hmrTraceText: string;
16
+ correlation: ErrorCorrelation | null;
17
+ };
18
+ export declare function diagnoseHMR(input: DiagnoseInput): DiagnosisResult[];
19
+ export declare function formatDiagnosisReport(results: DiagnosisResult[]): string;
@@ -0,0 +1,88 @@
1
+ export function diagnoseHMR(input) {
2
+ const results = [
3
+ detectCircularDependency(input),
4
+ detectMissingModule(input),
5
+ detectClosedWebsocket(input),
6
+ detectRepeatedFullReload(input),
7
+ ].filter((result) => result !== null);
8
+ if (results.length > 0)
9
+ return results;
10
+ return [
11
+ {
12
+ code: "hmr-websocket-closed",
13
+ status: "pass",
14
+ confidence: "low",
15
+ summary: "No obvious HMR failure pattern detected",
16
+ detail: "Runtime, current error text, and HMR trace did not match any built-in failure rules.",
17
+ suggestion: "If symptoms persist, inspect `vite hmr trace`, `network`, and `correlate errors` output together.",
18
+ },
19
+ ];
20
+ }
21
+ export function formatDiagnosisReport(results) {
22
+ const lines = ["# HMR Diagnosis", ""];
23
+ for (const result of results) {
24
+ lines.push(`## ${result.code}`, `Status: ${result.status}`, `Confidence: ${result.confidence}`, result.summary, result.detail, `Suggestion: ${result.suggestion}`, "");
25
+ }
26
+ return lines.join("\n").trimEnd();
27
+ }
28
+ function detectCircularDependency(input) {
29
+ const text = `${input.errorText}\n${input.hmrTraceText}`;
30
+ if (!/circular (dependency|import)|import cycle/i.test(text))
31
+ return null;
32
+ const moduleText = input.correlation?.matchingModules.length
33
+ ? `Likely modules: ${input.correlation.matchingModules.join(", ")}.`
34
+ : "The error text points to a circular import/dependency chain.";
35
+ return {
36
+ code: "circular-dependency",
37
+ status: "fail",
38
+ confidence: input.correlation?.matchingModules.length ? "high" : "medium",
39
+ summary: "HMR is likely breaking because of a circular dependency.",
40
+ detail: `${moduleText} Circular imports often prevent safe hot replacement and force stale state or reload loops.`,
41
+ suggestion: "Break the import cycle by extracting shared code into a leaf module or switching one edge to a lazy import.",
42
+ };
43
+ }
44
+ function detectMissingModule(input) {
45
+ const text = input.errorText;
46
+ if (!/failed to resolve import|cannot find module|could not resolve|does the file exist/i.test(text)) {
47
+ return null;
48
+ }
49
+ return {
50
+ code: "missing-module",
51
+ status: "fail",
52
+ confidence: "high",
53
+ summary: "A module import failed to resolve during HMR.",
54
+ detail: input.correlation?.matchingModules.length
55
+ ? `The current error overlaps with recent updates to ${input.correlation.matchingModules.join(", ")}.`
56
+ : "The current error text matches a missing or unresolved import pattern.",
57
+ suggestion: "Verify the import path, file extension, alias configuration, and whether the module exists on disk.",
58
+ };
59
+ }
60
+ function detectClosedWebsocket(input) {
61
+ const runtimeClosed = /HMR Socket:\s*(closed|closing|unknown)/i.test(input.runtimeText);
62
+ const traceClosed = /disconnected|failed to connect|connection lost|ws closed/i.test(input.hmrTraceText);
63
+ if (!runtimeClosed && !traceClosed)
64
+ return null;
65
+ return {
66
+ code: "hmr-websocket-closed",
67
+ status: "fail",
68
+ confidence: runtimeClosed && traceClosed ? "high" : "medium",
69
+ summary: "The HMR websocket is not healthy.",
70
+ detail: runtimeClosed
71
+ ? "Runtime status reports the HMR socket as closed, closing, or unknown."
72
+ : "HMR trace contains disconnect or websocket failure messages.",
73
+ suggestion: "Check the dev server is running, the page is connected to the correct origin, and no proxy/firewall is blocking the websocket.",
74
+ };
75
+ }
76
+ function detectRepeatedFullReload(input) {
77
+ const matches = input.hmrTraceText.match(/full-reload|page reload/gi) ?? [];
78
+ if (matches.length < 2)
79
+ return null;
80
+ return {
81
+ code: "repeated-full-reload",
82
+ status: "warn",
83
+ confidence: matches.length >= 3 ? "high" : "medium",
84
+ summary: "Vite is repeatedly falling back to full page reloads.",
85
+ detail: `Observed ${matches.length} full-reload events in the recent HMR trace.`,
86
+ suggestion: "Check whether the changed module is outside HMR boundaries, introduces side effects, or triggers a circular dependency.",
87
+ };
88
+ }
@@ -0,0 +1,22 @@
1
+ export type VBEventType = 'hmr-update' | 'hmr-error' | 'module-change' | 'network' | 'error' | 'render';
2
+ export interface VBEvent {
3
+ timestamp: number;
4
+ type: VBEventType;
5
+ payload: unknown;
6
+ }
7
+ export declare class EventQueue {
8
+ private events;
9
+ private readonly maxSize;
10
+ constructor(maxSize?: number);
11
+ push(event: VBEvent): void;
12
+ /**
13
+ * Return all events within the last `ms` milliseconds before `before`
14
+ */
15
+ window(ms: number, before?: number): VBEvent[];
16
+ /**
17
+ * Return all events of a given type
18
+ */
19
+ ofType(type: VBEventType): VBEvent[];
20
+ all(): VBEvent[];
21
+ clear(): void;
22
+ }
@@ -0,0 +1,32 @@
1
+ export class EventQueue {
2
+ events = [];
3
+ maxSize;
4
+ constructor(maxSize = 1000) {
5
+ this.maxSize = maxSize;
6
+ }
7
+ push(event) {
8
+ this.events.push(event);
9
+ if (this.events.length > this.maxSize) {
10
+ this.events.shift();
11
+ }
12
+ }
13
+ /**
14
+ * Return all events within the last `ms` milliseconds before `before`
15
+ */
16
+ window(ms, before = Date.now()) {
17
+ const start = before - ms;
18
+ return this.events.filter((e) => e.timestamp >= start && e.timestamp <= before);
19
+ }
20
+ /**
21
+ * Return all events of a given type
22
+ */
23
+ ofType(type) {
24
+ return this.events.filter((e) => e.type === type);
25
+ }
26
+ all() {
27
+ return [...this.events];
28
+ }
29
+ clear() {
30
+ this.events = [];
31
+ }
32
+ }
package/dist/network.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { Page } from "playwright";
2
+ import type { EventQueue } from "./event-queue.js";
3
+ export declare function setEventQueue(queue: EventQueue): void;
2
4
  export declare function attach(page: Page): void;
3
5
  export declare function clear(): void;
4
6
  export declare function detail(idx: number): Promise<string>;
package/dist/network.js CHANGED
@@ -4,6 +4,10 @@ import { join } from "node:path";
4
4
  const BODY_INLINE_LIMIT = 4000;
5
5
  let entries = [];
6
6
  let startTime = new Map();
7
+ let eventQueue = null;
8
+ export function setEventQueue(queue) {
9
+ eventQueue = queue;
10
+ }
7
11
  export function attach(page) {
8
12
  page.on("request", (req) => {
9
13
  if (req.resourceType() === "document" && req.frame() === page.mainFrame()) {
@@ -16,7 +20,21 @@ export function attach(page) {
16
20
  const t0 = startTime.get(req);
17
21
  if (t0 == null)
18
22
  return;
19
- entries.push({ req, res, ms: Date.now() - t0 });
23
+ const ms = Date.now() - t0;
24
+ entries.push({ req, res, ms });
25
+ // Push to event queue if available
26
+ if (eventQueue) {
27
+ eventQueue.push({
28
+ timestamp: Date.now(),
29
+ type: 'network',
30
+ payload: {
31
+ url: req.url(),
32
+ method: req.method(),
33
+ status: res.status(),
34
+ ms
35
+ }
36
+ });
37
+ }
20
38
  });
21
39
  page.on("requestfailed", (req) => {
22
40
  if (!startTime.has(req))
package/package.json CHANGED
@@ -1,8 +1,22 @@
1
1
  {
2
2
  "name": "@presto1314w/vite-devtools-browser",
3
- "version": "0.1.4",
4
- "description": "CLI for programmatic access to Vue/React DevTools in Vite applications",
5
- "license": "MIT",
3
+ "version": "0.2.0",
4
+ "description": "Runtime diagnostics CLI for Vite apps with event-stream correlation, HMR diagnosis, framework inspection, and mapped errors",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "vite",
8
+ "devtools",
9
+ "debugging",
10
+ "runtime-diagnostics",
11
+ "hmr",
12
+ "module-graph",
13
+ "sourcemap",
14
+ "vue",
15
+ "react",
16
+ "svelte",
17
+ "cli",
18
+ "ai-agents"
19
+ ],
6
20
  "repository": {
7
21
  "type": "git",
8
22
  "url": "git+https://github.com/MapleCity1314/vite-browser.git"