surf-cli 2.7.2 → 2.9.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 +208 -13
- package/dist/content/index.js +116 -0
- package/dist/content/index.js.map +1 -0
- package/dist/manifest.json +2 -11
- package/dist/options/options.js +3 -3
- package/dist/options/options.js.map +1 -1
- package/dist/service-worker/index.js +261 -61
- package/dist/service-worker/index.js.map +1 -1
- package/native/abort.cjs +65 -0
- package/native/ai-queue.cjs +64 -0
- package/native/aistudio-build.cjs +21 -13
- package/native/aistudio-client.cjs +40 -20
- package/native/browser-lock.cjs +169 -0
- package/native/chatgpt-client.cjs +63 -30
- package/native/cli.cjs +947 -460
- package/native/client-transport.cjs +168 -0
- package/native/config.cjs +2 -2
- package/native/do-executor.cjs +25 -51
- package/native/do-parser.cjs +12 -0
- package/native/doctor.cjs +633 -0
- package/native/endpoint.cjs +174 -0
- package/native/file-transfer.cjs +734 -0
- package/native/gemini-client.cjs +244 -88
- package/native/grok-client.cjs +321 -212
- package/native/host-helpers.cjs +88 -16
- package/native/host-sessions.cjs +283 -0
- package/native/host.cjs +811 -616
- package/native/listener.cjs +20 -0
- package/native/mcp-server.cjs +60 -62
- package/native/network-export.cjs +113 -0
- package/native/perplexity-client.cjs +46 -17
- package/native/remote-auth.cjs +279 -0
- package/native/remote-transport.cjs +337 -0
- package/native/request-pending.cjs +148 -0
- package/native/socket-path.cjs +46 -0
- package/package.json +11 -9
- package/scripts/install-native-host.cjs +184 -51
- package/scripts/uninstall-native-host.cjs +93 -15
- package/skills/README.md +11 -5
- package/skills/deep-x-research/SKILL.md +106 -0
- package/skills/surf/SKILL.md +77 -22
- package/dist/content/accessibility-tree.js +0 -11
- package/dist/content/accessibility-tree.js.map +0 -1
- package/dist/content/visual-indicator.js +0 -111
- package/dist/content/visual-indicator.js.map +0 -1
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { abortableDelay, raceAbort, throwIfAborted } = require("./abort.cjs");
|
|
3
|
+
|
|
1
4
|
const CHATGPT_URL = "https://chatgpt.com/";
|
|
2
5
|
|
|
3
6
|
const SELECTORS = {
|
|
@@ -12,8 +15,8 @@ const SELECTORS = {
|
|
|
12
15
|
cloudflareScript: 'script[src*="/challenge-platform/"]',
|
|
13
16
|
};
|
|
14
17
|
|
|
15
|
-
function delay(ms) {
|
|
16
|
-
return
|
|
18
|
+
function delay(ms, signal) {
|
|
19
|
+
return abortableDelay(ms, signal);
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
function buildClickDispatcher() {
|
|
@@ -187,8 +190,10 @@ function isChatGPTResponseComplete(snapshot, stableCycles, stableMs) {
|
|
|
187
190
|
return stableCycles >= 6 && stableMs >= 1200;
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
async function evaluate(cdp, expression) {
|
|
193
|
+
async function evaluate(cdp, expression, signal) {
|
|
194
|
+
throwIfAborted(signal);
|
|
191
195
|
const result = await cdp(expression);
|
|
196
|
+
throwIfAborted(signal);
|
|
192
197
|
if (result.exceptionDetails) {
|
|
193
198
|
const desc = result.exceptionDetails.exception?.description ||
|
|
194
199
|
result.exceptionDetails.text ||
|
|
@@ -201,14 +206,15 @@ async function evaluate(cdp, expression) {
|
|
|
201
206
|
return result.result?.value;
|
|
202
207
|
}
|
|
203
208
|
|
|
204
|
-
async function waitForPageLoad(cdp, timeoutMs = 45000) {
|
|
209
|
+
async function waitForPageLoad(cdp, timeoutMs = 45000, signal) {
|
|
210
|
+
throwIfAborted(signal);
|
|
205
211
|
const deadline = Date.now() + timeoutMs;
|
|
206
212
|
while (Date.now() < deadline) {
|
|
207
213
|
const ready = await evaluate(cdp, "document.readyState");
|
|
208
214
|
if (ready === "complete" || ready === "interactive") {
|
|
209
215
|
return;
|
|
210
216
|
}
|
|
211
|
-
await delay(100);
|
|
217
|
+
await delay(100, signal);
|
|
212
218
|
}
|
|
213
219
|
throw new Error("Page did not load in time");
|
|
214
220
|
}
|
|
@@ -250,7 +256,8 @@ async function checkLoginStatus(cdp) {
|
|
|
250
256
|
return result || { status: 0 };
|
|
251
257
|
}
|
|
252
258
|
|
|
253
|
-
async function waitForPromptReady(cdp, timeoutMs = 30000) {
|
|
259
|
+
async function waitForPromptReady(cdp, timeoutMs = 30000, signal) {
|
|
260
|
+
throwIfAborted(signal);
|
|
254
261
|
const deadline = Date.now() + timeoutMs;
|
|
255
262
|
const selectors = JSON.stringify(SELECTORS.promptTextarea.split(", "));
|
|
256
263
|
while (Date.now() < deadline) {
|
|
@@ -268,7 +275,7 @@ async function waitForPromptReady(cdp, timeoutMs = 30000) {
|
|
|
268
275
|
})()`
|
|
269
276
|
);
|
|
270
277
|
if (found) return true;
|
|
271
|
-
await delay(200);
|
|
278
|
+
await delay(200, signal);
|
|
272
279
|
}
|
|
273
280
|
return false;
|
|
274
281
|
}
|
|
@@ -300,7 +307,8 @@ function resolveChatGPTModelMenuOption(items, desiredModel) {
|
|
|
300
307
|
}) || null;
|
|
301
308
|
}
|
|
302
309
|
|
|
303
|
-
async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
|
|
310
|
+
async function selectModel(cdp, desiredModel, timeoutMs = 8000, signal) {
|
|
311
|
+
throwIfAborted(signal);
|
|
304
312
|
const modelButton = await evaluate(
|
|
305
313
|
cdp,
|
|
306
314
|
`(() => {
|
|
@@ -319,7 +327,7 @@ async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
|
|
|
319
327
|
if (btn) dispatchClickSequence(btn);
|
|
320
328
|
})()`
|
|
321
329
|
);
|
|
322
|
-
await delay(300);
|
|
330
|
+
await delay(300, signal);
|
|
323
331
|
|
|
324
332
|
const normalizedModel = normalizeChatGPTModelChoice(desiredModel);
|
|
325
333
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -359,7 +367,7 @@ async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
|
|
|
359
367
|
if (item) dispatchClickSequence(item);
|
|
360
368
|
})()`
|
|
361
369
|
);
|
|
362
|
-
await delay(200);
|
|
370
|
+
await delay(200, signal);
|
|
363
371
|
return match.label;
|
|
364
372
|
}
|
|
365
373
|
|
|
@@ -377,13 +385,14 @@ async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
|
|
|
377
385
|
);
|
|
378
386
|
}
|
|
379
387
|
|
|
380
|
-
await delay(100);
|
|
388
|
+
await delay(100, signal);
|
|
381
389
|
}
|
|
382
390
|
|
|
383
391
|
throw new Error(`Model not found: ${desiredModel} (timeout)`);
|
|
384
392
|
}
|
|
385
393
|
|
|
386
|
-
async function typePrompt(cdp, inputCdp, prompt) {
|
|
394
|
+
async function typePrompt(cdp, inputCdp, prompt, signal) {
|
|
395
|
+
throwIfAborted(signal);
|
|
387
396
|
const selectors = JSON.stringify(SELECTORS.promptTextarea.split(", "));
|
|
388
397
|
const encodedPrompt = JSON.stringify(prompt);
|
|
389
398
|
const focused = await evaluate(
|
|
@@ -414,7 +423,7 @@ async function typePrompt(cdp, inputCdp, prompt) {
|
|
|
414
423
|
throw new Error("Failed to focus prompt textarea");
|
|
415
424
|
}
|
|
416
425
|
await inputCdp("Input.insertText", { text: prompt });
|
|
417
|
-
await delay(300);
|
|
426
|
+
await delay(300, signal);
|
|
418
427
|
const verified = await evaluate(
|
|
419
428
|
cdp,
|
|
420
429
|
`(() => {
|
|
@@ -447,7 +456,8 @@ async function typePrompt(cdp, inputCdp, prompt) {
|
|
|
447
456
|
}
|
|
448
457
|
}
|
|
449
458
|
|
|
450
|
-
async function clickSend(cdp, inputCdp) {
|
|
459
|
+
async function clickSend(cdp, inputCdp, signal) {
|
|
460
|
+
throwIfAborted(signal);
|
|
451
461
|
const selectors = SELECTORS.sendButton.split(", ");
|
|
452
462
|
const selectorsJson = JSON.stringify(selectors);
|
|
453
463
|
const deadline = Date.now() + 8000;
|
|
@@ -473,7 +483,7 @@ async function clickSend(cdp, inputCdp) {
|
|
|
473
483
|
);
|
|
474
484
|
if (result === "clicked") return true;
|
|
475
485
|
if (result === "missing") break;
|
|
476
|
-
await delay(100);
|
|
486
|
+
await delay(100, signal);
|
|
477
487
|
}
|
|
478
488
|
await inputCdp("Input.dispatchKeyEvent", {
|
|
479
489
|
type: "keyDown",
|
|
@@ -572,8 +582,10 @@ async function waitForResponse(
|
|
|
572
582
|
cdp,
|
|
573
583
|
timeoutMs = 2700000,
|
|
574
584
|
baselineAssistant,
|
|
575
|
-
baselineAssistantCount
|
|
585
|
+
baselineAssistantCount,
|
|
586
|
+
signal
|
|
576
587
|
) {
|
|
588
|
+
throwIfAborted(signal);
|
|
577
589
|
const deadline = Date.now() + timeoutMs;
|
|
578
590
|
let previousText = "";
|
|
579
591
|
let stableCycles = 0;
|
|
@@ -586,7 +598,7 @@ async function waitForResponse(
|
|
|
586
598
|
const snapshot = await readChatGPTResponseSnapshot(cdp);
|
|
587
599
|
|
|
588
600
|
if (!snapshot) {
|
|
589
|
-
await delay(400);
|
|
601
|
+
await delay(400, signal);
|
|
590
602
|
continue;
|
|
591
603
|
}
|
|
592
604
|
|
|
@@ -600,7 +612,7 @@ async function waitForResponse(
|
|
|
600
612
|
);
|
|
601
613
|
|
|
602
614
|
if (!hasNewAssistantContent) {
|
|
603
|
-
await delay(400);
|
|
615
|
+
await delay(400, signal);
|
|
604
616
|
continue;
|
|
605
617
|
}
|
|
606
618
|
|
|
@@ -628,7 +640,7 @@ async function waitForResponse(
|
|
|
628
640
|
};
|
|
629
641
|
}
|
|
630
642
|
|
|
631
|
-
await delay(400);
|
|
643
|
+
await delay(400, signal);
|
|
632
644
|
}
|
|
633
645
|
|
|
634
646
|
throw new Error("Response timeout");
|
|
@@ -645,27 +657,33 @@ async function query(options) {
|
|
|
645
657
|
closeTab,
|
|
646
658
|
cdpEvaluate,
|
|
647
659
|
cdpCommand,
|
|
660
|
+
uploadFile,
|
|
648
661
|
log = () => {},
|
|
662
|
+
signal,
|
|
649
663
|
} = options;
|
|
664
|
+
throwIfAborted(signal);
|
|
665
|
+
const guardedUploadFile = uploadFile
|
|
666
|
+
? (...args) => raceAbort(() => uploadFile(...args), signal)
|
|
667
|
+
: uploadFile;
|
|
650
668
|
const startTime = Date.now();
|
|
651
669
|
log("Starting ChatGPT query");
|
|
652
|
-
const { cookies } = await getCookies
|
|
670
|
+
const { cookies } = await raceAbort(getCookies, signal);
|
|
653
671
|
if (!hasRequiredCookies(cookies)) {
|
|
654
672
|
throw new Error("ChatGPT login required");
|
|
655
673
|
}
|
|
656
674
|
log(`Got ${cookies.length} cookies`);
|
|
657
|
-
const tabInfo = await createTab
|
|
675
|
+
const tabInfo = await raceAbort(createTab, signal);
|
|
658
676
|
const { tabId } = tabInfo;
|
|
659
677
|
if (!tabId) {
|
|
660
678
|
throw new Error("Failed to create ChatGPT tab");
|
|
661
679
|
}
|
|
662
680
|
log(`Created tab ${tabId}`);
|
|
663
681
|
|
|
664
|
-
const cdp = (expr) => cdpEvaluate(tabId, expr);
|
|
665
|
-
const inputCdp = (method, params) => cdpCommand(tabId, method, params);
|
|
682
|
+
const cdp = (expr) => raceAbort(() => cdpEvaluate(tabId, expr), signal);
|
|
683
|
+
const inputCdp = (method, params) => raceAbort(() => cdpCommand(tabId, method, params), signal);
|
|
666
684
|
|
|
667
685
|
try {
|
|
668
|
-
await waitForPageLoad(cdp);
|
|
686
|
+
await waitForPageLoad(cdp, 45000, signal);
|
|
669
687
|
log("Page loaded");
|
|
670
688
|
if (await isCloudflareBlocked(cdp)) {
|
|
671
689
|
throw new Error("Cloudflare challenge detected - complete in browser");
|
|
@@ -682,28 +700,43 @@ async function query(options) {
|
|
|
682
700
|
throw new Error("ChatGPT login required");
|
|
683
701
|
}
|
|
684
702
|
log("Login verified");
|
|
685
|
-
const promptReady = await waitForPromptReady(cdp);
|
|
703
|
+
const promptReady = await waitForPromptReady(cdp, 30000, signal);
|
|
686
704
|
if (!promptReady) {
|
|
687
705
|
throw new Error("Prompt textarea not ready");
|
|
688
706
|
}
|
|
689
707
|
log("Prompt ready");
|
|
690
708
|
if (model) {
|
|
691
|
-
const selectedLabel = await selectModel(cdp, model);
|
|
709
|
+
const selectedLabel = await selectModel(cdp, model, 8000, signal);
|
|
692
710
|
log(`Selected model: ${selectedLabel}`);
|
|
693
711
|
}
|
|
694
712
|
if (file) {
|
|
695
|
-
|
|
713
|
+
if (!uploadFile) {
|
|
714
|
+
throw new Error("ChatGPT file upload unavailable: native host did not provide upload callback");
|
|
715
|
+
}
|
|
716
|
+
const files = Array.isArray(file) ? file : [file];
|
|
717
|
+
const absFiles = files.map((filePath) => path.resolve(process.cwd(), filePath));
|
|
718
|
+
log(`Uploading ${absFiles.length} file(s) to ChatGPT...`);
|
|
719
|
+
const uploadResult = await guardedUploadFile(tabId, absFiles);
|
|
720
|
+
if (uploadResult?.error) {
|
|
721
|
+
throw new Error(`ChatGPT file upload failed: ${uploadResult.error}`);
|
|
722
|
+
}
|
|
723
|
+
if (!uploadResult?.success) {
|
|
724
|
+
throw new Error("ChatGPT file upload failed: upload did not report success");
|
|
725
|
+
}
|
|
726
|
+
log("File uploaded, waiting for ChatGPT attachment processing...");
|
|
727
|
+
await delay(1500, signal);
|
|
696
728
|
}
|
|
697
|
-
await typePrompt(cdp, inputCdp, prompt);
|
|
729
|
+
await typePrompt(cdp, inputCdp, prompt, signal);
|
|
698
730
|
log("Prompt typed");
|
|
699
731
|
const baseline = normalizeResponseSnapshot(await readChatGPTResponseSnapshot(cdp));
|
|
700
|
-
await clickSend(cdp, inputCdp);
|
|
732
|
+
await clickSend(cdp, inputCdp, signal);
|
|
701
733
|
log("Prompt sent, waiting for response...");
|
|
702
734
|
const response = await waitForResponse(
|
|
703
735
|
cdp,
|
|
704
736
|
timeout,
|
|
705
737
|
baseline.latestAssistant,
|
|
706
|
-
baseline.assistantCount
|
|
738
|
+
baseline.assistantCount,
|
|
739
|
+
signal
|
|
707
740
|
);
|
|
708
741
|
log(`Response received (${response.text.length} chars)`);
|
|
709
742
|
return {
|