surf-cli 2.1.0 → 2.3.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/README.md +37 -2
- package/dist/content/accessibility-tree.js +8 -8
- package/dist/content/accessibility-tree.js.map +1 -1
- package/dist/service-worker/index.js +11 -11
- package/dist/service-worker/index.js.map +1 -1
- package/native/chatgpt-client.cjs +29 -22
- package/native/cli.cjs +67 -1
- package/native/config.cjs +12 -0
- package/native/grok-client.cjs +906 -0
- package/native/host-helpers.cjs +108 -1
- package/native/host.cjs +198 -0
- package/native/perplexity-client.cjs +26 -22
- package/package.json +1 -1
- package/native/stream-viewer.html +0 -415
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Surf Stream Viewer</title>
|
|
7
|
-
<style>
|
|
8
|
-
* {
|
|
9
|
-
margin: 0;
|
|
10
|
-
padding: 0;
|
|
11
|
-
box-sizing: border-box;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
body {
|
|
15
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
16
|
-
background: #1a1a1a;
|
|
17
|
-
color: #e0e0e0;
|
|
18
|
-
display: flex;
|
|
19
|
-
flex-direction: column;
|
|
20
|
-
height: 100vh;
|
|
21
|
-
overflow: hidden;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
#status {
|
|
25
|
-
background: #2d2d2d;
|
|
26
|
-
padding: 12px 20px;
|
|
27
|
-
border-bottom: 1px solid #404040;
|
|
28
|
-
display: flex;
|
|
29
|
-
align-items: center;
|
|
30
|
-
gap: 20px;
|
|
31
|
-
font-size: 14px;
|
|
32
|
-
flex-shrink: 0;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
#status.connected {
|
|
36
|
-
border-bottom-color: #4caf50;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
#status.disconnected {
|
|
40
|
-
border-bottom-color: #f44336;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.status-indicator {
|
|
44
|
-
width: 10px;
|
|
45
|
-
height: 10px;
|
|
46
|
-
border-radius: 50%;
|
|
47
|
-
background: #666;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.status-indicator.connected {
|
|
51
|
-
background: #4caf50;
|
|
52
|
-
box-shadow: 0 0 8px #4caf50;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.status-indicator.disconnected {
|
|
56
|
-
background: #f44336;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
#viewport-container {
|
|
60
|
-
flex: 1;
|
|
61
|
-
display: flex;
|
|
62
|
-
align-items: center;
|
|
63
|
-
justify-content: center;
|
|
64
|
-
overflow: auto;
|
|
65
|
-
background: #0a0a0a;
|
|
66
|
-
position: relative;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
#viewport {
|
|
70
|
-
display: block;
|
|
71
|
-
max-width: 100%;
|
|
72
|
-
max-height: 100%;
|
|
73
|
-
cursor: crosshair;
|
|
74
|
-
image-rendering: crisp-edges;
|
|
75
|
-
image-rendering: pixelated;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
#viewport:not(.ready) {
|
|
79
|
-
opacity: 0.5;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
#loading {
|
|
83
|
-
position: absolute;
|
|
84
|
-
top: 50%;
|
|
85
|
-
left: 50%;
|
|
86
|
-
transform: translate(-50%, -50%);
|
|
87
|
-
text-align: center;
|
|
88
|
-
color: #888;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.spinner {
|
|
92
|
-
border: 3px solid #333;
|
|
93
|
-
border-top: 3px solid #4caf50;
|
|
94
|
-
border-radius: 50%;
|
|
95
|
-
width: 40px;
|
|
96
|
-
height: 40px;
|
|
97
|
-
animation: spin 1s linear infinite;
|
|
98
|
-
margin: 0 auto 10px;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
@keyframes spin {
|
|
102
|
-
0% { transform: rotate(0deg); }
|
|
103
|
-
100% { transform: rotate(360deg); }
|
|
104
|
-
}
|
|
105
|
-
</style>
|
|
106
|
-
</head>
|
|
107
|
-
<body>
|
|
108
|
-
<div id="status" class="disconnected">
|
|
109
|
-
<div class="status-indicator disconnected"></div>
|
|
110
|
-
<span id="status-text">Connecting...</span>
|
|
111
|
-
<span id="viewport-info" style="margin-left: auto;"></span>
|
|
112
|
-
</div>
|
|
113
|
-
<div id="viewport-container">
|
|
114
|
-
<div id="loading">
|
|
115
|
-
<div class="spinner"></div>
|
|
116
|
-
<div>Waiting for stream...</div>
|
|
117
|
-
</div>
|
|
118
|
-
<canvas id="viewport"></canvas>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
<script>
|
|
122
|
-
const canvas = document.getElementById('viewport');
|
|
123
|
-
const ctx = canvas.getContext('2d');
|
|
124
|
-
const statusEl = document.getElementById('status');
|
|
125
|
-
const statusText = document.getElementById('status-text');
|
|
126
|
-
const viewportInfo = document.getElementById('viewport-info');
|
|
127
|
-
const loadingEl = document.getElementById('loading');
|
|
128
|
-
|
|
129
|
-
let ws = null;
|
|
130
|
-
let frameCount = 0;
|
|
131
|
-
let lastFrameTime = Date.now();
|
|
132
|
-
let fps = 0;
|
|
133
|
-
let isMouseDown = false;
|
|
134
|
-
let activeButton = 'left';
|
|
135
|
-
let activeTouches = new Map();
|
|
136
|
-
|
|
137
|
-
function connect() {
|
|
138
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
139
|
-
const wsUrl = `${protocol}//${window.location.host}`;
|
|
140
|
-
|
|
141
|
-
ws = new WebSocket(wsUrl);
|
|
142
|
-
|
|
143
|
-
ws.onopen = () => {
|
|
144
|
-
statusEl.className = 'connected';
|
|
145
|
-
statusEl.querySelector('.status-indicator').className = 'status-indicator connected';
|
|
146
|
-
statusText.textContent = 'Connected';
|
|
147
|
-
loadingEl.style.display = 'none';
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
ws.onmessage = (event) => {
|
|
151
|
-
try {
|
|
152
|
-
const message = JSON.parse(event.data);
|
|
153
|
-
|
|
154
|
-
if (message.type === 'frame') {
|
|
155
|
-
drawFrame(message);
|
|
156
|
-
} else if (message.type === 'status') {
|
|
157
|
-
updateStatus(message);
|
|
158
|
-
} else if (message.type === 'error') {
|
|
159
|
-
statusText.textContent = `Error: ${message.message}`;
|
|
160
|
-
statusEl.className = 'disconnected';
|
|
161
|
-
statusEl.querySelector('.status-indicator').className = 'status-indicator disconnected';
|
|
162
|
-
}
|
|
163
|
-
} catch (e) {
|
|
164
|
-
console.error('Error parsing message:', e);
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
ws.onerror = (error) => {
|
|
169
|
-
console.error('WebSocket error:', error);
|
|
170
|
-
statusText.textContent = 'Connection error';
|
|
171
|
-
statusEl.className = 'disconnected';
|
|
172
|
-
statusEl.querySelector('.status-indicator').className = 'status-indicator disconnected';
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
ws.onclose = () => {
|
|
176
|
-
statusText.textContent = 'Disconnected';
|
|
177
|
-
statusEl.className = 'disconnected';
|
|
178
|
-
statusEl.querySelector('.status-indicator').className = 'status-indicator disconnected';
|
|
179
|
-
loadingEl.style.display = 'block';
|
|
180
|
-
|
|
181
|
-
setTimeout(connect, 2000);
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function drawFrame(message) {
|
|
186
|
-
const img = new Image();
|
|
187
|
-
img.onload = () => {
|
|
188
|
-
canvas.width = img.width;
|
|
189
|
-
canvas.height = img.height;
|
|
190
|
-
ctx.drawImage(img, 0, 0);
|
|
191
|
-
canvas.classList.add('ready');
|
|
192
|
-
|
|
193
|
-
frameCount++;
|
|
194
|
-
const now = Date.now();
|
|
195
|
-
if (now - lastFrameTime >= 1000) {
|
|
196
|
-
fps = frameCount;
|
|
197
|
-
frameCount = 0;
|
|
198
|
-
lastFrameTime = now;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
updateViewportInfo(img.width, img.height, fps);
|
|
202
|
-
};
|
|
203
|
-
const mimeType = message.data.startsWith('iVBORw0K') ? 'image/png' : 'image/jpeg';
|
|
204
|
-
img.src = `data:${mimeType};base64,${message.data}`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function updateStatus(message) {
|
|
208
|
-
if (message.viewportWidth && message.viewportHeight) {
|
|
209
|
-
updateViewportInfo(message.viewportWidth, message.viewportHeight, fps);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function updateViewportInfo(width, height, fps) {
|
|
214
|
-
viewportInfo.textContent = `${width}×${height} | ${fps} fps`;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function getCanvasCoordinates(e) {
|
|
218
|
-
const rect = canvas.getBoundingClientRect();
|
|
219
|
-
const scaleX = canvas.width / rect.width;
|
|
220
|
-
const scaleY = canvas.height / rect.height;
|
|
221
|
-
|
|
222
|
-
const x = Math.round((e.clientX - rect.left) * scaleX);
|
|
223
|
-
const y = Math.round((e.clientY - rect.top) * scaleY);
|
|
224
|
-
|
|
225
|
-
// Check if click is within canvas bounds
|
|
226
|
-
if (x < 0 || x > canvas.width || y < 0 || y > canvas.height) {
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return { x, y };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function getModifiers(e) {
|
|
234
|
-
return (e.altKey ? 1 : 0) | (e.ctrlKey ? 2 : 0) | (e.metaKey ? 4 : 0) | (e.shiftKey ? 8 : 0);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function sendMouseEvent(eventType, e, button = 'left') {
|
|
238
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
239
|
-
|
|
240
|
-
const coords = getCanvasCoordinates(e);
|
|
241
|
-
if (!coords) return; // Click outside canvas bounds
|
|
242
|
-
|
|
243
|
-
ws.send(JSON.stringify({
|
|
244
|
-
type: 'input_mouse',
|
|
245
|
-
eventType,
|
|
246
|
-
x: coords.x,
|
|
247
|
-
y: coords.y,
|
|
248
|
-
button,
|
|
249
|
-
clickCount: 1,
|
|
250
|
-
modifiers: getModifiers(e),
|
|
251
|
-
}));
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
canvas.addEventListener('mousedown', (e) => {
|
|
255
|
-
e.preventDefault();
|
|
256
|
-
isMouseDown = true;
|
|
257
|
-
activeButton = e.button === 2 ? 'right' : e.button === 1 ? 'middle' : 'left';
|
|
258
|
-
// Send mouseMoved first to ensure element has hover state
|
|
259
|
-
sendMouseEvent('mouseMoved', e, 'none');
|
|
260
|
-
sendMouseEvent('mousePressed', e, activeButton);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
canvas.addEventListener('mouseup', (e) => {
|
|
264
|
-
e.preventDefault();
|
|
265
|
-
const button = e.button === 2 ? 'right' : e.button === 1 ? 'middle' : 'left';
|
|
266
|
-
sendMouseEvent('mouseReleased', e, button);
|
|
267
|
-
isMouseDown = false;
|
|
268
|
-
activeButton = 'left';
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
canvas.addEventListener('mousemove', (e) => {
|
|
272
|
-
e.preventDefault();
|
|
273
|
-
if (isMouseDown) {
|
|
274
|
-
sendMouseEvent('mouseMoved', e, activeButton);
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
canvas.addEventListener('mouseleave', (e) => {
|
|
279
|
-
if (isMouseDown) {
|
|
280
|
-
sendMouseEvent('mouseReleased', e, activeButton);
|
|
281
|
-
isMouseDown = false;
|
|
282
|
-
activeButton = 'left';
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
canvas.addEventListener('contextmenu', (e) => {
|
|
287
|
-
e.preventDefault();
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
canvas.addEventListener('wheel', (e) => {
|
|
291
|
-
e.preventDefault();
|
|
292
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
293
|
-
|
|
294
|
-
const coords = getCanvasCoordinates(e);
|
|
295
|
-
let deltaX = e.deltaX;
|
|
296
|
-
let deltaY = e.deltaY;
|
|
297
|
-
if (e.deltaMode === 1) {
|
|
298
|
-
deltaX *= 40;
|
|
299
|
-
deltaY *= 40;
|
|
300
|
-
} else if (e.deltaMode === 2) {
|
|
301
|
-
deltaX *= window.innerHeight;
|
|
302
|
-
deltaY *= window.innerHeight;
|
|
303
|
-
}
|
|
304
|
-
ws.send(JSON.stringify({
|
|
305
|
-
type: 'input_mouse',
|
|
306
|
-
eventType: 'mouseWheel',
|
|
307
|
-
x: coords.x,
|
|
308
|
-
y: coords.y,
|
|
309
|
-
deltaX,
|
|
310
|
-
deltaY,
|
|
311
|
-
modifiers: getModifiers(e),
|
|
312
|
-
}));
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
function getTouchCoordinates(touch) {
|
|
316
|
-
const rect = canvas.getBoundingClientRect();
|
|
317
|
-
const scaleX = canvas.width / rect.width;
|
|
318
|
-
const scaleY = canvas.height / rect.height;
|
|
319
|
-
return {
|
|
320
|
-
x: Math.round((touch.clientX - rect.left) * scaleX),
|
|
321
|
-
y: Math.round((touch.clientY - rect.top) * scaleY),
|
|
322
|
-
id: touch.identifier,
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function sendTouchEvent(eventType, touches) {
|
|
327
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
328
|
-
ws.send(JSON.stringify({
|
|
329
|
-
type: 'input_touch',
|
|
330
|
-
eventType,
|
|
331
|
-
touchPoints: touches,
|
|
332
|
-
modifiers: 0,
|
|
333
|
-
}));
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
canvas.addEventListener('touchstart', (e) => {
|
|
337
|
-
e.preventDefault();
|
|
338
|
-
for (const touch of e.changedTouches) {
|
|
339
|
-
const coords = getTouchCoordinates(touch);
|
|
340
|
-
activeTouches.set(touch.identifier, coords);
|
|
341
|
-
}
|
|
342
|
-
sendTouchEvent('touchStart', Array.from(activeTouches.values()));
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
canvas.addEventListener('touchend', (e) => {
|
|
346
|
-
e.preventDefault();
|
|
347
|
-
for (const touch of e.changedTouches) {
|
|
348
|
-
activeTouches.delete(touch.identifier);
|
|
349
|
-
}
|
|
350
|
-
sendTouchEvent('touchEnd', Array.from(activeTouches.values()));
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
canvas.addEventListener('touchmove', (e) => {
|
|
354
|
-
e.preventDefault();
|
|
355
|
-
for (const touch of e.changedTouches) {
|
|
356
|
-
const coords = getTouchCoordinates(touch);
|
|
357
|
-
activeTouches.set(touch.identifier, coords);
|
|
358
|
-
}
|
|
359
|
-
sendTouchEvent('touchMove', Array.from(activeTouches.values()));
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
canvas.addEventListener('touchcancel', (e) => {
|
|
363
|
-
e.preventDefault();
|
|
364
|
-
for (const touch of e.changedTouches) {
|
|
365
|
-
activeTouches.delete(touch.identifier);
|
|
366
|
-
}
|
|
367
|
-
sendTouchEvent('touchCancel', Array.from(activeTouches.values()));
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
document.addEventListener('keydown', (e) => {
|
|
371
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
372
|
-
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
373
|
-
|
|
374
|
-
e.preventDefault();
|
|
375
|
-
const modifiers = (e.altKey ? 1 : 0) | (e.ctrlKey ? 2 : 0) | (e.metaKey ? 4 : 0) | (e.shiftKey ? 8 : 0);
|
|
376
|
-
|
|
377
|
-
// Send keyDown event
|
|
378
|
-
ws.send(JSON.stringify({
|
|
379
|
-
type: 'input_keyboard',
|
|
380
|
-
eventType: 'keyDown',
|
|
381
|
-
key: e.key,
|
|
382
|
-
code: e.code,
|
|
383
|
-
modifiers,
|
|
384
|
-
}));
|
|
385
|
-
|
|
386
|
-
// For printable characters, also send a char event
|
|
387
|
-
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
|
|
388
|
-
ws.send(JSON.stringify({
|
|
389
|
-
type: 'input_keyboard',
|
|
390
|
-
eventType: 'char',
|
|
391
|
-
text: e.key,
|
|
392
|
-
modifiers,
|
|
393
|
-
}));
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
document.addEventListener('keyup', (e) => {
|
|
398
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
399
|
-
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
400
|
-
|
|
401
|
-
e.preventDefault();
|
|
402
|
-
|
|
403
|
-
ws.send(JSON.stringify({
|
|
404
|
-
type: 'input_keyboard',
|
|
405
|
-
eventType: 'keyUp',
|
|
406
|
-
key: e.key,
|
|
407
|
-
code: e.code,
|
|
408
|
-
modifiers: (e.altKey ? 1 : 0) | (e.ctrlKey ? 2 : 0) | (e.metaKey ? 4 : 0) | (e.shiftKey ? 8 : 0),
|
|
409
|
-
}));
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
connect();
|
|
413
|
-
</script>
|
|
414
|
-
</body>
|
|
415
|
-
</html>
|