gravity-lite 3.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 DharuNamikaze
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # gravity-lite
2
+
3
+ AI-powered CSS layout diagnostics. Zero-config MCP server for AI assistants.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/gravity-lite.svg)](https://www.npmjs.com/package/gravity-lite)
6
+ [![license](https://img.shields.io/npm/l/gravity-lite.svg)](LICENSE)
7
+
8
+ Give your AI assistant (Kiro, Cursor, Claude, etc.) the ability to **see what actually renders** in your browser — not just read source code. Gravity bridges AI tools to live Chrome tabs via the Chrome DevTools Protocol.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install -g gravity-lite
14
+ ```
15
+
16
+ **Requirements:** Node.js ≥ 16, Chrome ≥ 116
17
+
18
+ ---
19
+
20
+ ## Setup (one-time, ~2 minutes)
21
+
22
+ ### 1. Add to your IDE MCP config
23
+
24
+ **Kiro** (`.kiro/settings/mcp.json`), **Cursor** (`~/.cursor/mcp.json`), or **Claude Desktop** (`claude_desktop_config.json`):
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "gravity": { "command": "gravity" }
30
+ }
31
+ }
32
+ ```
33
+
34
+ Restart your IDE. The MCP server starts automatically.
35
+
36
+ ### 2. Load the Chrome extension
37
+
38
+ ```
39
+ chrome://extensions → enable Developer Mode → Load unpacked
40
+ ```
41
+
42
+ Point it at the `extension/` folder inside the package:
43
+
44
+ ```bash
45
+ # Print the exact path to load
46
+ gravity doctor
47
+ ```
48
+
49
+ ### 3. Connect to a tab
50
+
51
+ Click the **Gravity** icon in Chrome → **Connect to Tab**.
52
+
53
+ Done. The extension connects to the MCP server automatically.
54
+
55
+ ---
56
+
57
+ ## Tools
58
+
59
+ | Tool | What it does |
60
+ |---|---|
61
+ | `connect_browser` | Check extension connection status |
62
+ | `diagnose_layout` | Detect overflow, hidden, offscreen, unresolved CSS vars |
63
+ | `inspect_stacking` | Debug z-index failures and stacking context traps |
64
+ | `check_accessibility` | WCAG contrast ratio, touch targets, ARIA tree |
65
+ | `inspect_responsive` | Fixed widths that break on mobile, viewport overflow |
66
+ | `debug_flexgrid` | Flexbox/Grid container + children deep analysis |
67
+ | `get_computed_layout` | Full computed style snapshot with CSS rule specificity |
68
+ | `highlight_element` | Color-coded overlay in the browser (content/padding/border/margin) |
69
+ | `screenshot_element` | Capture any element as a base64 PNG |
70
+ | `get_page_performance` | Layout thrash metrics, paint timings |
71
+
72
+ ### Example prompts
73
+
74
+ ```
75
+ diagnose the #header element
76
+ why is .modal behind everything — inspect stacking context
77
+ check accessibility of the .submit-btn
78
+ screenshot the #hero section
79
+ highlight .nav-bar for 5 seconds
80
+ debug the flex container .card-grid
81
+ ```
82
+
83
+ ---
84
+
85
+ ## How it works
86
+
87
+ ```
88
+ IDE / AI ──stdio──► MCP Server (Node.js)
89
+
90
+ WS :9224 ◄── extension connects OUT to server
91
+ │ (no native messaging, no OS setup)
92
+ Chrome Extension (MV3)
93
+ ├── offscreen.js — persistent WebSocket
94
+ └── background.js — chrome.debugger API
95
+
96
+ Active Tab (CDP)
97
+ ```
98
+
99
+ **The key insight:** The extension connects *out* to the MCP server, not the other way around. No native messaging, no registry keys, no OS-specific setup. Works on Linux, macOS, and Windows.
100
+
101
+ ---
102
+
103
+ ## Commands
104
+
105
+ ```bash
106
+ gravity # start MCP server (called by IDE automatically)
107
+ gravity doctor # check setup status and print extension path
108
+ ```
109
+
110
+ ## Environment variables
111
+
112
+ | Variable | Default | Description |
113
+ |---|---|---|
114
+ | `GRAVITY_PORT` | `9224` | WebSocket bridge port |
115
+
116
+ ---
117
+
118
+ ## Troubleshooting
119
+
120
+ | Problem | Fix |
121
+ |---|---|
122
+ | "Browser extension not connected" | Open Chrome, load the extension, click "Connect to Tab" |
123
+ | "Port 9224 already in use" | Set `GRAVITY_PORT=9225` in your MCP config |
124
+ | MCP server dot stays red in popup | Make sure `gravity` is running (check IDE MCP status) |
125
+ | "Element not found: #selector" | Verify the selector exists on the current page |
126
+ | Chrome ≥ 116 required | The offscreen documents API was added in Chrome 116 |
@@ -0,0 +1,12 @@
1
+ /**
2
+ * bridge.ts — WebSocket server that the Chrome extension connects to.
3
+ *
4
+ * The MCP server IS the WebSocket server. The extension connects OUT to us.
5
+ * No native messaging. No manifest. No OS-specific registration.
6
+ *
7
+ * Extension ──WS──► Bridge (this) ──CDP──► MCP tools
8
+ */
9
+ export declare function startBridge(): Promise<void>;
10
+ export declare function sendCDP(method: string, params?: Record<string, unknown>): Promise<unknown>;
11
+ export declare function isExtensionConnected(): boolean;
12
+ export declare function getBridgePort(): number;
package/dist/bridge.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * bridge.ts — WebSocket server that the Chrome extension connects to.
3
+ *
4
+ * The MCP server IS the WebSocket server. The extension connects OUT to us.
5
+ * No native messaging. No manifest. No OS-specific registration.
6
+ *
7
+ * Extension ──WS──► Bridge (this) ──CDP──► MCP tools
8
+ */
9
+ import { WebSocketServer, WebSocket } from 'ws';
10
+ const PORT = process.env.GRAVITY_PORT ? Number(process.env.GRAVITY_PORT) : 9224;
11
+ const CDP_TIMEOUT_MS = 10_000;
12
+ let extensionSocket = null;
13
+ let msgIdCounter = 1;
14
+ const pending = new Map();
15
+ // ── Start the bridge server ───────────────────────────────────────────────────
16
+ export function startBridge() {
17
+ return new Promise((resolve, reject) => {
18
+ const wss = new WebSocketServer({ port: PORT, host: '127.0.0.1' });
19
+ wss.on('error', (err) => {
20
+ if (err.code === 'EADDRINUSE') {
21
+ reject(new Error(`Port ${PORT} is already in use. ` +
22
+ `Set GRAVITY_PORT env var to use a different port, ` +
23
+ `then reload the extension popup to reconnect.`));
24
+ }
25
+ else {
26
+ reject(err);
27
+ }
28
+ });
29
+ wss.on('listening', () => {
30
+ console.error(`[gravity] bridge listening on ws://127.0.0.1:${PORT}`);
31
+ resolve();
32
+ });
33
+ wss.on('connection', (ws, req) => {
34
+ const origin = req.headers.origin ?? 'unknown';
35
+ console.error(`[gravity] extension connected (origin: ${origin})`);
36
+ // Only one extension at a time — close the old one
37
+ if (extensionSocket && extensionSocket.readyState === WebSocket.OPEN) {
38
+ extensionSocket.close();
39
+ }
40
+ extensionSocket = ws;
41
+ ws.on('message', (data) => {
42
+ try {
43
+ const msg = JSON.parse(data.toString());
44
+ if (msg.type === 'cdp_response') {
45
+ const p = pending.get(msg.id);
46
+ if (p) {
47
+ clearTimeout(p.timer);
48
+ pending.delete(msg.id);
49
+ if (msg.error) {
50
+ p.reject(new Error(msg.error.message));
51
+ }
52
+ else {
53
+ p.resolve(msg.result);
54
+ }
55
+ }
56
+ }
57
+ }
58
+ catch (e) {
59
+ console.error('[gravity] bridge parse error:', e);
60
+ }
61
+ });
62
+ ws.on('close', () => {
63
+ console.error('[gravity] extension disconnected');
64
+ if (extensionSocket === ws)
65
+ extensionSocket = null;
66
+ // Reject all pending requests
67
+ for (const [id, p] of pending) {
68
+ clearTimeout(p.timer);
69
+ p.reject(new Error('Extension disconnected'));
70
+ pending.delete(id);
71
+ }
72
+ });
73
+ ws.on('error', (err) => {
74
+ console.error('[gravity] extension socket error:', err.message);
75
+ });
76
+ });
77
+ });
78
+ }
79
+ // ── Send a CDP command through the extension ──────────────────────────────────
80
+ export function sendCDP(method, params = {}) {
81
+ if (!extensionSocket || extensionSocket.readyState !== WebSocket.OPEN) {
82
+ return Promise.reject(new Error('Browser extension not connected. ' +
83
+ 'Make sure Chrome is open, the Gravity extension is loaded, ' +
84
+ 'and you clicked "Connect to Tab" in the popup.'));
85
+ }
86
+ const id = msgIdCounter++;
87
+ return new Promise((resolve, reject) => {
88
+ const timer = setTimeout(() => {
89
+ pending.delete(id);
90
+ reject(new Error(`CDP ${method} timed out after ${CDP_TIMEOUT_MS}ms`));
91
+ }, CDP_TIMEOUT_MS);
92
+ pending.set(id, { resolve, reject, timer });
93
+ extensionSocket.send(JSON.stringify({ type: 'cdp_request', id, method, params }));
94
+ });
95
+ }
96
+ // ── Status helpers ────────────────────────────────────────────────────────────
97
+ export function isExtensionConnected() {
98
+ return extensionSocket !== null && extensionSocket.readyState === WebSocket.OPEN;
99
+ }
100
+ export function getBridgePort() {
101
+ return PORT;
102
+ }
103
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli.ts — Gravity v3 CLI entry point
4
+ *
5
+ * Commands:
6
+ * gravity — start the MCP server (default)
7
+ * gravity doctor — check setup status
8
+ */
9
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli.ts — Gravity v3 CLI entry point
4
+ *
5
+ * Commands:
6
+ * gravity — start the MCP server (default)
7
+ * gravity doctor — check setup status
8
+ */
9
+ import { program } from 'commander';
10
+ import { createRequire } from 'module';
11
+ import { dirname, resolve } from 'path';
12
+ import { fileURLToPath } from 'url';
13
+ import { existsSync } from 'fs';
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const require = createRequire(import.meta.url);
16
+ const pkg = require('../package.json');
17
+ program
18
+ .name('gravity')
19
+ .description('AI-powered CSS layout diagnostics — MCP server')
20
+ .version(pkg.version);
21
+ // ── doctor ────────────────────────────────────────────────────────────────────
22
+ program
23
+ .command('doctor')
24
+ .description('Check setup status and print instructions')
25
+ .action(async () => {
26
+ const { default: WebSocket } = await import('ws');
27
+ const port = process.env.GRAVITY_PORT ? Number(process.env.GRAVITY_PORT) : 9224;
28
+ console.log('\nGravity v' + pkg.version + ' — setup check');
29
+ console.log('─'.repeat(40));
30
+ // Node version
31
+ const nodeOk = parseInt(process.version.slice(1)) >= 16;
32
+ console.log(`${nodeOk ? '✓' : '✗'} Node.js ${process.version}${nodeOk ? '' : ' (need ≥16)'}`);
33
+ // Extension folder
34
+ const extDir = resolve(__dirname, '..', 'extension');
35
+ const extOk = existsSync(resolve(extDir, 'manifest.json'));
36
+ console.log(`${extOk ? '✓' : '✗'} Extension folder: ${extDir}`);
37
+ if (!extOk)
38
+ console.log(' ↳ Run: npm install -g gravity-lite');
39
+ // Port availability / existing server
40
+ try {
41
+ await new Promise((resolve, reject) => {
42
+ const ws = new WebSocket(`ws://127.0.0.1:${port}`);
43
+ const t = setTimeout(() => { ws.close(); reject(new Error('timeout')); }, 2000);
44
+ ws.on('open', () => { clearTimeout(t); ws.close(); resolve(); });
45
+ ws.on('error', (e) => { clearTimeout(t); reject(e); });
46
+ });
47
+ console.log(`✓ MCP server is running on port ${port}`);
48
+ }
49
+ catch {
50
+ console.log(`✗ MCP server not running on port ${port}`);
51
+ console.log(' ↳ Add gravity to your IDE MCP config and restart');
52
+ }
53
+ console.log('─'.repeat(40));
54
+ console.log('\nExtension setup (one-time):');
55
+ console.log(` 1. Open chrome://extensions → enable Developer Mode`);
56
+ console.log(` 2. "Load unpacked" → select: ${extDir}`);
57
+ console.log(` 3. Click the Gravity icon → "Connect to Tab"`);
58
+ console.log('\nMCP config (add to your IDE):');
59
+ console.log(JSON.stringify({
60
+ mcpServers: {
61
+ gravity: { command: 'gravity' }
62
+ }
63
+ }, null, 2));
64
+ console.log();
65
+ });
66
+ // ── default: run MCP server ───────────────────────────────────────────────────
67
+ program.action(async () => {
68
+ const { GravityMCPServer } = await import('./mcp-server.js');
69
+ const srv = new GravityMCPServer();
70
+ await srv.run();
71
+ });
72
+ program.parse();
73
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * diagnostics.ts — CSS layout & accessibility analysis helpers.
3
+ * Pure functions — no I/O, no side effects.
4
+ */
5
+ export interface Bounds {
6
+ left: number;
7
+ top: number;
8
+ right: number;
9
+ bottom: number;
10
+ width: number;
11
+ height: number;
12
+ }
13
+ export interface Issue {
14
+ type: string;
15
+ severity: 'high' | 'medium' | 'low';
16
+ message: string;
17
+ suggestion: string;
18
+ }
19
+ export declare function extractBounds(model: {
20
+ content: number[];
21
+ width: number;
22
+ height: number;
23
+ }): Bounds;
24
+ export declare function checkVisibility(styles: Map<string, string>): Issue[];
25
+ export declare function checkOffscreen(bounds: Bounds, vp: {
26
+ clientWidth: number;
27
+ clientHeight: number;
28
+ }): Issue[];
29
+ export declare function checkOverflow(styles: Map<string, string>): Issue[];
30
+ export declare function checkZIndex(styles: Map<string, string>): Issue[];
31
+ export declare function checkStackingContextCreators(styles: Map<string, string>): {
32
+ creates: boolean;
33
+ reasons: string[];
34
+ };
35
+ export declare function checkFlexGrid(styles: Map<string, string>): Issue[];
36
+ export declare function checkResponsive(styles: Map<string, string>, bounds: Bounds, vp: {
37
+ clientWidth: number;
38
+ }): Issue[];
39
+ export declare function checkAccessibilityStyles(styles: Map<string, string>): Issue[];
40
+ /**
41
+ * Parse any CSS color string to { r, g, b } (0–255).
42
+ * Supports: rgb(...), rgba(...), #rrggbb, #rgb
43
+ */
44
+ export declare function parseColor(color: string): {
45
+ r: number;
46
+ g: number;
47
+ b: number;
48
+ } | null;
49
+ /** Relative luminance per WCAG 2.1 */
50
+ export declare function relativeLuminance(r: number, g: number, b: number): number;
51
+ /** Contrast ratio between two colours (1–21) */
52
+ export declare function contrastRatio(c1: {
53
+ r: number;
54
+ g: number;
55
+ b: number;
56
+ }, c2: {
57
+ r: number;
58
+ g: number;
59
+ b: number;
60
+ }): number;
61
+ export declare function checkColorContrast(styles: Map<string, string>): Issue[];
62
+ export declare function checkCustomProperties(styles: Map<string, string>): Issue[];
63
+ export declare function sortBySeverity(issues: Issue[]): Issue[];
64
+ export declare function validateSelector(selector: string): {
65
+ valid: boolean;
66
+ error?: string;
67
+ };