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.
Files changed (45) hide show
  1. package/README.md +208 -13
  2. package/dist/content/index.js +116 -0
  3. package/dist/content/index.js.map +1 -0
  4. package/dist/manifest.json +2 -11
  5. package/dist/options/options.js +3 -3
  6. package/dist/options/options.js.map +1 -1
  7. package/dist/service-worker/index.js +261 -61
  8. package/dist/service-worker/index.js.map +1 -1
  9. package/native/abort.cjs +65 -0
  10. package/native/ai-queue.cjs +64 -0
  11. package/native/aistudio-build.cjs +21 -13
  12. package/native/aistudio-client.cjs +40 -20
  13. package/native/browser-lock.cjs +169 -0
  14. package/native/chatgpt-client.cjs +63 -30
  15. package/native/cli.cjs +947 -460
  16. package/native/client-transport.cjs +168 -0
  17. package/native/config.cjs +2 -2
  18. package/native/do-executor.cjs +25 -51
  19. package/native/do-parser.cjs +12 -0
  20. package/native/doctor.cjs +633 -0
  21. package/native/endpoint.cjs +174 -0
  22. package/native/file-transfer.cjs +734 -0
  23. package/native/gemini-client.cjs +244 -88
  24. package/native/grok-client.cjs +321 -212
  25. package/native/host-helpers.cjs +88 -16
  26. package/native/host-sessions.cjs +283 -0
  27. package/native/host.cjs +811 -616
  28. package/native/listener.cjs +20 -0
  29. package/native/mcp-server.cjs +60 -62
  30. package/native/network-export.cjs +113 -0
  31. package/native/perplexity-client.cjs +46 -17
  32. package/native/remote-auth.cjs +279 -0
  33. package/native/remote-transport.cjs +337 -0
  34. package/native/request-pending.cjs +148 -0
  35. package/native/socket-path.cjs +46 -0
  36. package/package.json +11 -9
  37. package/scripts/install-native-host.cjs +184 -51
  38. package/scripts/uninstall-native-host.cjs +93 -15
  39. package/skills/README.md +11 -5
  40. package/skills/deep-x-research/SKILL.md +106 -0
  41. package/skills/surf/SKILL.md +77 -22
  42. package/dist/content/accessibility-tree.js +0 -11
  43. package/dist/content/accessibility-tree.js.map +0 -1
  44. package/dist/content/visual-indicator.js +0 -111
  45. 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 new Promise((resolve) => setTimeout(resolve, ms));
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
- throw new Error("File upload not yet implemented");
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 {