findweb 0.1.2 → 0.1.4

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 (3) hide show
  1. package/README.md +7 -6
  2. package/dist/index.js +323 -157
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,7 +42,7 @@ findweb login
42
42
 
43
43
  - If the profile is already prepared, search runs immediately.
44
44
  - If the profile has not been prepared yet, `findweb` automatically opens the login flow first.
45
- - After you sign in and close the browser window, `findweb` writes a local prepared-profile marker so future searches can start immediately.
45
+ - After sign-in is detected, `findweb` saves a local prepared-profile marker and continues automatically.
46
46
  - By default, the profile directory is `${XDG_DATA_HOME:-~/.local/share}/findweb/chrome-profile` unless you pass `--userDataDir` or set `GOOGLE_SEARCH_USER_DATA_DIR`.
47
47
 
48
48
  In practice, the first search on a fresh profile behaves like this:
@@ -53,7 +53,7 @@ findweb "yc"
53
53
 
54
54
  1. detect missing prepared-profile marker
55
55
  2. open headed Chrome login flow
56
- 3. wait for you to sign in and close the browser
56
+ 3. wait for you to finish signing in
57
57
  4. continue the original search
58
58
 
59
59
  ## Options
@@ -72,9 +72,10 @@ findweb "yc"
72
72
 
73
73
  1. Launches system Chrome (`/Applications/Google Chrome.app`) with a free debugging port
74
74
  2. Connects via CDP using puppeteer-core
