import { DuckDuckGoSearcher } from './search.js';
import { GitHubCodeSearcher } from './github-code-search.js';
import { ContentExtractor } from './extractor.js';
import { CLIInterface } from './cli.js';
import { config } from './config.js';
import type { SearchResultsPage, ExtractedContent, Searcher, GitHubCodeSearchOptions } from './types.js';
// Export the interfaces and classes for public use
export type { Searcher, SearchResultsPage, SearchStatistics, GitHubCodeSearchOptions } from './types.js';
export { DuckDuckGoSearcher } from './search.js';
export { GitHubCodeSearcher } from './github-code-search.js';
export { RatelimitException, DuckDuckGoSearchException, GitHubSearchException, GitHubAuthException } from './types.js';
export class LLMResearcher {
public searcher: Searcher;
public extractor: ContentExtractor;
public cli: CLIInterface;
constructor(searcher?: Searcher) {
this.searcher = searcher || new DuckDuckGoSearcher();
this.extractor = new ContentExtractor();
this.cli = new CLIInterface(this);
}
// Create a new instance with GitHub Code searcher
static withGitHubCodeSearch(options?: Partial<GitHubCodeSearchOptions>): LLMResearcher {
return new LLMResearcher(new GitHubCodeSearcher(options));
}
// Switch to GitHub Code searcher
useGitHubCodeSearch(options?: Partial<GitHubCodeSearchOptions>): void {
this.searcher = new GitHubCodeSearcher(options);
}
// Switch to DuckDuckGo searcher
useDuckDuckGoSearch(): void {
this.searcher = new DuckDuckGoSearcher();
}
// Get the current searcher type
getSearcherType(): string {
if (this.searcher instanceof GitHubCodeSearcher) {
return 'GitHub Code Search';
} else if (this.searcher instanceof DuckDuckGoSearcher) {
return 'DuckDuckGo';
}
return 'Unknown';
}
async search(query: string, nextToken?: string, options?: import('./types.js').SearchOptions): Promise<SearchResultsPage> {
config.log(`Starting search for: ${query}`);
return await this.searcher.search(query, nextToken, options);
}
async extractFromUrl(url: string, displayOutput = true): Promise<ExtractedContent> {
config.log(`Extracting content from: ${url}`);
try {
const content = await this.extractor.extract(url);
if (displayOutput) {
this.cli.displayContent(content);
// Only start interactive commands if we're in interactive mode
if (process.stdin.isTTY) {
await this.cli.handleContentCommands(content);
}
}
return content;
} catch (error) {
const message = `Failed to extract content from ${url}: ${(error as Error).message}`;
if (displayOutput) {
console.error(message);
}
throw new Error(message);
}
}
async interactive(): Promise<void> {
await this.cli.interactive();
await this.cleanup();
}
async cleanup(): Promise<void> {
config.log('Cleaning up resources...');
await this.extractor.close();
}
async searchAndDisplay(query: string): Promise<void> {
try {
const searchPage = await this.search(query);
if (searchPage.results.length === 0) {
console.log('No results found.');
return;
}
this.cli.currentResults = searchPage.results;
this.cli.currentPage = searchPage;
this.cli.displayResults(searchPage);
await this.cli.handleResultSelection();
} catch (error) {
console.error('Search failed:', (error as Error).message);
} finally {
await this.cleanup();
}
}
}
// Handle process termination
process.on('SIGINT', async () => {
console.log('\nShutting down...');
process.exit(0);
});
process.on('SIGTERM', async () => {
console.log('\nShutting down...');
process.exit(0);
});