/** * Web search CLI command handlers. * * Handles `omp q`/`omp web-search` subcommands for testing web search providers. */ import { APP_NAME } from "@oh-my-pi/pi-utils"; import chalk from "chalk"; import { initTheme, theme } from "../modes/theme/theme"; import { runSearchQuery, type SearchQueryParams } from "../web/search/index"; import { SEARCH_PROVIDER_ORDER } from "../web/search/provider"; import { renderSearchResult } from "../web/search/render"; import type { SearchProviderId } from "../web/search/types"; export interface SearchCommandArgs { query: string; provider?: SearchProviderId | "auto"; recency?: "day" | "week" | "month" | "year"; limit?: number; expanded: boolean; } const PROVIDERS: Array = ["auto", ...SEARCH_PROVIDER_ORDER]; const RECENCY_OPTIONS: SearchCommandArgs["recency"][] = ["day", "week", "month", "year"]; /** * Parse web search subcommand arguments. * Returns undefined if not a web search command. */ export function parseSearchArgs(args: string[]): SearchCommandArgs | undefined { if (args.length === 0 || (args[0] !== "q" && args[0] !== "web-search")) { return undefined; } const result: SearchCommandArgs = { query: "", expanded: true, }; const positional: string[] = []; for (let i = 1; i < args.length; i++) { const arg = args[i]; if (arg === "--provider") { result.provider = args[++i] as SearchCommandArgs["provider"]; } else if (arg === "--recency") { result.recency = args[++i] as SearchCommandArgs["recency"]; } else if (arg === "--limit" || arg === "-l") { result.limit = Number.parseInt(args[++i], 10); } else if (arg === "--compact") { result.expanded = false; } else if (!arg.startsWith("-")) { positional.push(arg); } } if (positional.length > 0) { result.query = positional.join(" "); } return result; } export async function runSearchCommand(cmd: SearchCommandArgs): Promise { if (!cmd.query) { process.stderr.write(`${chalk.red("Error: Query is required")}\n`); process.exit(1); } if (cmd.provider && !PROVIDERS.includes(cmd.provider)) { process.stderr.write(`${chalk.red(`Error: Unknown provider "${cmd.provider}"`)}\n`); process.stderr.write(`${chalk.dim(`Valid providers: ${PROVIDERS.join(", ")}`)}\n`); process.exit(1); } if (cmd.recency && !RECENCY_OPTIONS.includes(cmd.recency)) { process.stderr.write(`${chalk.red(`Error: Invalid recency "${cmd.recency}"`)}\n`); process.stderr.write(`${chalk.dim(`Valid recency values: ${RECENCY_OPTIONS.join(", ")}`)}\n`); process.exit(1); } if (cmd.limit !== undefined && Number.isNaN(cmd.limit)) { process.stderr.write(`${chalk.red("Error: --limit must be a number")}\n`); process.exit(1); } await initTheme(); const params: SearchQueryParams = { query: cmd.query, provider: cmd.provider, recency: cmd.recency, limit: cmd.limit, }; const result = await runSearchQuery(params); const component = renderSearchResult(result, { expanded: cmd.expanded, isPartial: false }, theme, { query: cmd.query, allowLongAnswer: true, maxAnswerLines: cmd.expanded ? undefined : 6, }); const width = Math.max(60, process.stdout.columns ?? 100); process.stdout.write(`${component.render(width).join("\n")}\n`); if (result.details?.error) { process.exitCode = 1; } } export function printSearchHelp(): void { process.stdout.write(`${chalk.bold(`${APP_NAME} q`)} - Test web search providers ${chalk.bold("Usage:")} ${APP_NAME} q [options] ${APP_NAME} web-search [options] ${chalk.bold("Arguments:")} query Search query text ${chalk.bold("Options:")} --provider Provider: ${PROVIDERS.join(", ")} --recency Recency filter (Brave/Perplexity): ${RECENCY_OPTIONS.join(", ")} -l, --limit Max results to return --compact Render condensed output -h, --help Show this help ${chalk.bold("Examples:")} ${APP_NAME} q --provider=exa "what's the color of the sky" ${APP_NAME} q --provider=brave --recency=week "latest TypeScript 5.7 changes" `); }