devlens-mcp 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
@@ -17,18 +17,13 @@ DevLens keeps a single Chromium instance alive for the duration of your Claude C
17
17
 
18
18
  ## Setup
19
19
 
20
- ```bash
21
- npm install -g devlens-mcp
22
- npx playwright install chromium
23
- ```
24
-
25
- Then in your project root:
20
+ In your project root:
26
21
 
27
22
  ```bash
28
23
  npx devlens-mcp init
29
24
  ```
30
25
 
31
- This creates `.mcp.json`, `.claude/skills/devlens.md`, and `devlens.config.ts` automatically.
26
+ This creates `.mcp.json`, `.claude/skills/devlens.md`, and `devlens.config.ts`, and installs the Chromium browser automatically.
32
27
 
33
28
  Edit `devlens.config.ts` to map your source files to dev server routes, then restart Claude Code.
34
29
 
package/SKILL.md CHANGED
@@ -16,12 +16,12 @@ You have access to four visual inspection tools: `dl_warmup`, `dl_capture`, `dl_
16
16
  ## Rules — follow these automatically, no user prompting needed
17
17
 
18
18
  ### Rule 1: Warmup at session start
19
- The first time you are about to edit any file matching `apps/web/src/**`, call `dl_warmup` first.
19
+ The first time you are about to edit any frontend file (`.tsx`, `.ts`, `.css`, `.vue`, `.svelte`), call `dl_warmup` first.
20
20
  This pre-warms the browser so subsequent captures cost ~50ms instead of ~500ms.
21
21
  Only call it once per session.
22
22
 
23
23
  ### Rule 2: Capture after every frontend file write
24
- After every `Edit` or `Write` to a file matching `apps/web/src/**/*.tsx` or `apps/web/src/**/*.css`:
24
+ After every `Edit` or `Write` to a file matching `**/*.tsx`, `**/*.vue`, `**/*.svelte`, or `**/*.css`:
25
25
  1. Call `dl_capture` with either `route` (if you know it) or `filePath` (auto-resolved from devlens.config.ts)
26
26
  2. Add `selector` if you only changed a specific component (keeps the image small and fast)
27
27
  3. Look at the returned image — does it look right?
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // src/cli/init.ts
4
4
  import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
5
5
  import { resolve, join } from "path";
6
+ import { spawnSync } from "child_process";
6
7
  var SKILL_CONTENT = `---
7
8
  name: devlens
8
9
  description: Real-time visual QA for frontend development. Auto-invoked when editing frontend files.
@@ -103,15 +104,19 @@ async function runInit() {
103
104
  } else {
104
105
  console.log(" \u2713 devlens.config.ts already exists \u2014 skipped");
105
106
  }
107
+ console.log(" Installing Chromium browser...");
108
+ const result = spawnSync("npx", ["playwright", "install", "chromium"], { stdio: "inherit", shell: true });
109
+ if (result.status === 0) {
110
+ console.log(" \u2713 Chromium installed");
111
+ } else {
112
+ console.warn(' \u26A0 Chromium install failed \u2014 run "npx playwright install chromium" manually');
113
+ }
106
114
  console.log("");
107
- console.log("DevLens initialized. Next steps:");
108
- console.log("");
109
- console.log(" 1. Install Chromium (one-time):");
110
- console.log(" npx playwright install chromium");
115
+ console.log("DevLens initialized. Next step:");
111
116
  console.log("");
112
- console.log(" 2. Edit devlens.config.ts to map your pages to routes");
117
+ console.log(" 1. Edit devlens.config.ts to map your pages to routes");
113
118
  console.log("");
114
- console.log(" 3. Restart Claude Code");
119
+ console.log(" 2. Restart Claude Code");
115
120
  console.log("");
116
121
  console.log(" Claude will then automatically screenshot your UI after every file write.");
117
122
  }
@@ -414,16 +419,24 @@ async function startServer() {
414
419
  ]
415
420
  }));