75
- 3. Loads the [Ghostery adblocker](https://github.com/ghostery/adblocker) engine programmatically on each page
76
- 4. Navigates to Google, submits the query through DOM manipulation, and extracts results from the rendered page
77
- 5. Returns results as plain text or JSON, then closes Chrome
75
+ 3. Reuses a background headless Chrome for the same profile when available
76
+ 4. Loads the [Ghostery adblocker](https://github.com/ghostery/adblocker) engine programmatically on each page
77
+ 5. Navigates directly to Google search results and extracts data from the rendered page
78
+ 6. Returns results as plain text or JSON, then disconnects from Chrome
78
79
 
79
80
  No Chromium download. No browser extension. No user confirmation.
80
81
 
@@ -98,7 +99,7 @@ You can trigger that ahead of time with:
98
99
  findweb login
99
100
  ```
100
101
 
101
- This opens a visible Chrome window with the Google sign-in page. After signing in, close the browser. The session is saved to the profile directory, and `findweb` records that the profile is ready for future searches.
102
+ This opens a visible Chrome window with the Google sign-in page. After sign-in is detected, `findweb` saves the session to the profile directory and records that the profile is ready for future searches.
102
103
 
103
104
  ## Output
104
105
 
package/dist/index.js CHANGED
@@ -66517,7 +66517,7 @@ var init_LaunchOptions = __esm(() => {
66517
66517
  });
66518
66518
 
66519
66519
  // src/index.ts
66520
- import process5 from "process";
66520
+ import process6 from "process";
66521
66521
 
66522
66522
  // node_modules/citty/dist/_chunks/libs/scule.mjs
66523
66523
  var NUMBER_CHAR_RE = /\d/;
@@ -66932,10 +66932,12 @@ async function runMain(cmd, opts = {}) {
66932
66932
  }
66933
66933
 
66934
66934
  // src/search/browser.ts
66935
+ import fs7 from "fs/promises";
66935
66936
  import { spawn as spawn3 } from "child_process";
66936
66937
  import http2 from "http";
66937
66938
  import os9 from "os";
66938
66939
  import path12 from "path";
66940
+ import process3 from "process";
66939
66941
 
66940
66942
  // node_modules/puppeteer-core/lib/esm/puppeteer/api/api.js
66941
66943
  init_Browser();
@@ -76219,9 +76221,42 @@ var puppeteer_core_default = puppeteer;
76219
76221
  // src/search/browser.ts
76220
76222
  var CHROME_BIN = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
76221
76223
  var DEFAULT_TIMEOUT_MS = 30000;
76224
+ var CDP_POLL_MS = 100;
76225
+ var REUSABLE_PAGE_URL = "about:blank";
76222
76226
  function createDebugServer() {
76223
76227
  return http2.createServer();
76224
76228
  }
76229
+ function persistentStatePath(userDataDir) {
76230
+ return path12.join(userDataDir, ".findweb-browser.json");
76231
+ }
76232
+ function isProcessRunning(pid) {
76233
+ try {
76234
+ process3.kill(pid, 0);
76235
+ return true;
76236
+ } catch {
76237
+ return false;
76238
+ }
76239
+ }
76240
+ async function readPersistentState(userDataDir) {
76241
+ try {
76242
+ const file = await fs7.readFile(persistentStatePath(userDataDir), "utf8");
76243
+ const parsed = JSON.parse(file);
76244
+ if (typeof parsed.pid !== "number" || typeof parsed.port !== "number") {
76245
+ return null;
76246
+ }
76247
+ return { pid: parsed.pid, port: parsed.port };
76248
+ } catch {
76249
+ return null;
76250
+ }
76251
+ }
76252
+ async function writePersistentState(userDataDir, state) {
76253
+ await fs7.mkdir(userDataDir, { recursive: true });
76254
+ await fs7.writeFile(persistentStatePath(userDataDir), `${JSON.stringify(state)}
76255
+ `, "utf8");
76256
+ }
76257
+ async function clearPersistentState(userDataDir) {
76258
+ await fs7.rm(persistentStatePath(userDataDir), { force: true });
76259
+ }
76225
76260
  function wait(delayMs) {
76226
76261
  return new Promise((resolve6) => setTimeout(resolve6, delayMs));
76227
76262
  }
@@ -76266,16 +76301,66 @@ async function isCdpReady(port) {
76266
76301
  async function waitForCdp(port, activeBrowser) {
76267
76302
  const deadline = Date.now() + DEFAULT_TIMEOUT_MS;
76268
76303
  while (Date.now() < deadline) {
76269
- if (activeBrowser.exitCode !== null) {
76304
+ if (activeBrowser?.exitCode !== null) {
76270
76305
  throw new Error(`Chrome exited before opening debugging port ${port}`);
76271
76306
  }
76272
76307
  if (await isCdpReady(port)) {
76273
76308
  return;
76274
76309
  }
76275
- await wait(250);
76310
+ await wait(CDP_POLL_MS);
76276
76311
  }
76277
76312
  throw new Error(`Chrome debugging port ${port} did not become ready in time`);
76278
76313
  }
76314
+ function reusablePageScore(url) {
76315
+ if (url.startsWith("https://www.google.com/search?")) {
76316
+ return 3;
76317
+ }
76318
+ if (url === REUSABLE_PAGE_URL) {
76319
+ return 2;
76320
+ }
76321
+ if (url.startsWith("https://www.google.com/")) {
76322
+ return 1;
76323
+ }
76324
+ return 0;
76325
+ }
76326
+ async function reusablePage(browser) {
76327
+ const pages = await browser.pages();
76328
+ const existing = pages.filter((page2) => !page2.isClosed()).sort((a, b) => reusablePageScore(b.url()) - reusablePageScore(a.url()))[0] ?? null;
76329
+ if (existing) {
76330
+ return existing;
76331
+ }
76332
+ const page = await browser.newPage().catch(() => null);
76333
+ if (!page) {
76334
+ return null;
76335
+ }
76336
+ await page.goto(REUSABLE_PAGE_URL, { waitUntil: "domcontentloaded" }).catch(() => {
76337
+ return;
76338
+ });
76339
+ return page;
76340
+ }
76341
+ async function connectToBrowser(port) {
76342
+ return puppeteer_core_default.connect({ browserURL: `http://127.0.0.1:${port}` });
76343
+ }
76344
+ async function connectPersistentBrowser(options) {
76345
+ const state = await readPersistentState(options.userDataDir);
76346
+ if (!state || !isProcessRunning(state.pid) || !await isCdpReady(state.port)) {
76347
+ await clearPersistentState(options.userDataDir);
76348
+ return null;
76349
+ }
76350
+ try {
76351
+ const browser = await connectToBrowser(state.port);
76352
+ return {
76353
+ browser,
76354
+ chromeProcess: null,
76355
+ initialPage: await reusablePage(browser),
76356
+ persistent: true,
76357
+ port: state.port
76358
+ };
76359
+ } catch {
76360
+ await clearPersistentState(options.userDataDir);
76361
+ return null;
76362
+ }
76363
+ }
76279
76364
  function createChromeArgs(options, port) {
76280
76365
  const args = [
76281
76366
  `--remote-debugging-port=${port}`,
@@ -76292,31 +76377,55 @@ function createChromeArgs(options, port) {
76292
76377
  return args;
76293
76378
  }
76294
76379
  async function launchSearchBrowser(options) {
76380
+ if (!options.headed) {
76381
+ const activeBrowser = await connectPersistentBrowser(options);
76382
+ if (activeBrowser) {
76383
+ return activeBrowser;
76384
+ }
76385
+ }
76295
76386
  const port = await findFreePort();
76296
76387
  const chromeProcess = spawn3(CHROME_BIN, createChromeArgs(options, port), {
76297
- stdio: ["ignore", "ignore", "pipe"]
76388
+ detached: !options.headed,
76389
+ stdio: options.headed ? ["ignore", "ignore", "pipe"] : "ignore"
76298
76390
  });
76391
+ if (!options.headed) {
76392
+ chromeProcess.unref();
76393
+ }
76299
76394
  await waitForCdp(port, chromeProcess);
76300
- const browser = await puppeteer_core_default.connect({ browserURL: `http://127.0.0.1:${port}` });
76301
- return { browser, chromeProcess, port };
76395
+ const browser = await connectToBrowser(port);
76396
+ const initialPage = await reusablePage(browser);
76397
+ if (!options.headed && typeof chromeProcess.pid === "number") {
76398
+ await writePersistentState(options.userDataDir, { pid: chromeProcess.pid, port });
76399
+ }
76400
+ return {
76401
+ browser,
76402
+ chromeProcess,
76403
+ initialPage,
76404
+ persistent: !options.headed,
76405
+ port
76406
+ };
76302
76407
  }
76303
76408
  async function closeSearchBrowser(activeBrowser) {
76409
+ if (activeBrowser.persistent) {
76410
+ activeBrowser.browser.disconnect();
76411
+ return;
76412
+ }
76304
76413
  await activeBrowser.browser.close().catch(() => {
76305
76414
  return;
76306
76415
  });
76307
- if (activeBrowser.chromeProcess.exitCode === null) {
76416
+ if (activeBrowser.chromeProcess?.exitCode === null) {
76308
76417
  activeBrowser.chromeProcess.kill("SIGTERM");
76309
76418
  }
76310
76419
  }
76311
76420
  function defaultXdgDataHome() {
76312
- const configured = process.env.XDG_DATA_HOME;
76421
+ const configured = process3.env.XDG_DATA_HOME;
76313
76422
  if (configured && path12.isAbsolute(configured)) {
76314
76423
  return configured;
76315
76424
  }
76316
76425
  return path12.join(os9.homedir(), ".local", "share");
76317
76426
  }
76318
76427
  function defaultUserDataDir() {
76319
- const configured = process.env.GOOGLE_SEARCH_USER_DATA_DIR;
76428
+ const configured = process3.env.GOOGLE_SEARCH_USER_DATA_DIR;
76320
76429
  if (configured) {
76321
76430
  return configured;
76322
76431
  }
@@ -76326,6 +76435,17 @@ function defaultUserDataDir() {
76326
76435
  // src/search/page.ts
76327
76436
  var DEFAULT_TIMEOUT_MS2 = 30000;
76328
76437
  var DEFAULT_PWS = "0";
76438
+ var SEARCH_READY_SCRIPT = `(() => {
76439
+ if (window.location.pathname.includes('/sorry/')) {
76440
+ return true;
76441
+ }
76442
+
76443
+ if (document.querySelector('a h3')) {
76444
+ return true;
76445
+ }
76446
+
76447
+ return window.location.pathname === '/search' && document.readyState !== 'loading' && Boolean(document.querySelector('textarea[name="q"], input[name="q"]'));
76448
+ })()`;
76329
76449
  function userAgent() {
76330
76450
  return [
76331
76451
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
@@ -76343,9 +76463,6 @@ function acceptLanguage(lang) {
76343
76463
  }
76344
76464
  return `${lang};q=1.0,en;q=0.8`;
76345
76465
  }
76346
- function createGoogleHomeUrl(lang, gl) {
76347
- return `https://www.google.com/?hl=${encodeURIComponent(lang)}&gl=${encodeURIComponent(gl)}&pws=${DEFAULT_PWS}`;
76348
- }
76349
76466
  function createGoogleSearchUrl(query, lang, gl) {
76350
76467
  return [
76351
76468
  "https://www.google.com/search",
@@ -76355,62 +76472,6 @@ function createGoogleSearchUrl(query, lang, gl) {
76355
76472
  `&q=${encodeURIComponent(query)}`
76356
76473
  ].join("");
76357
76474
  }
76358
- function createSubmitSearchScript(query, lang, gl) {
76359
- return `(() => {
76360
- const value = ${JSON.stringify(query)};
76361
- const lang = ${JSON.stringify(lang)};
76362
- const gl = ${JSON.stringify(gl)};
76363
- const pws = ${JSON.stringify(DEFAULT_PWS)};
76364
- const el = document.querySelector('textarea[name="q"], input[name="q"]');
76365
- if (!el) {
76366
- throw new Error("Google search input not found");
76367
- }
76368
-
76369
- const setHidden = (form, name, hiddenValue) => {
76370
- let input = form.querySelector('input[name="' + name + '"]');
76371
- if (!input) {
76372
- input = document.createElement("input");
76373
- input.setAttribute("type", "hidden");
76374
- input.setAttribute("name", name);
76375
- form.appendChild(input);
76376
- }
76377
- input.setAttribute("value", hiddenValue);
76378
- };
76379
-
76380
- el.focus();
76381
- const proto = Object.getPrototypeOf(el);
76382
- const descriptor = proto ? Object.getOwnPropertyDescriptor(proto, "value") : undefined;
76383
- if (descriptor && typeof descriptor.set === "function") {
76384
- descriptor.set.call(el, value);
76385
- } else {
76386
- el.value = value;
76387
- }
76388
-
76389
- el.dispatchEvent(new Event("input", { bubbles: true }));
76390
- el.dispatchEvent(new Event("change", { bubbles: true }));
76391
-
76392
- const form = el.form || document.querySelector('form[action="/search"]');
76393
- if (form && typeof form.requestSubmit === "function") {
76394
- setHidden(form, "hl", lang);
76395
- setHidden(form, "gl", gl);
76396
- setHidden(form, "pws", pws);
76397
- form.requestSubmit();
76398
- return;
76399
- }
76400
-
76401
- if (form) {
76402
- const action = new URL(form.getAttribute("action") || "/search", window.location.origin);
76403
- action.searchParams.set("hl", lang);
76404
- action.searchParams.set("gl", gl);
76405
- action.searchParams.set("pws", pws);
76406
- form.setAttribute("action", action.toString());
76407
- form.submit();
76408
- return;
76409
- }
76410
-
76411
- window.location.href = ${JSON.stringify(createGoogleSearchUrl(query, lang, gl))};
76412
- })()`;
76413
- }
76414
76475
  function createExtractResultsScript(limit) {
76415
76476
  return `(() => {
76416
76477
  const max = ${JSON.stringify(limit)};
@@ -76459,40 +76520,77 @@ async function preparePage(page, lang) {
76459
76520
  "accept-language": acceptLanguage(lang)
76460
76521
  });
76461
76522
  }
76462
- async function gotoGoogleHome(page, lang, gl) {
76463
- await page.goto(createGoogleHomeUrl(lang, gl), { waitUntil: "networkidle2" });
76464
- await page.waitForSelector('textarea[name="q"], input[name="q"]');
76465
- }
76466
- async function submitSearch(page, query, lang, gl) {
76467
- await Promise.all([
76468
- page.waitForNavigation({ waitUntil: "networkidle2" }),
76469
- page.evaluate(createSubmitSearchScript(query, lang, gl))
76470
- ]);
76523
+ async function gotoGoogleSearchResults(page, query, lang, gl) {
76524
+ const searchUrl = createGoogleSearchUrl(query, lang, gl);
76525
+ if (page.url() !== searchUrl) {
76526
+ await page.goto(searchUrl, { waitUntil: "domcontentloaded" });
76527
+ }
76528
+ await page.waitForFunction(SEARCH_READY_SCRIPT, { timeout: DEFAULT_TIMEOUT_MS2 });
76471
76529
  }
76472
76530
  async function extractResults(page, limit) {
76473
76531
  return page.evaluate(createExtractResultsScript(limit));
76474
76532
  }
76475
- async function waitForIdle(page) {
76476
- await page.waitForNetworkIdle({ idleTime: 700, timeout: DEFAULT_TIMEOUT_MS2 }).catch(() => {
76477
- return;
76478
- });
76479
- }
76480
76533
 
76481
76534
  // src/search/search.ts
76535
+ var LOGIN_POLL_MS = 250;
76536
+ function wait2(delayMs) {
76537
+ return new Promise((resolve6) => setTimeout(resolve6, delayMs));
76538
+ }
76539
+ function hostnameFromUrl(url) {
76540
+ try {
76541
+ return new URL(url).hostname.toLowerCase();
76542
+ } catch {
76543
+ return null;
76544
+ }
76545
+ }
76546
+ function isCompletedLoginUrl(url) {
76547
+ const hostname = hostnameFromUrl(url);
76548
+ return hostname !== null && hostname !== "accounts.google.com" && (hostname === "google.com" || hostname.endsWith(".google.com"));
76549
+ }
76550
+ function hasCompletedLoginPage(urls) {
76551
+ return urls.some((url) => isCompletedLoginUrl(url));
76552
+ }
76553
+ async function pageUrls(browser) {
76554
+ const pages = await browser.pages();
76555
+ return pages.map((page) => page.url());
76556
+ }
76557
+ async function waitForCompletedLogin(loginPage, browser) {
76558
+ let lastSeenUrls = [loginPage.url()];
76559
+ while (true) {
76560
+ if (browser.connected) {
76561
+ lastSeenUrls = await pageUrls(browser).catch(() => lastSeenUrls);
76562
+ if (hasCompletedLoginPage(lastSeenUrls)) {
76563
+ return;
76564
+ }
76565
+ }
76566
+ if (!browser.connected || loginPage.isClosed()) {
76567
+ if (hasCompletedLoginPage(lastSeenUrls)) {
76568
+ return;
76569
+ }
76570
+ throw new Error("Google login was not completed.");
76571
+ }
76572
+ await wait2(LOGIN_POLL_MS);
76573
+ }
76574
+ }
76482
76575
  function isSorryPage(url) {
76483
76576
  return url.includes("/sorry/");
76484
76577
  }
76485
76578
  function toErrorMessage(error) {
76486
76579
  return error instanceof Error ? error.message : String(error);
76487
76580
  }
76581
+ function isInterruptedLoginError(error, loginPage, browser) {
76582
+ if (!browser.connected || loginPage?.isClosed()) {
76583
+ return true;
76584
+ }
76585
+ const message = toErrorMessage(error);
76586
+ return message.includes("Navigating frame was detached") || message.includes("LifecycleWatcher disposed") || message.includes("Target closed") || message.includes("Session closed");
76587
+ }
76488
76588
  async function searchQuery(options) {
76489
- const page = await options.browser.newPage();
76589
+ const page = options.page ?? await options.browser.newPage();
76490
76590
  try {
76491
76591
  await preparePage(page, options.lang);
76492
76592
  await options.blocker.enableBlockingInPage(page);
76493
- await gotoGoogleHome(page, options.lang, options.gl);
76494
- await waitForIdle(page);
76495
- await submitSearch(page, options.query, options.lang, options.gl);
76593
+ await gotoGoogleSearchResults(page, options.query, options.lang, options.gl);
76496
76594
  if (isSorryPage(page.url())) {
76497
76595
  throw new Error("Google returned a /sorry/ page for this profile/IP. Re-run later or use a logged-in profile.");
76498
76596
  }
@@ -76501,9 +76599,28 @@ async function searchQuery(options) {
76501
76599
  await options.blocker.disableBlockingInPage(page).catch(() => {
76502
76600
  return;
76503
76601
  });
76504
- await page.close().catch(() => {
76505
- return;
76602
+ if (!options.keepOpen) {
76603
+ await page.close().catch(() => {
76604
+ return;
76605
+ });
76606
+ }
76607
+ }
76608
+ }
76609
+ async function runSingleQuery(browser, options) {
76610
+ try {
76611
+ const results = await searchQuery({
76612
+ blocker: options.blocker,
76613
+ browser,
76614
+ gl: options.gl,
76615
+ keepOpen: Boolean(options.page),
76616
+ lang: options.lang,
76617
+ num: options.num,
76618
+ page: options.page ?? undefined,
76619
+ query: options.query
76506
76620
  });
76621
+ return { error: null, query: options.query, results };
76622
+ } catch (error) {
76623
+ return { error: toErrorMessage(error), query: options.query, results: [] };
76507
76624
  }
76508
76625
  }
76509
76626
  async function runBatchWorker(browser, options) {
@@ -76529,51 +76646,76 @@ async function runBatchWorker(browser, options) {
76529
76646
  }
76530
76647
  }
76531
76648
  }
76532
- async function searchQueriesInTabs(browser, blocker, queries, options) {
76649
+ async function searchQueriesInTabs(browser, blocker, queries, options, initialPage = null) {
76533
76650
  const concurrency = Math.max(1, Math.min(options.parallel, queries.length));
76534
76651
  const results = new Array(queries.length);
76535
76652
  const cursor = { value: 0 };
76536
- await Promise.all(Array.from({ length: concurrency }, () => runBatchWorker(browser, {
76653
+ const initialTask = initialPage && queries[0] ? runSingleQuery(browser, {
76537
76654
  blocker,
76538
- cursor,
76539
76655
  gl: options.gl,
76540
76656
  lang: options.lang,
76541
76657
  num: options.num,
76658
+ page: initialPage,
76542
76659
  parallel: options.parallel,
76543
- queries,
76544
- results
76545
- })));
76660
+ query: queries[0]
76661
+ }).then((outcome) => {
76662
+ results[0] = outcome;
76663
+ }) : null;
76664
+ cursor.value = initialTask ? 1 : 0;
76665
+ await Promise.all([
76666
+ ...initialTask ? [initialTask] : [],
76667
+ ...Array.from({ length: Math.max(0, concurrency - (initialTask ? 1 : 0)) }, () => runBatchWorker(browser, {
76668
+ blocker,
76669
+ cursor,
76670
+ gl: options.gl,
76671
+ lang: options.lang,
76672
+ num: options.num,
76673
+ parallel: options.parallel,
76674
+ queries,
76675
+ results
76676
+ }))
76677
+ ]);
76546
76678
  return results;
76547
76679
  }
76548
76680
  async function runLoginSession(options) {
76549
- const loginPage = await options.browser.newPage();
76550
- await preparePage(loginPage, options.lang);
76551
- await loginPage.goto(`https://accounts.google.com/ServiceLogin?continue=${encodeURIComponent(`https://www.google.com/?hl=${options.lang}&gl=${options.gl}&pws=0`)}&hl=${encodeURIComponent(options.lang)}`, { waitUntil: "networkidle2" });
76552
- await new Promise((resolve6) => {
76553
- const done = () => resolve6();
76554
- options.browser.once("disconnected", done);
76555
- process.once("SIGINT", async () => {
76556
- await options.browser.close().catch(() => {
76557
- return;
76558
- });
76559
- done();
76681
+ let loginPage = null;
76682
+ const handleSigint = () => {
76683
+ options.browser.close().catch(() => {
76684
+ return;
76560
76685
  });
76561
- });
76686
+ };
76687
+ process.once("SIGINT", handleSigint);
76688
+ try {
76689
+ loginPage = await options.browser.newPage();
76690
+ await preparePage(loginPage, options.lang);
76691
+ await loginPage.goto(`https://accounts.google.com/ServiceLogin?continue=${encodeURIComponent(`https://www.google.com/?hl=${options.lang}&gl=${options.gl}&pws=0`)}&hl=${encodeURIComponent(options.lang)}`, { waitUntil: "networkidle2" });
76692
+ await waitForCompletedLogin(loginPage, options.browser);
76693
+ } catch (error) {
76694
+ if (isInterruptedLoginError(error, loginPage, options.browser)) {
76695
+ throw new Error("Google login was not completed.");
76696
+ }
76697
+ throw error;
76698
+ } finally {
76699
+ process.off("SIGINT", handleSigint);
76700
+ await loginPage?.close().catch(() => {
76701
+ return;
76702
+ });
76703
+ }
76562
76704
  }
76563
76705
 
76564
76706
  // src/cli/profile.ts
76565
- import fs7 from "fs/promises";
76707
+ import fs8 from "fs/promises";
76566
76708
  import path13 from "path";
76567
76709
  var PROFILE_READY_MARKER = ".findweb-profile-ready";
76568
76710
  async function ensureProfileDir(dirPath) {
76569
- await fs7.mkdir(dirPath, { recursive: true });
76711
+ await fs8.mkdir(dirPath, { recursive: true });
76570
76712
  }
76571
76713
  function readyMarkerPath(userDataDir) {
76572
76714
  return path13.join(userDataDir, PROFILE_READY_MARKER);
76573
76715
  }
76574
76716
  async function hasPreparedProfile(userDataDir) {
76575
76717
  try {
76576
- await fs7.access(readyMarkerPath(userDataDir));
76718
+ await fs8.access(readyMarkerPath(userDataDir));
76577
76719
  return true;
76578
76720
  } catch {
76579
76721
  return false;
@@ -76581,15 +76723,15 @@ async function hasPreparedProfile(userDataDir) {
76581
76723
  }
76582
76724
  async function markProfilePrepared(userDataDir) {
76583
76725
  await ensureProfileDir(userDataDir);
76584
- await fs7.writeFile(readyMarkerPath(userDataDir), `${new Date().toISOString()}
76726
+ await fs8.writeFile(readyMarkerPath(userDataDir), `${new Date().toISOString()}
76585
76727
  `, "utf8");
76586
76728
  }
76587
76729
 
76588
76730
  // src/cli/flows/login.ts
76589
76731
  function printLoginInstructions(userDataDir) {
76590
76732
  console.log(`Login browser launched with profile: ${userDataDir}`);
76591
- console.log("Sign in to Google to prepare this profile for future searches.");
76592
- console.log("Close the browser window when you are done.");
76733
+ console.log("Complete Google sign-in to prepare this profile for future searches.");
76734
+ console.log("The session is saved automatically once sign-in is detected.");
76593
76735
  }
76594
76736
  async function runInteractiveLoginFlow(options) {
76595
76737
  await ensureProfileDir(options.userDataDir);
@@ -76881,7 +77023,7 @@ __export(exports_core2, {
76881
77023
  safeDecode: () => safeDecode,
76882
77024
  registry: () => registry,
76883
77025
  regexes: () => exports_regexes,
76884
- process: () => process3,
77026
+ process: () => process4,
76885
77027
  prettifyError: () => prettifyError,
76886
77028
  parseAsync: () => parseAsync,
76887
77029
  parse: () => parse,
@@ -87345,7 +87487,7 @@ function initializeContext(params) {
87345
87487
  external: params?.external ?? undefined
87346
87488
  };
87347
87489
  }
87348
- function process3(schema, ctx, _params = { path: [], schemaPath: [] }) {
87490
+ function process4(schema, ctx, _params = { path: [], schemaPath: [] }) {
87349
87491
  var _a5;
87350
87492
  const def = schema._zod.def;
87351
87493
  const seen = ctx.seen.get(schema);
@@ -87382,7 +87524,7 @@ function process3(schema, ctx, _params = { path: [], schemaPath: [] }) {
87382
87524
  if (parent) {
87383
87525
  if (!result.ref)
87384
87526
  result.ref = parent;
87385
- process3(parent, ctx, params);
87527
+ process4(parent, ctx, params);
87386
87528
  ctx.seen.get(parent).isParent = true;
87387
87529
  }
87388
87530
  }
@@ -87658,14 +87800,14 @@ function isTransforming(_schema, _ctx) {
87658
87800
  }
87659
87801
  var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
87660
87802
  const ctx = initializeContext({ ...params, processors });
87661
- process3(schema, ctx);
87803
+ process4(schema, ctx);
87662
87804
  extractDefs(ctx, schema);
87663
87805
  return finalize(ctx, schema);
87664
87806
  };
87665
87807
  var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
87666
87808
  const { libraryOptions, target } = params ?? {};
87667
87809
  const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
87668
- process3(schema, ctx);
87810
+ process4(schema, ctx);
87669
87811
  extractDefs(ctx, schema);
87670
87812
  return finalize(ctx, schema);
87671
87813
  };
@@ -87916,7 +88058,7 @@ var arrayProcessor = (schema, ctx, _json, params) => {
87916
88058
  if (typeof maximum === "number")
87917
88059
  json.maxItems = maximum;
87918
88060
  json.type = "array";
87919
- json.items = process3(def.element, ctx, { ...params, path: [...params.path, "items"] });
88061
+ json.items = process4(def.element, ctx, { ...params, path: [...params.path, "items"] });
87920
88062
  };
87921
88063
  var objectProcessor = (schema, ctx, _json, params) => {
87922
88064
  const json = _json;
@@ -87925,7 +88067,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
87925
88067
  json.properties = {};
87926
88068
  const shape = def.shape;
87927
88069
  for (const key in shape) {
87928
- json.properties[key] = process3(shape[key], ctx, {
88070
+ json.properties[key] = process4(shape[key], ctx, {
87929
88071
  ...params,
87930
88072
  path: [...params.path, "properties", key]
87931
88073
  });
@@ -87948,7 +88090,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
87948
88090
  if (ctx.io === "output")
87949
88091
  json.additionalProperties = false;
87950
88092
  } else if (def.catchall) {
87951
- json.additionalProperties = process3(def.catchall, ctx, {
88093
+ json.additionalProperties = process4(def.catchall, ctx, {
87952
88094
  ...params,
87953
88095
  path: [...params.path, "additionalProperties"]
87954
88096
  });
@@ -87957,7 +88099,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
87957
88099
  var unionProcessor = (schema, ctx, json, params) => {
87958
88100
  const def = schema._zod.def;
87959
88101
  const isExclusive = def.inclusive === false;
87960
- const options = def.options.map((x, i) => process3(x, ctx, {
88102
+ const options = def.options.map((x, i) => process4(x, ctx, {
87961
88103
  ...params,
87962
88104
  path: [...params.path, isExclusive ? "oneOf" : "anyOf", i]
87963
88105
  }));
@@ -87969,11 +88111,11 @@ var unionProcessor = (schema, ctx, json, params) => {
87969
88111
  };
87970
88112
  var intersectionProcessor = (schema, ctx, json, params) => {
87971
88113
  const def = schema._zod.def;
87972
- const a = process3(def.left, ctx, {
88114
+ const a = process4(def.left, ctx, {
87973
88115
  ...params,
87974
88116
  path: [...params.path, "allOf", 0]
87975
88117
  });
87976
- const b = process3(def.right, ctx, {
88118
+ const b = process4(def.right, ctx, {
87977
88119
  ...params,
87978
88120
  path: [...params.path, "allOf", 1]
87979
88121
  });
@@ -87990,11 +88132,11 @@ var tupleProcessor = (schema, ctx, _json, params) => {
87990
88132
  json.type = "array";
87991
88133
  const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
87992
88134
  const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
87993
- const prefixItems = def.items.map((x, i) => process3(x, ctx, {
88135
+ const prefixItems = def.items.map((x, i) => process4(x, ctx, {
87994
88136
  ...params,
87995
88137
  path: [...params.path, prefixPath, i]
87996
88138
  }));
87997
- const rest = def.rest ? process3(def.rest, ctx, {
88139
+ const rest = def.rest ? process4(def.rest, ctx, {
87998
88140
  ...params,
87999
88141
  path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
88000
88142
  }) : null;
@@ -88034,7 +88176,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
88034
88176
  const keyBag = keyType._zod.bag;
88035
88177
  const patterns = keyBag?.patterns;
88036
88178
  if (def.mode === "loose" && patterns && patterns.size > 0) {
88037
- const valueSchema = process3(def.valueType, ctx, {
88179
+ const valueSchema = process4(def.valueType, ctx, {
88038
88180
  ...params,
88039
88181
  path: [...params.path, "patternProperties", "*"]
88040
88182
  });
@@ -88044,12 +88186,12 @@ var recordProcessor = (schema, ctx, _json, params) => {
88044
88186
  }
88045
88187
  } else {
88046
88188
  if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
88047
- json.propertyNames = process3(def.keyType, ctx, {
88189
+ json.propertyNames = process4(def.keyType, ctx, {
88048
88190
  ...params,
88049
88191
  path: [...params.path, "propertyNames"]
88050
88192
  });
88051
88193
  }
88052
- json.additionalProperties = process3(def.valueType, ctx, {
88194
+ json.additionalProperties = process4(def.valueType, ctx, {
88053
88195
  ...params,
88054
88196
  path: [...params.path, "additionalProperties"]
88055
88197
  });
@@ -88064,7 +88206,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
88064
88206
  };
88065
88207
  var nullableProcessor = (schema, ctx, json, params) => {
88066
88208
  const def = schema._zod.def;
88067
- const inner = process3(def.innerType, ctx, params);
88209
+ const inner = process4(def.innerType, ctx, params);
88068
88210
  const seen = ctx.seen.get(schema);
88069
88211
  if (ctx.target === "openapi-3.0") {
88070
88212
  seen.ref = def.innerType;
@@ -88075,20 +88217,20 @@ var nullableProcessor = (schema, ctx, json, params) => {
88075
88217
  };
88076
88218
  var nonoptionalProcessor = (schema, ctx, _json, params) => {
88077
88219
  const def = schema._zod.def;
88078
- process3(def.innerType, ctx, params);
88220
+ process4(def.innerType, ctx, params);
88079
88221
  const seen = ctx.seen.get(schema);
88080
88222
  seen.ref = def.innerType;
88081
88223
  };
88082
88224
  var defaultProcessor = (schema, ctx, json, params) => {
88083
88225
  const def = schema._zod.def;
88084
- process3(def.innerType, ctx, params);
88226
+ process4(def.innerType, ctx, params);
88085
88227
  const seen = ctx.seen.get(schema);
88086
88228
  seen.ref = def.innerType;
88087
88229
  json.default = JSON.parse(JSON.stringify(def.defaultValue));
88088
88230
  };
88089
88231
  var prefaultProcessor = (schema, ctx, json, params) => {
88090
88232
  const def = schema._zod.def;
88091
- process3(def.innerType, ctx, params);
88233
+ process4(def.innerType, ctx, params);
88092
88234
  const seen = ctx.seen.get(schema);
88093
88235
  seen.ref = def.innerType;
88094
88236
  if (ctx.io === "input")
@@ -88096,7 +88238,7 @@ var prefaultProcessor = (schema, ctx, json, params) => {
88096
88238
  };
88097
88239
  var catchProcessor = (schema, ctx, json, params) => {
88098
88240
  const def = schema._zod.def;
88099
- process3(def.innerType, ctx, params);
88241
+ process4(def.innerType, ctx, params);
88100
88242
  const seen = ctx.seen.get(schema);
88101
88243
  seen.ref = def.innerType;
88102
88244
  let catchValue;
@@ -88110,32 +88252,32 @@ var catchProcessor = (schema, ctx, json, params) => {
88110
88252
  var pipeProcessor = (schema, ctx, _json, params) => {
88111
88253
  const def = schema._zod.def;
88112
88254
  const innerType = ctx.io === "input" ? def.in._zod.def.type === "transform" ? def.out : def.in : def.out;
88113
- process3(innerType, ctx, params);
88255
+ process4(innerType, ctx, params);
88114
88256
  const seen = ctx.seen.get(schema);
88115
88257
  seen.ref = innerType;
88116
88258
  };
88117
88259
  var readonlyProcessor = (schema, ctx, json, params) => {
88118
88260
  const def = schema._zod.def;
88119
- process3(def.innerType, ctx, params);
88261
+ process4(def.innerType, ctx, params);
88120
88262
  const seen = ctx.seen.get(schema);
88121
88263
  seen.ref = def.innerType;
88122
88264
  json.readOnly = true;
88123
88265
  };
88124
88266
  var promiseProcessor = (schema, ctx, _json, params) => {
88125
88267
  const def = schema._zod.def;
88126
- process3(def.innerType, ctx, params);
88268
+ process4(def.innerType, ctx, params);
88127
88269
  const seen = ctx.seen.get(schema);
88128
88270
  seen.ref = def.innerType;
88129
88271
  };
88130
88272
  var optionalProcessor = (schema, ctx, _json, params) => {
88131
88273
  const def = schema._zod.def;
88132
- process3(def.innerType, ctx, params);
88274
+ process4(def.innerType, ctx, params);
88133
88275
  const seen = ctx.seen.get(schema);
88134
88276
  seen.ref = def.innerType;
88135
88277
  };
88136
88278
  var lazyProcessor = (schema, ctx, _json, params) => {
88137
88279
  const innerType = schema._zod.innerType;
88138
- process3(innerType, ctx, params);
88280
+ process4(innerType, ctx, params);
88139
88281
  const seen = ctx.seen.get(schema);
88140
88282
  seen.ref = innerType;
88141
88283
  };
@@ -88187,7 +88329,7 @@ function toJSONSchema(input2, params) {
88187
88329
  const defs = {};
88188
88330
  for (const entry of registry2._idmap.entries()) {
88189
88331
  const [_2, schema] = entry;
88190
- process3(schema, ctx2);
88332
+ process4(schema, ctx2);
88191
88333
  }
88192
88334
  const schemas = {};
88193
88335
  const external = {
@@ -88210,7 +88352,7 @@ function toJSONSchema(input2, params) {
88210
88352
  return { schemas };
88211
88353
  }
88212
88354
  const ctx = initializeContext({ ...params, processors: allProcessors });
88213
- process3(input2, ctx);
88355
+ process4(input2, ctx);
88214
88356
  extractDefs(ctx, input2);
88215
88357
  return finalize(ctx, input2);
88216
88358
  }
@@ -88256,7 +88398,7 @@ class JSONSchemaGenerator {
88256
88398
  });
88257
88399
  }
88258
88400
  process(schema, _params = { path: [], schemaPath: [] }) {
88259
- return process3(schema, this.ctx, _params);
88401
+ return process4(schema, this.ctx, _params);
88260
88402
  }
88261
88403
  emit(schema, _params) {
88262
88404
  if (_params) {
@@ -90229,6 +90371,10 @@ function createLoginArgs() {
90229
90371
  }
90230
90372
  };
90231
90373
  }
90374
+ function printCommandError(error48) {
90375
+ const message = error48 instanceof Error ? error48.message : String(error48);
90376
+ console.error(`ERROR: ${message}`);
90377
+ }
90232
90378
  async function runLogin(args) {
90233
90379
  const options = normalizeLoginOptions({
90234
90380
  gl: typeof args.gl === "string" ? args.gl : undefined,
@@ -90242,20 +90388,25 @@ function createLoginCommand(commandName = "findweb login") {
90242
90388
  meta: { name: commandName, description: "Open a reusable Google sign-in session." },
90243
90389
  args: createLoginArgs(),
90244
90390
  async run({ args }) {
90245
- await runLogin({
90246
- gl: args.gl,
90247
- lang: args.lang,
90248
- userDataDir: args.userDataDir
90249
- });
90391
+ try {
90392
+ await runLogin({
90393
+ gl: args.gl,
90394
+ lang: args.lang,
90395
+ userDataDir: args.userDataDir
90396
+ });
90397
+ } catch (error48) {
90398
+ printCommandError(error48);
90399
+ process.exitCode = 1;
90400
+ }
90250
90401
  }
90251
90402
  });
90252
90403
  }
90253
90404
 
90254
90405
  // src/cli/commands/search.ts
90255
- import process4 from "process";
90406
+ import process5 from "process";
90256
90407
 
90257
90408
  // src/search/blocker.ts
90258
- import fs8 from "fs/promises";
90409
+ import fs9 from "fs/promises";
90259
90410
  import os10 from "os";
90260
90411
  import path15 from "path";
90261
90412
 
@@ -101317,17 +101468,17 @@ function defaultCacheDir() {
101317
101468
  return process.env.GOOGLE_SEARCH_CACHE_DIR ?? path15.join(os10.homedir(), ".cache", "google-search");
101318
101469
  }
101319
101470
  async function readCache(filePath) {
101320
- return fs8.readFile(filePath);
101471
+ return fs9.readFile(filePath);
101321
101472
  }
101322
101473
  async function writeCache(filePath, buffer) {
101323
- await fs8.mkdir(path15.dirname(filePath), { recursive: true });
101324
- await fs8.writeFile(filePath, buffer);
101474
+ await fs9.mkdir(path15.dirname(filePath), { recursive: true });
101475
+ await fs9.writeFile(filePath, buffer);
101325
101476
  }
101326
101477
  async function loadBlocker() {
101327
101478
  if (!blockerPromise) {
101328
101479
  blockerPromise = (async () => {
101329
101480
  const cacheDir = defaultCacheDir();
101330
- await fs8.mkdir(cacheDir, { recursive: true });
101481
+ await fs9.mkdir(cacheDir, { recursive: true });
101331
101482
  return PuppeteerBlocker.fromPrebuiltAdsAndTracking(globalThis.fetch.bind(globalThis), {
101332
101483
  path: path15.join(cacheDir, "ghostery-engine.bin"),
101333
101484
  read: readCache,
@@ -101432,6 +101583,10 @@ var searchArgs = {
101432
101583
  description: "Print JSON output"
101433
101584
  }
101434
101585
  };
101586
+ function printCommandError2(error49) {
101587
+ const message = error49 instanceof Error ? error49.message : String(error49);
101588
+ console.error(`ERROR: ${message}`);
101589
+ }
101435
101590
  async function runSearch(args) {
101436
101591
  const options = normalizeSearchOptions({
101437
101592
  _: args._,
@@ -101465,9 +101620,9 @@ async function runSearch(args) {
101465
101620
  lang: options.lang,
101466
101621
  num: options.num,
101467
101622
  parallel: options.parallel
101468
- });
101623
+ }, activeBrowser.initialPage);
101469
101624
  printResults(options.json, outcomes);
101470
- process4.exitCode = exitCodeForOutcomes(outcomes);
101625
+ process5.exitCode = exitCodeForOutcomes(outcomes);
101471
101626
  } finally {
101472
101627
  await closeSearchBrowser(activeBrowser);
101473
101628
  }
@@ -101480,7 +101635,12 @@ function createSearchCommand(commandName = "findweb") {
101480
101635
  },
101481
101636
  args: searchArgs,
101482
101637
  async run({ args }) {
101483
- await runSearch(args);
101638
+ try {
101639
+ await runSearch(args);
101640
+ } catch (error49) {
101641
+ printCommandError2(error49);
101642
+ process5.exitCode = 1;
101643
+ }
101484
101644
  }
101485
101645
  });
101486
101646
  }
@@ -101548,7 +101708,7 @@ function showRootHelp() {
101548
101708
  console.log(renderRootHelp());
101549
101709
  }
101550
101710
  async function main() {
101551
- const rawArgs = process5.argv.slice(2);
101711
+ const rawArgs = process6.argv.slice(2);
101552
101712
  const action = resolveRootAction(rawArgs);
101553
101713
  if (action.kind === "help") {
101554
101714
  showRootHelp();
@@ -101560,4 +101720,10 @@ async function main() {
101560
101720
  }
101561
101721
  await runMain(createSearchCommand(), { rawArgs: action.rawArgs });
101562
101722
  }
101563
- await main();
101723
+ try {
101724
+ await main();
101725
+ } catch (error49) {
101726
+ const message = error49 instanceof Error ? error49.message : String(error49);
101727
+ console.error(`ERROR: ${message}`);
101728
+ process6.exitCode = 1;
101729
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "findweb",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Google search CLI powered by system Chrome",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.11",