research-cli
Version:
AI-powered research assistant with web search capabilities and beautiful terminal UI
134 lines (133 loc) • 4.99 kB
JavaScript
/**
* Research command - Clipanion implementation
*
* Main research command that handles both default execution and explicit "research" subcommand
*/
import { Command, Option } from "clipanion";
import * as t from "typanion";
import { runQuery } from "./query.js";
export class ResearchCommand extends Command {
static paths = [
["research"], // explicit research command
Command.Default, // default command (no subcommand)
];
static usage = Command.Usage({
category: "Research",
description: "🔍 Research any topic using AI with web search capabilities",
details: `
Execute AI-powered research queries with optional web search integration.
Supports multiple LLM providers (OpenAI, Perplexity) and various output formats.
Results can be streamed in real-time or buffered for complete responses.
`,
examples: [
[
"Basic research query",
'$0 "What are the latest developments in quantum computing?"',
],
[
"Research with web search enabled",
'$0 "Latest AI research papers 2024" --web',
],
[
"Using specific provider and model",
'$0 "Climate change solutions" --provider openai --model gpt-4o',
],
[
"Save results to file",
'$0 "TypeScript best practices" --output report.md',
],
],
});
// Positional argument - the research query
query = Option.String({ required: false });
// Provider options
provider = Option.String("--provider,-p", "openai", {
description: "LLM provider to use",
validator: t.isOneOf([t.isLiteral("openai"), t.isLiteral("perplexity")]),
});
model = Option.String("--model,-m", {
description: "Model to use (e.g. o3, gpt-4o, sonar-pro)",
});
// Web search options
web = Option.Boolean("--web", false, {
description: "Enable hosted web search tool when provider supports it",
});
webSearchContextSize = Option.String("--web-search-context-size", "low", {
description: "Web search context size: low, medium, high",
validator: t.isOneOf([
t.isLiteral("low"),
t.isLiteral("medium"),
t.isLiteral("high"),
]),
});
// Output options
stream = Option.Boolean("--stream", true, {
description: "Stream response in real-time (use --no-stream to buffer)",
});
format = Option.String("--format", "md", {
description: "Output format",
validator: t.isOneOf([
t.isLiteral("md"),
t.isLiteral("json"),
t.isLiteral("jsonl"),
t.isLiteral("raw"),
]),
});
output = Option.String("--output,-o", {
description: "Write final answer to file",
});
// Generation options
maxTokens = Option.String("--max-tokens", {
description: "Maximum tokens to generate",
validator: t.isNumber(),
});
temperature = Option.String("--temperature", {
description: "Temperature for generation (0.0-2.0)",
validator: t.isNumber(),
});
timeout = Option.String("--timeout", {
description: "Request timeout in milliseconds",
validator: t.isNumber(),
});
// Debug options
dryRun = Option.Boolean("--dry-run", false, {
description: "Print request payload without calling provider",
});
verbose = Option.Boolean("--verbose,-v", false, {
description: "Enable verbose logging",
});
async execute() {
// Handle the case where no query is provided - show help
if (!this.query) {
this.context.stdout.write("Error: Research query is required.\n\n");
this.cli.process(["--help"]);
return 1;
}
try {
// Convert Clipanion options to CliOptions format
const cliOptions = {
provider: this.provider,
model: this.model,
web: this.web,
webSearchContextSize: this
.webSearchContextSize,
stream: this.stream,
format: this.format,
maxTokens: this.maxTokens ? Number(this.maxTokens) : undefined,
temperature: this.temperature ? Number(this.temperature) : undefined,
timeout: this.timeout ? Number(this.timeout) : undefined,
dryRun: this.dryRun,
output: this.output,
verbose: this.verbose,
};
// Execute the research query
await runQuery(this.query, cliOptions);
return 0;
}
catch (error) {
this.context.stderr.write(`Error: ${error instanceof Error ? error.message : "Unknown error occurred"}\n`);
return 1;
}
}
}
//# sourceMappingURL=research.js.map