diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca5b825..70d7df8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,3 +58,16 @@ jobs: - name: Check build run: cargo check --release + + - name: Test verbose mode + run: | + # Build the CLI first + cargo build --release + + # Test verbose mode with a simple search + ./target/release/google-patent-cli search --query "test" --limit 1 --verbose || { + echo "Verbose mode test failed" + exit 1 + } + + echo "Verbose mode test passed" diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 6fe60d2..299ff1f 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -57,6 +57,10 @@ pub struct SearchArgs { #[arg(long, default_value_t = false)] pub debug: bool, + /// Enable verbose output (shows detailed progress) + #[arg(long, default_value_t = false)] + pub verbose: bool, + /// Language/locale for patent pages (e.g., ja, en, zh) #[arg(long)] pub language: Option, @@ -79,6 +83,10 @@ pub struct FetchArgs { #[arg(long, default_value_t = false)] pub debug: bool, + /// Enable verbose output (shows detailed progress) + #[arg(long, default_value_t = false)] + pub verbose: bool, + /// Language/locale for patent pages (e.g., ja, en, zh) #[arg(long)] pub language: Option, @@ -133,7 +141,9 @@ pub async fn run_app(cli: Cli) -> Result<()> { } let config = Config::load()?; - let searcher = PatentSearcher::new(config.browser_path, !args.head, args.debug).await?; + let searcher = + PatentSearcher::new(config.browser_path, !args.head, args.debug, args.verbose) + .await?; let options = SearchOptions { query: args.query, @@ -152,7 +162,9 @@ pub async fn run_app(cli: Cli) -> Result<()> { } Commands::Fetch { args } => { let config = Config::load()?; - let searcher = PatentSearcher::new(config.browser_path, !args.head, args.debug).await?; + let searcher = + PatentSearcher::new(config.browser_path, !args.head, args.debug, args.verbose) + .await?; if args.raw { let html = searcher.get_raw_html(&args.patent_id, args.language.as_deref()).await?; diff --git a/src/core/patent_search.rs b/src/core/patent_search.rs index fce5f07..a306154 100644 --- a/src/core/patent_search.rs +++ b/src/core/patent_search.rs @@ -11,6 +11,7 @@ pub trait PatentSearch: Send + Sync { pub struct PatentSearcher { browser_manager: BrowserManager, + verbose: bool, } #[async_trait] @@ -57,10 +58,11 @@ impl PatentSearcher { browser_path: Option, headless: bool, debug: bool, + verbose: bool, ) -> Result { let browser_manager = BrowserManager::new(browser_path, headless, debug); - Ok(Self { browser_manager }) + Ok(Self { browser_manager, verbose }) } async fn search_internal(&self, options: &SearchOptions) -> Result { @@ -70,10 +72,20 @@ impl PatentSearcher { let base_url = options.to_url()?; + if self.verbose { + eprintln!("Search URL: {}", base_url); + } + if let Some(patent_number) = &options.patent_number { // Single patent lookup - no pagination needed + if self.verbose { + eprintln!("Fetching single patent: {}", patent_number); + } page.goto(&base_url).await?; + if self.verbose { + eprintln!("Waiting for page to load..."); + } // Wait for meta description or title tag to ensure page is loaded let loaded = page .wait_for_element("meta[name='description'], meta[name='DC.title']", 15) @@ -92,6 +104,9 @@ impl PatentSearcher { // Give a little time for all dynamic content (like claims) to fully render tokio::time::sleep(std::time::Duration::from_millis(1000)).await; + if self.verbose { + eprintln!("Extracting patent data..."); + } // Single patent page - extract structured data let result = page.evaluate(include_str!("scripts/extract_patent.js")).await?; @@ -113,6 +128,10 @@ impl PatentSearcher { let mut top_assignees: Option> = None; let mut top_cpcs: Option> = None; + if self.verbose { + eprintln!("Fetching search results (limit: {})...", limit); + } + // Append num=100 to base_url to fetch more results per page if needed // This reduces the need for multiple page loads for limits <= 100 let base_url = if limit > 10 { format!("{}&num=100", base_url) } else { base_url }; @@ -132,16 +151,27 @@ impl PatentSearcher { format!("{}&page={}", base_url, page_num) }; + if self.verbose { + eprintln!("Loading page {} of {}...", page_num + 1, pages_needed); + eprintln!("URL: {}", page_url); + } + page.goto(&page_url).await?; // Wait for results to load let loaded = page.wait_for_element(".search-result-item", 15).await?; if !loaded { // No results on this page, stop pagination + if self.verbose { + eprintln!("No results found on this page, stopping pagination."); + } let _ = page.close().await; break; } + if self.verbose { + eprintln!("Extracting search results from page..."); + } let results = page.evaluate(include_str!("scripts/extract_search_results.js")).await?; @@ -149,7 +179,10 @@ impl PatentSearcher { // Only capture total results and summary data from the first page if page_num == 0 { - total_results_str = sr.total_results; + total_results_str = sr.total_results.clone(); + if self.verbose { + eprintln!("Total results found: {}", total_results_str); + } top_assignees = sr.top_assignees; // Two-step CPC extraction: click CPCs tab and wait for DOM update @@ -162,6 +195,10 @@ impl PatentSearcher { let page_patents = sr.patents; + if self.verbose { + eprintln!("Found {} patents on this page", page_patents.len()); + } + // If we got no results, stop pagination if page_patents.is_empty() { break; @@ -176,8 +213,15 @@ impl PatentSearcher { } let _ = page.close().await; + if self.verbose { + eprintln!("Total patents collected: {}", all_patents.len()); + } + // Truncate to exact limit if all_patents.len() > limit { + if self.verbose { + eprintln!("Truncating to limit: {}", limit); + } all_patents.truncate(limit); } diff --git a/src/mcp/mod.rs b/src/mcp/mod.rs index 26b37ab..81b7da3 100644 --- a/src/mcp/mod.rs +++ b/src/mcp/mod.rs @@ -154,7 +154,7 @@ impl ServerHandler for PatentHandler { /// Run the MCP server over stdio pub async fn run() -> anyhow::Result<()> { let config = Config::load()?; - let searcher = PatentSearcher::new(config.browser_path, true, false) + let searcher = PatentSearcher::new(config.browser_path, true, false, false) .await .map_err(|e| anyhow::anyhow!("Failed to create PatentSearcher: {}", e))?; let handler = PatentHandler::new(Arc::new(searcher));