416
421
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
417
- const { name, arguments: args } = req.params;
422
+ const { name } = req.params;
423
+ const args = req.params.arguments ?? {};
424
+ const str = (v) => typeof v === "string" ? v : void 0;
425
+ const num = (v) => typeof v === "number" ? v : void 0;
418
426
  try {
419
427
  if (name === "dl_warmup") {
420
- const result = await warmup(args.devServerUrl, config);
428
+ const result = await warmup(str(args.devServerUrl), config);
421
429
  return {
422
430
  content: [{ type: "text", text: `Warmed up in ${result.durationMs}ms \u2014 ${result.url}` }]
423
431
  };
424
432
  }
425
433
  if (name === "dl_capture") {
426
- const input = args;
434
+ const input = {
435
+ route: str(args.route),
436
+ filePath: str(args.filePath),
437
+ selector: str(args.selector),
438
+ waitMs: num(args.waitMs)
439
+ };
427
440
  const result = await capture(input, config);
428
441
  if ("error" in result) {
429
442
  return { content: [{ type: "text", text: `Error: ${result.error}` }] };
@@ -437,11 +450,12 @@ async function startServer() {
437
450
  return { content };
438
451
  }
439
452
  if (name === "dl_diff") {
440
- const { route, selector } = args;
453
+ const route = str(args.route) ?? "";
454
+ const selector = str(args.selector);
441
455
  const result = await diff(route, selector, diffStore, config);
442
456
  if ("error" in result) return { content: [{ type: "text", text: `Error: ${result.error}` }] };
443
457
  const content = [
444
- { type: "text", text: result.diff ? `Changed: ${result.diff.changedPercent}% (${result.diff.changedPixels} pixels)` : result.message ?? "Baseline set." }
458
+ { type: "text", text: result.diff ? `Changed: ${result.diff.changedPercent}% (${result.diff.changedPixels} pixels)` : "message" in result && typeof result.message === "string" ? result.message : "Baseline set." }
445
459
  ];
446
460
  if (result.diff?.diffImageBase64) {
447
461
  content.push({ type: "image", data: result.diff.diffImageBase64, mimeType: "image/png" });
@@ -449,7 +463,8 @@ async function startServer() {
449
463
  return { content };
450
464
  }
451
465
  if (name === "dl_snapshot") {
452
- const { route, selector } = args;
466
+ const route = str(args.route) ?? "";
467
+ const selector = str(args.selector);
453
468
  const result = await snapshot(route, selector, config);
454
469
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
455
470
  }
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "devlens-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Real-time visual feedback plugin for Claude Code frontend development",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
7
13
  "bin": {
8
14
  "devlens-mcp": "dist/index.js"
9
15
  },
@@ -26,19 +32,25 @@
26
32
  "dev": "tsup --watch",
27
33
  "test": "vitest run",
28
34
  "test:watch": "vitest",
29
- "start": "node dist/index.js"
35
+ "start": "node dist/index.js",
36
+ "lint": "eslint src --ext .ts",
37
+ "format": "prettier --write src"
30
38
  },
31
39
  "dependencies": {
32
40
  "@modelcontextprotocol/sdk": "^1.0.0",
33
41
  "minimatch": "^10.2.5",
34
42
  "pixelmatch": "^6.0.0",
35
43
  "playwright": "^1.45.0",
36
- "pngjs": "^7.0.0",
37
- "zod": "^3.22.0"
44
+ "pngjs": "^7.0.0"
38
45
  },
39
46
  "devDependencies": {
40
47
  "@types/node": "^20.0.0",
41
48
  "@types/pngjs": "^6.0.0",
49
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
50
+ "@typescript-eslint/parser": "^7.0.0",
51
+ "eslint": "^8.57.0",
52
+ "eslint-config-prettier": "^9.0.0",
53
+ "prettier": "^3.0.0",
42
54
  "tsup": "^8.0.0",
43
55
  "typescript": "^5.4.0",
44
56
  "vitest": "^1.6.0"