import { parseArgs } from "@std/cli/parse-args"; import type { Args } from "@std/cli/parse-args"; import { ensureDir } from "@std/fs"; import { parse, stringify } from "@std/yaml"; import { answerQuery, cleanTextFromHtml, getNewsUrls } from "./websearch.ts"; export interface Config { model?: string; search_url?: string; } const parsed: Args = parseArgs(Deno.args, { boolean: ["help"], string: ["model", "search_url"], }); if (parsed.help) { console.log(` Usage: websearch [--model MODEL] [--search_url SEARCH_URL] QUERY Options: --model Ollama model to use --search_url URL for SearXNG endpoint Arguments: QUERY The topic to search for news to summarize Configuration: The websearch configuration file is stored in the XDG Config directory /home/$USER/.config/websearch/config.yml. Both the model and search_url can be customized as an alternative to providing the options for each. If both the Ollama model and SearXNG endpoint are successful, the configuration is automatically saved/updated. `); Deno.exit(); } const configDir = `${Deno.env.get("HOME")}/.config/websearch`; const configPath = `${Deno.env.get("HOME")}/.config/websearch/config.yml`; async function loadConfig(): Promise { try { const yamlString = await Deno.readTextFile(configPath); return parse(yamlString) as Config; } catch { return {}; } } async function saveConfig(config: Config) { await ensureDir(configDir); await Deno.writeTextFile( configPath, stringify(config), ); } async function main(args: Args) { const query = args._.join(" "); if (!query) { throw new Error("Please provide a search query"); } console.log(`Query: ${query}`); try { const config = await loadConfig(); if (!config.model) { if (args.model) { config.model = args.model; } else { throw new Error("Provide --model or add Ollama model to configuration"); } } else if (args.model && args.model !== config.model) { config.model = args.model; } if (!config.search_url) { if (args.search_url) { config.search_url = args.search_url; } else { throw new Error( "Provide --search_url or add search_url to configuration", ); } } else if (args.search_url && args.search_url !== config.search_url) { config.search_url = args.search_url; } const urls = await getNewsUrls(config, query); if (!urls || urls.length === 0) { console.log("No results"); Deno.exit(1); } const cleanedTexts = await Promise.all( urls.map((url) => cleanTextFromHtml(url)), ); await answerQuery(config, query, cleanedTexts.join("\n\n")); await saveConfig(config); } catch (error: any) { console.error(`Error processing query "${query}":`, error.message); Deno.exit(1); } } if (import.meta.main) { main(parsed).catch((error) => { console.error("Unhandled exception:", error); Deno.exit(1); }); }