findweb 0.1.1 → 0.1.3

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 +4 -3
  2. package/dist/index.js +136 -48
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,7 +42,8 @@ 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
+ - 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`.
46
47
 
47
48
  In practice, the first search on a fresh profile behaves like this:
48
49
 
@@ -52,7 +53,7 @@ findweb "yc"
52
53
 
53
54
  1. detect missing prepared-profile marker
54
55
  2. open headed Chrome login flow
55
- 3. wait for you to sign in and close the browser
56
+ 3. wait for you to finish signing in
56
57
  4. continue the original search
57
58
 
58
59
  ## Options
@@ -97,7 +98,7 @@ You can trigger that ahead of time with:
97
98
  findweb login
98
99
  ```
99
100
 
100
- 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.
101
+ 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.
101
102
 
102
103
  ## Output
103
104
 
package/dist/index.js CHANGED
@@ -66932,9 +66932,10 @@ async function runMain(cmd, opts = {}) {
66932
66932
  }
66933
66933
 
66934
66934
  // src/search/browser.ts
66935
- import { existsSync as existsSync3 } from "fs";
66936
66935
  import { spawn as spawn3 } from "child_process";
66937
66936
  import http2 from "http";
66937
+ import os9 from "os";
66938
+ import path12 from "path";
66938
66939
 
66939
66940
  // node_modules/puppeteer-core/lib/esm/puppeteer/api/api.js
66940
66941
  init_Browser();
@@ -76307,13 +76308,19 @@ async function closeSearchBrowser(activeBrowser) {
76307
76308
  activeBrowser.chromeProcess.kill("SIGTERM");
76308
76309
  }
76309
76310
  }
76311
+ function defaultXdgDataHome() {
76312
+ const configured = process.env.XDG_DATA_HOME;
76313
+ if (configured && path12.isAbsolute(configured)) {
76314
+ return configured;
76315
+ }
76316
+ return path12.join(os9.homedir(), ".local", "share");
76317
+ }
76310
76318
  function defaultUserDataDir() {
76311
76319
  const configured = process.env.GOOGLE_SEARCH_USER_DATA_DIR;
76312
76320
  if (configured) {
76313
76321
  return configured;
76314
76322
  }
76315
- const legacyProfile = "/tmp/gsearch-manual-login-profile";
76316
- return ["/tmp/google-search-profile", legacyProfile].find((candidate) => existsSync3(candidate)) ?? "/tmp/google-search-profile";
76323
+ return path12.join(defaultXdgDataHome(), "findweb", "chrome-profile");
76317
76324
  }
76318
76325
 
76319
76326
  // src/search/page.ts
@@ -76472,12 +76479,59 @@ async function waitForIdle(page) {
76472
76479
  }
76473
76480
 
76474
76481
  // src/search/search.ts
76482
+ var LOGIN_POLL_MS = 250;
76483
+ function wait2(delayMs) {
76484
+ return new Promise((resolve6) => setTimeout(resolve6, delayMs));
76485
+ }
76486
+ function hostnameFromUrl(url) {
76487
+ try {
76488
+ return new URL(url).hostname.toLowerCase();
76489
+ } catch {
76490
+ return null;
76491
+ }
76492
+ }
76493
+ function isCompletedLoginUrl(url) {
76494
+ const hostname = hostnameFromUrl(url);
76495
+ return hostname !== null && hostname !== "accounts.google.com" && (hostname === "google.com" || hostname.endsWith(".google.com"));
76496
+ }
76497
+ function hasCompletedLoginPage(urls) {
76498
+ return urls.some((url) => isCompletedLoginUrl(url));
76499
+ }
76500
+ async function pageUrls(browser) {
76501
+ const pages = await browser.pages();
76502
+ return pages.map((page) => page.url());
76503
+ }
76504
+ async function waitForCompletedLogin(loginPage, browser) {
76505
+ let lastSeenUrls = [loginPage.url()];
76506
+ while (true) {
76507
+ if (browser.connected) {
76508
+ lastSeenUrls = await pageUrls(browser).catch(() => lastSeenUrls);
76509
+ if (hasCompletedLoginPage(lastSeenUrls)) {
76510
+ return;
76511
+ }
76512
+ }
76513
+ if (!browser.connected || loginPage.isClosed()) {
76514
+ if (hasCompletedLoginPage(lastSeenUrls)) {
76515
+ return;
76516
+ }
76517
+ throw new Error("Google login was not completed.");
76518
+ }
76519
+ await wait2(LOGIN_POLL_MS);
76520
+ }
76521
+ }
76475
76522
  function isSorryPage(url) {
76476
76523
  return url.includes("/sorry/");
76477
76524
  }
