Skip to main content

oy_code_cli/
lib.rs

1use std::env;
2
3use clap::Parser;
4use oy_agent::Orchestrator;
5use oy_agent::infrastructure::agents::main_agent::MainAgent;
6use oy_agent::infrastructure::tools::edit::EditTool;
7use oy_agent::infrastructure::tools::read::ReadTool;
8use oy_agent::infrastructure::tools::write::WriteTool;
9use oy_agent::infrastructure::tools::{ToolRegistry, bash::BashTool};
10use oy_ai::{AiConfig, OpenCodeGoProvider};
11use serde::Deserialize;
12
13/// CLI arguments for oy-agent
14#[derive(Parser, Debug)]
15#[command(author, version, about)]
16pub struct CliArgs {
17    /// Prompt to send to the agent (if omitted, launches the TUI)
18    #[arg(short = 'p', long)]
19    pub prompt: Option<String>,
20
21    #[arg(short = 'm', long)]
22    pub model: Option<String>,
23}
24
25/// Configuration loaded from ~/.oy-ai-agent/config.toml
26#[derive(Debug, Deserialize, Default)]
27pub struct CliConfig {
28    pub api_key: Option<String>,
29    pub base_url: Option<String>,
30    pub model: Option<String>,
31}
32
33impl CliConfig {
34    /// Load config from ~/.oy-ai-agent/config.toml, returning defaults for missing fields.
35    pub fn load() -> Self {
36        let home = match dirs::home_dir() {
37            Some(h) => h,
38            None => return Self::default(),
39        };
40        let config_path = home.join(".oy-ai-agent").join("config.toml");
41        if !config_path.exists() {
42            return Self::default();
43        }
44        match std::fs::read_to_string(&config_path) {
45            Ok(content) => toml::from_str(&content).unwrap_or_default(),
46            Err(_) => Self::default(),
47        }
48    }
49}
50
51/// Build an `AiConfig` by merging CLI args, config file, env vars, and defaults.
52///
53/// Priority (highest first):
54///   1. CLI argument (`--model`)
55///   2. Config file (`~/.oy-ai-agent/config.toml`)
56///   3. Environment variable (`OPENROUTER_*`)
57///   4. Hardcoded default
58///
59/// `api_key` is required: if none of the sources provide it, the process exits.
60pub fn build_provider_config(cli_config: &CliConfig, cli_args: &CliArgs) -> AiConfig {
61    let api_key = cli_config
62        .api_key
63        .clone()
64        .or_else(|| env::var("OPENROUTER_API_KEY").ok())
65        .unwrap_or_else(|| {
66            eprintln!(
67                "OPENROUTER_API_KEY is not set. Set it in ~/.oy-ai-agent/config.toml \
68                 or the OPENROUTER_API_KEY environment variable."
69            );
70            std::process::exit(1);
71        });
72
73    let base_url = cli_config
74        .base_url
75        .clone()
76        .or_else(|| env::var("OPENROUTER_BASE_URL").ok())
77        .unwrap_or_else(|| "https://openrouter.ai/api/v1".to_string());
78
79    let model = cli_args
80        .model
81        .clone()
82        .or_else(|| cli_config.model.clone())
83        .or_else(|| env::var("OPENROUTER_MODEL").ok())
84        .unwrap_or_else(|| "anthropic/claude-haiku-4.5".to_string());
85
86    AiConfig::new(base_url, api_key, model)
87}
88
89/// Register the default set of tools (Read, Write, Bash).
90pub fn register_default_tools(registry: &mut ToolRegistry) {
91    registry.register(ReadTool);
92    registry.register(WriteTool);
93    registry.register(EditTool);
94    registry.register(BashTool);
95}
96
97/// Run the agent with the given CLI arguments, or launch the TUI if no prompt is given.
98pub async fn run(args: CliArgs) -> Result<(), anyhow::Error> {
99    // No prompt → launch TUI
100    if args.prompt.is_none() {
101        oy_tui::run_tui()
102            .await
103            .map_err(|e| anyhow::Error::msg(format!("{}", e)))?;
104        return Ok(());
105    }
106
107    let prompt = args.prompt.as_ref().unwrap();
108    let cli_config = CliConfig::load();
109    let ai_config = build_provider_config(&cli_config, &args);
110
111    eprintln!("url:{}", ai_config.base_url);
112    eprintln!(
113        "key:{}...",
114        &ai_config.api_key[..8.min(ai_config.api_key.len())]
115    );
116
117    let provider = OpenCodeGoProvider::new(ai_config);
118    let mut registry = ToolRegistry::new();
119    register_default_tools(&mut registry);
120
121    let main_agent = MainAgent::new_with_max_iterations(None);
122    let mut orchestrator = Orchestrator::new(main_agent, provider, registry);
123    orchestrator.init();
124    let result = orchestrator.execute(prompt).await?;
125    println!("{}", result);
126    Ok(())
127}