@eyeglass/bridge 0.1.4 → 0.1.6
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 +17 -2
- package/dist/http.js +91 -0
- package/dist/store.js +32 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @eyeglass/bridge
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Server that connects the browser inspector to AI coding agents for [Eyeglass](https://github.com/donutboyband/eyeglass).
|
|
4
|
+
|
|
5
|
+
**Supported agents:** Claude Code, GitHub Copilot CLI, OpenAI Codex CLI
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -12,7 +14,9 @@ npm install @eyeglass/bridge
|
|
|
12
14
|
|
|
13
15
|
## Usage
|
|
14
16
|
|
|
15
|
-
The bridge runs as an MCP server
|
|
17
|
+
The bridge runs as an MCP server (Claude, Copilot) or HTTP server (Codex).
|
|
18
|
+
|
|
19
|
+
**MCP config** (`.claude/settings.json` or `.copilot/mcp-config.json`):
|
|
16
20
|
|
|
17
21
|
```json
|
|
18
22
|
{
|
|
@@ -37,8 +41,19 @@ The bridge runs as an MCP server. Add to `.claude/settings.json`:
|
|
|
37
41
|
| `wait_for_request` | Long-poll for new requests |
|
|
38
42
|
| `get_focus_history` | Get previously focused elements |
|
|
39
43
|
|
|
44
|
+
## HTTP API (for Codex)
|
|
45
|
+
|
|
46
|
+
| Endpoint | Description |
|
|
47
|
+
|----------|-------------|
|
|
48
|
+
| `GET /api/focus` | Get current focus as markdown |
|
|
49
|
+
| `GET /api/wait` | Long-poll for new requests |
|
|
50
|
+
| `POST /api/status` | Update status |
|
|
51
|
+
| `POST /api/thought` | Send thought |
|
|
52
|
+
| `POST /api/action` | Report action |
|
|
53
|
+
|
|
40
54
|
## Features
|
|
41
55
|
|
|
56
|
+
- **Rich context** - element info, accessibility, styles, and DOM neighborhood (parent layout context)
|
|
42
57
|
- **Auto-commits** changes on success with `[eyeglass:<id>]` tags
|
|
43
58
|
- **One-click undo** via `git revert`
|
|
44
59
|
- **Real-time updates** via Server-Sent Events
|
package/dist/http.js
CHANGED
|
@@ -85,6 +85,97 @@ export function startHttpServer() {
|
|
|
85
85
|
}
|
|
86
86
|
res.json({ success: true, message: result.message });
|
|
87
87
|
});
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// REST API for non-MCP agents (Codex CLI, Aider, etc.)
|
|
90
|
+
// These mirror the MCP tools but over HTTP
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Get current focus as markdown (mirrors get_focused_element)
|
|
93
|
+
app.get('/api/focus', (_req, res) => {
|
|
94
|
+
res.type('text/markdown').send(store.formatAsMarkdown());
|
|
95
|
+
});
|
|
96
|
+
// Wait for a new focus request (mirrors wait_for_request)
|
|
97
|
+
// This is a blocking/long-polling endpoint
|
|
98
|
+
app.get('/api/wait', async (req, res) => {
|
|
99
|
+
const timeout = parseInt(req.query.timeout) || 300000; // 5 min default
|
|
100
|
+
try {
|
|
101
|
+
await store.waitForFocus(timeout);
|
|
102
|
+
res.type('text/markdown').send(store.formatAsMarkdown());
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
res.status(408).json({ error: 'Timeout waiting for focus request' });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
// Update status (mirrors update_status)
|
|
109
|
+
app.post('/api/status', (req, res) => {
|
|
110
|
+
const { status, message } = req.body;
|
|
111
|
+
if (!status || !['idle', 'pending', 'fixing', 'success', 'failed'].includes(status)) {
|
|
112
|
+
res.status(400).json({ error: 'Invalid status. Must be: idle, pending, fixing, success, or failed' });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const active = store.getActive();
|
|
116
|
+
if (!active) {
|
|
117
|
+
res.status(404).json({ error: 'No active focus' });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
store.updateStatus(active.interactionId, status, message);
|
|
121
|
+
res.json({ success: true, status, message });
|
|
122
|
+
});
|
|
123
|
+
// Send a thought (mirrors send_thought)
|
|
124
|
+
app.post('/api/thought', (req, res) => {
|
|
125
|
+
const { content } = req.body;
|
|
126
|
+
if (!content || typeof content !== 'string') {
|
|
127
|
+
res.status(400).json({ error: 'Missing or invalid content' });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const active = store.getActive();
|
|
131
|
+
if (!active) {
|
|
132
|
+
res.status(404).json({ error: 'No active focus' });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
store.sendThought(active.interactionId, content);
|
|
136
|
+
res.json({ success: true });
|
|
137
|
+
});
|
|
138
|
+
// Report an action (mirrors report_action)
|
|
139
|
+
app.post('/api/action', (req, res) => {
|
|
140
|
+
const { action, target, complete } = req.body;
|
|
141
|
+
if (!action || !['reading', 'writing', 'searching', 'thinking'].includes(action)) {
|
|
142
|
+
res.status(400).json({ error: 'Invalid action. Must be: reading, writing, searching, or thinking' });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!target || typeof target !== 'string') {
|
|
146
|
+
res.status(400).json({ error: 'Missing or invalid target' });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const active = store.getActive();
|
|
150
|
+
if (!active) {
|
|
151
|
+
res.status(404).json({ error: 'No active focus' });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
store.reportAction(active.interactionId, action, target, complete ?? false);
|
|
155
|
+
res.json({ success: true });
|
|
156
|
+
});
|
|
157
|
+
// Get focus history (mirrors get_focus_history)
|
|
158
|
+
app.get('/api/history', (_req, res) => {
|
|
159
|
+
const history = store.getHistory();
|
|
160
|
+
if (history.length === 0) {
|
|
161
|
+
res.type('text/markdown').send('# No Focus History\n\nNo previous focus requests.');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const summary = history
|
|
165
|
+
.map((p, i) => {
|
|
166
|
+
const { snapshot, snapshots, userNote } = p;
|
|
167
|
+
const firstSnapshot = snapshot || (snapshots && snapshots[0]);
|
|
168
|
+
if (!firstSnapshot)
|
|
169
|
+
return `${i + 1}. **unknown** - "${userNote}"`;
|
|
170
|
+
const elementCount = snapshots ? ` (${snapshots.length} elements)` : '';
|
|
171
|
+
return `${i + 1}. **${firstSnapshot.framework.componentName || firstSnapshot.tagName}**${elementCount} - "${userNote}"`;
|
|
172
|
+
})
|
|
173
|
+
.join('\n');
|
|
174
|
+
res.type('text/markdown').send(`## Focus History\n\n${summary}`);
|
|
175
|
+
});
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// SSE and Browser endpoints
|
|
178
|
+
// ============================================================================
|
|
88
179
|
// SSE endpoint for real-time activity updates
|
|
89
180
|
app.get('/events', (req, res) => {
|
|
90
181
|
res.setHeader('Content-Type', 'text/event-stream');
|
package/dist/store.js
CHANGED
|
@@ -376,7 +376,28 @@ ${styles.gridTemplate ? `- Grid Template: ${styles.gridTemplate}` : ''}
|
|
|
376
376
|
- Detected: ${framework.name}
|
|
377
377
|
${framework.ancestry ? `- Component Tree: ${framework.ancestry.join(' > ')}` : ''}
|
|
378
378
|
${framework.props ? `- Props: ${JSON.stringify(framework.props, null, 2)}` : ''}
|
|
379
|
+
${snapshot.neighborhood ? `
|
|
380
|
+
### DOM Neighborhood
|
|
381
|
+
**Parents (layout context):**
|
|
382
|
+
${snapshot.neighborhood.parents.length > 0 ? snapshot.neighborhood.parents.map((p, i) => {
|
|
383
|
+
const styleInfo = [
|
|
384
|
+
p.styles.display,
|
|
385
|
+
p.styles.position !== 'static' ? p.styles.position : null,
|
|
386
|
+
p.styles.flexDirection,
|
|
387
|
+
p.styles.alignItems ? `align: ${p.styles.alignItems}` : null,
|
|
388
|
+
p.styles.justifyContent ? `justify: ${p.styles.justifyContent}` : null,
|
|
389
|
+
p.styles.gap ? `gap: ${p.styles.gap}` : null,
|
|
390
|
+
p.styles.gridTemplate ? `grid: ${p.styles.gridTemplate}` : null,
|
|
391
|
+
].filter(Boolean).join(', ');
|
|
392
|
+
return `${i + 1}. \`<${p.tagName}>\`${p.className ? ` .${p.className.split(' ')[0]}` : ''} — ${styleInfo}`;
|
|
393
|
+
}).join('\n') : '(none)'}
|
|
379
394
|
|
|
395
|
+
**Children:**
|
|
396
|
+
${snapshot.neighborhood.children.length > 0 ? snapshot.neighborhood.children.map(c => {
|
|
397
|
+
const countStr = c.count && c.count > 1 ? ` x${c.count}` : '';
|
|
398
|
+
return `- \`<${c.tagName}>\`${c.className ? ` .${c.className.split(' ')[0]}` : ''}${countStr}`;
|
|
399
|
+
}).join('\n') : '(none)'}
|
|
400
|
+
` : ''}
|
|
380
401
|
### Page Context
|
|
381
402
|
- URL: ${snapshot.url}
|
|
382
403
|
- Timestamp: ${new Date(snapshot.timestamp).toISOString()}
|
|
@@ -426,7 +447,17 @@ ${styles.gridTemplate ? `- Grid Template: ${styles.gridTemplate}` : ''}
|
|
|
426
447
|
- Detected: ${framework.name}
|
|
427
448
|
${framework.ancestry ? `- Component Tree: ${framework.ancestry.join(' > ')}` : ''}
|
|
428
449
|
${framework.props ? `- Props: ${JSON.stringify(framework.props, null, 2)}` : ''}
|
|
429
|
-
|
|
450
|
+
${snapshot.neighborhood ? `
|
|
451
|
+
### DOM Neighborhood
|
|
452
|
+
**Parents:** ${snapshot.neighborhood.parents.length > 0 ? snapshot.neighborhood.parents.map(p => {
|
|
453
|
+
const styleInfo = [p.styles.display, p.styles.flexDirection, p.styles.gap].filter(Boolean).join(', ');
|
|
454
|
+
return `\`<${p.tagName}>\` (${styleInfo})`;
|
|
455
|
+
}).join(' > ') : '(none)'}
|
|
456
|
+
**Children:** ${snapshot.neighborhood.children.length > 0 ? snapshot.neighborhood.children.map(c => {
|
|
457
|
+
const countStr = c.count && c.count > 1 ? ` x${c.count}` : '';
|
|
458
|
+
return `\`<${c.tagName}>\`${countStr}`;
|
|
459
|
+
}).join(', ') : '(none)'}
|
|
460
|
+
` : ''}`;
|
|
430
461
|
}).join('\n---\n\n');
|
|
431
462
|
return `# User Focus Request (${snapshots.length} Elements)
|
|
432
463
|
**Interaction ID:** ${interactionId}
|