@presto1314w/vite-devtools-browser 0.1.0 → 0.1.2

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,112 +1,118 @@
1
- # vite-browser
2
-
3
- CLI for programmatic access to Vue/React/Svelte DevTools in Vite applications. Provides component trees, props, state, Pinia stores, Vue Router, console logs, and network requests as structured text output designed for AI agents and automation.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install -g @presto1314w/vite-devtools-browser
9
- npx playwright install chromium
10
- ```
11
-
12
- ## Quick Start
13
-
14
- ```bash
15
- # Start your Vite dev server
16
- cd my-vue-app
17
- npm run dev
18
-
19
- # In another terminal
20
- vite-browser open http://localhost:5173
21
- vite-browser detect # vue@3.5.29 / react@19.x / svelte@x
22
- vite-browser vue tree # Vue component tree
23
- vite-browser react tree # React component tree
24
- vite-browser svelte tree # Svelte component tree (best effort)
25
- vite-browser screenshot # Take screenshot
26
- vite-browser logs # Console logs
27
- vite-browser network # Network requests
28
- vite-browser close
29
- ```
30
-
31
- ## Features
32
-
33
- - Framework Detection: Auto-detect Vue, React, Svelte and versions (best effort)
34
- - Vue DevTools: Component tree, props, state, computed properties, source locations
35
- - React DevTools: Component tree + component inspection (props/hooks/state/context)
36
- - Svelte Support: Component tree + component detail inspection when runtime metadata is available
37
- - Pinia Integration: Inspect store state and getters
38
- - Vue Router: Current route, params, query, all routes
39
- - Network Monitoring: Track requests, headers, bodies, and response payloads
40
- - Console Logs: Capture console.log, warn, error, debug
41
- - Screenshots: Full-page PNG screenshots
42
- - JavaScript Evaluation: Run arbitrary JS in page context
43
- - Vite Integration: Error tracking, HMR monitoring
44
-
45
- ## Commands
46
-
47
- ### Browser Control
48
- ```bash
49
- vite-browser open <url> # Launch browser and navigate
50
- vite-browser close # Close browser and daemon
51
- vite-browser goto <url> # Navigate to URL
52
- vite-browser back # Go back
53
- vite-browser reload # Reload page
54
- ```
55
-
56
- ### Framework Detection
57
- ```bash
58
- vite-browser detect # Detect framework and version
59
- ```
60
-
61
- ### Vue DevTools
62
- ```bash
63
- vite-browser vue tree # Show component tree
64
- vite-browser vue tree <id> # Inspect component details
65
- vite-browser vue pinia # List all Pinia stores
66
- vite-browser vue pinia <store> # Inspect specific store
67
- vite-browser vue router # Show router information
68
- ```
69
-
70
- ### React DevTools
71
- ```bash
72
- vite-browser react tree # Show React component tree
73
- vite-browser react tree <id> # Inspect props/hooks/state/context/source
74
- ```
75
-
76
- ### Svelte
77
- ```bash
78
- vite-browser svelte tree # Show Svelte component tree
79
- vite-browser svelte tree <id> # Inspect Svelte component details
80
- ```
81
-
82
- ### Debugging
83
- ```bash
84
- vite-browser screenshot # Take full-page screenshot
85
- vite-browser eval <script> # Run JavaScript in page
86
- vite-browser logs # Show console logs
87
- vite-browser errors # Show Vite errors
88
- vite-browser network # List network requests
89
- vite-browser network <idx> # Inspect specific request
90
- ```
91
-
92
- ## Architecture
93
-
94
- - Daemon + Socket: Background process with socket communication
95
- - Playwright: Headed Chromium browser
96
- - One Browser, One Page: Single persistent browser instance
97
- - Auto-start: Daemon starts automatically on first command
98
-
99
- ## Requirements
100
-
101
- - Node.js 20+
102
- - Chromium (via Playwright)
103
- - Vite dev server running
104
- - Vue/React/Svelte app for framework-specific commands
105
-
106
- ## Documentation
107
-
108
- See [SKILL.md](./skills/SKILL.md) for complete command reference and usage examples.
109
-
110
- ## License
111
-
112
- MIT
1
+ # vite-browser
2
+
3
+ Agent Skill for AI coding assistants to debug Vite applications with structured access to Vue/React/Svelte runtime state. The CLI is the supporting runtime used by the skill for component trees, store/router inspection, logs, network traces, screenshots, and scripted page evaluation.
4
+
5
+ ## Skills
6
+
7
+ ```bash
8
+ npx skills add MapleCity1314/vite-browser
9
+ ```
10
+
11
+ ## CLI Installation (Optional)
12
+
13
+ ```bash
14
+ npm install -g @presto1314w/vite-devtools-browser
15
+ npx playwright install chromium
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```bash
21
+ # Start your Vite dev server
22
+ cd my-vue-app
23
+ npm run dev
24
+
25
+ # In another terminal
26
+ vite-browser open http://localhost:5173
27
+ vite-browser detect # vue@3.5.29 / react@19.x / svelte@x
28
+ vite-browser vue tree # Vue component tree
29
+ vite-browser react tree # React component tree
30
+ vite-browser svelte tree # Svelte component tree (best effort)
31
+ vite-browser screenshot # Take screenshot
32
+ vite-browser logs # Console logs
33
+ vite-browser network # Network requests
34
+ vite-browser close
35
+ ```
36
+
37
+ ## Features
38
+
39
+ - Framework Detection: Auto-detect Vue, React, Svelte and versions (best effort)
40
+ - Vue DevTools: Component tree, props, state, computed properties, source locations
41
+ - React DevTools: Component tree + component inspection (props/hooks/state/context)
42
+ - Svelte Support: Component tree + component detail inspection when runtime metadata is available
43
+ - Pinia Integration: Inspect store state and getters
44
+ - Vue Router: Current route, params, query, all routes
45
+ - Network Monitoring: Track requests, headers, bodies, and response payloads
46
+ - Console Logs: Capture console.log, warn, error, debug
47
+ - Screenshots: Full-page PNG screenshots
48
+ - JavaScript Evaluation: Run arbitrary JS in page context
49
+ - Vite Integration: Error tracking, HMR monitoring
50
+
51
+ ## Commands
52
+
53
+ ### Browser Control
54
+ ```bash
55
+ vite-browser open <url> # Launch browser and navigate
56
+ vite-browser close # Close browser and daemon
57
+ vite-browser goto <url> # Navigate to URL
58
+ vite-browser back # Go back
59
+ vite-browser reload # Reload page
60
+ ```
61
+
62
+ ### Framework Detection
63
+ ```bash
64
+ vite-browser detect # Detect framework and version
65
+ ```
66
+
67
+ ### Vue DevTools
68
+ ```bash
69
+ vite-browser vue tree # Show component tree
70
+ vite-browser vue tree <id> # Inspect component details
71
+ vite-browser vue pinia # List all Pinia stores
72
+ vite-browser vue pinia <store> # Inspect specific store
73
+ vite-browser vue router # Show router information
74
+ ```
75
+
76
+ ### React DevTools
77
+ ```bash
78
+ vite-browser react tree # Show React component tree
79
+ vite-browser react tree <id> # Inspect props/hooks/state/context/source
80
+ ```
81
+
82
+ ### Svelte
83
+ ```bash
84
+ vite-browser svelte tree # Show Svelte component tree
85
+ vite-browser svelte tree <id> # Inspect Svelte component details
86
+ ```
87
+
88
+ ### Debugging
89
+ ```bash
90
+ vite-browser screenshot # Take full-page screenshot
91
+ vite-browser eval <script> # Run JavaScript in page
92
+ vite-browser logs # Show console logs
93
+ vite-browser errors # Show Vite errors
94
+ vite-browser network # List network requests
95
+ vite-browser network <idx> # Inspect specific request
96
+ ```
97
+
98
+ ## Architecture
99
+
100
+ - Daemon + Socket: Background process with socket communication
101
+ - Playwright: Headed Chromium browser
102
+ - One Browser, One Page: Single persistent browser instance
103
+ - Auto-start: Daemon starts automatically on first command
104
+
105
+ ## Requirements
106
+
107
+ - Node.js 20+
108
+ - Chromium (via Playwright)
109
+ - Vite dev server running
110
+ - Vue/React/Svelte app for framework-specific commands
111
+
112
+ ## Documentation
113
+
114
+ See [SKILL.md](./skills/SKILL.md) for complete command reference and usage examples.
115
+
116
+ ## License
117
+
118
+ MIT
package/dist/browser.js CHANGED
@@ -15,19 +15,12 @@ const installHook = hasReactExtension
15
15
  let context = null;
