glidercli 0.1.4 → 0.2.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/lib/bserve.js ADDED
@@ -0,0 +1,311 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * browser-relay-server.js
4
+ * Minimal CDP relay server - connects to Chrome extension for browser automation
5
+ * Based on playwriter architecture but stripped down for direct scripting
6
+ */
7
+
8
+ const { WebSocketServer, WebSocket } = require('ws');
9
+ const http = require('http');
10
+
11
+ const PORT = process.env.RELAY_PORT || 19988;
12
+ const HOST = '127.0.0.1';
13
+
14
+ // State
15
+ let extensionWs = null;
16
+ const playwrightClients = new Map();
17
+ const connectedTargets = new Map();
18
+ const pendingRequests = new Map();
19
+ let messageId = 0;
20
+
21
+ // Create HTTP server
22
+ const server = http.createServer((req, res) => {
23
+ if (req.url === '/') {
24
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
25
+ res.end('OK');
26
+ } else if (req.url === '/status') {
27
+ res.writeHead(200, { 'Content-Type': 'application/json' });
28
+ res.end(JSON.stringify({
29
+ extension: extensionWs !== null,
30
+ targets: connectedTargets.size,
31
+ clients: playwrightClients.size
32
+ }));
33
+ } else if (req.url === '/targets') {
34
+ res.writeHead(200, { 'Content-Type': 'application/json' });
35
+ res.end(JSON.stringify(Array.from(connectedTargets.values())));
36
+ } else if (req.url === '/cdp' && req.method === 'POST') {
37
+ // HTTP POST endpoint for CDP commands
38
+ let body = '';
39
+ req.on('data', chunk => body += chunk);
40
+ req.on('end', async () => {
41
+ try {
42
+ const { method, params, sessionId } = JSON.parse(body);
43
+ const result = await routeCDPCommand({ method, params, sessionId });
44
+ res.writeHead(200, { 'Content-Type': 'application/json' });
45
+ res.end(JSON.stringify(result));
46
+ } catch (e) {
47
+ res.writeHead(500, { 'Content-Type': 'application/json' });
48
+ res.end(JSON.stringify({ error: e.message }));
49
+ }
50
+ });
51
+ } else {
52
+ res.writeHead(404);
53
+ res.end('Not Found');
54
+ }
55
+ });
56
+
57
+ // WebSocket server
58
+ const wss = new WebSocketServer({ server });
59
+
60
+ wss.on('connection', (ws, req) => {
61
+ const path = req.url;
62
+
63
+ if (path === '/extension') {
64
+ handleExtensionConnection(ws);
65
+ } else if (path.startsWith('/cdp')) {
66
+ const clientId = path.split('/')[2] || 'default';
67
+ handleCDPConnection(ws, clientId);
68
+ } else {
69
+ ws.close(1000, 'Unknown path');
70
+ }
71
+ });
72
+
73
+ function handleExtensionConnection(ws) {
74
+ if (extensionWs) {
75
+ console.log('[relay] Replacing existing extension connection');
76
+ extensionWs.close(4001, 'Replaced');
77
+ connectedTargets.clear();
78
+ }
79
+
80
+ extensionWs = ws;
81
+ console.log('[relay] Extension connected');
82
+
83
+ // Ping to keep alive
84
+ const pingInterval = setInterval(() => {
85
+ if (ws.readyState === WebSocket.OPEN) {
86
+ ws.send(JSON.stringify({ method: 'ping' }));
87
+ }
88
+ }, 5000);
89
+
90
+ ws.on('message', (data) => {
91
+ try {
92
+ const msg = JSON.parse(data.toString());
93
+ handleExtensionMessage(msg);
94
+ } catch (e) {
95
+ console.error('[relay] Error parsing extension message:', e);
96
+ }
97
+ });
98
+
99
+ ws.on('close', () => {
100
+ console.log('[relay] Extension disconnected');
101
+ clearInterval(pingInterval);
102
+ extensionWs = null;
103
+ connectedTargets.clear();
104
+
105
+ // Notify all clients
106
+ for (const client of playwrightClients.values()) {
107
+ client.ws.close(1000, 'Extension disconnected');
108
+ }
109
+ playwrightClients.clear();
110
+ });
111
+ }
112
+
113
+ function handleExtensionMessage(msg) {
114
+ // Response to our request
115
+ if (msg.id !== undefined) {
116
+ const pending = pendingRequests.get(msg.id);
117
+ if (pending) {
118
+ pendingRequests.delete(msg.id);
119
+ if (msg.error) {
120
+ pending.reject(new Error(msg.error));
121
+ } else {
122
+ pending.resolve(msg.result);
123
+ }
124
+ }
125
+ return;
126
+ }
127
+
128
+ // Pong
129
+ if (msg.method === 'pong') return;
130
+
131
+ // Log from extension
132
+ if (msg.method === 'log') {
133
+ console.log(`[ext:${msg.params.level}]`, ...msg.params.args);
134
+ return;
135
+ }
136
+
137
+ // CDP event from extension
138
+ if (msg.method === 'forwardCDPEvent') {
139
+ const { method, params, sessionId } = msg.params;
140
+
141
+ // Track targets
142
+ if (method === 'Target.attachedToTarget') {
143
+ connectedTargets.set(params.sessionId, {
144
+ sessionId: params.sessionId,
145
+ targetId: params.targetInfo.targetId,
146
+ targetInfo: params.targetInfo
147
+ });
148
+ console.log(`[relay] Target attached: ${params.targetInfo.url}`);
149
+ } else if (method === 'Target.detachedFromTarget') {
150
+ connectedTargets.delete(params.sessionId);
151
+ console.log(`[relay] Target detached: ${params.sessionId}`);
152
+ } else if (method === 'Target.targetInfoChanged') {
153
+ const target = Array.from(connectedTargets.values())
154
+ .find(t => t.targetId === params.targetInfo.targetId);
155
+ if (target) {
156
+ target.targetInfo = params.targetInfo;
157
+ }
158
+ }
159
+
160
+ // Forward to all CDP clients
161
+ const cdpEvent = { method, params, sessionId };
162
+ for (const client of playwrightClients.values()) {
163
+ client.ws.send(JSON.stringify(cdpEvent));
164
+ }
165
+ }
166
+ }
167
+
168
+ function handleCDPConnection(ws, clientId) {
169
+ if (playwrightClients.has(clientId)) {
170
+ ws.close(1000, 'Client ID already connected');
171
+ return;
172
+ }
173
+
174
+ playwrightClients.set(clientId, { id: clientId, ws });
175
+ console.log(`[relay] CDP client connected: ${clientId}`);
176
+
177
+ ws.on('message', async (data) => {
178
+ try {
179
+ const msg = JSON.parse(data.toString());
180
+ const { id, method, params, sessionId } = msg;
181
+
182
+ if (!extensionWs) {
183
+ ws.send(JSON.stringify({ id, error: { message: 'Extension not connected' } }));
184
+ return;
185
+ }
186
+
187
+ try {
188
+ const result = await routeCDPCommand({ method, params, sessionId });
189
+ ws.send(JSON.stringify({ id, sessionId, result }));
190
+
191
+ // Send attachedToTarget events after setAutoAttach
192
+ if (method === 'Target.setAutoAttach' && !sessionId) {
193
+ for (const target of connectedTargets.values()) {
194
+ ws.send(JSON.stringify({
195
+ method: 'Target.attachedToTarget',
196
+ params: {
197
+ sessionId: target.sessionId,
198
+ targetInfo: { ...target.targetInfo, attached: true },
199
+ waitingForDebugger: false
200
+ }
201
+ }));
202
+ }
203
+ }
204
+ } catch (e) {
205
+ ws.send(JSON.stringify({ id, sessionId, error: { message: e.message } }));
206
+ }
207
+ } catch (e) {
208
+ console.error('[relay] Error handling CDP message:', e);
209
+ }
210
+ });
211
+
212
+ ws.on('close', () => {
213
+ playwrightClients.delete(clientId);
214
+ console.log(`[relay] CDP client disconnected: ${clientId}`);
215
+ });
216
+ }
217
+
218
+ async function sendToExtension({ method, params, timeout = 30000 }) {
219
+ if (!extensionWs) throw new Error('Extension not connected');
220
+
221
+ const id = ++messageId;
222
+ extensionWs.send(JSON.stringify({ id, method, params }));
223
+
224
+ return new Promise((resolve, reject) => {
225
+ const timer = setTimeout(() => {
226
+ pendingRequests.delete(id);
227
+ reject(new Error(`Timeout: ${method}`));
228
+ }, timeout);
229
+
230
+ pendingRequests.set(id, {
231
+ resolve: (result) => { clearTimeout(timer); resolve(result); },
232
+ reject: (error) => { clearTimeout(timer); reject(error); }
233
+ });
234
+ });
235
+ }
236
+
237
+ async function routeCDPCommand({ method, params, sessionId }) {
238
+ // Handle some commands locally
239
+ switch (method) {
240
+ case 'Browser.getVersion':
241
+ return {
242
+ protocolVersion: '1.3',
243
+ product: 'Chrome/Extension-Bridge',
244
+ revision: '1.0.0',
245
+ userAgent: 'CDP-Bridge/1.0.0',
246
+ jsVersion: 'V8'
247
+ };
248
+
249
+ case 'Target.setAutoAttach':
250
+ case 'Target.setDiscoverTargets':
251
+ return {};
252
+
253
+ case 'Target.getTargets':
254
+ return {
255
+ targetInfos: Array.from(connectedTargets.values())
256
+ .map(t => ({ ...t.targetInfo, attached: true }))
257
+ };
258
+
259
+ case 'Target.attachToTarget':
260
+ const targetId = params?.targetId;
261
+ for (const target of connectedTargets.values()) {
262
+ if (target.targetId === targetId) {
263
+ return { sessionId: target.sessionId };
264
+ }
265
+ }
266
+ throw new Error(`Target ${targetId} not found`);
267
+
268
+ case 'Target.getTargetInfo':
269
+ if (params?.targetId) {
270
+ for (const target of connectedTargets.values()) {
271
+ if (target.targetId === params.targetId) {
272
+ return { targetInfo: target.targetInfo };
273
+ }
274
+ }
275
+ }
276
+ if (sessionId) {
277
+ const target = connectedTargets.get(sessionId);
278
+ if (target) return { targetInfo: target.targetInfo };
279
+ }
280
+ const first = Array.from(connectedTargets.values())[0];
281
+ return { targetInfo: first?.targetInfo };
282
+ }
283
+
284
+ // Forward to extension
285
+ return await sendToExtension({
286
+ method: 'forwardCDPCommand',
287
+ params: { sessionId, method, params }
288
+ });
289
+ }
290
+
291
+ // Export for use as module
292
+ module.exports = { server, wss, routeCDPCommand };
293
+
294
+ // Start server if run directly
295
+ if (require.main === module) {
296
+ server.listen(PORT, HOST, () => {
297
+ console.log(`[relay] CDP relay server running on ws://${HOST}:${PORT}`);
298
+ console.log('[relay] Endpoints:');
299
+ console.log(` - Extension: ws://${HOST}:${PORT}/extension`);
300
+ console.log(` - CDP: ws://${HOST}:${PORT}/cdp`);
301
+ console.log(` - Status: http://${HOST}:${PORT}/status`);
302
+ console.log(` - Targets: http://${HOST}:${PORT}/targets`);
303
+ });
304
+
305
+ process.on('SIGINT', () => {
306
+ console.log('\n[relay] Shutting down...');
307
+ wss.close();
308
+ server.close();
309
+ process.exit(0);
310
+ });
311
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glidercli",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Browser automation CLI with autonomous loop execution. Control Chrome via CDP, run YAML task files, execute in Ralph Wiggum loops.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -33,5 +33,11 @@
33
33
  "dependencies": {
34
34
  "ws": "^8.18.0",
35
35
  "yaml": "^2.7.0"
36
- }
36
+ },
37
+ "files": [
38
+ "bin/",
39
+ "lib/",
40
+ "index.js",
41
+ "README.md"
42
+ ]
37
43
  }
