@vercel/next-browser 0.1.1 → 0.1.3
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 +33 -13
- package/dist/sourcemap.js +18 -1
- package/dist/suspense.js +22 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,25 +10,45 @@ Built for agents. An LLM can't read a DevTools panel, but it can run
|
|
|
10
10
|
command is a stateless one-shot against a long-lived browser daemon, so an
|
|
11
11
|
agent loop can fire them off without managing browser lifecycle.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Getting started
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
pnpm add -g @vercel/next-browser
|
|
17
|
-
```
|
|
15
|
+
You don't install or run this directly. Your agent does.
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
1. **Add the skill to your project.** From your Next.js repo:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx skills add vercel-labs/next-browser
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Works with Claude Code, Cursor, Cline, and [others](https://skills.sh).
|
|
24
|
+
|
|
25
|
+
2. **Start your agent** in that project.
|
|
26
|
+
|
|
27
|
+
3. **Type `/next-browser`** to invoke the skill.
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
4. The skill checks for the CLI and **installs `@vercel/next-browser`
|
|
30
|
+
globally** if it's missing (plus `playwright install chromium`).
|
|
31
|
+
|
|
32
|
+
5. It asks for your dev server URL and any cookies it needs, opens the
|
|
33
|
+
browser, and from there it's **pair programming** — tell it what you're
|
|
34
|
+
debugging and it drives the tree, navigates pages, inspects components,
|
|
35
|
+
and reads errors for you.
|
|
36
|
+
|
|
37
|
+
That's the whole flow. Run `npx skills upgrade` later to pull updates.
|
|
38
|
+
|
|
39
|
+
The rest of this README documents the raw CLI for the rare case where you're
|
|
40
|
+
scripting it yourself.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Manual install
|
|
22
45
|
|
|
23
46
|
```bash
|
|
24
|
-
next-browser
|
|
25
|
-
next-browser tree
|
|
26
|
-
next-browser ppr lock
|
|
27
|
-
next-browser push /dashboard
|
|
28
|
-
next-browser ppr unlock
|
|
29
|
-
next-browser close
|
|
47
|
+
pnpm add -g @vercel/next-browser
|
|
30
48
|
```
|
|
31
49
|
|
|
50
|
+
Requires Node `>=20`.
|
|
51
|
+
|
|
32
52
|
## Commands
|
|
33
53
|
|
|
34
54
|
```
|
|
@@ -41,7 +61,7 @@ back go back in history
|
|
|
41
61
|
reload reload current page
|
|
42
62
|
restart-server restart the Next.js dev server (clears fs cache)
|
|
43
63
|
|
|
44
|
-
ppr lock enter PPR instant-navigation mode
|
|
64
|
+
ppr lock enter PPR instant-navigation mode (requires cacheComponents)
|
|
45
65
|
ppr unlock exit PPR mode and show shell analysis
|
|
46
66
|
|
|
47
67
|
tree show React component tree
|
package/dist/sourcemap.js
CHANGED
|
@@ -24,6 +24,23 @@ export async function resolve(origin, file, line, column) {
|
|
|
24
24
|
return null;
|
|
25
25
|
return { file: frame.file, line: frame.line1, column: frame.column1 };
|
|
26
26
|
}
|
|
27
|
+
import { resolve as resolvePath } from "node:path";
|
|
28
|
+
import * as mcp from "./mcp.js";
|
|
29
|
+
const projectRoots = new Map();
|
|
30
|
+
async function projectRoot(origin) {
|
|
31
|
+
if (projectRoots.has(origin))
|
|
32
|
+
return projectRoots.get(origin);
|
|
33
|
+
const meta = await mcp.call(origin, "get_project_metadata").catch(() => null);
|
|
34
|
+
const root = typeof meta?.projectPath === "string" ? meta.projectPath : null;
|
|
35
|
+
projectRoots.set(origin, root);
|
|
36
|
+
return root;
|
|
37
|
+
}
|
|
38
|
+
async function absolutize(origin, path) {
|
|
39
|
+
if (path.startsWith("/") || path.startsWith("node_modules/"))
|
|
40
|
+
return path;
|
|
41
|
+
const root = await projectRoot(origin);
|
|
42
|
+
return root ? resolvePath(root, path) : path;
|
|
43
|
+
}
|
|
27
44
|
function normalize(file, origin) {
|
|
28
45
|
const stripped = file.replace(/^about:\/\/React\/[^/]+\//, "");
|
|
29
46
|
const isServer = file !== stripped;
|
|
@@ -48,7 +65,7 @@ export async function resolveViaMap(origin, file, line, column) {
|
|
|
48
65
|
const pos = consumer.originalPositionFor({ line, column });
|
|
49
66
|
if (!pos.source)
|
|
50
67
|
return null;
|
|
51
|
-
return { file: cleanPath(pos.source), line: pos.line, column: pos.column };
|
|
68
|
+
return { file: await absolutize(origin, cleanPath(pos.source)), line: pos.line, column: pos.column };
|
|
52
69
|
}
|
|
53
70
|
async function load(origin, path) {
|
|
54
71
|
if (consumers.has(path))
|
package/dist/suspense.js
CHANGED
|
@@ -276,10 +276,7 @@ async function inPageSuspense(inspect) {
|
|
|
276
276
|
const awaited = entry?.awaited;
|
|
277
277
|
if (!awaited)
|
|
278
278
|
continue;
|
|
279
|
-
const desc = awaited.description
|
|
280
|
-
|| awaited.value?.value
|
|
281
|
-
|| awaited.value?.preview_long
|
|
282
|
-
|| "";
|
|
279
|
+
const desc = preview(awaited.description) || preview(awaited.value);
|
|
283
280
|
boundary.suspendedBy.push({
|
|
284
281
|
name: awaited.name ?? "unknown",
|
|
285
282
|
description: desc,
|
|
@@ -311,4 +308,25 @@ async function inPageSuspense(inspect) {
|
|
|
311
308
|
}
|
|
312
309
|
}
|
|
313
310
|
}
|
|
311
|
+
function preview(v) {
|
|
312
|
+
if (v == null)
|
|
313
|
+
return "";
|
|
314
|
+
if (typeof v === "string")
|
|
315
|
+
return v;
|
|
316
|
+
if (typeof v !== "object")
|
|
317
|
+
return String(v);
|
|
318
|
+
if (typeof v.preview_long === "string")
|
|
319
|
+
return v.preview_long;
|
|
320
|
+
if (typeof v.preview_short === "string")
|
|
321
|
+
return v.preview_short;
|
|
322
|
+
if (typeof v.value === "string")
|
|
323
|
+
return v.value;
|
|
324
|
+
try {
|
|
325
|
+
const s = JSON.stringify(v);
|
|
326
|
+
return s.length > 80 ? s.slice(0, 77) + "..." : s;
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return "";
|
|
330
|
+
}
|
|
331
|
+
}
|
|
314
332
|
}
|