16
16
  let page = null;
17
17
  let framework = "unknown";
18
+ let extensionModeDisabled = false;
18
19
  const consoleLogs = [];
19
20
  const MAX_LOGS = 200;
20
21
  let lastReactSnapshot = [];
21
22
  export async function open(url) {
22
- if (!context) {
23
- context = await launch();
24
- page = context.pages()[0] ?? (await context.newPage());
25
- attachListeners(page);
26
- networkLog.attach(page);
27
- }
28
- const currentPage = page;
29
- if (!currentPage)
30
- throw new Error("browser not open");
23
+ const currentPage = await ensurePage();
31
24
  if (url) {
32
25
  await currentPage.goto(url, { waitUntil: "domcontentloaded" });
33
26
  await detectFramework();
@@ -48,20 +41,65 @@ export async function close() {
48
41
  networkLog.clear();
49
42
  lastReactSnapshot = [];
50
43
  }
44
+ async function ensurePage() {
45
+ if (!contextUsable(context)) {
46
+ await close();
47
+ context = await launch();
48
+ }
49
+ if (!context)
50
+ throw new Error("browser not open");
51
+ if (!page || page.isClosed()) {
52
+ try {
53
+ page = context.pages()[0] ?? (await context.newPage());
54
+ }
55
+ catch (error) {
56
+ if (!isClosedTargetError(error))
57
+ throw error;
58
+ await close();
59
+ extensionModeDisabled = true;
60
+ context = await launch();
61
+ page = context.pages()[0] ?? (await context.newPage());
62
+ }
63
+ attachListeners(page);
64
+ networkLog.attach(page);
65
+ }
66
+ return page;
67
+ }
68
+ function contextUsable(current) {
69
+ if (!current)
70
+ return false;
71
+ try {
72
+ current.pages();
73
+ return true;
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
79
+ function isClosedTargetError(error) {
80
+ if (!(error instanceof Error))
81
+ return false;
82
+ return /Target page, context or browser has been closed/i.test(error.message);
83
+ }
51
84
  async function launch() {
52
- if (hasReactExtension && installHook) {
53
- const ctx = await chromium.launchPersistentContext("", {
54
- headless: false,
55
- viewport: { width: 1280, height: 720 },
56
- args: [
57
- `--disable-extensions-except=${extensionPath}`,
58
- `--load-extension=${extensionPath}`,
59
- "--auto-open-devtools-for-tabs",
60
- ],
61
- });
62
- await ctx.waitForEvent("serviceworker").catch(() => { });
63
- await ctx.addInitScript(installHook);
64
- return ctx;
85
+ if (hasReactExtension && installHook && !extensionModeDisabled) {
86
+ try {
87
+ const ctx = await chromium.launchPersistentContext("", {
88
+ headless: false,
89
+ viewport: { width: 1280, height: 720 },
90
+ args: [
91
+ `--disable-extensions-except=${extensionPath}`,
92
+ `--load-extension=${extensionPath}`,
93
+ "--auto-open-devtools-for-tabs",
94
+ ],
95
+ });
96
+ await ctx.waitForEvent("serviceworker").catch(() => { });
97
+ await ctx.addInitScript(installHook);
98
+ return ctx;
99
+ }
100
+ catch {
101
+ extensionModeDisabled = true;
102
+ }
65
103
  }
66
104
  const browser = await chromium.launch({
67
105
  headless: false,
@@ -78,22 +116,19 @@ function attachListeners(currentPage) {
78
116
  });
79
117
  }
80
118
  export async function goto(url) {
81
- if (!page)
82
- throw new Error("browser not open");
83
- await page.goto(url, { waitUntil: "domcontentloaded" });
119
+ const currentPage = await ensurePage();
120
+ await currentPage.goto(url, { waitUntil: "domcontentloaded" });
84
121
  await detectFramework();
85
- return page.url();
122
+ return currentPage.url();
86
123
  }
87
124
  export async function back() {
88
- if (!page)
89
- throw new Error("browser not open");
90
- await page.goBack({ waitUntil: "domcontentloaded" });
125
+ const currentPage = await ensurePage();
126
+ await currentPage.goBack({ waitUntil: "domcontentloaded" });
91
127
  }
92
128
  export async function reload() {
93
- if (!page)
94
- throw new Error("browser not open");
95
- await page.reload({ waitUntil: "domcontentloaded" });
96
- return page.url();
129
+ const currentPage = await ensurePage();
130
+ await currentPage.reload({ waitUntil: "domcontentloaded" });
131
+ return currentPage.url();
97
132
  }
98
133
  export async function detectFramework() {
99
134
  if (!page)
package/dist/cli.js CHANGED
@@ -13,6 +13,7 @@ if (cmd === "open") {
13
13
  console.error("usage: vite-browser open <url> [--cookies-json <file>]");
14
14
  process.exit(1);
15
15
  }
16
+ const url = normalizeUrl(arg);
16
17
  const cookieIdx = args.indexOf("--cookies-json");
17
18
  const cookieFile = cookieIdx >= 0 ? args[cookieIdx + 1] : undefined;
18
19
  if (cookieFile) {
@@ -21,15 +22,15 @@ if (cmd === "open") {
21
22
  exit(res, "");
22
23
  const raw = readFileSync(cookieFile, "utf-8");
23
24
  const cookies = JSON.parse(raw);
24
- const domain = new URL(arg).hostname;
25
+ const domain = new URL(url).hostname;
25
26
  const cRes = await send("cookies", { cookies, domain });
26
27
  if (!cRes.ok)
27
28
  exit(cRes, "");
28
- await send("goto", { url: arg });
29
- exit(res, `opened -> ${arg} (${cookies.length} cookies for ${domain})`);
29
+ await send("goto", { url });
30
+ exit(res, `opened -> ${url} (${cookies.length} cookies for ${domain})`);
30
31
  }
31
- const res = await send("open", { url: arg });
32
- exit(res, `opened -> ${arg}`);
32
+ const res = await send("open", { url });
33
+ exit(res, `opened -> ${url}`);
33
34
  }
34
35
  if (cmd === "close") {
35
36
  const res = await send("close");
@@ -40,7 +41,7 @@ if (cmd === "goto") {
40
41
  console.error("usage: vite-browser goto <url>");
41
42
  process.exit(1);
42
43
  }
43
- const res = await send("goto", { url: arg });
44
+ const res = await send("goto", { url: normalizeUrl(arg) });
44
45
  exit(res, res.ok ? `-> ${res.data}` : "");
45
46
  }
46
47
  if (cmd === "back") {
@@ -166,3 +167,8 @@ OPTIONS
166
167
  -h, --help Show this help message
167
168
  `);
168
169
  }
170
+ function normalizeUrl(value) {
171
+ if (value.includes("://"))
172
+ return value;
173
+ return `http://${value}`;
174
+ }
package/dist/client.js CHANGED
@@ -40,7 +40,7 @@ function daemonAlive() {
40
40
  }
41
41
  catch {
42
42
  rmSync(pidFile, { force: true });
43
- rmSync(socketPath, { force: true });
43
+ removeSocketFile();
44
44
  return false;
45
45
  }
46
46
  }
@@ -70,3 +70,8 @@ function ok(s) {
70
70
  function no() {
71
71
  return false;
72
72
  }
73
+ function removeSocketFile() {
74
+ if (process.platform === "win32")
75
+ return;
76
+ rmSync(socketPath, { force: true });
77
+ }
package/dist/daemon.js CHANGED
@@ -3,7 +3,7 @@ import { mkdirSync, writeFileSync, rmSync } from "node:fs";
3
3
  import * as browser from "./browser.js";
4
4
  import { socketDir, socketPath, pidFile } from "./paths.js";
5
5
  mkdirSync(socketDir, { recursive: true, mode: 0o700 });
6
- rmSync(socketPath, { force: true });
6
+ removeSocketFile();
7
7
  rmSync(pidFile, { force: true });
8
8
  writeFileSync(pidFile, String(process.pid));
9
9
  const server = createServer((socket) => {
@@ -127,6 +127,12 @@ function shutdown() {
127
127
  process.exit(0);
128
128
  }
129
129
  function cleanup() {
130
- rmSync(socketPath, { force: true });
130
+ removeSocketFile();
131
131
  rmSync(pidFile, { force: true });
132
132
  }
133
+ function removeSocketFile() {
134
+ // Windows named pipes are not filesystem entries, so unlinking them fails with EPERM.
135
+ if (process.platform === "win32")
136
+ return;
137
+ rmSync(socketPath, { force: true });
138
+ }
@@ -176,12 +176,51 @@ export async function getComponentDetails(page, id) {
176
176
  */
177
177
  export async function getPiniaStores(page, storeName) {
178
178
  const result = await page.evaluate((name) => {
179
+ const safeJson = (value) => {
180
+ if (typeof value === "function")
181
+ return "[Function]";
182
+ if (typeof value === "bigint")
183
+ return value.toString();
184
+ const seen = new WeakSet();
185
+ try {
186
+ return JSON.stringify(value, (_, v) => {
187
+ if (typeof v === "function")
188
+ return "[Function]";
189
+ if (typeof v === "bigint")
190
+ return v.toString();
191
+ if (v && typeof v === "object") {
192
+ if (seen.has(v))
193
+ return "[Circular]";
194
+ seen.add(v);
195
+ }
196
+ return v;
197
+ });
198
+ }
199
+ catch {
200
+ return String(value);
201
+ }
202
+ };
203
+ const hook = window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
204
+ const piniaFromApp = hook?.apps?.[0]?.config?.globalProperties?.$pinia;
179
205
  // Try to find Pinia instance
180
- const pinia = window.__PINIA__ || window.pinia;
206
+ const pinia = window.__PINIA__ || window.pinia || piniaFromApp;
181
207
  if (!pinia)
182
208
  return "Pinia not found";
183
- const stores = pinia._s || pinia.state?.value || {};
184
- const storeKeys = Object.keys(stores);
209
+ // Pinia v3 uses Map for _s, older integrations can expose plain objects.
210
+ const storesById = {};
211
+ const registry = pinia._s;
212
+ if (registry instanceof Map) {
213
+ registry.forEach((store, id) => {
214
+ storesById[String(id)] = store;
215
+ });
216
+ }
217
+ else if (registry && typeof registry === "object") {
218
+ for (const [id, store] of Object.entries(registry)) {
219
+ storesById[id] = store;
220
+ }
221
+ }
222
+ const stateById = pinia.state?.value && typeof pinia.state.value === "object" ? pinia.state.value : {};
223
+ const storeKeys = Array.from(new Set([...Object.keys(storesById), ...Object.keys(stateById)]));
185
224
  if (storeKeys.length === 0)
186
225
  return "No Pinia stores found";
187
226
  const output = [];
@@ -195,29 +234,40 @@ export async function getPiniaStores(page, storeName) {
195
234
  return output.join("\n");
196
235
  }
197
236
  // Get specific store
198
- const store = stores[name];
199
- if (!store)
237
+ const store = storesById[name] ?? null;
238
+ const stateOnly = stateById[name];
239
+ if (!store && !stateOnly)
200
240
  return `Store '${name}' not found`;
201
241
  output.push(`# Pinia Store: ${name}\n`);
202
242
  // State
203
- const state = store.$state || store.state || store;
243
+ const state = store?.$state || store?.state || stateOnly || store;
204
244
  if (state && typeof state === 'object') {
205
245
  output.push("## State");
206
246
  for (const [key, value] of Object.entries(state)) {
207
247
  if (key.startsWith('$'))
208
248
  continue; // Skip Pinia internals
209
- output.push(` ${key}: ${JSON.stringify(value)}`);
249
+ output.push(` ${key}: ${safeJson(value)}`);
210
250
  }
211
251
  output.push("");
212
252
  }
213
253
  // Getters
214
- const getters = store._getters || {};
215
- if (Object.keys(getters).length > 0) {
254
+ const getterNames = [];
255
+ const rawGetters = store?._getters;
256
+ if (Array.isArray(rawGetters)) {
257
+ getterNames.push(...rawGetters.map((g) => String(g)));
258
+ }
259
+ else if (rawGetters instanceof Set) {
260
+ getterNames.push(...Array.from(rawGetters).map((g) => String(g)));
261
+ }
262
+ else if (rawGetters && typeof rawGetters === "object") {
263
+ getterNames.push(...Object.keys(rawGetters));
264
+ }
265
+ if (getterNames.length > 0) {
216
266
  output.push("## Getters");
217
- for (const key of Object.keys(getters)) {
267
+ for (const key of getterNames) {
218
268
  try {
219
269
  const value = store[key];
220
- output.push(` ${key}: ${JSON.stringify(value)}`);
270
+ output.push(` ${key}: ${safeJson(value)}`);
221
271
  }
222
272
  catch {
223
273
  output.push(` ${key}: [Error]`);
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "@presto1314w/vite-devtools-browser",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI for programmatic access to Vue/React DevTools in Vite applications",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/MapleCity1314/vite-browser.git"
9
+ },
10
+ "homepage": "https://github.com/MapleCity1314/vite-browser#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/MapleCity1314/vite-browser/issues"
13
+ },
6
14
  "type": "module",
7
15
  "engines": {
8
16
  "node": ">=20"
@@ -11,10 +19,10 @@
11
19
  "dist"
12
20
  ],
13
21
  "bin": {
14
- "vite-browser": "./dist/cli.js"
22
+ "vite-browser": "dist/cli.js"
15
23
  },
16
24
  "scripts": {
17
- "start": "node --loader tsx src/cli.ts",
25
+ "start": "node --import tsx src/cli.ts",
18
26
  "dev": "tsx src/cli.ts",
19
27
  "typecheck": "tsc --noEmit",
20
28
  "build": "tsc -p tsconfig.build.json",