76478
76525
  function toErrorMessage(error) {
76479
76526
  return error instanceof Error ? error.message : String(error);
76480
76527
  }
76528
+ function isInterruptedLoginError(error, loginPage, browser) {
76529
+ if (!browser.connected || loginPage?.isClosed()) {
76530
+ return true;
76531
+ }
76532
+ const message = toErrorMessage(error);
76533
+ return message.includes("Navigating frame was detached") || message.includes("LifecycleWatcher disposed") || message.includes("Target closed") || message.includes("Session closed");
76534
+ }
76481
76535
  async function searchQuery(options) {
76482
76536
  const page = await options.browser.newPage();
76483
76537
  try {
@@ -76539,30 +76593,40 @@ async function searchQueriesInTabs(browser, blocker, queries, options) {
76539
76593
  return results;
76540
76594
  }
76541
76595
  async function runLoginSession(options) {
76542
- const loginPage = await options.browser.newPage();
76543
- await preparePage(loginPage, options.lang);
76544
- 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" });
76545
- await new Promise((resolve6) => {
76546
- const done = () => resolve6();
76547
- options.browser.once("disconnected", done);
76548
- process.once("SIGINT", async () => {
76549
- await options.browser.close().catch(() => {
76550
- return;
76551
- });
76552
- done();
76596
+ let loginPage = null;
76597
+ const handleSigint = () => {
76598
+ options.browser.close().catch(() => {
76599
+ return;
76553
76600
  });
76554
- });
76601
+ };
76602
+ process.once("SIGINT", handleSigint);
76603
+ try {
76604
+ loginPage = await options.browser.newPage();
76605
+ await preparePage(loginPage, options.lang);
76606
+ 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" });
76607
+ await waitForCompletedLogin(loginPage, options.browser);
76608
+ } catch (error) {
76609
+ if (isInterruptedLoginError(error, loginPage, options.browser)) {
76610
+ throw new Error("Google login was not completed.");
76611
+ }
76612
+ throw error;
76613
+ } finally {
76614
+ process.off("SIGINT", handleSigint);
76615
+ await loginPage?.close().catch(() => {
76616
+ return;
76617
+ });
76618
+ }
76555
76619
  }
76556
76620
 
76557
76621
  // src/cli/profile.ts
76558
76622
  import fs7 from "fs/promises";
76559
- import path12 from "path";
76623
+ import path13 from "path";
76560
76624
  var PROFILE_READY_MARKER = ".findweb-profile-ready";
76561
76625
  async function ensureProfileDir(dirPath) {
76562
76626
  await fs7.mkdir(dirPath, { recursive: true });
76563
76627
  }
76564
76628
  function readyMarkerPath(userDataDir) {
76565
- return path12.join(userDataDir, PROFILE_READY_MARKER);
76629
+ return path13.join(userDataDir, PROFILE_READY_MARKER);
76566
76630
  }
76567
76631
  async function hasPreparedProfile(userDataDir) {
76568
76632
  try {
@@ -76581,8 +76645,8 @@ async function markProfilePrepared(userDataDir) {
76581
76645
  // src/cli/flows/login.ts
76582
76646
  function printLoginInstructions(userDataDir) {
76583
76647
  console.log(`Login browser launched with profile: ${userDataDir}`);
76584
- console.log("Sign in to Google to prepare this profile for future searches.");
76585
- console.log("Close the browser window when you are done.");
76648
+ console.log("Complete Google sign-in to prepare this profile for future searches.");
76649
+ console.log("The session is saved automatically once sign-in is detected.");
76586
76650
  }
76587
76651
  async function runInteractiveLoginFlow(options) {
76588
76652
  await ensureProfileDir(options.userDataDir);
@@ -76615,7 +76679,7 @@ async function ensureInteractiveLogin(options) {
76615
76679
  }
76616
76680
 
76617
76681
  // src/cli/schema.ts
76618
- import path13 from "path";
76682
+ import path14 from "path";
76619
76683
 
76620
76684
  // node_modules/zod/v4/classic/external.js
76621
76685
  var exports_external = {};
@@ -77382,10 +77446,10 @@ function mergeDefs(...defs) {
77382
77446
  function cloneDef(schema) {
77383
77447
  return mergeDefs(schema._zod.def);
77384
77448
  }
77385
- function getElementAtPath(obj, path13) {
77386
- if (!path13)
77449
+ function getElementAtPath(obj, path14) {
77450
+ if (!path14)
77387
77451
  return obj;
77388
- return path13.reduce((acc, key) => acc?.[key], obj);
77452
+ return path14.reduce((acc, key) => acc?.[key], obj);
77389
77453
  }
77390
77454
  function promiseAllObject(promisesObj) {
77391
77455
  const keys = Object.keys(promisesObj);
@@ -77766,11 +77830,11 @@ function aborted(x, startIndex = 0) {
77766
77830
  }
77767
77831
  return false;
77768
77832
  }
77769
- function prefixIssues(path13, issues) {
77833
+ function prefixIssues(path14, issues) {
77770
77834
  return issues.map((iss) => {
77771
77835
  var _a4;
77772
77836
  (_a4 = iss).path ?? (_a4.path = []);
77773
- iss.path.unshift(path13);
77837
+ iss.path.unshift(path14);
77774
77838
  return iss;
77775
77839
  });
77776
77840
  }
@@ -77953,7 +78017,7 @@ function formatError(error, mapper = (issue2) => issue2.message) {
77953
78017
  }
77954
78018
  function treeifyError(error, mapper = (issue2) => issue2.message) {
77955
78019
  const result = { errors: [] };
77956
- const processError = (error2, path13 = []) => {
78020
+ const processError = (error2, path14 = []) => {
77957
78021
  var _a4, _b2;
77958
78022
  for (const issue2 of error2.issues) {
77959
78023
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -77963,7 +78027,7 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
77963
78027
  } else if (issue2.code === "invalid_element") {
77964
78028
  processError({ issues: issue2.issues }, issue2.path);
77965
78029
  } else {
77966
- const fullpath = [...path13, ...issue2.path];
78030
+ const fullpath = [...path14, ...issue2.path];
77967
78031
  if (fullpath.length === 0) {
77968
78032
  result.errors.push(mapper(issue2));
77969
78033
  continue;
@@ -77995,8 +78059,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
77995
78059
  }
77996
78060
  function toDotPath(_path) {
77997
78061
  const segs = [];
77998
- const path13 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
77999
- for (const seg of path13) {
78062
+ const path14 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
78063
+ for (const seg of path14) {
78000
78064
  if (typeof seg === "number")
78001
78065
  segs.push(`[${seg}]`);
78002
78066
  else if (typeof seg === "symbol")
@@ -89743,13 +89807,13 @@ function resolveRef(ref, ctx) {
89743
89807
  if (!ref.startsWith("#")) {
89744
89808
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
89745
89809
  }
89746
- const path13 = ref.slice(1).split("/").filter(Boolean);
89747
- if (path13.length === 0) {
89810
+ const path14 = ref.slice(1).split("/").filter(Boolean);
89811
+ if (path14.length === 0) {
89748
89812
  return ctx.rootSchema;
89749
89813
  }
89750
89814
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
89751
- if (path13[0] === defsKey) {
89752
- const key = path13[1];
89815
+ if (path14[0] === defsKey) {
89816
+ const key = path14[1];
89753
89817
  if (!key || !ctx.defs[key]) {
89754
89818
  throw new Error(`Reference not found: ${ref}`);
89755
89819
  }
@@ -90158,7 +90222,7 @@ var sharedSchema = exports_external.object({
90158
90222
  headed: exports_external.boolean().default(false),
90159
90223
  json: exports_external.boolean().default(false),
90160
90224
  lang: defaultedString("lang", "en"),
90161
- userDataDir: defaultedString("userDataDir", defaultUserDataDir()).transform((value) => path13.resolve(value))
90225
+ userDataDir: defaultedString("userDataDir", defaultUserDataDir()).transform((value) => path14.resolve(value))
90162
90226
  });
90163
90227
  var searchSchema = sharedSchema.extend({
90164
90228
  num: positiveInteger("num").default(3),
@@ -90222,6 +90286,10 @@ function createLoginArgs() {
90222
90286
  }
90223
90287
  };
90224
90288
  }
90289
+ function printCommandError(error48) {
90290
+ const message = error48 instanceof Error ? error48.message : String(error48);
90291
+ console.error(`ERROR: ${message}`);
90292
+ }
90225
90293
  async function runLogin(args) {
90226
90294
  const options = normalizeLoginOptions({
90227
90295
  gl: typeof args.gl === "string" ? args.gl : undefined,
@@ -90235,11 +90303,16 @@ function createLoginCommand(commandName = "findweb login") {
90235
90303
  meta: { name: commandName, description: "Open a reusable Google sign-in session." },
90236
90304
  args: createLoginArgs(),
90237
90305
  async run({ args }) {
90238
- await runLogin({
90239
- gl: args.gl,
90240
- lang: args.lang,
90241
- userDataDir: args.userDataDir
90242
- });
90306
+ try {
90307
+ await runLogin({
90308
+ gl: args.gl,
90309
+ lang: args.lang,
90310
+ userDataDir: args.userDataDir
90311
+ });
90312
+ } catch (error48) {
90313
+ printCommandError(error48);
90314
+ process.exitCode = 1;
90315
+ }
90243
90316
  }
90244
90317
  });
90245
90318
  }
@@ -90249,8 +90322,8 @@ import process4 from "process";
90249
90322
 
90250
90323
  // src/search/blocker.ts
90251
90324
  import fs8 from "fs/promises";
90252
- import os9 from "os";
90253
- import path14 from "path";
90325
+ import os10 from "os";
90326
+ import path15 from "path";
90254
90327
 
90255
90328
  // node_modules/tldts-core/dist/es6/src/domain.js
90256
90329
  function shareSameDomainSuffix(hostname3, vhost) {
@@ -99514,8 +99587,8 @@ class FilterEngine extends EventEmitter5 {
99514
99587
  if (caching === undefined) {
99515
99588
  return init();
99516
99589
  }
99517
- const { path: path14, read, write } = caching;
99518
- return read(path14).then((buffer) => this.deserialize(buffer)).catch(() => init().then((engine) => write(path14, engine.serialize()).then(() => engine)));
99590
+ const { path: path15, read, write } = caching;
99591
+ return read(path15).then((buffer) => this.deserialize(buffer)).catch(() => init().then((engine) => write(path15, engine.serialize()).then(() => engine)));
99519
99592
  }
99520
99593
  static empty(config3 = {}) {
99521
99594
  return new this({ config: config3 });
@@ -101307,13 +101380,13 @@ class PuppeteerBlocker extends FilterEngine {
101307
101380
  // src/search/blocker.ts
101308
101381
  var blockerPromise;
101309
101382
  function defaultCacheDir() {
101310
- return process.env.GOOGLE_SEARCH_CACHE_DIR ?? path14.join(os9.homedir(), ".cache", "google-search");
101383
+ return process.env.GOOGLE_SEARCH_CACHE_DIR ?? path15.join(os10.homedir(), ".cache", "google-search");
101311
101384
  }
101312
101385
  async function readCache(filePath) {
101313
101386
  return fs8.readFile(filePath);
101314
101387
  }
101315
101388
  async function writeCache(filePath, buffer) {
101316
- await fs8.mkdir(path14.dirname(filePath), { recursive: true });
101389
+ await fs8.mkdir(path15.dirname(filePath), { recursive: true });
101317
101390
  await fs8.writeFile(filePath, buffer);
101318
101391
  }
101319
101392
  async function loadBlocker() {
@@ -101322,7 +101395,7 @@ async function loadBlocker() {
101322
101395
  const cacheDir = defaultCacheDir();
101323
101396
  await fs8.mkdir(cacheDir, { recursive: true });
101324
101397
  return PuppeteerBlocker.fromPrebuiltAdsAndTracking(globalThis.fetch.bind(globalThis), {
101325
- path: path14.join(cacheDir, "ghostery-engine.bin"),
101398
+ path: path15.join(cacheDir, "ghostery-engine.bin"),
101326
101399
  read: readCache,
101327
101400
  write: writeCache
101328
101401
  });
@@ -101425,6 +101498,10 @@ var searchArgs = {
101425
101498
  description: "Print JSON output"
101426
101499
  }
101427
101500
  };
101501
+ function printCommandError2(error49) {
101502
+ const message = error49 instanceof Error ? error49.message : String(error49);
101503
+ console.error(`ERROR: ${message}`);
101504
+ }
101428
101505
  async function runSearch(args) {
101429
101506
  const options = normalizeSearchOptions({
101430
101507
  _: args._,
@@ -101473,7 +101550,12 @@ function createSearchCommand(commandName = "findweb") {
101473
101550
  },
101474
101551
  args: searchArgs,
101475
101552
  async run({ args }) {
101476
- await runSearch(args);
101553
+ try {
101554
+ await runSearch(args);
101555
+ } catch (error49) {
101556
+ printCommandError2(error49);
101557
+ process4.exitCode = 1;
101558
+ }
101477
101559
  }
101478
101560
  });
101479
101561
  }
@@ -101553,4 +101635,10 @@ async function main() {
101553
101635
  }
101554
101636
  await runMain(createSearchCommand(), { rawArgs: action.rawArgs });
101555
101637
  }
101556
- await main();
101638
+ try {
101639
+ await main();
101640
+ } catch (error49) {
101641
+ const message = error49 instanceof Error ? error49.message : String(error49);
101642
+ console.error(`ERROR: ${message}`);
101643
+ process5.exitCode = 1;
101644
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "findweb",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Google search CLI powered by system Chrome",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.11",