Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vscode/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@
}
}
}




86 changes: 86 additions & 0 deletions packages/chrome-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Dev Inspector Chrome Extension

Chrome extension that brings dev-inspector functionality to any webpage using native messaging for ACP communication.

## Installation

### 1. Install Native Messaging Host

```bash
cd packages/native-messaging-host
pnpm install
pnpm build
node dist/install.js
```

**Important**: After running `install.js`, you need to update the extension ID in the manifest file.

### 2. Build Chrome Extension

```bash
cd packages/chrome-extension
pnpm install
pnpm build
```

### 3. Load Extension in Chrome

1. Open Chrome and navigate to `chrome://extensions`
2. Enable "Developer mode" (top right)
3. Click "Load unpacked"
4. Select `packages/chrome-extension/dist` directory
5. **Copy the extension ID** (it looks like `abcdefghijklmnopqrstuvwxyz123456`)

### 4. Update Native Host Manifest

1. On macOS, open:
```
~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.mcpc_tech.dev_inspector.json
```

2. Replace `EXTENSION_ID_PLACEHOLDER` with your actual extension ID

3. Reload the extension in Chrome

## Usage

1. Click the extension icon in Chrome toolbar
2. Click "Capture Element" button
3. Hover over any element on the page
4. Click to capture element info
5. The captured data will be sent to ACP agent for inspection

## Architecture

```
Chrome Extension → Native Messaging Host → ACP Agent (Claude Code/Cline)
(UI + Inspector) (stdio protocol) (AI analysis)
```

## Development

**Native Host**:
```bash
cd packages/native-messaging-host
pnpm dev # Watch mode
```

**Extension**:
```bash
cd packages/chrome-extension
pnpm dev # Watch mode
```

After changes, reload the extension in `chrome://extensions`.

## Troubleshooting

**Native host won't connect:**
- Check `chrome://extensions` for error messages
- Verify manifest file has correct extension ID
- Check native host path in manifest is correct

**Element capture not working:**
- Open DevTools console to see errors
- Check content script is loaded on the page
- Verify activeTab permission is granted
37 changes: 37 additions & 0 deletions packages/chrome-extension/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@mcpc-tech/dev-inspector-chrome-extension",
"version": "0.0.1",
"description": "Chrome extension for dev-inspector with native messaging",
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite build --watch"
},
"dependencies": {
"@ai-sdk/react": "^2.0.95",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-separator": "^1.1.8",
"ai": "^5.0.95",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.546.0",
"nanoid": "^5.1.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"tailwind-merge": "^3.3.1"
},
"devDependencies": {
"@types/chrome": "^0.0.277",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3",
"vite": "^6.0.7"
}
}
31 changes: 31 additions & 0 deletions packages/chrome-extension/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"manifest_version": 3,
"name": "Dev Inspector",
"version": "0.0.1",
"description": "AI-powered dev inspector for any webpage",
"permissions": [
"activeTab",
"scripting",
"storage"
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_title": "Dev Inspector"
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"web_accessible_resources": [
{
"resources": [
"inspector-overlay.css"
],
"matches": [
"<all_urls>"
]
}
]
}
102 changes: 102 additions & 0 deletions packages/chrome-extension/src/background/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* Background service worker for Chrome extension
* Injects the inspector client script from the running dev-inspector server
*/

const SERVER_HOST = 'localhost';
const SERVER_PORT = 8888;
const SERVER_BASE = `http://${SERVER_HOST}:${SERVER_PORT}`;

/**
* Inject the inspector script into the current tab
*/
async function injectInspector(tab: chrome.tabs.Tab) {
if (!tab.id) return;

try {
// 1. Check if server is reachable and script exists
const scriptUrl = `${SERVER_BASE}/__inspector__/inspector.iife.js`;
try {
const response = await fetch(scriptUrl, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`Server returned ${response.status} ${response.statusText}`);
}
} catch (error) {
console.error('[background] Failed to connect to dev-inspector server:', error);
// Notify user via alert script injection
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: (host, port) => {
alert(`Failed to connect to Dev Inspector server at http://${host}:${port}\n\nPlease make sure the server is running:\n npx dev-inspector-server --port ${port}`);
},
args: [SERVER_HOST, SERVER_PORT]
});
return;
}

console.log('[background] Injecting inspector into tab', tab.id);

// 2. Inject configuration
await chrome.scripting.executeScript({
target: { tabId: tab.id },
world: 'MAIN', // Inject into main world to share window object
func: (host, port) => {
// @ts-ignore
window.__DEV_INSPECTOR_CONFIG__ = {
host: host,
port: port,
base: '/'
};
console.log('[Dev Inspector] Configuration injected');
},
args: [SERVER_HOST, SERVER_PORT]
});

// 3. Inject the loader script that adds the script tag
// We inject a loader that creates a script tag pointing to the server
// This ensures the page loads the module/script from the server context
await chrome.scripting.executeScript({
target: { tabId: tab.id },
world: 'MAIN',
func: (baseUrl) => {
if (document.querySelector('dev-inspector-mcp')) {
console.log('[Dev Inspector] Already injected');
return;
}

// Create web component container
const inspector = document.createElement('dev-inspector-mcp');
document.body.appendChild(inspector);

// Create and append script
const script = document.createElement('script');
script.src = `${baseUrl}/__inspector__/inspector.iife.js`;
// script.type = 'module'; // It's an IIFE build now, not module
script.crossOrigin = 'anonymous';

script.onload = () => {
console.log('[Dev Inspector] Script loaded successfully');
// Trigger activation event if needed
window.dispatchEvent(new CustomEvent('activate-inspector'));
};

script.onerror = (e) => {
console.error('[Dev Inspector] Failed to load script', e);
};

document.head.appendChild(script);
},
args: [SERVER_BASE]
});

} catch (error) {
console.error('[background] Injection failed:', error);
}
}

// Handle extension icon click
chrome.action.onClicked.addListener((tab) => {
injectInspector(tab);
});

export { };
8 changes: 8 additions & 0 deletions packages/chrome-extension/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
36 changes: 36 additions & 0 deletions packages/chrome-extension/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
"types": [
"chrome"
],
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}
12 changes: 12 additions & 0 deletions packages/chrome-extension/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": [
"vite.config.ts"
]
}
25 changes: 25 additions & 0 deletions packages/chrome-extension/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
rollupOptions: {
input: {
background: resolve(__dirname, 'src/background/index.ts'),
},
output: {
entryFileNames: '[name].js',
chunkFileNames: '[name].js',
assetFileNames: '[name].[ext]',
},
},
},
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
});
Loading