@@ -1,4 +0,0 @@
1
- # This repository enforces personal git config only
2
- # DO NOT use git-amazon or any other corporate git configs
3
- ENFORCED_DATE=2026-01-10
4
- REQUIRED_CONFIG=personal
@@ -1,24 +0,0 @@
1
- #!/bin/bash
2
- # Auto-prompt to set email on checkout if not set correctly
3
- # If allowed_emails is empty, skip check
4
-
5
- ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
6
- CONFIG="$ROOT/repo.config.json"
7
- EMAIL=$(git config user.email 2>/dev/null)
8
-
9
- # Read allowed emails from config
10
- if [[ -f "$CONFIG" ]] && command -v jq &>/dev/null; then
11
- ALLOWED=$(jq -r '.allowed_emails[]' "$CONFIG" 2>/dev/null)
12
- else
13
- exit 0
14
- fi
15
-
16
- # If no emails configured, skip check
17
- [[ -z "$ALLOWED" ]] && exit 0
18
-
19
- echo "$ALLOWED" | grep -qx "$EMAIL" && exit 0
20
-
21
- echo ""
22
- echo "⚠ Git email not set. Pick one from repo.config.json:"
23
- echo "$ALLOWED" | sed 's/^/ git config user.email '\''/' | sed 's/$/'\''/'
24
- echo ""
@@ -1,30 +0,0 @@
1
- #!/bin/bash
2
- # Blocks commits if email not in allowed list (reads from repo.config.json)
3
- # If allowed_emails is empty, skip check (user hasn't configured yet)
4
-
5
- ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
6
- CONFIG="$ROOT/repo.config.json"
7
- EMAIL=$(git config user.email)
8
-
9
- # Read allowed emails from config
10
- if [[ -f "$CONFIG" ]] && command -v jq &>/dev/null; then
11
- ALLOWED=$(jq -r '.allowed_emails[]' "$CONFIG" 2>/dev/null)
12
- else
13
- # No config or jq - skip check
14
- exit 0
15
- fi
16
-
17
- # If no emails configured, skip check
18
- [[ -z "$ALLOWED" ]] && exit 0
19
-
20
- echo "$ALLOWED" | grep -qx "$EMAIL" && exit 0
21
-
22
- echo ""
23
- echo "❌ BLOCKED: $EMAIL not allowed"
24
- echo ""
25
- echo "Allowed emails (from repo.config.json):"
26
- echo "$ALLOWED" | sed 's/^/ /'
27
- echo ""
28
- echo "Fix: git config user.email '<one of the above>'"
29
- echo ""
30
- exit 1
@@ -1,13 +0,0 @@
1
- #!/bin/bash
2
- # pre-push hook - runs health check before push
3
- # Blocks push if large files or embedded repos detected
4
-
5
- ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
6
- HEALTH_SCRIPT="$ROOT/.github/scripts/health-check.sh"
7
-
8
- if [[ -x "$HEALTH_SCRIPT" ]]; then
9
- "$HEALTH_SCRIPT"
10
- exit $?
11
- fi
12
-
13
- exit 0
@@ -1,127 +0,0 @@
1
- #!/bin/bash
2
- # health-check.sh - Git health check before commit/push
3
- # Checks: large files (>90MB), embedded git repos
4
- # Run: .github/scripts/health-check.sh [size_mb]
5
-
6
- ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
7
- SIZE_MB="${1:-90}"
8
- LIMIT=$((SIZE_MB * 1024 * 1024))
9
-
10
- cd "$ROOT" || exit 1
11
-
12
- echo "🏥 Git Health Check: $ROOT"
13
- echo "==================="
14
- echo ""
15
-
16
- ISSUES=0
17
-
18
- # ============================================================================
19
- # CHECK 1: Large files (>90MB default)
20
- # ============================================================================
21
- echo "🔍 Checking for files > ${SIZE_MB}MB..."
22
- echo ""
23
-
24
- echo "=== UNSTAGED/UNTRACKED ==="
25
- while IFS= read -r line; do
26
- [[ -z "$line" ]] && continue
27
- file="${line:3}"
28
- # Handle renames
29
- [[ "$line" == R* ]] && file="${file##* -> }"
30
- if [[ -f "$file" ]]; then
31
- fsize=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo "0")
32
- if [[ "$fsize" -gt "$LIMIT" ]]; then
33
- size_human=$((fsize / 1024 / 1024))
34
- echo " 🔴 $file (${size_human}MB)"
35
- ISSUES=1
36
- fi
37
- fi
38
- done < <(git status --porcelain 2>/dev/null)
39
-
40
- echo ""
41
- echo "=== STAGED ==="
42
- while IFS= read -r file; do
43
- [[ -z "$file" ]] && continue
44
- if [[ -f "$file" ]]; then
45
- fsize=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo "0")
46
- if [[ "$fsize" -gt "$LIMIT" ]]; then
47
- size_human=$((fsize / 1024 / 1024))
48
- echo " 🔴 $file (${size_human}MB)"
49
- ISSUES=1
50
- fi
51
- fi
52
- done < <(git diff --cached --name-only 2>/dev/null)
53
-
54
- echo ""
55
- echo "=== COMMITTED (last 10) ==="
56
- while IFS= read -r file; do
57
- [[ -z "$file" ]] && continue
58
- if [[ -f "$file" ]]; then
59
- fsize=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo "0")
60
- if [[ "$fsize" -gt "$LIMIT" ]]; then
61
- size_human=$((fsize / 1024 / 1024))
62
- echo " 🔴 $file (${size_human}MB)"
63
- ISSUES=1
64
- fi
65
- fi
66
- done < <(git log --oneline -10 --diff-filter=A --name-only --pretty=format:"" 2>/dev/null | sort -u)
67
-
68
- if [[ $ISSUES -eq 0 ]]; then
69
- echo " 🟢 No large files"
70
- fi
71
-
72
- echo ""
73
-
74
- # ============================================================================
75
- # CHECK 2: Embedded git repos
76
- # ============================================================================
77
- echo "🔍 Checking for embedded git repos..."
78
- echo ""
79
-
80
- ROOT_GIT="$ROOT/.git"
81
- EMBEDDED=()
82
-
83
- while IFS= read -r git_dir; do
84
- [[ -z "$git_dir" ]] && continue
85
- [[ "$git_dir" == "$ROOT_GIT" ]] && continue
86
-
87
- repo_dir="${git_dir%/.git}"
88
- relative="${repo_dir#$ROOT/}"
89
-
90
- # Check if tracked or not ignored
91
- is_tracked=false
92
- git ls-files --cached "$relative" 2>/dev/null | grep -q . && is_tracked=true
93
-
94
- is_ignored=false
95
- git check-ignore -q "$relative" 2>/dev/null && is_ignored=true
96
-
97
- if [[ "$is_tracked" == "true" ]]; then
98
- echo " 🔴 $relative (TRACKED - needs removal)"
99
- EMBEDDED+=("$relative")
100
- ISSUES=1
101
- elif [[ "$is_ignored" == "false" ]]; then
102
- echo " 🟡 $relative (not ignored - add to .gitignore)"
103
- EMBEDDED+=("$relative")
104
- ISSUES=1
105
- fi
106
- done < <(find "$ROOT" -type d -name ".git" 2>/dev/null | sort)
107
-
108
- if [[ ${#EMBEDDED[@]} -eq 0 ]]; then
109
- echo " 🟢 No embedded repos"
110
- else
111
- echo ""
112
- echo "Fix: Add to .gitignore:"
113
- for repo in "${EMBEDDED[@]}"; do
114
- echo " $repo/"
115
- done
116
- fi
117
-
118
- echo ""
119
- echo "==================="
120
-
121
- if [[ $ISSUES -eq 0 ]]; then
122
- echo "🟢 All checks passed"
123
- exit 0
124
- else
125
- echo "🔴 Issues found - fix before pushing"
126
- exit 1
127
- fi
@@ -1,19 +0,0 @@
1
- #!/bin/bash
2
- # setup.sh - run after cloning
3
- # Installs hooks and prompts for email config
4
-
5
- ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
6
- cd "$ROOT"
7
-
8
- # 1. Install hooks
9
- mkdir -p .git/hooks
10
- cp .github/hooks/* .git/hooks/ 2>/dev/null
11
- chmod +x .git/hooks/* 2>/dev/null
12
- echo "✓ Hooks installed"
13
-
14
- # 2. Prompt user to set email
15
- echo ""
16
- echo "Set your git email:"
17
- echo " git config user.email 'github.relock416@passmail.net' # vdutts"
18
- echo " git config user.email 'me@vd7.io' # vdutts7"
19
- echo ""
File without changes
package/repo.config.json DELETED
@@ -1,31 +0,0 @@
1
- {
2
- "repo": {
3
- "name": "glidercli",
4
- "description": "Browser automation CLI with autonomous loop execution",
5
- "homepage": "https://npmjs.com/package/glidercli",
6
- "topics": ["ralph", "claude", "claude-code", "autonomous-agents", "browser-automation", "cdp", "chrome-devtools", "ralph-wiggum"]
7
- },
8
- "owner": {
9
- "github_username": "vdutts7",
10
- "website": "https://vd7.io",
11
- "twitter": "vaboratory"
12
- },
13
- "allowed_emails": [
14
- "github.relock416@passmail.net",
15
- "me@vd7.io"
16
- ],
17
- "social_preview": {
18
- "title": "glidercli",
19
- "icons_dir": "assets/icons",
20
- "icon_creator": "https://vd7.dev/icon-creator",
21
- "dimensions": {
22
- "width": 1280,
23
- "height": 640
24
- }
25
- },
26
- "npm": {
27
- "package_name": "glidercli",
28
- "alt_package": "@vd7/glider",
29
- "bin_command": "glider"
30
- }
31
- }